├── .editorconfig
├── .gitattributes
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── 10_design_proposal.md
│ └── 20_bug_report.md
├── pull_request_template.md
└── workflows
│ └── ci.yml
├── .gitignore
├── .gitmodules
├── .vscode
├── launch.json
└── tasks.json
├── .vsconfig
├── CHANGELOG.md
├── CODE-OF-CONDUCT.md
├── CONTRIBUTING.md
├── Directory.Build.props
├── Directory.Build.targets
├── HTMLTemplate.html
├── LICENSE.txt
├── NuGet.config
├── README.md
├── SUPPORT.md
├── UpgradeAssistant.Extensions.sln
├── azure-pipelines
├── build.yml
└── nuget-package.signproj
├── docs
├── Extensibility.APIMaps.md
├── Extensibility.PackageMaps.md
└── Traits.md
├── rules.ruleset
├── samples
├── Sample.apimap.json
└── Sample.packagemap.json
├── src
└── UpgradeAssistant.Mappings
│ ├── UpgradeAssistant.Mappings.csproj
│ └── mappings
│ ├── Esri
│ └── Esri.ArcGISRuntime.Xamarin.Forms
│ │ ├── esri.arcgisruntime.xamarin.forms.apimap.json
│ │ ├── esri.arcgisruntime.xamarin.forms.packagemap.json
│ │ └── metadata.json
│ ├── Microsoft
│ └── CommunityToolkit
│ │ ├── XamarinCommunityToolkit.apimap.json
│ │ ├── XamarinCommunityToolkit.packagemap.json
│ │ └── metadata.json
│ └── jsuarezruiz
│ └── AlohaKit.Animations
│ ├── alohakit.animations.apimap.json
│ ├── alohakit.animations.packagemap.json
│ └── metadata.json
├── stylecop.json
├── tests
└── UpgradeAssistant.Mappings.Tests
│ ├── ApiMapEntry.cs
│ ├── ApiMapValidationTests.cs
│ ├── MetadataValidationTests.cs
│ ├── PackageMapConfig.cs
│ ├── PackageMapEntry.cs
│ ├── PackageMapFrameworkEntry.cs
│ ├── PackageMapValidationTests.cs
│ ├── SchemaValidationTests.cs
│ ├── TestHelper.cs
│ ├── TraitToken.cs
│ ├── TraitsExpressionParser.cs
│ ├── TraitsExpressionSyntaxException.cs
│ └── UpgradeAssistant.Mappings.Tests.csproj
└── version.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://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 | dotnet_diagnostic.DV2002.severity = suggestion
10 | # (Please don't specify an indent_size here; that has too many unintended consequences.)
11 |
12 | # Code files
13 | [*.{cs,csx,vb,vbx}]
14 | indent_size = 4
15 | insert_final_newline = true
16 | charset = utf-8-bom
17 | file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.
18 |
19 | # XML project files
20 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
21 | indent_size = 4
22 |
23 | # XML config files
24 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
25 | indent_size = 2
26 |
27 | # JSON files
28 | [*.json]
29 | indent_size = 2
30 |
31 | # Powershell files
32 | [*.ps1]
33 | indent_size = 2
34 |
35 | # Shell script files
36 | [*.sh]
37 | end_of_line = lf
38 | indent_size = 2
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 | # Avoid "this." and "Me." if not necessary
45 | dotnet_style_qualification_for_field = false:refactoring
46 | dotnet_style_qualification_for_property = false:refactoring
47 | dotnet_style_qualification_for_method = false:refactoring
48 | dotnet_style_qualification_for_event = false:refactoring
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 | # Readonly fields are PascalCase
73 | dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = suggestion
74 | dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields
75 | dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_static_field_style
76 |
77 | dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
78 | dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
79 | dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
80 |
81 | dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case
82 |
83 | # Constants are PascalCase
84 | dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
85 | dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
86 | dotnet_naming_rule.constants_should_be_pascal_case.style = non_private_static_field_style
87 |
88 | dotnet_naming_symbols.constants.applicable_kinds = field, local
89 | dotnet_naming_symbols.constants.required_modifiers = const
90 |
91 | dotnet_naming_style.constant_style.capitalization = pascal_case
92 |
93 | # Readonly fields are PascalCase
94 | dotnet_naming_rule.static_readonly_fields_should_be_pascal_case_rule.severity = suggestion
95 | dotnet_naming_rule.static_readonly_fields_should_be_pascal_case_rule.symbols = static_readonly_fields
96 | dotnet_naming_rule.static_readonly_fields_should_be_pascal_case_rule.style = non_private_static_field_style
97 |
98 | dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field
99 | dotnet_naming_symbols.static_readonly_fields.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private
100 | dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly
101 |
102 | # Static fields are camelCase and start with s_
103 | dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion
104 | dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
105 | dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
106 |
107 | dotnet_naming_symbols.static_fields.applicable_kinds = field
108 | dotnet_naming_symbols.static_fields.required_modifiers = static
109 |
110 | dotnet_naming_style.static_field_style.capitalization = camel_case
111 | dotnet_naming_style.static_field_style.required_prefix = s_
112 |
113 | # Instance fields are camelCase and start with _
114 | dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
115 | dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
116 | dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
117 |
118 | dotnet_naming_symbols.instance_fields.applicable_kinds = field
119 |
120 | dotnet_naming_style.instance_field_style.capitalization = camel_case
121 | dotnet_naming_style.instance_field_style.required_prefix = _
122 |
123 | # Locals and parameters are camelCase
124 | dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
125 | dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
126 | dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
127 |
128 | dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
129 |
130 | dotnet_naming_style.camel_case_style.capitalization = camel_case
131 |
132 | # Local functions are PascalCase
133 | dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
134 | dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
135 | dotnet_naming_rule.local_functions_should_be_pascal_case.style = non_private_static_field_style
136 |
137 | dotnet_naming_symbols.local_functions.applicable_kinds = local_function
138 |
139 | dotnet_naming_style.local_function_style.capitalization = pascal_case
140 |
141 | # By default, name items with PascalCase
142 | dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
143 | dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
144 | dotnet_naming_rule.members_should_be_pascal_case.style = non_private_static_field_style
145 |
146 | dotnet_naming_symbols.all_members.applicable_kinds = *
147 |
148 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case
149 | dotnet_diagnostic.CA1848.severity = none
150 |
151 | # CSharp code style settings:
152 |
153 | # IDE0046: Convert to conditional expression
154 | dotnet_style_prefer_conditional_expression_over_return = false:silent
155 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
156 | dotnet_style_prefer_auto_properties = true:suggestion
157 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
158 | dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
159 | dotnet_style_operator_placement_when_wrapping = beginning_of_line
160 | tab_width = 4
161 | end_of_line = crlf
162 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
163 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
164 | dotnet_style_prefer_compound_assignment = true:suggestion
165 | dotnet_style_prefer_simplified_interpolation = true:suggestion
166 | dotnet_style_namespace_match_folder = true:suggestion
167 |
168 | [*.cs]
169 | # Newline settings
170 | csharp_new_line_before_open_brace = all
171 | csharp_new_line_before_else = true
172 | csharp_new_line_before_catch = true
173 | csharp_new_line_before_finally = true
174 | csharp_new_line_before_members_in_object_initializers = true
175 | csharp_new_line_before_members_in_anonymous_types = true
176 | csharp_new_line_between_query_expression_clauses = true
177 |
178 | # Indentation preferences
179 | csharp_indent_block_contents = true
180 | csharp_indent_braces = false
181 | csharp_indent_case_contents = true
182 | csharp_indent_case_contents_when_block = true
183 | csharp_indent_switch_labels = true
184 | csharp_indent_labels = flush_left
185 |
186 | # Prefer "var" everywhere
187 | csharp_style_var_for_built_in_types = true:suggestion
188 | csharp_style_var_when_type_is_apparent = true:suggestion
189 | csharp_style_var_elsewhere = true:suggestion
190 |
191 | # Prefer method-like constructs to have a block body
192 | csharp_style_expression_bodied_methods = false:none
193 | csharp_style_expression_bodied_constructors = false:none
194 | csharp_style_expression_bodied_operators = false:none
195 |
196 | # Prefer property-like constructs to have an expression-body
197 | csharp_style_expression_bodied_properties = true:none
198 | csharp_style_expression_bodied_indexers = true:none
199 | csharp_style_expression_bodied_accessors = true:none
200 |
201 | # Suggest more modern language features when available
202 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
203 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
204 | csharp_style_inlined_variable_declaration = true:suggestion
205 | csharp_style_throw_expression = true:suggestion
206 | csharp_style_conditional_delegate_call = true:suggestion
207 |
208 | # Space preferences
209 | csharp_space_after_cast = false
210 | csharp_space_after_colon_in_inheritance_clause = true
211 | csharp_space_after_comma = true
212 | csharp_space_after_dot = false
213 | csharp_space_after_keywords_in_control_flow_statements = true
214 | csharp_space_after_semicolon_in_for_statement = true
215 | csharp_space_around_binary_operators = before_and_after
216 | csharp_space_around_declaration_statements = do_not_ignore
217 | csharp_space_before_colon_in_inheritance_clause = true
218 | csharp_space_before_comma = false
219 | csharp_space_before_dot = false
220 | csharp_space_before_open_square_brackets = false
221 | csharp_space_before_semicolon_in_for_statement = false
222 | csharp_space_between_empty_square_brackets = false
223 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
224 | csharp_space_between_method_call_name_and_opening_parenthesis = false
225 | csharp_space_between_method_call_parameter_list_parentheses = false
226 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
227 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
228 | csharp_space_between_method_declaration_parameter_list_parentheses = false
229 | csharp_space_between_parentheses = false
230 | csharp_space_between_square_brackets = false
231 |
232 | # Blocks are allowed
233 | csharp_prefer_braces = true:silent
234 | csharp_preserve_single_line_blocks = true
235 | csharp_preserve_single_line_statements = true
236 |
237 | # SA0001: XML comment analysis is disabled due to project configuration
238 | dotnet_diagnostic.SA0001.severity = none
239 |
240 | # SA1413: Use trailing comma in multi-line initializers
241 | dotnet_diagnostic.SA1413.severity = none
242 |
243 | # SA1101: Prefix local calls with this
244 | dotnet_diagnostic.SA1101.severity = none
245 |
246 | # SA1116: The parameters should begin on the line after the declaration, whenever the parameter span across multiple lines
247 | dotnet_diagnostic.SA1116.severity = none
248 |
249 | # SA1117: The parameters should all be placed on the same line or each parameter should be placed on its own line
250 | dotnet_diagnostic.SA1117.severity = none
251 |
252 | # SA1118: Parameters should not span multiple lines
253 | dotnet_diagnostic.SA1118.severity = none
254 |
255 | # SA1200: Using directive should appear within a namespace declaration
256 | dotnet_diagnostic.SA1200.severity = none
257 |
258 | # SA1201: A method should not follow a class
259 | dotnet_diagnostic.SA1201.severity = none
260 |
261 | # SA1202: 'public' members should come before 'private' members
262 | dotnet_diagnostic.SA1202.severity = none
263 |
264 | # SA1204: Static members should appear before non-static members
265 | dotnet_diagnostic.SA1204.severity = none
266 |
267 | # SA1309: Field should not begin with an underscore
268 | dotnet_diagnostic.SA1309.severity = none
269 |
270 | # SA1310: Field should not contain an underscore
271 | dotnet_diagnostic.SA1310.severity = none
272 |
273 | # SA1502: Element should not be on a single line
274 | dotnet_diagnostic.SA1502.severity = none
275 |
276 | # CA1303: Do not pass literals as localized parameters
277 | dotnet_diagnostic.CA1303.severity = none
278 |
279 | # SA1600: Elements should be documented
280 | dotnet_diagnostic.SA1600.severity = none
281 |
282 | # SA1601: Partial elements should be documented
283 | dotnet_diagnostic.SA1601.severity = none
284 |
285 | # CA1819: Properties should not return arrays
286 | dotnet_diagnostic.CA1819.severity = none
287 |
288 | # CA2225: Operator overloads have named alternatives
289 | dotnet_diagnostic.CA2225.severity = none
290 |
291 | # CA1812: Avoid uninstantiated internal classes
292 | dotnet_diagnostic.CA1812.severity = none
293 |
294 | # CA1711: Identifiers should not have incorrect suffix
295 | dotnet_diagnostic.CA1711.severity = none
296 |
297 | # CA1716: Using a reserved keyword as the name of a namespace makes it harder for consumers in other languages to use the namespace
298 | dotnet_diagnostic.CA1716.severity = none
299 |
300 | # CA1308: Normalize strings to uppercase
301 | dotnet_diagnostic.CA1308.severity = none
302 | csharp_using_directive_placement = outside_namespace:silent
303 | csharp_prefer_simple_using_statement = true:suggestion
304 | csharp_style_namespace_declarations = block_scoped:silent
305 | csharp_style_prefer_method_group_conversion = true:silent
306 | csharp_style_prefer_top_level_statements = true:silent
307 | csharp_style_expression_bodied_lambdas = true:silent
308 | csharp_style_expression_bodied_local_functions = false:silent
309 | csharp_style_prefer_null_check_over_type_check = true:suggestion
310 | csharp_prefer_simple_default_expression = true:suggestion
311 | csharp_style_prefer_local_over_anonymous_function = true:suggestion
312 | csharp_style_prefer_index_operator = true:suggestion
313 | csharp_style_prefer_range_operator = true:suggestion
314 | csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
315 | csharp_style_prefer_tuple_swap = true:suggestion
316 | csharp_style_prefer_utf8_string_literals = true:suggestion
317 | csharp_style_deconstructed_variable_declaration = false:suggestion
318 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion
319 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent
320 |
321 | # Default severity for analyzer diagnostics with category 'Style'
322 | dotnet_analyzer_diagnostic.category-Style.severity = none
323 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set the merge driver for project and solution files
8 | #
9 | # Merging from the command prompt will add diff markers to the files if there
10 | # are conflicts (Merging from VS is not affected by the settings below, in VS
11 | # the diff markers are never inserted). Diff markers may cause the following
12 | # file extensions to fail to load in VS. An alternative would be to treat
13 | # these files as binary and thus will always conflict and require user
14 | # intervention with every merge. To do so, just comment the entries below and
15 | # uncomment the group further below
16 | ###############################################################################
17 |
18 | *.sln text eol=crlf
19 | *.csproj text eol=crlf
20 | *.vbproj text eol=crlf
21 | *.vcxproj text eol=crlf
22 | *.vcproj text eol=crlf
23 | *.dbproj text eol=crlf
24 | *.fsproj text eol=crlf
25 | *.lsproj text eol=crlf
26 | *.wixproj text eol=crlf
27 | *.modelproj text eol=crlf
28 | *.sqlproj text eol=crlf
29 | *.wwaproj text eol=crlf
30 |
31 | *.xproj text eol=crlf
32 | *.props text eol=crlf
33 | *.filters text eol=crlf
34 | *.vcxitems text eol=crlf
35 |
36 |
37 | #*.sln merge=binary
38 | #*.csproj merge=binary
39 | #*.vbproj merge=binary
40 | #*.vcxproj merge=binary
41 | #*.vcproj merge=binary
42 | #*.dbproj merge=binary
43 | #*.fsproj merge=binary
44 | #*.lsproj merge=binary
45 | #*.wixproj merge=binary
46 | #*.modelproj merge=binary
47 | #*.sqlproj merge=binary
48 | #*.wwaproj merge=binary
49 |
50 | #*.xproj merge=binary
51 | #*.props merge=binary
52 | #*.filters merge=binary
53 | #*.vcxitems merge=binary
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Users referenced in this file will automatically be requested as reviewers for PRs that modify the given paths.
2 | # See https://help.github.com/articles/about-code-owners/
3 |
4 | * @dotnet/dotnet-upgrade-assistant-admin
5 |
6 | *maui* @dotnet/dotnet-upgrade-assistant-maui @dotnet/dotnet-upgrade-assistant-admin
7 | /src/extensions/maui/ @dotnet/dotnet-upgrade-assistant-maui @dotnet/dotnet-upgrade-assistant-admin
8 |
9 | *uwp* @dotnet/dotnet-upgrade-assistant-uwp @dotnet/dotnet-upgrade-assistant-admin
10 | /src/extensions/windows/ @dotnet/dotnet-upgrade-assistant-uwp @dotnet/dotnet-upgrade-assistant-admin
11 |
12 | *wcf* @dotnet/dotnet-upgrade-assistant-wcf @dotnet/dotnet-upgrade-assistant-admin
13 | /src/extensions/wcf/ @dotnet/dotnet-upgrade-assistant-wcf @dotnet/dotnet-upgrade-assistant-admin
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/10_design_proposal.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🤔 Design proposal
3 | about: Collaborate on a design for a feature/fix or other change
4 | labels: design-proposal
5 | ---
6 |
7 |
10 |
11 | ## Summary
12 |
13 | 1-2 sentences. Say what this is about.
14 |
15 | ## Motivation and goals
16 |
17 | 1-2 paragraphs, or a bullet-pointed list. What existing pain points does this solve? What evidence shows it's valuable to solve this?
18 |
19 | ## In scope
20 |
21 | A list of major scenarios, perhaps in priority order.
22 |
23 | ## Out of scope
24 |
25 | Scenarios you explicitly want to exclude.
26 |
27 | ## Risks / unknowns
28 |
29 | How might developers misinterpret/misuse this? How might implementing it restrict us from other enhancements in the future? Also list any perf/security/correctness concerns.
30 |
31 | ## Examples
32 |
33 | Give brief examples of possible developer experiences (e.g., code they would write).
34 |
35 | Don't be deeply concerned with how it would be implemented yet. Your examples could even be from other technology stacks.
36 |
37 |
50 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/20_bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🐞 Bug report
3 | about: Create a report about something that is not working
4 | ---
5 |
6 |
11 |
12 |
15 |
16 | - [ ] Include the log file `upgrade-assistant.clef` that is produced in the working directory
17 |
18 | ### Describe the bug
19 | A clear and concise description of what the bug is.
20 |
21 | ### To Reproduce
22 |
25 |
26 | ### Exceptions (if any)
27 |
30 |
31 | ### Further technical details
32 | - Windows version and bitness info (32-bit or 64-bit)
33 | - Include the output of `dotnet --list-sdks`
34 | - Include the output of `upgrade-assistant --version`
35 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 | - [ ] You've read the [Contributor Guide](../CONTRIBUTING.md) and [Code of Conduct](../CODE-OF-CONDUCT.md).
8 | - [ ] You've included unit or integration tests for your change, where applicable.
9 | - [ ] You've included inline docs for your change, where applicable.
10 | - [ ] There's an open issue for the PR that you are making. If you'd like to propose a new feature or change, please open an issue to discuss the change or find an existing issue.
11 |
12 |
13 | **For PRs which target a specific extension within UA, changes won't be approved by a member of the `dotnet-upgrade-assistant-admin` group until a member of the code owners of the extension has approved, when required.**
14 |
15 |
16 |
17 | ## Description
18 | Detail 1
19 | Detail 2
20 |
21 | Addresses #bugnumber (in this specific format)
22 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: upgrade-assistant CI/CD pipeline
2 |
3 | on: [push, pull_request, workflow_dispatch]
4 |
5 | jobs:
6 | ci:
7 | runs-on: windows-latest
8 | environment: ci
9 | steps:
10 | - name: Checkout repository
11 | uses: actions/checkout@v4
12 | with:
13 | submodules: recursive
14 | fetch-depth: 0
15 |
16 | - name: Setup/Install the .NET 8 SDK
17 | uses: actions/setup-dotnet@v4
18 | with:
19 | dotnet-version: 8.0.x
20 |
21 | - name: Run .NET restore
22 | run: dotnet restore UpgradeAssistant.Extensions.sln
23 |
24 | - name: Run .NET tool restore
25 | run: dotnet tool restore
26 |
27 | - name: Build solution
28 | run: dotnet build --configuration Debug --no-restore UpgradeAssistant.Extensions.sln
29 |
30 | - name: Run unit tests
31 | run: dotnet test --configuration Debug --no-restore UpgradeAssistant.Extensions.sln
32 |
--------------------------------------------------------------------------------
/.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 | # NPM packages
7 | node_modules/
8 |
9 | # User-specific files
10 | *.rsuser
11 | *.suo
12 | *.user
13 | *.userosscache
14 | *.sln.docstates
15 |
16 | # Log files
17 | log.txt
18 |
19 | # User-specific files (MonoDevelop/Xamarin Studio)
20 | *.userprefs
21 |
22 | # Mono auto generated files
23 | mono_crash.*
24 |
25 | # Build results
26 | [Dd]ebug/
27 | [Dd]ebugPublic/
28 | [Rr]elease/
29 | [Rr]eleases/
30 | x64/
31 | x86/
32 | [Aa][Rr][Mm]/
33 | [Aa][Rr][Mm]64/
34 | bld/
35 | [Bb]in/
36 | [Oo]bj/
37 | [Ll]og/
38 | [Ll]ogs/
39 |
40 | # Visual Studio 2015/2017 cache/options directory
41 | .vs/
42 |
43 | wwwroot/
44 |
45 | # Visual Studio 2017 auto generated files
46 | Generated\ Files/
47 |
48 | # MSTest test Results
49 | [Tt]est[Rr]esult*/
50 | [Bb]uild[Ll]og.*
51 |
52 | # NUnit
53 | *.VisualState.xml
54 | TestResult.xml
55 | nunit-*.xml
56 |
57 | # Build Results of an ATL Project
58 | [Dd]ebugPS/
59 | [Rr]eleasePS/
60 | dlldata.c
61 |
62 | # Benchmark Results
63 | BenchmarkDotNet.Artifacts/
64 |
65 | # .NET Core
66 | project.lock.json
67 | project.fragment.lock.json
68 | artifacts/
69 |
70 | # StyleCop
71 | StyleCopReport.xml
72 |
73 | # Publish Web Output
74 | *.[Pp]ublish.xml
75 | *.azurePubxml
76 | # Note: Comment the next line if you want to checkin your web deploy settings,
77 | # but database connection strings (with potential passwords) will be unencrypted
78 | *.pubxml
79 | *.publishproj
80 |
81 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
82 | # checkin your Azure Web App publish settings, but sensitive information contained
83 | # in these scripts will be unencrypted
84 | PublishScripts/
85 |
86 | # Ignore VS Code temporary files
87 | .vscode/*
88 | !.vscode/settings.json
89 | !.vscode/tasks.json
90 | !.vscode/launch.json
91 | !.vscode/extensions.json
92 |
93 | .packages/
94 | /.dotnet
95 |
96 | # Bundled dotnet tools
97 | .tools/
98 | tools/
99 | /src/extensions/looseassembly/LooseAssembly.NuGet/.data/
100 |
101 | # Generated NuGet packages and content files
102 | src/UpgradeAssistant.Mappings/tmp
103 | src/*/*.nupkg
104 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet/upgrade-assistant/1acbdcac7dcfbdc02ef3b64aeaf7f71bda826bc6/.gitmodules
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": ".NET Core Launch (console)",
6 | "type": "coreclr",
7 | "request": "launch",
8 | "preLaunchTask": "build",
9 | "program": "${workspaceFolder}/artifacts/bin/Microsoft.DotNet.UpgradeAssistant.Cli/Debug/net6.0/Microsoft.DotNet.UpgradeAssistant.Cli.dll",
10 | "args": [],
11 | "cwd": "${workspaceFolder}/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli",
12 | "console": "internalConsole",
13 | "stopAtEntry": false
14 | },
15 | {
16 | "name": ".NET Core Attach",
17 | "type": "coreclr",
18 | "request": "attach"
19 | }
20 | ]
21 | }
--------------------------------------------------------------------------------
/.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}/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/Microsoft.DotNet.UpgradeAssistant.Cli.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/Microsoft.DotNet.UpgradeAssistant.Cli.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "--project",
36 | "${workspaceFolder}/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/Microsoft.DotNet.UpgradeAssistant.Cli.csproj"
37 | ],
38 | "problemMatcher": "$msCompile"
39 | }
40 | ]
41 | }
--------------------------------------------------------------------------------
/.vsconfig:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0",
3 | "components": [
4 | "Microsoft.VisualStudio.Component.CoreEditor",
5 | "Microsoft.VisualStudio.Workload.CoreEditor",
6 | "Microsoft.NetCore.Component.Runtime.5.0",
7 | "Microsoft.NetCore.Component.Runtime.3.1",
8 | "Microsoft.NetCore.Component.SDK",
9 | "Microsoft.VisualStudio.Component.NuGet",
10 | "Microsoft.Net.Component.4.6.1.TargetingPack",
11 | "Microsoft.VisualStudio.Component.Roslyn.Compiler",
12 | "Microsoft.VisualStudio.Component.Roslyn.LanguageServices",
13 | "Microsoft.VisualStudio.Component.FSharp",
14 | "Microsoft.VisualStudio.Component.PortableLibrary",
15 | "Microsoft.ComponentGroup.ClickOnce.Publish",
16 | "Microsoft.NetCore.Component.DevelopmentTools",
17 | "Microsoft.VisualStudio.Component.FSharp.WebTemplates",
18 | "Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions",
19 | "Microsoft.VisualStudio.Component.DockerTools",
20 | "Microsoft.NetCore.Component.Web",
21 | "Microsoft.Net.Component.4.8.SDK",
22 | "Microsoft.Net.Component.4.7.2.TargetingPack",
23 | "Microsoft.Net.ComponentGroup.DevelopmentPrerequisites",
24 | "Microsoft.VisualStudio.Component.TypeScript.4.3",
25 | "Microsoft.VisualStudio.Component.JavaScript.TypeScript",
26 | "Microsoft.VisualStudio.Component.JavaScript.Diagnostics",
27 | "Microsoft.Component.MSBuild",
28 | "Microsoft.VisualStudio.Component.TextTemplating",
29 | "Component.Microsoft.VisualStudio.RazorExtension",
30 | "Microsoft.VisualStudio.Component.IISExpress",
31 | "Microsoft.VisualStudio.Component.SQL.ADAL",
32 | "Microsoft.VisualStudio.Component.SQL.LocalDB.Runtime",
33 | "Microsoft.VisualStudio.Component.Common.Azure.Tools",
34 | "Microsoft.VisualStudio.Component.SQL.CLR",
35 | "Microsoft.VisualStudio.Component.MSODBC.SQL",
36 | "Microsoft.VisualStudio.Component.MSSQL.CMDLnUtils",
37 | "Microsoft.VisualStudio.Component.ManagedDesktop.Core",
38 | "Microsoft.Net.Component.4.5.2.TargetingPack",
39 | "Microsoft.Net.Component.4.5.TargetingPack",
40 | "Microsoft.VisualStudio.Component.SQL.SSDT",
41 | "Microsoft.VisualStudio.Component.SQL.DataSources",
42 | "Component.Microsoft.Web.LibraryManager",
43 | "Component.Microsoft.WebTools.BrowserLink.WebLivePreview",
44 | "Microsoft.VisualStudio.ComponentGroup.Web",
45 | "Microsoft.VisualStudio.Component.DependencyValidation.Enterprise",
46 | "Microsoft.VisualStudio.Workload.NetCoreTools"
47 | ]
48 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to the .NET Upgrade Assistant will be documented in this file. Version numbers below will follow (best effort) the corresponding NuGet package versions here: https://www.nuget.org/packages/upgrade-assistant/
4 |
5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
6 |
7 |
--------------------------------------------------------------------------------
/CODE-OF-CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | This project has adopted the code of conduct defined by the Contributor Covenant
4 | to clarify expected behavior in our community.
5 |
6 | For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct).
7 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | One of the easiest ways for you to contribute is to participate in discussions on GitHub issues. You can also contribute by submitting pull requests with code changes.
4 |
5 | ## General feedback and discussions?
6 |
7 | Start a discussion on the [repository issue tracker](https://github.com/dotnet/upgrade-assistant/issues).
8 |
9 | ## Bugs and feature requests?
10 |
11 | For non-security related bugs, please [log a new issue](https://github.com/dotnet/upgrade-assistant/issues) or simply click [this link](https://github.com/dotnet/upgrade-assistant/issues/new?assignees=&labels=bug&template=20_bug_report.md).
12 |
13 | ## Get and build source code
14 |
15 | In order to get the source, clone the repo with submodules:
16 |
17 | ```bash
18 | git clone https://github.com/dotnet/upgrade-assistant
19 | git submodule update --init --recursive
20 | ```
21 |
22 | Once complete, launch `UpgradeAssistant.Extensions.sln` from the upgrade-assistant directory.
23 |
24 | ## How to submit a PR
25 |
26 | We are always happy to see PRs from community members both for bug fixes as well as new features.
27 |
28 | To help you be successful we've put together a few simple rules to follow when you prepare to contribute to our codebase:
29 |
30 | ### Before submitting the pull request
31 |
32 | Before submitting a pull request, make sure that it checks the following requirements:
33 |
34 | - You find an existing issue with the "help-wanted" label or discuss with the team to agree on adding a new issue with that label
35 | - You post a high-level description of how it will be implemented, and receive a positive acknowledgement from the team before getting too committed to the approach or investing too much effort implementing it
36 | - You add test coverage following existing patterns within the codebase
37 | - Your code matches the existing syntax conventions within the codebase
38 | - Your PR is small, focused, and avoids making unrelated changes
39 |
40 | If your pull request contains any of the below, it's less likely to be merged:
41 |
42 | - Changes that break existing functionality.
43 | - Changes that are only wanted by one person/company. Changes need to benefit a large enough portion of upgrade-assistant users.
44 | - Changes that add entirely new feature areas without prior agreement
45 | - Changes that are mostly about refactoring existing code or code style
46 | - Very large PRs that would take hours to review (remember, we're trying to help lots of people at once). For larger work areas, please discuss with us to find ways of breaking it down into smaller, incremental pieces that can go into separate PRs.
47 |
48 | ### Submitting a pull request
49 |
50 | You will need to sign a [Contributor License Agreement](https://cla.dotnetfoundation.org/) when submitting your pull request. To complete the Contributor License Agreement (CLA), you will need to follow the instructions provided by the CLA bot when you send the pull request. This needs to only be done once for any .NET Foundation OSS project.
51 |
52 | If you don't know what a pull request is read this article: https://help.github.com/articles/using-pull-requests. Make sure the repository can build and all tests pass. Familiarize yourself with the project workflow and our coding conventions. For general coding guidelines, see [here](https://github.com/dotnet/aspnetcore/wiki/Engineering-guidelines#coding-guidelines).
53 |
54 | ### During pull request review
55 |
56 | A core contributor will review your pull request and provide feedback. To ensure that there is not a large backlog of inactive PRs, the pull request will be marked as stale after two weeks of no activity. After another four days, it will be closed.
57 |
58 | ## Resources to help you get started
59 |
60 | Here are some resources to help you get started on how to contribute code or new content.
61 |
62 | - Look at the [Contributor documentation](/README.md) to get started on building the source code on your own.
63 | - The **UpgradeAssistant.Mappings.Tests** project will validate that the syntax of packagemap and apimap json files is correct.
64 |
65 | ### Feedback
66 |
67 | Your pull request will now go through extensive checks by the subject matter experts on our team. Please be patient while upgrade-assistant team gets through it. Update your pull request according to feedback until it is approved by one of the upgrade-assistant team members. Once the PR is approved, one of the upgrade-assistant team members will merge your PR into the repo.
68 |
69 | ## Code of conduct
70 |
71 | See [CODE-OF-CONDUCT.md](./CODE-OF-CONDUCT.md)
72 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | all
6 | 3.6.133
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(GenerateNuspecDependsOn);TimestampNugetPackage
5 |
6 |
7 | true
8 |
9 |
10 |
11 |
12 | $([System.DateTime]::Now.ToString(yyyyMMdd-HHmm))
13 | $(PackageVersion)-preview.$(CurrentDate)
14 | version=$(PackageVersion)
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) .NET Foundation and Contributors
4 |
5 | All rights reserved.
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
--------------------------------------------------------------------------------
/NuGet.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Upgrade Assistant
2 |
3 | This repository is now home to 3rd-party extensions to the [Visual Studio Upgrade Assistant](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.upgradeassistant)
4 | and its [CLI](https://www.nuget.org/packages/upgrade-assistant#versions-body-tab) alternative.
5 |
6 | ## Status
7 |
8 | | |Build (Debug)|Build (Release)|
9 | |---|:--:|:--:|
10 | | ci |[](https://dev.azure.com/dnceng/public/_build/latest?definitionId=953&branchName=main)|[](https://dev.azure.com/dnceng/public/_build/latest?definitionId=953&branchName=main)|
11 | | official | [](https://dev.azure.com/dnceng/internal/_build/latest?definitionId=949&branchName=main)|[](https://dev.azure.com/dnceng/internal/_build/latest?definitionId=949&branchName=main)|
12 |
13 | ## Overview
14 |
15 | This project aims to bring extensibility to the dotnet Upgrade Assistant tool. One of the extensibility points are mappings such as [Package Maps](docs/Extensibility.PackageMaps.md)
16 | and [API Maps](docs/Extensibility.APIMaps.md), allowing third-party vendors to provide the necessary information needed to upgrade projects from old versions of .NET and/or old
17 | vendor APIs to a newer version.
18 |
19 | ### Supported project types and languages
20 |
21 | Currently, the tool supports the following project types:
22 |
23 | - ASP.NET MVC
24 | - Windows Forms
25 | - Windows Presentation Foundation (WPF)
26 | - Console app
27 | - Libraries
28 | - UWP to Windows App SDK (WinUI)
29 | - Xamarin.Forms to .NET MAUI
30 |
31 | The tool supports C# and Visual Basic projects.
32 |
33 | ### Extensibility
34 |
35 | #### Mappings Extension
36 |
37 | ##### Directory Structure
38 |
39 | In the [src/UpgradeAssistant.Mappings/mappings](src/UpgradeAssistant.Mappings/mappings) directory, each vendor *SHOULD* create their own subdirectory.
40 | Each vendor *MAY* decide to subdivide their vendor-specific subdirectory into further subdirectories based on product names or any other criteria that makes sense for
41 | their needs.
42 |
43 | Nested in these subdirectories are 3 types of files: *metadata.json*, *packagemap.json*, and *apimap.json*.
44 |
45 | ##### metadata.json
46 |
47 | Whether you want to provide mappings for NuGet package upgrades or API changes, you'll want to have a **metadata.json** file.
48 |
49 | The *metadata.json* file should look something like this:
50 |
51 | ```json
52 | {
53 | "traits": "",
54 | "order": 1000
55 | }
56 | ```
57 |
58 | The *traits* and *order* metadata in *metadata.json* files are automatically inherited by any *packagemap.json* and *apimap.json* files that exist
59 | in the same directory or any subdirectory (regardless of subdirectiory depth).
60 |
61 | ###### The "traits" property
62 |
63 | The *traits* property is a string that defines in what circumstances the Upgrade Assistant should apply the package mappings and/or API mappings.
64 |
65 | For example, if a vendor wants to upgrade NuGet package references for a **Xamarin.Forms** project, they might use a *traits* string of `Xamarin`
66 | or `(Xamarin | Maui)`.
67 |
68 | More information about traits can be found [here](docs/Traits.md).
69 |
70 | ###### The "order" property
71 |
72 | The *order* property is an integer value that is used for the purposes of sorting the order in which package mapping changes and API mapping changes are applied.
73 |
74 | We recommend a starting value of *1000* for vendor-specific mappings.
75 |
76 | ##### Package Maps
77 |
78 | Package Maps define how a NuGet package reference in a project can be upgraded to reference either an alternative NuGet package or a newer version of the same
79 | NuGet package.
80 |
81 | Below is an example *packagemap.json* file with comments that explain some of the different possibilities:
82 |
83 | ```json
84 | {
85 | "packages": [
86 | {
87 | "name": "Vendor.ProductName",
88 | "frameworks": {
89 | ".NETCoreApp,Version=v5.0": [
90 | // An empty array will remove the package from projects with the specified target framework.
91 | ],
92 | ".NETCoreApp,Version=v6.0": [
93 | {
94 | // An empty package definition will upgrade the package to the latest available version of the same package name.
95 | // When the "name" property is null or unspecified, it will automatically default to the original package name.
96 | // When the "version" is null or unspecified, it will default to the latest version available.
97 | // Thus, if neither are specified, it will default to the latest version of the original package.
98 | }
99 | ],
100 | ".NETCoreApp,Version=v7.0": [
101 | {
102 | // Specifying a "name" and "version" will upgrade the package to an exact package.
103 | "name": "Vendor.NewProductName",
104 | "version": "7.0.11"
105 | }
106 | ],
107 | ".NETCoreApp,Version=v8.0": [
108 | {
109 | // Specifying a "name" and a wildcard "version" will upgrade the package to the latest version that matches the wildcard version.
110 | "name": "Vendor.ProductName.Abstractions",
111 | "version": "8.*",
112 |
113 | // Specifying "prerelease": true will tell the UpgradeAssistant that it can match against prerelease versions.
114 | "prerelease": true
115 | },
116 | {
117 | "name": "Vendor.ProductName.Core",
118 | "version": "8.*",
119 | "prerelease": true
120 | }
121 | ]
122 | }
123 | }
124 | ]
125 | }
126 | ```
127 |
128 | It is also worth noting that in the above example, when the Upgrade Assistant is upgrading a project to .NET 8.0, it will upgrade references to `Vendor.ProductName` to
129 | `Vendor.ProductName.Abstractions` *and* `Vendor.ProductName.Core`. This is useful in scenarios where a package has been broken into multiple packages.
130 |
131 | ##### API Maps
132 |
133 | API Maps define how the Upgrade Assistant should transform namespaces, type names, method names and property names in user-code when upgrading a project.
134 |
135 | An *apimap.json* file consists of a dictionary of API mappings. An example *apimap.json* file with only 1 mapping might look like this:
136 |
137 | ```json
138 | {
139 | "Windows.UI.WindowManagement.AppWindow.TryCreateAsync": {
140 | "value": "Microsoft.UI.Windowing.AppWindow.Create", // new value to replace old one with, if empty if state is not Replaced
141 | "kind": "method", // method|property|namespace|type
142 | "state": "Replaced", // Replaced|Removed|NotImplemented
143 | "isStatic": true,
144 | "needsManualUpgrade": false, // if true, only comment is added, no other code modifications happening
145 | "documentationUrl": "some url", // link to documentation URL,
146 | "needsTodoInComment": true, // if true TODO is added to the comment if comment is being added
147 | "isAsync": false,
148 | "messageId": "resource id", // in case custom comment needs to be added, this resource id will be looked up in the ResourceManager,
149 | "MessageParams": [ "", "" ] // parameters to be passed into string format for custom message
150 | }
151 | }
152 | ```
153 |
154 | The above example would transform all occurrences of `Windows.UI.WindowManagement.AppWindow.TryCreateAsync()` in user-code with `Microsoft.UI.Windowing.AppWindow.Create`.
155 |
156 | Since the Upgrade Assistant uses Roslyn to parse and manipulate user-code, even user-code that calls `AppWindow.TryCreate()` will be upgraded.
157 |
158 | For XAML upgrades additional mappings are needed. Here is an example:
159 | ```json
160 | {
161 | "http://xamarin.com/schemas/2020/toolkit": {
162 | "value": "http://schemas.microsoft.com/dotnet/2022/maui/toolkit",
163 | "kind": "xmlnamespace",
164 | "state": "Replaced",
165 | "properties": [
166 | "Xamarin.CommunityToolkit.Behaviors",
167 | "Xamarin.CommunityToolkit.Converters",
168 | "Xamarin.CommunityToolkit.UI.Views"
169 | ]
170 | }
171 | }
172 | ```
173 |
174 | Upgrade Assistant will update XML namespaces like `xmlns:toolkit="http://xamarin.com/schemas/2020/toolkit"` to `xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"`.
175 | The `properties` contain values from assembly level XmlnsDefinitionAttribute from old SDK. Those values will be used to update
176 | xmlns values like `xmlns:tk='clr-namespace:Xamarin.CommunityToolkit.Behaviors; assembly=...'` to `xmlns:tk='clr-namespace:Microsoft.Maui.Behaviors; assembly=...'`.
177 |
178 | ## Engage, Contribute and Give Feedback
179 |
180 | Some of the best ways to contribute are to use the tool to upgrade your apps to the latest version of .NET (STS, LTS, or preview), file issues for feature-requests or bugs, join in design conversations, and make pull-requests.
181 |
182 | Check out the [contributing](/CONTRIBUTING.md) page for more details on the best places to log issues, start discussions, PR process etc.
183 |
184 | Happy upgrading to the latest version of .NET (STS, LTS, or preview)!
185 |
--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------
1 | # Support #
2 |
3 | ### How to file issues and get help ###
4 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a [new Issue](https://github.com/dotnet/upgrade-assistant/issues/new/choose).
5 |
6 |
7 | ### Microsoft Support Policy ###
8 | Support for this project is limited to the resources listed above.
--------------------------------------------------------------------------------
/UpgradeAssistant.Extensions.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.9.34701.34
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UpgradeAssistant.Mappings", "src\UpgradeAssistant.Mappings\UpgradeAssistant.Mappings.csproj", "{BC5DCE4F-9D8F-4DC2-94C1-8DFCD58C7CA1}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UpgradeAssistant.Mappings.Tests", "tests\UpgradeAssistant.Mappings.Tests\UpgradeAssistant.Mappings.Tests.csproj", "{7B5CF532-A3AD-4375-8B15-7A6B63A1B00F}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {BC5DCE4F-9D8F-4DC2-94C1-8DFCD58C7CA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {BC5DCE4F-9D8F-4DC2-94C1-8DFCD58C7CA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {BC5DCE4F-9D8F-4DC2-94C1-8DFCD58C7CA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {BC5DCE4F-9D8F-4DC2-94C1-8DFCD58C7CA1}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {7B5CF532-A3AD-4375-8B15-7A6B63A1B00F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {7B5CF532-A3AD-4375-8B15-7A6B63A1B00F}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {7B5CF532-A3AD-4375-8B15-7A6B63A1B00F}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {7B5CF532-A3AD-4375-8B15-7A6B63A1B00F}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {B84D8086-691A-4DE6-8E4F-50CE9F3C6BC2}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/azure-pipelines/build.yml:
--------------------------------------------------------------------------------
1 | name: $(Date:yyyyMMdd).$(Rev:r)
2 | parameters:
3 | - name: OfficialRelease
4 | displayName: "Build and publish an official release"
5 | type: boolean
6 | default: false
7 | variables:
8 | - name: Build.OfficialRelease
9 | value: ${{ parameters.OfficialRelease }}
10 | - name: Codeql.Enabled
11 | value: true
12 | - name: SignType
13 | ${{ if eq(parameters.OfficialRelease, true) }}:
14 | value: real
15 | ${{ else }}:
16 | value: test
17 | - name: TeamName
18 | value: dotnetupgradeassistant
19 | - name: TimestampPackage
20 | value: ${{ not (parameters.OfficialRelease) }}
21 | trigger:
22 | branches:
23 | include:
24 | - refs/heads/main
25 | resources:
26 | repositories:
27 | - repository: MicroBuildTemplate
28 | type: git
29 | name: 1ESPipelineTemplates/MicroBuildTemplate
30 | ref: refs/tags/release
31 | extends:
32 | template: azure-pipelines/MicroBuild.1ES.Official.yml@MicroBuildTemplate
33 | parameters:
34 | pool:
35 | name: VSEngSS-MicroBuild2022-1ES
36 | sdl:
37 | sourceAnalysisPool:
38 | name: AzurePipelines-EO
39 | image: 1ESPT-Windows2022
40 | os: windows
41 | customBuildTags:
42 | - ES365AIMigrationTooling
43 | stages:
44 | - stage: Build
45 | jobs:
46 | - job: Build
47 | displayName: 'Build'
48 | cancelTimeoutInMinutes: 1
49 | pool:
50 | name: VSEngSS-MicroBuild2022-1ES
51 | templateContext:
52 | mb:
53 | signing:
54 | enabled: true
55 | signType: $(SignType)
56 | sbom:
57 | enabled: true
58 | outputs:
59 | - output: pipelineArtifact
60 | displayName: 'Publish Logs'
61 | condition: always()
62 | targetPath: '$(Build.ArtifactStagingDirectory)\Logs'
63 | artifactType: container
64 | sbomEnabled: false
65 | - output: nuget
66 | displayName: 'Publish packages to NuGet.org'
67 | condition: and(succeeded(), eq(variables['Build.OfficialRelease'], 'true'))
68 | packageParentPath: '$(Build.ArtifactStagingDirectory)\Packages'
69 | packagesToPush: $(Build.ArtifactStagingDirectory)\Packages\*.nupkg;!$(Build.ArtifactStagingDirectory)\Packages\*.symbols.nupkg
70 | nuGetFeedType: external
71 | publishFeedCredentials: UpgradeAssistantExtensions-NuGet.org
72 | sbomEnabled: false
73 | steps:
74 | - checkout: self
75 | clean: true
76 | fetchTags: false
77 | persistCredentials: true
78 | - task: DeleteFiles@1
79 | displayName: Delete output files from src
80 | inputs:
81 | SourceFolder: src
82 | Contents: |-
83 | **\bin\**\*
84 | **\obj\**\*
85 | - task: UseDotNet@2
86 | displayName: Use .NET Core SDK 8.x
87 | inputs:
88 | version: 8.x
89 | performMultiLevelLookup: true
90 | - task: NuGetToolInstaller@1
91 | displayName: Use NuGet 6.x
92 | inputs:
93 | versionSpec: 6.x
94 | - task: NuGetAuthenticate@1
95 | displayName: NuGet Authenticate
96 | - task: NuGetCommand@2
97 | displayName: Restore NuGet Packages
98 | inputs:
99 | solution: UpgradeAssistant.Extensions.sln
100 | - task: VSBuild@1
101 | displayName: Build UpgradeAssistant.Extensions.sln
102 | inputs:
103 | solution: UpgradeAssistant.Extensions.sln
104 | platform: Any CPU
105 | configuration: Release
106 | clean: true
107 | msbuildArgs: /p:PublicRelease=$(Build.OfficialRelease)
108 | - task: VSTest@2
109 | displayName: Run Unit Tests
110 | inputs:
111 | testAssemblyVer2: |-
112 | **\UpgradeAssistant.*.Tests.dll
113 | - task: MicroBuildSigningPlugin@4
114 | inputs:
115 | signType: '$(SignType)'
116 | feedSource: 'https://devdiv.pkgs.visualstudio.com/DefaultCollection/_packaging/MicroBuildToolset/nuget/v3/index.json'
117 | env:
118 | TeamName: '$(TeamName)'
119 | - task: VSBuild@1
120 | displayName: Build Mappings NuGet package
121 | inputs:
122 | solution: src\UpgradeAssistant.Mappings\UpgradeAssistant.Mappings.csproj
123 | msbuildArgs: /t:Pack /p:PublicRelease=$(Build.OfficialRelease) /p:TimestampPackage=$(TimestampPackage) /p:PackageOutputPath="$(Build.ArtifactStagingDirectory)\Packages"
124 | configuration: release
125 | - task: NuGetCommand@2
126 | displayName: Restore NuGet Packages (Package Signing)
127 | inputs:
128 | solution: azure-pipelines\nuget-package.signproj
129 | - task: MSBuild@1
130 | displayName: 'Sign NuGet Packages'
131 | inputs:
132 | solution: azure-pipelines\nuget-package.signproj
133 | msbuildArguments: '/t:SignFiles /v:diagnostic /bl:$(Build.ArtifactStagingDirectory)\Logs\SignNugetPackages.binlog /p:OutDir=$(Build.ArtifactStagingDirectory)\Packages /p:PackagesPath=$(Build.ArtifactStagingDirectory)\Packages'
134 |
--------------------------------------------------------------------------------
/azure-pipelines/nuget-package.signproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | net8.0
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | NuGet
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/docs/Extensibility.APIMaps.md:
--------------------------------------------------------------------------------
1 | # API Map transformations
2 |
3 | ## Overview
4 |
5 | Its pretty common for different project flavor upgrades to:
6 |
7 | - replace old namespaces with new ones (in usings or in invocations etc)
8 | - replace old type names (including namespace) with new types
9 | - replace member invocations (methods, properties) static or instance based with new ones
10 |
11 | and/or add a comment next to a corresponding old code element with suggestions about manual upgrade steps when automatic upgrades are not possible.
12 |
13 | All of that is generalized via API maps and corresponding base abstract transformers to be subclassed for each project scenario and provide corresponding maps to be executed by the base transformers logic.
14 |
15 | All base transformers working with API maps support both C# and VB.
16 |
17 | ## API map format
18 |
19 | An API map is a json file containing a dictionary of map between old entity (namespace, type or member) to the model describing how to replace it with new one.
20 |
21 | Here is a sample json with descriptions for each model element:
22 |
23 | ```json
24 | {
25 | "Windows.UI.WindowManagement.AppWindow.TryCreateAsync": {
26 | "value": "Microsoft.UI.Windowing.AppWindow.Create", // new value to replace old one with, if empty if state is not Replaced
27 | "kind": "method", // method|property|namespace|type
28 | "state": "Replaced", // Replaced|Removed|NotImplemented
29 | "isStatic": true,
30 | "needsManualUpgrade": false, // if true, only comment is added, no other code modifications happening
31 | "documentationUrl": "some url", // link to documentation URL,
32 | "needsTodoInComment": true, // if true TODO is added to the comment if comment is being added
33 | "isAsync": false,
34 | "messageId": "resource id", // [internal only] in case custom comment needs to be added, this resource id will be looked up in the ResourceManager,
35 | "MessageParams": [ "", "" ] // [internal only] parameters to be passed into string format for custom message
36 | }
37 | }
38 | ```
39 |
--------------------------------------------------------------------------------
/docs/Extensibility.PackageMaps.md:
--------------------------------------------------------------------------------
1 | # Package Map transformations
2 |
3 | ## Overview
4 |
5 | Package Maps define how a NuGet package reference in a project can be upgraded to reference either an alternative NuGet package or a newer version of the same
6 | NuGet package.
7 |
8 | ## Package map format
9 |
10 | Below is an example *packagemap.json* file with comments that explain some of the different possibilities:
11 |
12 | ```json
13 | {
14 | "packages": [
15 | {
16 | "name": "Vendor.ProductName",
17 | "frameworks": {
18 | ".NETCoreApp,Version=v5.0": [
19 | // An empty array will remove the package from projects with the specified target framework.
20 | ],
21 | ".NETCoreApp,Version=v6.0": [
22 | {
23 | // An empty package definition will upgrade the package to the latest available version of the same package name.
24 | // When the "name" property is null or unspecified, it will automatically default to the original package name.
25 | // When the "version" is null or unspecified, it will default to the latest version available.
26 | // Thus, if neither are specified, it will default to the latest version of the original package.
27 | }
28 | ],
29 | ".NETCoreApp,Version=v7.0": [
30 | {
31 | // Specifying a "name" and "version" will upgrade the package to an exact package.
32 | "name": "Vendor.NewProductName",
33 | "version": "7.0.11"
34 | }
35 | ],
36 | ".NETCoreApp,Version=v8.0": [
37 | {
38 | // Specifying a "name" and a wildcard "version" will upgrade the package to the latest version that matches the wildcard version.
39 | "name": "Vendor.ProductName.Abstractions",
40 | "version": "8.*",
41 |
42 | // Specifying "prerelease": true will tell the UpgradeAssistant that it can match against prerelease versions.
43 | "prerelease": true
44 | },
45 | {
46 | "name": "Vendor.ProductName.Core",
47 | "version": "8.*",
48 | "prerelease": true
49 | }
50 | ]
51 | }
52 | }
53 | ]
54 | }
55 | ```
56 |
57 | It is also worth noting that in the above example, when the Upgrade Assistant is upgrading a project to .NET 8.0, it will upgrade references to `Vendor.ProductName` to
58 | `Vendor.ProductName.Abstractions` *and* `Vendor.ProductName.Core`. This is useful in scenarios where a package has been broken into multiple packages.
59 |
--------------------------------------------------------------------------------
/docs/Traits.md:
--------------------------------------------------------------------------------
1 | # Traits
2 |
3 | ## What are traits and how they work
4 |
5 | When contracts are discovered, we need to have a way to filter out contracts that are applicable to:
6 |
7 | - current project kind
8 | - current upgrade kind
9 | - project language
10 | - anything else that comes up in the future.
11 |
12 | To generalize this filtering logic, we use `traits` expressions specified in the contract attributes like `SliceNodeTransformer` or `SliceNodeProvider`. This expression has similar syntax as in VS project system's capabilities expressions and platform contracts. However, traits set can contain any trait objects and capabilities are just one example of it.
13 |
14 | To produce this set of unique trait objects we use generic contracts `ITraitProvider` which are annotated via `TraitProviderAttribute` and in turn could be filtered per project flavor. We run all trait providers applicable to current project and combine them into set of unique trait objects. Trait object could be a plain `string` (like capability) or an implementation based on `Trait` class.
15 |
16 | When trait is a simple string then just basic capabilities like expression operators are applied to it:
17 |
18 | - & and operator,
19 | - | or operator,
20 | - ! operator,
21 | - () grouping
22 | - trait characters should be alpha numeric and here is a list of disallowed characters that cannot be used in the trait name "'`:;,+-*/\\!~|&%$@^()={}[]<>? \t\b\n\r
23 | - spaces are ignored
24 |
25 | Here is an example of simple expression:
26 |
27 | ```text
28 | Web & !CPS | (OutputTypeLibrary & CSharp)
29 | ```
30 |
31 | Which would result in `true` for classic web app or C# class library projects.
32 |
33 | When trait object is an implementation of `Trait`, during expression matching we would run `Trait.EvaluateAsync` method where we would pass a `TraitToken` object to check and return `true` or `false`. `TraitToken` object is generated from a special token operator that can also be specified in the trait expression for a contract:
34 |
35 | - token is specified in the following format: `{key=value}` where we support following operators between key and a value: =,<,>,<=,>=. Key and value can be any kind of string constructed with allowed characters and a particular `Trait` implementation knows how to interpret them and apply each operator. Key can be a composite object in the form of `KeyName.Property` if we see the dot we see that the name of this `Trait` is `KeyName`.
36 |
37 | An example of such `Trait` object is `TargetFramework` trait:
38 |
39 | ```text
40 | Inplace & !CPS & {TargetFramework.Name=net} & {TargetFramework.Version<5.0} & CSharp
41 | ```
42 |
43 | When this expression is parsed, we see that there is a `TargetFramework` trait token and try to find a `Trait` object in the set of project traits with the name `TargetFramework`. If found we pass `TraitToken` object having key, operator and value and let this trait evaluate them and return `true` or `false`. Note: for simplicity when dealing with target frameworks we understand short names and full names.
44 |
45 | In the example above trait expression would result in true if project is targeting .NET Framework with version less than 5.0 and upgrade operation is running in-place and project is not SDK style.
46 |
47 | Note: If a contract does not have traits expression in the attribute, it is applicable to all scenarios.
48 |
49 | ## Existing traits
50 |
51 | So far we have providers for following kind of traits:
52 |
53 | - project capabilities coming from msbuild
54 | - project's OutputType property concatenated with its value: `OutputTypeLibrary`, `OutputTypeExe`, `OutputTypeWinExe` etc.
55 | - language: `CSharp`, `VB`
56 | - for web projects we ensure web trait: `Web`
57 | - upgrade kind: `Inplace`, `SideBySide`, `Incremental`
58 | - some properties that we collect during upgrade operation flow, like `NewProject` etc
59 | - target framework composite trait
60 | - for windows projects we add either `WPF` or `WinForms`
61 | - for Xamarin.iOS and Android projects, we have `Xamarin`. Additionally, there are also `IOS` and `Android` traits for the specific platforms.
62 | - for MAUI projects, we have `Maui`
63 | - when running in Visual Studio, we add `VS` trait
64 |
--------------------------------------------------------------------------------
/rules.ruleset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/samples/Sample.apimap.json:
--------------------------------------------------------------------------------
1 | // This is a sample API Map
2 | {
3 | // Remove a namespace:
4 | "SampleApi.RemovedNamespace": {
5 | "kind": "namespace",
6 | "state": "Removed"
7 | },
8 |
9 | // Remove multiple namespaces using a wildcard:
10 | "SampleApi.RemovedNamespaces.*": {
11 | "kind": "namespace",
12 | "state": "Removed"
13 | },
14 |
15 | // Replace a namespace:
16 | "SampleApi.OldNamespace": {
17 | "value" :"SampleApi.NewNamespace",
18 | "kind": "namespace",
19 | "state": "Replaced"
20 | },
21 |
22 | // Replace a namespace with multiple namespaces:
23 | "SampleApi.OldNamespaceThatHasBeenSplitIntoMultipleNamespaces": {
24 | "value": "SampleApi.NewNamespace1;SampleApi.NewNamespace2;SampleApi.NewNamespace3",
25 | "kind": "namespace",
26 | "state": "Replaced"
27 | },
28 |
29 | // Remove a type:
30 | "SampleApi.RemovedType": {
31 | "kind": "type",
32 | "state": "Removed"
33 | },
34 |
35 | // Replace a type:
36 | "SampleApi.OldTypeName": {
37 | "value" :"SampleApi.NewTypeName",
38 | "kind": "type",
39 | "state": "Replaced"
40 | },
41 |
42 | // Remove a method call:
43 | "SampleApi.ClassName.RemovedMethodName": {
44 | "kind": "method",
45 | "state": "Removed"
46 | },
47 |
48 | // Replace a method call:
49 | "SampleApi.ClassName.OldMethodNameAsync": {
50 | "value": "SampleApi.ClassName.NewMethodNameAsync",
51 | "kind": "method",
52 | "state": "Replaced",
53 | "isStatic": true,
54 | "isAsync": true
55 | },
56 |
57 | // Add a comment to old method calls requring manual upgrading:
58 | "SampleApi.ClassName.MethodThatNeedsManualUpgrade": {
59 | "kind": "method",
60 | "state": "Removed",
61 | "needsManualUpgrade": true,
62 | "documentationUrl": "https://sampleapi.net/docs/upgrading",
63 | "needsTodoInComment": true
64 | },
65 |
66 | // Remove a property:
67 | "SampleApi.ClassName.RemovedProperty": {
68 | "kind": "property",
69 | "state": "Removed"
70 | },
71 |
72 | // Replace a property:
73 | "SampleApi.ClassName.OldPropertyName": {
74 | "value" :"SampleApi.ClassName.NewPropertyNameName",
75 | "kind": "property",
76 | "state": "Replaced"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/samples/Sample.packagemap.json:
--------------------------------------------------------------------------------
1 | // This is a sample Package Map
2 | {
3 | // A PackageMap consists of an array of "packages"
4 | "packages": [
5 |
6 | // Each mapping can consist of just a package name...
7 | {
8 | "name": "Xamarin.Forms"
9 | },
10 |
11 | // (which may include wildcards)
12 | {
13 | "name": "Xamarin.Android.Support.*"
14 | },
15 |
16 | // ... or a package name and the new package to use for various target frameworks
17 | {
18 | "name": "Xamarin.CommunityToolkit",
19 | "frameworks": {
20 | ".NETCoreApp,Version=v6.0": [
21 | {
22 | "name": "CommunityToolkit.Maui",
23 | "version": "2.*"
24 | }
25 | ],
26 | ".NETCoreApp,Version=v7.0": [
27 | {
28 | "name": "CommunityToolkit.Maui",
29 | "version": "5.*"
30 | }
31 | ],
32 | ".NETCoreApp,Version=v8.0": [
33 | {
34 | "name": "CommunityToolkit.Maui",
35 | "prerelease": true
36 | }
37 | ]
38 | }
39 | }
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/src/UpgradeAssistant.Mappings/UpgradeAssistant.Mappings.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 | true
10 | Microsoft.UpgradeAssistant.Mappings
11 | 1.0.0
12 | Microsoft
13 | A set of package and API mappings for the dotnet UpgradeAssistant.
14 | © Microsoft Corporation. All rights reserved.
15 | https://go.microsoft.com/fwlink/?LinkID=2202641
16 | https://github.com/dotnet/upgrade-assistant
17 | MIT
18 | README.md
19 | false
20 |
21 |
22 |
23 |
24 | PreserveNewest
25 | content/mappings
26 | True
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/UpgradeAssistant.Mappings/mappings/Esri/Esri.ArcGISRuntime.Xamarin.Forms/esri.arcgisruntime.xamarin.forms.apimap.json:
--------------------------------------------------------------------------------
1 | {
2 | "Esri.ArcGISRuntime.Xamarin.Forms": {
3 | "value": "Esri.ArcGISRuntime.Maui",
4 | "kind": "namespace",
5 | "state": "Replaced"
6 | },
7 | "Esri.ArcGISRuntime.Toolkit.Xamarin.Forms": {
8 | "value": "Esri.ArcGISRuntime.Toolkit.Maui",
9 | "kind": "namespace",
10 | "state": "Replaced"
11 | },
12 | "Esri.ArcGISRuntime.Toolkit.UI.Controls": {
13 | "kind": "namespace",
14 | "state": "Removed"
15 | },
16 | "Esri.ArcGISRuntime.ARToolkit.Forms": {
17 | "kind": "namespace",
18 | "state": "NotImplemented"
19 | },
20 | "Esri.ArcGISRuntime.Toolkit.Xamarin.Forms.LayerLegend": {
21 | "kind": "type",
22 | "state": "Removed",
23 | "needsManualUpgrade": true,
24 | "needsTodoInComment": true
25 | },
26 | "Esri.ArcGISRuntime.Toolkit.Xamarin.Forms.TimeSlider": {
27 | "kind": "type",
28 | "state": "NotImplemented",
29 | "needsManualUpgrade": true,
30 | "needsTodoInComment": true
31 | },
32 | "Esri.ArcGISRuntime.Toolkit.Xamarin.Forms.CompassRenderer": {
33 | "kind": "type",
34 | "state": "Removed",
35 | "needsManualUpgrade": true,
36 | "needsTodoInComment": true
37 | },
38 | "Esri.ArcGISRuntime.Toolkit.Xamarin.Forms.ScaleLineRenderer": {
39 | "kind": "type",
40 | "state": "Removed",
41 | "needsManualUpgrade": true,
42 | "needsTodoInComment": true
43 | },
44 | "Esri.ArcGISRuntime.Toolkit.Xamarin.Forms.TimeSliderRenderer": {
45 | "kind": "type",
46 | "state": "Removed",
47 | "needsManualUpgrade": true,
48 | "needsTodoInComment": true
49 | },
50 | "Esri.ArcGISRuntime.Toolkit.Xamarin.Forms.LayerLegendRenderer": {
51 | "kind": "type",
52 | "state": "Removed",
53 | "needsManualUpgrade": true,
54 | "needsTodoInComment": true
55 | },
56 | "Esri.ArcGISRuntime.Toolkit.Xamarin.Forms.PopupViewerRenderer": {
57 | "kind": "type",
58 | "state": "Removed",
59 | "needsManualUpgrade": true,
60 | "needsTodoInComment": true
61 | },
62 | "Esri.ArcGISRuntime.Toolkit.Xamarin.Forms.SymbolDisplayRenderer": {
63 | "kind": "type",
64 | "state": "Removed",
65 | "needsManualUpgrade": true,
66 | "needsTodoInComment": true
67 | }
68 | }
--------------------------------------------------------------------------------
/src/UpgradeAssistant.Mappings/mappings/Esri/Esri.ArcGISRuntime.Xamarin.Forms/esri.arcgisruntime.xamarin.forms.packagemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": [
3 | {
4 | "name": "Esri.ArcGISRuntime.Xamarin.Forms",
5 | "frameworks": {
6 | ".NETCoreApp,Version=v6.0": [
7 | {
8 | "name": "Esri.ArcGISRuntime.Maui",
9 | "version": "200.1"
10 | }
11 | ],
12 | ".NETCoreApp,Version=v7.0": [
13 | {
14 | "name": "Esri.ArcGISRuntime.Maui",
15 | "version": "200.2"
16 | }
17 | ],
18 | ".NETCoreApp,Version=v8.0": [
19 | {
20 | "name": "Esri.ArcGISRuntime.Maui",
21 | "version": "200.*"
22 | }
23 | ]
24 | }
25 | },
26 | {
27 | "name": "Esri.ArcGISRuntime.Toolkit.Xamarin.Forms",
28 | "frameworks": {
29 | ".NETCoreApp,Version=v6.0": [
30 | {
31 | "name": "Esri.ArcGISRuntime.Toolkit.Maui",
32 | "version": "200.1"
33 | }
34 | ],
35 | ".NETCoreApp,Version=v7.0": [
36 | {
37 | "name": "Esri.ArcGISRuntime.Toolkit.Maui",
38 | "version": "200.2"
39 | }
40 | ],
41 | ".NETCoreApp,Version=v8.0": [
42 | {
43 | "name": "Esri.ArcGISRuntime.Toolkit.Maui",
44 | "version": "200.*"
45 | }
46 | ]
47 | }
48 | },
49 | {
50 | "name": "Esri.ArcGISRuntime.Xamarin.Android",
51 | "frameworks": {
52 | ".NETCoreApp,Version=v6.0": [
53 | {
54 | "name": "Esri.ArcGISRuntime.Android",
55 | "version": "200.1"
56 | }
57 | ],
58 | ".NETCoreApp,Version=v7.0": [
59 | {
60 | "name": "Esri.ArcGISRuntime.Android",
61 | "version": "200.2"
62 | }
63 | ],
64 | ".NETCoreApp,Version=v8.0": [
65 | {
66 | "name": "Esri.ArcGISRuntime.Android",
67 | "version": "200.*"
68 | }
69 | ]
70 | }
71 | },
72 | {
73 | "name": "Esri.ArcGISRuntime.Xamarin.iOS",
74 | "frameworks": {
75 | ".NETCoreApp,Version=v6.0": [
76 | {
77 | "name": "Esri.ArcGISRuntime.iOS",
78 | "version": "200.1"
79 | }
80 | ],
81 | ".NETCoreApp,Version=v7.0": [
82 | {
83 | "name": "Esri.ArcGISRuntime.iOS",
84 | "version": "200.2"
85 | }
86 | ],
87 | ".NETCoreApp,Version=v8.0": [
88 | {
89 | "name": "Esri.ArcGISRuntime.iOS",
90 | "version": "200.*"
91 | }
92 | ]
93 | }
94 | },
95 | {
96 | "name": "Esri.ArcGISRuntime.UWP",
97 | "frameworks": {
98 | ".NETCoreApp,Version=v6.0": [
99 | {
100 | "name": "Esri.ArcGISRuntime.WinUI",
101 | "version": "200.1"
102 | }
103 | ],
104 | ".NETCoreApp,Version=v7.0": [
105 | {
106 | "name": "Esri.ArcGISRuntime.WinUI",
107 | "version": "200.2"
108 | }
109 | ],
110 | ".NETCoreApp,Version=v8.0": [
111 | {
112 | "name": "Esri.ArcGISRuntime.WinUI",
113 | "version": "200.*"
114 | }
115 | ]
116 | }
117 | },
118 | {
119 | "name": "Esri.ArcGISRuntime.runtimes.win10",
120 | "frameworks": {
121 | ".NETCoreApp,Version=v8.0": [
122 | {
123 | "name": "Esri.ArcGISRuntime.runtimes.win",
124 | "version": "200.*"
125 | }
126 | ]
127 | }
128 | }
129 | ]
130 | }
--------------------------------------------------------------------------------
/src/UpgradeAssistant.Mappings/mappings/Esri/Esri.ArcGISRuntime.Xamarin.Forms/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "traits": "(Xamarin | Maui)",
3 | "order": 1000
4 | }
5 |
--------------------------------------------------------------------------------
/src/UpgradeAssistant.Mappings/mappings/Microsoft/CommunityToolkit/XamarinCommunityToolkit.apimap.json:
--------------------------------------------------------------------------------
1 | {
2 | "Xamarin.CommunityToolkit.Behaviors": {
3 | "value": "CommunityToolkit.Maui.Behaviors;CommunityToolkit.Maui.Animations",
4 | "kind": "namespace",
5 | "state": "Replaced"
6 | },
7 | "Xamarin.CommunityToolkit.Behaviors.Internals": {
8 | "value": "CommunityToolkit.Maui.Behaviors",
9 | "kind": "namespace",
10 | "state": "Replaced"
11 | },
12 | "Xamarin.CommunityToolkit.Converters": {
13 | "value": "CommunityToolkit.Maui.Converters",
14 | "kind": "namespace",
15 | "state": "Replaced"
16 | },
17 | "Xamarin.CommunityToolkit.Core": {
18 | "value": "CommunityToolkit.Maui.Core",
19 | "kind": "namespace",
20 | "state": "Replaced"
21 | },
22 | "Xamarin.CommunityToolkit.Effects": {
23 | "value": "CommunityToolkit.Maui.Core",
24 | "kind": "namespace",
25 | "state": "Replaced"
26 | },
27 | "Xamarin.CommunityToolkit.Extensions": {
28 | "value": "CommunityToolkit.Maui.Extensions",
29 | "kind": "namespace",
30 | "state": "Replaced"
31 | },
32 | "Xamarin.CommunityToolkit.Extensions.Internals": {
33 | "value": "CommunityToolkit.Maui.Extensions",
34 | "kind": "namespace",
35 | "state": "Replaced"
36 | },
37 | "Xamarin.CommunityToolkit.Helpers": {
38 | "value": "CommunityToolkit.Maui.Converters",
39 | "kind": "namespace",
40 | "state": "Replaced"
41 | },
42 | "Xamarin.CommunityToolkit.UI.Views": {
43 | "value": "CommunityToolkit.Maui.Views;CommunityToolkit.Maui.Layouts;CommunityToolkit.Maui.Core;CommunityToolkit.Maui;CommunityToolkit.Maui.ImageSources;CommunityToolkit.Maui.Converters",
44 | "kind": "namespace",
45 | "state": "Replaced"
46 | },
47 | "Xamarin.CommunityToolkit.Markup": {
48 | "value": "CommunityToolkit.Maui.Markup",
49 | "kind": "namespace",
50 | "state": "Replaced"
51 | },
52 | "Xamarin.CommunityToolkit.Markup.LeftToRight": {
53 | "value": "CommunityToolkit.Maui.Markup",
54 | "kind": "namespace",
55 | "state": "Replaced"
56 | },
57 | "Xamarin.CommunityToolkit.Markup.RightToLeft": {
58 | "value": "CommunityToolkit.Maui.Markup",
59 | "kind": "namespace",
60 | "state": "Replaced"
61 | },
62 | "http://xamarin.com/schemas/2020/toolkit": {
63 | "value": "http://schemas.microsoft.com/dotnet/2022/maui/toolkit",
64 | "kind": "xmlnamespace",
65 | "state": "Replaced",
66 | "properties": {
67 | "xmlnsdefinitions": [
68 | "Xamarin.CommunityToolkit.Behaviors",
69 | "Xamarin.CommunityToolkit.Converters",
70 | "Xamarin.CommunityToolkit.Effects",
71 | "Xamarin.CommunityToolkit.Extensions",
72 | "Xamarin.CommunityToolkit.UI.Views"
73 | ]
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/src/UpgradeAssistant.Mappings/mappings/Microsoft/CommunityToolkit/XamarinCommunityToolkit.packagemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": [
3 | {
4 | "name": "Xamarin.CommunityToolkit",
5 | "frameworks": {
6 | ".NETCoreApp,Version=v6.0": [
7 | {
8 | "name": "CommunityToolkit.Maui",
9 | "version": "2.*"
10 | }
11 | ],
12 | ".NETCoreApp,Version=v7.0": [
13 | {
14 | "name": "CommunityToolkit.Maui",
15 | "version": "5.*"
16 | }
17 | ],
18 | ".NETCoreApp,Version=v8.0": [
19 | {
20 | "name": "CommunityToolkit.Maui",
21 | "version": "5.*"
22 | }
23 | ],
24 | ".NETCoreApp,Version=v9.0": [
25 | {
26 | "name": "CommunityToolkit.Maui",
27 | "prerelease": true
28 | }
29 | ]
30 | }
31 | }
32 | ]
33 | }
--------------------------------------------------------------------------------
/src/UpgradeAssistant.Mappings/mappings/Microsoft/CommunityToolkit/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "traits": "(Xamarin | Maui)",
3 | "order": 1000
4 | }
5 |
--------------------------------------------------------------------------------
/src/UpgradeAssistant.Mappings/mappings/jsuarezruiz/AlohaKit.Animations/alohakit.animations.apimap.json:
--------------------------------------------------------------------------------
1 | {
2 | "Xamanimation": {
3 | "value": "AlohaKit.Animations",
4 | "kind": "namespace",
5 | "state": "Replaced"
6 | }
7 | }
--------------------------------------------------------------------------------
/src/UpgradeAssistant.Mappings/mappings/jsuarezruiz/AlohaKit.Animations/alohakit.animations.packagemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": [
3 | {
4 | "name": "Xamanimation",
5 | "frameworks": {
6 | ".NETCoreApp,Version=v8.0": [
7 | {
8 | "name": "AlohaKit.Animations",
9 | "version": "1.*"
10 | }
11 | ]
12 | }
13 | }
14 | ]
15 | }
--------------------------------------------------------------------------------
/src/UpgradeAssistant.Mappings/mappings/jsuarezruiz/AlohaKit.Animations/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "traits": "(Xamarin | Maui)",
3 | "order": 1000
4 | }
5 |
--------------------------------------------------------------------------------
/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 | "documentExposedElements": false,
6 | "documentInternalElements": false,
7 | "companyName": ".NET Foundation",
8 | "copyrightText": "Licensed to the {companyName} under one or more agreements.\nThe {companyName} licenses this file to you under the {licenseName} license.",
9 | "xmlHeader": false,
10 | "variables": {
11 | "licenseName": "MIT",
12 | "licenseFile": "LICENSE"
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/UpgradeAssistant.Mappings.Tests/ApiMapEntry.cs:
--------------------------------------------------------------------------------
1 | // Licensed to the .NET Foundation under one or more agreements.
2 | // The .NET Foundation licenses this file to you under the MIT license.
3 |
4 | namespace Microsoft.UpgradeAssistant.Mappings.Tests;
5 |
6 | internal class ApiMapEntry
7 | {
8 | public string? Value { get; set; }
9 |
10 | public string Kind { get; set; } = Kinds.Type;
11 |
12 | public string State { get; set; } = States.Replaced;
13 |
14 | public bool NeedsManualUpgrade { get; set; }
15 |
16 | public string? DocumentationUrl { get; set; }
17 |
18 | public bool NeedsTodoInComment { get; set; } = true;
19 |
20 | public bool IsAsync { get; set; }
21 |
22 | ///
23 | /// Gets or sets whether the API represents an extension method.
24 | ///
25 | ///
26 | /// Although extension methods can only be static, this setting is disconnected from .
27 | ///
28 | public bool IsExtension { get; set; }
29 |
30 | public bool IsStatic { get; set; }
31 |
32 | public string? MessageId { get; set; }
33 |
34 | public string[]? MessageParams { get; set; }
35 |
36 | public static class Kinds
37 | {
38 | public const string Property = nameof(Property);
39 | public const string Method = nameof(Method);
40 | public const string Namespace = nameof(Namespace);
41 | public const string Type = nameof(Type);
42 | }
43 |
44 | public static class States
45 | {
46 | public const string NotImplemented = nameof(NotImplemented);
47 | public const string Removed = nameof(Removed);
48 | public const string Replaced = nameof(Replaced);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/UpgradeAssistant.Mappings.Tests/ApiMapValidationTests.cs:
--------------------------------------------------------------------------------
1 | // Licensed to the .NET Foundation under one or more agreements.
2 | // The .NET Foundation licenses this file to you under the MIT license.
3 |
4 | using System.Text.Json;
5 |
6 | using Microsoft.VisualStudio.TestTools.UnitTesting;
7 |
8 | namespace Microsoft.UpgradeAssistant.Mappings.Tests;
9 |
10 | public partial class ValidationTests
11 | {
12 | private static readonly string[] Kinds = new string[] { "property", "method", "namespace", "type", "xmlnamespace" };
13 | private static readonly string[] States = new string[] { "NotImplemented", "Removed", "Replaced" };
14 |
15 | [TestMethod]
16 | public void ValidateApiMaps()
17 | {
18 | var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
19 | {
20 | ReadCommentHandling = JsonCommentHandling.Skip
21 | };
22 |
23 | var jsonFiles = Directory.GetFiles(TestHelper.MappingsDir, "*.json", SearchOption.AllDirectories);
24 |
25 | foreach (var path in jsonFiles)
26 | {
27 | var fileName = Path.GetFileName(path);
28 |
29 | if (fileName.Equals("apimap.json", StringComparison.OrdinalIgnoreCase) || fileName.EndsWith(".apimap.json", StringComparison.OrdinalIgnoreCase))
30 | {
31 | AssertApiMap(options, path);
32 | }
33 | }
34 | }
35 |
36 | private static void AssertApiMap(JsonSerializerOptions options, string fullPath)
37 | {
38 | var relativePath = TestHelper.GetRelativePath(fullPath);
39 | Dictionary mappings;
40 |
41 | try
42 | {
43 | var json = File.ReadAllText(fullPath);
44 | mappings = JsonSerializer.Deserialize>(json, options)
45 | ?? new Dictionary();
46 | }
47 | catch (Exception ex)
48 | {
49 | Assert.Fail($"Failed to deserialize {relativePath}: {ex}");
50 | return;
51 | }
52 |
53 | foreach (var mapping in mappings)
54 | {
55 | var entry = mapping.Value;
56 |
57 | Assert.IsNotNull(entry.Kind, $"`{relativePath}' - [\"{mapping.Key}\"][\"kind\"] cannot be null.");
58 | Assert.IsTrue(Kinds.Contains(entry.Kind.ToLowerInvariant()), $"`{relativePath}' - [\"{mapping.Key}\"][\"kind\"] must be one of: {string.Join(", ", Kinds)}");
59 |
60 | Assert.IsNotNull(entry.State, $"`{relativePath}' - [\"{mapping.Key}\"][\"state\"] cannot be null.");
61 | Assert.IsTrue(States.Contains(entry.State), $"`{relativePath}' - [\"{mapping.Key}\"][\"state\"] must be one of: {string.Join(", ", States)}");
62 |
63 | if (entry.Value != null)
64 | {
65 | Assert.IsFalse(string.IsNullOrEmpty(entry.Value), $"`{relativePath}' - [\"{mapping.Key}\"][\"value\"] cannot be empty.");
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/tests/UpgradeAssistant.Mappings.Tests/MetadataValidationTests.cs:
--------------------------------------------------------------------------------
1 | // Licensed to the .NET Foundation under one or more agreements.
2 | // The .NET Foundation licenses this file to you under the MIT license.
3 |
4 | using System.Text.Json;
5 |
6 | using Microsoft.VisualStudio.TestTools.UnitTesting;
7 |
8 | namespace Microsoft.UpgradeAssistant.Mappings.Tests;
9 |
10 | public partial class ValidationTests
11 | {
12 | [TestMethod]
13 | public void ValidateMetadataFiles()
14 | {
15 | var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
16 | {
17 | ReadCommentHandling = JsonCommentHandling.Skip
18 | };
19 |
20 | var jsonFiles = Directory.GetFiles(TestHelper.MappingsDir, "metadata.json", SearchOption.AllDirectories);
21 |
22 | foreach (var path in jsonFiles)
23 | {
24 | AssertMetadataFile(options, path);
25 | }
26 | }
27 |
28 | private static void AssertMetadataFile(JsonSerializerOptions options, string fullPath)
29 | {
30 | var relativePath = TestHelper.GetRelativePath(fullPath);
31 | var json = File.ReadAllText(fullPath);
32 | JsonDocument doc;
33 |
34 | try
35 | {
36 | doc = JsonDocument.Parse(json, new JsonDocumentOptions
37 | {
38 | AllowTrailingCommas = options.AllowTrailingCommas,
39 | CommentHandling = options.ReadCommentHandling,
40 | MaxDepth = options.MaxDepth
41 | });
42 | }
43 | catch (Exception ex)
44 | {
45 | Assert.Fail($"Failed to parse {relativePath}: {ex.Message}");
46 | return;
47 | }
48 |
49 | var root = doc.RootElement;
50 |
51 | foreach (var property in root.EnumerateObject())
52 | {
53 | if (property.NameEquals("traits"))
54 | {
55 | AssertPropertyType(relativePath, string.Empty, property, JsonValueKind.String);
56 | var traits = property.Value.GetString();
57 |
58 | if (traits != null)
59 | {
60 | try
61 | {
62 | TraitsExpressionParser.Validate(traits);
63 | }
64 | catch (Exception ex)
65 | {
66 | Assert.Fail(ex.Message);
67 | }
68 | }
69 | }
70 | else if (property.NameEquals("order"))
71 | {
72 | AssertPropertyType(relativePath, string.Empty, property, JsonValueKind.Number);
73 | Assert.IsTrue(property.Value.TryGetInt32(out int order), $"Failed to parse \"{property.Name}\" property in `{relativePath}': {property}");
74 | Assert.IsTrue(order >= 0, $"`{relativePath}' - [\"{property.Name}\"] must be greater than or equal to 0.");
75 | }
76 | else
77 | {
78 | AssertUnknownProperty(relativePath, string.Empty, property);
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/tests/UpgradeAssistant.Mappings.Tests/PackageMapConfig.cs:
--------------------------------------------------------------------------------
1 | // Licensed to the .NET Foundation under one or more agreements.
2 | // The .NET Foundation licenses this file to you under the MIT license.
3 |
4 | namespace Microsoft.UpgradeAssistant.Mappings.Tests;
5 |
6 | internal class PackageMapConfig
7 | {
8 | public PackageMapEntry? Defaults { get; set; }
9 |
10 | public IList? Packages { get; set; }
11 | }
12 |
--------------------------------------------------------------------------------
/tests/UpgradeAssistant.Mappings.Tests/PackageMapEntry.cs:
--------------------------------------------------------------------------------
1 | // Licensed to the .NET Foundation under one or more agreements.
2 | // The .NET Foundation licenses this file to you under the MIT license.
3 |
4 | namespace Microsoft.UpgradeAssistant.Mappings.Tests;
5 |
6 | internal class PackageMapEntry
7 | {
8 | public string? Name { get; set; }
9 |
10 | public IReadOnlyDictionary>? Frameworks { get; set; }
11 | }
12 |
--------------------------------------------------------------------------------
/tests/UpgradeAssistant.Mappings.Tests/PackageMapFrameworkEntry.cs:
--------------------------------------------------------------------------------
1 | // Licensed to the .NET Foundation under one or more agreements.
2 | // The .NET Foundation licenses this file to you under the MIT license.
3 |
4 | namespace Microsoft.UpgradeAssistant.Mappings.Tests;
5 |
6 | internal class PackageMapFrameworkEntry
7 | {
8 | public string? Name { get; set; }
9 |
10 | public string? Version { get; set; }
11 |
12 | public bool Prerelease { get; set; }
13 | }
14 |
--------------------------------------------------------------------------------
/tests/UpgradeAssistant.Mappings.Tests/PackageMapValidationTests.cs:
--------------------------------------------------------------------------------
1 | // Licensed to the .NET Foundation under one or more agreements.
2 | // The .NET Foundation licenses this file to you under the MIT license.
3 |
4 | using System.Text.Json;
5 |
6 | using Microsoft.VisualStudio.TestTools.UnitTesting;
7 |
8 | namespace Microsoft.UpgradeAssistant.Mappings.Tests;
9 |
10 | public partial class ValidationTests
11 | {
12 | [TestMethod]
13 | public void ValidatePackageMaps()
14 | {
15 | var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
16 | {
17 | ReadCommentHandling = JsonCommentHandling.Skip
18 | };
19 |
20 | var jsonFiles = Directory.GetFiles(TestHelper.MappingsDir, "*.json", SearchOption.AllDirectories);
21 |
22 | foreach (var path in jsonFiles)
23 | {
24 | var fileName = Path.GetFileName(path);
25 |
26 | if (fileName.Equals("packagemap.json", StringComparison.OrdinalIgnoreCase) || fileName.EndsWith(".packagemap.json", StringComparison.OrdinalIgnoreCase))
27 | {
28 | AssertPackageMap(options, path);
29 | }
30 | }
31 | }
32 |
33 | private static void AssertPackageMapEntry(string relativePath, string packagePath, PackageMapEntry package)
34 | {
35 | if (package.Frameworks is not null)
36 | {
37 | foreach (var framework in package.Frameworks)
38 | {
39 | var count = framework.Value.Count;
40 | int index = 0;
41 |
42 | foreach (var frameworkEntry in framework.Value)
43 | {
44 | var frameworkEntryPath = $"{packagePath}[\"frameworks\"][{index++}]";
45 |
46 | if (count > 1 && string.IsNullOrEmpty(frameworkEntry.Name))
47 | {
48 | // if there are more than one packages to be added instead of old, their names should be specified.
49 | Assert.Fail($"`{relativePath}' - {frameworkEntryPath}[\"name\"] cannot be empty if there are more than 1 packages to be added.");
50 | break;
51 | }
52 |
53 | if (frameworkEntry.Version is not null && frameworkEntry.Version.Contains('*') && !frameworkEntry.Version.EndsWith('*'))
54 | {
55 | // if version has * it should be at the end
56 | Assert.Fail($"`{relativePath}' - {frameworkEntryPath}[\"version\"] is invalid. Wildcards may only be used at the end of the version string.");
57 | break;
58 | }
59 | }
60 | }
61 | }
62 | }
63 |
64 | private static void AssertPackageMap(JsonSerializerOptions options, string fullPath)
65 | {
66 | var relativePath = TestHelper.GetRelativePath(fullPath);
67 | PackageMapConfig config;
68 |
69 | try
70 | {
71 | var json = File.ReadAllText(fullPath);
72 | config = JsonSerializer.Deserialize(json, options)
73 | ?? new PackageMapConfig();
74 | }
75 | catch (Exception ex)
76 | {
77 | Assert.Fail($"Failed to deserialize {TestHelper.GetRelativePath(fullPath)}: {ex}");
78 | return;
79 | }
80 |
81 | if (config.Defaults != null)
82 | {
83 | AssertPackageMapEntry(relativePath, "[\"defaults\"]", config.Defaults);
84 | }
85 |
86 | if (config.Packages != null)
87 | {
88 | int index = 0;
89 |
90 | foreach (var package in config.Packages)
91 | {
92 | var packagePath = $"[\"packages\"][\"{index++}\"]";
93 |
94 | AssertPackageMapEntry(relativePath, packagePath, package);
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/tests/UpgradeAssistant.Mappings.Tests/SchemaValidationTests.cs:
--------------------------------------------------------------------------------
1 | // Licensed to the .NET Foundation under one or more agreements.
2 | // The .NET Foundation licenses this file to you under the MIT license.
3 |
4 | using System.Text.Json;
5 |
6 | using Microsoft.VisualStudio.TestTools.UnitTesting;
7 |
8 | namespace Microsoft.UpgradeAssistant.Mappings.Tests;
9 |
10 | [TestClass]
11 | public partial class ValidationTests
12 | {
13 | [TestMethod]
14 | public void ValidateSchemas()
15 | {
16 | var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
17 | {
18 | ReadCommentHandling = JsonCommentHandling.Skip
19 | };
20 |
21 | var jsonFiles = Directory.GetFiles(TestHelper.MappingsDir, "*.json", SearchOption.AllDirectories);
22 |
23 | foreach (var path in jsonFiles)
24 | {
25 | var fileName = Path.GetFileName(path);
26 |
27 | if (fileName.Equals("packagemap.json", StringComparison.OrdinalIgnoreCase) || fileName.EndsWith(".packagemap.json", StringComparison.OrdinalIgnoreCase))
28 | {
29 | AssertPackageMapSchema(options, path);
30 | }
31 | else if (fileName.Equals("apimap.json", StringComparison.OrdinalIgnoreCase) || fileName.EndsWith(".apimap.json", StringComparison.OrdinalIgnoreCase))
32 | {
33 | AssertApiMapSchema(options, path);
34 | }
35 | else if (fileName.Equals("metadata.json", StringComparison.OrdinalIgnoreCase))
36 | {
37 | AssertConfigSchema(options, path);
38 | }
39 | else
40 | {
41 | //Assert.Fail($"Unknown file type: {fileName}");
42 | }
43 | }
44 | }
45 |
46 | private static void AssertElementType(string relativePath, string elementPath, JsonElement element, JsonValueKind expectedKind)
47 | {
48 | Assert.AreEqual(expectedKind, element.ValueKind, $"The {elementPath} element in `{relativePath}' is expected to be a {expectedKind.ToString().ToLowerInvariant()}.");
49 | }
50 |
51 | private static string GetPropertyPath(string elementPath, JsonProperty property)
52 | {
53 | return $"{elementPath}[\"{property.Name}\"]";
54 | }
55 |
56 | private static void AssertPropertyType(string relativePath, string elementPath, JsonProperty property, JsonValueKind expectedKind)
57 | {
58 | var propertyPath = GetPropertyPath(elementPath, property);
59 |
60 | Assert.AreEqual(expectedKind, property.Value.ValueKind, $"The {propertyPath} property in `{relativePath}' is expected to be a {expectedKind.ToString().ToLowerInvariant()}.");
61 | }
62 |
63 | private static void AssertPropertyTypeIsBoolean(string relativePath, string elementPath, JsonProperty property)
64 | {
65 | var propertyPath = GetPropertyPath(elementPath, property);
66 |
67 | Assert.IsTrue(property.Value.ValueKind == JsonValueKind.True || property.Value.ValueKind == JsonValueKind.False, $"The {propertyPath} property in `{relativePath}' is expected to be a boolean.");
68 | }
69 |
70 | private static void AssertUnknownProperty(string relativePath, string elementPath, JsonProperty property)
71 | {
72 | Assert.Fail($"Unknown property in `{relativePath}': {GetPropertyPath(elementPath, property)}");
73 | }
74 |
75 | private static void AssertPackageMapEntryFramework(string relativePath, string frameworkPath, JsonElement framework)
76 | {
77 | int index = 0;
78 |
79 | foreach (var element in framework.EnumerateArray())
80 | {
81 | var elementPath = $"{frameworkPath}[{index++}]";
82 |
83 | AssertElementType(relativePath, elementPath, element, JsonValueKind.Object);
84 | foreach (var property in element.EnumerateObject())
85 | {
86 | if (property.NameEquals("name"))
87 | {
88 | AssertPropertyType(relativePath, elementPath, property, JsonValueKind.String);
89 | }
90 | else if (property.NameEquals("version"))
91 | {
92 | AssertPropertyType(relativePath, elementPath, property, JsonValueKind.String);
93 | }
94 | else if (property.NameEquals("prerelease"))
95 | {
96 | AssertPropertyTypeIsBoolean(relativePath, elementPath, property);
97 | }
98 | else
99 | {
100 | AssertUnknownProperty(relativePath, elementPath, property);
101 | }
102 | }
103 | }
104 | }
105 |
106 | private static void AssertPackageMapFrameworks(string relativePath, string elementPath, JsonElement frameworks)
107 | {
108 | foreach (var framework in frameworks.EnumerateObject())
109 | {
110 | var frameworkPath = GetPropertyPath(elementPath, framework);
111 | AssertPropertyType(relativePath, frameworkPath, framework, JsonValueKind.Array);
112 | AssertPackageMapEntryFramework(relativePath, frameworkPath, framework.Value);
113 | }
114 | }
115 |
116 | private static void AssertPackageMapEntry(string relativePath, string elementPath, JsonElement defaults)
117 | {
118 | foreach (var property in defaults.EnumerateObject())
119 | {
120 | if (property.NameEquals("name"))
121 | {
122 | AssertPropertyType(relativePath, elementPath, property, JsonValueKind.String);
123 | }
124 | else if (property.NameEquals("frameworks"))
125 | {
126 | AssertPropertyType(relativePath, elementPath, property, JsonValueKind.Object);
127 | AssertPackageMapFrameworks(relativePath, GetPropertyPath(elementPath, property), property.Value);
128 | }
129 | else
130 | {
131 | AssertUnknownProperty(relativePath, elementPath, property);
132 | }
133 | }
134 | }
135 |
136 | private static void AssertPackageMapPackages(string relativePath, string packagesPath, JsonElement packages)
137 | {
138 | int index = 0;
139 |
140 | foreach (var element in packages.EnumerateArray())
141 | {
142 | var elementPath = $"{packagesPath}[{index++}]";
143 | AssertElementType(relativePath, elementPath, element, JsonValueKind.Object);
144 | AssertPackageMapEntry(relativePath, elementPath, element);
145 | }
146 | }
147 |
148 | private static void AssertPackageMapSchema(JsonSerializerOptions options, string fullPath)
149 | {
150 | var relativePath = TestHelper.GetRelativePath(fullPath);
151 | var json = File.ReadAllText(fullPath);
152 | JsonDocument doc;
153 |
154 | try
155 | {
156 | doc = JsonDocument.Parse(json, new JsonDocumentOptions
157 | {
158 | AllowTrailingCommas = options.AllowTrailingCommas,
159 | CommentHandling = options.ReadCommentHandling,
160 | MaxDepth = options.MaxDepth
161 | });
162 | }
163 | catch (Exception ex)
164 | {
165 | Assert.Fail($"Failed to parse {relativePath}: {ex.Message}");
166 | return;
167 | }
168 |
169 | var root = doc.RootElement;
170 |
171 | foreach (var property in root.EnumerateObject())
172 | {
173 | if (property.NameEquals("defaults"))
174 | {
175 | AssertPropertyType(relativePath, string.Empty, property, JsonValueKind.Object);
176 | AssertPackageMapEntry(relativePath, GetPropertyPath(string.Empty, property), property.Value);
177 | }
178 | else if (property.NameEquals("packages"))
179 | {
180 | AssertPropertyType(relativePath, string.Empty, property, JsonValueKind.Array);
181 | AssertPackageMapPackages(relativePath, GetPropertyPath(string.Empty, property), property.Value);
182 | }
183 | else
184 | {
185 | AssertUnknownProperty(relativePath, string.Empty, property);
186 | }
187 | }
188 | }
189 |
190 | private static void AssertApiMapEntry(string relativePath, string elementPath, JsonElement entry)
191 | {
192 | foreach (var property in entry.EnumerateObject())
193 | {
194 | if (property.NameEquals("value"))
195 | {
196 | AssertPropertyType(relativePath, elementPath, property, JsonValueKind.String);
197 | }
198 | else if (property.NameEquals("kind"))
199 | {
200 | AssertPropertyType(relativePath, elementPath, property, JsonValueKind.String);
201 | }
202 | else if (property.NameEquals("state"))
203 | {
204 | AssertPropertyType(relativePath, elementPath, property, JsonValueKind.String);
205 | }
206 | else if (property.NameEquals("isAsync"))
207 | {
208 | AssertPropertyTypeIsBoolean(relativePath, elementPath, property);
209 | }
210 | else if (property.NameEquals("isExtension"))
211 | {
212 | AssertPropertyTypeIsBoolean(relativePath, elementPath, property);
213 | }
214 | else if (property.NameEquals("isStatic"))
215 | {
216 | AssertPropertyTypeIsBoolean(relativePath, elementPath, property);
217 | }
218 | else if (property.NameEquals("messageId"))
219 | {
220 | AssertPropertyType(relativePath, elementPath, property, JsonValueKind.String);
221 | }
222 | else if (property.NameEquals("messageParams"))
223 | {
224 | AssertPropertyType(relativePath, elementPath, property, JsonValueKind.Array);
225 |
226 | var propertyPath = GetPropertyPath(elementPath, property);
227 | int index = 0;
228 |
229 | foreach (var paramElement in property.Value.EnumerateArray())
230 | {
231 | var paramPath = $"{propertyPath}[{index++}]";
232 | AssertElementType(relativePath, paramPath, paramElement, JsonValueKind.String);
233 | }
234 | }
235 | else if (property.NameEquals("needsManualUpgrade"))
236 | {
237 | AssertPropertyTypeIsBoolean(relativePath, elementPath, property);
238 | }
239 | else if (property.NameEquals("needsTodoInComment"))
240 | {
241 | AssertPropertyTypeIsBoolean(relativePath, elementPath, property);
242 | }
243 | else if (property.NameEquals("documentationUrl"))
244 | {
245 | AssertPropertyType(relativePath, elementPath, property, JsonValueKind.String);
246 | }
247 | else if (property.NameEquals("properties"))
248 | {
249 | AssertPropertyType(relativePath, elementPath, property, JsonValueKind.Object);
250 | }
251 | else
252 | {
253 | AssertUnknownProperty(relativePath, elementPath, property);
254 | }
255 | }
256 | }
257 |
258 | private static void AssertApiMapSchema(JsonSerializerOptions options, string fullPath)
259 | {
260 | var relativePath = TestHelper.GetRelativePath(fullPath);
261 | var json = File.ReadAllText(fullPath);
262 | JsonDocument doc;
263 |
264 | try
265 | {
266 | doc = JsonDocument.Parse(json, new JsonDocumentOptions
267 | {
268 | AllowTrailingCommas = options.AllowTrailingCommas,
269 | CommentHandling = options.ReadCommentHandling,
270 | MaxDepth = options.MaxDepth
271 | });
272 | }
273 | catch (Exception ex)
274 | {
275 | Assert.Fail($"Failed to parse {relativePath}: {ex.Message}");
276 | return;
277 | }
278 |
279 | var root = doc.RootElement;
280 |
281 | foreach (var property in root.EnumerateObject())
282 | {
283 | AssertPropertyType(relativePath, string.Empty, property, JsonValueKind.Object);
284 | AssertApiMapEntry(relativePath, GetPropertyPath(string.Empty, property), property.Value);
285 | }
286 | }
287 |
288 | private static void AssertConfigSchema(JsonSerializerOptions options, string fullPath)
289 | {
290 | var relativePath = TestHelper.GetRelativePath(fullPath);
291 | var json = File.ReadAllText(fullPath);
292 | JsonDocument doc;
293 |
294 | try
295 | {
296 | doc = JsonDocument.Parse(json, new JsonDocumentOptions
297 | {
298 | AllowTrailingCommas = options.AllowTrailingCommas,
299 | CommentHandling = options.ReadCommentHandling,
300 | MaxDepth = options.MaxDepth
301 | });
302 | }
303 | catch (Exception ex)
304 | {
305 | Assert.Fail($"Failed to parse {relativePath}: {ex.Message}");
306 | return;
307 | }
308 |
309 | var root = doc.RootElement;
310 |
311 | foreach (var property in root.EnumerateObject())
312 | {
313 | if (property.NameEquals("traits"))
314 | {
315 | AssertPropertyType(relativePath, string.Empty, property, JsonValueKind.String);
316 | }
317 | else if (property.NameEquals("order"))
318 | {
319 | AssertPropertyType(relativePath, string.Empty, property, JsonValueKind.Number);
320 | }
321 | else
322 | {
323 | AssertUnknownProperty(relativePath, string.Empty, property);
324 | }
325 | }
326 | }
327 | }
328 |
--------------------------------------------------------------------------------
/tests/UpgradeAssistant.Mappings.Tests/TestHelper.cs:
--------------------------------------------------------------------------------
1 | // Licensed to the .NET Foundation under one or more agreements.
2 | // The .NET Foundation licenses this file to you under the MIT license.
3 |
4 | namespace Microsoft.UpgradeAssistant.Mappings.Tests;
5 |
6 | static class TestHelper
7 | {
8 | public static readonly string MappingsDir;
9 |
10 | static TestHelper()
11 | {
12 | var location = typeof(TestHelper).Assembly.Location;
13 | var baseDir = Path.GetDirectoryName(location);
14 |
15 | MappingsDir = Path.GetFullPath(Path.Combine(baseDir!, "mappings"));
16 | }
17 |
18 | public static string GetRelativePath(string path)
19 | {
20 | return Path.GetRelativePath(MappingsDir, path);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/UpgradeAssistant.Mappings.Tests/TraitToken.cs:
--------------------------------------------------------------------------------
1 | // Licensed to the .NET Foundation under one or more agreements.
2 | // The .NET Foundation licenses this file to you under the MIT license.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 |
7 | namespace Microsoft.UpgradeAssistant.Mappings.Tests;
8 |
9 | internal class TraitToken
10 | {
11 | private static readonly char[] DotSeparator = new[] { '.' };
12 |
13 | public static string? GetPropertyName(TraitToken token, string id)
14 | {
15 | if (!string.Equals(token.TraitName, id, StringComparison.OrdinalIgnoreCase))
16 | {
17 | return null;
18 | }
19 |
20 | return token.PropertyName;
21 | }
22 |
23 | public const string ColonOperator = ":";
24 | public const string LessOperator = "<";
25 | public const string MoreOperator = ">";
26 | public const string EqualsOperator = "=";
27 | public const string MoreOrEqualsOperator = ">=";
28 | public const string LessOrEqualsOperator = "<=";
29 |
30 | private static readonly HashSet Operators = new(StringComparer.Ordinal)
31 | {
32 | ColonOperator,
33 | LessOperator,
34 | MoreOperator,
35 | EqualsOperator,
36 | MoreOrEqualsOperator,
37 | LessOrEqualsOperator
38 | };
39 |
40 | private static readonly char[] OperatorSymbols = ":<>=!".ToCharArray();
41 |
42 | public static bool TryParse(string input, out TraitToken? token)
43 | {
44 | token = null;
45 |
46 | if (string.IsNullOrEmpty(input))
47 | {
48 | return false;
49 | }
50 |
51 | int i = 0;
52 | int begin = i;
53 |
54 | while (i < input.Length && !IsOperatorCharacter(input[i])) { i++; }
55 |
56 | if (i == input.Length)
57 | {
58 | return false;
59 | }
60 |
61 | string key = input.Substring(begin, i).Trim();
62 | if (string.IsNullOrEmpty(key))
63 | {
64 | return false;
65 | }
66 |
67 | begin = i;
68 |
69 | while (i < input.Length && IsOperatorCharacter(input[i])) { i++; }
70 |
71 | string @operator = input.Substring(begin, i - begin);
72 | if (!Operators.Contains(@operator))
73 | {
74 | return false;
75 | }
76 |
77 | string value = i < input.Length ? input.Substring(i) : string.Empty;
78 |
79 | token = new TraitToken(key, value.Trim(), @operator);
80 |
81 | return true;
82 | }
83 |
84 | private static bool IsOperatorCharacter(char ch)
85 | {
86 | return Array.IndexOf(OperatorSymbols, ch) >= 0;
87 | }
88 |
89 | public TraitToken(string key, string value, string @operator)
90 | {
91 | Key = key;
92 | Value = value;
93 | Operator = @operator;
94 | }
95 |
96 | public string Key { get; }
97 |
98 | public string Value { get; }
99 |
100 | public string Operator { get; }
101 |
102 | private string? _traitName;
103 | public string? TraitName
104 | {
105 | get
106 | {
107 | if (string.IsNullOrEmpty(Key))
108 | {
109 | return null;
110 | }
111 |
112 | if (_traitName is null)
113 | {
114 | var parts = Key.Trim().Split(DotSeparator, StringSplitOptions.RemoveEmptyEntries);
115 | if (parts.Length < 1 || parts.Length > 2)
116 | {
117 | return null;
118 | }
119 |
120 | _traitName = parts[0];
121 | _propertyName = parts.Length == 2 ? parts[1] : null;
122 | }
123 |
124 | return _traitName;
125 | }
126 | }
127 |
128 | private string? _propertyName;
129 | public string? PropertyName
130 | {
131 | get
132 | {
133 | if (string.IsNullOrEmpty(Key))
134 | {
135 | return null;
136 | }
137 |
138 | if (_propertyName is null)
139 | {
140 | var parts = Key.Trim().Split(DotSeparator, StringSplitOptions.RemoveEmptyEntries);
141 | if (parts.Length < 1 || parts.Length > 2)
142 | {
143 | return null;
144 | }
145 |
146 | _traitName = parts[0];
147 | _propertyName = parts.Length == 2 ? parts[1] : null;
148 | }
149 |
150 | return _propertyName;
151 | }
152 | }
153 |
154 | public override string ToString()
155 | {
156 | return $"{{{Key}{Operator}{Value}}}";
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/tests/UpgradeAssistant.Mappings.Tests/TraitsExpressionParser.cs:
--------------------------------------------------------------------------------
1 | // Licensed to the .NET Foundation under one or more agreements.
2 | // The .NET Foundation licenses this file to you under the MIT license.
3 |
4 | using System;
5 |
6 | namespace Microsoft.UpgradeAssistant.Mappings.Tests;
7 |
8 | internal struct TraitsExpressionParser
9 | {
10 | private static readonly char[] DisallowedCharacters = "\"'`:;,+-*/\\!~|&%$@^()={}[]<>? \t\b\n\r".ToCharArray();
11 | private const string InvalidTraitExpression = "Invalid capability expression at position {0} in the expression \"{1}\".";
12 | private const string UnknownTraitToken = "Unknown trait token \"{0}\".";
13 |
14 | ///
15 | /// The tokenizer that reads the trait expression.
16 | ///
17 | private Tokenizer _tokenizer;
18 |
19 | ///
20 | /// Initializes a new instance of the struct.
21 | ///
22 | /// The trait expression.
23 | private TraitsExpressionParser(
24 | string expression)
25 | {
26 | _tokenizer = new Tokenizer(expression);
27 | }
28 |
29 | ///
30 | /// Checks whether a given trait expression is valid.
31 | ///
32 | ///
33 | /// The trait expression, such as "(VisualC | CSharp) + (MSTest | NUnit)".
34 | /// The '|' is the OR operator.
35 | /// The '&' and '+' characters are both AND operators.
36 | /// The '!' character is the NOT operator.
37 | /// Parentheses force evaluation precedence order.
38 | /// A null or empty expression is evaluated as a match.
39 | ///
40 | /// The result of the expression validation.
41 | public static void Validate(string expression)
42 | {
43 | var parser = new TraitsExpressionParser(expression);
44 | parser.Validate();
45 | }
46 |
47 | ///
48 | /// Checks whether a given character is an allowed member of a trait term.
49 | ///
50 | /// The character to test.
51 | /// if the character would be an allowed member of a trait term; otherwise, .
52 | private static bool IsSymbolCharacter(char ch)
53 | {
54 | return Array.IndexOf(DisallowedCharacters, ch) == -1;
55 | }
56 |
57 | ///
58 | /// Processes | operators.
59 | ///
60 | private void ValidateOrTerm()
61 | {
62 | ValidateAndTerm();
63 | while (_tokenizer.Peek() == "|")
64 | {
65 | _tokenizer.Next();
66 | ValidateAndTerm();
67 | }
68 | }
69 |
70 | ///
71 | /// Processes & operators.
72 | ///
73 | private void ValidateAndTerm()
74 | {
75 | ValidateTerm();
76 | while (_tokenizer.Peek() == "&")
77 | {
78 | _tokenizer.Next();
79 | ValidateTerm();
80 | }
81 | }
82 |
83 | ///
84 | /// Processes trait terms.
85 | ///
86 | private void ValidateTerm()
87 | {
88 | int notCount = 0;
89 | while (_tokenizer.Peek() == "!")
90 | {
91 | _tokenizer.Next();
92 | notCount++;
93 | }
94 |
95 | if (_tokenizer.Peek() == "(")
96 | {
97 | _tokenizer.Next();
98 | ValidateOrTerm();
99 |
100 | if (_tokenizer.Peek() != ")")
101 | {
102 | // Avoid inlining check to avoid boxing the length
103 | throw new TraitsExpressionSyntaxException(string.Format(InvalidTraitExpression, _tokenizer.Position, _tokenizer.Input));
104 | }
105 |
106 | _tokenizer.Next();
107 | }
108 | else if (_tokenizer.Peek() != null && IsSymbolCharacter(_tokenizer.Peek()![0]))
109 | {
110 | string ident = _tokenizer.Next()!;
111 | }
112 | else if (_tokenizer.Peek() != null && _tokenizer.Peek()![0] == '{')
113 | {
114 | ProcessToken(_tokenizer.Next()!);
115 | }
116 | else
117 | {
118 | throw new TraitsExpressionSyntaxException(string.Format(InvalidTraitExpression, _tokenizer.Position, _tokenizer.Input));
119 | }
120 | }
121 |
122 | ///
123 | /// Process special tokens like {key:value} where ':' should be =, <, >, <=, >=, !=.
124 | ///
125 | private void ProcessToken(string input)
126 | {
127 | input = input.TrimStart('{').TrimEnd('}');
128 |
129 | if (string.IsNullOrEmpty(input) || !TraitToken.TryParse(input, out var token))
130 | {
131 | throw new TraitsExpressionSyntaxException(string.Format(UnknownTraitToken, input));
132 | }
133 |
134 | var traitName = token!.TraitName;
135 | if (string.IsNullOrEmpty(traitName))
136 | {
137 | throw new TraitsExpressionSyntaxException(string.Format(UnknownTraitToken, input));
138 | }
139 | }
140 |
141 | private void Validate()
142 | {
143 | ValidateOrTerm();
144 |
145 | if (_tokenizer.Peek() != null)
146 | {
147 | // Avoid inlining check to avoid boxing the length
148 | throw new TraitsExpressionSyntaxException(string.Format(InvalidTraitExpression, _tokenizer.Input.Length, _tokenizer.Input));
149 | }
150 | }
151 |
152 | ///
153 | /// The expression tokenizer.
154 | ///
155 | ///
156 | /// This is a struct rather than a class to avoid allocating memory unnecessarily.
157 | ///
158 | private struct Tokenizer
159 | {
160 | ///
161 | /// The most recently previewed token.
162 | ///
163 | private string? _peeked;
164 |
165 | ///
166 | /// Initializes a new instance of the struct.
167 | ///
168 | /// The expression to parse.
169 | internal Tokenizer(string input)
170 | {
171 | Input = input;
172 | Position = 0;
173 | _peeked = null;
174 | }
175 |
176 | ///
177 | /// Gets the entire expression being tokenized.
178 | ///
179 | internal string Input { get; }
180 |
181 | ///
182 | /// Gets the position of the next token.
183 | ///
184 | internal int Position { get; private set; }
185 |
186 | ///
187 | /// Gets the next token in the expression.
188 | ///
189 | internal string? Next()
190 | {
191 | // If the last call to Next() was within a Peek() method call,
192 | // we need to return the same value again this time so that
193 | // the Peek() doesn't impact the token stream.
194 | if (_peeked != null)
195 | {
196 | string token = _peeked;
197 | _peeked = null;
198 | return token;
199 | }
200 |
201 | // Skip whitespace.
202 | while (Position < Input.Length && char.IsWhiteSpace(Input[Position]))
203 | {
204 | Position++;
205 | }
206 |
207 | if (Position == Input.Length)
208 | {
209 | return null;
210 | }
211 |
212 | if (IsSymbolCharacter(Input[Position]))
213 | {
214 | int begin = Position;
215 | while (Position < Input.Length && IsSymbolCharacter(Input[Position]))
216 | {
217 | Position++;
218 | }
219 |
220 | int end = Position;
221 | return Input.Substring(begin, end - begin);
222 | }
223 | else if (Input[Position] == '&' || Input[Position] == '+') // we prefer & but also accept + so that XML manifest files don't have to write the & escape sequence.
224 | {
225 | Position++;
226 | return "&"; // always return '&' to simplify the parser logic by consolidating on only one of the two possible operators.
227 | }
228 | else if (Input[Position] == '|')
229 | {
230 | Position++;
231 | return "|";
232 | }
233 | else if (Input[Position] == '(')
234 | {
235 | Position++;
236 | return "(";
237 | }
238 | else if (Input[Position] == ')')
239 | {
240 | Position++;
241 | return ")";
242 | }
243 | else if (Input[Position] == '!')
244 | {
245 | Position++;
246 | return "!";
247 | }
248 | else if (Input[Position] == '{')
249 | {
250 | // read special tokens like {xxx:vvv}
251 | int begin = Position;
252 | while (Position < Input.Length && Input[Position] != '}')
253 | {
254 | Position++;
255 | }
256 |
257 | int end = Position;
258 |
259 | Position++;
260 |
261 | return Input.Substring(begin, end - begin);
262 | }
263 | else
264 | {
265 | throw new TraitsExpressionSyntaxException(string.Format(InvalidTraitExpression, Position, Input));
266 | }
267 | }
268 |
269 | ///
270 | /// Peeks at the next token in the stream without skipping it on
271 | /// the next invocation of .
272 | ///
273 | internal string? Peek()
274 | {
275 | return _peeked = Next();
276 | }
277 | }
278 | }
279 |
--------------------------------------------------------------------------------
/tests/UpgradeAssistant.Mappings.Tests/TraitsExpressionSyntaxException.cs:
--------------------------------------------------------------------------------
1 | // Licensed to the .NET Foundation under one or more agreements.
2 | // The .NET Foundation licenses this file to you under the MIT license.
3 |
4 | using System;
5 |
6 | namespace Microsoft.UpgradeAssistant.Mappings.Tests;
7 |
8 | internal class TraitsExpressionSyntaxException : FormatException
9 | {
10 | public TraitsExpressionSyntaxException(string message)
11 | : base(message)
12 | {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tests/UpgradeAssistant.Mappings.Tests/UpgradeAssistant.Mappings.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 | false
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/version.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
3 | "version": "1.0",
4 | "publicReleaseRefSpec": [
5 | "^refs/heads/main$",
6 | "^refs/heads/v\\d+(?:\\.\\d+)?$"
7 | ],
8 | "cloudBuild": {
9 | "buildNumber": {
10 | "enabled": true
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------