├── .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 |[![Build Status](https://dev.azure.com/dnceng/public/_apis/build/status/dotnet/upgrade-assistant/dotnet.upgrade-assistant?branchName=main&jobName=Windows_NT&configuration=Windows_NT%20Debug)](https://dev.azure.com/dnceng/public/_build/latest?definitionId=953&branchName=main)|[![Build Status](https://dev.azure.com/dnceng/public/_apis/build/status/dotnet/upgrade-assistant/dotnet.upgrade-assistant?branchName=main&jobName=Windows_NT&configuration=Windows_NT%20Release)](https://dev.azure.com/dnceng/public/_build/latest?definitionId=953&branchName=main)| 11 | | official | [![Build Status](https://dev.azure.com/dnceng/internal/_apis/build/status/dotnet/upgrade-assistant/dotnet-upgrade-assistant?branchName=main&stageName=Build&jobName=Windows_NT&configuration=Windows_NT%20Debug)](https://dev.azure.com/dnceng/internal/_build/latest?definitionId=949&branchName=main)|[![Build Status](https://dev.azure.com/dnceng/internal/_apis/build/status/dotnet/upgrade-assistant/dotnet-upgrade-assistant?branchName=main&stageName=Build&jobName=Windows_NT&configuration=Windows_NT%20Release)](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 | } --------------------------------------------------------------------------------