├── .config
└── dotnet-tools.json
├── .devcontainer
├── Dockerfile
└── devcontainer.json
├── .editorconfig
├── .gitattributes
├── .github
└── dependabot.yml
├── .gitignore
├── .prettierrc.yaml
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── CONTRIBUTING.md
├── CSharpIsNullAnalyzer.sln
├── Directory.Build.props
├── Directory.Build.rsp
├── Directory.Build.targets
├── Directory.Packages.props
├── LICENSE
├── README.md
├── azure-pipelines.yml
├── azure-pipelines
├── Get-ArtifactsStagingDirectory.ps1
├── Get-CodeCovTool.ps1
├── Get-NuGetTool.ps1
├── Get-ProcDump.ps1
├── Get-SymbolFiles.ps1
├── Get-TempToolsPath.ps1
├── Merge-CodeCoverage.ps1
├── artifacts
│ ├── Variables.ps1
│ ├── _all.ps1
│ ├── _pipelines.ps1
│ ├── _stage_all.ps1
│ ├── build_logs.ps1
│ ├── coverageResults.ps1
│ ├── deployables.ps1
│ ├── projectAssetsJson.ps1
│ ├── symbols.ps1
│ ├── testResults.ps1
│ └── test_symbols.ps1
├── build.yml
├── dotnet-test-cloud.ps1
├── dotnet.yml
├── install-dependencies.yml
├── justnugetorg.nuget.config
├── publish-CodeCov.ps1
├── publish-codecoverage.yml
├── publish-deployables.yml
├── publish-symbols.yml
├── release.yml
├── test.runsettings
└── variables
│ ├── DotNetSdkVersion.ps1
│ ├── _all.ps1
│ └── _pipelines.ps1
├── doc
└── analyzers
│ ├── CSIsNull001.md
│ └── CSIsNull002.md
├── global.json
├── init.cmd
├── init.ps1
├── nuget.config
├── settings.VisualStudio.json
├── src
├── .editorconfig
├── AssemblyInfo.cs
├── AssemblyInfo.vb
├── CSharpIsNullAnalyzer.CodeFixes
│ ├── AssemblyInfo.cs
│ ├── CSIsNull001Fixer.cs
│ ├── CSIsNull002Fixer.cs
│ ├── CSharpIsNullAnalyzer.CodeFixes.csproj
│ ├── IsFixer.cs
│ ├── Strings.Designer.cs
│ ├── Strings.resx
│ └── tools
│ │ ├── install.ps1
│ │ └── uninstall.ps1
├── CSharpIsNullAnalyzer
│ ├── AnalyzerReleases.Shipped.md
│ ├── AnalyzerReleases.Unshipped.md
│ ├── AssemblyInfo.cs
│ ├── CSIsNull001.cs
│ ├── CSIsNull002.cs
│ ├── CSharpIsNullAnalyzer.csproj
│ ├── IOperationExtensions.cs
│ ├── Strings.Designer.cs
│ ├── Strings.resx
│ ├── Utils.cs
│ └── WellKnownTypeNames.cs
├── Directory.Build.props
└── Directory.Build.targets
├── strongname.snk
├── stylecop.json
├── test
├── .editorconfig
├── CSharpIsNullAnalyzer.Tests
│ ├── CSIsNull001Tests.cs
│ ├── CSIsNull002Tests.cs
│ ├── CSharpIsNullAnalyzer.Tests.csproj
│ ├── Helpers
│ │ ├── CSharpCodeFixVerifier`2+Test.cs
│ │ ├── CSharpCodeFixVerifier`2.cs
│ │ └── ReferencesHelper.cs
│ └── app.config
├── Directory.Build.props
└── Directory.Build.targets
├── tools
├── Check-DotNetRuntime.ps1
├── Check-DotNetSdk.ps1
├── Install-DotNetSdk.ps1
├── Install-NuGetCredProvider.ps1
└── Set-EnvVars.ps1
└── version.json
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "powershell": {
6 | "version": "7.4.3",
7 | "commands": [
8 | "pwsh"
9 | ]
10 | },
11 | "dotnet-coverage": {
12 | "version": "17.11.3",
13 | "commands": [
14 | "dotnet-coverage"
15 | ]
16 | },
17 | "nbgv": {
18 | "version": "3.6.139",
19 | "commands": [
20 | "nbgv"
21 | ]
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | # Refer to https://hub.docker.com/_/microsoft-dotnet-sdk for available versions
2 | FROM mcr.microsoft.com/dotnet/sdk:8.0.300-jammy
3 |
4 | # Installing mono makes `dotnet test` work without errors even for net472.
5 | # But installing it takes a long time, so it's excluded by default.
6 | #RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
7 | #RUN echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" | tee /etc/apt/sources.list.d/mono-official-stable.list
8 | #RUN apt-get update
9 | #RUN DEBIAN_FRONTEND=noninteractive apt-get install -y mono-devel
10 |
11 | # Clear the NUGET_XMLDOC_MODE env var so xml api doc files get unpacked, allowing a rich experience in Intellisense.
12 | # See https://github.com/dotnet/dotnet-docker/issues/2790 for a discussion on this, where the prioritized use case
13 | # was *not* devcontainers, sadly.
14 | ENV NUGET_XMLDOC_MODE=
15 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Dev space",
3 | "dockerFile": "Dockerfile",
4 | "settings": {
5 | "terminal.integrated.shell.linux": "/usr/bin/pwsh"
6 | },
7 | "postCreateCommand": "./init.ps1 -InstallLocality machine",
8 | "extensions": [
9 | "ms-azure-devops.azure-pipelines",
10 | "ms-dotnettools.csharp",
11 | "k--kato.docomment",
12 | "editorconfig.editorconfig",
13 | "pflannery.vscode-versionlens",
14 | "davidanson.vscode-markdownlint",
15 | "dotjoshjohnson.xml",
16 | "ms-vscode-remote.remote-containers",
17 | "ms-azuretools.vscode-docker",
18 | "ms-vscode.powershell"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome:http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Don't use tabs for indentation.
7 | [*]
8 | indent_style = space
9 |
10 | # (Please don't specify an indent_size here; that has too many unintended consequences.)
11 |
12 | [*.yml]
13 | indent_size = 2
14 | indent_style = space
15 |
16 | # Code files
17 | [*.{cs,csx,vb,vbx,h,cpp,idl}]
18 | indent_size = 4
19 | insert_final_newline = true
20 | trim_trailing_whitespace = true
21 |
22 | # MSBuild project files
23 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,msbuildproj,props,targets}]
24 | indent_size = 2
25 |
26 | # Xml config files
27 | [*.{ruleset,config,nuspec,resx,vsixmanifest,vsct,runsettings}]
28 | indent_size = 2
29 | indent_style = space
30 |
31 | # JSON files
32 | [*.json]
33 | indent_size = 2
34 | indent_style = space
35 |
36 | [*.ps1]
37 | indent_style = space
38 | indent_size = 4
39 |
40 | # Dotnet code style settings:
41 | [*.{cs,vb}]
42 | # Sort using and Import directives with System.* appearing first
43 | dotnet_sort_system_directives_first = true
44 | dotnet_separate_import_directive_groups = false
45 | dotnet_style_qualification_for_field = true:warning
46 | dotnet_style_qualification_for_property = true:warning
47 | dotnet_style_qualification_for_method = true:warning
48 | dotnet_style_qualification_for_event = true:warning
49 |
50 | # Use language keywords instead of framework type names for type references
51 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
52 | dotnet_style_predefined_type_for_member_access = true:suggestion
53 |
54 | # Suggest more modern language features when available
55 | dotnet_style_object_initializer = true:suggestion
56 | dotnet_style_collection_initializer = true:suggestion
57 | dotnet_style_coalesce_expression = true:suggestion
58 | dotnet_style_null_propagation = true:suggestion
59 | dotnet_style_explicit_tuple_names = true:suggestion
60 |
61 | # Non-private static fields are PascalCase
62 | dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
63 | dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
64 | dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
65 |
66 | dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
67 | dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected
68 | dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
69 |
70 | dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
71 |
72 | # Constants are PascalCase
73 | dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
74 | dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
75 | dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
76 |
77 | dotnet_naming_symbols.constants.applicable_kinds = field, local
78 | dotnet_naming_symbols.constants.required_modifiers = const
79 |
80 | dotnet_naming_style.constant_style.capitalization = pascal_case
81 |
82 | # Static fields are camelCase
83 | dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion
84 | dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
85 | dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
86 |
87 | dotnet_naming_symbols.static_fields.applicable_kinds = field
88 | dotnet_naming_symbols.static_fields.required_modifiers = static
89 |
90 | dotnet_naming_style.static_field_style.capitalization = camel_case
91 |
92 | # Instance fields are camelCase
93 | dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
94 | dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
95 | dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
96 |
97 | dotnet_naming_symbols.instance_fields.applicable_kinds = field
98 |
99 | dotnet_naming_style.instance_field_style.capitalization = camel_case
100 |
101 | # Locals and parameters are camelCase
102 | dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
103 | dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
104 | dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
105 |
106 | dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
107 |
108 | dotnet_naming_style.camel_case_style.capitalization = camel_case
109 |
110 | # Local functions are PascalCase
111 | dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
112 | dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
113 | dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
114 |
115 | dotnet_naming_symbols.local_functions.applicable_kinds = local_function
116 |
117 | dotnet_naming_style.local_function_style.capitalization = pascal_case
118 |
119 | # By default, name items with PascalCase
120 | dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
121 | dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
122 | dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
123 |
124 | dotnet_naming_symbols.all_members.applicable_kinds = *
125 |
126 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case
127 |
128 | # CSharp code style settings:
129 | [*.cs]
130 | # Indentation preferences
131 | csharp_indent_block_contents = true
132 | csharp_indent_braces = false
133 | csharp_indent_case_contents = true
134 | csharp_indent_switch_labels = true
135 | csharp_indent_labels = flush_left
136 |
137 | # Prefer "var" everywhere
138 | csharp_style_var_for_built_in_types = false
139 | csharp_style_var_when_type_is_apparent = true:suggestion
140 | csharp_style_var_elsewhere = false:warning
141 |
142 | # Prefer method-like constructs to have a block body
143 | csharp_style_expression_bodied_methods = false:none
144 | csharp_style_expression_bodied_constructors = false:none
145 | csharp_style_expression_bodied_operators = false:none
146 |
147 | # Prefer property-like constructs to have an expression-body
148 | csharp_style_expression_bodied_properties = true:none
149 | csharp_style_expression_bodied_indexers = true:none
150 | csharp_style_expression_bodied_accessors = true:none
151 |
152 | # Suggest more modern language features when available
153 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
154 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
155 | csharp_style_inlined_variable_declaration = true:suggestion
156 | csharp_style_throw_expression = true:suggestion
157 | csharp_style_conditional_delegate_call = true:suggestion
158 |
159 | # Newline settings
160 | csharp_new_line_before_open_brace = all
161 | csharp_new_line_before_else = true
162 | csharp_new_line_before_catch = true
163 | csharp_new_line_before_finally = true
164 | csharp_new_line_before_members_in_object_initializers = true
165 | csharp_new_line_before_members_in_anonymous_types = true
166 |
167 | # Blocks are allowed
168 | csharp_prefer_braces = true:silent
169 |
170 | # SA1130: Use lambda syntax
171 | dotnet_diagnostic.SA1130.severity = silent
172 |
173 | # IDE1006: Naming Styles - StyleCop handles these for us
174 | dotnet_diagnostic.IDE1006.severity = none
175 |
176 | dotnet_diagnostic.DOC100.severity = silent
177 | dotnet_diagnostic.DOC104.severity = warning
178 | dotnet_diagnostic.DOC105.severity = warning
179 | dotnet_diagnostic.DOC106.severity = warning
180 | dotnet_diagnostic.DOC107.severity = warning
181 | dotnet_diagnostic.DOC108.severity = warning
182 | dotnet_diagnostic.DOC200.severity = warning
183 | dotnet_diagnostic.DOC202.severity = warning
184 |
185 | # CA1062: Validate arguments of public methods
186 | dotnet_diagnostic.CA1062.severity = warning
187 |
188 | [*.sln]
189 | indent_style = tab
190 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | # Ensure shell scripts use LF line endings (linux only accepts LF)
7 | *.sh eol=lf
8 | *.ps1 eol=lf
9 |
10 | # The macOS codesign tool is extremely picky, and requires LF line endings.
11 | *.plist eol=lf
12 |
13 | ###############################################################################
14 | # Set default behavior for command prompt diff.
15 | #
16 | # This is need for earlier builds of msysgit that does not have it on by
17 | # default for csharp files.
18 | # Note: This is only used by command line
19 | ###############################################################################
20 | #*.cs diff=csharp
21 |
22 | ###############################################################################
23 | # Set the merge driver for project and solution files
24 | #
25 | # Merging from the command prompt will add diff markers to the files if there
26 | # are conflicts (Merging from VS is not affected by the settings below, in VS
27 | # the diff markers are never inserted). Diff markers may cause the following
28 | # file extensions to fail to load in VS. An alternative would be to treat
29 | # these files as binary and thus will always conflict and require user
30 | # intervention with every merge. To do so, just uncomment the entries below
31 | ###############################################################################
32 | #*.sln merge=binary
33 | #*.csproj merge=binary
34 | #*.vbproj merge=binary
35 | #*.vcxproj merge=binary
36 | #*.vcproj merge=binary
37 | #*.dbproj merge=binary
38 | #*.fsproj merge=binary
39 | #*.lsproj merge=binary
40 | #*.wixproj merge=binary
41 | #*.modelproj merge=binary
42 | #*.sqlproj merge=binary
43 | #*.wwaproj merge=binary
44 |
45 | ###############################################################################
46 | # behavior for image files
47 | #
48 | # image files are treated as binary by default.
49 | ###############################################################################
50 | #*.jpg binary
51 | #*.png binary
52 | #*.gif binary
53 |
54 | ###############################################################################
55 | # diff behavior for common document formats
56 | #
57 | # Convert binary document formats to text before diffing them. This feature
58 | # is only available from the command line. Turn it on by uncommenting the
59 | # entries below.
60 | ###############################################################################
61 | #*.doc diff=astextplain
62 | #*.DOC diff=astextplain
63 | #*.docx diff=astextplain
64 | #*.DOCX diff=astextplain
65 | #*.dot diff=astextplain
66 | #*.DOT diff=astextplain
67 | #*.pdf diff=astextplain
68 | #*.PDF diff=astextplain
69 | #*.rtf diff=astextplain
70 | #*.RTF diff=astextplain
71 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Please see the documentation for all configuration options:
2 | # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
3 |
4 | version: 2
5 | updates:
6 | - package-ecosystem: nuget
7 | directory: /
8 | schedule:
9 | interval: weekly
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 | *.lutconfig
13 | launchSettings.json
14 |
15 | # User-specific files (MonoDevelop/Xamarin Studio)
16 | *.userprefs
17 |
18 | # Mono auto generated files
19 | mono_crash.*
20 |
21 | # Build results
22 | [Dd]ebug/
23 | [Dd]ebugPublic/
24 | [Rr]elease/
25 | [Rr]eleases/
26 | x64/
27 | x86/
28 | [Aa][Rr][Mm]/
29 | [Aa][Rr][Mm]64/
30 | bld/
31 | [Bb]in/
32 | [Oo]bj/
33 | [Ll]og/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # StyleCop
61 | StyleCopReport.xml
62 |
63 | # Files built by Visual Studio
64 | *_i.c
65 | *_p.c
66 | *_h.h
67 | *.ilk
68 | *.meta
69 | *.obj
70 | *.iobj
71 | *.pch
72 | *.pdb
73 | *.ipdb
74 | *.pgc
75 | *.pgd
76 | *.rsp
77 | !Directory.Build.rsp
78 | *.sbr
79 | *.tlb
80 | *.tli
81 | *.tlh
82 | *.tmp
83 | *.tmp_proj
84 | *_wpftmp.csproj
85 | *.log
86 | *.vspscc
87 | *.vssscc
88 | .builds
89 | *.pidb
90 | *.svclog
91 | *.scc
92 |
93 | # Chutzpah Test files
94 | _Chutzpah*
95 |
96 | # Visual C++ cache files
97 | ipch/
98 | *.aps
99 | *.ncb
100 | *.opendb
101 | *.opensdf
102 | *.sdf
103 | *.cachefile
104 | *.VC.db
105 | *.VC.VC.opendb
106 |
107 | # Visual Studio profiler
108 | *.psess
109 | *.vsp
110 | *.vspx
111 | *.sap
112 |
113 | # Visual Studio Trace Files
114 | *.e2e
115 |
116 | # TFS 2012 Local Workspace
117 | $tf/
118 |
119 | # Guidance Automation Toolkit
120 | *.gpState
121 |
122 | # ReSharper is a .NET coding add-in
123 | _ReSharper*/
124 | *.[Rr]e[Ss]harper
125 | *.DotSettings.user
126 |
127 | # JustCode is a .NET coding add-in
128 | .JustCode
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 | /coveragereport/
144 |
145 | # NCrunch
146 | _NCrunch_*
147 | .*crunch*.local.xml
148 | nCrunchTemp_*
149 |
150 | # MightyMoose
151 | *.mm.*
152 | AutoTest.Net/
153 |
154 | # Web workbench (sass)
155 | .sass-cache/
156 |
157 | # Installshield output folder
158 | [Ee]xpress/
159 |
160 | # DocProject is a documentation generator add-in
161 | DocProject/buildhelp/
162 | DocProject/Help/*.HxT
163 | DocProject/Help/*.HxC
164 | DocProject/Help/*.hhc
165 | DocProject/Help/*.hhk
166 | DocProject/Help/*.hhp
167 | DocProject/Help/Html2
168 | DocProject/Help/html
169 |
170 | # Click-Once directory
171 | publish/
172 |
173 | # Publish Web Output
174 | *.[Pp]ublish.xml
175 | *.azurePubxml
176 | # Note: Comment the next line if you want to checkin your web deploy settings,
177 | # but database connection strings (with potential passwords) will be unencrypted
178 | *.pubxml
179 | *.publishproj
180 |
181 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
182 | # checkin your Azure Web App publish settings, but sensitive information contained
183 | # in these scripts will be unencrypted
184 | PublishScripts/
185 |
186 | # NuGet Packages
187 | *.nupkg
188 | # NuGet Symbol Packages
189 | *.snupkg
190 | # The packages folder can be ignored because of Package Restore
191 | **/[Pp]ackages/*
192 | # except build/, which is used as an MSBuild target.
193 | !**/[Pp]ackages/build/
194 | # Uncomment if necessary however generally it will be regenerated when needed
195 | #!**/[Pp]ackages/repositories.config
196 | # NuGet v3's project.json files produces more ignorable files
197 | *.nuget.props
198 | *.nuget.targets
199 |
200 | # Microsoft Azure Build Output
201 | csx/
202 | *.build.csdef
203 |
204 | # Microsoft Azure Emulator
205 | ecf/
206 | rcf/
207 |
208 | # Windows Store app package directories and files
209 | AppPackages/
210 | BundleArtifacts/
211 | Package.StoreAssociation.xml
212 | _pkginfo.txt
213 | *.appx
214 | *.appxbundle
215 | *.appxupload
216 |
217 | # Visual Studio cache files
218 | # files ending in .cache can be ignored
219 | *.[Cc]ache
220 | # but keep track of directories ending in .cache
221 | !?*.[Cc]ache/
222 |
223 | # Others
224 | ClientBin/
225 | ~$*
226 | *~
227 | *.dbmdl
228 | *.dbproj.schemaview
229 | *.jfm
230 | *.pfx
231 | *.publishsettings
232 | orleans.codegen.cs
233 |
234 | # Including strong name files can present a security risk
235 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
236 | #*.snk
237 |
238 | # Since there are multiple workflows, uncomment next line to ignore bower_components
239 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
240 | #bower_components/
241 |
242 | # RIA/Silverlight projects
243 | Generated_Code/
244 |
245 | # Backup & report files from converting an old project file
246 | # to a newer Visual Studio version. Backup files are not needed,
247 | # because we have git ;-)
248 | _UpgradeReport_Files/
249 | Backup*/
250 | UpgradeLog*.XML
251 | UpgradeLog*.htm
252 | ServiceFabricBackup/
253 | *.rptproj.bak
254 |
255 | # SQL Server files
256 | *.mdf
257 | *.ldf
258 | *.ndf
259 |
260 | # Business Intelligence projects
261 | *.rdl.data
262 | *.bim.layout
263 | *.bim_*.settings
264 | *.rptproj.rsuser
265 | *- [Bb]ackup.rdl
266 | *- [Bb]ackup ([0-9]).rdl
267 | *- [Bb]ackup ([0-9][0-9]).rdl
268 |
269 | # Microsoft Fakes
270 | FakesAssemblies/
271 |
272 | # GhostDoc plugin setting file
273 | *.GhostDoc.xml
274 |
275 | # Node.js Tools for Visual Studio
276 | .ntvs_analysis.dat
277 | node_modules/
278 |
279 | # Visual Studio 6 build log
280 | *.plg
281 |
282 | # Visual Studio 6 workspace options file
283 | *.opt
284 |
285 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
286 | *.vbw
287 |
288 | # Visual Studio LightSwitch build output
289 | **/*.HTMLClient/GeneratedArtifacts
290 | **/*.DesktopClient/GeneratedArtifacts
291 | **/*.DesktopClient/ModelManifest.xml
292 | **/*.Server/GeneratedArtifacts
293 | **/*.Server/ModelManifest.xml
294 | _Pvt_Extensions
295 |
296 | # Paket dependency manager
297 | .paket/paket.exe
298 | paket-files/
299 |
300 | # FAKE - F# Make
301 | .fake/
302 |
303 | # CodeRush personal settings
304 | .cr/personal
305 |
306 | # Python Tools for Visual Studio (PTVS)
307 | __pycache__/
308 | *.pyc
309 |
310 | # Cake - Uncomment if you are using it
311 | # tools/**
312 | # !tools/packages.config
313 |
314 | # Tabs Studio
315 | *.tss
316 |
317 | # Telerik's JustMock configuration file
318 | *.jmconfig
319 |
320 | # BizTalk build output
321 | *.btp.cs
322 | *.btm.cs
323 | *.odx.cs
324 | *.xsd.cs
325 |
326 | # OpenCover UI analysis results
327 | OpenCover/
328 |
329 | # Azure Stream Analytics local run output
330 | ASALocalRun/
331 |
332 | # MSBuild Binary and Structured Log
333 | *.binlog
334 |
335 | # NVidia Nsight GPU debugger configuration file
336 | *.nvuser
337 |
338 | # MFractors (Xamarin productivity tool) working folder
339 | .mfractor/
340 |
341 | # Local History for Visual Studio
342 | .localhistory/
343 |
344 | # BeatPulse healthcheck temp database
345 | healthchecksdb
346 |
347 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
348 | MigrationBackup/
349 |
350 | # dotnet tool local install directory
351 | .store/
352 |
353 | # mac-created file to track user view preferences for a directory
354 | .DS_Store
355 |
356 | # Analysis results
357 | *.sarif
358 |
--------------------------------------------------------------------------------
/.prettierrc.yaml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AArnott/CSharpIsNull/e29603ae4a24d6db86811135401ad5905a87d593/.prettierrc.yaml
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
4 | // List of extensions which should be recommended for users of this workspace.
5 | "recommendations": [
6 | "ms-azure-devops.azure-pipelines",
7 | "ms-dotnettools.csharp",
8 | "k--kato.docomment",
9 | "editorconfig.editorconfig",
10 | "esbenp.prettier-vscode",
11 | "pflannery.vscode-versionlens",
12 | "davidanson.vscode-markdownlint",
13 | "dotjoshjohnson.xml",
14 | "ms-vscode-remote.remote-containers",
15 | "ms-azuretools.vscode-docker",
16 | "tintoy.msbuild-project-tools"
17 | ],
18 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
19 | "unwantedRecommendations": []
20 | }
21 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for C# debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Attach",
9 | "type": "coreclr",
10 | "request": "attach",
11 | "processId": "${command:pickProcess}"
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.trimTrailingWhitespace": true,
3 | "files.insertFinalNewline": true,
4 | "files.trimFinalNewlines": true,
5 | "omnisharp.enableEditorConfigSupport": true,
6 | "omnisharp.enableRoslynAnalyzers": true,
7 | "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true,
8 | "editor.formatOnSave": true,
9 | "[xml]": {
10 | "editor.wordWrap": "off"
11 | },
12 | // Treat these files as Azure Pipelines files
13 | "files.associations": {
14 | "**/azure-pipelines/**/*.yml": "azure-pipelines",
15 | "azure-pipelines.yml": "azure-pipelines"
16 | },
17 | // Use Prettier as the default formatter for Azure Pipelines files.
18 | // Needs to be explicitly configured: https://github.com/Microsoft/azure-pipelines-vscode#document-formatting
19 | "[azure-pipelines]": {
20 | "editor.defaultFormatter": "esbenp.prettier-vscode",
21 | "editor.formatOnSave": false // enable this when they conform
22 | },
23 | }
24 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | This project has adopted the [Microsoft Open Source Code of
4 | Conduct](https://opensource.microsoft.com/codeofconduct/).
5 | For more information see the [Code of Conduct
6 | FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
7 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com)
8 | with any additional questions or comments.
9 |
10 | ## Best practices
11 |
12 | * Use Windows PowerShell or [PowerShell Core][pwsh] (including on Linux/OSX) to run .ps1 scripts.
13 | Some scripts set environment variables to help you, but they are only retained if you use PowerShell as your shell.
14 |
15 | ## Prerequisites
16 |
17 | All dependencies can be installed by running the `init.ps1` script at the root of the repository
18 | using Windows PowerShell or [PowerShell Core][pwsh] (on any OS).
19 | Some dependencies installed by `init.ps1` may only be discoverable from the same command line environment the init script was run from due to environment variables, so be sure to launch Visual Studio or build the repo from that same environment.
20 | Alternatively, run `init.ps1 -InstallLocality Machine` (which may require elevation) in order to install dependencies at machine-wide locations so Visual Studio and builds work everywhere.
21 |
22 | The only prerequisite for building, testing, and deploying from this repository
23 | is the [.NET SDK](https://get.dot.net/).
24 | You should install the version specified in `global.json` or a later version within
25 | the same major.minor.Bxx "hundreds" band.
26 | For example if 2.2.300 is specified, you may install 2.2.300, 2.2.301, or 2.2.310
27 | while the 2.2.400 version would not be considered compatible by .NET SDK.
28 | See [.NET Core Versioning](https://docs.microsoft.com/dotnet/core/versions/) for more information.
29 |
30 | ## Package restore
31 |
32 | The easiest way to restore packages may be to run `init.ps1` which automatically authenticates
33 | to the feeds that packages for this repo come from, if any.
34 | `dotnet restore` or `nuget restore` also work but may require extra steps to authenticate to any applicable feeds.
35 |
36 | ## Building
37 |
38 | This repository can be built on Windows, Linux, and OSX.
39 |
40 | Building, testing, and packing this repository can be done by using the standard dotnet CLI commands (e.g. `dotnet build`, `dotnet test`, `dotnet pack`, etc.).
41 |
42 | [pwsh]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell?view=powershell-6
43 |
--------------------------------------------------------------------------------
/CSharpIsNullAnalyzer.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31620.382
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1CE9670B-D5FF-46A7-9D00-24E70E6ED48B}"
7 | ProjectSection(SolutionItems) = preProject
8 | .editorconfig = .editorconfig
9 | Directory.Build.props = Directory.Build.props
10 | Directory.Build.targets = Directory.Build.targets
11 | Directory.Packages.props = Directory.Packages.props
12 | global.json = global.json
13 | nuget.config = nuget.config
14 | README.md = README.md
15 | stylecop.json = stylecop.json
16 | version.json = version.json
17 | EndProjectSection
18 | EndProject
19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9E154A29-1796-4B85-BD81-B6A385D8FF71}"
20 | ProjectSection(SolutionItems) = preProject
21 | src\.editorconfig = src\.editorconfig
22 | src\Directory.Build.props = src\Directory.Build.props
23 | src\Directory.Build.targets = src\Directory.Build.targets
24 | EndProjectSection
25 | EndProject
26 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{36CCE840-6FE5-4DB9-A8D5-8CF3CB6D342A}"
27 | ProjectSection(SolutionItems) = preProject
28 | test\.editorconfig = test\.editorconfig
29 | test\Directory.Build.props = test\Directory.Build.props
30 | test\Directory.Build.targets = test\Directory.Build.targets
31 | EndProjectSection
32 | EndProject
33 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DF4CAFE5-2F72-4227-A4B3-9446049DAFC8}"
34 | EndProject
35 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpIsNullAnalyzer", "src\CSharpIsNullAnalyzer\CSharpIsNullAnalyzer.csproj", "{25873F90-8908-4E3D-A0B4-F668217A1653}"
36 | EndProject
37 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{44275BDE-734A-4B65-A8C5-61608C892A76}"
38 | EndProject
39 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpIsNullAnalyzer.Tests", "test\CSharpIsNullAnalyzer.Tests\CSharpIsNullAnalyzer.Tests.csproj", "{9CA85BC1-5565-4498-928D-F2685D337B71}"
40 | EndProject
41 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpIsNullAnalyzer.CodeFixes", "src\CSharpIsNullAnalyzer.CodeFixes\CSharpIsNullAnalyzer.CodeFixes.csproj", "{A8C3211C-32CB-41EE-8D9A-384D0F1F6947}"
42 | EndProject
43 | Global
44 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
45 | Debug|Any CPU = Debug|Any CPU
46 | Release|Any CPU = Release|Any CPU
47 | EndGlobalSection
48 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
49 | {25873F90-8908-4E3D-A0B4-F668217A1653}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50 | {25873F90-8908-4E3D-A0B4-F668217A1653}.Debug|Any CPU.Build.0 = Debug|Any CPU
51 | {25873F90-8908-4E3D-A0B4-F668217A1653}.Release|Any CPU.ActiveCfg = Release|Any CPU
52 | {25873F90-8908-4E3D-A0B4-F668217A1653}.Release|Any CPU.Build.0 = Release|Any CPU
53 | {9CA85BC1-5565-4498-928D-F2685D337B71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
54 | {9CA85BC1-5565-4498-928D-F2685D337B71}.Debug|Any CPU.Build.0 = Debug|Any CPU
55 | {9CA85BC1-5565-4498-928D-F2685D337B71}.Release|Any CPU.ActiveCfg = Release|Any CPU
56 | {9CA85BC1-5565-4498-928D-F2685D337B71}.Release|Any CPU.Build.0 = Release|Any CPU
57 | {A8C3211C-32CB-41EE-8D9A-384D0F1F6947}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
58 | {A8C3211C-32CB-41EE-8D9A-384D0F1F6947}.Debug|Any CPU.Build.0 = Debug|Any CPU
59 | {A8C3211C-32CB-41EE-8D9A-384D0F1F6947}.Release|Any CPU.ActiveCfg = Release|Any CPU
60 | {A8C3211C-32CB-41EE-8D9A-384D0F1F6947}.Release|Any CPU.Build.0 = Release|Any CPU
61 | EndGlobalSection
62 | GlobalSection(SolutionProperties) = preSolution
63 | HideSolutionNode = FALSE
64 | EndGlobalSection
65 | GlobalSection(NestedProjects) = preSolution
66 | {9E154A29-1796-4B85-BD81-B6A385D8FF71} = {1CE9670B-D5FF-46A7-9D00-24E70E6ED48B}
67 | {36CCE840-6FE5-4DB9-A8D5-8CF3CB6D342A} = {1CE9670B-D5FF-46A7-9D00-24E70E6ED48B}
68 | {25873F90-8908-4E3D-A0B4-F668217A1653} = {DF4CAFE5-2F72-4227-A4B3-9446049DAFC8}
69 | {9CA85BC1-5565-4498-928D-F2685D337B71} = {44275BDE-734A-4B65-A8C5-61608C892A76}
70 | {A8C3211C-32CB-41EE-8D9A-384D0F1F6947} = {DF4CAFE5-2F72-4227-A4B3-9446049DAFC8}
71 | EndGlobalSection
72 | GlobalSection(ExtensibilityGlobals) = postSolution
73 | SolutionGuid = {E3944F6A-384B-4B0F-B93F-3BD513DC57BD}
74 | EndGlobalSection
75 | EndGlobal
76 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | $(MSBuildThisFileDirectory)
6 | $(RepoRootPath)obj\$([MSBuild]::MakeRelative($(RepoRootPath), $(MSBuildProjectDirectory)))\
7 | $(RepoRootPath)bin\$(MSBuildProjectName)\
8 | $(RepoRootPath)bin\Packages\$(Configuration)\
9 | enable
10 | enable
11 | latest
12 | true
13 | true
14 | true
15 |
16 |
17 | true
18 |
19 |
20 |
21 | false
22 |
23 |
24 | $(MSBuildThisFileDirectory)
25 |
26 |
27 | embedded
28 |
29 | true
30 | $(MSBuildThisFileDirectory)strongname.snk
31 |
32 | https://github.com/AArnott/CSharpIsNull
33 | Andrew Arnott
34 | Andrew Arnott
35 | © Andrew Arnott. All rights reserved.
36 | MIT
37 | true
38 | true
39 | true
40 | snupkg
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | $(PackageProjectUrl)/releases/tag/v$(Version)
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/Directory.Build.rsp:
--------------------------------------------------------------------------------
1 | #------------------------------------------------------------------------------
2 | # This file contains command-line options that MSBuild will process as part of
3 | # every build, unless the "/noautoresponse" switch is specified.
4 | #
5 | # MSBuild processes the options in this file first, before processing the
6 | # options on the command line. As a result, options on the command line can
7 | # override the options in this file. However, depending on the options being
8 | # set, the overriding can also result in conflicts.
9 | #
10 | # NOTE: The "/noautoresponse" switch cannot be specified in this file, nor in
11 | # any response file that is referenced by this file.
12 | #------------------------------------------------------------------------------
13 | /nr:false
14 | /m
15 | /verbosity:minimal
16 | /clp:Summary;ForceNoAlign
17 |
--------------------------------------------------------------------------------
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 12
5 | 16.9
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Directory.Packages.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | true
6 | true
7 |
8 | 3.7.0
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Andrew Arnott
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # C# null test syntax analyzers
2 |
3 | [](https://nuget.org/packages/CSharpIsNullAnalyzer)
4 | [](https://dev.azure.com/andrewarnott/OSS/_packaging?_a=package&feed=PublicCI&package=CSharpIsNullAnalyzer&version=0.1.278-beta&protocolType=NuGet)
5 | [](https://dev.azure.com/andrewarnott/OSS/_build/latest?definitionId=54&branchName=main)
6 |
7 | ## Features
8 |
9 | * Guard against bugs from testing structs against `null`.
10 | * Bulk code fix will update all your code at once.
11 |
12 | ### Analyzers
13 |
14 | * [CSIsNull001](doc/analyzers/CSIsNull001.md) to catch uses of `== null`
15 | * [CSIsNull002](doc/analyzers/CSIsNull002.md) to catch uses of `!= null`
16 |
17 | ## Consumption
18 |
19 | Install it via NuGet through the nuget badge at the top of this file.
20 |
21 | ### Consume from CI
22 |
23 | To get the very latest analyzer [from my CI feed](https://dev.azure.com/andrewarnott/OSS/_packaging?_a=package&feed=PublicCI&package=CSharpIsNullAnalyzer&protocolType=NuGet):
24 |
25 | [Connect to the feed](https://dev.azure.com/andrewarnott/OSS/_packaging?_a=connect&feed=PublicCI):
26 |
27 | ```xml
28 |
29 | ```
30 |
31 | Then install the package with this command in your Package Manager Console:
32 |
33 | ```ps1
34 | Install-Package CSharpIsNullAnalyzer -pre
35 | ```
36 |
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | batch: true
3 | branches:
4 | include:
5 | - main
6 | - 'validate/*'
7 | paths:
8 | exclude:
9 | - doc/
10 | - '*.md'
11 | - .vscode/
12 | - .github/
13 | - azure-pipelines/release.yml
14 |
15 | parameters:
16 | - name: RunTests
17 | displayName: Run tests
18 | type: boolean
19 | default: true
20 |
21 | variables:
22 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
23 | BuildConfiguration: Release
24 | codecov_token: 4cea7d85-5a0a-458e-beca-260c04190f2b
25 | ci_feed: https://pkgs.dev.azure.com/andrewarnott/OSS/_packaging/PublicCI/nuget/v3/index.json
26 | NUGET_PACKAGES: $(Agent.TempDirectory)/.nuget/packages/
27 |
28 | jobs:
29 | - template: azure-pipelines/build.yml
30 | parameters:
31 | RunTests: ${{ parameters.RunTests }}
32 |
--------------------------------------------------------------------------------
/azure-pipelines/Get-ArtifactsStagingDirectory.ps1:
--------------------------------------------------------------------------------
1 | Param(
2 | [switch]$CleanIfLocal
3 | )
4 | if ($env:BUILD_ARTIFACTSTAGINGDIRECTORY) {
5 | $ArtifactStagingFolder = $env:BUILD_ARTIFACTSTAGINGDIRECTORY
6 | } elseif ($env:RUNNER_TEMP) {
7 | $ArtifactStagingFolder = "$env:RUNNER_TEMP\_artifacts"
8 | } else {
9 | $ArtifactStagingFolder = [System.IO.Path]::GetFullPath("$PSScriptRoot/../obj/_artifacts")
10 | if ($CleanIfLocal -and (Test-Path $ArtifactStagingFolder)) {
11 | Remove-Item $ArtifactStagingFolder -Recurse -Force
12 | }
13 | }
14 |
15 | $ArtifactStagingFolder
16 |
--------------------------------------------------------------------------------
/azure-pipelines/Get-CodeCovTool.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Downloads the CodeCov.io uploader tool and returns the path to it.
4 | .PARAMETER AllowSkipVerify
5 | Allows skipping signature verification of the downloaded tool if gpg is not installed.
6 | #>
7 | [CmdletBinding()]
8 | Param(
9 | [switch]$AllowSkipVerify
10 | )
11 |
12 | if ($IsMacOS) {
13 | $codeCovUrl = "https://uploader.codecov.io/latest/macos/codecov"
14 | $toolName = 'codecov'
15 | }
16 | elseif ($IsLinux) {
17 | $codeCovUrl = "https://uploader.codecov.io/latest/linux/codecov"
18 | $toolName = 'codecov'
19 | }
20 | else {
21 | $codeCovUrl = "https://uploader.codecov.io/latest/windows/codecov.exe"
22 | $toolName = 'codecov.exe'
23 | }
24 |
25 | $shaSuffix = ".SHA256SUM"
26 | $sigSuffix = $shaSuffix + ".sig"
27 |
28 | Function Get-FileFromWeb([Uri]$Uri, $OutDir) {
29 | $OutFile = Join-Path $OutDir $Uri.Segments[-1]
30 | if (!(Test-Path $OutFile)) {
31 | Write-Verbose "Downloading $Uri..."
32 | if (!(Test-Path $OutDir)) { New-Item -ItemType Directory -Path $OutDir | Out-Null }
33 | try {
34 | (New-Object System.Net.WebClient).DownloadFile($Uri, $OutFile)
35 | } finally {
36 | # This try/finally causes the script to abort
37 | }
38 | }
39 |
40 | $OutFile
41 | }
42 |
43 | $toolsPath = & "$PSScriptRoot\Get-TempToolsPath.ps1"
44 | $binaryToolsPath = Join-Path $toolsPath codecov
45 | $testingPath = Join-Path $binaryToolsPath unverified
46 | $finalToolPath = Join-Path $binaryToolsPath $toolName
47 |
48 | if (!(Test-Path $finalToolPath)) {
49 | if (Test-Path $testingPath) {
50 | Remove-Item -Recurse -Force $testingPath # ensure we download all matching files
51 | }
52 | $tool = Get-FileFromWeb $codeCovUrl $testingPath
53 | $sha = Get-FileFromWeb "$codeCovUrl$shaSuffix" $testingPath
54 | $sig = Get-FileFromWeb "$codeCovUrl$sigSuffix" $testingPath
55 | $key = Get-FileFromWeb https://keybase.io/codecovsecurity/pgp_keys.asc $testingPath
56 |
57 | if ((Get-Command gpg -ErrorAction SilentlyContinue)) {
58 | Write-Host "Importing codecov key" -ForegroundColor Yellow
59 | gpg --import $key
60 | Write-Host "Verifying signature on codecov hash" -ForegroundColor Yellow
61 | gpg --verify $sig $sha
62 | } else {
63 | if ($AllowSkipVerify) {
64 | Write-Warning "gpg not found. Unable to verify hash signature."
65 | } else {
66 | throw "gpg not found. Unable to verify hash signature. Install gpg or add -AllowSkipVerify to override."
67 | }
68 | }
69 |
70 | Write-Host "Verifying hash on downloaded tool" -ForegroundColor Yellow
71 | $actualHash = (Get-FileHash -Path $tool -Algorithm SHA256).Hash
72 | $expectedHash = (Get-Content $sha).Split()[0]
73 | if ($actualHash -ne $expectedHash) {
74 | # Validation failed. Delete the tool so we can't execute it.
75 | #Remove-Item $codeCovPath
76 | throw "codecov uploader tool failed signature validation."
77 | }
78 |
79 | Copy-Item $tool $finalToolPath
80 |
81 | if ($IsMacOS -or $IsLinux) {
82 | chmod u+x $finalToolPath
83 | }
84 | }
85 |
86 | return $finalToolPath
87 |
--------------------------------------------------------------------------------
/azure-pipelines/Get-NuGetTool.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Downloads the NuGet.exe tool and returns the path to it.
4 | .PARAMETER NuGetVersion
5 | The version of the NuGet tool to acquire.
6 | #>
7 | Param(
8 | [Parameter()]
9 | [string]$NuGetVersion='6.4.0'
10 | )
11 |
12 | $toolsPath = & "$PSScriptRoot\Get-TempToolsPath.ps1"
13 | $binaryToolsPath = Join-Path $toolsPath $NuGetVersion
14 | if (!(Test-Path $binaryToolsPath)) { $null = mkdir $binaryToolsPath }
15 | $nugetPath = Join-Path $binaryToolsPath nuget.exe
16 |
17 | if (!(Test-Path $nugetPath)) {
18 | Write-Host "Downloading nuget.exe $NuGetVersion..." -ForegroundColor Yellow
19 | (New-Object System.Net.WebClient).DownloadFile("https://dist.nuget.org/win-x86-commandline/v$NuGetVersion/NuGet.exe", $nugetPath)
20 | }
21 |
22 | return (Resolve-Path $nugetPath).Path
23 |
--------------------------------------------------------------------------------
/azure-pipelines/Get-ProcDump.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Downloads 32-bit and 64-bit procdump executables and returns the path to where they were installed.
4 | #>
5 | $version = '0.0.1'
6 | $baseDir = "$PSScriptRoot\..\obj\tools"
7 | $procDumpToolPath = "$baseDir\procdump.$version\bin"
8 | if (-not (Test-Path $procDumpToolPath)) {
9 | if (-not (Test-Path $baseDir)) { New-Item -Type Directory -Path $baseDir | Out-Null }
10 | $baseDir = (Resolve-Path $baseDir).Path # Normalize it
11 | & (& $PSScriptRoot\Get-NuGetTool.ps1) install procdump -version $version -PackageSaveMode nuspec -OutputDirectory $baseDir -Source https://api.nuget.org/v3/index.json | Out-Null
12 | }
13 |
14 | (Resolve-Path $procDumpToolPath).Path
15 |
--------------------------------------------------------------------------------
/azure-pipelines/Get-SymbolFiles.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Collect the list of PDBs built in this repo.
4 | .PARAMETER Path
5 | The directory to recursively search for PDBs.
6 | .PARAMETER Tests
7 | A switch indicating to find PDBs only for test binaries instead of only for shipping shipping binaries.
8 | #>
9 | [CmdletBinding()]
10 | param (
11 | [parameter(Mandatory=$true)]
12 | [string]$Path,
13 | [switch]$Tests
14 | )
15 |
16 | $ActivityName = "Collecting symbols from $Path"
17 | Write-Progress -Activity $ActivityName -CurrentOperation "Discovery PDB files"
18 | $PDBs = Get-ChildItem -rec "$Path/*.pdb"
19 |
20 | # Filter PDBs to product OR test related.
21 | $testregex = "unittest|tests|\.test\."
22 |
23 | Write-Progress -Activity $ActivityName -CurrentOperation "De-duplicating symbols"
24 | $PDBsByHash = @{}
25 | $i = 0
26 | $PDBs |% {
27 | Write-Progress -Activity $ActivityName -CurrentOperation "De-duplicating symbols" -PercentComplete (100 * $i / $PDBs.Length)
28 | $hash = Get-FileHash $_
29 | $i++
30 | Add-Member -InputObject $_ -MemberType NoteProperty -Name Hash -Value $hash.Hash
31 | Write-Output $_
32 | } | Sort-Object CreationTime |% {
33 | # De-dupe based on hash. Prefer the first match so we take the first built copy.
34 | if (-not $PDBsByHash.ContainsKey($_.Hash)) {
35 | $PDBsByHash.Add($_.Hash, $_.FullName)
36 | Write-Output $_
37 | }
38 | } |? {
39 | if ($Tests) {
40 | $_.FullName -match $testregex
41 | } else {
42 | $_.FullName -notmatch $testregex
43 | }
44 | } |% {
45 | # Collect the DLLs/EXEs as well.
46 | $rootName = "$($_.Directory)/$($_.BaseName)"
47 | if ($rootName.EndsWith('.ni')) {
48 | $rootName = $rootName.Substring(0, $rootName.Length - 3)
49 | }
50 |
51 | $dllPath = "$rootName.dll"
52 | $exePath = "$rootName.exe"
53 | if (Test-Path $dllPath) {
54 | $BinaryImagePath = $dllPath
55 | } elseif (Test-Path $exePath) {
56 | $BinaryImagePath = $exePath
57 | } else {
58 | Write-Warning "`"$_`" found with no matching binary file."
59 | $BinaryImagePath = $null
60 | }
61 |
62 | if ($BinaryImagePath) {
63 | Write-Output $BinaryImagePath
64 | Write-Output $_.FullName
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/azure-pipelines/Get-TempToolsPath.ps1:
--------------------------------------------------------------------------------
1 | if ($env:AGENT_TEMPDIRECTORY) {
2 | $path = "$env:AGENT_TEMPDIRECTORY\$env:BUILD_BUILDID"
3 | } elseif ($env:localappdata) {
4 | $path = "$env:localappdata\gitrepos\tools"
5 | } else {
6 | $path = "$PSScriptRoot\..\obj\tools"
7 | }
8 |
9 | if (!(Test-Path $path)) {
10 | New-Item -ItemType Directory -Path $Path | Out-Null
11 | }
12 |
13 | (Resolve-Path $path).Path
14 |
--------------------------------------------------------------------------------
/azure-pipelines/Merge-CodeCoverage.ps1:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env pwsh
2 |
3 | <#
4 | .SYNOPSIS
5 | Merges code coverage reports.
6 | .PARAMETER Path
7 | The path(s) to search for Cobertura code coverage reports.
8 | .PARAMETER Format
9 | The format for the merged result. The default is Cobertura
10 | .PARAMETER OutputDir
11 | The directory the merged result will be written to. The default is `coveragereport` in the root of this repo.
12 | #>
13 | [CmdletBinding()]
14 | Param(
15 | [Parameter(Mandatory=$true)]
16 | [string[]]$Path,
17 | [ValidateSet('Badges', 'Clover', 'Cobertura', 'CsvSummary', 'Html', 'Html_Dark', 'Html_Light', 'HtmlChart', 'HtmlInline', 'HtmlInline_AzurePipelines', 'HtmlInline_AzurePipelines_Dark', 'HtmlInline_AzurePipelines_Light', 'HtmlSummary', 'JsonSummary', 'Latex', 'LatexSummary', 'lcov', 'MarkdownSummary', 'MHtml', 'PngChart', 'SonarQube', 'TeamCitySummary', 'TextSummary', 'Xml', 'XmlSummary')]
18 | [string]$Format='Cobertura',
19 | [string]$OutputFile=("$PSScriptRoot/../coveragereport/merged.cobertura.xml")
20 | )
21 |
22 | $RepoRoot = [string](Resolve-Path $PSScriptRoot/..)
23 | Push-Location $RepoRoot
24 | try {
25 | Write-Verbose "Searching $Path for *.cobertura.xml files"
26 | $reports = Get-ChildItem -Recurse $Path -Filter *.cobertura.xml
27 |
28 | if ($reports) {
29 | $reports |% { $_.FullName } |% {
30 | # In addition to replacing {reporoot}, we also normalize on one kind of slash so that the report aggregates data for a file whether data was collected on Windows or not.
31 | $xml = [xml](Get-Content -Path $_)
32 | $xml.coverage.packages.package.classes.class |? { $_.filename} |% {
33 | $_.filename = $_.filename.Replace('{reporoot}', $RepoRoot).Replace([IO.Path]::AltDirectorySeparatorChar, [IO.Path]::DirectorySeparatorChar)
34 | }
35 |
36 | $xml.Save($_)
37 | }
38 |
39 | $Inputs = $reports |% { Resolve-Path -relative $_.FullName }
40 |
41 | if ((Split-Path $OutputFile) -and -not (Test-Path (Split-Path $OutputFile))) {
42 | New-Item -Type Directory -Path (Split-Path $OutputFile) | Out-Null
43 | }
44 |
45 | & dotnet tool run dotnet-coverage merge $Inputs -o $OutputFile -f cobertura
46 | } else {
47 | Write-Error "No reports found to merge."
48 | }
49 | } finally {
50 | Pop-Location
51 | }
52 |
--------------------------------------------------------------------------------
/azure-pipelines/artifacts/Variables.ps1:
--------------------------------------------------------------------------------
1 | # This artifact captures all variables defined in the ..\variables folder.
2 | # It "snaps" the values of these variables where we can compute them during the build,
3 | # and otherwise captures the scripts to run later during an Azure Pipelines environment release.
4 |
5 | $RepoRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot/../..")
6 | $ArtifactBasePath = "$RepoRoot/obj/_artifacts"
7 | $VariablesArtifactPath = Join-Path $ArtifactBasePath variables
8 | if (-not (Test-Path $VariablesArtifactPath)) { New-Item -ItemType Directory -Path $VariablesArtifactPath | Out-Null }
9 |
10 | # Copy variables, either by value if the value is calculable now, or by script
11 | Get-ChildItem "$PSScriptRoot/../variables" |% {
12 | $value = $null
13 | if (-not $_.BaseName.StartsWith('_')) { # Skip trying to interpret special scripts
14 | # First check the environment variables in case the variable was set in a queued build
15 | # Always use all caps for env var access because Azure Pipelines converts variables to upper-case for env vars,
16 | # and on non-Windows env vars are case sensitive.
17 | $envVarName = $_.BaseName.ToUpper()
18 | if (Test-Path env:$envVarName) {
19 | $value = Get-Content "env:$envVarName"
20 | }
21 |
22 | # If that didn't give us anything, try executing the script right now from its original position
23 | if (-not $value) {
24 | $value = & $_.FullName
25 | }
26 |
27 | if ($value) {
28 | # We got something, so wrap it with quotes so it's treated like a literal value.
29 | $value = "'$value'"
30 | }
31 | }
32 |
33 | # If that didn't get us anything, just copy the script itself
34 | if (-not $value) {
35 | $value = Get-Content -Path $_.FullName
36 | }
37 |
38 | Set-Content -Path "$VariablesArtifactPath/$($_.Name)" -Value $value
39 | }
40 |
41 | @{
42 | "$VariablesArtifactPath" = (Get-ChildItem $VariablesArtifactPath -Recurse);
43 | }
44 |
--------------------------------------------------------------------------------
/azure-pipelines/artifacts/_all.ps1:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env pwsh
2 |
3 | <#
4 | .SYNOPSIS
5 | This script returns all the artifacts that should be collected after a build.
6 | Each powershell artifact is expressed as an object with these properties:
7 | Source - the full path to the source file
8 | ArtifactName - the name of the artifact to upload to
9 | ContainerFolder - the relative path within the artifact in which the file should appear
10 | Each artifact aggregating .ps1 script should return a hashtable:
11 | Key = path to the directory from which relative paths within the artifact should be calculated
12 | Value = an array of paths (absolute or relative to the BaseDirectory) to files to include in the artifact.
13 | FileInfo objects are also allowed.
14 | .PARAMETER Force
15 | Executes artifact scripts even if they have already been staged.
16 | #>
17 |
18 | [CmdletBinding(SupportsShouldProcess = $true)]
19 | param (
20 | [string]$ArtifactNameSuffix,
21 | [switch]$Force
22 | )
23 |
24 | Function EnsureTrailingSlash($path) {
25 | if ($path.length -gt 0 -and !$path.EndsWith('\') -and !$path.EndsWith('/')) {
26 | $path = $path + [IO.Path]::DirectorySeparatorChar
27 | }
28 |
29 | $path.Replace('\', [IO.Path]::DirectorySeparatorChar)
30 | }
31 |
32 | Function Test-ArtifactStaged($artifactName) {
33 | $varName = "ARTIFACTSTAGED_$($artifactName.ToUpper())"
34 | Test-Path "env:$varName"
35 | }
36 |
37 | Get-ChildItem "$PSScriptRoot\*.ps1" -Exclude "_*" -Recurse | % {
38 | $ArtifactName = $_.BaseName
39 | if ($Force -or !(Test-ArtifactStaged($ArtifactName + $ArtifactNameSuffix))) {
40 | $totalFileCount = 0
41 | Write-Verbose "Collecting file list for artifact $($_.BaseName)"
42 | $fileGroups = & $_
43 | if ($fileGroups) {
44 | $fileGroups.GetEnumerator() | % {
45 | $BaseDirectory = New-Object Uri ((EnsureTrailingSlash $_.Key.ToString()), [UriKind]::Absolute)
46 | $_.Value | ? { $_ } | % {
47 | if ($_.GetType() -eq [IO.FileInfo] -or $_.GetType() -eq [IO.DirectoryInfo]) {
48 | $_ = $_.FullName
49 | }
50 |
51 | $artifact = New-Object -TypeName PSObject
52 | Add-Member -InputObject $artifact -MemberType NoteProperty -Name ArtifactName -Value $ArtifactName
53 |
54 | $SourceFullPath = New-Object Uri ($BaseDirectory, $_)
55 | Add-Member -InputObject $artifact -MemberType NoteProperty -Name Source -Value $SourceFullPath.LocalPath
56 |
57 | $RelativePath = [Uri]::UnescapeDataString($BaseDirectory.MakeRelative($SourceFullPath))
58 | Add-Member -InputObject $artifact -MemberType NoteProperty -Name ContainerFolder -Value (Split-Path $RelativePath)
59 |
60 | Write-Output $artifact
61 | $totalFileCount += 1
62 | }
63 | }
64 | }
65 |
66 | if ($totalFileCount -eq 0) {
67 | Write-Warning "No files found for the `"$ArtifactName`" artifact."
68 | }
69 | } else {
70 | Write-Host "Skipping $ArtifactName because it has already been staged." -ForegroundColor DarkGray
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/azure-pipelines/artifacts/_pipelines.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | This script translates all the artifacts described by _all.ps1
4 | into commands that instruct Azure Pipelines to actually collect those artifacts.
5 | #>
6 |
7 | [CmdletBinding()]
8 | param (
9 | [string]$ArtifactNameSuffix,
10 | [switch]$StageOnly,
11 | [switch]$AvoidSymbolicLinks
12 | )
13 |
14 | Function Set-PipelineVariable($name, $value) {
15 | if ((Test-Path "Env:\$name") -and (Get-Item "Env:\$name").Value -eq $value) {
16 | return # already set
17 | }
18 |
19 | #New-Item -Path "Env:\$name".ToUpper() -Value $value -Force | Out-Null
20 | Write-Host "##vso[task.setvariable variable=$name]$value"
21 | }
22 |
23 | Function Test-ArtifactUploaded($artifactName) {
24 | $varName = "ARTIFACTUPLOADED_$($artifactName.ToUpper())"
25 | Test-Path "env:$varName"
26 | }
27 |
28 | & "$PSScriptRoot/_stage_all.ps1" -ArtifactNameSuffix $ArtifactNameSuffix -AvoidSymbolicLinks:$AvoidSymbolicLinks |% {
29 | # Set a variable which will out-live this script so that a subsequent attempt to collect and upload artifacts
30 | # will skip this one from a check in the _all.ps1 script.
31 | Set-PipelineVariable "ARTIFACTSTAGED_$($_.Name.ToUpper())" 'true'
32 | Write-Host "Staged artifact $($_.Name) to $($_.Path)"
33 |
34 | if (!$StageOnly) {
35 | if (Test-ArtifactUploaded $_.Name) {
36 | Write-Host "Skipping $($_.Name) because it has already been uploaded." -ForegroundColor DarkGray
37 | } else {
38 | Write-Host "##vso[artifact.upload containerfolder=$($_.Name);artifactname=$($_.Name);]$($_.Path)"
39 |
40 | # Set a variable which will out-live this script so that a subsequent attempt to collect and upload artifacts
41 | # will skip this one from a check in the _all.ps1 script.
42 | Set-PipelineVariable "ARTIFACTUPLOADED_$($_.Name.ToUpper())" 'true'
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/azure-pipelines/artifacts/_stage_all.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | This script links all the artifacts described by _all.ps1
4 | into a staging directory, reading for uploading to a cloud build artifact store.
5 | It returns a sequence of objects with Name and Path properties.
6 | #>
7 |
8 | [CmdletBinding()]
9 | param (
10 | [string]$ArtifactNameSuffix,
11 | [switch]$AvoidSymbolicLinks
12 | )
13 |
14 | $ArtifactStagingFolder = & "$PSScriptRoot/../Get-ArtifactsStagingDirectory.ps1" -CleanIfLocal
15 |
16 | function Create-SymbolicLink {
17 | param (
18 | $Link,
19 | $Target
20 | )
21 |
22 | if ($Link -eq $Target) {
23 | return
24 | }
25 |
26 | if (Test-Path $Link) { Remove-Item $Link }
27 | $LinkContainer = Split-Path $Link -Parent
28 | if (!(Test-Path $LinkContainer)) { mkdir $LinkContainer }
29 | if ($IsMacOS -or $IsLinux) {
30 | ln $Target $Link | Out-Null
31 | } else {
32 | cmd /c "mklink `"$Link`" `"$Target`"" | Out-Null
33 | }
34 |
35 | if ($LASTEXITCODE -ne 0) {
36 | # Windows requires admin privileges to create symbolic links
37 | # unless Developer Mode has been enabled.
38 | throw "Failed to create symbolic link at $Link that points to $Target"
39 | }
40 | }
41 |
42 | # Stage all artifacts
43 | $Artifacts = & "$PSScriptRoot\_all.ps1" -ArtifactNameSuffix $ArtifactNameSuffix
44 | $Artifacts |% {
45 | $DestinationFolder = [System.IO.Path]::GetFullPath("$ArtifactStagingFolder/$($_.ArtifactName)$ArtifactNameSuffix/$($_.ContainerFolder)").TrimEnd('\')
46 | $Name = "$(Split-Path $_.Source -Leaf)"
47 |
48 | #Write-Host "$($_.Source) -> $($_.ArtifactName)\$($_.ContainerFolder)" -ForegroundColor Yellow
49 |
50 | if (-not (Test-Path $DestinationFolder)) { New-Item -ItemType Directory -Path $DestinationFolder | Out-Null }
51 | if (Test-Path -PathType Leaf $_.Source) { # skip folders
52 | $TargetPath = Join-Path $DestinationFolder $Name
53 | if ($AvoidSymbolicLinks) {
54 | Copy-Item -Path $_.Source -Destination $TargetPath
55 | } else {
56 | Create-SymbolicLink -Link $TargetPath -Target $_.Source
57 | }
58 | }
59 | }
60 |
61 | $ArtifactNames = $Artifacts |% { "$($_.ArtifactName)$ArtifactNameSuffix" }
62 | $ArtifactNames += Get-ChildItem env:ARTIFACTSTAGED_* |% {
63 | # Return from ALLCAPS to the actual capitalization used for the artifact.
64 | $artifactNameAllCaps = "$($_.Name.Substring('ARTIFACTSTAGED_'.Length))"
65 | (Get-ChildItem $ArtifactStagingFolder\$artifactNameAllCaps* -Filter $artifactNameAllCaps).Name
66 | }
67 | $ArtifactNames | Get-Unique |% {
68 | $artifact = New-Object -TypeName PSObject
69 | Add-Member -InputObject $artifact -MemberType NoteProperty -Name Name -Value $_
70 | Add-Member -InputObject $artifact -MemberType NoteProperty -Name Path -Value (Join-Path $ArtifactStagingFolder $_)
71 | Write-Output $artifact
72 | }
73 |
--------------------------------------------------------------------------------
/azure-pipelines/artifacts/build_logs.ps1:
--------------------------------------------------------------------------------
1 | $ArtifactStagingFolder = & "$PSScriptRoot/../Get-ArtifactsStagingDirectory.ps1"
2 |
3 | if (!(Test-Path $ArtifactStagingFolder/build_logs)) { return }
4 |
5 | @{
6 | "$ArtifactStagingFolder/build_logs" = (Get-ChildItem -Recurse "$ArtifactStagingFolder/build_logs")
7 | }
8 |
--------------------------------------------------------------------------------
/azure-pipelines/artifacts/coverageResults.ps1:
--------------------------------------------------------------------------------
1 | $RepoRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..")
2 |
3 | $coverageFiles = @(Get-ChildItem "$RepoRoot/test/*.cobertura.xml" -Recurse | Where {$_.FullName -notlike "*/In/*" -and $_.FullName -notlike "*\In\*" })
4 |
5 | # Prepare code coverage reports for merging on another machine
6 | if ($env:SYSTEM_DEFAULTWORKINGDIRECTORY) {
7 | Write-Host "Substituting $env:SYSTEM_DEFAULTWORKINGDIRECTORY with `"{reporoot}`""
8 | $coverageFiles |% {
9 | $content = Get-Content -Path $_ |% { $_ -Replace [regex]::Escape($env:SYSTEM_DEFAULTWORKINGDIRECTORY), "{reporoot}" }
10 | Set-Content -Path $_ -Value $content -Encoding UTF8
11 | }
12 | } else {
13 | Write-Warning "coverageResults: Azure Pipelines not detected. Machine-neutral token replacement skipped."
14 | }
15 |
16 | if (!((Test-Path $RepoRoot\bin) -and (Test-Path $RepoRoot\obj))) { return }
17 |
18 | @{
19 | $RepoRoot = (
20 | $coverageFiles +
21 | (Get-ChildItem "$RepoRoot\obj\*.cs" -Recurse)
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/azure-pipelines/artifacts/deployables.ps1:
--------------------------------------------------------------------------------
1 | $RepoRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..")
2 | $BuildConfiguration = $env:BUILDCONFIGURATION
3 | if (!$BuildConfiguration) {
4 | $BuildConfiguration = 'Debug'
5 | }
6 |
7 | $PackagesRoot = "$RepoRoot/bin/Packages/$BuildConfiguration"
8 |
9 | if (!(Test-Path $PackagesRoot)) { return }
10 |
11 | @{
12 | "$PackagesRoot" = (Get-ChildItem $PackagesRoot -Recurse)
13 | }
14 |
--------------------------------------------------------------------------------
/azure-pipelines/artifacts/projectAssetsJson.ps1:
--------------------------------------------------------------------------------
1 | $ObjRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\obj")
2 |
3 | if (!(Test-Path $ObjRoot)) { return }
4 |
5 | @{
6 | "$ObjRoot" = (
7 | (Get-ChildItem "$ObjRoot\project.assets.json" -Recurse)
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/azure-pipelines/artifacts/symbols.ps1:
--------------------------------------------------------------------------------
1 | $BinPath = [System.IO.Path]::GetFullPath("$PSScriptRoot/../../bin")
2 | if (!(Test-Path $BinPath)) { return }
3 | $symbolfiles = & "$PSScriptRoot/../Get-SymbolFiles.ps1" -Path $BinPath | Get-Unique
4 |
5 | @{
6 | "$BinPath" = $SymbolFiles;
7 | }
8 |
--------------------------------------------------------------------------------
/azure-pipelines/artifacts/testResults.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | Param(
3 | )
4 |
5 | $result = @{}
6 |
7 | $testRoot = Resolve-Path "$PSScriptRoot\..\..\test"
8 | $result[$testRoot] = (Get-ChildItem "$testRoot\TestResults" -Recurse -Directory | Get-ChildItem -Recurse -File)
9 |
10 | $testlogsPath = "$env:BUILD_ARTIFACTSTAGINGDIRECTORY\test_logs"
11 | if (Test-Path $testlogsPath) {
12 | $result[$testlogsPath] = Get-ChildItem "$testlogsPath\*";
13 | }
14 |
15 | $result
16 |
--------------------------------------------------------------------------------
/azure-pipelines/artifacts/test_symbols.ps1:
--------------------------------------------------------------------------------
1 | $BinPath = [System.IO.Path]::GetFullPath("$PSScriptRoot/../../bin")
2 | if (!(Test-Path $BinPath)) { return }
3 | $symbolfiles = & "$PSScriptRoot/../Get-SymbolFiles.ps1" -Path $BinPath -Tests | Get-Unique
4 |
5 | @{
6 | "$BinPath" = $SymbolFiles;
7 | }
8 |
--------------------------------------------------------------------------------
/azure-pipelines/build.yml:
--------------------------------------------------------------------------------
1 | parameters:
2 | - name: RunTests
3 | type: boolean
4 | default: true
5 |
6 | jobs:
7 | - job: Linux
8 | pool:
9 | vmImage: Ubuntu 20.04
10 | steps:
11 | - checkout: self
12 | fetchDepth: 0 # avoid shallow clone so nbgv can do its work.
13 | clean: true
14 | - template: install-dependencies.yml
15 | - script: dotnet nbgv cloud -c
16 | displayName: ⚙ Set build number
17 | - template: dotnet.yml
18 | parameters:
19 | RunTests: ${{ parameters.RunTests }}
20 | - script: dotnet format --verify-no-changes --no-restore
21 | displayName: 💅 Verify formatted code
22 | - template: publish-symbols.yml
23 | - ${{ if parameters.RunTests }}:
24 | - template: publish-codecoverage.yml
25 | - template: publish-deployables.yml
26 |
--------------------------------------------------------------------------------
/azure-pipelines/dotnet-test-cloud.ps1:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env pwsh
2 |
3 | <#
4 | .SYNOPSIS
5 | Runs tests as they are run in cloud test runs.
6 | .PARAMETER Configuration
7 | The configuration within which to run tests
8 | .PARAMETER Agent
9 | The name of the agent. This is used in preparing test run titles.
10 | .PARAMETER PublishResults
11 | A switch to publish results to Azure Pipelines.
12 | .PARAMETER x86
13 | A switch to run the tests in an x86 process.
14 | .PARAMETER dotnet32
15 | The path to a 32-bit dotnet executable to use.
16 | #>
17 | [CmdletBinding()]
18 | Param(
19 | [string]$Configuration='Debug',
20 | [string]$Agent='Local',
21 | [switch]$PublishResults,
22 | [switch]$x86,
23 | [string]$dotnet32
24 | )
25 |
26 | $RepoRoot = (Resolve-Path "$PSScriptRoot/..").Path
27 | $ArtifactStagingFolder = & "$PSScriptRoot/Get-ArtifactsStagingDirectory.ps1"
28 |
29 | $dotnet = 'dotnet'
30 | if ($x86) {
31 | $x86RunTitleSuffix = ", x86"
32 | if ($dotnet32) {
33 | $dotnet = $dotnet32
34 | } else {
35 | $dotnet32Possibilities = "$PSScriptRoot\../obj/tools/x86/.dotnet/dotnet.exe", "$env:AGENT_TOOLSDIRECTORY/x86/dotnet/dotnet.exe", "${env:ProgramFiles(x86)}\dotnet\dotnet.exe"
36 | $dotnet32Matches = $dotnet32Possibilities |? { Test-Path $_ }
37 | if ($dotnet32Matches) {
38 | $dotnet = Resolve-Path @($dotnet32Matches)[0]
39 | Write-Host "Running tests using `"$dotnet`"" -ForegroundColor DarkGray
40 | } else {
41 | Write-Error "Unable to find 32-bit dotnet.exe"
42 | return 1
43 | }
44 | }
45 | }
46 |
47 | & $dotnet test $RepoRoot `
48 | --no-build `
49 | -c $Configuration `
50 | --filter "TestCategory!=FailsInCloudTest" `
51 | --collect "Code Coverage;Format=cobertura" `
52 | --settings "$PSScriptRoot/test.runsettings" `
53 | --blame-hang-timeout 60s `
54 | --blame-crash `
55 | -bl:"$ArtifactStagingFolder/build_logs/test.binlog" `
56 | --diag "$ArtifactStagingFolder/test_logs/diag.log;TraceLevel=info" `
57 | --logger trx `
58 |
59 | $unknownCounter = 0
60 | Get-ChildItem -Recurse -Path $RepoRoot\test\*.trx |% {
61 | Copy-Item $_ -Destination $ArtifactStagingFolder/test_logs/
62 |
63 | if ($PublishResults) {
64 | $x = [xml](Get-Content -Path $_)
65 | $runTitle = $null
66 | if ($x.TestRun.TestDefinitions -and $x.TestRun.TestDefinitions.GetElementsByTagName('UnitTest')) {
67 | $storage = $x.TestRun.TestDefinitions.GetElementsByTagName('UnitTest')[0].storage -replace '\\','/'
68 | if ($storage -match '/(?net[^/]+)/(?:(?[^/]+)/)?(?[^/]+)\.dll$') {
69 | if ($matches.rid) {
70 | $runTitle = "$($matches.lib) ($($matches.tfm), $($matches.rid), $Agent)"
71 | } else {
72 | $runTitle = "$($matches.lib) ($($matches.tfm)$x86RunTitleSuffix, $Agent)"
73 | }
74 | }
75 | }
76 | if (!$runTitle) {
77 | $unknownCounter += 1;
78 | $runTitle = "unknown$unknownCounter ($Agent$x86RunTitleSuffix)";
79 | }
80 |
81 | Write-Host "##vso[results.publish type=VSTest;runTitle=$runTitle;publishRunAttachments=true;resultFiles=$_;failTaskOnFailedTests=true;testRunSystem=VSTS - PTR;]"
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/azure-pipelines/dotnet.yml:
--------------------------------------------------------------------------------
1 | parameters:
2 | RunTests:
3 |
4 | steps:
5 |
6 | - script: dotnet build -t:build,pack --no-restore -c $(BuildConfiguration) -warnaserror /bl:"$(Build.ArtifactStagingDirectory)/build_logs/build.binlog"
7 | displayName: 🛠 dotnet build
8 |
9 | - powershell: azure-pipelines/dotnet-test-cloud.ps1 -Configuration $(BuildConfiguration) -Agent $(Agent.JobName) -PublishResults
10 | displayName: 🧪 dotnet test
11 | condition: and(succeeded(), ${{ parameters.RunTests }})
12 |
13 | - powershell: azure-pipelines/variables/_pipelines.ps1
14 | failOnStderr: true
15 | displayName: ⚙ Update pipeline variables based on build outputs
16 | condition: succeededOrFailed()
17 |
18 | - powershell: azure-pipelines/artifacts/_pipelines.ps1 -ArtifactNameSuffix "-$(Agent.JobName)" -Verbose
19 | failOnStderr: true
20 | displayName: 📢 Publish artifacts
21 | condition: succeededOrFailed()
22 |
23 | - ${{ if and(ne(variables['codecov_token'], ''), parameters.RunTests) }}:
24 | - powershell: |
25 | $ArtifactStagingFolder = & "azure-pipelines/Get-ArtifactsStagingDirectory.ps1"
26 | $CoverageResultsFolder = Join-Path $ArtifactStagingFolder "coverageResults-$(Agent.JobName)"
27 | azure-pipelines/publish-CodeCov.ps1 -CodeCovToken "$(codecov_token)" -PathToCodeCoverage "$CoverageResultsFolder" -Name "$(Agent.JobName) Coverage Results" -Flags "$(Agent.JobName)Host,$(BuildConfiguration)"
28 | displayName: 📢 Publish code coverage results to codecov.io
29 | timeoutInMinutes: 3
30 | continueOnError: true
31 |
--------------------------------------------------------------------------------
/azure-pipelines/install-dependencies.yml:
--------------------------------------------------------------------------------
1 | parameters:
2 | initArgs:
3 |
4 | steps:
5 |
6 | - task: NuGetAuthenticate@1
7 | displayName: 🔏 Authenticate NuGet feeds
8 | inputs:
9 | forceReinstallCredentialProvider: true
10 |
11 | - powershell: |
12 | $AccessToken = '$(System.AccessToken)' # Avoid specifying the access token directly on the init.ps1 command line to avoid it showing up in errors
13 | .\init.ps1 -AccessToken $AccessToken ${{ parameters['initArgs'] }} -UpgradePrerequisites -NoNuGetCredProvider
14 | dotnet --info
15 |
16 | # Print mono version if it is present.
17 | if (Get-Command mono -ErrorAction SilentlyContinue) {
18 | mono --version
19 | }
20 | displayName: ⚙ Install prerequisites
21 |
22 | - powershell: azure-pipelines/variables/_pipelines.ps1
23 | failOnStderr: true
24 | displayName: ⚙ Set pipeline variables based on source
25 | name: SetPipelineVariables
26 |
--------------------------------------------------------------------------------
/azure-pipelines/justnugetorg.nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/azure-pipelines/publish-CodeCov.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Uploads code coverage to codecov.io
4 | .PARAMETER CodeCovToken
5 | Code coverage token to use
6 | .PARAMETER PathToCodeCoverage
7 | Path to root of code coverage files
8 | .PARAMETER Name
9 | Name to upload with codecoverge
10 | .PARAMETER Flags
11 | Flags to upload with codecoverge
12 | #>
13 | [CmdletBinding()]
14 | Param (
15 | [Parameter(Mandatory=$true)]
16 | [string]$CodeCovToken,
17 | [Parameter(Mandatory=$true)]
18 | [string]$PathToCodeCoverage,
19 | [string]$Name,
20 | [string]$Flags
21 | )
22 |
23 | $RepoRoot = (Resolve-Path "$PSScriptRoot/..").Path
24 |
25 | Get-ChildItem -Recurse -Path $PathToCodeCoverage -Filter "*.cobertura.xml" | % {
26 | $relativeFilePath = Resolve-Path -relative $_.FullName
27 |
28 | Write-Host "Uploading: $relativeFilePath" -ForegroundColor Yellow
29 | & (& "$PSScriptRoot/Get-CodeCovTool.ps1") -t $CodeCovToken -f $relativeFilePath -R $RepoRoot -F $Flags -n $Name
30 | }
31 |
--------------------------------------------------------------------------------
/azure-pipelines/publish-codecoverage.yml:
--------------------------------------------------------------------------------
1 | steps:
2 | - powershell: azure-pipelines/Merge-CodeCoverage.ps1 -Path $env:BUILD_ARTIFACTSTAGINGDIRECTORY -OutputFile coveragereport/merged.cobertura.xml -Format Cobertura -Verbose
3 | displayName: ⚙ Merge coverage
4 | - task: PublishCodeCoverageResults@1
5 | displayName: 📢 Publish code coverage results to Azure DevOps
6 | inputs:
7 | codeCoverageTool: cobertura
8 | summaryFileLocation: coveragereport/merged.cobertura.xml
9 | failIfCoverageEmpty: true
10 |
--------------------------------------------------------------------------------
/azure-pipelines/publish-deployables.yml:
--------------------------------------------------------------------------------
1 | steps:
2 | - powershell: dotnet nuget push "$(Resolve-Path '$(Build.ArtifactStagingDirectory)\deployables-Linux\')*.nupkg" -s $(ci_feed) -k azdo --skip-duplicate
3 | displayName: 📦 Push packages to CI feed
4 | condition: and(succeeded(), ne(variables['ci_feed'], ''), ne(variables['Build.Reason'], 'PullRequest'))
5 |
--------------------------------------------------------------------------------
/azure-pipelines/publish-symbols.yml:
--------------------------------------------------------------------------------
1 | steps:
2 | - task: PublishSymbols@2
3 | inputs:
4 | SymbolsFolder: $(Build.ArtifactStagingDirectory)/symbols
5 | SearchPattern: '**/*.pdb'
6 | IndexSources: false
7 | SymbolServerType: TeamServices
8 | displayName: 📢 Publish symbols
9 |
10 | - task: PublishSymbols@2
11 | inputs:
12 | SymbolsFolder: $(Build.ArtifactStagingDirectory)/test_symbols
13 | SearchPattern: '**/*.pdb'
14 | IndexSources: false
15 | SymbolServerType: TeamServices
16 | displayName: 📢 Publish test symbols
17 |
--------------------------------------------------------------------------------
/azure-pipelines/release.yml:
--------------------------------------------------------------------------------
1 | trigger: none # We only want to trigger manually or based on resources
2 | pr: none
3 |
4 | resources:
5 | pipelines:
6 | - pipeline: CI
7 | source: CSharpIsNull
8 | trigger:
9 | tags:
10 | - auto-release
11 |
12 | variables:
13 | - group: Publishing secrets
14 |
15 | jobs:
16 | - job: release
17 | pool:
18 | vmImage: ubuntu-latest
19 | steps:
20 | - checkout: none
21 | - powershell: |
22 | Write-Host "##vso[build.updatebuildnumber]$(resources.pipeline.CI.runName)"
23 | if ('$(resources.pipeline.CI.runName)'.Contains('-')) {
24 | Write-Host "##vso[task.setvariable variable=IsPrerelease]true"
25 | } else {
26 | Write-Host "##vso[task.setvariable variable=IsPrerelease]false"
27 | }
28 | displayName: ⚙ Set up pipeline
29 | - task: UseDotNet@2
30 | displayName: ⚙ Install .NET SDK
31 | inputs:
32 | packageType: sdk
33 | version: 6.x
34 | - download: CI
35 | artifact: deployables-Linux
36 | displayName: 🔻 Download deployables-Linux artifact
37 | patterns: 'deployables-Linux/*'
38 | - task: GitHubRelease@1
39 | displayName: 📢 GitHub release (create)
40 | inputs:
41 | gitHubConnection: github.com_AArnott_OAuth
42 | repositoryName: $(Build.Repository.Name)
43 | target: $(resources.pipeline.CI.sourceCommit)
44 | tagSource: userSpecifiedTag
45 | tag: v$(resources.pipeline.CI.runName)
46 | title: v$(resources.pipeline.CI.runName)
47 | isDraft: true # After running this step, visit the new draft release, edit, and publish.
48 | isPreRelease: $(IsPrerelease)
49 | assets: $(Pipeline.Workspace)/CI/deployables-Linux/*.nupkg
50 | changeLogCompareToRelease: lastNonDraftRelease
51 | changeLogType: issueBased
52 | changeLogLabels: |
53 | [
54 | { "label" : "breaking change", "displayName" : "Breaking changes", "state" : "closed" },
55 | { "label" : "bug", "displayName" : "Fixes", "state" : "closed" },
56 | { "label" : "enhancement", "displayName": "Enhancements", "state" : "closed" }
57 | ]
58 | - script: dotnet nuget push $(Pipeline.Workspace)/CI/deployables-Linux/*.nupkg -s https://api.nuget.org/v3/index.json --api-key $(NuGetOrgApiKey) --skip-duplicate
59 | displayName: 📦 Push packages to nuget.org
60 | condition: and(succeeded(), ne(variables['NuGetOrgApiKey'], ''))
61 |
--------------------------------------------------------------------------------
/azure-pipelines/test.runsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | \.dll$
10 | \.exe$
11 |
12 |
13 | xunit\..*
14 |
15 |
16 |
17 |
18 | ^System\.Diagnostics\.DebuggerHiddenAttribute$
19 | ^System\.Diagnostics\.DebuggerNonUserCodeAttribute$
20 | ^System\.CodeDom\.Compiler\.GeneratedCodeAttribute$
21 | ^System\.Diagnostics\.CodeAnalysis\.ExcludeFromCodeCoverageAttribute$
22 |
23 |
24 |
25 |
26 | True
27 |
28 | True
29 |
30 | True
31 |
32 | False
33 |
34 | False
35 |
36 | False
37 |
38 | True
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/azure-pipelines/variables/DotNetSdkVersion.ps1:
--------------------------------------------------------------------------------
1 | $globalJson = Get-Content -Path "$PSScriptRoot\..\..\global.json" | ConvertFrom-Json
2 | $globalJson.sdk.version
3 |
--------------------------------------------------------------------------------
/azure-pipelines/variables/_all.ps1:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env pwsh
2 |
3 | <#
4 | .SYNOPSIS
5 | This script returns a hashtable of build variables that should be set
6 | at the start of a build or release definition's execution.
7 | #>
8 |
9 | [CmdletBinding(SupportsShouldProcess = $true)]
10 | param (
11 | )
12 |
13 | $vars = @{}
14 |
15 | Get-ChildItem "$PSScriptRoot\*.ps1" -Exclude "_*" |% {
16 | Write-Host "Computing $($_.BaseName) variable"
17 | $vars[$_.BaseName] = & $_
18 | }
19 |
20 | $vars
21 |
--------------------------------------------------------------------------------
/azure-pipelines/variables/_pipelines.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | This script translates the variables returned by the _all.ps1 script
4 | into commands that instruct Azure Pipelines to actually set those variables for other pipeline tasks to consume.
5 |
6 | The build or release definition may have set these variables to override
7 | what the build would do. So only set them if they have not already been set.
8 | #>
9 |
10 | [CmdletBinding()]
11 | param (
12 | )
13 |
14 | (& "$PSScriptRoot\_all.ps1").GetEnumerator() |% {
15 | # Always use ALL CAPS for env var names since Azure Pipelines converts variable names to all caps and on non-Windows OS, env vars are case sensitive.
16 | $keyCaps = $_.Key.ToUpper()
17 | if ((Test-Path "env:$keyCaps") -and (Get-Content "env:$keyCaps")) {
18 | Write-Host "Skipping setting $keyCaps because variable is already set to '$(Get-Content env:$keyCaps)'." -ForegroundColor Cyan
19 | } else {
20 | Write-Host "$keyCaps=$($_.Value)" -ForegroundColor Yellow
21 | if ($env:TF_BUILD) {
22 | # Create two variables: the first that can be used by its simple name and accessible only within this job.
23 | Write-Host "##vso[task.setvariable variable=$keyCaps]$($_.Value)"
24 | # and the second that works across jobs and stages but must be fully qualified when referenced.
25 | Write-Host "##vso[task.setvariable variable=$keyCaps;isOutput=true]$($_.Value)"
26 | } elseif ($env:GITHUB_ACTIONS) {
27 | Add-Content -Path $env:GITHUB_ENV -Value "$keyCaps=$($_.Value)"
28 | }
29 | Set-Item -Path "env:$keyCaps" -Value $_.Value
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/doc/analyzers/CSIsNull001.md:
--------------------------------------------------------------------------------
1 | # CSIsNull001
2 |
3 | This analyzer flags use of `== null` to test whether a value is `null`.
4 |
5 | For example this code would produce a diagnostic:
6 |
7 | ```cs
8 | if (o == null)
9 | {
10 | }
11 | ```
12 |
13 | A code fix is offered to automate the fix, which is to use pattern syntax instead:
14 |
15 | ```cs
16 | if (o is null)
17 | {
18 | }
19 | ```
20 |
21 | Pattern syntax is preferred because if `o` is typed as a struct, the compiler will report an error when testing it for `null`, which a struct can never be.
22 |
--------------------------------------------------------------------------------
/doc/analyzers/CSIsNull002.md:
--------------------------------------------------------------------------------
1 | # CSIsNull002
2 |
3 | This analyzer flags use of `!= null` to test whether a value is not `null`.
4 |
5 | For example this code would produce a diagnostic:
6 |
7 | ```cs
8 | if (o != null)
9 | {
10 | }
11 | ```
12 |
13 | A code fix is offered to automate the fix, which is to use pattern syntax instead:
14 |
15 | ```cs
16 | if (o is object)
17 | {
18 | }
19 | ```
20 |
21 | Pattern syntax is preferred because if `o` is typed as a struct, the compiler will report an error when testing it for `null`, which a struct can never be.
22 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "8.0.300",
4 | "rollForward": "patch",
5 | "allowPrerelease": false
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/init.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | SETLOCAL
3 | set PS1UnderCmd=1
4 |
5 | :: Get the datetime in a format that can go in a filename.
6 | set _my_datetime=%date%_%time%
7 | set _my_datetime=%_my_datetime: =_%
8 | set _my_datetime=%_my_datetime::=%
9 | set _my_datetime=%_my_datetime:/=_%
10 | set _my_datetime=%_my_datetime:.=_%
11 | set CmdEnvScriptPath=%temp%\envvarscript_%_my_datetime%.cmd
12 |
13 | powershell.exe -NoProfile -NoLogo -ExecutionPolicy bypass -Command "try { & '%~dpn0.ps1' %*; exit $LASTEXITCODE } catch { write-host $_; exit 1 }"
14 |
15 | :: Set environment variables in the parent cmd.exe process.
16 | IF EXIST "%CmdEnvScriptPath%" (
17 | ENDLOCAL
18 | CALL "%CmdEnvScriptPath%"
19 | DEL "%CmdEnvScriptPath%"
20 | )
21 |
--------------------------------------------------------------------------------
/init.ps1:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env pwsh
2 |
3 | <#
4 | .SYNOPSIS
5 | Installs dependencies required to build and test the projects in this repository.
6 | .DESCRIPTION
7 | This MAY not require elevation, as the SDK and runtimes are installed to a per-user location,
8 | unless the `-InstallLocality` switch is specified directing to a per-repo or per-machine location.
9 | See detailed help on that switch for more information.
10 |
11 | The CmdEnvScriptPath environment variable may be optionally set to a path to a cmd shell script to be created (or appended to if it already exists) that will set the environment variables in cmd.exe that are set within the PowerShell environment.
12 | This is used by init.cmd in order to reapply any new environment variables to the parent cmd.exe process that were set in the powershell child process.
13 | .PARAMETER InstallLocality
14 | A value indicating whether dependencies should be installed locally to the repo or at a per-user location.
15 | Per-user allows sharing the installed dependencies across repositories and allows use of a shared expanded package cache.
16 | Visual Studio will only notice and use these SDKs/runtimes if VS is launched from the environment that runs this script.
17 | Per-repo allows for high isolation, allowing for a more precise recreation of the environment within an Azure Pipelines build.
18 | When using 'repo', environment variables are set to cause the locally installed dotnet SDK to be used.
19 | Per-repo can lead to file locking issues when dotnet.exe is left running as a build server and can be mitigated by running `dotnet build-server shutdown`.
20 | Per-machine requires elevation and will download and install all SDKs and runtimes to machine-wide locations so all applications can find it.
21 | .PARAMETER NoPrerequisites
22 | Skips the installation of prerequisite software (e.g. SDKs, tools).
23 | .PARAMETER NoNuGetCredProvider
24 | Skips the installation of the NuGet credential provider. Useful in pipelines with the `NuGetAuthenticate` task, as a workaround for https://github.com/microsoft/artifacts-credprovider/issues/244.
25 | This switch is ignored and installation is skipped when -NoPrerequisites is specified.
26 | .PARAMETER UpgradePrerequisites
27 | Takes time to install prerequisites even if they are already present in case they need to be upgraded.
28 | No effect if -NoPrerequisites is specified.
29 | .PARAMETER NoRestore
30 | Skips the package restore step.
31 | .PARAMETER NoToolRestore
32 | Skips the dotnet tool restore step.
33 | .PARAMETER AccessToken
34 | An optional access token for authenticating to Azure Artifacts authenticated feeds.
35 | .PARAMETER Interactive
36 | Runs NuGet restore in interactive mode. This can turn authentication failures into authentication challenges.
37 | #>
38 | [CmdletBinding(SupportsShouldProcess = $true)]
39 | Param (
40 | [ValidateSet('repo', 'user', 'machine')]
41 | [string]$InstallLocality = 'user',
42 | [Parameter()]
43 | [switch]$NoPrerequisites,
44 | [Parameter()]
45 | [switch]$NoNuGetCredProvider,
46 | [Parameter()]
47 | [switch]$UpgradePrerequisites,
48 | [Parameter()]
49 | [switch]$NoRestore,
50 | [Parameter()]
51 | [switch]$NoToolRestore,
52 | [Parameter()]
53 | [string]$AccessToken,
54 | [Parameter()]
55 | [switch]$Interactive
56 | )
57 |
58 | $EnvVars = @{}
59 | $PrependPath = @()
60 |
61 | if (!$NoPrerequisites) {
62 | if (!$NoNuGetCredProvider) {
63 | & "$PSScriptRoot\tools\Install-NuGetCredProvider.ps1" -AccessToken $AccessToken -Force:$UpgradePrerequisites
64 | }
65 |
66 | & "$PSScriptRoot\tools\Install-DotNetSdk.ps1" -InstallLocality $InstallLocality
67 | if ($LASTEXITCODE -eq 3010) {
68 | Exit 3010
69 | }
70 |
71 | # The procdump tool and env var is required for dotnet test to collect hang/crash dumps of tests.
72 | # But it only works on Windows.
73 | if ($env:OS -eq 'Windows_NT') {
74 | $EnvVars['PROCDUMP_PATH'] = & "$PSScriptRoot\azure-pipelines\Get-ProcDump.ps1"
75 | }
76 | }
77 |
78 | # Workaround nuget credential provider bug that causes very unreliable package restores on Azure Pipelines
79 | $env:NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS = 20
80 | $env:NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS = 20
81 |
82 | Push-Location $PSScriptRoot
83 | try {
84 | $HeaderColor = 'Green'
85 |
86 | $RestoreArguments = @()
87 | if ($Interactive) {
88 | $RestoreArguments += '--interactive'
89 | }
90 |
91 | if (!$NoRestore -and $PSCmdlet.ShouldProcess("NuGet packages", "Restore")) {
92 | Write-Host "Restoring NuGet packages" -ForegroundColor $HeaderColor
93 | dotnet restore @RestoreArguments
94 | if ($lastexitcode -ne 0) {
95 | throw "Failure while restoring packages."
96 | }
97 | }
98 |
99 | if (!$NoToolRestore -and $PSCmdlet.ShouldProcess("dotnet tool", "restore")) {
100 | dotnet tool restore @RestoreArguments
101 | if ($lastexitcode -ne 0) {
102 | throw "Failure while restoring dotnet CLI tools."
103 | }
104 | }
105 |
106 | & "$PSScriptRoot/tools/Set-EnvVars.ps1" -Variables $EnvVars -PrependPath $PrependPath | Out-Null
107 | }
108 | catch {
109 | Write-Error $error[0]
110 | exit $lastexitcode
111 | }
112 | finally {
113 | Pop-Location
114 | }
115 |
--------------------------------------------------------------------------------
/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/settings.VisualStudio.json:
--------------------------------------------------------------------------------
1 | {
2 | "textEditor.codeCleanup.profile": "profile1"
3 | }
4 |
--------------------------------------------------------------------------------
/src/.editorconfig:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AArnott/CSharpIsNull/e29603ae4a24d6db86811135401ad5905a87d593/src/.editorconfig
--------------------------------------------------------------------------------
/src/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Andrew Arnott. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Runtime.InteropServices;
5 |
6 | [assembly: DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
7 |
--------------------------------------------------------------------------------
/src/AssemblyInfo.vb:
--------------------------------------------------------------------------------
1 | ' Copyright (c) COMPANY-PLACEHOLDER. All rights reserved.
2 | ' Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | Imports System.Runtime.InteropServices
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer.CodeFixes/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Andrew Arnott. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Resources;
5 |
6 | [assembly: NeutralResourcesLanguage("en-US")]
7 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer.CodeFixes/CSIsNull001Fixer.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Andrew Arnott. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Collections.Immutable;
5 | using Microsoft.CodeAnalysis;
6 | using Microsoft.CodeAnalysis.CodeActions;
7 | using Microsoft.CodeAnalysis.CodeFixes;
8 | using Microsoft.CodeAnalysis.CSharp;
9 | using Microsoft.CodeAnalysis.CSharp.Syntax;
10 | using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
11 |
12 | namespace CSharpIsNullAnalyzer;
13 |
14 | ///
15 | /// Provides code fixes for .
16 | ///
17 | [ExportCodeFixProvider(LanguageNames.CSharp)]
18 | public class CSIsNull001Fixer : CodeFixProvider
19 | {
20 | private static readonly ImmutableArray ReusableFixableDiagnosticIds = ImmutableArray.Create(
21 | CSIsNull001.Id);
22 |
23 | private static readonly ConstantPatternSyntax NullPattern = ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression));
24 |
25 | ///
26 | public override ImmutableArray FixableDiagnosticIds => ReusableFixableDiagnosticIds;
27 |
28 | ///
29 | public override async Task RegisterCodeFixesAsync(CodeFixContext context)
30 | {
31 | foreach (Diagnostic diagnostic in context.Diagnostics)
32 | {
33 | if (diagnostic.Id == CSIsNull001.Id)
34 | {
35 | SyntaxNode? syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
36 | BinaryExpressionSyntax? expr = syntaxRoot?.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true).FirstAncestorOrSelf();
37 | if (expr is not null)
38 | {
39 | context.RegisterCodeFix(
40 | CodeAction.Create(
41 | Strings.CSIsNull001_FixTitle,
42 | ct => expr.ReplaceBinaryExpressionWithIsPattern(context.Document, syntaxRoot!, NullPattern),
43 | equivalenceKey: "isNull"),
44 | diagnostic);
45 | }
46 | }
47 | }
48 | }
49 |
50 | ///
51 | public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
52 | }
53 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer.CodeFixes/CSIsNull002Fixer.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Andrew Arnott. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Collections.Immutable;
5 | using Microsoft.CodeAnalysis;
6 | using Microsoft.CodeAnalysis.CodeActions;
7 | using Microsoft.CodeAnalysis.CodeFixes;
8 | using Microsoft.CodeAnalysis.CSharp;
9 | using Microsoft.CodeAnalysis.CSharp.Syntax;
10 | using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
11 |
12 | namespace CSharpIsNullAnalyzer;
13 |
14 | ///
15 | /// Provides code fixes for .
16 | ///
17 | [ExportCodeFixProvider(LanguageNames.CSharp)]
18 | public class CSIsNull002Fixer : CodeFixProvider
19 | {
20 | ///
21 | /// The equivalence key used for the code fix that uses is object syntax.
22 | ///
23 | public const string IsObjectEquivalenceKey = "IsObject";
24 |
25 | ///
26 | /// The equivalence key used for the code fix that uses is not null syntax.
27 | ///
28 | public const string IsNotNullEquivalenceKey = "IsNotNull";
29 |
30 | private static readonly ImmutableArray ReusableFixableDiagnosticIds = ImmutableArray.Create(
31 | CSIsNull002.Id);
32 |
33 | private static readonly ExpressionSyntax ObjectLiteral = PredefinedType(Token(SyntaxKind.ObjectKeyword));
34 | private static readonly PatternSyntax NotNullPattern = UnaryPattern(Token(SyntaxKind.NotKeyword), ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression)));
35 |
36 | ///
37 | public override ImmutableArray FixableDiagnosticIds => ReusableFixableDiagnosticIds;
38 |
39 | ///
40 | public override async Task RegisterCodeFixesAsync(CodeFixContext context)
41 | {
42 | foreach (Diagnostic diagnostic in context.Diagnostics)
43 | {
44 | if (diagnostic.Id == CSIsNull002.Id)
45 | {
46 | SyntaxNode? syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
47 | BinaryExpressionSyntax? expr = syntaxRoot?.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true).FirstAncestorOrSelf();
48 | if (expr is not null)
49 | {
50 | if (context.Document.Project.ParseOptions is CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp9 } &&
51 | diagnostic.Properties.ContainsKey(CSIsNull002.OfferIsNotNullFixKey))
52 | {
53 | context.RegisterCodeFix(
54 | CodeAction.Create(
55 | Strings.CSIsNull002_Fix2Title,
56 | ct => expr.ReplaceBinaryExpressionWithIsPattern(context.Document, syntaxRoot!, NotNullPattern),
57 | equivalenceKey: IsNotNullEquivalenceKey),
58 | diagnostic);
59 | }
60 |
61 | context.RegisterCodeFix(
62 | CodeAction.Create(
63 | Strings.CSIsNull002_Fix1Title,
64 | ct => expr.ReplaceBinaryExpressionWithIsExpression(context.Document, syntaxRoot!, ObjectLiteral),
65 | equivalenceKey: IsObjectEquivalenceKey),
66 | diagnostic);
67 | }
68 | }
69 | }
70 | }
71 |
72 | ///
73 | public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
74 | }
75 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer.CodeFixes/CSharpIsNullAnalyzer.CodeFixes.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CSharpIsNullAnalyzer
5 | CSharpIsNullAnalyzer
6 | true
7 | C# analyzer to encourage use of `is null` and `is object` syntax over `== null` or `!= null`.
8 | C#;Analyzers
9 | false
10 | $(TargetsForTfmSpecificContentInPackage);PackBuildOutputs
11 | true
12 | false
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | True
32 | True
33 | Strings.resx
34 |
35 |
36 |
37 |
38 |
39 | ResXFileCodeGenerator
40 | Strings.Designer.cs
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer.CodeFixes/IsFixer.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Andrew Arnott. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Linq.Expressions;
5 | using Microsoft.CodeAnalysis;
6 | using Microsoft.CodeAnalysis.CodeFixes;
7 | using Microsoft.CodeAnalysis.CSharp;
8 | using Microsoft.CodeAnalysis.CSharp.Syntax;
9 | using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
10 |
11 | namespace CSharpIsNullAnalyzer
12 | {
13 | ///
14 | /// Provides shared methods to replace binary expression with 'is' expression/Pattern syntax.
15 | ///
16 | internal static class IsFixer
17 | {
18 | ///
19 | /// Replaces the with an 'is' expressionOrPattern.
20 | ///
21 | /// The binary expressionOrPattern to replace.
22 | /// The document in which to do the fix.
23 | /// The root SyntaxNode to update.
24 | /// The expressionOrPattern to replace the binary expressio with.
25 | /// Document with binary expressionOrPattern replace with expressionOrPattern syntax.
26 | public static Task ReplaceBinaryExpressionWithIsPattern(this BinaryExpressionSyntax expr, Document document, SyntaxNode syntaxRoot, PatternSyntax pattern)
27 | => expr.ReplaceBinaryExpressionWithIsExpressionOrPattern(document, syntaxRoot, pattern, IsPatternExpression);
28 |
29 | ///
30 | /// Replaces the with an 'is' expressionOrPattern.
31 | ///
32 | /// The binary expressionOrPattern to replace.
33 | /// The document in which to do the fix.
34 | /// The root SyntaxNode to update.
35 | /// The expressionOrPattern to replace the binary expressio with.
36 | /// Document with binary expressionOrPattern replace with expressionOrPattern syntax.
37 | public static Task ReplaceBinaryExpressionWithIsExpression(this BinaryExpressionSyntax expr, Document document, SyntaxNode syntaxRoot, ExpressionSyntax expression)
38 | => expr.ReplaceBinaryExpressionWithIsExpressionOrPattern(document, syntaxRoot, expression, IsExpression);
39 |
40 | private static Task ReplaceBinaryExpressionWithIsExpressionOrPattern(
41 | this BinaryExpressionSyntax expr,
42 | Document document,
43 | SyntaxNode syntaxRoot,
44 | T expressionOrPattern,
45 | Func create)
46 | where T : ExpressionOrPatternSyntax
47 | {
48 | T expressionWithTrivia = expressionOrPattern.WithTriviaFrom(expr.Right);
49 | ExpressionSyntax changedExpression = expr.Right is LiteralExpressionSyntax { RawKind: (int)SyntaxKind.NullLiteralExpression or (int)SyntaxKind.DefaultLiteralExpression } or DefaultExpressionSyntax
50 | ? create(expr.Left, expressionWithTrivia)
51 | : create(expr.Right.WithoutTrailingTrivia().WithTrailingTrivia(Space), expressionWithTrivia);
52 | SyntaxNode updatedSyntaxRoot = syntaxRoot.ReplaceNode(expr, changedExpression);
53 | return Task.FromResult(document.WithSyntaxRoot(updatedSyntaxRoot));
54 | }
55 |
56 | ///
57 | /// Creates a new IsExpressionSyntax instance.
58 | ///
59 | private static BinaryExpressionSyntax IsExpression(ExpressionSyntax left, ExpressionSyntax right)
60 | => BinaryExpression(SyntaxKind.IsExpression, left, right);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer.CodeFixes/Strings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace CSharpIsNullAnalyzer {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Strings {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Strings() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CSharpIsNullAnalyzer.Strings", typeof(Strings).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string similar to Use `is null` instead.
65 | ///
66 | internal static string CSIsNull001_FixTitle {
67 | get {
68 | return ResourceManager.GetString("CSIsNull001_FixTitle", resourceCulture);
69 | }
70 | }
71 |
72 | ///
73 | /// Looks up a localized string similar to Use `is object` instead.
74 | ///
75 | internal static string CSIsNull002_Fix1Title {
76 | get {
77 | return ResourceManager.GetString("CSIsNull002_Fix1Title", resourceCulture);
78 | }
79 | }
80 |
81 | ///
82 | /// Looks up a localized string similar to Use `is not null` instead.
83 | ///
84 | internal static string CSIsNull002_Fix2Title {
85 | get {
86 | return ResourceManager.GetString("CSIsNull002_Fix2Title", resourceCulture);
87 | }
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer.CodeFixes/Strings.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | Use `is null` instead
122 |
123 |
124 | Use `is object` instead
125 |
126 |
127 | Use `is not null` instead
128 |
129 |
130 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer.CodeFixes/tools/install.ps1:
--------------------------------------------------------------------------------
1 | param($installPath, $toolsPath, $package, $project)
2 |
3 | if($project.Object.SupportsPackageDependencyResolution)
4 | {
5 | if($project.Object.SupportsPackageDependencyResolution())
6 | {
7 | # Do not install analyzers via install.ps1, instead let the project system handle it.
8 | return
9 | }
10 | }
11 |
12 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve
13 |
14 | foreach($analyzersPath in $analyzersPaths)
15 | {
16 | if (Test-Path $analyzersPath)
17 | {
18 | # Install the language agnostic analyzers.
19 | foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll)
20 | {
21 | if($project.Object.AnalyzerReferences)
22 | {
23 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
24 | }
25 | }
26 | }
27 | }
28 |
29 | # $project.Type gives the language name like (C# or VB.NET)
30 | $languageFolder = ""
31 | if($project.Type -eq "C#")
32 | {
33 | $languageFolder = "cs"
34 | }
35 | if($project.Type -eq "VB.NET")
36 | {
37 | $languageFolder = "vb"
38 | }
39 | if($languageFolder -eq "")
40 | {
41 | return
42 | }
43 |
44 | foreach($analyzersPath in $analyzersPaths)
45 | {
46 | # Install language specific analyzers.
47 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder
48 | if (Test-Path $languageAnalyzersPath)
49 | {
50 | foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll)
51 | {
52 | if($project.Object.AnalyzerReferences)
53 | {
54 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer.CodeFixes/tools/uninstall.ps1:
--------------------------------------------------------------------------------
1 | param($installPath, $toolsPath, $package, $project)
2 |
3 | if($project.Object.SupportsPackageDependencyResolution)
4 | {
5 | if($project.Object.SupportsPackageDependencyResolution())
6 | {
7 | # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it.
8 | return
9 | }
10 | }
11 |
12 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve
13 |
14 | foreach($analyzersPath in $analyzersPaths)
15 | {
16 | # Uninstall the language agnostic analyzers.
17 | if (Test-Path $analyzersPath)
18 | {
19 | foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll)
20 | {
21 | if($project.Object.AnalyzerReferences)
22 | {
23 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
24 | }
25 | }
26 | }
27 | }
28 |
29 | # $project.Type gives the language name like (C# or VB.NET)
30 | $languageFolder = ""
31 | if($project.Type -eq "C#")
32 | {
33 | $languageFolder = "cs"
34 | }
35 | if($project.Type -eq "VB.NET")
36 | {
37 | $languageFolder = "vb"
38 | }
39 | if($languageFolder -eq "")
40 | {
41 | return
42 | }
43 |
44 | foreach($analyzersPath in $analyzersPaths)
45 | {
46 | # Uninstall language specific analyzers.
47 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder
48 | if (Test-Path $languageAnalyzersPath)
49 | {
50 | foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll)
51 | {
52 | if($project.Object.AnalyzerReferences)
53 | {
54 | try
55 | {
56 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
57 | }
58 | catch
59 | {
60 |
61 | }
62 | }
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer/AnalyzerReleases.Shipped.md:
--------------------------------------------------------------------------------
1 | ; Shipped analyzer releases
2 | ; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
3 |
4 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer/AnalyzerReleases.Unshipped.md:
--------------------------------------------------------------------------------
1 | ; Unshipped analyzer release
2 | ; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
3 |
4 | ### New Rules
5 | Rule ID | Category | Severity | Notes
6 | --------|----------|----------|-------
7 | CSIsNull001 | Usage | Info | CSIsNull001
8 | CSIsNull002 | Usage | Info | CSIsNull002
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Andrew Arnott. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Resources;
5 |
6 | [assembly: NeutralResourcesLanguage("en-US")]
7 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer/CSIsNull001.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Andrew Arnott. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Collections.Immutable;
5 | using Microsoft.CodeAnalysis;
6 | using Microsoft.CodeAnalysis.CSharp.Syntax;
7 | using Microsoft.CodeAnalysis.Diagnostics;
8 | using Microsoft.CodeAnalysis.Operations;
9 | using Microsoft.CodeAnalysis.Text;
10 |
11 | namespace CSharpIsNullAnalyzer;
12 |
13 | ///
14 | /// An analyzer that finds == null expressions.
15 | ///
16 | [DiagnosticAnalyzer(LanguageNames.CSharp)]
17 | public class CSIsNull001 : DiagnosticAnalyzer
18 | {
19 | ///
20 | /// The ID for diagnostics reported by this analyzer.
21 | ///
22 | public const string Id = "CSIsNull001";
23 |
24 | ///
25 | /// The descriptor used for diagnostics created by this rule.
26 | ///
27 | internal static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(
28 | id: Id,
29 | title: Strings.CSIsNull001_Title,
30 | messageFormat: Strings.CSIsNull001_MessageFormat,
31 | helpLinkUri: Utils.GetHelpLink(Id),
32 | category: "Usage",
33 | defaultSeverity: DiagnosticSeverity.Info,
34 | isEnabledByDefault: true);
35 |
36 | ///
37 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Descriptor);
38 |
39 | ///
40 | public override void Initialize(AnalysisContext context)
41 | {
42 | if (context is null)
43 | {
44 | throw new ArgumentNullException(nameof(context));
45 | }
46 |
47 | context.EnableConcurrentExecution();
48 | context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics);
49 |
50 | context.RegisterCompilationStartAction(
51 | startContext =>
52 | {
53 | INamedTypeSymbol? linqExpressionType = startContext.Compilation.GetTypeByMetadataName(WellKnownTypeNames.SystemLinqExpressionsExpression1);
54 | startContext.RegisterOperationAction(
55 | ctxt =>
56 | {
57 | if (ctxt.Operation.Type.SpecialType == SpecialType.System_Boolean)
58 | {
59 | if (ctxt.Operation is IBinaryOperation { OperatorKind: BinaryOperatorKind.Equals } binaryOp)
60 | {
61 | Location? location = null;
62 | if (binaryOp.RightOperand.IsNullCheck())
63 | {
64 | location = binaryOp.RightOperand.Syntax.GetLocation();
65 | if (binaryOp.Syntax is BinaryExpressionSyntax { OperatorToken: { } operatorLocation, Right: { } right })
66 | {
67 | location = ctxt.Operation.Syntax.SyntaxTree.GetLocation(new TextSpan(operatorLocation.SpanStart, right.Span.End - operatorLocation.SpanStart));
68 | }
69 | }
70 | else if (binaryOp.LeftOperand.IsNullCheck())
71 | {
72 | location = binaryOp.LeftOperand.Syntax.GetLocation();
73 | if (binaryOp.Syntax is BinaryExpressionSyntax { OperatorToken: { } operatorLocation, Left: { } left })
74 | {
75 | location = ctxt.Operation.Syntax.SyntaxTree.GetLocation(new TextSpan(left.SpanStart, operatorLocation.Span.End - left.SpanStart));
76 | }
77 | }
78 |
79 | if (location is not null && !binaryOp.IsWithinExpressionTree(linqExpressionType))
80 | {
81 | ctxt.ReportDiagnostic(Diagnostic.Create(Descriptor, location));
82 | }
83 | }
84 | }
85 | },
86 | OperationKind.Binary);
87 | });
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer/CSIsNull002.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Andrew Arnott. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Collections.Immutable;
5 | using Microsoft.CodeAnalysis;
6 | using Microsoft.CodeAnalysis.CSharp.Syntax;
7 | using Microsoft.CodeAnalysis.Diagnostics;
8 | using Microsoft.CodeAnalysis.Operations;
9 | using Microsoft.CodeAnalysis.Text;
10 |
11 | namespace CSharpIsNullAnalyzer;
12 |
13 | ///
14 | /// An analyzer that finds != null expressions.
15 | ///
16 | [DiagnosticAnalyzer(LanguageNames.CSharp)]
17 | public class CSIsNull002 : DiagnosticAnalyzer
18 | {
19 | ///
20 | /// The ID for diagnostics reported by this analyzer.
21 | ///
22 | public const string Id = "CSIsNull002";
23 |
24 | ///
25 | /// A key to a property that will be set in a diagnostic if the is not null code fix should be offered.
26 | ///
27 | public const string OfferIsNotNullFixKey = "OfferIsNotNullFix";
28 |
29 | ///
30 | /// The descriptor used for diagnostics created by this rule.
31 | ///
32 | internal static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(
33 | id: Id,
34 | title: Strings.CSIsNull002_Title,
35 | messageFormat: Strings.CSIsNull002_MessageFormat,
36 | helpLinkUri: Utils.GetHelpLink(Id),
37 | category: "Usage",
38 | defaultSeverity: DiagnosticSeverity.Info,
39 | isEnabledByDefault: true);
40 |
41 | ///
42 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Descriptor);
43 |
44 | ///
45 | public override void Initialize(AnalysisContext context)
46 | {
47 | if (context is null)
48 | {
49 | throw new ArgumentNullException(nameof(context));
50 | }
51 |
52 | context.EnableConcurrentExecution();
53 | context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics);
54 |
55 | context.RegisterCompilationStartAction(
56 | startContext =>
57 | {
58 | INamedTypeSymbol? linqExpressionType = startContext.Compilation.GetTypeByMetadataName(WellKnownTypeNames.SystemLinqExpressionsExpression1);
59 | startContext.RegisterOperationAction(
60 | ctxt =>
61 | {
62 | if (ctxt.Operation.Type.SpecialType == SpecialType.System_Boolean)
63 | {
64 | if (ctxt.Operation is IBinaryOperation { OperatorKind: BinaryOperatorKind.NotEquals } binaryOp)
65 | {
66 | Location? location = null;
67 | if (binaryOp.RightOperand.IsNullCheck())
68 | {
69 | location = binaryOp.RightOperand.Syntax.GetLocation();
70 | if (binaryOp.Syntax is BinaryExpressionSyntax { OperatorToken: { } operatorLocation, Right: { } right })
71 | {
72 | location = ctxt.Operation.Syntax.SyntaxTree.GetLocation(new TextSpan(operatorLocation.SpanStart, right.Span.End - operatorLocation.SpanStart));
73 | }
74 | }
75 | else if (binaryOp.LeftOperand.IsNullCheck())
76 | {
77 | location = binaryOp.LeftOperand.Syntax.GetLocation();
78 | if (binaryOp.Syntax is BinaryExpressionSyntax { OperatorToken: { } operatorLocation, Left: { } left })
79 | {
80 | location = ctxt.Operation.Syntax.SyntaxTree.GetLocation(new TextSpan(left.SpanStart, operatorLocation.Span.End - left.SpanStart));
81 | }
82 | }
83 |
84 | if (location is not null)
85 | {
86 | ImmutableDictionary properties = ImmutableDictionary.Empty;
87 | if (!binaryOp.IsWithinExpressionTree(linqExpressionType))
88 | {
89 | properties = properties.Add(OfferIsNotNullFixKey, "true");
90 | }
91 |
92 | ctxt.ReportDiagnostic(Diagnostic.Create(Descriptor, location, properties));
93 | }
94 | }
95 | }
96 | },
97 | OperationKind.Binary);
98 | });
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer/CSharpIsNullAnalyzer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 | CSharpIsNullJustAnalyzer
5 | true
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | True
16 | True
17 | Strings.resx
18 |
19 |
20 |
21 |
22 |
23 | ResXFileCodeGenerator
24 | Strings.Designer.cs
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer/IOperationExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Andrew Arnott. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Collections.Immutable;
5 | using System.Diagnostics.CodeAnalysis;
6 | using Microsoft.CodeAnalysis;
7 | using Microsoft.CodeAnalysis.Operations;
8 |
9 | namespace CSharpIsNullAnalyzer;
10 |
11 | ///
12 | /// Extensions methods for the interface.
13 | ///
14 | internal static class IOperationExtensions
15 | {
16 | private static readonly ImmutableArray LambdaAndLocalFunctionKinds =
17 | ImmutableArray.Create(OperationKind.AnonymousFunction, OperationKind.LocalFunction);
18 |
19 | ///
20 | /// Tests whether an operation falls within an expression tree.
21 | ///
22 | /// The operation.
23 | /// The result of a call to , passing in
24 | /// the full name of the type.
25 | /// A value indicating whether this operation falls within an expression tree.
26 | internal static bool IsWithinExpressionTree(this IOperation operation, [NotNullWhen(true)] INamedTypeSymbol? linqExpressionTreeType)
27 | => linqExpressionTreeType != null
28 | && operation.GetAncestor(LambdaAndLocalFunctionKinds)?.Parent?.Type?.OriginalDefinition is { } lambdaType
29 | && linqExpressionTreeType.Equals(lambdaType, SymbolEqualityComparer.Default);
30 |
31 | ///
32 | /// Gets the first ancestor of this operation with:
33 | /// 1. Any OperationKind from the specified .
34 | /// 2. If is non-null, it succeeds for the ancestor.
35 | /// Returns null if there is no such ancestor.
36 | ///
37 | /// The operation to start the search.
38 | /// The kinds of ancestors to look for.
39 | /// An optional test to apply before matching.
40 | /// The matching ancestor operation, if found.
41 | internal static IOperation? GetAncestor(this IOperation root, ImmutableArray ancestorKinds, Func? predicate = null)
42 | {
43 | IOperation? ancestor = root;
44 | do
45 | {
46 | ancestor = ancestor.Parent;
47 | }
48 | while (ancestor is object && !ancestorKinds.Contains(ancestor.Kind));
49 |
50 | if (ancestor is object)
51 | {
52 | if (predicate is object && !predicate(ancestor))
53 | {
54 | return GetAncestor(ancestor, ancestorKinds, predicate);
55 | }
56 |
57 | return ancestor;
58 | }
59 | else
60 | {
61 | return default;
62 | }
63 | }
64 |
65 | ///
66 | /// Checks if this is a null check.
67 | ///
68 | /// The operation to check.
69 | /// if this operation checks for null, otherwise.
70 | internal static bool IsNullCheck(this IOperation operation) =>
71 | operation is (IConversionOperation or ILiteralOperation or IDefaultValueOperation) and { ConstantValue: { HasValue: true, Value: null } };
72 | }
73 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer/Strings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace CSharpIsNullAnalyzer {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Strings {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Strings() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CSharpIsNullAnalyzer.Strings", typeof(Strings).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string similar to Use `is null` instead of `== null` for null checks so the compiler can help you avoid testing struct equality to null..
65 | ///
66 | internal static string CSIsNull001_MessageFormat {
67 | get {
68 | return ResourceManager.GetString("CSIsNull001_MessageFormat", resourceCulture);
69 | }
70 | }
71 |
72 | ///
73 | /// Looks up a localized string similar to Use `is null` for null checks.
74 | ///
75 | internal static string CSIsNull001_Title {
76 | get {
77 | return ResourceManager.GetString("CSIsNull001_Title", resourceCulture);
78 | }
79 | }
80 |
81 | ///
82 | /// Looks up a localized string similar to Use `is object` instead of `!= null` for null checks so the compiler can help you avoid testing struct equality to null..
83 | ///
84 | internal static string CSIsNull002_MessageFormat {
85 | get {
86 | return ResourceManager.GetString("CSIsNull002_MessageFormat", resourceCulture);
87 | }
88 | }
89 |
90 | ///
91 | /// Looks up a localized string similar to Use `is object` for non-null checks.
92 | ///
93 | internal static string CSIsNull002_Title {
94 | get {
95 | return ResourceManager.GetString("CSIsNull002_Title", resourceCulture);
96 | }
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer/Strings.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | Use `is null` instead of `== null` for null checks so the compiler can help you avoid testing struct equality to null.
122 |
123 |
124 | Use `is null` for null checks
125 |
126 |
127 | Use `is object` instead of `!= null` for null checks so the compiler can help you avoid testing struct equality to null.
128 |
129 |
130 | Use `is object` for non-null checks
131 |
132 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer/Utils.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Andrew Arnott. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace CSharpIsNullAnalyzer;
5 |
6 | ///
7 | /// Utility methods for analyzers.
8 | ///
9 | internal static class Utils
10 | {
11 | ///
12 | /// Gets the URL to the help topic for a particular analyzer.
13 | ///
14 | /// The ID of the analyzer.
15 | /// The URL for the analyzer's documentation.
16 | internal static string GetHelpLink(string analyzerId)
17 | {
18 | return $"https://github.com/AArnott/CSharpIsNull/blob/main/doc/analyzers/{analyzerId}.md";
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/CSharpIsNullAnalyzer/WellKnownTypeNames.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Andrew Arnott. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace CSharpIsNullAnalyzer;
5 |
6 | ///
7 | /// Well known type names.
8 | ///
9 | internal static class WellKnownTypeNames
10 | {
11 | ///
12 | /// The full name of the type.
13 | ///
14 | internal const string SystemLinqExpressionsExpression1 = "System.Linq.Expressions.Expression`1";
15 | }
16 |
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | README.md
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | netstandard2.0
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/strongname.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AArnott/CSharpIsNull/e29603ae4a24d6db86811135401ad5905a87d593/strongname.snk
--------------------------------------------------------------------------------
/stylecop.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
3 | "settings": {
4 | "documentationRules": {
5 | "companyName": "Andrew Arnott",
6 | "copyrightText": "Copyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName} license. See {licenseFile} file in the project root for full license information.",
7 | "variables": {
8 | "licenseName": "MIT",
9 | "licenseFile": "LICENSE"
10 | },
11 | "fileNamingConvention": "metadata",
12 | "xmlHeader": false
13 | },
14 | "orderingRules": {
15 | "usingDirectivesPlacement": "outsideNamespace"
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/test/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 |
3 | # SA1600: Elements should be documented
4 | dotnet_diagnostic.SA1600.severity = silent
5 |
6 | # SA1601: Partial elements should be documented
7 | dotnet_diagnostic.SA1601.severity = silent
8 |
9 | # SA1602: Enumeration items should be documented
10 | dotnet_diagnostic.SA1602.severity = silent
11 |
12 | # SA1615: Element return value should be documented
13 | dotnet_diagnostic.SA1615.severity = silent
14 |
15 | # VSTHRD103: Call async methods when in an async method
16 | dotnet_diagnostic.VSTHRD103.severity = silent
17 |
18 | # VSTHRD111: Use .ConfigureAwait(bool)
19 | dotnet_diagnostic.VSTHRD111.severity = none
20 |
21 | # VSTHRD200: Use Async suffix for async methods
22 | dotnet_diagnostic.VSTHRD200.severity = silent
23 |
24 | # CA1014: Mark assemblies with CLSCompliant
25 | dotnet_diagnostic.CA1014.severity = none
26 |
27 | # CA1050: Declare types in namespaces
28 | dotnet_diagnostic.CA1050.severity = none
29 |
30 | # CA1303: Do not pass literals as localized parameters
31 | dotnet_diagnostic.CA1303.severity = none
32 |
33 | # CS1591: Missing XML comment for publicly visible type or member
34 | dotnet_diagnostic.CS1591.severity = silent
35 |
36 | # CA1707: Identifiers should not contain underscores
37 | dotnet_diagnostic.CA1707.severity = silent
38 |
39 | # CA1062: Validate arguments of public methods
40 | dotnet_diagnostic.CA1062.severity = suggestion
41 |
42 | # CA1063: Implement IDisposable Correctly
43 | dotnet_diagnostic.CA1063.severity = silent
44 |
45 | # CA1816: Dispose methods should call SuppressFinalize
46 | dotnet_diagnostic.CA1816.severity = silent
47 |
48 | # CA2007: Consider calling ConfigureAwait on the awaited task
49 | dotnet_diagnostic.CA2007.severity = none
50 |
51 | # SA1401: Fields should be private
52 | dotnet_diagnostic.SA1401.severity = silent
53 |
54 | # SA1133: Do not combine attributes
55 | dotnet_diagnostic.SA1133.severity = silent
56 |
--------------------------------------------------------------------------------
/test/CSharpIsNullAnalyzer.Tests/CSIsNull001Tests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Andrew Arnott. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Xunit;
5 | using VerifyCS = CSharpCodeFixVerifier;
6 |
7 | public class CsIsNull001Tests
8 | {
9 | [Fact]
10 | public async Task EqualsNullInExpressionBody_ProducesDiagnostic()
11 | {
12 | string source = @"
13 | class Test
14 | {
15 | bool Method(object o) => o [|== null|];
16 | }";
17 |
18 | string fixedSource = @"
19 | class Test
20 | {
21 | bool Method(object o) => o is null;
22 | }";
23 |
24 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource);
25 | }
26 |
27 | [Fact]
28 | public async Task NullEqualsInExpressionBody_ProducesDiagnostic()
29 | {
30 | string source = @"
31 | class Test
32 | {
33 | bool Method(object o) => [|null ==|] o;
34 | }";
35 |
36 | string fixedSource = @"
37 | class Test
38 | {
39 | bool Method(object o) => o is null;
40 | }";
41 |
42 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource);
43 | }
44 |
45 | [Fact]
46 | public async Task EqualsNullInIfExpression_ProducesDiagnostic()
47 | {
48 | string source = @"
49 | class Test
50 | {
51 | void Method(object o)
52 | {
53 | if (o [|== null|])
54 | {
55 | }
56 | }
57 | }";
58 |
59 | string fixedSource = @"
60 | class Test
61 | {
62 | void Method(object o)
63 | {
64 | if (o is null)
65 | {
66 | }
67 | }
68 | }";
69 |
70 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource);
71 | }
72 |
73 | [Fact]
74 | public async Task NullEqualsInIfExpression_ProducesDiagnostic()
75 | {
76 | string source = @"
77 | class Test
78 | {
79 | void Method(object o)
80 | {
81 | if ([|null ==|] o)
82 | {
83 | }
84 | }
85 | }";
86 |
87 | string fixedSource = @"
88 | class Test
89 | {
90 | void Method(object o)
91 | {
92 | if (o is null)
93 | {
94 | }
95 | }
96 | }";
97 |
98 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource);
99 | }
100 |
101 | [Fact]
102 | public async Task EqualsInArgument_ProducesDiagnostic()
103 | {
104 | string source = @"
105 | class Test
106 | {
107 | void Method(object o)
108 | {
109 | Other(o [|== null|]);
110 | }
111 |
112 | void Other(bool condition) { }
113 | }";
114 |
115 | string fixedSource = @"
116 | class Test
117 | {
118 | void Method(object o)
119 | {
120 | Other(o is null);
121 | }
122 |
123 | void Other(bool condition) { }
124 | }";
125 |
126 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource);
127 | }
128 |
129 | [Fact]
130 | public async Task EqualsNull_NearExpressionTreeAssignment_ProducesDiagnostic()
131 | {
132 | string source = @"
133 | using System;
134 | using System.Linq.Expressions;
135 |
136 | class Test
137 | {
138 | void Method()
139 | {
140 | Expression> e = """" [|== null|] ? (s => s == null) : (Expression>)null;
141 | }
142 | }";
143 |
144 | string fixedSource = @"
145 | using System;
146 | using System.Linq.Expressions;
147 |
148 | class Test
149 | {
150 | void Method()
151 | {
152 | Expression> e = """" is null ? (s => s == null) : (Expression>)null;
153 | }
154 | }";
155 |
156 | await VerifyCS.VerifyAnalyzerAsync(source);
157 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource);
158 | }
159 |
160 | [Fact]
161 | public async Task EqualsNullInExpressionTree_ProducesNoDiagnostic()
162 | {
163 | string source = @"
164 | using System;
165 | using System.Linq.Expressions;
166 |
167 | class Test
168 | {
169 | void Method()
170 | {
171 | _ = (Expression>)(s => s == null);
172 | }
173 | }";
174 |
175 | await VerifyCS.VerifyAnalyzerAsync(source);
176 | }
177 |
178 | [Fact]
179 | public async Task NullEqualsInExpressionTree_ProducesNoDiagnostic()
180 | {
181 | string source = @"
182 | using System;
183 | using System.Linq.Expressions;
184 |
185 | class Test
186 | {
187 | void Method()
188 | {
189 | _ = (Expression>)(s => null == s);
190 | }
191 | }";
192 |
193 | await VerifyCS.VerifyAnalyzerAsync(source);
194 | }
195 |
196 | [Fact]
197 | public async Task EqualsNullInExpressionTree_TargetTyped_ProducesNoDiagnostic()
198 | {
199 | string source = @"
200 | using System;
201 | using System.Linq.Expressions;
202 |
203 | class Test
204 | {
205 | void Method()
206 | {
207 | Expression> e = s => s == null;
208 | }
209 | }";
210 |
211 | await VerifyCS.VerifyAnalyzerAsync(source);
212 | }
213 |
214 | [Fact]
215 | public async Task EqualsDefaultInIfExpression_ProducesDiagnostic()
216 | {
217 | string source = @"
218 | class Test
219 | {
220 | void Method(object o)
221 | {
222 | if (o [|== default|])
223 | {
224 | }
225 | }
226 | }";
227 |
228 | string fixedSource = @"
229 | class Test
230 | {
231 | void Method(object o)
232 | {
233 | if (o is null)
234 | {
235 | }
236 | }
237 | }";
238 |
239 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource);
240 | }
241 |
242 | [Fact]
243 | public async Task EqualsDefaultKeywordInIfExpression_ProducesDiagnostic()
244 | {
245 | string source = @"
246 | class Test
247 | {
248 | void Method(string s)
249 | {
250 | if (s [|== default(string)|])
251 | {
252 | }
253 | }
254 | }";
255 |
256 | string fixedSource = @"
257 | class Test
258 | {
259 | void Method(string s)
260 | {
261 | if (s is null)
262 | {
263 | }
264 | }
265 | }";
266 |
267 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource);
268 | }
269 |
270 | [Fact]
271 | public async Task EqualsDefaultTInIfExpression_ProducesDiagnostic()
272 | {
273 | string source = @"
274 | class Test
275 | {
276 | void Method(Test t)
277 | {
278 | if (t [|== default(Test)|])
279 | {
280 | }
281 | }
282 | }";
283 |
284 | string fixedSource = @"
285 | class Test
286 | {
287 | void Method(Test t)
288 | {
289 | if (t is null)
290 | {
291 | }
292 | }
293 | }";
294 |
295 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource);
296 | }
297 |
298 | [Fact]
299 | public async Task EqualsDefaultValueType_ProducesNoDiagnostic()
300 | {
301 | string source = @"
302 | class Test
303 | {
304 | void Method(int o)
305 | {
306 | if (o == default)
307 | {
308 | }
309 | }
310 | }";
311 |
312 | await VerifyCS.VerifyAnalyzerAsync(source);
313 | }
314 |
315 | [Fact]
316 | public async Task CodeFixDoesNotAffectFormatting()
317 | {
318 | string source = @"
319 | class Test
320 | {
321 | string SafeString(string? message)
322 | {
323 | return message [|== null|] // Some Comment
324 | ? string.Empty
325 | : message;
326 | }
327 | }";
328 |
329 | string fixedSource = @"
330 | class Test
331 | {
332 | string SafeString(string? message)
333 | {
334 | return message is null // Some Comment
335 | ? string.Empty
336 | : message;
337 | }
338 | }";
339 |
340 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource);
341 | }
342 |
343 | [Fact]
344 | public async Task CodeFixDoesNotAffectFormattingReversed()
345 | {
346 | string source = @"
347 | class Test
348 | {
349 | string SafeString(string? message)
350 | {
351 | return [|null ==|] message
352 | ? string.Empty
353 | : message;
354 | }
355 | }";
356 |
357 | string fixedSource = @"
358 | class Test
359 | {
360 | string SafeString(string? message)
361 | {
362 | return message is null
363 | ? string.Empty
364 | : message;
365 | }
366 | }";
367 |
368 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource);
369 | }
370 | }
371 |
--------------------------------------------------------------------------------
/test/CSharpIsNullAnalyzer.Tests/CSIsNull002Tests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Andrew Arnott. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using CSharpIsNullAnalyzer;
5 | using Xunit;
6 | using VerifyCS = CSharpCodeFixVerifier;
7 |
8 | public class CSIsNull002Tests
9 | {
10 | [Fact]
11 | public async Task NotEqualsNullInExpressionBody_ProducesDiagnostic()
12 | {
13 | string source = @"
14 | class Test
15 | {
16 | bool Method(object o) => o [|!= null|];
17 | }";
18 |
19 | string fixedSource1 = @"
20 | class Test
21 | {
22 | bool Method(object o) => o is object;
23 | }";
24 |
25 | string fixedSource2 = @"
26 | class Test
27 | {
28 | bool Method(object o) => o is not null;
29 | }";
30 |
31 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource1, CSIsNull002Fixer.IsObjectEquivalenceKey);
32 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource2, CSIsNull002Fixer.IsNotNullEquivalenceKey);
33 | }
34 |
35 | [Fact]
36 | public async Task NullNotEqualsInExpressionBody_ProducesDiagnostic()
37 | {
38 | string source = @"
39 | class Test
40 | {
41 | bool Method(object o) => [|null !=|] o;
42 | }";
43 |
44 | string fixedSource1 = @"
45 | class Test
46 | {
47 | bool Method(object o) => o is object;
48 | }";
49 |
50 | string fixedSource2 = @"
51 | class Test
52 | {
53 | bool Method(object o) => o is not null;
54 | }";
55 |
56 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource1, CSIsNull002Fixer.IsObjectEquivalenceKey);
57 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource2, CSIsNull002Fixer.IsNotNullEquivalenceKey);
58 | }
59 |
60 | [Fact]
61 | public async Task NotEqualsNullInIfExpression_ProducesDiagnostic()
62 | {
63 | string source = @"
64 | class Test
65 | {
66 | void Method(object o)
67 | {
68 | if (o [|!= null|])
69 | {
70 | }
71 | }
72 | }";
73 |
74 | string fixedSource1 = @"
75 | class Test
76 | {
77 | void Method(object o)
78 | {
79 | if (o is object)
80 | {
81 | }
82 | }
83 | }";
84 |
85 | string fixedSource2 = @"
86 | class Test
87 | {
88 | void Method(object o)
89 | {
90 | if (o is not null)
91 | {
92 | }
93 | }
94 | }";
95 |
96 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource1, CSIsNull002Fixer.IsObjectEquivalenceKey);
97 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource2, CSIsNull002Fixer.IsNotNullEquivalenceKey);
98 | }
99 |
100 | [Fact]
101 | public async Task NullNotEqualsInIfExpression_ProducesDiagnostic()
102 | {
103 | string source = @"
104 | class Test
105 | {
106 | void Method(object o)
107 | {
108 | if ([|null !=|] o)
109 | {
110 | }
111 | }
112 | }";
113 |
114 | string fixedSource1 = @"
115 | class Test
116 | {
117 | void Method(object o)
118 | {
119 | if (o is object)
120 | {
121 | }
122 | }
123 | }";
124 |
125 | string fixedSource2 = @"
126 | class Test
127 | {
128 | void Method(object o)
129 | {
130 | if (o is not null)
131 | {
132 | }
133 | }
134 | }";
135 |
136 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource1, CSIsNull002Fixer.IsObjectEquivalenceKey);
137 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource2, CSIsNull002Fixer.IsNotNullEquivalenceKey);
138 | }
139 |
140 | [Fact]
141 | public async Task NullNotEqualsInArgument_ProducesDiagnostic()
142 | {
143 | string source = @"
144 | class Test
145 | {
146 | void Method(object o)
147 | {
148 | Other([|null !=|] o);
149 | }
150 |
151 | void Other(bool condition) { }
152 | }";
153 |
154 | string fixedSource1 = @"
155 | class Test
156 | {
157 | void Method(object o)
158 | {
159 | Other(o is object);
160 | }
161 |
162 | void Other(bool condition) { }
163 | }";
164 |
165 | string fixedSource2 = @"
166 | class Test
167 | {
168 | void Method(object o)
169 | {
170 | Other(o is not null);
171 | }
172 |
173 | void Other(bool condition) { }
174 | }";
175 |
176 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource1, CSIsNull002Fixer.IsObjectEquivalenceKey);
177 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource2, CSIsNull002Fixer.IsNotNullEquivalenceKey);
178 | }
179 |
180 | [Fact]
181 | public async Task NotEqualsNullInLargerExpressionInExpressionTree_OffersOneCodeFix()
182 | {
183 | string source = @"
184 | using System;
185 | using System.Linq.Expressions;
186 | class Test
187 | {
188 | void Method(int o)
189 | {
190 | bool? b = true;
191 | Expression> e = () => (b [|!= null|] || 3 < 5);
192 | }
193 | }";
194 |
195 | string fixedSource = @"
196 | using System;
197 | using System.Linq.Expressions;
198 | class Test
199 | {
200 | void Method(int o)
201 | {
202 | bool? b = true;
203 | Expression> e = () => (b is object || 3 < 5);
204 | }
205 | }";
206 |
207 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource, CSIsNull002Fixer.IsObjectEquivalenceKey);
208 | await VerifyCS.VerifyCodeFixAsync(source, source, CSIsNull002Fixer.IsNotNullEquivalenceKey); // assert that this fix is not offered.
209 | }
210 |
211 | [Fact]
212 | public async Task NullNotEqualsInLargerExpressionInExpressionTree_OffersOneCodeFix()
213 | {
214 | string source = @"
215 | using System;
216 | using System.Linq.Expressions;
217 | class Test
218 | {
219 | void Method(int o)
220 | {
221 | bool? b = true;
222 | Expression> e = () => ([|null !=|] b || 3 < 5);
223 | }
224 | }";
225 |
226 | string fixedSource = @"
227 | using System;
228 | using System.Linq.Expressions;
229 | class Test
230 | {
231 | void Method(int o)
232 | {
233 | bool? b = true;
234 | Expression> e = () => (b is object || 3 < 5);
235 | }
236 | }";
237 |
238 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource, CSIsNull002Fixer.IsObjectEquivalenceKey);
239 | await VerifyCS.VerifyCodeFixAsync(source, source, CSIsNull002Fixer.IsNotNullEquivalenceKey); // assert that this fix is not offered.
240 | }
241 |
242 | [Fact]
243 | public async Task NotEqualsNullInExpressionTree_TargetTyped_OffersOneCodeFix()
244 | {
245 | string source = @"
246 | using System;
247 | using System.Linq.Expressions;
248 |
249 | class Test
250 | {
251 | void Method()
252 | {
253 | Expression> e = s => s [|!= null|];
254 | }
255 | }";
256 |
257 | string fixedSource = @"
258 | using System;
259 | using System.Linq.Expressions;
260 |
261 | class Test
262 | {
263 | void Method()
264 | {
265 | Expression> e = s => s is object;
266 | }
267 | }";
268 |
269 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource, CSIsNull002Fixer.IsObjectEquivalenceKey);
270 | await VerifyCS.VerifyCodeFixAsync(source, source, CSIsNull002Fixer.IsNotNullEquivalenceKey); // assert that this fix is not offered.
271 | }
272 |
273 | [Fact]
274 | public async Task NotEqualsNullInExpressionTree_OffersOneCodeFix()
275 | {
276 | string source = @"
277 | using System;
278 | using System.Linq.Expressions;
279 |
280 | class Test
281 | {
282 | void Method()
283 | {
284 | _ = (Expression>)(s => s [|!= null|]);
285 | }
286 | }";
287 |
288 | string fixedSource = @"
289 | using System;
290 | using System.Linq.Expressions;
291 |
292 | class Test
293 | {
294 | void Method()
295 | {
296 | _ = (Expression>)(s => s is object);
297 | }
298 | }";
299 |
300 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource, CSIsNull002Fixer.IsObjectEquivalenceKey);
301 | await VerifyCS.VerifyCodeFixAsync(source, source, CSIsNull002Fixer.IsNotNullEquivalenceKey); // assert that this fix is not offered.
302 | }
303 |
304 | [Fact]
305 | public async Task NotEqualsDefaultInIfExpression_ProducesDiagnostic()
306 | {
307 | string source = @"
308 | class Test
309 | {
310 | void Method(object o)
311 | {
312 | if (o [|!= default|])
313 | {
314 | }
315 | }
316 | }";
317 |
318 | string fixedSource1 = @"
319 | class Test
320 | {
321 | void Method(object o)
322 | {
323 | if (o is object)
324 | {
325 | }
326 | }
327 | }";
328 |
329 | string fixedSource2 = @"
330 | class Test
331 | {
332 | void Method(object o)
333 | {
334 | if (o is not null)
335 | {
336 | }
337 | }
338 | }";
339 |
340 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource1, CSIsNull002Fixer.IsObjectEquivalenceKey);
341 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource2, CSIsNull002Fixer.IsNotNullEquivalenceKey);
342 | }
343 |
344 | [Fact]
345 | public async Task NotEqualsDefaultKeywordInIfExpression_ProducesDiagnostic()
346 | {
347 | string source = @"
348 | class Test
349 | {
350 | void Method(string s)
351 | {
352 | if (s [|!= default(string)|])
353 | {
354 | }
355 | }
356 | }";
357 |
358 | string fixedSource1 = @"
359 | class Test
360 | {
361 | void Method(string s)
362 | {
363 | if (s is object)
364 | {
365 | }
366 | }
367 | }";
368 |
369 | string fixedSource2 = @"
370 | class Test
371 | {
372 | void Method(string s)
373 | {
374 | if (s is not null)
375 | {
376 | }
377 | }
378 | }";
379 |
380 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource1, CSIsNull002Fixer.IsObjectEquivalenceKey);
381 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource2, CSIsNull002Fixer.IsNotNullEquivalenceKey);
382 | }
383 |
384 | [Fact]
385 | public async Task NotEqualsDefaultTInIfExpression_ProducesDiagnostic()
386 | {
387 | string source = @"
388 | class Test
389 | {
390 | void Method(Test t)
391 | {
392 | if (t [|!= default(Test)|])
393 | {
394 | }
395 | }
396 | }";
397 |
398 | string fixedSource1 = @"
399 | class Test
400 | {
401 | void Method(Test t)
402 | {
403 | if (t is object)
404 | {
405 | }
406 | }
407 | }";
408 |
409 | string fixedSource2 = @"
410 | class Test
411 | {
412 | void Method(Test t)
413 | {
414 | if (t is not null)
415 | {
416 | }
417 | }
418 | }";
419 |
420 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource1, CSIsNull002Fixer.IsObjectEquivalenceKey);
421 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource2, CSIsNull002Fixer.IsNotNullEquivalenceKey);
422 | }
423 |
424 | [Fact]
425 | public async Task NotEqualsDefaultValueType_ProducesNoDiagnostic()
426 | {
427 | string source = @"
428 | class Test
429 | {
430 | void Method(int o)
431 | {
432 | if (o != default)
433 | {
434 | }
435 | }
436 | }";
437 |
438 | await VerifyCS.VerifyAnalyzerAsync(source);
439 | }
440 |
441 | [Fact]
442 | public async Task CodeFixDoesNotAffectFormatting()
443 | {
444 | string source = @"
445 | class Test
446 | {
447 | string SafeString(string? message)
448 | {
449 | return message [|!= null|] // Some Comment
450 | ? message
451 | : string.Empty;
452 | }
453 | }";
454 |
455 | string fixedSource1 = @"
456 | class Test
457 | {
458 | string SafeString(string? message)
459 | {
460 | return message is object // Some Comment
461 | ? message
462 | : string.Empty;
463 | }
464 | }";
465 |
466 | string fixedSource2 = @"
467 | class Test
468 | {
469 | string SafeString(string? message)
470 | {
471 | return message is not null // Some Comment
472 | ? message
473 | : string.Empty;
474 | }
475 | }";
476 |
477 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource1, CSIsNull002Fixer.IsObjectEquivalenceKey);
478 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource2, CSIsNull002Fixer.IsNotNullEquivalenceKey);
479 | }
480 |
481 | [Fact]
482 | public async Task CodeFixDoesNotAffectFormattingReversed()
483 | {
484 | string source = @"
485 | class Test
486 | {
487 | string SafeString(string? message)
488 | {
489 | return [|null !=|] message // Some Comment
490 | ? message
491 | : string.Empty;
492 | }
493 | }";
494 |
495 | string fixedSource1 = @"
496 | class Test
497 | {
498 | string SafeString(string? message)
499 | {
500 | return message is object // Some Comment
501 | ? message
502 | : string.Empty;
503 | }
504 | }";
505 |
506 | string fixedSource2 = @"
507 | class Test
508 | {
509 | string SafeString(string? message)
510 | {
511 | return message is not null // Some Comment
512 | ? message
513 | : string.Empty;
514 | }
515 | }";
516 |
517 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource1, CSIsNull002Fixer.IsObjectEquivalenceKey);
518 | await VerifyCS.VerifyCodeFixAsync(source, fixedSource2, CSIsNull002Fixer.IsNotNullEquivalenceKey);
519 | }
520 | }
521 |
--------------------------------------------------------------------------------
/test/CSharpIsNullAnalyzer.Tests/CSharpIsNullAnalyzer.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0;net472
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/test/CSharpIsNullAnalyzer.Tests/Helpers/CSharpCodeFixVerifier`2+Test.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Andrew Arnott. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Reflection;
5 | using Microsoft.CodeAnalysis.CSharp;
6 | using Microsoft.CodeAnalysis.CSharp.Testing;
7 | using Microsoft.CodeAnalysis.Testing;
8 | using Microsoft.CodeAnalysis.Text;
9 |
10 | public static partial class CSharpCodeFixVerifier
11 | {
12 | public class Test : CSharpCodeFixTest
13 | {
14 | public Test()
15 | {
16 | this.ReferenceAssemblies = ReferencesHelper.DefaultReferences;
17 | this.TestBehaviors |= Microsoft.CodeAnalysis.Testing.TestBehaviors.SkipGeneratedCodeCheck;
18 |
19 | this.SolutionTransforms.Add((solution, projectId) =>
20 | {
21 | var parseOptions = (CSharpParseOptions)solution.GetProject(projectId)!.ParseOptions!;
22 | solution = solution.WithProjectParseOptions(projectId, parseOptions.WithLanguageVersion(LanguageVersion.CSharp9));
23 |
24 | return solution;
25 | });
26 |
27 | this.TestState.AdditionalFilesFactories.Add(() =>
28 | {
29 | const string additionalFilePrefix = "AdditionalFiles.";
30 | return from resourceName in Assembly.GetExecutingAssembly().GetManifestResourceNames()
31 | where resourceName.StartsWith(additionalFilePrefix, StringComparison.Ordinal)
32 | let content = ReadManifestResource(Assembly.GetExecutingAssembly(), resourceName)
33 | select (filename: resourceName.Substring(additionalFilePrefix.Length), SourceText.From(content));
34 | });
35 | }
36 |
37 | private static string ReadManifestResource(Assembly assembly, string resourceName)
38 | {
39 | using (var reader = new StreamReader(assembly.GetManifestResourceStream(resourceName) ?? throw new ArgumentException("No such resource stream", nameof(resourceName))))
40 | {
41 | return reader.ReadToEnd();
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/CSharpIsNullAnalyzer.Tests/Helpers/CSharpCodeFixVerifier`2.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Andrew Arnott. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Microsoft.CodeAnalysis;
5 | using Microsoft.CodeAnalysis.CodeFixes;
6 | using Microsoft.CodeAnalysis.CSharp.Testing;
7 | using Microsoft.CodeAnalysis.Diagnostics;
8 | using Microsoft.CodeAnalysis.Testing;
9 |
10 | public static partial class CSharpCodeFixVerifier
11 | where TAnalyzer : DiagnosticAnalyzer, new()
12 | where TCodeFix : CodeFixProvider, new()
13 | {
14 | public static DiagnosticResult Diagnostic()
15 | => CSharpCodeFixVerifier.Diagnostic();
16 |
17 | public static DiagnosticResult Diagnostic(string diagnosticId)
18 | => CSharpCodeFixVerifier.Diagnostic(diagnosticId);
19 |
20 | public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor)
21 | => new DiagnosticResult(descriptor);
22 |
23 | public static Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected)
24 | {
25 | var test = new Test { TestCode = source };
26 | test.ExpectedDiagnostics.AddRange(expected);
27 | return test.RunAsync();
28 | }
29 |
30 | public static Task VerifyCodeFixAsync(string source, string fixedSource, string? codeFixEquivalenceKey = null)
31 | => VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource, codeFixEquivalenceKey);
32 |
33 | public static Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource)
34 | => VerifyCodeFixAsync(source, new[] { expected }, fixedSource);
35 |
36 | public static Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource, string? codeFixEquivalenceKey = null)
37 | {
38 | var test = new Test
39 | {
40 | TestCode = source,
41 | FixedCode = fixedSource,
42 | CodeActionEquivalenceKey = codeFixEquivalenceKey,
43 | };
44 |
45 | test.ExpectedDiagnostics.AddRange(expected);
46 | return test.RunAsync();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/test/CSharpIsNullAnalyzer.Tests/Helpers/ReferencesHelper.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Andrew Arnott. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Microsoft.CodeAnalysis.Testing;
5 |
6 | internal static class ReferencesHelper
7 | {
8 | internal static ReferenceAssemblies DefaultReferences = ReferenceAssemblies.NetFramework.Net472.Default;
9 | }
10 |
--------------------------------------------------------------------------------
/test/CSharpIsNullAnalyzer.Tests/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/test/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | false
7 | true
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/tools/Check-DotNetRuntime.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Checks whether a given .NET Core runtime is installed.
4 | #>
5 | [CmdletBinding()]
6 | Param (
7 | [Parameter()]
8 | [ValidateSet('Microsoft.AspNetCore.App','Microsoft.NETCore.App')]
9 | [string]$Runtime='Microsoft.NETCore.App',
10 | [Parameter(Mandatory=$true)]
11 | [Version]$Version
12 | )
13 |
14 | $dotnet = Get-Command dotnet -ErrorAction SilentlyContinue
15 | if (!$dotnet) {
16 | # Nothing is installed.
17 | Write-Output $false
18 | exit 1
19 | }
20 |
21 | Function IsVersionMatch {
22 | Param(
23 | [Parameter()]
24 | $actualVersion
25 | )
26 | return $actualVersion -and
27 | $Version.Major -eq $actualVersion.Major -and
28 | $Version.Minor -eq $actualVersion.Minor -and
29 | (($Version.Build -eq -1) -or ($Version.Build -eq $actualVersion.Build)) -and
30 | (($Version.Revision -eq -1) -or ($Version.Revision -eq $actualVersion.Revision))
31 | }
32 |
33 | $installedRuntimes = dotnet --list-runtimes |? { $_.Split()[0] -ieq $Runtime } |% { $v = $null; [Version]::tryparse($_.Split()[1], [ref] $v); $v }
34 | $matchingRuntimes = $installedRuntimes |? { IsVersionMatch -actualVersion $_ }
35 | if (!$matchingRuntimes) {
36 | Write-Output $false
37 | exit 1
38 | }
39 |
40 | Write-Output $true
41 | exit 0
42 |
--------------------------------------------------------------------------------
/tools/Check-DotNetSdk.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Checks whether the .NET Core SDK required by this repo is installed.
4 | #>
5 | [CmdletBinding()]
6 | Param (
7 | )
8 |
9 | $dotnet = Get-Command dotnet -ErrorAction SilentlyContinue
10 | if (!$dotnet) {
11 | # Nothing is installed.
12 | Write-Output $false
13 | exit 1
14 | }
15 |
16 | # We need to set the current directory so dotnet considers the SDK required by our global.json file.
17 | Push-Location "$PSScriptRoot\.."
18 | try {
19 | dotnet -h 2>&1 | Out-Null
20 | if (($LASTEXITCODE -eq 129) -or # On Linux
21 | ($LASTEXITCODE -eq -2147450751) # On Windows
22 | ) {
23 | # These exit codes indicate no matching SDK exists.
24 | Write-Output $false
25 | exit 2
26 | }
27 |
28 | # The required SDK is already installed!
29 | Write-Output $true
30 | exit 0
31 | } catch {
32 | # I don't know why, but on some build agents (e.g. MicroBuild), an exception is thrown from the `dotnet` invocation when a match is not found.
33 | Write-Output $false
34 | exit 3
35 | } finally {
36 | Pop-Location
37 | }
38 |
--------------------------------------------------------------------------------
/tools/Install-DotNetSdk.ps1:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env pwsh
2 |
3 | <#
4 | .SYNOPSIS
5 | Installs the .NET SDK specified in the global.json file at the root of this repository,
6 | along with supporting .NET runtimes used for testing.
7 | .DESCRIPTION
8 | This MAY not require elevation, as the SDK and runtimes are installed locally to this repo location,
9 | unless `-InstallLocality machine` is specified.
10 | .PARAMETER InstallLocality
11 | A value indicating whether dependencies should be installed locally to the repo or at a per-user location.
12 | Per-user allows sharing the installed dependencies across repositories and allows use of a shared expanded package cache.
13 | Visual Studio will only notice and use these SDKs/runtimes if VS is launched from the environment that runs this script.
14 | Per-repo allows for high isolation, allowing for a more precise recreation of the environment within an Azure Pipelines build.
15 | When using 'repo', environment variables are set to cause the locally installed dotnet SDK to be used.
16 | Per-repo can lead to file locking issues when dotnet.exe is left running as a build server and can be mitigated by running `dotnet build-server shutdown`.
17 | Per-machine requires elevation and will download and install all SDKs and runtimes to machine-wide locations so all applications can find it.
18 | .PARAMETER SdkOnly
19 | Skips installing the runtime.
20 | .PARAMETER IncludeX86
21 | Installs a x86 SDK and runtimes in addition to the x64 ones. Only supported on Windows. Ignored on others.
22 | .PARAMETER IncludeAspNetCore
23 | Installs the ASP.NET Core runtime along with the .NET runtime.
24 | #>
25 | [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='Medium')]
26 | Param (
27 | [ValidateSet('repo','user','machine')]
28 | [string]$InstallLocality='user',
29 | [switch]$SdkOnly,
30 | [switch]$IncludeX86,
31 | [switch]$IncludeAspNetCore
32 | )
33 |
34 | $DotNetInstallScriptRoot = "$PSScriptRoot/../obj/tools"
35 | if (!(Test-Path $DotNetInstallScriptRoot)) { New-Item -ItemType Directory -Path $DotNetInstallScriptRoot -WhatIf:$false | Out-Null }
36 | $DotNetInstallScriptRoot = Resolve-Path $DotNetInstallScriptRoot
37 |
38 | # Look up actual required .NET SDK version from global.json
39 | $sdkVersion = & "$PSScriptRoot/../azure-pipelines/variables/DotNetSdkVersion.ps1"
40 |
41 | If ($IncludeX86 -and ($IsMacOS -or $IsLinux)) {
42 | Write-Verbose "Ignoring -IncludeX86 switch because 32-bit runtimes are only supported on Windows."
43 | $IncludeX86 = $false
44 | }
45 |
46 | $arch = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture
47 | if (!$arch) { # Windows Powershell leaves this blank
48 | $arch = 'x64'
49 | if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { $arch = 'ARM64' }
50 | if (${env:ProgramFiles(Arm)}) { $arch = 'ARM64' }
51 | }
52 |
53 | # Search for all .NET runtime versions referenced from MSBuild projects and arrange to install them.
54 | $runtimeVersions = @()
55 | $windowsDesktopRuntimeVersions = @()
56 | $aspnetRuntimeVersions = @()
57 | if (!$SdkOnly) {
58 | Get-ChildItem "$PSScriptRoot\..\src\*.*proj","$PSScriptRoot\..\test\*.*proj","$PSScriptRoot\..\Directory.Build.props" -Recurse |% {
59 | $projXml = [xml](Get-Content -Path $_)
60 | $pg = $projXml.Project.PropertyGroup
61 | if ($pg) {
62 | $targetFrameworks = @()
63 | $tf = $pg.TargetFramework
64 | $targetFrameworks += $tf
65 | $tfs = $pg.TargetFrameworks
66 | if ($tfs) {
67 | $targetFrameworks = $tfs -Split ';'
68 | }
69 | }
70 | $targetFrameworks |? { $_ -match 'net(?:coreapp)?(\d+\.\d+)' } |% {
71 | $v = $Matches[1]
72 | $runtimeVersions += $v
73 | $aspnetRuntimeVersions += $v
74 | if ($v -ge '3.0' -and -not ($IsMacOS -or $IsLinux)) {
75 | $windowsDesktopRuntimeVersions += $v
76 | }
77 | }
78 |
79 | # Add target frameworks of the form: netXX
80 | $targetFrameworks |? { $_ -match 'net(\d+\.\d+)' } |% {
81 | $v = $Matches[1]
82 | $runtimeVersions += $v
83 | $aspnetRuntimeVersions += $v
84 | if (-not ($IsMacOS -or $IsLinux)) {
85 | $windowsDesktopRuntimeVersions += $v
86 | }
87 | }
88 | }
89 | }
90 |
91 | if (!$IncludeAspNetCore) {
92 | $aspnetRuntimeVersions = @()
93 | }
94 |
95 | Function Get-FileFromWeb([Uri]$Uri, $OutDir) {
96 | $OutFile = Join-Path $OutDir $Uri.Segments[-1]
97 | if (!(Test-Path $OutFile)) {
98 | Write-Verbose "Downloading $Uri..."
99 | if (!(Test-Path $OutDir)) { New-Item -ItemType Directory -Path $OutDir | Out-Null }
100 | try {
101 | (New-Object System.Net.WebClient).DownloadFile($Uri, $OutFile)
102 | } finally {
103 | # This try/finally causes the script to abort
104 | }
105 | }
106 |
107 | $OutFile
108 | }
109 |
110 | Function Get-InstallerExe(
111 | $Version,
112 | $Architecture,
113 | [ValidateSet('Sdk','Runtime','WindowsDesktop')]
114 | [string]$sku
115 | ) {
116 | # Get the latest/actual version for the specified one
117 | $TypedVersion = $null
118 | if (![Version]::TryParse($Version, [ref] $TypedVersion)) {
119 | Write-Error "Unable to parse $Version into an a.b.c.d version. This version cannot be installed machine-wide."
120 | exit 1
121 | }
122 |
123 | if ($TypedVersion.Build -eq -1) {
124 | $versionInfo = -Split (Invoke-WebRequest -Uri "https://dotnetcli.blob.core.windows.net/dotnet/$sku/$Version/latest.version" -UseBasicParsing)
125 | $Version = $versionInfo[-1]
126 | }
127 |
128 | $majorMinor = "$($TypedVersion.Major).$($TypedVersion.Minor)"
129 | $ReleasesFile = Join-Path $DotNetInstallScriptRoot "$majorMinor\releases.json"
130 | if (!(Test-Path $ReleasesFile)) {
131 | Get-FileFromWeb -Uri "https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/$majorMinor/releases.json" -OutDir (Split-Path $ReleasesFile) | Out-Null
132 | }
133 |
134 | $releases = Get-Content $ReleasesFile | ConvertFrom-Json
135 | $url = $null
136 | foreach ($release in $releases.releases) {
137 | $filesElement = $null
138 | if ($release.$sku.version -eq $Version) {
139 | $filesElement = $release.$sku.files
140 | }
141 | if (!$filesElement -and ($sku -eq 'sdk') -and $release.sdks) {
142 | foreach ($sdk in $release.sdks) {
143 | if ($sdk.version -eq $Version) {
144 | $filesElement = $sdk.files
145 | break
146 | }
147 | }
148 | }
149 |
150 | if ($filesElement) {
151 | foreach ($file in $filesElement) {
152 | if ($file.rid -eq "win-$Architecture") {
153 | $url = $file.url
154 | Break
155 | }
156 | }
157 |
158 | if ($url) {
159 | Break
160 | }
161 | }
162 | }
163 |
164 | if ($url) {
165 | Get-FileFromWeb -Uri $url -OutDir $DotNetInstallScriptRoot
166 | } else {
167 | throw "Unable to find release of $sku v$Version"
168 | }
169 | }
170 |
171 | Function Install-DotNet($Version, $Architecture, [ValidateSet('Sdk','Runtime','WindowsDesktop','AspNetCore')][string]$sku = 'Sdk') {
172 | Write-Host "Downloading .NET $sku $Version..."
173 | $Installer = Get-InstallerExe -Version $Version -Architecture $Architecture -sku $sku
174 | Write-Host "Installing .NET $sku $Version..."
175 | cmd /c start /wait $Installer /install /passive /norestart
176 | if ($LASTEXITCODE -eq 3010) {
177 | Write-Verbose "Restart required"
178 | } elseif ($LASTEXITCODE -ne 0) {
179 | throw "Failure to install .NET SDK"
180 | }
181 | }
182 |
183 | $switches = @()
184 | $envVars = @{
185 | # For locally installed dotnet, skip first time experience which takes a long time
186 | 'DOTNET_SKIP_FIRST_TIME_EXPERIENCE' = 'true';
187 | }
188 |
189 | if ($InstallLocality -eq 'machine') {
190 | if ($IsMacOS -or $IsLinux) {
191 | $DotNetInstallDir = '/usr/share/dotnet'
192 | } else {
193 | $restartRequired = $false
194 | if ($PSCmdlet.ShouldProcess(".NET SDK $sdkVersion", "Install")) {
195 | Install-DotNet -Version $sdkVersion -Architecture $arch
196 | $restartRequired = $restartRequired -or ($LASTEXITCODE -eq 3010)
197 |
198 | if ($IncludeX86) {
199 | Install-DotNet -Version $sdkVersion -Architecture x86
200 | $restartRequired = $restartRequired -or ($LASTEXITCODE -eq 3010)
201 | }
202 | }
203 |
204 | $runtimeVersions | Sort-Object | Get-Unique |% {
205 | if ($PSCmdlet.ShouldProcess(".NET runtime $_", "Install")) {
206 | Install-DotNet -Version $_ -sku Runtime -Architecture $arch
207 | $restartRequired = $restartRequired -or ($LASTEXITCODE -eq 3010)
208 |
209 | if ($IncludeX86) {
210 | Install-DotNet -Version $_ -sku Runtime -Architecture x86
211 | $restartRequired = $restartRequired -or ($LASTEXITCODE -eq 3010)
212 | }
213 | }
214 | }
215 |
216 | $windowsDesktopRuntimeVersions | Sort-Object | Get-Unique |% {
217 | if ($PSCmdlet.ShouldProcess(".NET Windows Desktop $_", "Install")) {
218 | Install-DotNet -Version $_ -sku WindowsDesktop -Architecture $arch
219 | $restartRequired = $restartRequired -or ($LASTEXITCODE -eq 3010)
220 |
221 | if ($IncludeX86) {
222 | Install-DotNet -Version $_ -sku WindowsDesktop -Architecture x86
223 | $restartRequired = $restartRequired -or ($LASTEXITCODE -eq 3010)
224 | }
225 | }
226 | }
227 |
228 | $aspnetRuntimeVersions | Sort-Object | Get-Unique |% {
229 | if ($PSCmdlet.ShouldProcess("ASP.NET Core $_", "Install")) {
230 | Install-DotNet -Version $_ -sku AspNetCore -Architecture $arch
231 | $restartRequired = $restartRequired -or ($LASTEXITCODE -eq 3010)
232 |
233 | if ($IncludeX86) {
234 | Install-DotNet -Version $_ -sku AspNetCore -Architecture x86
235 | $restartRequired = $restartRequired -or ($LASTEXITCODE -eq 3010)
236 | }
237 | }
238 | }
239 | if ($restartRequired) {
240 | Write-Host -ForegroundColor Yellow "System restart required"
241 | Exit 3010
242 | }
243 |
244 | return
245 | }
246 | } elseif ($InstallLocality -eq 'repo') {
247 | $DotNetInstallDir = "$DotNetInstallScriptRoot/.dotnet"
248 | $DotNetX86InstallDir = "$DotNetInstallScriptRoot/x86/.dotnet"
249 | } elseif ($env:AGENT_TOOLSDIRECTORY) {
250 | $DotNetInstallDir = "$env:AGENT_TOOLSDIRECTORY/dotnet"
251 | $DotNetX86InstallDir = "$env:AGENT_TOOLSDIRECTORY/x86/dotnet"
252 | } else {
253 | $DotNetInstallDir = Join-Path $HOME .dotnet
254 | }
255 |
256 | if ($DotNetInstallDir) {
257 | if (!(Test-Path $DotNetInstallDir)) { New-Item -ItemType Directory -Path $DotNetInstallDir }
258 | $DotNetInstallDir = Resolve-Path $DotNetInstallDir
259 | Write-Host "Installing .NET SDK and runtimes to $DotNetInstallDir" -ForegroundColor Blue
260 | $envVars['DOTNET_MULTILEVEL_LOOKUP'] = '0'
261 | $envVars['DOTNET_ROOT'] = $DotNetInstallDir
262 | }
263 |
264 | if ($IncludeX86) {
265 | if ($DotNetX86InstallDir) {
266 | if (!(Test-Path $DotNetX86InstallDir)) { New-Item -ItemType Directory -Path $DotNetX86InstallDir }
267 | $DotNetX86InstallDir = Resolve-Path $DotNetX86InstallDir
268 | Write-Host "Installing x86 .NET SDK and runtimes to $DotNetX86InstallDir" -ForegroundColor Blue
269 | } else {
270 | # Only machine-wide or repo-wide installations can handle two unique dotnet.exe architectures.
271 | Write-Error "The installation location or OS isn't supported for x86 installation. Try a different -InstallLocality value."
272 | return 1
273 | }
274 | }
275 |
276 | if ($IsMacOS -or $IsLinux) {
277 | $DownloadUri = "https://raw.githubusercontent.com/dotnet/install-scripts/0b09de9bc136cacb5f849a6957ebd4062173c148/src/dotnet-install.sh"
278 | $DotNetInstallScriptPath = "$DotNetInstallScriptRoot/dotnet-install.sh"
279 | } else {
280 | $DownloadUri = "https://raw.githubusercontent.com/dotnet/install-scripts/0b09de9bc136cacb5f849a6957ebd4062173c148/src/dotnet-install.ps1"
281 | $DotNetInstallScriptPath = "$DotNetInstallScriptRoot/dotnet-install.ps1"
282 | }
283 |
284 | if (-not (Test-Path $DotNetInstallScriptPath)) {
285 | Invoke-WebRequest -Uri $DownloadUri -OutFile $DotNetInstallScriptPath -UseBasicParsing
286 | if ($IsMacOS -or $IsLinux) {
287 | chmod +x $DotNetInstallScriptPath
288 | }
289 | }
290 |
291 | # In case the script we invoke is in a directory with spaces, wrap it with single quotes.
292 | # In case the path includes single quotes, escape them.
293 | $DotNetInstallScriptPathExpression = $DotNetInstallScriptPath.Replace("'", "''")
294 | $DotNetInstallScriptPathExpression = "& '$DotNetInstallScriptPathExpression'"
295 |
296 | $anythingInstalled = $false
297 | $global:LASTEXITCODE = 0
298 |
299 | if ($PSCmdlet.ShouldProcess(".NET SDK $sdkVersion", "Install")) {
300 | $anythingInstalled = $true
301 | Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Version $sdkVersion -Architecture $arch -InstallDir $DotNetInstallDir $switches"
302 |
303 | if ($LASTEXITCODE -ne 0) {
304 | Write-Error ".NET SDK installation failure: $LASTEXITCODE"
305 | exit $LASTEXITCODE
306 | }
307 | } else {
308 | Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Version $sdkVersion -Architecture $arch -InstallDir $DotNetInstallDir $switches -DryRun"
309 | }
310 |
311 | if ($IncludeX86) {
312 | if ($PSCmdlet.ShouldProcess(".NET x86 SDK $sdkVersion", "Install")) {
313 | $anythingInstalled = $true
314 | Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Version $sdkVersion -Architecture x86 -InstallDir $DotNetX86InstallDir $switches"
315 |
316 | if ($LASTEXITCODE -ne 0) {
317 | Write-Error ".NET x86 SDK installation failure: $LASTEXITCODE"
318 | exit $LASTEXITCODE
319 | }
320 | } else {
321 | Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Version $sdkVersion -Architecture x86 -InstallDir $DotNetX86InstallDir $switches -DryRun"
322 | }
323 | }
324 |
325 | $dotnetRuntimeSwitches = $switches + '-Runtime','dotnet'
326 |
327 | $runtimeVersions | Sort-Object -Unique |% {
328 | if ($PSCmdlet.ShouldProcess(".NET $Arch runtime $_", "Install")) {
329 | $anythingInstalled = $true
330 | Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Channel $_ -Architecture $arch -InstallDir $DotNetInstallDir $dotnetRuntimeSwitches"
331 |
332 | if ($LASTEXITCODE -ne 0) {
333 | Write-Error ".NET SDK installation failure: $LASTEXITCODE"
334 | exit $LASTEXITCODE
335 | }
336 | } else {
337 | Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Channel $_ -Architecture $arch -InstallDir $DotNetInstallDir $dotnetRuntimeSwitches -DryRun"
338 | }
339 |
340 | if ($IncludeX86) {
341 | if ($PSCmdlet.ShouldProcess(".NET x86 runtime $_", "Install")) {
342 | $anythingInstalled = $true
343 | Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Channel $_ -Architecture x86 -InstallDir $DotNetX86InstallDir $dotnetRuntimeSwitches"
344 |
345 | if ($LASTEXITCODE -ne 0) {
346 | Write-Error ".NET SDK installation failure: $LASTEXITCODE"
347 | exit $LASTEXITCODE
348 | }
349 | } else {
350 | Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Channel $_ -Architecture x86 -InstallDir $DotNetX86InstallDir $dotnetRuntimeSwitches -DryRun"
351 | }
352 | }
353 | }
354 |
355 | $windowsDesktopRuntimeSwitches = $switches + '-Runtime','windowsdesktop'
356 |
357 | $windowsDesktopRuntimeVersions | Sort-Object -Unique |% {
358 | if ($PSCmdlet.ShouldProcess(".NET WindowsDesktop $arch runtime $_", "Install")) {
359 | $anythingInstalled = $true
360 | Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Channel $_ -Architecture $arch -InstallDir $DotNetInstallDir $windowsDesktopRuntimeSwitches"
361 |
362 | if ($LASTEXITCODE -ne 0) {
363 | Write-Error ".NET SDK installation failure: $LASTEXITCODE"
364 | exit $LASTEXITCODE
365 | }
366 | } else {
367 | Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Channel $_ -Architecture $arch -InstallDir $DotNetInstallDir $windowsDesktopRuntimeSwitches -DryRun"
368 | }
369 |
370 | if ($IncludeX86) {
371 | if ($PSCmdlet.ShouldProcess(".NET WindowsDesktop x86 runtime $_", "Install")) {
372 | $anythingInstalled = $true
373 | Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Channel $_ -Architecture x86 -InstallDir $DotNetX86InstallDir $windowsDesktopRuntimeSwitches"
374 |
375 | if ($LASTEXITCODE -ne 0) {
376 | Write-Error ".NET SDK installation failure: $LASTEXITCODE"
377 | exit $LASTEXITCODE
378 | }
379 | } else {
380 | Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Channel $_ -Architecture x86 -InstallDir $DotNetX86InstallDir $windowsDesktopRuntimeSwitches -DryRun"
381 | }
382 | }
383 | }
384 |
385 | $aspnetRuntimeSwitches = $switches + '-Runtime','aspnetcore'
386 |
387 | $aspnetRuntimeVersions | Sort-Object -Unique |% {
388 | if ($PSCmdlet.ShouldProcess(".NET ASP.NET Core $arch runtime $_", "Install")) {
389 | $anythingInstalled = $true
390 | Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Channel $_ -Architecture $arch -InstallDir $DotNetInstallDir $aspnetRuntimeSwitches"
391 |
392 | if ($LASTEXITCODE -ne 0) {
393 | Write-Error ".NET SDK installation failure: $LASTEXITCODE"
394 | exit $LASTEXITCODE
395 | }
396 | } else {
397 | Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Channel $_ -Architecture $arch -InstallDir $DotNetInstallDir $aspnetRuntimeSwitches -DryRun"
398 | }
399 |
400 | if ($IncludeX86) {
401 | if ($PSCmdlet.ShouldProcess(".NET ASP.NET Core x86 runtime $_", "Install")) {
402 | $anythingInstalled = $true
403 | Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Channel $_ -Architecture x86 -InstallDir $DotNetX86InstallDir $aspnetRuntimeSwitches"
404 |
405 | if ($LASTEXITCODE -ne 0) {
406 | Write-Error ".NET SDK installation failure: $LASTEXITCODE"
407 | exit $LASTEXITCODE
408 | }
409 | } else {
410 | Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Channel $_ -Architecture x86 -InstallDir $DotNetX86InstallDir $aspnetRuntimeSwitches -DryRun"
411 | }
412 | }
413 | }
414 |
415 | if ($PSCmdlet.ShouldProcess("Set DOTNET environment variables to discover these installed runtimes?")) {
416 | & "$PSScriptRoot/Set-EnvVars.ps1" -Variables $envVars -PrependPath $DotNetInstallDir | Out-Null
417 | }
418 |
419 | if ($anythingInstalled -and ($InstallLocality -ne 'machine') -and !$env:TF_BUILD -and !$env:GITHUB_ACTIONS) {
420 | Write-Warning ".NET runtimes or SDKs were installed to a non-machine location. Perform your builds or open Visual Studio from this same environment in order for tools to discover the location of these dependencies."
421 | }
422 |
--------------------------------------------------------------------------------
/tools/Install-NuGetCredProvider.ps1:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env pwsh
2 |
3 | <#
4 | .SYNOPSIS
5 | Downloads and installs the Microsoft Artifacts Credential Provider
6 | from https://github.com/microsoft/artifacts-credprovider
7 | to assist in authenticating to Azure Artifact feeds in interactive development
8 | or unattended build agents.
9 | .PARAMETER Force
10 | Forces install of the CredProvider plugin even if one already exists. This is useful to upgrade an older version.
11 | .PARAMETER AccessToken
12 | An optional access token for authenticating to Azure Artifacts authenticated feeds.
13 | #>
14 | [CmdletBinding()]
15 | Param (
16 | [Parameter()]
17 | [switch]$Force,
18 | [Parameter()]
19 | [string]$AccessToken
20 | )
21 |
22 | $envVars = @{}
23 |
24 | $toolsPath = & "$PSScriptRoot\..\azure-pipelines\Get-TempToolsPath.ps1"
25 |
26 | if ($IsMacOS -or $IsLinux) {
27 | $installerScript = "installcredprovider.sh"
28 | $sourceUrl = "https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.sh"
29 | } else {
30 | $installerScript = "installcredprovider.ps1"
31 | $sourceUrl = "https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.ps1"
32 | }
33 |
34 | $installerScript = Join-Path $toolsPath $installerScript
35 |
36 | if (!(Test-Path $installerScript) -or $Force) {
37 | Invoke-WebRequest $sourceUrl -OutFile $installerScript
38 | }
39 |
40 | $installerScript = (Resolve-Path $installerScript).Path
41 |
42 | if ($IsMacOS -or $IsLinux) {
43 | chmod u+x $installerScript
44 | }
45 |
46 | & $installerScript -Force:$Force -AddNetfx -InstallNet6
47 |
48 | if ($AccessToken) {
49 | $endpoints = @()
50 |
51 | $endpointURIs = @()
52 | Get-ChildItem "$PSScriptRoot\..\nuget.config" -Recurse |% {
53 | $nugetConfig = [xml](Get-Content -Path $_)
54 |
55 | $nugetConfig.configuration.packageSources.add |? { ($_.value -match '^https://pkgs\.dev\.azure\.com/') -or ($_.value -match '^https://[\w\-]+\.pkgs\.visualstudio\.com/') } |% {
56 | if ($endpointURIs -notcontains $_.Value) {
57 | $endpointURIs += $_.Value
58 | $endpoint = New-Object -TypeName PSObject
59 | Add-Member -InputObject $endpoint -MemberType NoteProperty -Name endpoint -Value $_.value
60 | Add-Member -InputObject $endpoint -MemberType NoteProperty -Name username -Value ado
61 | Add-Member -InputObject $endpoint -MemberType NoteProperty -Name password -Value $AccessToken
62 | $endpoints += $endpoint
63 | }
64 | }
65 | }
66 |
67 | $auth = New-Object -TypeName PSObject
68 | Add-Member -InputObject $auth -MemberType NoteProperty -Name endpointCredentials -Value $endpoints
69 |
70 | $authJson = ConvertTo-Json -InputObject $auth
71 | $envVars += @{
72 | 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS'=$authJson;
73 | }
74 | }
75 |
76 | & "$PSScriptRoot/Set-EnvVars.ps1" -Variables $envVars | Out-Null
77 |
--------------------------------------------------------------------------------
/tools/Set-EnvVars.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Set environment variables in the environment.
4 | Azure Pipeline and CMD environments are considered.
5 | .PARAMETER Variables
6 | A hashtable of variables to be set.
7 | .PARAMETER PrependPath
8 | A set of paths to prepend to the PATH environment variable.
9 | .OUTPUTS
10 | A boolean indicating whether the environment variables can be expected to propagate to the caller's environment.
11 | .DESCRIPTION
12 | The CmdEnvScriptPath environment variable may be optionally set to a path to a cmd shell script to be created (or appended to if it already exists) that will set the environment variables in cmd.exe that are set within the PowerShell environment.
13 | This is used by init.cmd in order to reapply any new environment variables to the parent cmd.exe process that were set in the powershell child process.
14 | #>
15 | [CmdletBinding(SupportsShouldProcess=$true)]
16 | Param(
17 | [Parameter(Mandatory=$true, Position=1)]
18 | $Variables,
19 | [string[]]$PrependPath
20 | )
21 |
22 | if ($Variables.Count -eq 0) {
23 | return $true
24 | }
25 |
26 | $cmdInstructions = !$env:TF_BUILD -and !$env:GITHUB_ACTIONS -and !$env:CmdEnvScriptPath -and ($env:PS1UnderCmd -eq '1')
27 | if ($cmdInstructions) {
28 | Write-Warning "Environment variables have been set that will be lost because you're running under cmd.exe"
29 | Write-Host "Environment variables that must be set manually:" -ForegroundColor Blue
30 | } else {
31 | Write-Host "Environment variables set:" -ForegroundColor Blue
32 | Write-Host ($Variables | Out-String)
33 | if ($PrependPath) {
34 | Write-Host "Paths prepended to PATH: $PrependPath"
35 | }
36 | }
37 |
38 | if ($env:TF_BUILD) {
39 | Write-Host "Azure Pipelines detected. Logging commands will be used to propagate environment variables and prepend path."
40 | }
41 |
42 | if ($env:GITHUB_ACTIONS) {
43 | Write-Host "GitHub Actions detected. Logging commands will be used to propagate environment variables and prepend path."
44 | }
45 |
46 | $CmdEnvScript = ''
47 | $Variables.GetEnumerator() |% {
48 | Set-Item -Path env:$($_.Key) -Value $_.Value
49 |
50 | # If we're running in a cloud CI, set these environment variables so they propagate.
51 | if ($env:TF_BUILD) {
52 | Write-Host "##vso[task.setvariable variable=$($_.Key);]$($_.Value)"
53 | }
54 | if ($env:GITHUB_ACTIONS) {
55 | Add-Content -Path $env:GITHUB_ENV -Value "$($_.Key)=$($_.Value)"
56 | }
57 |
58 | if ($cmdInstructions) {
59 | Write-Host "SET $($_.Key)=$($_.Value)"
60 | }
61 |
62 | $CmdEnvScript += "SET $($_.Key)=$($_.Value)`r`n"
63 | }
64 |
65 | $pathDelimiter = ';'
66 | if ($IsMacOS -or $IsLinux) {
67 | $pathDelimiter = ':'
68 | }
69 |
70 | if ($PrependPath) {
71 | $PrependPath |% {
72 | $newPathValue = "$_$pathDelimiter$env:PATH"
73 | Set-Item -Path env:PATH -Value $newPathValue
74 | if ($cmdInstructions) {
75 | Write-Host "SET PATH=$newPathValue"
76 | }
77 |
78 | if ($env:TF_BUILD) {
79 | Write-Host "##vso[task.prependpath]$_"
80 | }
81 | if ($env:GITHUB_ACTIONS) {
82 | Add-Content -Path $env:GITHUB_PATH -Value $_
83 | }
84 |
85 | $CmdEnvScript += "SET PATH=$_$pathDelimiter%PATH%"
86 | }
87 | }
88 |
89 | if ($env:CmdEnvScriptPath) {
90 | if (Test-Path $env:CmdEnvScriptPath) {
91 | $CmdEnvScript = (Get-Content -Path $env:CmdEnvScriptPath) + $CmdEnvScript
92 | }
93 |
94 | Set-Content -Path $env:CmdEnvScriptPath -Value $CmdEnvScript
95 | }
96 |
97 | return !$cmdInstructions
98 |
--------------------------------------------------------------------------------
/version.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
3 | "version": "0.1",
4 | "assemblyVersion": {
5 | "precision": "revision"
6 | },
7 | "publicReleaseRefSpec": [
8 | "^refs/heads/main$",
9 | "^refs/heads/v\\d+(?:\\.\\d+)?$"
10 | ],
11 | "cloudBuild": {
12 | "setVersionVariables": false
13 | }
14 | }
15 |
--------------------------------------------------------------------------------