diff --git a/.editorconfig b/.editorconfig index bc17bbe69267..34450257dc72 100644 --- a/.editorconfig +++ b/.editorconfig @@ -280,6 +280,10 @@ dotnet_diagnostic.IDE0200.severity = none dotnet_diagnostic.IDE0240.severity = warning # Additional rules for template engine source code + +# Default severity for analyzer diagnostics with category 'StyleCop.CSharp.SpacingRules' +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.SpacingRules.severity = none + [{src,test}/**{Microsoft.TemplateEngine.*,dotnet-new?*}/**.cs] # Default analyzed API surface = 'public' (public APIs) dotnet_code_quality.api_surface = public @@ -511,11 +515,11 @@ dotnet_diagnostic.IDE0040.severity = warning insert_final_newline = false # Verify settings -[*.{received,verified}.{txt,xml,json}] +[*.{received,verified}.{txt,xml,json,sh,zsh,nu,fish,ps1}] charset = "utf-8-bom" end_of_line = lf indent_size = unset indent_style = unset insert_final_newline = false tab_width = unset -trim_trailing_whitespace = false \ No newline at end of file +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes index 2f46e347155a..a7c35ea1b75a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -67,3 +67,7 @@ *.verified.txt text eol=lf working-tree-encoding=UTF-8 *.verified.xml text eol=lf working-tree-encoding=UTF-8 *.verified.json text eol=lf working-tree-encoding=UTF-8 +*.verified.sh text eol=lf working-tree-encoding=UTF-8 +*.verified.zsh text eol=lf working-tree-encoding=UTF-8 +*.verified.nu text eol=lf working-tree-encoding=UTF-8 +*.verified.fish text eol=lf working-tree-encoding=UTF-8 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index c31c8405a9b1..d772f896381f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -5,6 +5,9 @@ Coding Style and Changes: - When generating code, run `dotnet format` to ensure uniform formatting. - Prefer using file-based namespaces for new code. - Do not allow unused `using` directives to be committed. +- Commit your changes, and then format them. +- Add the format commit SHA to the .git-blame-ignore-revs file so that the commit doesn't dirty git blame in the future +- Use `#if NET` blocks for .NET Core specific code, and `#if NETFRAMEWORK` for .NET Framework specific code. Testing: - Large changes should always include test changes. @@ -31,3 +34,8 @@ Localization: Documentation: - Do not manually edit files under documentation/manpages/sdk as these are generated based on documentation and should not be manually modified. + +External Dependencies: +- Changes that require modifications to the dotnet/templating repository (Microsoft.TemplateEngine packages) should be made directly in that repository, not worked around in this repo. +- The dotnet/templating repository owns the TemplateEngine.Edge, TemplateEngine.Abstractions, and related packages. +- If a change requires updates to template engine behavior or formatting (e.g., DisplayName properties), file an issue in dotnet/templating and make the changes there rather than adding workarounds in this SDK repository. diff --git a/.github/workflows/add-lockdown-label.yml b/.github/workflows/add-lockdown-label.yml index 529578d0b89c..dfafacd12008 100644 --- a/.github/workflows/add-lockdown-label.yml +++ b/.github/workflows/add-lockdown-label.yml @@ -3,6 +3,11 @@ name: Add Branch Lockdown Label to PRs on: pull_request_target: workflow_dispatch: # Allows manual triggering of the workflow + branches: + - 'release/8.*' + - 'release/9.*' + - 'release/10.*' + - 'main' permissions: actions: write # For managing the operation state cache @@ -11,6 +16,8 @@ permissions: jobs: add-label: runs-on: ubuntu-latest + # Only run on the main repository, not forks + if: github.repository == 'dotnet/sdk' permissions: contents: read @@ -19,6 +26,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 + with: + persist-credentials: false - name: Install jq run: sudo apt-get install -y jq diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 9008eb69744a..db1219a15c42 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -20,6 +20,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Do an initial build to ensure all dependencies are restored continue-on-error: true run: | @@ -31,3 +33,18 @@ jobs: - name: Check dotnet version run: | dotnet --version + + # For MCP servers like nuget's + - name: Install .NET 10.x + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 10.x + dotnet-quality: preview + + # for MCP servers + - name: Install .NET 8.x + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 8.x diff --git a/.github/workflows/remove-lockdown-label.yml b/.github/workflows/remove-lockdown-label.yml index 51c6b82c8a68..0013a4b383f0 100644 --- a/.github/workflows/remove-lockdown-label.yml +++ b/.github/workflows/remove-lockdown-label.yml @@ -2,7 +2,11 @@ name: Remove Lockdown Label from PRs on: pull_request_target: - types: [closed] + branches: + - 'release/8.*' + - 'release/9.*' + - 'release/10.*' + - 'main' permissions: actions: write @@ -10,7 +14,8 @@ permissions: jobs: remove-labels: - if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'Branding') + # Only run on the main repository, not forks + if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'Branding') && github.repository == 'dotnet/sdk' runs-on: ubuntu-latest steps: - name: PR's only change is in eng/Versions.props diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index e3faa1263a49..62de55321f6f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -3,22 +3,10 @@ on: schedule: - cron: '19 4,16 * * *' # Twice daily at 19 minutes after the hour (random/uncommon time) - workflow_dispatch: - # Manual triggering through the GitHub UI, API, or CLI - inputs: - daysBeforeStale: - required: true - default: "1827" - daysBeforeClose: - required: true - default: "30" - operationsPerRun: - required: true - default: "4000" - permissions: actions: write # For managing the operation state cache issues: write + pull-requests: write jobs: stale: @@ -29,11 +17,10 @@ jobs: - uses: actions/stale@v9 # https://github.com/actions/stale/blob/v9/README.md with: ascending: true # Process the oldest issues first - stale-issue-label: 'stale' - stale-issue-message: "Due to lack of recent activity, this issue has been labeled as 'stale'. It will be closed if no further activity occurs within 30 more days. Any new comment will remove the label." - close-issue-message: "This issue will now be closed since it has been labeled 'stale' without activity for 30 days." - days-before-stale: ${{ fromJson(inputs.daysBeforeStale || 2192) }} # Default to 6 years if not specified as input - days-before-close: ${{ fromJson(inputs.daysBeforeClose || 30 ) }} # Default to 30 days if not specified as input - days-before-pr-stale: -1 # Do not label PRs as 'stale' - days-before-pr-close: -1 # Do not close PRs labeled as 'stale' - operations-per-run: ${{ fromJson(inputs.operationsPerRun || 4000 )}} + stale-issue-message: "Due to lack of recent activity, this issue has been labeled as 'Stale'. It will be closed if no further activity occurs within 30 more days. Any new comment will remove the label." + stale-pr-message: "Due to lack of recent activity, this PR has been labeled as 'Stale'. It will be closed if no further activity occurs within 7 more days. Any new comment will remove the label." + days-before-issue-stale: 1827 # ~5 years + days-before-issue-close: 30 + days-before-pr-stale: 180 # 6 months + days-before-pr-close: 7 + operations-per-run: 4000 diff --git a/.github/workflows/update-man-pages.yml b/.github/workflows/update-man-pages.yml index 48e2e9bd5706..cfc8f245dc21 100644 --- a/.github/workflows/update-man-pages.yml +++ b/.github/workflows/update-man-pages.yml @@ -1,5 +1,6 @@ name: Update man-pages workflow on: + workflow_dispatch: schedule: - cron: "42 3 1/15 * *" # Trigger every 15 days at 03:42 #- cron: "0,5,10,15,20,25,30,35,40,45,50,55 * * * *" # For testing @@ -14,6 +15,9 @@ jobs: steps: - name: Checkout repository code uses: actions/checkout@v4 + with: + ref: release/10.0.1xx + persist-credentials: false - name: Update man-pages run: | @@ -38,7 +42,7 @@ jobs: git checkout -b $branch git commit -m "$title" git push -u origin $branch --force - gh pr create --base main --head $branch --title "$title" --body "$body" + gh pr create --base release/10.0.1xx --head $branch --title "$title" --body "$body" fi env: GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/update-static-web-assets-baselines.yml b/.github/workflows/update-static-web-assets-baselines.yml new file mode 100644 index 000000000000..685efb7d7d9f --- /dev/null +++ b/.github/workflows/update-static-web-assets-baselines.yml @@ -0,0 +1,118 @@ +name: Update Static Web Assets Baselines + +on: + workflow_dispatch: + inputs: + pr_number: + description: 'Pull Request number' + required: true + type: string + +jobs: + update-baselines: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + + - name: Checkout PR branch + run: | + gh pr checkout ${GITHUB_EVENT_INPUTS_PR_NUMBER} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_EVENT_INPUTS_PR_NUMBER: ${{ github.event.inputs.pr_number }} + + - name: Run build script + id: build + run: | + chmod +x ./build.sh + ./build.sh -c Release + continue-on-error: true + + - name: Update baselines + id: update + if: steps.build.outcome == 'success' + run: | + # Set DOTNET_ROOT to the local dotnet installation + export DOTNET_ROOT="$(pwd)/.dotnet" + # Prepend DOTNET_ROOT to PATH + export PATH="$DOTNET_ROOT:$PATH" + + # Run the update baselines script + chmod +x src/RazorSdk/update-test-baselines.sh + src/RazorSdk/update-test-baselines.sh + continue-on-error: true + + - name: Check for changes + id: check-changes + if: steps.update.outcome == 'success' + run: | + # Check if there are changes to any *.json files under the test folder + if git diff --name-only | grep -E '^test/.*\.json$'; then + echo "changes=true" >> $GITHUB_OUTPUT + else + echo "changes=false" >> $GITHUB_OUTPUT + fi + + - name: Commit and push changes + id: commit + if: steps.update.outcome == 'success' && steps.check-changes.outputs.changes == 'true' + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + + # Create commit with formatted date + COMMIT_DATE=$(date -u +"%Y-%m-%d") + git add test/**/*.json + git commit -m "[Infrastructure] Update baselines $COMMIT_DATE" + + # Push to the PR branch + git push + continue-on-error: true + + - name: Comment on PR - No changes + if: steps.update.outcome == 'success' && steps.check-changes.outputs.changes == 'false' + run: | + gh pr comment ${GITHUB_EVENT_INPUTS_PR_NUMBER} \ + --body "No baselines were updated." + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_EVENT_INPUTS_PR_NUMBER: ${{ github.event.inputs.pr_number }} + + - name: Comment on PR - Changes pushed + if: steps.commit.outcome == 'success' + run: | + gh pr comment ${GITHUB_EVENT_INPUTS_PR_NUMBER} \ + --body "Baselines updated." + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_EVENT_INPUTS_PR_NUMBER: ${{ github.event.inputs.pr_number }} + + - name: Comment on PR - Failure + if: steps.build.outcome == 'failure' || steps.update.outcome == 'failure' || (steps.check-changes.outputs.changes == 'true' && steps.commit.outcome == 'failure') + run: | + ERROR_MSG="Update baselines failed" + + if [[ "${STEPS_BUILD_OUTCOME}" == "failure" ]]; then + ERROR_MSG="$ERROR_MSG: Build script failed" + elif [[ "${STEPS_UPDATE_OUTCOME}" == "failure" ]]; then + ERROR_MSG="$ERROR_MSG: Update baselines script failed" + elif [[ "${STEPS_COMMIT_OUTCOME}" == "failure" ]]; then + ERROR_MSG="$ERROR_MSG: Failed to commit or push changes" + fi + + gh pr comment ${GITHUB_EVENT_INPUTS_PR_NUMBER} \ + --body "$ERROR_MSG" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + STEPS_BUILD_OUTCOME: ${{ steps.build.outcome }} + STEPS_UPDATE_OUTCOME: ${{ steps.update.outcome }} + STEPS_COMMIT_OUTCOME: ${{ steps.commit.outcome }} + GITHUB_EVENT_INPUTS_PR_NUMBER: ${{ github.event.inputs.pr_number }} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 1cdcc7c1869e..212ebd955c26 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,11 @@ { "dotnet.testWindow.disableAutoDiscovery": true, - "dotnet.defaultSolution": "sdk.slnx" + "dotnet.testWindow.disableBuildOnRun": true, + "dotnet.defaultSolution": "cli.slnf", + "files.associations": { + "*.slnf": "json", + "*.props": "xml", + "*.targets": "xml", + "*.*proj": "xml" + } } diff --git a/.vsts-ci.yml b/.vsts-ci.yml index b7fc54598d53..24277c9557f1 100644 --- a/.vsts-ci.yml +++ b/.vsts-ci.yml @@ -5,7 +5,7 @@ trigger: branches: include: - main - - release/10.0.1* + - release/10.0.2* - internal/release/* - exp/* @@ -299,6 +299,7 @@ extends: publishUsingPipelines: true publishAssetsImmediately: true isAssetlessBuild: true + repositoryAlias: self pool: name: $(DncEngInternalBuildPool) image: 1es-windows-2022 diff --git a/CODEOWNERS b/CODEOWNERS index a3dc756e44fa..4591eb1ff8d7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,5 +1,6 @@ # This is a comment. # Each line is a file pattern followed by one or more owners. +# Syntax: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-syntax # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, @@ -100,14 +101,20 @@ /test/Microsoft.DotNet.GenAPI/ @dotnet/area-infrastructure-libraries /test/Microsoft.DotNet.ApiSymbolExtensions.Tests/ @dotnet/area-infrastructure-libraries -# Area: dotnet containers +# Area-Containers /src/Cli/Containers @dotnet/sdk-container-builds-maintainers /test/containerize.UnitTests @dotnet/sdk-container-builds-maintainers /test/Microsoft.NET.Build.Containers.IntegrationTests @dotnet/sdk-container-builds-maintainers /test/Microsoft.NET.Build.Containers.UnitTests @dotnet/sdk-container-builds-maintainers -# dotnet-format +# Area-Format /src/BuiltInTools/dotnet-format @dotnet/roslyn-ide /test/dotnet-format.UnitTests @dotnet/roslyn-ide +# Area-Microsoft.CodeAnalysis.NetAnalyzers /src/Microsoft.CodeAnalysis.NetAnalyzers @dotnet/dotnet-analyzers + +# Area-Infrastructure +.vsts-ci.yml @MiYanni +.vsts-pr.yml @MiYanni +/eng/pipelines @MiYanni \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 30d83f68c73f..b7c097f97cdc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,4 +1,3 @@ - diff --git a/Directory.Build.targets b/Directory.Build.targets index 464f65cfee17..2b77ecc8829b 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,6 +1,5 @@ - - + - @@ -24,6 +23,8 @@ + + diff --git a/OverrideTest.targets b/OverrideTest.targets index 7a9151b15e1a..f8019c6d7e23 100644 --- a/OverrideTest.targets +++ b/OverrideTest.targets @@ -1,7 +1,7 @@ - - - + + diff --git a/cli.slnf b/cli.slnf new file mode 100644 index 000000000000..a300800156bf --- /dev/null +++ b/cli.slnf @@ -0,0 +1,15 @@ +{ + "solution": { + "path": "sdk.slnx", + "projects": [ + "src\\BuiltInTools\\dotnet-watch\\dotnet-watch.csproj", + "src\\Cli\\dotnet\\dotnet.csproj", + "src\\Cli\\Microsoft.DotNet.Cli.Utils\\Microsoft.DotNet.Cli.Utils.csproj", + "test\\dotnet-new.IntegrationTests\\dotnet-new.IntegrationTests.csproj", + "test\\dotnet-watch.Tests\\dotnet-watch.Tests.csproj", + "test\\dotnet.Tests\\dotnet.Tests.csproj", + "test\\Microsoft.DotNet.Cli.Utils.Tests\\Microsoft.DotNet.Cli.Utils.Tests.csproj", + "test\\Microsoft.NET.TestFramework\\Microsoft.NET.TestFramework.csproj" + ] + } +} diff --git a/documentation/general/analyzer-redirecting.md b/documentation/general/analyzer-redirecting.md index e999b98d76fe..993445af4ce6 100644 --- a/documentation/general/analyzer-redirecting.md +++ b/documentation/general/analyzer-redirecting.md @@ -19,11 +19,12 @@ Targeting an SDK (and hence also loading analyzers) with newer major version in ## Overview -- The SDK will contain a VSIX with the analyzer DLLs and an MEF-exported implementation of `IAnalyzerAssemblyRedirector`. +- The SDK will deploy analyzer DLLs and Roslyn will deploy an implementation of `IAnalyzerAssemblyRedirector` + (could be deployed by SDK but we are saving on analyzer loads and Roslyn already has a VSIX with a DLL). Implementations of this interface are imported by Roslyn and can redirect analyzer DLL loading. -- The SDK's implementation of `IAnalyzerAssemblyRedirector` will redirect any analyzer DLL matching some pattern - to the corresponding DLL deployed via the VSIX. +- That implementation of `IAnalyzerAssemblyRedirector` will redirect any analyzer DLL matching some pattern + to the corresponding DLL deployed with VS. Details of this process are described below. - Note that when `IAnalyzerAssemblyRedirector` is involved, Roslyn is free to not use shadow copy loading and instead load the DLLs directly. @@ -33,7 +34,7 @@ Targeting an SDK (and hence also loading analyzers) with newer major version in ## Details -The VSIX contains some analyzers, for example: +The VS deployment contains some analyzers, for example: ``` AspNetCoreAnalyzers\analyzers\dotnet\cs\Microsoft.AspNetCore.App.Analyzers.dll @@ -69,7 +70,7 @@ C:\Program Files\dotnet\sdk\9.0.100-preview.5.24307.3\Sdks\Microsoft.NET.Sdk\ana will be redirected to ``` -{VSIX}\SDKAnalyzers\Sdks\Microsoft.NET.Sdk\analyzers\Microsoft.CodeAnalysis.NetAnalyzers.dll +{InstallDir}\SDKAnalyzers\Sdks\Microsoft.NET.Sdk\analyzers\Microsoft.CodeAnalysis.NetAnalyzers.dll ``` where `metadata.json` has `"SDKAnalyzers": "9.0.100-dev"`, because @@ -83,6 +84,5 @@ Analyzers that cannot be matched will continue to be loaded from the SDK ### Implementation Analyzer DLLs are contained in transport package `VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers`. -The redirecting logic lives in "system" VS extension `Microsoft.Net.Sdk.AnalyzerRedirecting`. [torn-sdk]: https://github.com/dotnet/sdk/issues/42087 diff --git a/documentation/general/dotnet-run-file.md b/documentation/general/dotnet-run-file.md index 41bb96b5f6a2..a14132092ee1 100644 --- a/documentation/general/dotnet-run-file.md +++ b/documentation/general/dotnet-run-file.md @@ -56,6 +56,9 @@ Additionally, the implicit project file has the following customizations: in case there is a project or solution in the same directory as the file-based app. This ensures that items from nested projects and artifacts are not included by the app. + - `EnableDefaultEmbeddedResourceItems` and `EnableDefaultNoneItems` properties are set to `false` if the default SDK (`Microsoft.NET.Sdk`) is being used. + This avoids including files like `./**/*.resx` in simple file-based apps where users usually don't expect that. + ## Grow up When file-based programs reach an inflection point where build customizations in a project file are needed, diff --git a/documentation/specs/dotnet-run-for-maui.md b/documentation/specs/dotnet-run-for-maui.md new file mode 100644 index 000000000000..6ee879a91010 --- /dev/null +++ b/documentation/specs/dotnet-run-for-maui.md @@ -0,0 +1,222 @@ +# `dotnet run` for .NET MAUI Scenarios + +The current state of `dotnet run` for .NET MAUI projects is summarized +by this issue from 2021: + +* [Figure out 'dotnet run' CLI experience for iOS and Android](https://github.com/dotnet/xamarin/issues/26) + +The pain points being: + +* iOS and Android use different properties to select a device, + emulator, or simulator. It is difficult for developers to make this + selection as you need to run platform-specific commands along with + complex MSBuild properties. + +* Each platform has different behavior, regards to displaying console + output, etc. + +* Using an MSIX with WindowsAppSDK doesn't support `dotnet run` at all, + relying completely on IDEs like Visual Studio. + +* `dotnet run` does not have a concept of a "deploy" step. + +This has become more relevant in the AI era, as someone is going to +expect AIs in "agent mode" to build and run their app. If the +command-line options are difficult, AIs will fail just as badly as +humans do today. + +## The `dotnet run` Pipeline + +These are the high-level steps during `dotnet run`, that we would like +to make extensible for .NET MAUI (and future) scenarios. + +* `restore`: unchanged + +* "Pre-run evaluation" + + * If the project is multi-targeted, containing the + `$(TargetFrameworks)` property _and_ that property has more than + one item in it, and `-f` was not supplied... + + * Prompt the user to select from a list of the + `$(TargetFrameworks)` + + * Non-interactive mode will give a friendly error message, + suggesting to supply the `-f` property, listing available target + frameworks in the project. + + * Once a `$(TargetFramework)` is selected, either from previous + steps or `-f`... + + * If a `ComputeAvailableDevices` MSBuild target is available, provided by + the iOS or Android workload, etc. ... + + * Call the MSBuild target, which returns a list of `@(Devices)` items... + +```xml + + + + + + + + + + + +``` + +_NOTE: each workload can decide which metadata values for `%(Type)` +and `%(Status)` are useful, filtering offline devices, etc. The output +above would be analogous to running `adb devices`, `xcrun simctl list +devices`, or `xcrun devicectl list devices`._ + +* Continuing on... + + * Prompt the user to select from this list of devices, emulators, + or simulators. + + * Non-interactive mode will error, suggesting to supply the + `--device` switch. Listing the options returned by the + `ComputeAvailableDevices` MSBuild target. + +* `build`: unchanged, but is passed `-p:Device`. + +* `deploy` + + * If a `DeployToDevice` MSBuild target is available, provided by the + iOS or Android workload, etc. + + * Call the MSBuild target, passing in the identifier for the selected + `-p:Device` global MSBuild property. + + * This step needs to run, even with `--no-build`, as you may have + selected a different device. + +* `ComputeRunArguments`: unchanged, but is passed `-p:Device`. + +* `run`: unchanged. `ComputeRunArguments` should have set a valid + `$(RunCommand)` and `$(RunArguments)` using the value supplied by + `-p:Device`. + +## New `dotnet run` Command-line Switches + +So far, it feels like no new subcommand is needed. In interactive +mode, `dotnet run` will now prompt to select a `$(TargetFramework)` +for all multi-targeted projects. Platform-specific projects like +Android, iOS, etc. will prompt for device selection. + +`dotnet run --list-devices` will: + +* Prompt for `$(TargetFramework)` for multi-targeted projects just + like when `--list-devices` is omitted. + + * If there is a single `$(TargetFramework)`, skip to the next step. + +* Call `ComputeAvailableDevices` if the MSBuild target exists, just + like when `--list-devices` is omitted. + + * List the available targets by name, a unique identifier, and an + optional status of the device. + + * Print a friendly message that says how to run `dotnet run` with + the new `--device` switch. + + * If `ComputeAvailableDevices` does not exist in the project + (workload), it can print a friendly message and exit. + +* `dotnet run --list-devices` will then basically exit early, never + running any build, deploy, `ComputeRunArguments`, or run steps. + +A new `--device` switch will: + +* bypass the device-selection portion of the `run` workflow described above + +* Pass in the `-p:Device` global MSBuild property to all build, + deploy, `ComputeRunArguments`, or run steps. + +* The iOS and Android workloads will know how to interpret `$(Device)` + to select an appropriate device, emulator, or simulator. + +## What about Launch Profiles? + +The iOS and Android workloads ignore all +`Properties/launchSettings.json` files and do nothing with them. + +WindowsAppSDK requires a launch profile to select between "packaged" +MSIX and an "unpackaged" .exe, and so we currently provide this in the +`dotnet new maui` project template: + +```json +{ + "profiles": { + "Windows Machine": { + "commandName": "Project", + "nativeDebugging": false + } + } +} +``` + +Or if you want to be "packaged": + +```json +{ + "profiles": { + "Windows Machine": { + "commandName": "MsixPackage", + "nativeDebugging": false + } + } +} +``` + +Launch profiles don't immediately look useful for iOS or Android, as +the identifier you'd put in here would be different per developer -- +you wouldn't want the value saved in source control. You could put a +generic selection like device vs emulator/simulator, but that isn't +addressing the problem we are interested in. Full launch profile +support for .NET MAUI projects may be interesting to address in the +future. + +## iOS and Android workload behavior + +We will work to align as much behavior as possible between the +platforms. Most of this work will happen in the dotnet/android and +dotnet/macios repo, and would be orthogonal to the changes in the .NET +SDK. + +## macOS and MacCatalyst Behavior + +`dotnet run` "just works" on macOS Desktop, because `$(RunCommand)` +can launch the app directly -- similar to console apps. + +## WindowsAppSDK Behavior + +Running a `dotnet new maui` project works today because of the default +launch profile in "unpackaged" mode: + +```dotnetcli +> dotnet run -f net10.0-windows10.0.19041.0 +Using launch settings from D:\src\hellomaui\Properties\launchSettings.json... +``` + +To improve the behavior for "packaged" / MSIX, we could: + +* Implement the `DeployToDevice` MSBuild target. + +* `ComputeAvailableDevices` is not needed. + +* Implement the `ComputeRunArguments` MSBuild target. + +In the future, we can either add this logic into the .NET MAUI +workload or WindowsAppSDK itself. + +## Other frameworks: Avalonia, Uno, MonoGame, etc. + +When these frameworks run on iOS or Android, they are basically using +the `ios` and `android` workloads _without_ .NET MAUI. Any iOS or +Android-specific behavior would apply to these project types in the +same way a `dotnet new android` or `dotnet new ios` project template +would. diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 6c5c392b5d7d..b67b6b847d0f 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -6,146 +6,147 @@ This file should be imported by eng/Versions.props - 10.0.0-rtm.25515.111 - 10.0.0-rtm.25515.111 - 10.0.0-rtm.25515.111 - 10.0.0-rtm.25515.111 - 10.0.0 - 10.0.0-rtm.25515.111 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0-rtm.25515.111 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0-rtm.25515.111 - 10.0.0 - 10.0.0-rtm.25515.111 - 10.0.0-rtm.25515.111 - 10.0.0-preview.25515.111 - 10.0.0 - 10.0.0 - 18.0.0 - 18.0.0-servicing-25515-111 - 7.0.0-rc.1611 - 10.0.0-beta.25515.111 - 5.0.0-2.25515.111 - 5.0.0-2.25515.111 - 5.0.0-2.25515.111 - 5.0.0-2.25515.111 - 5.0.0-2.25515.111 - 5.0.0-2.25515.111 - 5.0.0-2.25515.111 - 10.0.0-preview.25515.111 - 5.0.0-2.25515.111 - 5.0.0-2.25515.111 - 2.0.0-preview.1.25515.111 - 2.2.0-beta.25515.111 - 10.0.0-beta.25515.111 - 10.0.0-beta.25515.111 - 10.0.0-beta.25515.111 - 10.0.0-beta.25515.111 - 10.0.0-beta.25515.111 - 10.0.0-beta.25515.111 - 10.0.0 - 10.0.0 - 10.0.0-beta.25515.111 - 10.0.0-beta.25515.111 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 14.0.100-rc2.25515.111 - 10.0.0 - 5.0.0-2.25515.111 - 5.0.0-2.25515.111 - 10.0.0-rtm.25515.111 - 10.0.0 - 10.0.0 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-preview.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 18.1.0-preview-25515-110 + 18.1.0-preview-25515-110 + 10.0.0-beta.25515.110 + 5.3.0-1.25515.110 + 5.3.0-1.25515.110 + 5.3.0-1.25515.110 + 5.3.0-1.25515.110 + 5.3.0-1.25515.110 + 5.3.0-1.25515.110 + 5.3.0-1.25515.110 + 10.0.0-preview.25515.110 + 5.3.0-1.25515.110 + 5.3.0-1.25515.110 + 2.0.0-preview.1.25515.110 + 2.2.0-beta.25515.110 + 11.0.0-beta.25515.110 + 11.0.0-beta.25515.110 + 11.0.0-beta.25515.110 + 11.0.0-beta.25515.110 + 11.0.0-beta.25515.110 + 11.0.0-beta.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 11.0.0-beta.25515.110 + 11.0.0-beta.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 14.0.0-alpha1.25515.110 + 10.0.0-rc.1.25515.110 + 5.3.0-1.25515.110 + 5.3.0-1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 10.0.0-preview.7.25377.103 - 10.0.0-preview.25515.111 - 10.0.0-rtm.25515.111 - 18.0.0-release-25515-111 - 10.0.0 - 10.0.0-rtm.25515.111 - 10.0.0-beta.25515.111 - 10.0.0-beta.25515.111 - 10.0.0-beta.25515.111 - 10.0.0-beta.25515.111 - 10.0.0-beta.25515.111 - 10.0.100 - 10.0.100 - 10.0.100 - 10.0.100-rtm.25515.111 - 10.0.100 - 10.0.100-rtm.25515.111 - 10.0.100 - 10.0.100 - 10.0.100-rtm.25515.111 - 18.0.0-release-25515-111 - 18.0.0-release-25515-111 - 3.2.0-preview.25515.111 - 10.0.0 - 10.0.0-rtm.25515.111 - 10.0.0 - 7.0.0-rc.1611 - 7.0.0-rc.1611 - 7.0.0-rc.1611 - 7.0.0-rc.1611 - 7.0.0-rc.1611 - 7.0.0-rc.1611 - 7.0.0-rc.1611 - 7.0.0-rc.1611 - 7.0.0-rc.1611 - 7.0.0-rc.1611 - 7.0.0-rc.1611 - 7.0.0-rc.1611 - 7.0.0-rc.1611 - 7.0.0-rc.1611 - 7.0.0-rc.1611 - 7.0.0-rc.1611 - 10.0.0 - 2.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 - 10.0.0 + 10.0.0-preview.25515.110 + 10.0.0-rc.1.25515.110 + 18.1.0-preview-25515-110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-beta.25515.110 + 10.0.0-beta.25515.110 + 10.0.0-beta.25515.110 + 10.0.0-beta.25515.110 + 10.0.0-beta.25515.110 + 11.0.100-alpha.25515.110 + 11.0.100-alpha.25515.110 + 11.0.100-alpha.25515.110 + 11.0.100-alpha.25515.110 + 11.0.100-alpha.25515.110 + 11.0.100-alpha.25515.110 + 11.0.100-alpha.25515.110 + 11.0.100-alpha.25515.110 + 11.0.100-alpha.25515.110 + 18.1.0-preview-25515-110 + 18.1.0-preview-25515-110 + 3.2.0-preview.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 2.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 + 10.0.0-rc.1.25515.110 2.1.0 + + 7.0.0-preview.2.256 + 7.0.0-preview.2.256 + 7.0.0-preview.2.256 + 7.0.0-preview.2.256 + 7.0.0-preview.2.256 + 7.0.0-preview.2.256 + 7.0.0-preview.2.256 + 7.0.0-preview.2.256 + 7.0.0-preview.2.256 + 7.0.0-preview.2.256 + 7.0.0-preview.2.256 + 7.0.0-preview.2.256 + 7.0.0-preview.2.256 + 7.0.0-preview.2.256 + 7.0.0-preview.2.256 + 7.0.0-preview.2.256 + 7.0.0-preview.2.256 - 2.1.0-preview.25520.2 - 4.1.0-preview.25520.2 + 2.1.0-preview.25553.3 + 4.1.0-preview.25553.3 - + $(dotnetdevcertsPackageVersion) $(dotnetuserjwtsPackageVersion) $(dotnetusersecretsPackageVersion) @@ -173,7 +174,6 @@ This file should be imported by eng/Versions.props $(MicrosoftBclAsyncInterfacesPackageVersion) $(MicrosoftBuildPackageVersion) $(MicrosoftBuildLocalizationPackageVersion) - $(MicrosoftBuildNuGetSdkResolverPackageVersion) $(MicrosoftBuildTasksGitPackageVersion) $(MicrosoftCodeAnalysisPackageVersion) $(MicrosoftCodeAnalysisBuildClientPackageVersion) @@ -187,16 +187,8 @@ This file should be imported by eng/Versions.props $(MicrosoftCodeAnalysisWorkspacesMSBuildPackageVersion) $(MicrosoftDeploymentDotNetReleasesPackageVersion) $(MicrosoftDiaSymReaderPackageVersion) - $(MicrosoftDotNetArcadeSdkPackageVersion) - $(MicrosoftDotNetBuildTasksInstallersPackageVersion) - $(MicrosoftDotNetBuildTasksTemplatingPackageVersion) - $(MicrosoftDotNetBuildTasksWorkloadsPackageVersion) - $(MicrosoftDotNetHelixSdkPackageVersion) - $(MicrosoftDotNetSignToolPackageVersion) $(MicrosoftDotNetWebItemTemplates100PackageVersion) $(MicrosoftDotNetWebProjectTemplates100PackageVersion) - $(MicrosoftDotNetXliffTasksPackageVersion) - $(MicrosoftDotNetXUnitExtensionsPackageVersion) $(MicrosoftExtensionsConfigurationIniPackageVersion) $(MicrosoftExtensionsDependencyModelPackageVersion) $(MicrosoftExtensionsFileProvidersAbstractionsPackageVersion) @@ -213,7 +205,6 @@ This file should be imported by eng/Versions.props $(MicrosoftNETHostModelPackageVersion) $(MicrosoftNETILLinkTasksPackageVersion) $(MicrosoftNETRuntimeEmscripten3156Cachewinx64PackageVersion) - $(MicrosoftNETRuntimeEmscriptenSdkInternalPackageVersion) $(MicrosoftNETSdkRazorSourceGeneratorsTransportPackageVersion) $(MicrosoftNETSdkWindowsDesktopPackageVersion) $(MicrosoftNETTestSdkPackageVersion) @@ -239,22 +230,6 @@ This file should be imported by eng/Versions.props $(MicrosoftWin32SystemEventsPackageVersion) $(MicrosoftWindowsDesktopAppInternalPackageVersion) $(MicrosoftWindowsDesktopAppRefPackageVersion) - $(NuGetBuildTasksPackageVersion) - $(NuGetBuildTasksConsolePackageVersion) - $(NuGetBuildTasksPackPackageVersion) - $(NuGetCommandLineXPlatPackageVersion) - $(NuGetCommandsPackageVersion) - $(NuGetCommonPackageVersion) - $(NuGetConfigurationPackageVersion) - $(NuGetCredentialsPackageVersion) - $(NuGetDependencyResolverCorePackageVersion) - $(NuGetFrameworksPackageVersion) - $(NuGetLibraryModelPackageVersion) - $(NuGetLocalizationPackageVersion) - $(NuGetPackagingPackageVersion) - $(NuGetProjectModelPackageVersion) - $(NuGetProtocolPackageVersion) - $(NuGetVersioningPackageVersion) $(SystemCodeDomPackageVersion) $(SystemCommandLinePackageVersion) $(SystemComponentModelCompositionPackageVersion) @@ -279,6 +254,24 @@ This file should be imported by eng/Versions.props $(SystemWindowsExtensionsPackageVersion) $(NETStandardLibraryRefPackageVersion) + + $(MicrosoftBuildNuGetSdkResolverPackageVersion) + $(NuGetBuildTasksPackageVersion) + $(NuGetBuildTasksConsolePackageVersion) + $(NuGetBuildTasksPackPackageVersion) + $(NuGetCommandLineXPlatPackageVersion) + $(NuGetCommandsPackageVersion) + $(NuGetCommonPackageVersion) + $(NuGetConfigurationPackageVersion) + $(NuGetCredentialsPackageVersion) + $(NuGetDependencyResolverCorePackageVersion) + $(NuGetFrameworksPackageVersion) + $(NuGetLibraryModelPackageVersion) + $(NuGetLocalizationPackageVersion) + $(NuGetPackagingPackageVersion) + $(NuGetProjectModelPackageVersion) + $(NuGetProtocolPackageVersion) + $(NuGetVersioningPackageVersion) $(MicrosoftTestingPlatformPackageVersion) $(MSTestPackageVersion) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index d4f121c19936..4a369e67ceb2 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,62 +1,62 @@ - + - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 @@ -68,170 +68,170 @@ https://github.com/dotnet/dotnet 6a953e76162f3f079405f80e28664fa51b136740 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - - https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + + https://github.com/nuget/nuget.client + 97c64b5dfcc39b3babf6b3dfa828aa737860c145 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 - + https://github.com/microsoft/testfx - d4c42dc54c6dd64f61c41349180ab19b621a9895 + b2b75ecb52bcff592001df6d08f5fffccd945fdd - + https://github.com/microsoft/testfx - d4c42dc54c6dd64f61c41349180ab19b621a9895 + b2b75ecb52bcff592001df6d08f5fffccd945fdd - + https://github.com/dotnet/dotnet - 79c85d969a02abd06c2202949318fd4c21e5e7a0 + be28ec777bf12db631725399c442448d52093087 diff --git a/eng/Versions.props b/eng/Versions.props index cfd965a85e6d..9cc9ebe8ba88 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -6,7 +6,7 @@ 10 0 - 1 + 2 00 - rtm + preview rtm servicing - + 0 @@ -38,8 +38,8 @@ 36 20 - $([MSBuild]::Add($(VersionFeature), 22)) - $([MSBuild]::Add($(VersionFeature), 11)) + 21 + 10 <_NET70ILLinkPackVersion>7.0.100-1.23211.1 @@ -88,6 +88,7 @@ 9.0.0 9.0.0 9.0.0 + 8.0.0 9.0.0 4.5.4 8.0.0 diff --git a/eng/common/SetupNugetSources.sh b/eng/common/SetupNugetSources.sh index b97cc536379d..dd2564aef012 100755 --- a/eng/common/SetupNugetSources.sh +++ b/eng/common/SetupNugetSources.sh @@ -66,8 +66,10 @@ EnableInternalPackageSource() { grep -i " /dev/null if [ "$?" == "0" ]; then echo "Enabling internal source '$PackageSourceName'." - # Remove the disabled entry (including any surrounding comments or whitespace on the same line) - sed -i.bak "//d" "$ConfigFile" + # Remove the disabled entry + local OldDisableValue="" + local NewDisableValue="" + sed -i.bak "s|$OldDisableValue|$NewDisableValue|" "$ConfigFile" # Add the source name to PackageSources for credential handling PackageSources+=("$PackageSourceName") diff --git a/eng/common/build.sh b/eng/common/build.sh index 9767bb411a4f..ec3e80d189ea 100755 --- a/eng/common/build.sh +++ b/eng/common/build.sh @@ -92,7 +92,7 @@ runtime_source_feed='' runtime_source_feed_key='' properties=() -while [[ $# > 0 ]]; do +while [[ $# -gt 0 ]]; do opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -help|-h) diff --git a/eng/common/core-templates/job/job.yml b/eng/common/core-templates/job/job.yml index 5ce518406198..cb4ccc023a33 100644 --- a/eng/common/core-templates/job/job.yml +++ b/eng/common/core-templates/job/job.yml @@ -19,6 +19,8 @@ parameters: # publishing defaults artifacts: '' enableMicrobuild: false + enablePreviewMicrobuild: false + microbuildPluginVersion: 'latest' enableMicrobuildForMacAndLinux: false microbuildUseESRP: true enablePublishBuildArtifacts: false @@ -128,6 +130,8 @@ jobs: - template: /eng/common/core-templates/steps/install-microbuild.yml parameters: enableMicrobuild: ${{ parameters.enableMicrobuild }} + enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }} + microbuildPluginVersion: ${{ parameters.microbuildPluginVersion }} enableMicrobuildForMacAndLinux: ${{ parameters.enableMicrobuildForMacAndLinux }} microbuildUseESRP: ${{ parameters.microbuildUseESRP }} continueOnError: ${{ parameters.continueOnError }} @@ -153,6 +157,8 @@ jobs: - template: /eng/common/core-templates/steps/cleanup-microbuild.yml parameters: enableMicrobuild: ${{ parameters.enableMicrobuild }} + enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }} + microbuildPluginVersion: ${{ parameters.microbuildPluginVersion }} enableMicrobuildForMacAndLinux: ${{ parameters.enableMicrobuildForMacAndLinux }} continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/common/core-templates/steps/generate-sbom.yml b/eng/common/core-templates/steps/generate-sbom.yml index c05f65027979..003f7eae0fa5 100644 --- a/eng/common/core-templates/steps/generate-sbom.yml +++ b/eng/common/core-templates/steps/generate-sbom.yml @@ -5,7 +5,7 @@ # IgnoreDirectories - Directories to ignore for SBOM generation. This will be passed through to the CG component detector. parameters: - PackageVersion: 10.0.0 + PackageVersion: 11.0.0 BuildDropPath: '$(System.DefaultWorkingDirectory)/artifacts' PackageName: '.NET' ManifestDirPath: $(Build.ArtifactStagingDirectory)/sbom diff --git a/eng/common/core-templates/steps/install-microbuild-impl.yml b/eng/common/core-templates/steps/install-microbuild-impl.yml new file mode 100644 index 000000000000..9fdf3a116774 --- /dev/null +++ b/eng/common/core-templates/steps/install-microbuild-impl.yml @@ -0,0 +1,34 @@ +parameters: + - name: microbuildTaskInputs + type: object + default: {} + + - name: microbuildEnv + type: object + default: {} + + - name: enablePreviewMicrobuild + type: boolean + default: false + + - name: condition + type: string + + - name: continueOnError + type: boolean + +steps: +- ${{ if eq(parameters.enablePreviewMicrobuild, 'true') }}: + - task: MicroBuildSigningPluginPreview@4 + displayName: Install Preview MicroBuild plugin (Windows) + inputs: ${{ parameters.microbuildTaskInputs }} + env: ${{ parameters.microbuildEnv }} + continueOnError: ${{ parameters.continueOnError }} + condition: ${{ parameters.condition }} +- ${{ else }}: + - task: MicroBuildSigningPlugin@4 + displayName: Install MicroBuild plugin (Windows) + inputs: ${{ parameters.microbuildTaskInputs }} + env: ${{ parameters.microbuildEnv }} + continueOnError: ${{ parameters.continueOnError }} + condition: ${{ parameters.condition }} \ No newline at end of file diff --git a/eng/common/core-templates/steps/install-microbuild.yml b/eng/common/core-templates/steps/install-microbuild.yml index d6b9878f54db..54eac9a21ed7 100644 --- a/eng/common/core-templates/steps/install-microbuild.yml +++ b/eng/common/core-templates/steps/install-microbuild.yml @@ -4,6 +4,8 @@ parameters: # Enable install tasks for MicroBuild on Mac and Linux # Will be ignored if 'enableMicrobuild' is false or 'Agent.Os' is 'Windows_NT' enableMicrobuildForMacAndLinux: false + # Enable preview version of MB signing plugin + enablePreviewMicrobuild: false # Determines whether the ESRP service connection information should be passed to the signing plugin. # This overlaps with _SignType to some degree. We only need the service connection for real signing. # It's important that the service connection not be passed to the MicroBuildSigningPlugin task in this place. @@ -14,20 +16,24 @@ parameters: # Location of the MicroBuild output folder # NOTE: There's something that relies on this being in the "default" source directory for tasks such as Signing to work properly. microBuildOutputFolder: '$(Build.SourcesDirectory)' + # Microbuild version + microbuildPluginVersion: 'latest' continueOnError: false steps: - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - ${{ if eq(parameters.enableMicrobuildForMacAndLinux, 'true') }}: - # Needed to download the MicroBuild plugin nupkgs on Mac and Linux when nuget.exe is unavailable + # Installing .NET 8 is required to use the MicroBuild signing plugin on non-Windows platforms - task: UseDotNet@2 displayName: Install .NET 8.0 SDK for MicroBuild Plugin inputs: packageType: sdk version: 8.0.x - installationPath: ${{ parameters.microBuildOutputFolder }}/.dotnet - workingDirectory: ${{ parameters.microBuildOutputFolder }} + # Installing the SDK in a '.dotnet-microbuild' directory is required for signing. + # See target FindDotNetPathForMicroBuild in arcade/src/Microsoft.DotNet.Arcade.Sdk/tools/Sign.proj + # Do not remove '.dotnet-microbuild' from the path without changing the corresponding logic. + installationPath: $(Agent.TempDirectory)/.dotnet-microbuild condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) - script: | @@ -51,41 +57,45 @@ steps: # YAML expansion, and Windows vs. Linux/Mac uses different service connections. However, # we can avoid including the MB install step if not enabled at all. This avoids a bunch of # extra pipeline authorizations, since most pipelines do not sign on non-Windows. - - task: MicroBuildSigningPlugin@4 - displayName: Install MicroBuild plugin (Windows) - inputs: - signType: $(_SignType) - zipSources: false - feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json - ${{ if eq(parameters.microbuildUseESRP, true) }}: - ConnectedServiceName: 'MicroBuild Signing Task (DevDiv)' - ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - ConnectedPMEServiceName: 6cc74545-d7b9-4050-9dfa-ebefcc8961ea - ${{ else }}: - ConnectedPMEServiceName: 248d384a-b39b-46e3-8ad5-c2c210d5e7ca - env: - TeamName: $(_TeamName) - MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }} - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - continueOnError: ${{ parameters.continueOnError }} - condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT'), in(variables['_SignType'], 'real', 'test')) - - - ${{ if eq(parameters.enableMicrobuildForMacAndLinux, true) }}: - - task: MicroBuildSigningPlugin@4 - displayName: Install MicroBuild plugin (non-Windows) - inputs: + - template: /eng/common/core-templates/steps/install-microbuild-impl.yml@self + parameters: + enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }} + microbuildTaskInputs: signType: $(_SignType) zipSources: false feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json + version: ${{ parameters.microbuildPluginVersion }} ${{ if eq(parameters.microbuildUseESRP, true) }}: ConnectedServiceName: 'MicroBuild Signing Task (DevDiv)' ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - ConnectedPMEServiceName: beb8cb23-b303-4c95-ab26-9e44bc958d39 + ConnectedPMEServiceName: 6cc74545-d7b9-4050-9dfa-ebefcc8961ea ${{ else }}: - ConnectedPMEServiceName: c24de2a5-cc7a-493d-95e4-8e5ff5cad2bc - env: + ConnectedPMEServiceName: 248d384a-b39b-46e3-8ad5-c2c210d5e7ca + microbuildEnv: TeamName: $(_TeamName) MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) continueOnError: ${{ parameters.continueOnError }} - condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT'), eq(variables['_SignType'], 'real')) + condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT'), in(variables['_SignType'], 'real', 'test')) + + - ${{ if eq(parameters.enableMicrobuildForMacAndLinux, true) }}: + - template: /eng/common/core-templates/steps/install-microbuild-impl.yml@self + parameters: + enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }} + microbuildTaskInputs: + signType: $(_SignType) + zipSources: false + feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json + version: ${{ parameters.microbuildPluginVersion }} + ${{ if eq(parameters.microbuildUseESRP, true) }}: + ConnectedServiceName: 'MicroBuild Signing Task (DevDiv)' + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + ConnectedPMEServiceName: beb8cb23-b303-4c95-ab26-9e44bc958d39 + ${{ else }}: + ConnectedPMEServiceName: c24de2a5-cc7a-493d-95e4-8e5ff5cad2bc + microbuildEnv: + TeamName: $(_TeamName) + MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT'), eq(variables['_SignType'], 'real')) diff --git a/eng/common/core-templates/steps/source-index-stage1-publish.yml b/eng/common/core-templates/steps/source-index-stage1-publish.yml index e9a694afa58e..eff4573c6e5f 100644 --- a/eng/common/core-templates/steps/source-index-stage1-publish.yml +++ b/eng/common/core-templates/steps/source-index-stage1-publish.yml @@ -1,6 +1,6 @@ parameters: - sourceIndexUploadPackageVersion: 2.0.0-20250818.1 - sourceIndexProcessBinlogPackageVersion: 1.0.1-20250818.1 + sourceIndexUploadPackageVersion: 2.0.0-20250906.1 + sourceIndexProcessBinlogPackageVersion: 1.0.1-20250906.1 sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json binlogPath: artifacts/log/Debug/Build.binlog diff --git a/eng/common/darc-init.sh b/eng/common/darc-init.sh index e889f439b8dc..9f5ad6b763b5 100755 --- a/eng/common/darc-init.sh +++ b/eng/common/darc-init.sh @@ -5,7 +5,7 @@ darcVersion='' versionEndpoint='https://maestro.dot.net/api/assets/darc-version?api-version=2020-02-20' verbosity='minimal' -while [[ $# > 0 ]]; do +while [[ $# -gt 0 ]]; do opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in --darcversion) diff --git a/eng/common/dotnet-install.sh b/eng/common/dotnet-install.sh index 7b9d97e3bd4d..61f302bb6775 100755 --- a/eng/common/dotnet-install.sh +++ b/eng/common/dotnet-install.sh @@ -18,7 +18,7 @@ architecture='' runtime='dotnet' runtimeSourceFeed='' runtimeSourceFeedKey='' -while [[ $# > 0 ]]; do +while [[ $# -gt 0 ]]; do opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in -version|-v) diff --git a/eng/common/dotnet.sh b/eng/common/dotnet.sh index 2ef68235675f..f6d24871c1d4 100755 --- a/eng/common/dotnet.sh +++ b/eng/common/dotnet.sh @@ -19,7 +19,7 @@ source $scriptroot/tools.sh InitializeDotNetCli true # install # Invoke acquired SDK with args if they are provided -if [[ $# > 0 ]]; then +if [[ $# -gt 0 ]]; then __dotnetDir=${_InitializeDotNetCli} dotnetPath=${__dotnetDir}/dotnet ${dotnetPath} "$@" diff --git a/eng/common/internal-feed-operations.sh b/eng/common/internal-feed-operations.sh index 9378223ba095..6299e7effd4c 100755 --- a/eng/common/internal-feed-operations.sh +++ b/eng/common/internal-feed-operations.sh @@ -100,7 +100,7 @@ operation='' authToken='' repoName='' -while [[ $# > 0 ]]; do +while [[ $# -gt 0 ]]; do opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in --operation) diff --git a/eng/common/native/install-dependencies.sh b/eng/common/native/install-dependencies.sh index 477a44f335be..f7bd4af0c8db 100755 --- a/eng/common/native/install-dependencies.sh +++ b/eng/common/native/install-dependencies.sh @@ -30,6 +30,8 @@ case "$os" in elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ] || [ "$ID" = "azurelinux" ]; then pkg_mgr="$(command -v tdnf 2>/dev/null || command -v dnf)" $pkg_mgr install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio + elif [ "$ID" = "amzn" ]; then + dnf install -y cmake llvm lld lldb clang python libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio elif [ "$ID" = "alpine" ]; then apk add build-base cmake bash curl clang llvm-dev lld lldb krb5-dev lttng-ust-dev icu-dev openssl-dev pigz cpio else diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1 index b62e132d32a4..4655af7a2d88 100644 --- a/eng/common/sdk-task.ps1 +++ b/eng/common/sdk-task.ps1 @@ -68,7 +68,7 @@ try { $GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty } if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) { - $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.13.0" -MemberType NoteProperty + $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.14.16" -MemberType NoteProperty } if ($GlobalJson.tools."xcopy-msbuild".Trim() -ine "none") { $xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index 06b44de78709..4bc50bd568ca 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -394,8 +394,8 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = # If the version of msbuild is going to be xcopied, # use this version. Version matches a package here: - # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/Microsoft.DotNet.Arcade.MSBuild.Xcopy/versions/17.13.0 - $defaultXCopyMSBuildVersion = '17.13.0' + # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/Microsoft.DotNet.Arcade.MSBuild.Xcopy/versions/17.14.16 + $defaultXCopyMSBuildVersion = '17.14.16' if (!$vsRequirements) { if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { diff --git a/eng/dogfood.ps1 b/eng/dogfood.ps1 index e7f97d313192..7330e8449307 100644 --- a/eng/dogfood.ps1 +++ b/eng/dogfood.ps1 @@ -26,7 +26,7 @@ function Print-Usage() { Write-Host "if it is set, will be used." } -function Global:prompt {"(dogfood) PS $PWD> "} +function Global:prompt { "(dogfood) PS $PWD> " } if ($help -or (($command -ne $null) -and ($command.Contains("/help") -or $command.Contains("/?")))) { Print-Usage @@ -35,7 +35,7 @@ if ($help -or (($command -ne $null) -and ($command.Contains("/help") -or $comman try { $toolsetBuildProj = InitializeToolset - . $PSScriptroot\restore-toolset.ps1 + . $PSScriptRoot\restore-toolset.ps1 $env:SDK_REPO_ROOT = $RepoRoot @@ -62,6 +62,7 @@ try { $Host.UI.RawUI.WindowTitle = "SDK Test ($RepoRoot) ($configuration)" & $command[0] $command[1..($command.Length-1)] } + Set-Location "$PSScriptRoot\..\artifacts\tmp\Debug\testing" } catch { Write-Host $_ diff --git a/eng/dogfood.sh b/eng/dogfood.sh index 4aca25a7c757..fb3273b9055c 100755 --- a/eng/dogfood.sh +++ b/eng/dogfood.sh @@ -27,4 +27,5 @@ export PATH=$testDotnetRoot:$PATH export DOTNET_ROOT=$testDotnetRoot export DOTNET_ADD_GLOBAL_TOOLS_TO_PATH=0 export PS1="(dogfood) $PS1" -export DOTNET_SYSTEM_NET_SECURITY_NOREVOCATIONCHECKBYDEFAULT="true" \ No newline at end of file +export DOTNET_SYSTEM_NET_SECURITY_NOREVOCATIONCHECKBYDEFAULT="true" +cd "$scriptroot/../artifacts/tmp/Debug/testing" \ No newline at end of file diff --git a/eng/dotnet-format/dotnet-format-integration.yml b/eng/dotnet-format/dotnet-format-integration.yml index 25c17948bf59..77b3baec5db9 100644 --- a/eng/dotnet-format/dotnet-format-integration.yml +++ b/eng/dotnet-format/dotnet-format-integration.yml @@ -50,13 +50,6 @@ parameters: _branchName: "main" _sha: "1b2ff365399ab6736a9ea4c98ab1b60acda5d917" _useParentSdk: 0 - - Name: razor_tooling - _repo: "https://github.com/dotnet/razor" - _repoName: "dotnet/razor" - _targetSolution: "Razor.sln" - _branchName: "main" - _sha: "ecb4b595e3322a18c240f50a763868540f51eaaa" - _useParentSdk: 0 - name: timeoutInMinutes type: number diff --git a/github-merge-flow.jsonc b/github-merge-flow.jsonc index d7a5b4ef056d..2b61aa4e815e 100644 --- a/github-merge-flow.jsonc +++ b/github-merge-flow.jsonc @@ -21,10 +21,25 @@ "MergeToBranch": "release/9.0.3xx", "ExtraSwitches": "-QuietComments" }, - // Automate opening PRs to merge sdk repos from release/9.0.3xx to main + // Automate opening PRs to merge sdk repos from release/9.0.3xx to release/10.0.1xx "release/9.0.3xx":{ + "MergeToBranch": "release/10.0.1xx", + "ExtraSwitches": "-QuietComments" + }, + // Automate opening PRs to merge sdk repos from release/10.0.1xx to release/10.0.2xx + "release/10.0.1xx":{ + "MergeToBranch": "release/10.0.2xx", + "ExtraSwitches": "-QuietComments" + }, + // Automate opening PRs to merge sdk repos from release/10.0.2xx to main + "release/10.0.2xx":{ "MergeToBranch": "main", "ExtraSwitches": "-QuietComments" + }, + // Automate opening PRs to merge sdk repos from dnup to release/dnup + "dnup":{ + "MergeToBranch": "release/dnup", + "ExtraSwitches": "-QuietComments" } } } diff --git a/global.json b/global.json index 9021a562cc03..3d11cb5599a3 100644 --- a/global.json +++ b/global.json @@ -21,8 +21,8 @@ } }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25515.111", - "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.25515.111", + "Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.25515.110", + "Microsoft.DotNet.Helix.Sdk": "11.0.0-beta.25515.110", "Microsoft.Build.NoTargets": "3.7.0", "Microsoft.Build.Traversal": "3.4.0", "Microsoft.WixToolset.Sdk": "5.0.2-dotnet.2811440" diff --git a/sdk.slnx b/sdk.slnx index dc0633c42d02..c8592eeb1f7b 100644 --- a/sdk.slnx +++ b/sdk.slnx @@ -24,7 +24,6 @@ - @@ -49,18 +48,24 @@ + + - + + + + + @@ -333,7 +338,6 @@ - diff --git a/src/BlazorWasmSdk/Targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.props b/src/BlazorWasmSdk/Targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.props index d7acba0e5779..7efe6d02c27e 100644 --- a/src/BlazorWasmSdk/Targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.props +++ b/src/BlazorWasmSdk/Targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.props @@ -9,11 +9,12 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and Copyright (c) .NET Foundation. All rights reserved. *********************************************************************************************** --> - + exe browser-wasm + $(RuntimeIdentifier) false false diff --git a/src/BuiltInTools/BrowserRefresh/.editorconfig b/src/BuiltInTools/BrowserRefresh/.editorconfig deleted file mode 100644 index 4ae90dd7a7a0..000000000000 --- a/src/BuiltInTools/BrowserRefresh/.editorconfig +++ /dev/null @@ -1,2 +0,0 @@ -[*.js] -indent_size = 2 diff --git a/src/BuiltInTools/BrowserRefresh/Microsoft.AspNetCore.Watch.BrowserRefresh.csproj b/src/BuiltInTools/BrowserRefresh/Microsoft.AspNetCore.Watch.BrowserRefresh.csproj index 84852c4df9a9..4aeac077f070 100644 --- a/src/BuiltInTools/BrowserRefresh/Microsoft.AspNetCore.Watch.BrowserRefresh.csproj +++ b/src/BuiltInTools/BrowserRefresh/Microsoft.AspNetCore.Watch.BrowserRefresh.csproj @@ -5,23 +5,13 @@ When updating the TFM also update minimal supported version in dotnet-watch.csproj and WebApplicationAppModel.cs. --> net6.0 - MicrosoftAspNetCore - - - true - true - true - Microsoft.DotNet.HotReload.Web.Middleware - Package containing Hot Reload middleware. true + + MicrosoftAspNetCore - - - - @@ -34,4 +24,11 @@ + + + + + %(FileName)%(Extension) + + diff --git a/src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj b/src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj index 5172480c5f98..87a397c3dd15 100644 --- a/src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj +++ b/src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj @@ -1,9 +1,4 @@  - - - - - net6.0;net10.0 MicrosoftAspNetCore - - - true - true - true - Microsoft.DotNet.HotReload.Agent.Host - Package containing Hot Reload agent host. - true - - - + + + + diff --git a/src/BuiltInTools/HotReloadAgent.Host/.editorconfig b/src/BuiltInTools/HotReloadAgent.Host/.editorconfig new file mode 100644 index 000000000000..4694508c4da5 --- /dev/null +++ b/src/BuiltInTools/HotReloadAgent.Host/.editorconfig @@ -0,0 +1,6 @@ +[*.cs] + +# IDE0240: Remove redundant nullable directive +# The directive needs to be included since all sources in a source package are considered generated code +# when referenced from a project via package reference. +dotnet_diagnostic.IDE0240.severity = none \ No newline at end of file diff --git a/src/BuiltInTools/HotReloadAgent.Host/Microsoft.DotNet.HotReload.Agent.Host.Package.csproj b/src/BuiltInTools/HotReloadAgent.Host/Microsoft.DotNet.HotReload.Agent.Host.Package.csproj new file mode 100644 index 000000000000..b5630dcc8e6e --- /dev/null +++ b/src/BuiltInTools/HotReloadAgent.Host/Microsoft.DotNet.HotReload.Agent.Host.Package.csproj @@ -0,0 +1,42 @@ + + + + + net6.0;net10.0 + true + + false + none + false + preview + + + true + true + true + Microsoft.DotNet.HotReload.Agent.Host + false + Package containing Hot Reload agent host. + + $(NoWarn);NU5128 + + + + + + + + + + + + + + + + diff --git a/src/BuiltInTools/HotReloadAgent.Host/Microsoft.DotNet.HotReload.Agent.Host.projitems b/src/BuiltInTools/HotReloadAgent.Host/Microsoft.DotNet.HotReload.Agent.Host.projitems new file mode 100644 index 000000000000..c4cd6379e476 --- /dev/null +++ b/src/BuiltInTools/HotReloadAgent.Host/Microsoft.DotNet.HotReload.Agent.Host.projitems @@ -0,0 +1,14 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 15C936B2-901D-4A27-AFA6-89CF56F5EB20 + + + Microsoft.DotNet.HotReload + + + + + \ No newline at end of file diff --git a/src/BuiltInTools/HotReloadAgent.Host/Microsoft.DotNet.HotReload.Agent.Host.shproj b/src/BuiltInTools/HotReloadAgent.Host/Microsoft.DotNet.HotReload.Agent.Host.shproj new file mode 100644 index 000000000000..771b511f6b46 --- /dev/null +++ b/src/BuiltInTools/HotReloadAgent.Host/Microsoft.DotNet.HotReload.Agent.Host.shproj @@ -0,0 +1,13 @@ + + + + 15C936B2-901D-4A27-AFA6-89CF56F5EB20 + 14.0 + + + + + + + + \ No newline at end of file diff --git a/src/BuiltInTools/DotNetDeltaApplier/PipeListener.cs b/src/BuiltInTools/HotReloadAgent.Host/PipeListener.cs similarity index 98% rename from src/BuiltInTools/DotNetDeltaApplier/PipeListener.cs rename to src/BuiltInTools/HotReloadAgent.Host/PipeListener.cs index 6110f0fa631c..dfa108189dff 100644 --- a/src/BuiltInTools/DotNetDeltaApplier/PipeListener.cs +++ b/src/BuiltInTools/HotReloadAgent.Host/PipeListener.cs @@ -1,10 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + +using System; using System.Diagnostics; using System.IO.Pipes; using System.Reflection; using System.Runtime.Loader; +using System.Threading; +using System.Threading.Tasks; namespace Microsoft.DotNet.HotReload; diff --git a/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs b/src/BuiltInTools/HotReloadAgent.Host/StartupHook.cs similarity index 76% rename from src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs rename to src/BuiltInTools/HotReloadAgent.Host/StartupHook.cs index 03c7b04a4fea..8c55314f2d0f 100644 --- a/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs +++ b/src/BuiltInTools/HotReloadAgent.Host/StartupHook.cs @@ -1,12 +1,19 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + +using System; using System.Diagnostics; +using System.IO; using System.IO.Pipes; +using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Runtime.Loader; +using System.Threading; +using System.Threading.Tasks; using Microsoft.DotNet.HotReload; -using Microsoft.DotNet.Watch; /// /// The runtime startup hook looks for top-level type named "StartupHook". @@ -92,7 +99,27 @@ private static void RegisterSignalHandlers() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - ProcessUtilities.EnableWindowsCtrlCHandling(Log); + // Enables handling of Ctrl+C in a process where it was disabled. + // + // If a process is launched with CREATE_NEW_PROCESS_GROUP flag + // it allows the parent process to send Ctrl+C event to the child process, + // but also disables Ctrl+C handlers. + // + // "If the HandlerRoutine parameter is NULL, a TRUE value causes the calling process to ignore CTRL+C input, + // and a FALSE value restores normal processing of CTRL+C input. + // This attribute of ignoring or processing CTRL+C is inherited by child processes." + + if (SetConsoleCtrlHandler(null, false)) + { + Log("Windows Ctrl+C handling enabled."); + } + else + { + Log($"Failed to enable Ctrl+C handling: {GetLastPInvokeErrorMessage()}"); + } + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool SetConsoleCtrlHandler(Delegate? handler, bool add); } else { @@ -118,6 +145,16 @@ private static void RegisterSignalHandlers() } } + private static string GetLastPInvokeErrorMessage() + { + var error = Marshal.GetLastPInvokeError(); +#if NET10_0_OR_GREATER + return $"{Marshal.GetPInvokeErrorMessage(error)} (code {error})"; +#else + return $"error code {error}"; +#endif + } + private static void Log(string message) { var prefix = s_standardOutputLogPrefix; diff --git a/src/BuiltInTools/HotReloadClient/Web/AbstractBrowserRefreshServer.cs b/src/BuiltInTools/HotReloadClient/Web/AbstractBrowserRefreshServer.cs index d3608901433e..d39ea6c0f519 100644 --- a/src/BuiltInTools/HotReloadClient/Web/AbstractBrowserRefreshServer.cs +++ b/src/BuiltInTools/HotReloadClient/Web/AbstractBrowserRefreshServer.cs @@ -29,9 +29,6 @@ internal abstract class AbstractBrowserRefreshServer(string middlewareAssemblyPa { public const string ServerLogComponentName = "BrowserRefreshServer"; - private static readonly ReadOnlyMemory s_reloadMessage = Encoding.UTF8.GetBytes("Reload"); - private static readonly ReadOnlyMemory s_waitMessage = Encoding.UTF8.GetBytes("Wait"); - private static readonly ReadOnlyMemory s_pingMessage = Encoding.UTF8.GetBytes("""{ "type" : "Ping" }"""); private static readonly JsonSerializerOptions s_jsonSerializerOptions = new(JsonSerializerDefaults.Web); private readonly List _activeConnections = []; @@ -233,19 +230,15 @@ public ValueTask SendJsonMessageAsync(TValue value, CancellationToken ca public ValueTask SendReloadMessageAsync(CancellationToken cancellationToken) { logger.Log(LogEvents.ReloadingBrowser); - return SendAsync(s_reloadMessage, cancellationToken); + return SendAsync(JsonReloadRequest.Message, cancellationToken); } public ValueTask SendWaitMessageAsync(CancellationToken cancellationToken) { logger.Log(LogEvents.SendingWaitMessage); - return SendAsync(s_waitMessage, cancellationToken); + return SendAsync(JsonWaitRequest.Message, cancellationToken); } - // obsolete: to be removed - public ValueTask SendPingMessageAsync(CancellationToken cancellationToken) - => SendAsync(s_pingMessage, cancellationToken); - private ValueTask SendAsync(ReadOnlyMemory messageBytes, CancellationToken cancellationToken) => SendAndReceiveAsync(request: _ => messageBytes, response: null, cancellationToken); @@ -293,13 +286,13 @@ public async ValueTask SendAndReceiveAsync( public ValueTask RefreshBrowserAsync(CancellationToken cancellationToken) { logger.Log(LogEvents.RefreshingBrowser); - return SendJsonMessageAsync(new AspNetCoreHotReloadApplied(), cancellationToken); + return SendAsync(JsonRefreshBrowserRequest.Message, cancellationToken); } public ValueTask ReportCompilationErrorsInBrowserAsync(ImmutableArray compilationErrors, CancellationToken cancellationToken) { logger.Log(LogEvents.UpdatingDiagnostics); - return SendJsonMessageAsync(new HotReloadDiagnostics { Diagnostics = compilationErrors }, cancellationToken); + return SendJsonMessageAsync(new JsonReportDiagnosticsRequest { Diagnostics = compilationErrors }, cancellationToken); } public async ValueTask UpdateStaticAssetsAsync(IEnumerable relativeUrls, CancellationToken cancellationToken) @@ -308,24 +301,37 @@ public async ValueTask UpdateStaticAssetsAsync(IEnumerable relativeUrls, foreach (var relativeUrl in relativeUrls) { logger.Log(LogEvents.SendingStaticAssetUpdateRequest, relativeUrl); - var message = JsonSerializer.SerializeToUtf8Bytes(new UpdateStaticFileMessage { Path = relativeUrl }, s_jsonSerializerOptions); + var message = JsonSerializer.SerializeToUtf8Bytes(new JasonUpdateStaticFileRequest { Path = relativeUrl }, s_jsonSerializerOptions); await SendAsync(message, cancellationToken); } } - private readonly struct AspNetCoreHotReloadApplied + private readonly struct JsonWaitRequest + { + public string Type => "Wait"; + public static readonly ReadOnlyMemory Message = JsonSerializer.SerializeToUtf8Bytes(new JsonWaitRequest(), s_jsonSerializerOptions); + } + + private readonly struct JsonReloadRequest + { + public string Type => "Reload"; + public static readonly ReadOnlyMemory Message = JsonSerializer.SerializeToUtf8Bytes(new JsonReloadRequest(), s_jsonSerializerOptions); + } + + private readonly struct JsonRefreshBrowserRequest { - public string Type => "AspNetCoreHotReloadApplied"; + public string Type => "RefreshBrowser"; + public static readonly ReadOnlyMemory Message = JsonSerializer.SerializeToUtf8Bytes(new JsonRefreshBrowserRequest(), s_jsonSerializerOptions); } - private readonly struct HotReloadDiagnostics + private readonly struct JsonReportDiagnosticsRequest { - public string Type => "HotReloadDiagnosticsv1"; + public string Type => "ReportDiagnostics"; public IEnumerable Diagnostics { get; init; } } - private readonly struct UpdateStaticFileMessage + private readonly struct JasonUpdateStaticFileRequest { public string Type => "UpdateStaticFile"; public string Path { get; init; } diff --git a/src/BuiltInTools/HotReloadClient/Web/WebAssemblyHotReloadClient.cs b/src/BuiltInTools/HotReloadClient/Web/WebAssemblyHotReloadClient.cs index 2a45bfb07370..f47d6eb3a5ba 100644 --- a/src/BuiltInTools/HotReloadClient/Web/WebAssemblyHotReloadClient.cs +++ b/src/BuiltInTools/HotReloadClient/Web/WebAssemblyHotReloadClient.cs @@ -133,7 +133,7 @@ public override async Task ApplyManagedCodeUpdatesAsync(ImmutableAr var anyFailure = false; await browserRefreshServer.SendAndReceiveAsync( - request: sharedSecret => new JsonApplyHotReloadDeltasRequest + request: sharedSecret => new JsonApplyManagedCodeUpdatesRequest { SharedSecret = sharedSecret, UpdateId = batchId, @@ -178,9 +178,9 @@ private static bool ReceiveUpdateResponseAsync(ReadOnlySpan value, ILogger public override Task InitialUpdatesAppliedAsync(CancellationToken cancellationToken) => Task.CompletedTask; - private readonly struct JsonApplyHotReloadDeltasRequest + private readonly struct JsonApplyManagedCodeUpdatesRequest { - public string Type => "BlazorHotReloadDeltav3"; + public string Type => "ApplyManagedCodeUpdates"; public string? SharedSecret { get; init; } public int UpdateId { get; init; } @@ -211,7 +211,7 @@ private readonly struct JsonLogEntry private readonly struct JsonGetApplyUpdateCapabilitiesRequest { - public string Type => "BlazorRequestApplyUpdateCapabilities2"; + public string Type => "GetApplyUpdateCapabilities"; } } } diff --git a/src/BuiltInTools/Web.Middleware/.editorconfig b/src/BuiltInTools/Web.Middleware/.editorconfig new file mode 100644 index 000000000000..fb89bbbf5f84 --- /dev/null +++ b/src/BuiltInTools/Web.Middleware/.editorconfig @@ -0,0 +1,9 @@ +[*.cs] + +# IDE0240: Remove redundant nullable directive +# The directive needs to be included since all sources in a source package are considered generated code +# when referenced from a project via package reference. +dotnet_diagnostic.IDE0240.severity = none + +[*.js] +indent_size = 2 \ No newline at end of file diff --git a/src/BuiltInTools/BrowserRefresh/ApplicationPaths.cs b/src/BuiltInTools/Web.Middleware/ApplicationPaths.cs similarity index 99% rename from src/BuiltInTools/BrowserRefresh/ApplicationPaths.cs rename to src/BuiltInTools/Web.Middleware/ApplicationPaths.cs index 1d8ea6069bb5..6d784e60e117 100644 --- a/src/BuiltInTools/BrowserRefresh/ApplicationPaths.cs +++ b/src/BuiltInTools/Web.Middleware/ApplicationPaths.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Watch.BrowserRefresh diff --git a/src/BuiltInTools/BrowserRefresh/BlazorHotReload.js b/src/BuiltInTools/Web.Middleware/BlazorHotReload.js similarity index 100% rename from src/BuiltInTools/BrowserRefresh/BlazorHotReload.js rename to src/BuiltInTools/Web.Middleware/BlazorHotReload.js diff --git a/src/BuiltInTools/BrowserRefresh/BlazorWasmHotReloadMiddleware.cs b/src/BuiltInTools/Web.Middleware/BlazorWasmHotReloadMiddleware.cs similarity index 97% rename from src/BuiltInTools/BrowserRefresh/BlazorWasmHotReloadMiddleware.cs rename to src/BuiltInTools/Web.Middleware/BlazorWasmHotReloadMiddleware.cs index 6ccdd09f6bc3..036ae918f03a 100644 --- a/src/BuiltInTools/BrowserRefresh/BlazorWasmHotReloadMiddleware.cs +++ b/src/BuiltInTools/Web.Middleware/BlazorWasmHotReloadMiddleware.cs @@ -1,7 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + +using System.Collections.Generic; using System.Text.Json; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; diff --git a/src/BuiltInTools/BrowserRefresh/BrowserRefreshMiddleware.cs b/src/BuiltInTools/Web.Middleware/BrowserRefreshMiddleware.cs similarity index 97% rename from src/BuiltInTools/BrowserRefresh/BrowserRefreshMiddleware.cs rename to src/BuiltInTools/Web.Middleware/BrowserRefreshMiddleware.cs index 13fac3ceae8e..13612643de99 100644 --- a/src/BuiltInTools/BrowserRefresh/BrowserRefreshMiddleware.cs +++ b/src/BuiltInTools/Web.Middleware/BrowserRefreshMiddleware.cs @@ -1,6 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Logging; @@ -170,6 +176,8 @@ internal static bool IsBrowserDocumentRequest(HttpContext context) if (request.Headers.TryGetValue("Sec-Fetch-Dest", out var values) && !StringValues.IsNullOrEmpty(values) && !string.Equals(values[0], "document", StringComparison.OrdinalIgnoreCase) && + !string.Equals(values[0], "frame", StringComparison.OrdinalIgnoreCase) && + !string.Equals(values[0], "iframe", StringComparison.OrdinalIgnoreCase) && !IsProgressivelyEnhancedNavigation(context.Request)) { // See https://github.com/dotnet/aspnetcore/issues/37326. diff --git a/src/BuiltInTools/BrowserRefresh/BrowserScriptMiddleware.cs b/src/BuiltInTools/Web.Middleware/BrowserScriptMiddleware.cs similarity index 93% rename from src/BuiltInTools/BrowserRefresh/BrowserScriptMiddleware.cs rename to src/BuiltInTools/Web.Middleware/BrowserScriptMiddleware.cs index a9bffacdf930..bade75e4fedd 100644 --- a/src/BuiltInTools/BrowserRefresh/BrowserScriptMiddleware.cs +++ b/src/BuiltInTools/Web.Middleware/BrowserScriptMiddleware.cs @@ -1,7 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + +using System; using System.Globalization; +using System.IO; +using System.Text; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -42,7 +48,7 @@ public async Task InvokeAsync(HttpContext context) // for backwards compat only internal static ReadOnlyMemory GetBlazorHotReloadJS() { - var jsFileName = "Microsoft.AspNetCore.Watch.BrowserRefresh.BlazorHotReload.js"; + var jsFileName = "BlazorHotReload.js"; using var stream = new MemoryStream(); var manifestStream = typeof(BrowserScriptMiddleware).Assembly.GetManifestResourceStream(jsFileName)!; manifestStream.CopyTo(stream); @@ -60,7 +66,7 @@ internal static ReadOnlyMemory GetBrowserRefreshJS() internal static ReadOnlyMemory GetWebSocketClientJavaScript(string hostString, string serverKey) { - var jsFileName = "Microsoft.AspNetCore.Watch.BrowserRefresh.WebSocketScriptInjection.js"; + var jsFileName = "WebSocketScriptInjection.js"; using var reader = new StreamReader(typeof(BrowserScriptMiddleware).Assembly.GetManifestResourceStream(jsFileName)!); var script = reader.ReadToEnd() .Replace("{{hostString}}", hostString) diff --git a/src/BuiltInTools/BrowserRefresh/HostingStartup.cs b/src/BuiltInTools/Web.Middleware/HostingStartup.cs similarity index 97% rename from src/BuiltInTools/BrowserRefresh/HostingStartup.cs rename to src/BuiltInTools/Web.Middleware/HostingStartup.cs index 369133ccbec3..d9354ebdac34 100644 --- a/src/BuiltInTools/BrowserRefresh/HostingStartup.cs +++ b/src/BuiltInTools/Web.Middleware/HostingStartup.cs @@ -1,6 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + +using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; diff --git a/src/BuiltInTools/Web.Middleware/Microsoft.DotNet.HotReload.Web.Middleware.Package.csproj b/src/BuiltInTools/Web.Middleware/Microsoft.DotNet.HotReload.Web.Middleware.Package.csproj new file mode 100644 index 000000000000..5bdde90926bf --- /dev/null +++ b/src/BuiltInTools/Web.Middleware/Microsoft.DotNet.HotReload.Web.Middleware.Package.csproj @@ -0,0 +1,56 @@ + + + + + net6.0 + true + + false + none + false + preview + + + true + true + true + Microsoft.DotNet.HotReload.Web.Middleware + false + Package containing web server middleware to support Hot Reload. + + $(NoWarn);NU5128 + + + + + + + + + + + + + + + + + + + $(TargetsForTfmSpecificContentInPackage);_AddJSFilesToSourcePackage + + + + + + + <_File Include="$(MSBuildProjectDirectory)\**\*.js" TargetDir="contentFiles/cs/$(TargetFramework)" BuildAction="EmbeddedResource" /> + + + + diff --git a/src/BuiltInTools/Web.Middleware/Microsoft.DotNet.HotReload.Web.Middleware.projitems b/src/BuiltInTools/Web.Middleware/Microsoft.DotNet.HotReload.Web.Middleware.projitems new file mode 100644 index 000000000000..acee58a75952 --- /dev/null +++ b/src/BuiltInTools/Web.Middleware/Microsoft.DotNet.HotReload.Web.Middleware.projitems @@ -0,0 +1,15 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 9F3A6169-66C9-4244-8FC6-222E4D6F8D29 + + + Microsoft.DotNet.HotReload + + + + + + \ No newline at end of file diff --git a/src/BuiltInTools/Web.Middleware/Microsoft.DotNet.HotReload.Web.Middleware.shproj b/src/BuiltInTools/Web.Middleware/Microsoft.DotNet.HotReload.Web.Middleware.shproj new file mode 100644 index 000000000000..6d8a9e4077cb --- /dev/null +++ b/src/BuiltInTools/Web.Middleware/Microsoft.DotNet.HotReload.Web.Middleware.shproj @@ -0,0 +1,13 @@ + + + + 9F3A6169-66C9-4244-8FC6-222E4D6F8D29 + 14.0 + + + + + + + + \ No newline at end of file diff --git a/src/BuiltInTools/BrowserRefresh/ResponseStreamWrapper.cs b/src/BuiltInTools/Web.Middleware/ResponseStreamWrapper.cs similarity index 98% rename from src/BuiltInTools/BrowserRefresh/ResponseStreamWrapper.cs rename to src/BuiltInTools/Web.Middleware/ResponseStreamWrapper.cs index 16b2256e3f8d..9d2d7f93ad37 100644 --- a/src/BuiltInTools/BrowserRefresh/ResponseStreamWrapper.cs +++ b/src/BuiltInTools/Web.Middleware/ResponseStreamWrapper.cs @@ -1,9 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + +using System; using System.Diagnostics; +using System.IO; using System.IO.Compression; using System.IO.Pipelines; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; diff --git a/src/BuiltInTools/BrowserRefresh/ScriptInjectingStream.cs b/src/BuiltInTools/Web.Middleware/ScriptInjectingStream.cs similarity index 99% rename from src/BuiltInTools/BrowserRefresh/ScriptInjectingStream.cs rename to src/BuiltInTools/Web.Middleware/ScriptInjectingStream.cs index 7ba91142531f..477979e40de1 100644 --- a/src/BuiltInTools/BrowserRefresh/ScriptInjectingStream.cs +++ b/src/BuiltInTools/Web.Middleware/ScriptInjectingStream.cs @@ -1,8 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + +using System; using System.Diagnostics; +using System.IO; using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace Microsoft.AspNetCore.Watch.BrowserRefresh; diff --git a/src/BuiltInTools/BrowserRefresh/StartupHook.cs b/src/BuiltInTools/Web.Middleware/StartupHook.cs similarity index 93% rename from src/BuiltInTools/BrowserRefresh/StartupHook.cs rename to src/BuiltInTools/Web.Middleware/StartupHook.cs index 93be4f31fafa..01ae4ee90a00 100644 --- a/src/BuiltInTools/BrowserRefresh/StartupHook.cs +++ b/src/BuiltInTools/Web.Middleware/StartupHook.cs @@ -1,6 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + +using System; + internal class StartupHook { public static void Initialize() diff --git a/src/BuiltInTools/BrowserRefresh/WebSocketScriptInjection.js b/src/BuiltInTools/Web.Middleware/WebSocketScriptInjection.js similarity index 77% rename from src/BuiltInTools/BrowserRefresh/WebSocketScriptInjection.js rename to src/BuiltInTools/Web.Middleware/WebSocketScriptInjection.js index aa8f1fd60f98..44d6ae3250ad 100644 --- a/src/BuiltInTools/BrowserRefresh/WebSocketScriptInjection.js +++ b/src/BuiltInTools/Web.Middleware/WebSocketScriptInjection.js @@ -26,38 +26,22 @@ setTimeout(async function () { let waiting = false; - connection.onmessage = function (message) { - if (message.data === 'Reload') { - console.debug('Server is ready. Reloading...'); - location.reload(); - } else if (message.data === 'Wait') { - if (waiting) { - return; - } - waiting = true; - console.debug('File changes detected. Waiting for application to rebuild.'); - const glyphs = ['☱', '☲', '☴']; - const title = document.title; - let i = 0; - setInterval(function () { document.title = glyphs[i++ % glyphs.length] + ' ' + title; }, 240); + connection.onmessage = function (message) { + const payload = JSON.parse(message.data); + const action = { + 'Reload': () => reload(), + 'Wait': () => wait(), + 'UpdateStaticFile': () => updateStaticFile(payload.path), + 'ApplyManagedCodeUpdates': () => applyManagedCodeUpdates(payload.sharedSecret, payload.updateId, payload.deltas, payload.responseLoggingLevel), + 'ReportDiagnostics': () => reportDiagnostics(payload.diagnostics), + 'GetApplyUpdateCapabilities': () => getApplyUpdateCapabilities(), + 'RefreshBrowser': () => refreshBrowser() + }; + + if (payload.type && action.hasOwnProperty(payload.type)) { + action[payload.type](); } else { - const payload = JSON.parse(message.data); - const action = { - 'UpdateStaticFile': () => updateStaticFile(payload.path), - 'BlazorHotReloadDeltav1': () => applyBlazorDeltas_legacy(payload.sharedSecret, payload.deltas, false), - 'BlazorHotReloadDeltav2': () => applyBlazorDeltas_legacy(payload.sharedSecret, payload.deltas, true), - 'BlazorHotReloadDeltav3': () => applyBlazorDeltas(payload.sharedSecret, payload.updateId, payload.deltas, payload.responseLoggingLevel), - 'HotReloadDiagnosticsv1': () => displayDiagnostics(payload.diagnostics), - 'BlazorRequestApplyUpdateCapabilities': () => getBlazorWasmApplyUpdateCapabilities(false), - 'BlazorRequestApplyUpdateCapabilities2': () => getBlazorWasmApplyUpdateCapabilities(true), - 'AspNetCoreHotReloadApplied': () => aspnetCoreHotReloadApplied() - }; - - if (payload.type && action.hasOwnProperty(payload.type)) { - action[payload.type](); - } else { - console.error('Unknown payload:', message.data); - } + console.error('Unknown payload:', message.data); } } @@ -106,12 +90,12 @@ setTimeout(async function () { return messageAndStack } - function getBlazorWasmApplyUpdateCapabilities(sendErrorToClient) { + function getApplyUpdateCapabilities() { let applyUpdateCapabilities; try { applyUpdateCapabilities = window.Blazor._internal.getApplyUpdateCapabilities(); } catch (error) { - applyUpdateCapabilities = sendErrorToClient ? "!" + getMessageAndStack(error) : ''; + applyUpdateCapabilities = "!" + getMessageAndStack(error); } connection.send(applyUpdateCapabilities); } @@ -137,41 +121,6 @@ setTimeout(async function () { styleElement.parentNode.insertBefore(newElement, styleElement.nextSibling); } - async function applyBlazorDeltas_legacy(serverSecret, deltas, sendErrorToClient) { - if (sharedSecret && (serverSecret != sharedSecret.encodedSharedSecret)) { - // Validate the shared secret if it was specified. It might be unspecified in older versions of VS - // that do not support this feature as yet. - throw 'Unable to validate the server. Rejecting apply-update payload.'; - } - - let applyError = undefined; - - try { - applyDeltas_legacy(deltas) - } catch (error) { - console.warn(error); - applyError = error; - } - - const body = JSON.stringify({ - id: deltas[0].sequenceId, - deltas: deltas - }); - try { - await fetch('/_framework/blazor-hotreload', { method: 'post', headers: { 'content-type': 'application/json' }, body: body }); - } catch (error) { - console.warn(error); - applyError = error; - } - - if (applyError) { - sendDeltaNotApplied(sendErrorToClient ? applyError : undefined); - } else { - sendDeltaApplied(); - notifyHotReloadApplied(); - } - } - function applyDeltas_legacy(deltas) { let apply = window.Blazor?._internal?.applyHotReload @@ -190,26 +139,16 @@ setTimeout(async function () { }); } } - function sendDeltaApplied() { - connection.send(new Uint8Array([1]).buffer); - } - - function sendDeltaNotApplied(error) { - if (error) { - let encoder = new TextEncoder() - connection.send(encoder.encode("\0" + error.message + "\0" + error.stack)); - } else { - connection.send(new Uint8Array([0]).buffer); - } - } - async function applyBlazorDeltas(serverSecret, updateId, deltas, responseLoggingLevel) { + async function applyManagedCodeUpdates(serverSecret, updateId, deltas, responseLoggingLevel) { if (sharedSecret && (serverSecret != sharedSecret.encodedSharedSecret)) { // Validate the shared secret if it was specified. It might be unspecified in older versions of VS // that do not support this feature as yet. throw 'Unable to validate the server. Rejecting apply-update payload.'; } + console.debug('Applying managed code updates.'); + const AgentMessageSeverity_Error = 2 let applyError = undefined; @@ -261,11 +200,13 @@ setTimeout(async function () { })); if (!applyError) { - notifyHotReloadApplied(); + displayChangesAppliedToast(); } } - function displayDiagnostics(diagnostics) { + function reportDiagnostics(diagnostics) { + console.debug('Reporting Hot Reload diagnostics.'); + document.querySelectorAll('#dotnet-compile-error').forEach(el => el.remove()); const el = document.body.appendChild(document.createElement('div')); el.id = 'dotnet-compile-error'; @@ -280,7 +221,7 @@ setTimeout(async function () { }); } - function notifyHotReloadApplied() { + function displayChangesAppliedToast() { document.querySelectorAll('#dotnet-compile-error').forEach(el => el.remove()); if (document.querySelector('#dotnet-hotreload-toast')) { return; @@ -298,7 +239,7 @@ setTimeout(async function () { setTimeout(() => el.remove(), 2000); } - function aspnetCoreHotReloadApplied() { + function refreshBrowser() { if (window.Blazor) { window[hotReloadActiveKey] = true; // hotReloadApplied triggers an enhanced navigation to @@ -306,17 +247,39 @@ setTimeout(async function () { // Blazor SSR. if (window.Blazor?._internal?.hotReloadApplied) { + console.debug('Refreshing browser: WASM.'); Blazor._internal.hotReloadApplied(); } else { - notifyHotReloadApplied(); + console.debug('Refreshing browser.'); + displayChangesAppliedToast(); } } else { + console.debug('Refreshing browser: Reloading.'); location.reload(); } } + function reload() { + console.debug('Reloading.'); + location.reload(); + } + + function wait() { + console.debug('Waiting for application to rebuild.'); + + if (waiting) { + return; + } + + waiting = true; + const glyphs = ['☱', '☲', '☴']; + const title = document.title; + let i = 0; + setInterval(function () { document.title = glyphs[i++ % glyphs.length] + ' ' + title; }, 240); + } + async function getSecret(serverKeyString) { if (!serverKeyString || !window.crypto || !window.crypto.subtle) { return null; @@ -382,8 +345,8 @@ setTimeout(async function () { webSocket.addEventListener('close', onClose); if (window.Blazor?.removeEventListener && window.Blazor?.addEventListener) { - webSocket.addEventListener('close', () => window.Blazor?.removeEventListener('enhancedload', notifyHotReloadApplied)); - window.Blazor?.addEventListener('enhancedload', notifyHotReloadApplied); + webSocket.addEventListener('close', () => window.Blazor?.removeEventListener('enhancedload', displayChangesAppliedToast)); + window.Blazor?.addEventListener('enhancedload', displayChangesAppliedToast); } }); } diff --git a/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerFormatter.cs b/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerFormatter.cs index 9b78488b0aeb..d711ac3baeb2 100644 --- a/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerFormatter.cs +++ b/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerFormatter.cs @@ -315,6 +315,13 @@ internal static async Task DoesAnalyzerSupportLanguage(analyzer, project.Language)); foreach (var analyzer in filteredAnalyzer) { + // Allow suppressors unconditionally + if (analyzer is DiagnosticSuppressor suppressor) + { + analyzers.Add(suppressor); + continue; + } + // Filter by excluded diagnostics if (!excludeDiagnostics.IsEmpty && analyzer.SupportedDiagnostics.All(descriptor => excludeDiagnostics.Contains(descriptor.Id))) diff --git a/src/BuiltInTools/dotnet-format/Analyzers/CodeStyleInformationProvider.cs b/src/BuiltInTools/dotnet-format/Analyzers/CodeStyleInformationProvider.cs index 701f16410335..ebaa07487e06 100644 --- a/src/BuiltInTools/dotnet-format/Analyzers/CodeStyleInformationProvider.cs +++ b/src/BuiltInTools/dotnet-format/Analyzers/CodeStyleInformationProvider.cs @@ -33,6 +33,11 @@ public ImmutableDictionary GetAnalyzersAndFixers( .Select(path => new AnalyzerFileReference(path, analyzerAssemblyLoader)); var analyzersByLanguage = new Dictionary(); + + // We need AnalyzerReferenceInformationProvider to get all project suppressors + var referenceProvider = new AnalyzerReferenceInformationProvider(); + var perProjectAnalyzersAndFixers = referenceProvider.GetAnalyzersAndFixers(workspace, solution, formatOptions, logger); + return solution.Projects .ToImmutableDictionary( project => project.Id, @@ -40,9 +45,17 @@ public ImmutableDictionary GetAnalyzersAndFixers( { if (!analyzersByLanguage.TryGetValue(project.Language, out var analyzersAndFixers)) { - var analyzers = references.SelectMany(reference => reference.GetAnalyzers(project.Language)).ToImmutableArray(); + var analyzers = ImmutableArray.CreateBuilder(); + analyzers.AddRange(references.SelectMany(reference => reference.GetAnalyzers(project.Language))); var codeFixes = AnalyzerFinderHelpers.LoadFixers(references.Select(reference => reference.GetAssembly()), project.Language); - analyzersAndFixers = new AnalyzersAndFixers(analyzers, codeFixes); + + // Add project suppressors to featured analyzers + if (perProjectAnalyzersAndFixers.TryGetValue(project.Id, out var thisProjectAnalyzersAndFixers)) + { + analyzers.AddRange(thisProjectAnalyzersAndFixers.Analyzers.OfType()); + } + + analyzersAndFixers = new AnalyzersAndFixers(analyzers.ToImmutableArray(), codeFixes); analyzersByLanguage.Add(project.Language, analyzersAndFixers); } diff --git a/src/BuiltInTools/dotnet-watch.slnf b/src/BuiltInTools/dotnet-watch.slnf index b7454eb8869a..a3cc85fc0d92 100644 --- a/src/BuiltInTools/dotnet-watch.slnf +++ b/src/BuiltInTools/dotnet-watch.slnf @@ -9,6 +9,8 @@ "src\\BuiltInTools\\DotNetWatchTasks\\DotNetWatchTasks.csproj", "src\\BuiltInTools\\HotReloadAgent.Data\\Microsoft.DotNet.HotReload.Agent.Data.Package.csproj", "src\\BuiltInTools\\HotReloadAgent.Data\\Microsoft.DotNet.HotReload.Agent.Data.shproj", + "src\\BuiltInTools\\HotReloadAgent.Host\\Microsoft.DotNet.HotReload.Agent.Host.Package.csproj", + "src\\BuiltInTools\\HotReloadAgent.Host\\Microsoft.DotNet.HotReload.Agent.Host.shproj", "src\\BuiltInTools\\HotReloadAgent.PipeRpc\\Microsoft.DotNet.HotReload.Agent.PipeRpc.Package.csproj", "src\\BuiltInTools\\HotReloadAgent.PipeRpc\\Microsoft.DotNet.HotReload.Agent.PipeRpc.shproj", "src\\BuiltInTools\\HotReloadAgent.WebAssembly.Browser\\Microsoft.DotNet.HotReload.WebAssembly.Browser.csproj", @@ -16,6 +18,8 @@ "src\\BuiltInTools\\HotReloadAgent\\Microsoft.DotNet.HotReload.Agent.shproj", "src\\BuiltInTools\\HotReloadClient\\Microsoft.DotNet.HotReload.Client.Package.csproj", "src\\BuiltInTools\\HotReloadClient\\Microsoft.DotNet.HotReload.Client.shproj", + "src\\BuiltInTools\\Web.Middleware\\Microsoft.DotNet.HotReload.Web.Middleware.Package.csproj", + "src\\BuiltInTools\\Web.Middleware\\Microsoft.DotNet.HotReload.Web.Middleware.shproj", "src\\BuiltInTools\\dotnet-watch\\dotnet-watch.csproj", "test\\Microsoft.AspNetCore.Watch.BrowserRefresh.Tests\\Microsoft.AspNetCore.Watch.BrowserRefresh.Tests.csproj", "test\\Microsoft.DotNet.HotReload.Client.Tests\\Microsoft.DotNet.HotReload.Client.Tests.csproj", diff --git a/src/BuiltInTools/dotnet-watch/Build/BuildReporter.cs b/src/BuiltInTools/dotnet-watch/Build/BuildReporter.cs index 2bd96b5f3eb6..f062e112d457 100644 --- a/src/BuiltInTools/dotnet-watch/Build/BuildReporter.cs +++ b/src/BuiltInTools/dotnet-watch/Build/BuildReporter.cs @@ -102,7 +102,7 @@ private void Write(string message) public void ReportOutput() { _logger.LogInformation("MSBuild output:"); - BuildOutput.ReportBuildOutput(_logger, Messages, success: false, projectDisplay: null); + BuildOutput.ReportBuildOutput(_logger, Messages, success: false); } } } diff --git a/src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs b/src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs index 966ea12c87c4..d4090ce69840 100644 --- a/src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs +++ b/src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs @@ -32,12 +32,26 @@ public void WatchFiles(FileWatcher fileWatcher) fileWatcher.WatchFiles(BuildFiles); } + public static ImmutableDictionary GetGlobalBuildOptions(IEnumerable buildArguments, EnvironmentOptions environmentOptions) + { + // See https://github.com/dotnet/project-system/blob/main/docs/well-known-project-properties.md + + return CommandLineOptions.ParseBuildProperties(buildArguments) + .ToImmutableDictionary(keySelector: arg => arg.key, elementSelector: arg => arg.value) + .SetItem(PropertyNames.DotNetWatchBuild, "true") + .SetItem(PropertyNames.DesignTimeBuild, "true") + .SetItem(PropertyNames.SkipCompilerExecution, "true") + .SetItem(PropertyNames.ProvideCommandLineArgs, "true") + // F# targets depend on host path variable: + .SetItem("DOTNET_HOST_PATH", environmentOptions.MuxerPath); + } + /// /// Loads project graph and performs design-time build. /// public static EvaluationResult? TryCreate( + ProjectGraphFactory factory, string rootProjectPath, - IEnumerable buildArguments, ILogger logger, GlobalOptions options, EnvironmentOptions environmentOptions, @@ -46,20 +60,8 @@ public void WatchFiles(FileWatcher fileWatcher) { var buildReporter = new BuildReporter(logger, options, environmentOptions); - // See https://github.com/dotnet/project-system/blob/main/docs/well-known-project-properties.md - - var globalOptions = CommandLineOptions.ParseBuildProperties(buildArguments) - .ToImmutableDictionary(keySelector: arg => arg.key, elementSelector: arg => arg.value) - .SetItem(PropertyNames.DotNetWatchBuild, "true") - .SetItem(PropertyNames.DesignTimeBuild, "true") - .SetItem(PropertyNames.SkipCompilerExecution, "true") - .SetItem(PropertyNames.ProvideCommandLineArgs, "true") - // F# targets depend on host path variable: - .SetItem("DOTNET_HOST_PATH", environmentOptions.MuxerPath); - - var projectGraph = ProjectGraphUtilities.TryLoadProjectGraph( + var projectGraph = factory.TryLoadProjectGraph( rootProjectPath, - globalOptions, logger, projectGraphRequired: true, cancellationToken); diff --git a/src/BuiltInTools/dotnet-watch/Build/ProjectGraphFactory.cs b/src/BuiltInTools/dotnet-watch/Build/ProjectGraphFactory.cs new file mode 100644 index 000000000000..520bd4b8f972 --- /dev/null +++ b/src/BuiltInTools/dotnet-watch/Build/ProjectGraphFactory.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Graph; +using Microsoft.Extensions.Logging; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace Microsoft.DotNet.Watch; + +internal sealed class ProjectGraphFactory(ImmutableDictionary globalOptions) +{ + /// + /// Reuse with XML element caching to improve performance. + /// + /// The cache is automatically updated when build files change. + /// https://github.com/dotnet/msbuild/blob/b6f853defccd64ae1e9c7cf140e7e4de68bff07c/src/Build/Definition/ProjectCollection.cs#L343-L354 + /// + private readonly ProjectCollection _collection = new( + globalProperties: globalOptions, + loggers: [], + remoteLoggers: [], + ToolsetDefinitionLocations.Default, + maxNodeCount: 1, + onlyLogCriticalEvents: false, + loadProjectsReadOnly: false, + useAsynchronousLogging: false, + reuseProjectRootElementCache: true); + + /// + /// Tries to create a project graph by running the build evaluation phase on the . + /// + public ProjectGraph? TryLoadProjectGraph( + string rootProjectFile, + ILogger logger, + bool projectGraphRequired, + CancellationToken cancellationToken) + { + var entryPoint = new ProjectGraphEntryPoint(rootProjectFile, globalOptions); + try + { + return new ProjectGraph([entryPoint], _collection, projectInstanceFactory: null, cancellationToken); + } + catch (Exception e) when (e is not OperationCanceledException) + { + // ProejctGraph aggregates OperationCanceledException exception, + // throw here to propagate the cancellation. + cancellationToken.ThrowIfCancellationRequested(); + + logger.LogDebug("Failed to load project graph."); + + if (e is AggregateException { InnerExceptions: var innerExceptions }) + { + foreach (var inner in innerExceptions) + { + Report(inner); + } + } + else + { + Report(e); + } + + void Report(Exception e) + { + if (projectGraphRequired) + { + logger.LogError(e.Message); + } + else + { + logger.LogWarning(e.Message); + } + } + } + + return null; + } +} diff --git a/src/BuiltInTools/dotnet-watch/Build/ProjectGraphUtilities.cs b/src/BuiltInTools/dotnet-watch/Build/ProjectGraphUtilities.cs index 56bcba3427e6..6cb4c6b57a04 100644 --- a/src/BuiltInTools/dotnet-watch/Build/ProjectGraphUtilities.cs +++ b/src/BuiltInTools/dotnet-watch/Build/ProjectGraphUtilities.cs @@ -1,82 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Immutable; -using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Graph; using Microsoft.DotNet.Cli; -using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Microsoft.DotNet.Watch; internal static class ProjectGraphUtilities { - /// - /// Tries to create a project graph by running the build evaluation phase on the . - /// - public static ProjectGraph? TryLoadProjectGraph( - string rootProjectFile, - ImmutableDictionary globalOptions, - ILogger logger, - bool projectGraphRequired, - CancellationToken cancellationToken) - { - var entryPoint = new ProjectGraphEntryPoint(rootProjectFile, globalOptions); - try - { - // Create a new project collection that does not reuse element cache - // to work around https://github.com/dotnet/msbuild/issues/12064: - var collection = new ProjectCollection( - globalProperties: globalOptions, - loggers: [], - remoteLoggers: [], - ToolsetDefinitionLocations.Default, - maxNodeCount: 1, - onlyLogCriticalEvents: false, - loadProjectsReadOnly: false, - useAsynchronousLogging: false, - reuseProjectRootElementCache: false); - - return new ProjectGraph([entryPoint], collection, projectInstanceFactory: null, cancellationToken); - } - catch (Exception e) when (e is not OperationCanceledException) - { - // ProejctGraph aggregates OperationCanceledException exception, - // throw here to propagate the cancellation. - cancellationToken.ThrowIfCancellationRequested(); - - logger.LogDebug("Failed to load project graph."); - - if (e is AggregateException { InnerExceptions: var innerExceptions }) - { - foreach (var inner in innerExceptions) - { - Report(inner); - } - } - else - { - Report(e); - } - - void Report(Exception e) - { - if (projectGraphRequired) - { - logger.LogError(e.Message); - } - else - { - logger.LogWarning(e.Message); - } - } - } - - return null; - } - public static string GetDisplayName(this ProjectGraphNode projectNode) => $"{Path.GetFileNameWithoutExtension(projectNode.ProjectInstance.FullPath)} ({projectNode.GetTargetFramework()})"; diff --git a/src/BuiltInTools/dotnet-watch/CommandLine/CommandLineOptions.cs b/src/BuiltInTools/dotnet-watch/CommandLine/CommandLineOptions.cs index 95e98a584af6..0679d27d6897 100644 --- a/src/BuiltInTools/dotnet-watch/CommandLine/CommandLineOptions.cs +++ b/src/BuiltInTools/dotnet-watch/CommandLine/CommandLineOptions.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using Microsoft.Build.Logging; using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Extensions; using Microsoft.Extensions.Logging; @@ -119,7 +120,7 @@ internal sealed class CommandLineOptions // determine subcommand: var explicitCommand = TryGetSubcommand(parseResult); var command = explicitCommand ?? RunCommandParser.GetCommand(); - var buildOptions = command.Options.Where(o => o is IForwardedOption); + var buildOptions = command.Options.Where(o => o.ForwardingFunction is not null); foreach (var buildOption in buildOptions) { @@ -161,7 +162,7 @@ internal sealed class CommandLineOptions var commandArguments = GetCommandArguments(parseResult, watchOptions, explicitCommand, out var binLogToken, out var binLogPath); // We assume that forwarded options, if any, are intended for dotnet build. - var buildArguments = buildOptions.Select(option => ((IForwardedOption)option).GetForwardingFunction()(parseResult)).SelectMany(args => args).ToList(); + var buildArguments = buildOptions.Select(option => option.ForwardingFunction!(parseResult)).SelectMany(args => args).ToList(); if (binLogToken != null) { diff --git a/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentOptions.cs b/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentOptions.cs index 72f787332533..d0e84844de86 100644 --- a/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentOptions.cs +++ b/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentOptions.cs @@ -12,21 +12,16 @@ internal enum TestFlags RunningAsTest = 1 << 0, MockBrowser = 1 << 1, - /// - /// Elevates the severity of from . - /// - ElevateWaitingForChangesMessageSeverity = 1 << 2, - /// /// Instead of using to watch for Ctrl+C, Ctlr+R, and other keys, read from standard input. /// This allows tests to trigger key based events. /// - ReadKeyFromStdin = 1 << 3, + ReadKeyFromStdin = 1 << 2, /// /// Redirects the output of the launched browser process to watch output. /// - RedirectBrowserOutput = 1 << 4, + RedirectBrowserOutput = 1 << 3, } internal sealed record EnvironmentOptions( diff --git a/src/BuiltInTools/dotnet-watch/HotReload/HotReloadDotNetWatcher.cs b/src/BuiltInTools/dotnet-watch/HotReload/HotReloadDotNetWatcher.cs index 38e313f6f746..5466d27b32c2 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/HotReloadDotNetWatcher.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/HotReloadDotNetWatcher.cs @@ -20,6 +20,7 @@ internal sealed class HotReloadDotNetWatcher private readonly RestartPrompt? _rudeEditRestartPrompt; private readonly DotNetWatchContext _context; + private readonly ProjectGraphFactory _designTimeBuildGraphFactory; internal Task? Test_FileChangesCompletedTask { get; set; } @@ -40,6 +41,11 @@ public HotReloadDotNetWatcher(DotNetWatchContext context, IConsole console, IRun _rudeEditRestartPrompt = new RestartPrompt(context.Logger, consoleInput, noPrompt ? true : null); } + + _designTimeBuildGraphFactory = new ProjectGraphFactory( + EvaluationResult.GetGlobalBuildOptions( + context.RootProjectOptions.BuildArguments, + context.EnvironmentOptions)); } public async Task WatchAsync(CancellationToken shutdownCancellationToken) @@ -82,8 +88,7 @@ public async Task WatchAsync(CancellationToken shutdownCancellationToken) { var rootProjectOptions = _context.RootProjectOptions; - var (buildSucceeded, buildOutput, _) = await BuildProjectAsync(rootProjectOptions.ProjectPath, rootProjectOptions.BuildArguments, iterationCancellationToken); - BuildOutput.ReportBuildOutput(_context.BuildLogger, buildOutput, buildSucceeded, projectDisplay: rootProjectOptions.ProjectPath); + var buildSucceeded = await BuildProjectAsync(rootProjectOptions.ProjectPath, rootProjectOptions.BuildArguments, iterationCancellationToken); if (!buildSucceeded) { continue; @@ -189,7 +194,7 @@ void FileChangedCallback(ChangedPath change) fileChangedCallback = FileChangedCallback; fileWatcher.OnFileChange += fileChangedCallback; - ReportWatchingForChanges(); + _context.Logger.Log(MessageDescriptor.WaitingForChanges); // Hot Reload loop - exits when the root process needs to be restarted. bool extendTimeout = false; @@ -328,15 +333,19 @@ void FileChangedCallback(ChangedPath change) fileWatcher.SuppressEvents = true; try { - var buildResults = await Task.WhenAll( - projectsToRebuild.Select(projectPath => BuildProjectAsync(projectPath, rootProjectOptions.BuildArguments, iterationCancellationToken))); - - foreach (var (success, output, projectPath) in buildResults) + // Build projects sequentially to avoid failed attempts to overwrite dependent project outputs. + // TODO: Ideally, dotnet build would be able to build multiple projects. https://github.com/dotnet/sdk/issues/51311 + var success = true; + foreach (var projectPath in projectsToRebuild) { - BuildOutput.ReportBuildOutput(_context.BuildLogger, output, success, projectPath); + success = await BuildProjectAsync(projectPath, rootProjectOptions.BuildArguments, iterationCancellationToken); + if (!success) + { + break; + } } - if (buildResults.All(result => result.success)) + if (success) { break; } @@ -772,12 +781,6 @@ internal static IEnumerable NormalizePathChanges(IEnumerable item != null) .Select(item => item!.Value); - private void ReportWatchingForChanges() - { - _context.Logger.Log(MessageDescriptor.WaitingForChanges - .WithSeverityWhen(MessageSeverity.Output, _context.EnvironmentOptions.TestFlags.HasFlag(TestFlags.ElevateWaitingForChangesMessageSeverity))); - } - private void ReportFileChanges(IReadOnlyList changedFiles) { Report(kind: ChangeKind.Add); @@ -823,15 +826,20 @@ private async ValueTask EvaluateRootProjectAsync(bool restore, { cancellationToken.ThrowIfCancellationRequested(); + _context.Logger.LogInformation("Evaluating projects ..."); + var stopwatch = Stopwatch.StartNew(); + var result = EvaluationResult.TryCreate( - _context.RootProjectOptions.ProjectPath, - _context.RootProjectOptions.BuildArguments, + _designTimeBuildGraphFactory, + _context.RootProjectOptions.ProjectPath, _context.BuildLogger, _context.Options, _context.EnvironmentOptions, restore, cancellationToken); + _context.Logger.LogInformation("Evaluation completed in {Time}s.", stopwatch.Elapsed.TotalSeconds.ToString("0.0")); + if (result != null) { return result; @@ -846,31 +854,43 @@ await FileWatcher.WaitForFileChangeAsync( } } - private async Task<(bool success, ImmutableArray output, string projectPath)> BuildProjectAsync( - string projectPath, IReadOnlyList buildArguments, CancellationToken cancellationToken) + private async Task BuildProjectAsync(string projectPath, IReadOnlyList buildArguments, CancellationToken cancellationToken) { - var buildOutput = new List(); + List? capturedOutput = _context.EnvironmentOptions.TestFlags != TestFlags.None ? [] : null; var processSpec = new ProcessSpec { Executable = _context.EnvironmentOptions.MuxerPath, WorkingDirectory = Path.GetDirectoryName(projectPath)!, IsUserApplication = false, - OnOutput = line => - { - lock (buildOutput) + + // Capture output if running in a test environment. + // If the output is not captured dotnet build will show live build progress. + OnOutput = capturedOutput != null + ? line => { - buildOutput.Add(line); + lock (capturedOutput) + { + capturedOutput.Add(line); + } } - }, + : null, + // pass user-specified build arguments last to override defaults: Arguments = ["build", projectPath, "-consoleLoggerParameters:NoSummary;Verbosity=minimal", .. buildArguments] }; _context.BuildLogger.Log(MessageDescriptor.Building, projectPath); - var exitCode = await _context.ProcessRunner.RunAsync(processSpec, _context.Logger, launchResult: null, cancellationToken); - return (exitCode == 0, buildOutput.ToImmutableArray(), projectPath); + var success = await _context.ProcessRunner.RunAsync(processSpec, _context.Logger, launchResult: null, cancellationToken) == 0; + + if (capturedOutput != null) + { + _context.BuildLogger.Log(success ? MessageDescriptor.BuildSucceeded : MessageDescriptor.BuildFailed, projectPath); + BuildOutput.ReportBuildOutput(_context.BuildLogger, capturedOutput, success); + } + + return success; } private string GetRelativeFilePath(string path) diff --git a/src/BuiltInTools/dotnet-watch/HotReload/IncrementalMSBuildWorkspace.cs b/src/BuiltInTools/dotnet-watch/HotReload/IncrementalMSBuildWorkspace.cs index f4f3f499aec2..65ab6089466c 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/IncrementalMSBuildWorkspace.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/IncrementalMSBuildWorkspace.cs @@ -15,6 +15,7 @@ namespace Microsoft.DotNet.Watch; internal sealed class IncrementalMSBuildWorkspace : Workspace { private readonly ILogger _logger; + private int _solutionUpdateId; public IncrementalMSBuildWorkspace(ILogger logger) : base(MSBuildMefHostServices.DefaultServices, WorkspaceKind.MSBuild) @@ -35,6 +36,9 @@ public IncrementalMSBuildWorkspace(ILogger logger) public async Task UpdateProjectConeAsync(string rootProjectPath, CancellationToken cancellationToken) { + _logger.LogInformation("Loading projects ..."); + + var stopwatch = Stopwatch.StartNew(); var oldSolution = CurrentSolution; var loader = new MSBuildProjectLoader(this); @@ -94,9 +98,11 @@ public async Task UpdateProjectConeAsync(string rootProjectPath, CancellationTok .WithCompilationOutputInfo(newProjectInfo.CompilationOutputInfo)); } - await ReportSolutionFilesAsync(SetCurrentSolution(newSolution), cancellationToken); + await UpdateSolutionAsync(newSolution, operationDisplayName: "project update", cancellationToken); UpdateReferencesAfterAdd(); + _logger.LogInformation("Projects loaded in {Time}s.", stopwatch.Elapsed.TotalSeconds.ToString("0.0")); + ProjectReference MapProjectReference(ProjectReference pr) // Only C# and VB projects are loaded by the MSBuildProjectLoader, so some references might be missing. // When a new project is added along with a new project reference the old project id is also null. @@ -154,6 +160,8 @@ public async ValueTask UpdateFileContentAsync(IEnumerable changedFi var newText = await GetSourceTextAsync(changedFile.FilePath, oldText.Encoding, oldText.ChecksumAlgorithm, cancellationToken); + _logger.LogDebug("Updating document text of '{FilePath}'.", changedFile.FilePath); + updatedSolution = textDocument switch { Document document => document.WithText(newText).Project.Solution, @@ -166,7 +174,7 @@ public async ValueTask UpdateFileContentAsync(IEnumerable changedFi updatedSolution = RemoveDocuments(updatedSolution, documentsToRemove); - await ReportSolutionFilesAsync(SetCurrentSolution(updatedSolution), cancellationToken); + await UpdateSolutionAsync(updatedSolution, operationDisplayName: "document update", cancellationToken); } private static Solution RemoveDocuments(Solution solution, IEnumerable ids) @@ -217,10 +225,21 @@ private static async ValueTask GetSourceTextAsync(string filePath, E return null; } - public async Task ReportSolutionFilesAsync(Solution solution, CancellationToken cancellationToken) + private Task UpdateSolutionAsync(Solution newSolution, string operationDisplayName, CancellationToken cancellationToken) + => ReportSolutionFilesAsync(SetCurrentSolution(newSolution), Interlocked.Increment(ref _solutionUpdateId), operationDisplayName, cancellationToken); + + private async Task ReportSolutionFilesAsync(Solution solution, int updateId, string operationDisplayName, CancellationToken cancellationToken) { #if DEBUG _logger.LogDebug("Solution: {Path}", solution.FilePath); + + if (!_logger.IsEnabled(LogLevel.Debug)) + { + return; + } + + _logger.LogDebug("Solution after {Operation}: v{Version}", operationDisplayName, updateId); + foreach (var project in solution.Projects) { _logger.LogDebug(" Project: {Path}", project.FilePath); diff --git a/src/BuiltInTools/dotnet-watch/UI/BuildOutput.cs b/src/BuiltInTools/dotnet-watch/UI/BuildOutput.cs index 50474d870ca6..ab32830bd6ab 100644 --- a/src/BuiltInTools/dotnet-watch/UI/BuildOutput.cs +++ b/src/BuiltInTools/dotnet-watch/UI/BuildOutput.cs @@ -13,20 +13,8 @@ internal static partial class BuildOutput [GeneratedRegex(@"[^:]+: (error|warning) [A-Za-z]+[0-9]+: .+")] private static partial Regex GetBuildDiagnosticRegex(); - public static void ReportBuildOutput(ILogger logger, IEnumerable buildOutput, bool success, string? projectDisplay) + public static void ReportBuildOutput(ILogger logger, IEnumerable buildOutput, bool success) { - if (projectDisplay != null) - { - if (success) - { - logger.Log(MessageDescriptor.BuildSucceeded, projectDisplay); - } - else - { - logger.Log(MessageDescriptor.BuildFailed, projectDisplay); - } - } - foreach (var (line, isError) in buildOutput) { if (isError) diff --git a/src/BuiltInTools/dotnet-watch/UI/IReporter.cs b/src/BuiltInTools/dotnet-watch/UI/IReporter.cs index 319fcc1740b0..4224aefb4855 100644 --- a/src/BuiltInTools/dotnet-watch/UI/IReporter.cs +++ b/src/BuiltInTools/dotnet-watch/UI/IReporter.cs @@ -186,7 +186,7 @@ public MessageDescriptor WithSeverityWhen(MessageSeverity severity, bool conditi public static readonly MessageDescriptor ProjectsRestarted = Create("Projects restarted ({0})", Emoji.HotReload, MessageSeverity.Verbose); public static readonly MessageDescriptor ProjectDependenciesDeployed = Create("Project dependencies deployed ({0})", Emoji.HotReload, MessageSeverity.Verbose); public static readonly MessageDescriptor FixBuildError = Create("Fix the error to continue or press Ctrl+C to exit.", Emoji.Watch, MessageSeverity.Warning); - public static readonly MessageDescriptor WaitingForChanges = Create("Waiting for changes", Emoji.Watch, MessageSeverity.Verbose); + public static readonly MessageDescriptor WaitingForChanges = Create("Waiting for changes", Emoji.Watch, MessageSeverity.Output); public static readonly MessageDescriptor LaunchedProcess = Create("Launched '{0}' with arguments '{1}': process id {2}", Emoji.Launch, MessageSeverity.Verbose); public static readonly MessageDescriptor HotReloadChangeHandled = Create("Hot reload change handled in {0}ms.", Emoji.HotReload, MessageSeverity.Verbose); public static readonly MessageDescriptor HotReloadSucceeded = Create(LogEvents.HotReloadSucceeded, Emoji.HotReload); diff --git a/src/BuiltInTools/dotnet-watch/Utilities/ProcessUtilities.cs b/src/BuiltInTools/dotnet-watch/Utilities/ProcessUtilities.cs index bcc5ea0374f9..4040e186f0c7 100644 --- a/src/BuiltInTools/dotnet-watch/Utilities/ProcessUtilities.cs +++ b/src/BuiltInTools/dotnet-watch/Utilities/ProcessUtilities.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Runtime.InteropServices; using System.Diagnostics; namespace Microsoft.DotNet.Watch; @@ -9,34 +11,6 @@ internal static class ProcessUtilities { public const int SIGKILL = 9; public const int SIGTERM = 15; - - /// - /// Enables handling of Ctrl+C in a process where it was disabled. - /// - /// If a process is launched with CREATE_NEW_PROCESS_GROUP flag - /// it allows the parent process to send Ctrl+C event to the child process, - /// but also disables Ctrl+C handlers. - /// - public static void EnableWindowsCtrlCHandling(Action log) - { - Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); - - // "If the HandlerRoutine parameter is NULL, a TRUE value causes the calling process to ignore CTRL+C input, - // and a FALSE value restores normal processing of CTRL+C input. - // This attribute of ignoring or processing CTRL+C is inherited by child processes." - - if (SetConsoleCtrlHandler(null, false)) - { - log("Windows Ctrl+C handling enabled."); - } - else - { - log($"Failed to enable Ctrl+C handling: {GetLastPInvokeErrorMessage()}"); - } - - [DllImport("kernel32.dll", SetLastError = true)] - static extern bool SetConsoleCtrlHandler(Delegate? handler, bool add); - } public static string? SendWindowsCtrlCEvent(int processId) { diff --git a/src/BuiltInTools/dotnet-watch/Watch/MsBuildFileSetFactory.cs b/src/BuiltInTools/dotnet-watch/Watch/MsBuildFileSetFactory.cs index d1c580ecb386..50ce65d4e9a1 100644 --- a/src/BuiltInTools/dotnet-watch/Watch/MsBuildFileSetFactory.cs +++ b/src/BuiltInTools/dotnet-watch/Watch/MsBuildFileSetFactory.cs @@ -30,6 +30,9 @@ internal class MSBuildFileSetFactory( private EnvironmentOptions EnvironmentOptions => buildReporter.EnvironmentOptions; private ILogger Logger => buildReporter.Logger; + private readonly ProjectGraphFactory _buildGraphFactory = new( + globalOptions: CommandLineOptions.ParseBuildProperties(buildArguments).ToImmutableDictionary(keySelector: arg => arg.key, elementSelector: arg => arg.value)); + internal sealed class EvaluationResult(IReadOnlyDictionary files, ProjectGraph? projectGraph) { public readonly IReadOnlyDictionary Files = files; @@ -73,7 +76,7 @@ internal sealed class EvaluationResult(IReadOnlyDictionary fil Logger.LogInformation("MSBuild output from target '{TargetName}':", TargetName); } - BuildOutput.ReportBuildOutput(Logger, capturedOutput, success, projectDisplay: null); + BuildOutput.ReportBuildOutput(Logger, capturedOutput, success); if (!success) { return null; @@ -124,10 +127,7 @@ void AddFile(string filePath, string? staticWebAssetPath) ProjectGraph? projectGraph = null; if (requireProjectGraph != null) { - var globalOptions = CommandLineOptions.ParseBuildProperties(buildArguments) - .ToImmutableDictionary(keySelector: arg => arg.key, elementSelector: arg => arg.value); - - projectGraph = ProjectGraphUtilities.TryLoadProjectGraph(rootProjectFile, globalOptions, Logger, requireProjectGraph.Value, cancellationToken); + projectGraph = _buildGraphFactory.TryLoadProjectGraph(rootProjectFile, Logger, requireProjectGraph.Value, cancellationToken); if (projectGraph == null && requireProjectGraph == true) { return null; diff --git a/src/Cli/Microsoft.DotNet.Cli.CommandLine/ArgumentBuilderExtensions.cs b/src/Cli/Microsoft.DotNet.Cli.CommandLine/ArgumentBuilderExtensions.cs new file mode 100644 index 000000000000..a88efd6259bd --- /dev/null +++ b/src/Cli/Microsoft.DotNet.Cli.CommandLine/ArgumentBuilderExtensions.cs @@ -0,0 +1,19 @@ +using System.CommandLine; +using System.CommandLine.Completions; + +namespace Microsoft.DotNet.Cli.CommandLine; + +/// +/// Extension methods that make it easier to chain argument configuration methods when building arguments. +/// +public static class ArgumentBuilderExtensions +{ + extension(Argument argument) + { + public Argument AddCompletions(Func> completionSource) + { + argument.CompletionSources.Add(completionSource); + return argument; + } + } +} diff --git a/src/Cli/Microsoft.DotNet.Cli.CommandLine/ForwardedOptionExtensions.cs b/src/Cli/Microsoft.DotNet.Cli.CommandLine/ForwardedOptionExtensions.cs new file mode 100644 index 000000000000..040f4595f7b8 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.Cli.CommandLine/ForwardedOptionExtensions.cs @@ -0,0 +1,219 @@ +using System.CommandLine; +using System.CommandLine.Parsing; + +namespace Microsoft.DotNet.Cli.CommandLine; + +/// +/// Extensions for tracking and invoking forwarding functions on options and arguments. +/// Forwarding functions are used to translate the parsed value of an option or argument +/// into a set of zero or more string values that will be passed to an inner command. +/// +public static class ForwardedOptionExtensions +{ + private static readonly Dictionary>> s_forwardingFunctions = []; + private static readonly Lock s_lock = new(); + + extension(Option option) + { + /// + /// If this option has a forwarding function, this property will return it; otherwise, it will be null. + /// + /// + /// This getter is on the untyped Option because much of the _processing_ of option forwarding + /// is done at the ParseResult level, where we don't have the generic type parameter. + /// + public Func>? ForwardingFunction => s_forwardingFunctions.GetValueOrDefault(option); + } + + extension(Option option) + { + /// + /// Internal-only helper function that ensures the provided forwarding function is only called + /// if the option actually has a value. + /// + private Func> GetForwardingFunction(Func> func) + { + return (ParseResult parseResult) => + { + if (parseResult.GetResult(option) is OptionResult r) + { + if (r.GetValueOrDefault() is TValue value) + { + return func(value); + } + else + { + return []; + } + } + return []; + }; + } + + /// + /// Internal-only helper function that ensures the provided forwarding function is only called + /// if the option actually has a value. + /// + private Func> GetForwardingFunction(Func> func) + { + return (ParseResult parseResult) => + { + if (parseResult.GetResult(option) is OptionResult r) + { + if (r.GetValueOrDefault() is TValue value) + { + return func(value, parseResult); + } + else + { + return []; + } + } + return []; + }; + } + + /// + /// Forwards the option using the provided function to convert the option's value to zero or more string values. + /// The function will only be called if the option has a value. + /// + public Option SetForwardingFunction(Func> func) + { + lock (s_lock) + { + s_forwardingFunctions[option] = option.GetForwardingFunction(func); + } + return option; + } + + /// + /// Forward the option using the provided function to convert the option's value to a single string value. + /// The function will only be called if the option has a value. + /// + public Option SetForwardingFunction(Func format) + { + lock (s_lock) + { + s_forwardingFunctions[option] = option.GetForwardingFunction(o => [format(o)]); + } + return option; + } + + /// + /// Forward the option using the provided function to convert the option's value to a single string value. + /// The function will only be called if the option has a value. + /// + public Option SetForwardingFunction(Func> func) + { + lock (s_lock) + { + s_forwardingFunctions[option] = option.GetForwardingFunction(func); + } + return option; + } + + /// + /// Forward the option as multiple calculated string values from whatever the option's value is. + /// + /// + /// + public Option ForwardAsMany(Func> format) => option.SetForwardingFunction(format); + + /// + /// Forward the option as its own name. + /// + /// + public Option Forward() => option.SetForwardingFunction((TValue? o) => [option.Name]); + + /// + /// Forward the option as a string value. This value will be forwarded as long as the option has a OptionResult - which means that + /// any implicit value calculation will cause the string value to be forwarded. + /// + public Option ForwardAs(string value) => option.SetForwardingFunction((TValue? o) => [value]); + + /// + /// Forward the option as a singular calculated string value. + /// + public Option ForwardAsSingle(Func format) => option.SetForwardingFunction(format); + } + + extension(Option option) + { + /// + /// Forward the boolean option as a string value. This value will be forwarded as long as the option has a OptionResult - which means that + /// any implicit value calculation will cause the string value to be forwarded. For boolean options specifically, if the option is zero arity + /// and has no default value factory, S.CL will synthesize a true or false value based on whether the option was provided or not, so we need to + /// add an additional implicit 'value is true' check to prevent accidentally forwarding the option for flags that are absent.. + /// + public Option ForwardIfEnabled(string value) => option.SetForwardingFunction((bool o) => o ? [value] : []); + /// + /// Forward the boolean option as a string value. This value will be forwarded as long as the option has a OptionResult - which means that + /// any implicit value calculation will cause the string value to be forwarded. For boolean options specifically, if the option is zero arity + /// and has no default value factory, S.CL will synthesize a true or false value based on whether the option was provided or not, so we need to + /// add an additional implicit 'value is true' check to prevent accidentally forwarding the option for flags that are absent.. + /// + public Option ForwardIfEnabled(string[] value) => option.SetForwardingFunction((bool o) => o ? value : []); + + /// + /// Forward the boolean option as a string value. This value will be forwarded as long as the option has a OptionResult - which means that + /// any implicit value calculation will cause the string value to be forwarded. For boolean options specifically, if the option is zero arity + /// and has no default value factory, S.CL will synthesize a true or false value based on whether the option was provided or not, so we need to + /// add an additional implicit 'value is true' check to prevent accidentally forwarding the option for flags that are absent.. + /// + public Option ForwardAs(string value) => option.ForwardIfEnabled(value); + } + + extension(Option> option) + { + /// + /// Foreach argument in the option's value, yield the followed by the argument. + /// + public Option> ForwardAsManyArgumentsEachPrefixedByOption(string alias) => + option.ForwardAsMany(o => ForwardedArguments(alias, o)); + } + + extension(ParseResult parseResult) + { + /// + /// Calls the forwarding functions for all options that have declared a forwarding function (via 's extension members) in the provided . + /// + /// + /// If not provided, uses the 's . + /// + public IEnumerable OptionValuesToBeForwarded(Command? command = null) => + (command ?? parseResult.CommandResult.Command) + .Options + .Select(o => o.ForwardingFunction) + .SelectMany(f => f is not null ? f(parseResult) : []); + + /// + /// Tries to find the first option named in , and if found, + /// invokes its forwarding function (if any) and returns the result. If no option with that name is found, or if the option + /// has no forwarding function, returns an empty enumeration. + /// + /// + /// + /// + public IEnumerable ForwardedOptionValues(Command command, string alias) + { + var func = command.Options? + .Where(o => + (o.Name.Equals(alias) || o.Aliases.Contains(alias)) + && o.ForwardingFunction is not null) + .FirstOrDefault()?.ForwardingFunction; + return func?.Invoke(parseResult) ?? []; + } + } + + /// + /// For each argument in , yield the followed by the argument. + /// + private static IEnumerable ForwardedArguments(string alias, IEnumerable? arguments) + { + foreach (string arg in arguments ?? []) + { + yield return alias; + yield return arg; + } + } +} diff --git a/src/Cli/Microsoft.DotNet.Cli.CommandLine/Microsoft.DotNet.Cli.CommandLine.csproj b/src/Cli/Microsoft.DotNet.Cli.CommandLine/Microsoft.DotNet.Cli.CommandLine.csproj new file mode 100644 index 000000000000..4e2669b28bad --- /dev/null +++ b/src/Cli/Microsoft.DotNet.Cli.CommandLine/Microsoft.DotNet.Cli.CommandLine.csproj @@ -0,0 +1,19 @@ + + + + $(SdkTargetFramework) + enable + enable + MicrosoftAspNetCore + true + true + + + + + + + + + + diff --git a/src/Cli/Microsoft.DotNet.Cli.CommandLine/OptionBuilderExtensions.cs b/src/Cli/Microsoft.DotNet.Cli.CommandLine/OptionBuilderExtensions.cs new file mode 100644 index 000000000000..19b502a4b70d --- /dev/null +++ b/src/Cli/Microsoft.DotNet.Cli.CommandLine/OptionBuilderExtensions.cs @@ -0,0 +1,46 @@ +using System.CommandLine; +using System.CommandLine.Completions; + +namespace Microsoft.DotNet.Cli.CommandLine; + +/// +/// Extension methods that make it easier to chain option configuration methods when building options. +/// +public static class OptionBuilderExtensions +{ + extension(T option) where T : Option + { + /// + /// Forces an option that represents a collection-type to only allow a single + /// argument per instance of the option. This means that you'd have to + /// use the option multiple times to pass multiple values. + /// This prevents ambiguity in parsing when argument tokens may appear after the option. + /// + /// + /// + /// + public T AllowSingleArgPerToken() + { + option.AllowMultipleArgumentsPerToken = false; + return option; + } + + + public T AggregateRepeatedTokens() + { + option.AllowMultipleArgumentsPerToken = true; + return option; + } + + public T Hide() + { + option.Hidden = true; + return option; + } + public T AddCompletions(Func> completionSource) + { + option.CompletionSources.Add(completionSource); + return option; + } + } +} diff --git a/src/Cli/Microsoft.DotNet.Cli.CommandLine/README.md b/src/Cli/Microsoft.DotNet.Cli.CommandLine/README.md new file mode 100644 index 000000000000..82e8afb6e8de --- /dev/null +++ b/src/Cli/Microsoft.DotNet.Cli.CommandLine/README.md @@ -0,0 +1,12 @@ +# Microsoft.Dotnet.Cli.CommandLine + +This project contains extensions and utilities for building command line applications. + +These extensions are layered on top of core System.CommandLine concepts and types, and +do not directly reference concepts that are specific to the `dotnet` CLI. We hope that +these would be published separately as a NuGet package for use by other command line +applications in the future. + +From a layering perspective, everything that is specific to the `dotnet` CLI should +be in the `src/Cli/dotnet` or `src/Cli/Microsoft.DotNet.Cli.Utils` projects, which +reference this project. Keep this one generally-speaking clean. diff --git a/src/Cli/Microsoft.DotNet.Cli.CommandLine/ResultNavigationExtensions.cs b/src/Cli/Microsoft.DotNet.Cli.CommandLine/ResultNavigationExtensions.cs new file mode 100644 index 000000000000..b4bd612cc6c6 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.Cli.CommandLine/ResultNavigationExtensions.cs @@ -0,0 +1,94 @@ +using System.CommandLine; +using System.CommandLine.Parsing; + +namespace Microsoft.DotNet.Cli.CommandLine; + +/// +/// Extension methods for safely navigating ParseResult and SymbolResult to get option values. +/// +public static class ResultNavigationExtensions +{ + /// + /// Only returns the value for this option if the option is present and there are no parse errors for that option. + /// This allows cross-cutting code like the telemetry filters to safely get the value without throwing on null-ref errors. + /// If you are inside a command handler or 'normal' System.CommandLine code then you don't need this - the parse error handling + /// will have covered these cases. + /// + public static T? SafelyGetValueForOption(this ParseResult parseResult, Option optionToGet) + { + if (parseResult.GetResult(optionToGet) is OptionResult optionResult // only return a value if there _is_ a value - default or otherwise + && !parseResult.Errors.Any(e => e.SymbolResult == optionResult) // only return a value if this isn't a parsing error + && optionResult.Option.ValueType.IsAssignableTo(typeof(T))) // only return a value if coercing the type won't error + { + // shouldn't happen because of the above checks, but we can be safe since this is only used in telemetry, and should + // be resistant to errors + try + { + return optionResult.GetValue(optionToGet); + } + catch + { + return default; + } + } + else + { + return default; + } + } + + /// + /// Only returns the value for this option if the option is present and there are no parse errors for that option. + /// This allows cross-cutting code like the telemetry filters to safely get the value without throwing on null-ref errors. + /// If you are inside a command handler or 'normal' System.CommandLine code then you don't need this - the parse error handling + /// will have covered these cases. + /// + public static T? SafelyGetValueForOption(this ParseResult parseResult, string name) + { + if (parseResult.GetResult(name) is OptionResult optionResult // only return a value if there _is_ a value - default or otherwise + && !parseResult.Errors.Any(e => e.SymbolResult == optionResult) // only return a value if this isn't a parsing error + && optionResult.Option.ValueType.IsAssignableTo(typeof(T))) // only return a value if coercing the type won't error + { + // shouldn't happen because of the above checks, but we can be safe since this is only used in telemetry, and should + // be resistant to errors + try + { + return optionResult.GetValue(name); + } + catch + { + return default; + } + } + else + { + return default; + } + } + + /// + /// Checks if the option is present and not implicit (i.e. not set by default). + /// This is useful for checking if the user has explicitly set an option, as opposed to it being set by default. + /// + public static bool HasOption(this ParseResult parseResult, Option option) => parseResult.GetResult(option) is OptionResult or && !or.Implicit; + + /// + /// Checks if the option with given name is present and not implicit (i.e. not set by default). + /// This is useful for checking if the user has explicitly set an option, as opposed to it being set by default. + /// + public static bool HasOption(this ParseResult parseResult, string name) + => parseResult.GetResult(name) is OptionResult or && !or.Implicit; + + /// + /// Checks if the option is present and not implicit (i.e. not set by default). + /// This is useful for checking if the user has explicitly set an option, as opposed to it being set by default. + /// + public static bool HasOption(this SymbolResult symbolResult, Option option) => symbolResult.GetResult(option) is OptionResult or && !or.Implicit; + + /// + /// Checks if the option with given name is present and not implicit (i.e. not set by default). + /// This is useful for checking if the user has explicitly set an option, as opposed to it being set by default. + /// + public static bool HasOption(this SymbolResult symbolResult, string name) + => symbolResult.GetResult(name) is OptionResult or && !or.Implicit; +} diff --git a/src/Cli/Microsoft.DotNet.Cli.CommandLine/SpanParsableExtensions.cs b/src/Cli/Microsoft.DotNet.Cli.CommandLine/SpanParsableExtensions.cs new file mode 100644 index 000000000000..9419a038ccd9 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.Cli.CommandLine/SpanParsableExtensions.cs @@ -0,0 +1,153 @@ +using System.Collections.Immutable; +using System.CommandLine; +using System.CommandLine.Parsing; + +namespace Microsoft.DotNet.Cli.CommandLine; + +public static class SpanParserExtensions +{ + extension(Option o) where T : ISpanParsable + { + /// + /// Configures the option with a custom parser that uses the implementation to parse the tokens provided. + /// Will parse a single token with , and if the option allows multiple tokens will take the 'last one wins' approach. + /// + /// + /// Without this, Options will fall-back to using potentially-reflection-based parsing in S.CL, or + /// if the type doesn't have built-in S.CL parsing support will fail to parse at runtime. + /// + public Option AsSpanParsable() + { + o.CustomParser = StaticSingleItemParser; + return o; + } + } + + extension(Option> o) where T : ISpanParsable + { + /// + /// Configures the option with a custom parser that uses the implementation to parse the tokens provided. + /// This parser handles multiple tokens, using for each token. + /// + /// + /// Without this, Options will fall-back to using potentially-reflection-based parsing in S.CL, or + /// if the type doesn't have built-in S.CL parsing support will fail to parse at runtime. + /// + public Option> AsSpanParsable() + { + o.CustomParser = StaticMultiItemItemParser; + return o; + } + } + + extension(Argument a) where T : ISpanParsable + { + /// + /// Configures the argument with a custom parser that uses the implementation to parse the value. + /// Will parse a single token with , and if the argument allows multiple tokens will take the 'last one wins' approach. + /// + /// + /// Without this, Arguments will fall-back to using potentially-reflection-based parsing in S.CL, or + /// if the type doesn't have built-in S.CL parsing support will fail to parse at runtime. + /// + public Argument AsSpanParsable() + { + a.CustomParser = StaticSingleItemParser; + return a; + } + } + + extension(Argument> a) where T : ISpanParsable + { + /// + /// Configures the argument with a custom parser that uses the implementation to parse the value. + /// This parser handles multiple tokens, using for each token. + /// + /// + /// Without this, Arguments will fall-back to using potentially-reflection-based parsing in S.CL, or + /// if the type doesn't have built-in S.CL parsing support will fail to parse at runtime. + /// + public Argument> AsSpanParsable() + { + a.CustomParser = StaticMultiItemItemParser; + return a; + } + } + + internal static IReadOnlyCollection? StaticMultiItemItemParser(ArgumentResult tokenizationResult) + where T : ISpanParsable + { + if (tokenizationResult.Tokens.Count == 0) + { + return default; + } + + var parentName = + tokenizationResult.Parent switch + { + OptionResult optionResult => optionResult.Option.Name, + ArgumentResult argumentResult => argumentResult.Argument.Name, + CommandResult or null => tokenizationResult.Argument.Name, + _ => "" + }; + var coll = ImmutableArray.CreateBuilder(tokenizationResult.Tokens.Count); + + foreach (var token in tokenizationResult.Tokens) + { + var tokenToParse = token.Value; + + if (string.IsNullOrEmpty(tokenToParse)) + { + tokenizationResult.AddError($"Cannot parse null or empty value for symbol '{parentName}'"); + continue; + } + + if (!T.TryParse(tokenToParse, null, out var result)) + { + tokenizationResult.AddError($"Cannot parse value '{tokenToParse}' for symbol '{parentName}' as a {typeof(T).Name}"); + continue; + } + + coll.Add(result); + } + + return coll.ToImmutableArray(); + } + + internal static T? StaticSingleItemParser(ArgumentResult tokenizationResult) + where T : ISpanParsable + { + if (tokenizationResult.Tokens.Count == 0) + { + return default; + } + + var parentName = + tokenizationResult.Parent switch + { + OptionResult optionResult => optionResult.Option.Name, + ArgumentResult argumentResult => argumentResult.Argument.Name, + CommandResult or null => tokenizationResult.Argument.Name, + _ => "" + }; + // we explicitly only support parsing one token, so let's do a last-one-wins approach here + var tokenToParse = + tokenizationResult.Tokens switch + { + [var onlyToken] => onlyToken.Value, + _ => tokenizationResult.Tokens[^1].Value + }; + + if (string.IsNullOrEmpty(tokenToParse)) + { + tokenizationResult.AddError($"Cannot parse null or empty value for symbol '{parentName}'"); + } + + if (!T.TryParse(tokenToParse, null, out var result)) + { + tokenizationResult.AddError($"Cannot parse value '{tokenToParse}' for symbol '{parentName}' as a {typeof(T).Name}"); + } + + return result; + } +} diff --git a/src/Cli/Microsoft.DotNet.Cli.CommandLine/SymbolDocumentationExtensions.cs b/src/Cli/Microsoft.DotNet.Cli.CommandLine/SymbolDocumentationExtensions.cs new file mode 100644 index 000000000000..908a8911b4c7 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.Cli.CommandLine/SymbolDocumentationExtensions.cs @@ -0,0 +1,40 @@ +using System.CommandLine; + +namespace Microsoft.DotNet.Cli.CommandLine; + +/// +/// Extension methods for adding documentation links to commands, options and arguments. +/// +public static class SymbolDocumentationExtensions +{ + private static readonly Dictionary s_documentationLinks = new(); + private static readonly Lock s_lock = new(); + + extension(Symbol symbol) + { + /// + /// Gets or sets the documentation link for this command, option or argument. + /// This link is intended to be shown in help or error messages to point users to more information. + /// It is not used by the command line parser itself. + /// + public string? DocsLink + { + get => s_documentationLinks.TryGetValue(symbol, out var link) ? link : null; + set + { + lock (s_lock) + { + if (string.IsNullOrEmpty(value)) + { + s_documentationLinks.Remove(symbol); + } + else + { + s_documentationLinks[symbol] = value; + } + } + } + } + } + +} diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/Activities.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/Activities.cs index 9f4665572436..d477b8b342f1 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/Activities.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/Activities.cs @@ -6,15 +6,27 @@ namespace Microsoft.DotNet.Cli.Utils; /// -/// Contains helpers for working with Activities in the .NET CLI. +/// Contains helpers for working with Activities in the .NET CLI. /// public static class Activities { - /// /// The main entrypoint for creating Activities in the .NET CLI. /// All activities created in the CLI should use this , to allow /// consumers to easily filter and trace CLI activities. /// public static ActivitySource Source { get; } = new("dotnet-cli", Product.Version); + + /// + /// The environment variable used to transfer the chain of parent activity IDs. + /// This should be used when constructing new sub-processes in order to + /// track spans across calls. + /// + public const string TRACEPARENT = nameof(TRACEPARENT); + /// + /// The environment variable used to transfer the trace state of the parent activities. + /// This should be used when constructing new sub-processes in order to + /// track spans across calls. + /// + public const string TRACESTATE = nameof(TRACESTATE); } diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/Env.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/Env.cs index dbedb133bfc0..c85eaa1d0bf7 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/Env.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/Env.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; + namespace Microsoft.DotNet.Cli.Utils; public static class Env @@ -21,6 +23,9 @@ public static class Env public static bool GetEnvironmentVariableAsBool(string name, bool defaultValue = false) => s_environment.GetEnvironmentVariableAsBool(name, defaultValue); + public static bool TryGetEnvironmentVariableAsBool(string name, [NotNullWhen(true)] out bool value) => + s_environment.TryGetEnvironmentVariableAsBool(name, out value); + public static int? GetEnvironmentVariableAsNullableInt(string name) => s_environment.GetEnvironmentVariableAsNullableInt(name); diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs index 50984a692d1c..06c64b67110a 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using Microsoft.DotNet.Cli.Utils.Extensions; namespace Microsoft.DotNet.Cli.Utils; @@ -136,6 +137,12 @@ public bool GetEnvironmentVariableAsBool(string name, bool defaultValue) return Environment.GetEnvironmentVariable(variable, target); } + public bool TryGetEnvironmentVariable(string name, [NotNullWhen(true)] out string? value) + { + value = Environment.GetEnvironmentVariable(name); + return value != null; + } + public void SetEnvironmentVariable(string variable, string value, EnvironmentVariableTarget target) { Environment.SetEnvironmentVariable(variable, value, target); @@ -150,4 +157,57 @@ public void SetEnvironmentVariable(string variable, string value, EnvironmentVar return null; } + + public bool TryGetEnvironmentVariableAsBool(string name, [NotNullWhen(true)] out bool value) + { + if (TryGetEnvironmentVariable(name, out string? strValue) && + (bool.TryParse(strValue, out bool boolValue) + || TryParseNonBoolConstantStringAsBool(strValue, out boolValue))) + { + value = boolValue; + return true; + } + else + { + value = false; + return false; + } + } + + /// + /// Parses non-boolean constant strings like "1", "0", "yes", "no", "on", "off" as boolean values. + /// + private static bool TryParseNonBoolConstantStringAsBool(string? strValue, out bool value) + { + switch (strValue?.ToLowerInvariant()) + { + case "1": + case "yes": + case "on": + value = true; + return true; + case "0": + case "no": + case "off": + value = false; + return true; + default: + value = false; + return false; + } + } + + public bool TryGetEnvironmentVariableAsInt(string name, [NotNullWhen(true)] out int value) + { + if (TryGetEnvironmentVariable(name, out string? strValue) && int.TryParse(strValue, out int intValue)) + { + value = intValue; + return true; + } + else + { + value = -1; + return false; + } + } } diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/IEnvironmentProvider.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/IEnvironmentProvider.cs index 385f989d4990..8aaacf30fbad 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/IEnvironmentProvider.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/IEnvironmentProvider.cs @@ -3,6 +3,9 @@ namespace Microsoft.DotNet.Cli.Utils; +using System.Diagnostics.CodeAnalysis; + + public interface IEnvironmentProvider { IEnumerable ExecutableExtensions { get; } @@ -19,6 +22,10 @@ public interface IEnvironmentProvider string? GetEnvironmentVariable(string name); + bool TryGetEnvironmentVariable(string name, [NotNullWhen(true)] out string? value); + bool TryGetEnvironmentVariableAsBool(string name, [NotNullWhen(true)] out bool value); + bool TryGetEnvironmentVariableAsInt(string name, [NotNullWhen(true)] out int value); + string? GetEnvironmentVariable(string variable, EnvironmentVariableTarget target); void SetEnvironmentVariable(string variable, string value, EnvironmentVariableTarget target); diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildArgs.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildArgs.cs index 12226f42f0d6..7fedc17f3878 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildArgs.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildArgs.cs @@ -21,7 +21,9 @@ private MSBuildArgs( string[]? getTargetResult, string[]? getResultOutputFile, VerbosityOptions? verbosity, - string[]? otherMSBuildArgs) + bool noLogo, + string[]? otherMSBuildArgs + ) { GlobalProperties = properties; RestoreGlobalProperties = restoreProperties; @@ -31,6 +33,7 @@ private MSBuildArgs( GetTargetResult = getTargetResult; GetResultOutputFile = getResultOutputFile; Verbosity = verbosity; + NoLogo = noLogo; OtherMSBuildArgs = otherMSBuildArgs is not null ? [.. otherMSBuildArgs] : new List(); @@ -51,16 +54,33 @@ private MSBuildArgs( /// public string[]? RequestedTargets { get; } + /// + /// If specified, the list of MSBuild Property names to retrieve and report directly for this build of a single project. + /// public string[]? GetProperty { get; } + /// + /// If specified, the list of MSBuild Item names to retrieve and report directly for this build of a single project. + /// public string[]? GetItem { get; } + /// + /// If specified, the list of MSBuild Target Output/Return Items to retrieve and report directly for this build of a single project. + /// public string[]? GetTargetResult { get; } + /// + /// If specified, the list of output files to which to write --getProperty, --getItem, and --getTargetResult outputs. + /// public string[]? GetResultOutputFile { get; } public VerbosityOptions? Verbosity { get; } + /// + /// Whether or not the MSBuild product header text should be emitted at the start of this build + /// + public bool NoLogo { get; } + /// /// All other arguments that aren't already explicitly modeled by this structure. /// The main categories of these today are logger configurations @@ -89,16 +109,15 @@ public static MSBuildArgs AnalyzeMSBuildArguments(IEnumerable forwardedA } var parseResult = fakeCommand.Parse([.. forwardedAndUserFacingArgs], _analysisParsingConfiguration); - var globalProperties = parseResult.GetResult("--property") is OptionResult propResult ? propResult.GetValueOrDefault?>() : null; - var restoreProperties = parseResult.GetResult("--restoreProperty") is OptionResult restoreResult ? restoreResult.GetValueOrDefault?>() : null; - var requestedTargets = parseResult.GetResult("--target") is OptionResult targetResult ? targetResult.GetValueOrDefault() : null; + var globalProperties = TryGetValue?>("--property"); + var restoreProperties = TryGetValue?>("--restoreProperty"); + var requestedTargets = TryGetValue("--target"); var getProperty = TryGetValue("--getProperty"); var getItem = TryGetValue("--getItem"); var getTargetResult = TryGetValue("--getTargetResult"); var getResultOutputFile = TryGetValue("--getResultOutputFile"); - var verbosity = parseResult.GetResult("--verbosity") is OptionResult verbosityResult - ? verbosityResult.GetValueOrDefault() - : null; + var verbosity = TryGetValue("--verbosity"); + var nologo = TryGetValue("--no-logo") ?? true; // Default to nologo if not specified var otherMSBuildArgs = parseResult.UnmatchedTokens.ToArray(); return new MSBuildArgs( properties: globalProperties, @@ -109,8 +128,12 @@ public static MSBuildArgs AnalyzeMSBuildArguments(IEnumerable forwardedA getTargetResult: getTargetResult, getResultOutputFile: getResultOutputFile, otherMSBuildArgs: otherMSBuildArgs, - verbosity: verbosity); + verbosity: verbosity, + noLogo: nologo); + /// We can't use to check if the names of the options we care + /// about were specified, because if they weren't specified it throws. + /// So we first check if the option was specified, and only then get its value. T? TryGetValue(string name) { return options.Any(o => o.Name == name) ? parseResult.GetValue(name) : default; @@ -120,19 +143,19 @@ public static MSBuildArgs AnalyzeMSBuildArguments(IEnumerable forwardedA public static MSBuildArgs FromProperties(ReadOnlyDictionary? properties) { - return new MSBuildArgs(properties, null, null, null, null, null, null, null, null); + return new MSBuildArgs(properties, null, null, null, null, null, null, null, noLogo: false, null); } public static MSBuildArgs FromOtherArgs(params ReadOnlySpan args) { - return new MSBuildArgs(null, null, null, null, null, null, null, null, args.ToArray()); + return new MSBuildArgs(null, null, null, null, null, null, null, null, noLogo: false, args.ToArray()); } public static MSBuildArgs FromVerbosity(VerbosityOptions verbosity) { - return new MSBuildArgs(null, null, null, null, null, null, null, verbosity, null); + return new MSBuildArgs(null, null, null, null, null, null, null, verbosity, noLogo: false, null); } - public static readonly MSBuildArgs ForHelp = new(null, null, null, null, null, null, null, null, ["--help"]); + public static readonly MSBuildArgs ForHelp = new(null, null, null, null, null, null, null, null, noLogo: true, ["--help"]); /// /// Completely replaces the MSBuild arguments with the provided . @@ -148,6 +171,7 @@ public MSBuildArgs CloneWithExplicitArgs(string[] newArgs) getTargetResult: GetTargetResult, getResultOutputFile: GetResultOutputFile, otherMSBuildArgs: newArgs, + noLogo: NoLogo, verbosity: Verbosity); } @@ -168,6 +192,7 @@ public MSBuildArgs CloneWithAdditionalArgs(params string[] additionalArgs) GetTargetResult, GetResultOutputFile, Verbosity, + NoLogo, OtherMSBuildArgs.ToArray()); } @@ -180,6 +205,7 @@ public MSBuildArgs CloneWithAdditionalArgs(params string[] additionalArgs) GetTargetResult, GetResultOutputFile, Verbosity, + NoLogo, [.. OtherMSBuildArgs, .. additionalArgs]); } @@ -197,6 +223,7 @@ public MSBuildArgs CloneWithAdditionalRestoreProperties(ReadOnlyDictionary additi GetTargetResult, GetResultOutputFile, Verbosity, + NoLogo, OtherMSBuildArgs.ToArray()); } @@ -305,6 +338,22 @@ public MSBuildArgs CloneWithVerbosity(VerbosityOptions newVerbosity) GetTargetResult, GetResultOutputFile, newVerbosity, + NoLogo, + OtherMSBuildArgs.ToArray()); + } + + public MSBuildArgs CloneWithNoLogo(bool noLogo) + { + return new MSBuildArgs( + GlobalProperties, + RestoreGlobalProperties, + RequestedTargets, + GetProperty, + GetItem, + GetTargetResult, + GetResultOutputFile, + Verbosity, + noLogo, OtherMSBuildArgs.ToArray()); } diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs index e0dca709ac3c..87d021eda157 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs @@ -44,17 +44,11 @@ public static string MSBuildVersion private readonly List _msbuildRequiredParameters = ["-maxcpucount", $"--verbosity:{DefaultVerbosity}"]; - public MSBuildForwardingAppWithoutLogging(MSBuildArgs msbuildArgs, string? msbuildPath = null, bool includeLogo = false, bool isRestoring = true) + public MSBuildForwardingAppWithoutLogging(MSBuildArgs msbuildArgs, string? msbuildPath = null) { string defaultMSBuildPath = GetMSBuildExePath(); _msbuildArgs = msbuildArgs; - if (!includeLogo && !msbuildArgs.OtherMSBuildArgs.Contains("-nologo", StringComparer.OrdinalIgnoreCase)) - { - // If the user didn't explicitly ask for -nologo, we add it to avoid the MSBuild logo. - // This is useful for scenarios like restore where we don't want to print the logo. - // Note that this is different from the default behavior of MSBuild, which prints the logo. - msbuildArgs.OtherMSBuildArgs.Add("-nologo"); - } + string? tlpDefault = TerminalLoggerDefault; if (string.IsNullOrWhiteSpace(tlpDefault)) { @@ -101,6 +95,7 @@ private string[] EmitMSBuildArgs(MSBuildArgs msbuildArgs) => [ .. msbuildArgs.RestoreGlobalProperties?.Select(kvp => EmitProperty(kvp, "restoreProperty")) ?? [], .. msbuildArgs.RequestedTargets?.Select(target => $"--target:{target}") ?? [], .. msbuildArgs.Verbosity is not null ? new string[1] { $"--verbosity:{msbuildArgs.Verbosity}" } : [], + .. msbuildArgs.NoLogo is true ? new string[1] { "--nologo" } : [], .. msbuildArgs.OtherMSBuildArgs ]; diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/Polyfills.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/Polyfills.cs index ab674a2906ee..59668dbae394 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/Polyfills.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/Polyfills.cs @@ -4,11 +4,24 @@ #if NET472 #pragma warning disable IDE0130 // Namespace does not match folder structure -namespace System.Runtime.CompilerServices; -#pragma warning restore IDE0130 // Namespace does not match folder structure +namespace System.Runtime.CompilerServices { + + internal static class IsExternalInit + { + } + +} -internal static class IsExternalInit +namespace System.Diagnostics.CodeAnalysis { + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute : Attribute + { + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + public bool ReturnValue { get; } + } } +#pragma warning restore IDE0130 // Namespace does not match folder structure #endif diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/.editorconfig b/src/Cli/Microsoft.DotNet.FileBasedPrograms/.editorconfig new file mode 100644 index 000000000000..5aebb5380316 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/.editorconfig @@ -0,0 +1,20 @@ +# Note: this editorconfig is *only* used during local builds. +# The actual source package will include 'eng\config\SourcePackage.editorconfig' or similar per-TFM config to control analyzer behavior at the consumption side. + +[*.cs] +# IDE0240: Remove redundant nullable directive +# The directive needs to be included since all sources in a source package are considered generated code +# when referenced from a project via package reference. +dotnet_diagnostic.IDE0240.severity = none + +dotnet_diagnostic.RS0051.severity = warning # Add internal types and members to the declared API +dotnet_diagnostic.RS0052.severity = warning # Remove deleted types and members from the declared internal API +dotnet_diagnostic.RS0053.severity = warning # The contents of the internal API files are invalid +dotnet_diagnostic.RS0054.severity = warning # Do not duplicate symbols in internal API files +dotnet_diagnostic.RS0055.severity = warning # Annotate nullability of internal types and members in the declared API +dotnet_diagnostic.RS0056.severity = warning # Enable tracking of nullability of reference types in the declared API +dotnet_diagnostic.RS0057.severity = warning # Internal members should not use oblivious types +dotnet_diagnostic.RS0058.severity = warning # Missing shipped or unshipped internal API file +dotnet_diagnostic.RS0059.severity = warning # Do not add multiple public overloads with optional parameters +dotnet_diagnostic.RS0060.severity = warning # API with optional parameter(s) should have the most parameters amongst its public overloads +dotnet_diagnostic.RS0061.severity = warning # Constructor make noninheritable base class inheritable diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/ExternalHelpers.cs b/src/Cli/Microsoft.DotNet.FileBasedPrograms/ExternalHelpers.cs new file mode 100644 index 000000000000..6b554d381eed --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/ExternalHelpers.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable +using System; +using System.IO; + +namespace Microsoft.DotNet.FileBasedPrograms; + +/// +/// When targeting netstandard2.0, the user of the source package must "implement" certain methods by declaring members in this type. +/// +internal partial class ExternalHelpers +{ + public static partial int CombineHashCodes(int value1, int value2); + public static partial string GetRelativePath(string relativeTo, string path); + + public static partial bool IsPathFullyQualified(string path); + +#if NET + public static partial int CombineHashCodes(int value1, int value2) + => HashCode.Combine(value1, value2); + + public static partial string GetRelativePath(string relativeTo, string path) + => Path.GetRelativePath(relativeTo, path); + + public static partial bool IsPathFullyQualified(string path) + => Path.IsPathFullyQualified(path); + +#elif FILE_BASED_PROGRAMS_SOURCE_PACKAGE_BUILD + // This path should only be used when we are verifying that the source package itself builds under netstandard2.0. + public static partial int CombineHashCodes(int value1, int value2) + => throw new NotImplementedException(); + + public static partial string GetRelativePath(string relativeTo, string path) + => throw new NotImplementedException(); + + public static partial bool IsPathFullyQualified(string path) + => throw new NotImplementedException(); + +#endif +} + +// https://github.com/dotnet/sdk/issues/51487: Remove usage of GracefulException from the source package +#if FILE_BASED_PROGRAMS_SOURCE_PACKAGE_GRACEFUL_EXCEPTION +internal class GracefulException : Exception +{ + public GracefulException() + { + } + + public GracefulException(string? message) : base(message) + { + } + + public GracefulException(string format, string arg) : this(string.Format(format, arg)) + { + } + + public GracefulException(string? message, Exception? innerException) : base(message, innerException) + { + } +} +#endif diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileBasedProgramsResources.resx b/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileBasedProgramsResources.resx new file mode 100644 index 000000000000..1161700f99d9 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileBasedProgramsResources.resx @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Could not find any project in `{0}`. + + + Could not find project or directory `{0}`. + + + Found more than one project in `{0}`. Specify which one to use. + + + Invalid property name: {0} + {0} is an inner exception message. + + + The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue'. + {Locked="#:property"} + + + Static graph restore is not supported for file-based apps. Remove the '#:property'. + {Locked="#:property"} + + + error + Used when reporting directive errors like "file(location): error: message". + + + The directive should contain a name without special characters and an optional value separated by '{1}' like '#:{0} Name{1}Value'. + {0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='. + + + Some directives cannot be converted. Run the file to see all compilation errors. Specify '--force' to convert anyway. + {Locked="--force"} + + + Duplicate directives are not supported: {0} + {0} is the directive type and name. + + + Directives currently cannot contain double quotes ("). + + + The '#:project' directive is invalid: {0} + {0} is the inner error message. + + + Missing name of '{0}'. + {0} is the directive name like 'package' or 'sdk'. + + + Unrecognized directive '{0}'. + {0} is the directive name like 'package' or 'sdk'. + + \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs b/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs new file mode 100644 index 000000000000..22fd4000c0ff --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs @@ -0,0 +1,625 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; +using System.Xml; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +// https://github.com/dotnet/sdk/issues/51487: Remove usage of GracefulException from the source package +#if !FILE_BASED_PROGRAMS_SOURCE_PACKAGE_GRACEFUL_EXCEPTION +using Microsoft.DotNet.Cli.Utils; +#endif + +namespace Microsoft.DotNet.FileBasedPrograms; + +// https://github.com/dotnet/sdk/issues/51487: Use 'file class' where appropriate to reduce exposed internal API surface +internal static class FileLevelDirectiveHelpers +{ + public static SyntaxTokenParser CreateTokenizer(SourceText text) + { + return SyntaxFactory.CreateTokenParser(text, + CSharpParseOptions.Default.WithFeatures([new("FileBasedProgram", "true")])); + } + + /// + /// If , the whole is parsed to find diagnostics about every app directive. + /// Otherwise, only directives up to the first C# token is checked. + /// The former is useful for dotnet project convert where we want to report all errors because it would be difficult to fix them up after the conversion. + /// The latter is useful for dotnet run file.cs where if there are app directives after the first token, + /// compiler reports anyway, so we speed up success scenarios by not parsing the whole file up front in the SDK CLI. + /// + public static ImmutableArray FindDirectives(SourceFile sourceFile, bool reportAllErrors, DiagnosticBag diagnostics) + { + var builder = ImmutableArray.CreateBuilder(); + var tokenizer = CreateTokenizer(sourceFile.Text); + + var result = tokenizer.ParseLeadingTrivia(); + var triviaList = result.Token.LeadingTrivia; + + FindLeadingDirectives(sourceFile, triviaList, diagnostics, builder); + + // In conversion mode, we want to report errors for any invalid directives in the rest of the file + // so users don't end up with invalid directives in the converted project. + if (reportAllErrors) + { + tokenizer.ResetTo(result); + + do + { + result = tokenizer.ParseNextToken(); + + foreach (var trivia in result.Token.LeadingTrivia) + { + ReportErrorFor(trivia); + } + + foreach (var trivia in result.Token.TrailingTrivia) + { + ReportErrorFor(trivia); + } + } + while (!result.Token.IsKind(SyntaxKind.EndOfFileToken)); + } + + void ReportErrorFor(SyntaxTrivia trivia) + { + if (trivia.ContainsDiagnostics && trivia.IsKind(SyntaxKind.IgnoredDirectiveTrivia)) + { + diagnostics.AddError(sourceFile, trivia.Span, FileBasedProgramsResources.CannotConvertDirective); + } + } + + // The result should be ordered by source location, RemoveDirectivesFromFile depends on that. + return builder.ToImmutable(); + } + + /// Finds file-level directives in the leading trivia list of a compilation unit and reports diagnostics on them. + /// The builder to store the parsed directives in, or null if the parsed directives are not needed. + public static void FindLeadingDirectives( + SourceFile sourceFile, + SyntaxTriviaList triviaList, + DiagnosticBag diagnostics, + ImmutableArray.Builder? builder) + { + Debug.Assert(triviaList.Span.Start == 0); + + var deduplicated = new Dictionary(NamedDirectiveComparer.Instance); + TextSpan previousWhiteSpaceSpan = default; + + for (var index = 0; index < triviaList.Count; index++) + { + var trivia = triviaList[index]; + // Stop when the trivia contains an error (e.g., because it's after #if). + if (trivia.ContainsDiagnostics) + { + break; + } + + if (trivia.IsKind(SyntaxKind.WhitespaceTrivia)) + { + Debug.Assert(previousWhiteSpaceSpan.IsEmpty); + previousWhiteSpaceSpan = trivia.FullSpan; + continue; + } + + if (trivia.IsKind(SyntaxKind.ShebangDirectiveTrivia)) + { + TextSpan span = GetFullSpan(previousWhiteSpaceSpan, trivia); + + var whiteSpace = GetWhiteSpaceInfo(triviaList, index); + var info = new CSharpDirective.ParseInfo + { + Span = span, + LeadingWhiteSpace = whiteSpace.Leading, + TrailingWhiteSpace = whiteSpace.Trailing, + }; + builder?.Add(new CSharpDirective.Shebang(info)); + } + else if (trivia.IsKind(SyntaxKind.IgnoredDirectiveTrivia)) + { + TextSpan span = GetFullSpan(previousWhiteSpaceSpan, trivia); + + var message = trivia.GetStructure() is IgnoredDirectiveTriviaSyntax { Content: { RawKind: (int)SyntaxKind.StringLiteralToken } content } + ? content.Text.AsSpan().Trim() + : ""; + var parts = Patterns.Whitespace.Split(message.ToString(), 2); + var name = parts.Length > 0 ? parts[0] : ""; + var value = parts.Length > 1 ? parts[1] : ""; + Debug.Assert(!(parts.Length > 2)); + + var whiteSpace = GetWhiteSpaceInfo(triviaList, index); + var context = new CSharpDirective.ParseContext + { + Info = new() + { + Span = span, + LeadingWhiteSpace = whiteSpace.Leading, + TrailingWhiteSpace = whiteSpace.Trailing, + }, + Diagnostics = diagnostics, + SourceFile = sourceFile, + DirectiveKind = name, + DirectiveText = value, + }; + + // Block quotes now so we can later support quoted values without a breaking change. https://github.com/dotnet/sdk/issues/49367 + if (value.Contains('"')) + { + diagnostics.AddError(sourceFile, context.Info.Span, FileBasedProgramsResources.QuoteInDirective); + } + + if (CSharpDirective.Parse(context) is { } directive) + { + // If the directive is already present, report an error. + if (deduplicated.ContainsKey(directive)) + { + var existingDirective = deduplicated[directive]; + var typeAndName = $"#:{existingDirective.GetType().Name.ToLowerInvariant()} {existingDirective.Name}"; + diagnostics.AddError(sourceFile, directive.Info.Span, string.Format(FileBasedProgramsResources.DuplicateDirective, typeAndName)); + } + else + { + deduplicated.Add(directive, directive); + } + + builder?.Add(directive); + } + } + + previousWhiteSpaceSpan = default; + } + + return; + + static TextSpan GetFullSpan(TextSpan previousWhiteSpaceSpan, SyntaxTrivia trivia) + { + // Include the preceding whitespace in the span, i.e., span will be the whole line. + return previousWhiteSpaceSpan.IsEmpty ? trivia.FullSpan : TextSpan.FromBounds(previousWhiteSpaceSpan.Start, trivia.FullSpan.End); + } + + static (WhiteSpaceInfo Leading, WhiteSpaceInfo Trailing) GetWhiteSpaceInfo(in SyntaxTriviaList triviaList, int index) + { + (WhiteSpaceInfo Leading, WhiteSpaceInfo Trailing) result = default; + + for (int i = index - 1; i >= 0; i--) + { + if (!Fill(ref result.Leading, triviaList, i)) break; + } + + for (int i = index + 1; i < triviaList.Count; i++) + { + if (!Fill(ref result.Trailing, triviaList, i)) break; + } + + return result; + + static bool Fill(ref WhiteSpaceInfo info, in SyntaxTriviaList triviaList, int index) + { + var trivia = triviaList[index]; + if (trivia.IsKind(SyntaxKind.EndOfLineTrivia)) + { + info.LineBreaks += 1; + info.TotalLength += trivia.FullSpan.Length; + return true; + } + + if (trivia.IsKind(SyntaxKind.WhitespaceTrivia)) + { + info.TotalLength += trivia.FullSpan.Length; + return true; + } + + return false; + } + } + } +} + +internal readonly record struct SourceFile(string Path, SourceText Text) +{ + public static SourceFile Load(string filePath) + { + using var stream = File.OpenRead(filePath); + return new SourceFile(filePath, SourceText.From(stream, Encoding.UTF8)); + } + + public SourceFile WithText(SourceText newText) + { + return new SourceFile(Path, newText); + } + + public void Save() + { + using var stream = File.Open(Path, FileMode.Create, FileAccess.Write); + using var writer = new StreamWriter(stream, Encoding.UTF8); + Text.Write(writer); + } + + public FileLinePositionSpan GetFileLinePositionSpan(TextSpan span) + { + return new FileLinePositionSpan(Path, Text.Lines.GetLinePositionSpan(span)); + } + + public string GetLocationString(TextSpan span) + { + var positionSpan = GetFileLinePositionSpan(span); + return $"{positionSpan.Path}({positionSpan.StartLinePosition.Line + 1})"; + } +} + +internal static partial class Patterns +{ + public static Regex Whitespace { get; } = new Regex("""\s+""", RegexOptions.Compiled); + + public static Regex DisallowedNameCharacters { get; } = new Regex("""[\s@=/]""", RegexOptions.Compiled); + + public static Regex EscapedCompilerOption { get; } = new Regex("""^/\w+:".*"$""", RegexOptions.Compiled | RegexOptions.Singleline); +} + +internal struct WhiteSpaceInfo +{ + public int LineBreaks; + public int TotalLength; +} + +/// +/// Represents a C# directive starting with #: (a.k.a., "file-level directive"). +/// Those are ignored by the language but recognized by us. +/// +internal abstract class CSharpDirective(in CSharpDirective.ParseInfo info) +{ + public ParseInfo Info { get; } = info; + + public readonly struct ParseInfo + { + /// + /// Span of the full line including the trailing line break. + /// + public required TextSpan Span { get; init; } + public required WhiteSpaceInfo LeadingWhiteSpace { get; init; } + public required WhiteSpaceInfo TrailingWhiteSpace { get; init; } + } + + public readonly struct ParseContext + { + public required ParseInfo Info { get; init; } + public required DiagnosticBag Diagnostics { get; init; } + public required SourceFile SourceFile { get; init; } + public required string DirectiveKind { get; init; } + public required string DirectiveText { get; init; } + } + + public static Named? Parse(in ParseContext context) + { + return context.DirectiveKind switch + { + "sdk" => Sdk.Parse(context), + "property" => Property.Parse(context), + "package" => Package.Parse(context), + "project" => Project.Parse(context), + var other => context.Diagnostics.AddError(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.UnrecognizedDirective, other)), + }; + } + + private static (string, string?)? ParseOptionalTwoParts(in ParseContext context, char separator) + { + var separatorIndex = context.DirectiveText.IndexOf(separator); + var firstPart = (separatorIndex < 0 ? context.DirectiveText : context.DirectiveText.AsSpan(0, separatorIndex)).TrimEnd(); + + string directiveKind = context.DirectiveKind; + if (firstPart.IsWhiteSpace()) + { + return context.Diagnostics.AddError<(string, string?)?>(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.MissingDirectiveName, directiveKind)); + } + + // If the name contains characters that resemble separators, report an error to avoid any confusion. + if (Patterns.DisallowedNameCharacters.Match(context.DirectiveText, beginning: 0, length: firstPart.Length).Success) + { + return context.Diagnostics.AddError<(string, string?)?>(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.InvalidDirectiveName, directiveKind, separator)); + } + + if (separatorIndex < 0) + { + return (firstPart.ToString(), null); + } + + var secondPart = context.DirectiveText.AsSpan(separatorIndex + 1).TrimStart(); + if (secondPart.IsWhiteSpace()) + { + Debug.Assert(secondPart.Length == 0, + "We have trimmed the second part, so if it's white space, it should be actually empty."); + + return (firstPart.ToString(), string.Empty); + } + + return (firstPart.ToString(), secondPart.ToString()); + } + + public abstract override string ToString(); + + /// + /// #! directive. + /// + public sealed class Shebang(in ParseInfo info) : CSharpDirective(info) + { + public override string ToString() => "#!"; + } + + public abstract class Named(in ParseInfo info) : CSharpDirective(info) + { + public required string Name { get; init; } + } + + /// + /// #:sdk directive. + /// + public sealed class Sdk(in ParseInfo info) : Named(info) + { + public string? Version { get; init; } + + public static new Sdk? Parse(in ParseContext context) + { + if (ParseOptionalTwoParts(context, separator: '@') is not var (sdkName, sdkVersion)) + { + return null; + } + + return new Sdk(context.Info) + { + Name = sdkName, + Version = sdkVersion, + }; + } + + public override string ToString() => Version is null ? $"#:sdk {Name}" : $"#:sdk {Name}@{Version}"; + } + + /// + /// #:property directive. + /// + public sealed class Property(in ParseInfo info) : Named(info) + { + public required string Value { get; init; } + + public static new Property? Parse(in ParseContext context) + { + if (ParseOptionalTwoParts(context, separator: '=') is not var (propertyName, propertyValue)) + { + return null; + } + + if (propertyValue is null) + { + return context.Diagnostics.AddError(context.SourceFile, context.Info.Span, FileBasedProgramsResources.PropertyDirectiveMissingParts); + } + + try + { + propertyName = XmlConvert.VerifyName(propertyName); + } + catch (XmlException ex) + { + return context.Diagnostics.AddError(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.PropertyDirectiveInvalidName, ex.Message), ex); + } + + if (propertyName.Equals("RestoreUseStaticGraphEvaluation", StringComparison.OrdinalIgnoreCase) && + MSBuildUtilities.ConvertStringToBool(propertyValue)) + { + context.Diagnostics.AddError(context.SourceFile, context.Info.Span, FileBasedProgramsResources.StaticGraphRestoreNotSupported); + } + + return new Property(context.Info) + { + Name = propertyName, + Value = propertyValue, + }; + } + + public override string ToString() => $"#:property {Name}={Value}"; + } + + /// + /// #:package directive. + /// + public sealed class Package(in ParseInfo info) : Named(info) + { + public string? Version { get; init; } + + public static new Package? Parse(in ParseContext context) + { + if (ParseOptionalTwoParts(context, separator: '@') is not var (packageName, packageVersion)) + { + return null; + } + + return new Package(context.Info) + { + Name = packageName, + Version = packageVersion, + }; + } + + public override string ToString() => Version is null ? $"#:package {Name}" : $"#:package {Name}@{Version}"; + } + + /// + /// #:project directive. + /// + public sealed class Project(in ParseInfo info) : Named(info) + { + public static new Project? Parse(in ParseContext context) + { + var directiveText = context.DirectiveText; + if (directiveText.IsWhiteSpace()) + { + string directiveKind = context.DirectiveKind; + return context.Diagnostics.AddError(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.MissingDirectiveName, directiveKind)); + } + + try + { + // If the path is a directory like '../lib', transform it to a project file path like '../lib/lib.csproj'. + // Also normalize blackslashes to forward slashes to ensure the directive works on all platforms. + // https://github.com/dotnet/sdk/issues/51487: Behavior should not depend on process current directory + var sourceDirectory = Path.GetDirectoryName(context.SourceFile.Path) ?? "."; + var resolvedProjectPath = Path.Combine(sourceDirectory, directiveText.Replace('\\', '/')); + if (Directory.Exists(resolvedProjectPath)) + { + var fullFilePath = GetProjectFileFromDirectory(resolvedProjectPath).FullName; + + // Keep a relative path only if the original directive was a relative path. + directiveText = ExternalHelpers.IsPathFullyQualified(directiveText) + ? fullFilePath + : ExternalHelpers.GetRelativePath(relativeTo: sourceDirectory, fullFilePath); + } + else if (!File.Exists(resolvedProjectPath)) + { + throw new GracefulException(FileBasedProgramsResources.CouldNotFindProjectOrDirectory, resolvedProjectPath); + } + } + catch (GracefulException e) + { + context.Diagnostics.AddError(context.SourceFile, context.Info.Span, string.Format(FileBasedProgramsResources.InvalidProjectDirective, e.Message), e); + } + + return new Project(context.Info) + { + Name = directiveText, + }; + } + + public Project WithName(string name) + { + return new Project(Info) { Name = name }; + } + + // https://github.com/dotnet/sdk/issues/51487: Delete copies of methods from MsbuildProject and MSBuildUtilities from the source package, sharing the original method(s) under src/Cli instead. + public static FileInfo GetProjectFileFromDirectory(string projectDirectory) + { + DirectoryInfo dir; + try + { + dir = new DirectoryInfo(projectDirectory); + } + catch (ArgumentException) + { + throw new GracefulException(FileBasedProgramsResources.CouldNotFindProjectOrDirectory, projectDirectory); + } + + if (!dir.Exists) + { + throw new GracefulException(FileBasedProgramsResources.CouldNotFindProjectOrDirectory, projectDirectory); + } + + FileInfo[] files = dir.GetFiles("*proj"); + if (files.Length == 0) + { + throw new GracefulException( + FileBasedProgramsResources.CouldNotFindAnyProjectInDirectory, + projectDirectory); + } + + if (files.Length > 1) + { + throw new GracefulException(FileBasedProgramsResources.MoreThanOneProjectInDirectory, projectDirectory); + } + + return files.First(); + } + + public override string ToString() => $"#:project {Name}"; + } +} + +/// +/// Used for deduplication - compares directives by their type and name (ignoring case). +/// +internal sealed class NamedDirectiveComparer : IEqualityComparer +{ + public static readonly NamedDirectiveComparer Instance = new(); + + private NamedDirectiveComparer() { } + + public bool Equals(CSharpDirective.Named? x, CSharpDirective.Named? y) + { + if (ReferenceEquals(x, y)) return true; + + if (x is null || y is null) return false; + + return x.GetType() == y.GetType() && + StringComparer.OrdinalIgnoreCase.Equals(x.Name, y.Name); + } + + public int GetHashCode(CSharpDirective.Named obj) + { + return ExternalHelpers.CombineHashCodes( + obj.GetType().GetHashCode(), + StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Name)); + } +} + +internal sealed class SimpleDiagnostic +{ + public required Position Location { get; init; } + public required string Message { get; init; } + + /// + /// An adapter of that ensures we JSON-serialize only the necessary fields. + /// + /// + /// note: this type is only serialized for run-api scenarios. + /// If/when run-api is removed, we would also want to remove the usage of System.Text.Json attributes. + /// + public readonly struct Position + { + public required string Path { get; init; } + public required LinePositionSpan Span { get; init; } + [JsonIgnore] + public TextSpan TextSpan { get; init; } + } +} + +internal readonly struct DiagnosticBag +{ + public bool IgnoreDiagnostics { get; private init; } + + /// + /// If and is , the first diagnostic is thrown as . + /// + public ImmutableArray.Builder? Builder { get; private init; } + + public static DiagnosticBag ThrowOnFirst() => default; + public static DiagnosticBag Collect(out ImmutableArray.Builder builder) => new() { Builder = builder = ImmutableArray.CreateBuilder() }; + public static DiagnosticBag Ignore() => new() { IgnoreDiagnostics = true, Builder = null }; + + public void AddError(SourceFile sourceFile, TextSpan textSpan, string message, Exception? inner = null) + { + if (Builder != null) + { + Debug.Assert(!IgnoreDiagnostics); + Builder.Add(new SimpleDiagnostic { Location = new SimpleDiagnostic.Position() { Path = sourceFile.Path, TextSpan = textSpan, Span = sourceFile.GetFileLinePositionSpan(textSpan).Span }, Message = message }); + } + else if (!IgnoreDiagnostics) + { + throw new GracefulException($"{sourceFile.GetLocationString(textSpan)}: {FileBasedProgramsResources.DirectiveError}: {message}", inner); + } + } + + public T? AddError(SourceFile sourceFile, TextSpan span, string message, Exception? inner = null) + { + AddError(sourceFile, span, message, inner); + return default; + } +} diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Shipped.txt b/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Shipped.txt new file mode 100644 index 000000000000..7dc5c58110bf --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Unshipped.txt b/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Unshipped.txt new file mode 100644 index 000000000000..84565c88284c --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Unshipped.txt @@ -0,0 +1,127 @@ +Microsoft.DotNet.FileBasedPrograms.CSharpDirective +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.CSharpDirective(in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo info) -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Info.get -> Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Named +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Named.Name.get -> string! +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Named.Name.init -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Named.Named(in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo info) -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Package +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Package.Package(in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo info) -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Package.Version.get -> string? +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Package.Version.init -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.Diagnostics.get -> Microsoft.DotNet.FileBasedPrograms.DiagnosticBag +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.Diagnostics.init -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.DirectiveKind.get -> string! +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.DirectiveKind.init -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.DirectiveText.get -> string! +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.DirectiveText.init -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.Info.get -> Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.Info.init -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.ParseContext() -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.SourceFile.get -> Microsoft.DotNet.FileBasedPrograms.SourceFile +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext.SourceFile.init -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo.LeadingWhiteSpace.get -> Microsoft.DotNet.FileBasedPrograms.WhiteSpaceInfo +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo.LeadingWhiteSpace.init -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo.ParseInfo() -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo.Span.get -> Microsoft.CodeAnalysis.Text.TextSpan +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo.Span.init -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo.TrailingWhiteSpace.get -> Microsoft.DotNet.FileBasedPrograms.WhiteSpaceInfo +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo.TrailingWhiteSpace.init -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project.Project(in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo info) -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project.WithName(string! name) -> Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project! +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Property +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Property.Property(in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo info) -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Property.Value.get -> string! +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Property.Value.init -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Sdk +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Sdk.Sdk(in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo info) -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Sdk.Version.get -> string? +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Sdk.Version.init -> void +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Shebang +Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Shebang.Shebang(in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseInfo info) -> void +Microsoft.DotNet.FileBasedPrograms.DiagnosticBag +Microsoft.DotNet.FileBasedPrograms.DiagnosticBag.AddError(Microsoft.DotNet.FileBasedPrograms.SourceFile sourceFile, Microsoft.CodeAnalysis.Text.TextSpan textSpan, string! message, System.Exception? inner = null) -> void +Microsoft.DotNet.FileBasedPrograms.DiagnosticBag.AddError(Microsoft.DotNet.FileBasedPrograms.SourceFile sourceFile, Microsoft.CodeAnalysis.Text.TextSpan span, string! message, System.Exception? inner = null) -> T? +Microsoft.DotNet.FileBasedPrograms.DiagnosticBag.Builder.get -> System.Collections.Immutable.ImmutableArray.Builder? +Microsoft.DotNet.FileBasedPrograms.DiagnosticBag.DiagnosticBag() -> void +Microsoft.DotNet.FileBasedPrograms.DiagnosticBag.IgnoreDiagnostics.get -> bool +Microsoft.DotNet.FileBasedPrograms.ExternalHelpers +Microsoft.DotNet.FileBasedPrograms.ExternalHelpers.ExternalHelpers() -> void +Microsoft.DotNet.FileBasedPrograms.FileLevelDirectiveHelpers +Microsoft.DotNet.FileBasedPrograms.GracefulException +Microsoft.DotNet.FileBasedPrograms.GracefulException.GracefulException() -> void +Microsoft.DotNet.FileBasedPrograms.GracefulException.GracefulException(string! format, string! arg) -> void +Microsoft.DotNet.FileBasedPrograms.GracefulException.GracefulException(string? message) -> void +Microsoft.DotNet.FileBasedPrograms.GracefulException.GracefulException(string? message, System.Exception? innerException) -> void +Microsoft.DotNet.FileBasedPrograms.MSBuildUtilities +Microsoft.DotNet.FileBasedPrograms.MSBuildUtilities.MSBuildUtilities() -> void +Microsoft.DotNet.FileBasedPrograms.NamedDirectiveComparer +Microsoft.DotNet.FileBasedPrograms.NamedDirectiveComparer.Equals(Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Named? x, Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Named? y) -> bool +Microsoft.DotNet.FileBasedPrograms.NamedDirectiveComparer.GetHashCode(Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Named! obj) -> int +Microsoft.DotNet.FileBasedPrograms.Patterns +Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic +Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.Location.get -> Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.Position +Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.Location.init -> void +Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.Message.get -> string! +Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.Message.init -> void +Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.Position +Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.Position.Path.get -> string! +Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.Position.Path.init -> void +Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.Position.Position() -> void +Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.Position.Span.get -> Microsoft.CodeAnalysis.Text.LinePositionSpan +Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.Position.Span.init -> void +Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.Position.TextSpan.get -> Microsoft.CodeAnalysis.Text.TextSpan +Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.Position.TextSpan.init -> void +Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.SimpleDiagnostic() -> void +Microsoft.DotNet.FileBasedPrograms.SourceFile +Microsoft.DotNet.FileBasedPrograms.SourceFile.Deconstruct(out string! Path, out Microsoft.CodeAnalysis.Text.SourceText! Text) -> void +Microsoft.DotNet.FileBasedPrograms.SourceFile.Equals(Microsoft.DotNet.FileBasedPrograms.SourceFile other) -> bool +Microsoft.DotNet.FileBasedPrograms.SourceFile.GetFileLinePositionSpan(Microsoft.CodeAnalysis.Text.TextSpan span) -> Microsoft.CodeAnalysis.FileLinePositionSpan +Microsoft.DotNet.FileBasedPrograms.SourceFile.GetLocationString(Microsoft.CodeAnalysis.Text.TextSpan span) -> string! +Microsoft.DotNet.FileBasedPrograms.SourceFile.Path.get -> string! +Microsoft.DotNet.FileBasedPrograms.SourceFile.Path.init -> void +Microsoft.DotNet.FileBasedPrograms.SourceFile.Save() -> void +Microsoft.DotNet.FileBasedPrograms.SourceFile.SourceFile() -> void +Microsoft.DotNet.FileBasedPrograms.SourceFile.SourceFile(string! Path, Microsoft.CodeAnalysis.Text.SourceText! Text) -> void +Microsoft.DotNet.FileBasedPrograms.SourceFile.Text.get -> Microsoft.CodeAnalysis.Text.SourceText! +Microsoft.DotNet.FileBasedPrograms.SourceFile.Text.init -> void +Microsoft.DotNet.FileBasedPrograms.SourceFile.WithText(Microsoft.CodeAnalysis.Text.SourceText! newText) -> Microsoft.DotNet.FileBasedPrograms.SourceFile +Microsoft.DotNet.FileBasedPrograms.WhiteSpaceInfo +Microsoft.DotNet.FileBasedPrograms.WhiteSpaceInfo.LineBreaks -> int +Microsoft.DotNet.FileBasedPrograms.WhiteSpaceInfo.TotalLength -> int +Microsoft.DotNet.FileBasedPrograms.WhiteSpaceInfo.WhiteSpaceInfo() -> void +override abstract Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ToString() -> string! +override Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Package.ToString() -> string! +override Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project.ToString() -> string! +override Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Property.ToString() -> string! +override Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Sdk.ToString() -> string! +override Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Shebang.ToString() -> string! +override Microsoft.DotNet.FileBasedPrograms.SourceFile.GetHashCode() -> int +static Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Package.Parse(in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext context) -> Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Package? +static Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Parse(in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext context) -> Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Named? +static Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project.GetProjectFileFromDirectory(string! projectDirectory) -> System.IO.FileInfo! +static Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project.Parse(in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext context) -> Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Project? +static Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Property.Parse(in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext context) -> Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Property? +static Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Sdk.Parse(in Microsoft.DotNet.FileBasedPrograms.CSharpDirective.ParseContext context) -> Microsoft.DotNet.FileBasedPrograms.CSharpDirective.Sdk? +static Microsoft.DotNet.FileBasedPrograms.DiagnosticBag.Collect(out System.Collections.Immutable.ImmutableArray.Builder! builder) -> Microsoft.DotNet.FileBasedPrograms.DiagnosticBag +static Microsoft.DotNet.FileBasedPrograms.DiagnosticBag.Ignore() -> Microsoft.DotNet.FileBasedPrograms.DiagnosticBag +static Microsoft.DotNet.FileBasedPrograms.DiagnosticBag.ThrowOnFirst() -> Microsoft.DotNet.FileBasedPrograms.DiagnosticBag +static Microsoft.DotNet.FileBasedPrograms.ExternalHelpers.CombineHashCodes(int value1, int value2) -> int +static Microsoft.DotNet.FileBasedPrograms.ExternalHelpers.GetRelativePath(string! relativeTo, string! path) -> string! +static Microsoft.DotNet.FileBasedPrograms.ExternalHelpers.IsPathFullyQualified(string! path) -> bool +static Microsoft.DotNet.FileBasedPrograms.FileLevelDirectiveHelpers.CreateTokenizer(Microsoft.CodeAnalysis.Text.SourceText! text) -> Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser! +static Microsoft.DotNet.FileBasedPrograms.FileLevelDirectiveHelpers.FindDirectives(Microsoft.DotNet.FileBasedPrograms.SourceFile sourceFile, bool reportAllErrors, Microsoft.DotNet.FileBasedPrograms.DiagnosticBag diagnostics) -> System.Collections.Immutable.ImmutableArray +static Microsoft.DotNet.FileBasedPrograms.FileLevelDirectiveHelpers.FindLeadingDirectives(Microsoft.DotNet.FileBasedPrograms.SourceFile sourceFile, Microsoft.CodeAnalysis.SyntaxTriviaList triviaList, Microsoft.DotNet.FileBasedPrograms.DiagnosticBag diagnostics, System.Collections.Immutable.ImmutableArray.Builder? builder) -> void +static Microsoft.DotNet.FileBasedPrograms.MSBuildUtilities.ConvertStringToBool(string? parameterValue, bool defaultValue = false) -> bool +static Microsoft.DotNet.FileBasedPrograms.Patterns.DisallowedNameCharacters.get -> System.Text.RegularExpressions.Regex! +static Microsoft.DotNet.FileBasedPrograms.Patterns.EscapedCompilerOption.get -> System.Text.RegularExpressions.Regex! +static Microsoft.DotNet.FileBasedPrograms.Patterns.Whitespace.get -> System.Text.RegularExpressions.Regex! +static Microsoft.DotNet.FileBasedPrograms.SourceFile.Load(string! filePath) -> Microsoft.DotNet.FileBasedPrograms.SourceFile +static Microsoft.DotNet.FileBasedPrograms.SourceFile.operator !=(Microsoft.DotNet.FileBasedPrograms.SourceFile left, Microsoft.DotNet.FileBasedPrograms.SourceFile right) -> bool +static Microsoft.DotNet.FileBasedPrograms.SourceFile.operator ==(Microsoft.DotNet.FileBasedPrograms.SourceFile left, Microsoft.DotNet.FileBasedPrograms.SourceFile right) -> bool +static readonly Microsoft.DotNet.FileBasedPrograms.NamedDirectiveComparer.Instance -> Microsoft.DotNet.FileBasedPrograms.NamedDirectiveComparer! +~override Microsoft.DotNet.FileBasedPrograms.SourceFile.Equals(object obj) -> bool +~override Microsoft.DotNet.FileBasedPrograms.SourceFile.ToString() -> string \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/MSBuildUtilities.cs b/src/Cli/Microsoft.DotNet.FileBasedPrograms/MSBuildUtilities.cs new file mode 100644 index 000000000000..0b556f86daee --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/MSBuildUtilities.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// https://github.com/dotnet/sdk/issues/51487: avoid this extra copy of the file. +#nullable enable +using System; + +namespace Microsoft.DotNet.FileBasedPrograms +{ + /// + /// Internal utilities copied from microsoft/MSBuild repo. + /// + class MSBuildUtilities + { + /// + /// Converts a string to a bool. We consider "true/false", "on/off", and + /// "yes/no" to be valid boolean representations in the XML. + /// Modified from its original version to not throw, but return a default value. + /// + /// The string to convert. + /// Boolean true or false, corresponding to the string. + internal static bool ConvertStringToBool(string? parameterValue, bool defaultValue = false) + { + if (string.IsNullOrEmpty(parameterValue)) + { + return defaultValue; + } + else if (ValidBooleanTrue(parameterValue)) + { + return true; + } + else if (ValidBooleanFalse(parameterValue)) + { + return false; + } + else + { + // Unsupported boolean representation. + return defaultValue; + } + } + + /// + /// Returns true if the string represents a valid MSBuild boolean true value, + /// such as "on", "!false", "yes" + /// + private static bool ValidBooleanTrue(string? parameterValue) + { + return ((string.Compare(parameterValue, "true", StringComparison.OrdinalIgnoreCase) == 0) || + (string.Compare(parameterValue, "on", StringComparison.OrdinalIgnoreCase) == 0) || + (string.Compare(parameterValue, "yes", StringComparison.OrdinalIgnoreCase) == 0) || + (string.Compare(parameterValue, "!false", StringComparison.OrdinalIgnoreCase) == 0) || + (string.Compare(parameterValue, "!off", StringComparison.OrdinalIgnoreCase) == 0) || + (string.Compare(parameterValue, "!no", StringComparison.OrdinalIgnoreCase) == 0)); + } + + /// + /// Returns true if the string represents a valid MSBuild boolean false value, + /// such as "!on" "off" "no" "!true" + /// + private static bool ValidBooleanFalse(string? parameterValue) + { + return ((string.Compare(parameterValue, "false", StringComparison.OrdinalIgnoreCase) == 0) || + (string.Compare(parameterValue, "off", StringComparison.OrdinalIgnoreCase) == 0) || + (string.Compare(parameterValue, "no", StringComparison.OrdinalIgnoreCase) == 0) || + (string.Compare(parameterValue, "!true", StringComparison.OrdinalIgnoreCase) == 0) || + (string.Compare(parameterValue, "!on", StringComparison.OrdinalIgnoreCase) == 0) || + (string.Compare(parameterValue, "!yes", StringComparison.OrdinalIgnoreCase) == 0)); + } + } +} diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/Microsoft.DotNet.FileBasedPrograms.Package.csproj b/src/Cli/Microsoft.DotNet.FileBasedPrograms/Microsoft.DotNet.FileBasedPrograms.Package.csproj new file mode 100644 index 000000000000..ead3a31bed97 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/Microsoft.DotNet.FileBasedPrograms.Package.csproj @@ -0,0 +1,80 @@ + + + + + $(VisualStudioServiceTargetFramework);netstandard2.0 + false + none + false + preview + + + true + true + true + Microsoft.DotNet.FileBasedPrograms + false + Package containing sources for file-based programs support. + + $(NoWarn);NU5128 + false + $(DefineConstants);FILE_BASED_PROGRAMS_SOURCE_PACKAGE_BUILD;FILE_BASED_PROGRAMS_SOURCE_PACKAGE_GRACEFUL_EXCEPTION + + disable + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + External\%(NuGetPackageId)\%(Link) + + + + + + + + + true + contentFiles\cs\any\FileBasedProgramsResources.resx + + + true + contentFiles\cs\any\xlf + + + + + + + + <_PackageFiles Remove="@(_PackageFiles)" Condition="$([System.String]::Copy('%(_PackageFiles.Identity)').EndsWith('FileBasedProgramsResources.cs'))" /> + + + + diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/Microsoft.DotNet.FileBasedPrograms.projitems b/src/Cli/Microsoft.DotNet.FileBasedPrograms/Microsoft.DotNet.FileBasedPrograms.projitems new file mode 100644 index 000000000000..31ce6cd9eabc --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/Microsoft.DotNet.FileBasedPrograms.projitems @@ -0,0 +1,15 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 374C251E-BF99-45B2-A58E-40229ED8AACA + + + Microsoft.DotNet.FileBasedPrograms + + + + + + \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/Microsoft.DotNet.FileBasedPrograms.shproj b/src/Cli/Microsoft.DotNet.FileBasedPrograms/Microsoft.DotNet.FileBasedPrograms.shproj new file mode 100644 index 000000000000..68cb2e509ef5 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/Microsoft.DotNet.FileBasedPrograms.shproj @@ -0,0 +1,13 @@ + + + + 374C251E-BF99-45B2-A58E-40229ED8AACA + 14.0 + + + + + + + + \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/README.md b/src/Cli/Microsoft.DotNet.FileBasedPrograms/README.md new file mode 100644 index 000000000000..ddce21ddf94d --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/README.md @@ -0,0 +1,21 @@ +# Microsoft.DotNet.FileBasedPrograms Source Package + +This is a source package containing shared code for [file-based programs](../../../documentation/general/dotnet-run-file.md) support. This package is intended only for internal use by .NET components. + +## Usage in Consuming Projects + +To use this package in your project, add the following to your `.csproj` file: + +```xml + + + + + + + + $(DefineConstants);FILE_BASED_PROGRAMS_SOURCE_PACKAGE_GRACEFUL_EXCEPTION + +``` diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.cs.xlf b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.cs.xlf new file mode 100644 index 000000000000..d2dd3b59202f --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.cs.xlf @@ -0,0 +1,77 @@ + + + + + + Some directives cannot be converted. Run the file to see all compilation errors. Specify '--force' to convert anyway. + Některé direktivy nelze převést. Spuštěním souboru zobrazíte všechny chyby kompilace. Zadejte „--force“, pokud chcete přesto provést převod. + {Locked="--force"} + + + Could not find any project in `{0}`. + V {0} se nenašel žádný projekt. + + + + Could not find project or directory `{0}`. + Nenašel se projekt ani adresář {0}. + + + + error + chyba + Used when reporting directive errors like "file(location): error: message". + + + Duplicate directives are not supported: {0} + Duplicitní direktivy nejsou podporovány: {0} + {0} is the directive type and name. + + + The directive should contain a name without special characters and an optional value separated by '{1}' like '#:{0} Name{1}Value'. + Direktiva by měla obsahovat název bez speciálních znaků a volitelnou hodnotu oddělenou znakem {1}, například #:{0} Název{1}Hodnota. + {0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='. + + + The '#:project' directive is invalid: {0} + Direktiva #:project je neplatná: {0} + {0} is the inner error message. + + + Missing name of '{0}'. + Chybí název pro: {0}. + {0} is the directive name like 'package' or 'sdk'. + + + Found more than one project in `{0}`. Specify which one to use. + V {0} se našlo několik projektů. Vyberte, který z nich chcete použít. + + + + Invalid property name: {0} + Neplatný název vlastnosti: {0} + {0} is an inner exception message. + + + The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue'. + Direktiva property musí mít dvě části oddělené znakem =, například #:property PropertyName=PropertyValue. + {Locked="#:property"} + + + Directives currently cannot contain double quotes ("). + Direktivy v současné době nemůžou obsahovat dvojité uvozovky ("). + + + + Static graph restore is not supported for file-based apps. Remove the '#:property'. + Statické obnovení grafu se pro souborové aplikace nepodporuje. Odeberte #:property. + {Locked="#:property"} + + + Unrecognized directive '{0}'. + Nerozpoznaná direktiva {0}. + {0} is the directive name like 'package' or 'sdk'. + + + + \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.de.xlf b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.de.xlf new file mode 100644 index 000000000000..13eb91fbaee3 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.de.xlf @@ -0,0 +1,77 @@ + + + + + + Some directives cannot be converted. Run the file to see all compilation errors. Specify '--force' to convert anyway. + Einige Anweisungen können nicht konvertiert werden. Führen Sie die Datei aus, um alle Kompilierungsfehler anzuzeigen. Geben Sie „--force“ an, um das Umwandeln trotzdem auszuführen. + {Locked="--force"} + + + Could not find any project in `{0}`. + In "{0}" wurde kein Projekt gefunden. + + + + Could not find project or directory `{0}`. + Das Projekt oder Verzeichnis "{0}" wurde nicht gefunden. + + + + error + Fehler + Used when reporting directive errors like "file(location): error: message". + + + Duplicate directives are not supported: {0} + Doppelte Anweisungen werden nicht unterstützt: {0} + {0} is the directive type and name. + + + The directive should contain a name without special characters and an optional value separated by '{1}' like '#:{0} Name{1}Value'. + Die Anweisung sollte einen Namen ohne Sonderzeichen und einen optionalen Wert enthalten, die durch „{1}“ getrennt sind, wie „#:{0} Name{1}Wert“. + {0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='. + + + The '#:project' directive is invalid: {0} + Die Anweisung „#:p roject“ ist ungültig: {0} + {0} is the inner error message. + + + Missing name of '{0}'. + Fehlender Name der Anweisung „{0}“. + {0} is the directive name like 'package' or 'sdk'. + + + Found more than one project in `{0}`. Specify which one to use. + In "{0}" wurden mehrere Projekte gefunden. Geben Sie an, welches davon verwendet werden soll. + + + + Invalid property name: {0} + Ungültiger Eigenschaftenname: {0} + {0} is an inner exception message. + + + The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue'. + Die Eigenschaftsanweisung muss zwei durch „=“ getrennte Teile aufweisen, z. B. „#:property PropertyName=PropertyValue“. + {Locked="#:property"} + + + Directives currently cannot contain double quotes ("). + Direktiven dürfen derzeit keine doppelten Anführungszeichen (") enthalten. + + + + Static graph restore is not supported for file-based apps. Remove the '#:property'. + Die Statische Graphwiederherstellung wird für dateibasierte Apps nicht unterstützt. Entfernen Sie '#:property'. + {Locked="#:property"} + + + Unrecognized directive '{0}'. + Unbekannte Anweisung „{0}“. + {0} is the directive name like 'package' or 'sdk'. + + + + \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.es.xlf b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.es.xlf new file mode 100644 index 000000000000..38d1daaa07ae --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.es.xlf @@ -0,0 +1,77 @@ + + + + + + Some directives cannot be converted. Run the file to see all compilation errors. Specify '--force' to convert anyway. + Algunas directivas no se pueden convertir. Ejecute el archivo para ver todos los errores de compilación. Especifique "--force" para convertir de todos modos. + {Locked="--force"} + + + Could not find any project in `{0}`. + No se encuentra ningún proyecto en "{0}". + + + + Could not find project or directory `{0}`. + No se encuentra el proyecto o directorio "{0}". + + + + error + error + Used when reporting directive errors like "file(location): error: message". + + + Duplicate directives are not supported: {0} + No se admiten directivas duplicadas: {0} + {0} is the directive type and name. + + + The directive should contain a name without special characters and an optional value separated by '{1}' like '#:{0} Name{1}Value'. + La directiva debe contener un nombre sin caracteres especiales y un valor opcional separado por "{1}" como "#:{0} Nombre{1}Valor". + {0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='. + + + The '#:project' directive is invalid: {0} + La directiva "#:project" no es válida: {0} + {0} is the inner error message. + + + Missing name of '{0}'. + Falta el nombre de "{0}". + {0} is the directive name like 'package' or 'sdk'. + + + Found more than one project in `{0}`. Specify which one to use. + Se han encontrado varios proyectos en "{0}". Especifique el que debe usarse. + + + + Invalid property name: {0} + Nombre de propiedad no válido {0} + {0} is an inner exception message. + + + The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue'. + La directiva de propiedad debe tener dos partes separadas por "=", como "#:property PropertyName=PropertyValue". + {Locked="#:property"} + + + Directives currently cannot contain double quotes ("). + Las directivas no pueden contener comillas dobles ("), por ahora. + + + + Static graph restore is not supported for file-based apps. Remove the '#:property'. + No se admite la restauración de gráficos estáticos para aplicaciones basadas en archivos. Elimine "#:property". + {Locked="#:property"} + + + Unrecognized directive '{0}'. + Directiva no reconocida "{0}". + {0} is the directive name like 'package' or 'sdk'. + + + + \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.fr.xlf b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.fr.xlf new file mode 100644 index 000000000000..e034912dd36e --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.fr.xlf @@ -0,0 +1,77 @@ + + + + + + Some directives cannot be converted. Run the file to see all compilation errors. Specify '--force' to convert anyway. + Vous ne pouvez pas convertir certaines directives. Exécutez le fichier pour voir toutes les erreurs de compilation. Spécifiez « --force » pour convertir quand même. + {Locked="--force"} + + + Could not find any project in `{0}`. + Projet introuvable dans '{0}'. + + + + Could not find project or directory `{0}`. + Projet ou répertoire '{0}' introuvable. + + + + error + erreur + Used when reporting directive errors like "file(location): error: message". + + + Duplicate directives are not supported: {0} + Les directives dupliquées ne sont pas prises en charge : {0} + {0} is the directive type and name. + + + The directive should contain a name without special characters and an optional value separated by '{1}' like '#:{0} Name{1}Value'. + La directive dans doit contenir un nom sans caractères spéciaux et une valeur facultative séparée par « {1} » comme « # :{0} Nom{1}Valeur ». + {0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='. + + + The '#:project' directive is invalid: {0} + La directive « #:project » n’est pas valide : {0} + {0} is the inner error message. + + + Missing name of '{0}'. + Nom manquant pour « {0} ». + {0} is the directive name like 'package' or 'sdk'. + + + Found more than one project in `{0}`. Specify which one to use. + Plusieurs projets dans '{0}'. Spécifiez celui à utiliser. + + + + Invalid property name: {0} + Nom de propriété non valide : {0} + {0} is an inner exception message. + + + The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue'. + La directive de propriété doit avoir deux parties séparées par '=' comme '#:property PropertyName=PropertyValue'. + {Locked="#:property"} + + + Directives currently cannot contain double quotes ("). + Les directives ne peuvent actuellement pas contenir de guillemets doubles ("). + + + + Static graph restore is not supported for file-based apps. Remove the '#:property'. + La restauration de graphique statique n’est pas prise en charge pour les applications basées sur des fichiers. Supprimer la « #:property ». + {Locked="#:property"} + + + Unrecognized directive '{0}'. + Directive « {0} » non reconnue. + {0} is the directive name like 'package' or 'sdk'. + + + + \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.it.xlf b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.it.xlf new file mode 100644 index 000000000000..846df0ee663f --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.it.xlf @@ -0,0 +1,77 @@ + + + + + + Some directives cannot be converted. Run the file to see all compilation errors. Specify '--force' to convert anyway. + Non è possibile convertire alcune direttive. Eseguire il file per visualizzare tutti gli errori di compilazione. Specificare '--force' per eseguire comunque la conversione. + {Locked="--force"} + + + Could not find any project in `{0}`. + Non è stato trovato alcun progetto in `{0}`. + + + + Could not find project or directory `{0}`. + Non sono stati trovati progetti o directory `{0}`. + + + + error + errore + Used when reporting directive errors like "file(location): error: message". + + + Duplicate directives are not supported: {0} + Le direttive duplicate non supportate: {0} + {0} is the directive type and name. + + + The directive should contain a name without special characters and an optional value separated by '{1}' like '#:{0} Name{1}Value'. + La direttiva deve contenere un nome senza caratteri speciali e un valore facoltativo delimitato da '{1}' come '#:{0}Nome {1}Valore'. + {0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='. + + + The '#:project' directive is invalid: {0} + La direttiva '#:project' non è valida: {0} + {0} is the inner error message. + + + Missing name of '{0}'. + Manca il nome di '{0}'. + {0} is the directive name like 'package' or 'sdk'. + + + Found more than one project in `{0}`. Specify which one to use. + Sono stati trovati più progetti in `{0}`. Specificare quello da usare. + + + + Invalid property name: {0} + Nome proprietà non valido: {0} + {0} is an inner exception message. + + + The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue'. + La direttiva di proprietà deve avere due parti delimitate da '=', come '#:property PropertyName=PropertyValue'. + {Locked="#:property"} + + + Directives currently cannot contain double quotes ("). + Le direttive attualmente non possono contenere virgolette doppie ("). + + + + Static graph restore is not supported for file-based apps. Remove the '#:property'. + Il ripristino statico del grafo non è supportato per le app basate su file. Rimuovere '#:property'. + {Locked="#:property"} + + + Unrecognized directive '{0}'. + Direttiva non riconosciuta '{0}'. + {0} is the directive name like 'package' or 'sdk'. + + + + \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.ja.xlf b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.ja.xlf new file mode 100644 index 000000000000..18e3732d59d1 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.ja.xlf @@ -0,0 +1,77 @@ + + + + + + Some directives cannot be converted. Run the file to see all compilation errors. Specify '--force' to convert anyway. + 一部のディレクティブは変換できません。ファイルを実行して、すべてのコンパイル エラーを表示します。それでも変換する場合は '--force' を指定してください。 + {Locked="--force"} + + + Could not find any project in `{0}`. + `{0}` にプロジェクトが見つかりませんでした。 + + + + Could not find project or directory `{0}`. + プロジェクトまたはディレクトリ `{0}` が見つかりませんでした。 + + + + error + エラー + Used when reporting directive errors like "file(location): error: message". + + + Duplicate directives are not supported: {0} + 重複するディレクティブはサポートされていません: {0} + {0} is the directive type and name. + + + The directive should contain a name without special characters and an optional value separated by '{1}' like '#:{0} Name{1}Value'. + ディレクティブには、特殊文字を含まない名前と、'#:{0} Name{1}Value' などの '{1}' で区切られた省略可能な値を含める必要があります。 + {0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='. + + + The '#:project' directive is invalid: {0} + '#:p roject' ディレクティブが無効です: {0} + {0} is the inner error message. + + + Missing name of '{0}'. + '{0}' の名前がありません。 + {0} is the directive name like 'package' or 'sdk'. + + + Found more than one project in `{0}`. Specify which one to use. + `{0}` に複数のプロジェクトが見つかりました。使用するプロジェクトを指定してください。 + + + + Invalid property name: {0} + 無効なプロパティ名: {0} + {0} is an inner exception message. + + + The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue'. + プロパティ ディレクティブには、'#:property PropertyName=PropertyValue' のように '=' で区切られた 2 つの部分が必要です。 + {Locked="#:property"} + + + Directives currently cannot contain double quotes ("). + ディレクティブには二重引用符 (") を含めることはできません。 + + + + Static graph restore is not supported for file-based apps. Remove the '#:property'. + 静的グラフの復元はファイルベースのアプリではサポートされていません。'#:property' を削除します。 + {Locked="#:property"} + + + Unrecognized directive '{0}'. + 認識されないディレクティブ '{0}' です。 + {0} is the directive name like 'package' or 'sdk'. + + + + \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.ko.xlf b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.ko.xlf new file mode 100644 index 000000000000..d65519a8a4e0 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.ko.xlf @@ -0,0 +1,77 @@ + + + + + + Some directives cannot be converted. Run the file to see all compilation errors. Specify '--force' to convert anyway. + 일부 지시문을 변환할 수 없습니다. 파일을 실행하여 모든 컴파일 오류를 확인하세요. 변환을 강제로 진행하려면 '--force'를 지정하세요. + {Locked="--force"} + + + Could not find any project in `{0}`. + '{0}'에서 프로젝트를 찾을 수 없습니다. + + + + Could not find project or directory `{0}`. + 프로젝트 또는 디렉터리 {0}을(를) 찾을 수 없습니다. + + + + error + 오류 + Used when reporting directive errors like "file(location): error: message". + + + Duplicate directives are not supported: {0} + 중복 지시문은 지원되지 않습니다. {0} + {0} is the directive type and name. + + + The directive should contain a name without special characters and an optional value separated by '{1}' like '#:{0} Name{1}Value'. + 지시문에는 특수 문자가 없는 이름과 '#:{0} 이름{1}값'과 같이 '{1}'(으)로 구분된 선택적 값이 포함되어야 합니다. + {0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='. + + + The '#:project' directive is invalid: {0} + '#:p roject' 지시문이 잘못되었습니다. {0} + {0} is the inner error message. + + + Missing name of '{0}'. + '{0}' 이름이 없습니다. + {0} is the directive name like 'package' or 'sdk'. + + + Found more than one project in `{0}`. Specify which one to use. + '{0}'에서 프로젝트를 두 개 이상 찾았습니다. 사용할 프로젝트를 지정하세요. + + + + Invalid property name: {0} + 잘못된 속성 이름: {0} + {0} is an inner exception message. + + + The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue'. + property 지시문에는 '#:property PropertyName=PropertyValue'와 같이 '='로 구분된 두 부분이 있어야 합니다. + {Locked="#:property"} + + + Directives currently cannot contain double quotes ("). + 지시문은 현재 큰따옴표(")를 포함할 수 없습니다. + + + + Static graph restore is not supported for file-based apps. Remove the '#:property'. + 정적 그래프 복원은 파일 기반 앱에서 지원되지 않습니다. '#:property'를 제거합니다. + {Locked="#:property"} + + + Unrecognized directive '{0}'. + 인식할 수 없는 지시문 '{0}'입니다. + {0} is the directive name like 'package' or 'sdk'. + + + + \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.pl.xlf b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.pl.xlf new file mode 100644 index 000000000000..4cf66127fa52 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.pl.xlf @@ -0,0 +1,77 @@ + + + + + + Some directives cannot be converted. Run the file to see all compilation errors. Specify '--force' to convert anyway. + Nie można przekonwertować niektórych dyrektyw. Uruchom plik, aby wyświetlić wszystkie błędy kompilacji. Określ element „--force”, aby mimo to przekonwertować. + {Locked="--force"} + + + Could not find any project in `{0}`. + Nie można odnaleźć żadnego projektu w lokalizacji „{0}”. + + + + Could not find project or directory `{0}`. + Nie można odnaleźć projektu ani katalogu „{0}”. + + + + error + błąd + Used when reporting directive errors like "file(location): error: message". + + + Duplicate directives are not supported: {0} + Zduplikowane dyrektywy nie są obsługiwane: {0} + {0} is the directive type and name. + + + The directive should contain a name without special characters and an optional value separated by '{1}' like '#:{0} Name{1}Value'. + Dyrektywa powinna zawierać nazwę bez znaków specjalnych i opcjonalną wartość rozdzieloną znakiem "{1}#:{0} Name{1}Value". + {0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='. + + + The '#:project' directive is invalid: {0} + Dyrektywa „#:project” jest nieprawidłowa: {0} + {0} is the inner error message. + + + Missing name of '{0}'. + Brak nazwy „{0}”. + {0} is the directive name like 'package' or 'sdk'. + + + Found more than one project in `{0}`. Specify which one to use. + Znaleziono więcej niż jeden projekt w lokalizacji „{0}”. Określ, który ma zostać użyty. + + + + Invalid property name: {0} + Nieprawidłowa nazwa właściwości: {0} + {0} is an inner exception message. + + + The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue'. + Dyrektywa właściwości musi mieć dwie części oddzielone znakiem „=”, na przykład „#:property PropertyName=PropertyValue”. + {Locked="#:property"} + + + Directives currently cannot contain double quotes ("). + Dyrektywy nie mogą obecnie zawierać podwójnych cudzysłowów ("). + + + + Static graph restore is not supported for file-based apps. Remove the '#:property'. + Przywracanie statycznego grafu nie jest obsługiwane w przypadku aplikacji opartych na plikach. Usuń element „#:property”. + {Locked="#:property"} + + + Unrecognized directive '{0}'. + Nierozpoznana dyrektywa „{0}”. + {0} is the directive name like 'package' or 'sdk'. + + + + \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.pt-BR.xlf b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.pt-BR.xlf new file mode 100644 index 000000000000..732bdffaeaba --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.pt-BR.xlf @@ -0,0 +1,77 @@ + + + + + + Some directives cannot be converted. Run the file to see all compilation errors. Specify '--force' to convert anyway. + Algumas diretivas não podem ser convertidas. Execute o arquivo para ver todos os erros de compilação. Especifique '--force' para converter mesmo assim. + {Locked="--force"} + + + Could not find any project in `{0}`. + Não foi possível encontrar nenhum projeto em ‘{0}’. + + + + Could not find project or directory `{0}`. + Não foi possível encontrar o projeto ou diretório ‘{0}’. + + + + error + erro + Used when reporting directive errors like "file(location): error: message". + + + Duplicate directives are not supported: {0} + Diretivas duplicadas não são suportadas:{0} + {0} is the directive type and name. + + + The directive should contain a name without special characters and an optional value separated by '{1}' like '#:{0} Name{1}Value'. + A diretiva deve conter um nome sem caracteres especiais e um valor opcional separado por '{1}' como '#:{0} Nome{1}Valor'. + {0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='. + + + The '#:project' directive is invalid: {0} + A diretiva '#:project' é inválida:{0} + {0} is the inner error message. + + + Missing name of '{0}'. + Nome de '{0}' ausente. + {0} is the directive name like 'package' or 'sdk'. + + + Found more than one project in `{0}`. Specify which one to use. + Foi encontrado mais de um projeto em ‘{0}’. Especifique qual deve ser usado. + + + + Invalid property name: {0} + Nome de propriedade inválido: {0} + {0} is an inner exception message. + + + The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue'. + A diretiva de propriedade precisa ter duas partes separadas por '=' como '#:property PropertyName=PropertyValue'. + {Locked="#:property"} + + + Directives currently cannot contain double quotes ("). + No momento, as diretivas não podem conter aspas duplas ("). + + + + Static graph restore is not supported for file-based apps. Remove the '#:property'. + A restauração de grafo estático não é suportada para aplicativos baseados em arquivos. Remova '#:property'. + {Locked="#:property"} + + + Unrecognized directive '{0}'. + Diretiva não reconhecida '{0}'. + {0} is the directive name like 'package' or 'sdk'. + + + + \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.ru.xlf b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.ru.xlf new file mode 100644 index 000000000000..ca7d77675a4f --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.ru.xlf @@ -0,0 +1,77 @@ + + + + + + Some directives cannot be converted. Run the file to see all compilation errors. Specify '--force' to convert anyway. + Некоторые директивы невозможно преобразовать. Запустите файл, чтобы увидеть все ошибки компиляции. Укажите параметр "--force", чтобы выполнить преобразование, невзирая на ошибки. + {Locked="--force"} + + + Could not find any project in `{0}`. + Не удалось найти проекты в "{0}". + + + + Could not find project or directory `{0}`. + Не удалось найти проект или каталог "{0}". + + + + error + ошибка + Used when reporting directive errors like "file(location): error: message". + + + Duplicate directives are not supported: {0} + Повторяющиеся директивы не поддерживаются: {0} + {0} is the directive type and name. + + + The directive should contain a name without special characters and an optional value separated by '{1}' like '#:{0} Name{1}Value'. + Директива должна содержать имя без специальных символов и необязательное значение, разделенные символом-разделителем "{1}", например "#:{0} Имя{1}Значение". + {0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='. + + + The '#:project' directive is invalid: {0} + Недопустимая директива "#:project": {0} + {0} is the inner error message. + + + Missing name of '{0}'. + Отсутствует имя "{0}". + {0} is the directive name like 'package' or 'sdk'. + + + Found more than one project in `{0}`. Specify which one to use. + Найдено несколько проектов в "{0}". Выберите один. + + + + Invalid property name: {0} + Недопустимое имя свойства: {0} + {0} is an inner exception message. + + + The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue'. + Директива свойства должна иметь две части, разделенные символом "=", например "#:property PropertyName=PropertyValue". + {Locked="#:property"} + + + Directives currently cannot contain double quotes ("). + В директивах пока нельзя использовать двойные кавычки ("). + + + + Static graph restore is not supported for file-based apps. Remove the '#:property'. + Восстановление статического графа не поддерживается для приложений на основе файлов. Удалите "#:property". + {Locked="#:property"} + + + Unrecognized directive '{0}'. + Нераспознанная директива "{0}". + {0} is the directive name like 'package' or 'sdk'. + + + + \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.tr.xlf b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.tr.xlf new file mode 100644 index 000000000000..aa8bf66a3cdc --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.tr.xlf @@ -0,0 +1,77 @@ + + + + + + Some directives cannot be converted. Run the file to see all compilation errors. Specify '--force' to convert anyway. + Bazı yönergeler dönüştürülemez. Tüm derleme hatalarını görmek için dosyayı çalıştırın. Yine de dönüştürmek için '--force' belirtin. + {Locked="--force"} + + + Could not find any project in `{0}`. + `{0}` içinde proje bulunamadı. + + + + Could not find project or directory `{0}`. + `{0}` projesi veya dizini bulunamadı. + + + + error + hata + Used when reporting directive errors like "file(location): error: message". + + + Duplicate directives are not supported: {0} + Yinelenen yönergeler desteklenmez: {0} + {0} is the directive type and name. + + + The directive should contain a name without special characters and an optional value separated by '{1}' like '#:{0} Name{1}Value'. + Yönerge, özel karakterler içermeyen bir ad ve ‘#:{0} Ad{1}Değer’ gibi '{1}' ile ayrılmış isteğe bağlı bir değer içermelidir. + {0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='. + + + The '#:project' directive is invalid: {0} + ‘#:project’ yönergesi geçersizdir: {0} + {0} is the inner error message. + + + Missing name of '{0}'. + '{0}' adı eksik. + {0} is the directive name like 'package' or 'sdk'. + + + Found more than one project in `{0}`. Specify which one to use. + `{0}` içinde birden fazla proje bulundu. Hangisinin kullanılacağını belirtin. + + + + Invalid property name: {0} + Geçersiz özellik adı: {0} + {0} is an inner exception message. + + + The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue'. + Özellik yönergesi, ‘#:property PropertyName=PropertyValue’ gibi ‘=’ ile ayrılmış iki bölümden oluşmalıdır. + {Locked="#:property"} + + + Directives currently cannot contain double quotes ("). + Yönergeler şu anda çift tırnak (") içeremez. + + + + Static graph restore is not supported for file-based apps. Remove the '#:property'. + Dosya tabanlı uygulamalar için statik grafik geri yükleme desteklenmemektedir. ‘#:property’i kaldırın. + {Locked="#:property"} + + + Unrecognized directive '{0}'. + Tanınmayan yönerge '{0}'. + {0} is the directive name like 'package' or 'sdk'. + + + + \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.zh-Hans.xlf b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.zh-Hans.xlf new file mode 100644 index 000000000000..7a3b8fdc7a4e --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.zh-Hans.xlf @@ -0,0 +1,77 @@ + + + + + + Some directives cannot be converted. Run the file to see all compilation errors. Specify '--force' to convert anyway. + 一些指令无法转换。运行该文件以查看所有编译错误。请指定 '--force' 以进行转换。 + {Locked="--force"} + + + Could not find any project in `{0}`. + “{0}”中找不到任何项目。 + + + + Could not find project or directory `{0}`. + 找不到项目或目录“{0}”。 + + + + error + 错误 + Used when reporting directive errors like "file(location): error: message". + + + Duplicate directives are not supported: {0} + 不支持重复指令: {0} + {0} is the directive type and name. + + + The directive should contain a name without special characters and an optional value separated by '{1}' like '#:{0} Name{1}Value'. + 该指令应包含一个不带特殊字符的名称,以及一个以 '#:{0} Name{1}Value' 等 ‘{1}’ 分隔的可选值。 + {0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='. + + + The '#:project' directive is invalid: {0} + '#:project' 指令无效: {0} + {0} is the inner error message. + + + Missing name of '{0}'. + 缺少 '{0}' 的名称。 + {0} is the directive name like 'package' or 'sdk'. + + + Found more than one project in `{0}`. Specify which one to use. + 在“{0}”中找到多个项目。请指定使用哪一个。 + + + + Invalid property name: {0} + 属性名无效: {0} + {0} is an inner exception message. + + + The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue'. + 属性指令需要包含两个由 ‘=’ 分隔的部件,例如 '#:property PropertyName=PropertyValue'。 + {Locked="#:property"} + + + Directives currently cannot contain double quotes ("). + 指令当前不能包含双引号(")。 + + + + Static graph restore is not supported for file-based apps. Remove the '#:property'. + 基于文件的应用不支持静态图形还原。移除 '#:property'。 + {Locked="#:property"} + + + Unrecognized directive '{0}'. + 无法识别的指令 ‘{0}’。 + {0} is the directive name like 'package' or 'sdk'. + + + + \ No newline at end of file diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.zh-Hant.xlf b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.zh-Hant.xlf new file mode 100644 index 000000000000..d865da88d7d8 --- /dev/null +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.zh-Hant.xlf @@ -0,0 +1,77 @@ + + + + + + Some directives cannot be converted. Run the file to see all compilation errors. Specify '--force' to convert anyway. + 無法轉換某些指示詞。執行檔案以查看所有編譯錯誤。指定 '--force' 以繼續轉換。 + {Locked="--force"} + + + Could not find any project in `{0}`. + 在 `{0}` 中找不到任何專案。 + + + + Could not find project or directory `{0}`. + 找不到專案或目錄 `{0}`。 + + + + error + 錯誤 + Used when reporting directive errors like "file(location): error: message". + + + Duplicate directives are not supported: {0} + 不支援重複的指示詞: {0} + {0} is the directive type and name. + + + The directive should contain a name without special characters and an optional value separated by '{1}' like '#:{0} Name{1}Value'. + 指示詞應包含不含特殊字元的名稱,以及 '{1}' 分隔的選用值,例如 '#:{0} Name{1}Value'。 + {0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='. + + + The '#:project' directive is invalid: {0} + '#:project' 指示詞無效: {0} + {0} is the inner error message. + + + Missing name of '{0}'. + 缺少 '{0}' 的名稱。 + {0} is the directive name like 'package' or 'sdk'. + + + Found more than one project in `{0}`. Specify which one to use. + 在 `{0}` 中找到多個專案。請指定要使用的專案。 + + + + Invalid property name: {0} + 屬性名稱無效: {0} + {0} is an inner exception message. + + + The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue'. + 屬性指示詞必須有兩個部分,其以 '=' 分隔,例如 '#:property PropertyName=PropertyValue'。 + {Locked="#:property"} + + + Directives currently cannot contain double quotes ("). + 指令目前不能包含雙引號 (")。 + + + + Static graph restore is not supported for file-based apps. Remove the '#:property'. + 檔案型應用程式不支援靜態圖表還原。移除 ''#:property'。 + {Locked="#:property"} + + + Unrecognized directive '{0}'. + 無法識別的指示詞 '{0}'。 + {0} is the directive name like 'package' or 'sdk'. + + + + \ No newline at end of file diff --git a/src/Cli/Microsoft.TemplateEngine.Cli/Commands/ICommandDocument.cs b/src/Cli/Microsoft.TemplateEngine.Cli/Commands/ICommandDocument.cs deleted file mode 100644 index dc11c8327f36..000000000000 --- a/src/Cli/Microsoft.TemplateEngine.Cli/Commands/ICommandDocument.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.CommandLine; - -namespace Microsoft.TemplateEngine.Cli.Commands; - -/// -/// If a implements this interface, it can open -/// its documentation page online. -/// -public interface ICommandDocument -{ - /// - /// The URL to the documentation page for this command. - /// - string DocsLink { get; } -} diff --git a/src/Cli/Microsoft.TemplateEngine.Cli/Commands/NewCommand.cs b/src/Cli/Microsoft.TemplateEngine.Cli/Commands/NewCommand.cs index 3db21ea63d74..e8883d6a9f6f 100644 --- a/src/Cli/Microsoft.TemplateEngine.Cli/Commands/NewCommand.cs +++ b/src/Cli/Microsoft.TemplateEngine.Cli/Commands/NewCommand.cs @@ -3,18 +3,20 @@ using System.CommandLine; using System.CommandLine.Completions; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.TemplateEngine.Abstractions; using Microsoft.TemplateEngine.Edge.Settings; namespace Microsoft.TemplateEngine.Cli.Commands { - internal partial class NewCommand : BaseCommand, ICustomHelp, ICommandDocument + internal partial class NewCommand : BaseCommand, ICustomHelp { internal NewCommand( string commandName, Func hostBuilder) : base(hostBuilder, commandName, SymbolStrings.Command_New_Description) { + this.DocsLink = "https://aka.ms/dotnet-new"; TreatUnmatchedTokensAsErrors = true; //it is important that legacy commands are built before non-legacy, as non legacy commands are building validators that rely on legacy stuff @@ -44,8 +46,6 @@ internal NewCommand( Options.Add(SharedOptions.ProjectPathOption); } - public string DocsLink { get; } = "https://aka.ms/dotnet-new"; - internal static Option DebugCustomSettingsLocationOption { get; } = new("--debug:custom-hive") { Description = SymbolStrings.Option_Debug_CustomSettings, diff --git a/src/Cli/Microsoft.TemplateEngine.Cli/Commands/create/InstantiateCommand.cs b/src/Cli/Microsoft.TemplateEngine.Cli/Commands/create/InstantiateCommand.cs index 7889716ea67b..485aa9493408 100644 --- a/src/Cli/Microsoft.TemplateEngine.Cli/Commands/create/InstantiateCommand.cs +++ b/src/Cli/Microsoft.TemplateEngine.Cli/Commands/create/InstantiateCommand.cs @@ -51,13 +51,13 @@ internal InstantiateCommand( Arity = new ArgumentArity(0, 999) }; - internal IReadOnlyList public MSBuildForwardingApp(IEnumerable rawMSBuildArgs, string? msbuildPath = null) : this( - MSBuildArgs.AnalyzeMSBuildArguments(rawMSBuildArgs.ToArray(), CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, CommonOptions.MSBuildTargetOption(), CommonOptions.VerbosityOption()), + MSBuildArgs.AnalyzeMSBuildArguments(rawMSBuildArgs.ToArray(), CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, CommonOptions.MSBuildTargetOption(), CommonOptions.VerbosityOption(), CommonOptions.NoLogoOption()), msbuildPath) { } - public MSBuildForwardingApp(MSBuildArgs msBuildArgs, string? msbuildPath = null, bool includeLogo = false) + public MSBuildForwardingApp(MSBuildArgs msBuildArgs, string? msbuildPath = null) { var modifiedMSBuildArgs = CommonRunHelpers.AdjustMSBuildForLLMs(ConcatTelemetryLogger(msBuildArgs)); _forwardingAppWithoutLogging = new MSBuildForwardingAppWithoutLogging( modifiedMSBuildArgs, - msbuildPath: msbuildPath, - includeLogo: includeLogo); + msbuildPath: msbuildPath); // Add the performance log location to the environment of the target process. if (PerformanceLogManager.Instance != null && !string.IsNullOrEmpty(PerformanceLogManager.Instance.CurrentLogDirectory)) diff --git a/src/Cli/dotnet/Commands/MSBuild/MSBuildLogger.cs b/src/Cli/dotnet/Commands/MSBuild/MSBuildLogger.cs index 265b1eafbb76..2656f9798a9e 100644 --- a/src/Cli/dotnet/Commands/MSBuild/MSBuildLogger.cs +++ b/src/Cli/dotnet/Commands/MSBuild/MSBuildLogger.cs @@ -204,8 +204,8 @@ internal static void FormatAndSend(ITelemetry? telemetry, TelemetryEventArgs arg break; case LoggingConfigurationTelemetryEventName: TrackEvent(telemetry, $"msbuild/{LoggingConfigurationTelemetryEventName}", args.Properties, - toBeMeasured: ["FileLoggersCount"] - ); + toBeHashed: [], + toBeMeasured: []); break; case BuildcheckAcquisitionFailureEventName: TrackEvent(telemetry, $"msbuild/{BuildcheckAcquisitionFailureEventName}", args.Properties, diff --git a/src/Cli/dotnet/Commands/New/DotnetCommandCallbacks.cs b/src/Cli/dotnet/Commands/New/DotnetCommandCallbacks.cs index 46ddfe28c4b0..4aae2f0f037e 100644 --- a/src/Cli/dotnet/Commands/New/DotnetCommandCallbacks.cs +++ b/src/Cli/dotnet/Commands/New/DotnetCommandCallbacks.cs @@ -38,7 +38,7 @@ internal static bool RestoreProject(string pathToRestore) { PathUtility.EnsureAllPathsExist([pathToRestore], CliStrings.CommonFileNotFound, allowDirectories: true); // for the implicit restore we do not want the terminal logger to emit any output unless there are errors - return RestoreCommand.Run([pathToRestore, "-tlp:verbosity=quiet"]) == 0; + return RestoreCommand.Run([pathToRestore, "-tlp:verbosity=quiet", "--no-logo"]) == 0; } internal static bool AddProjectsToSolution(string solutionPath, IReadOnlyList projectsToAdd, string? solutionFolder, bool? inRoot) diff --git a/src/Cli/dotnet/Commands/New/NewCommandParser.cs b/src/Cli/dotnet/Commands/New/NewCommandParser.cs index 5bb9db983a84..45337f19ea72 100644 --- a/src/Cli/dotnet/Commands/New/NewCommandParser.cs +++ b/src/Cli/dotnet/Commands/New/NewCommandParser.cs @@ -3,6 +3,7 @@ using System.CommandLine; using System.CommandLine.Parsing; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.New.MSBuildEvaluation; using Microsoft.DotNet.Cli.Commands.New.PostActions; using Microsoft.DotNet.Cli.Commands.Workload; diff --git a/src/Cli/dotnet/Commands/NuGet/NuGetCommandParser.cs b/src/Cli/dotnet/Commands/NuGet/NuGetCommandParser.cs index 89af3722dd9a..814ad45f8544 100644 --- a/src/Cli/dotnet/Commands/NuGet/NuGetCommandParser.cs +++ b/src/Cli/dotnet/Commands/NuGet/NuGetCommandParser.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; -using Microsoft.DotNet.Cli.Extensions; +using Microsoft.DotNet.Cli.CommandLine; using NuGetWhyCommand = NuGet.CommandLine.XPlat.Commands.Why.WhyCommand; namespace Microsoft.DotNet.Cli.Commands.NuGet; @@ -22,10 +22,11 @@ public static Command GetCommand() private static Command ConstructCommand() { - var command = new DocumentedCommand("nuget", DocsLink) + var command = new Command("nuget") { // some subcommands are not defined here and just forwarded to NuGet app - TreatUnmatchedTokensAsErrors = false + TreatUnmatchedTokensAsErrors = false, + DocsLink = DocsLink }; command.Options.Add(new Option("--version") @@ -149,7 +150,7 @@ private static Command GetVerifyCommand() { Arity = ArgumentArity.Zero }); - verifyCommand.Options.Add(new ForwardedOption>(fingerprint) + verifyCommand.Options.Add(new Option>(fingerprint) .ForwardAsManyArgumentsEachPrefixedByOption(fingerprint) .AllowSingleArgPerToken()); verifyCommand.Options.Add(CommonOptions.VerbosityOption(Utils.VerbosityOptions.normal)); diff --git a/src/Cli/dotnet/Commands/Pack/PackCommand.cs b/src/Cli/dotnet/Commands/Pack/PackCommand.cs index 1e88f22688f5..9f31bfa145fe 100644 --- a/src/Cli/dotnet/Commands/Pack/PackCommand.cs +++ b/src/Cli/dotnet/Commands/Pack/PackCommand.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.CommandLine; using System.CommandLine.Parsing; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Build; using Microsoft.DotNet.Cli.Commands.Restore; using Microsoft.DotNet.Cli.Commands.Run; @@ -59,6 +60,7 @@ public static CommandBase FromParseResult(ParseResult parseResult, string? msbui CommonOptions.RestorePropertiesOption, PackCommandParser.TargetOption, PackCommandParser.VerbosityOption, + PackCommandParser.NoLogoOption ], parseResult, msbuildPath, @@ -92,14 +94,14 @@ public static int RunPackCommand(ParseResult parseResult) if (args.Count != 1) { - Console.Error.WriteLine(CliStrings.PackCmd_OneNuspecAllowed); + Console.Error.WriteLine(CliStrings.PackCmd_OneNuspecAllowed); return 1; } var nuspecPath = args[0]; var packArgs = new PackArgs() - { + { Logger = new NuGetConsoleLogger(), Exclude = new List(), OutputDirectory = parseResult.GetValue(PackCommandParser.OutputOption), diff --git a/src/Cli/dotnet/Commands/Pack/PackCommandParser.cs b/src/Cli/dotnet/Commands/Pack/PackCommandParser.cs index c0ee6b1a8b41..23fc1ef58f69 100644 --- a/src/Cli/dotnet/Commands/Pack/PackCommandParser.cs +++ b/src/Cli/dotnet/Commands/Pack/PackCommandParser.cs @@ -2,9 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Build; using Microsoft.DotNet.Cli.Commands.Restore; -using Microsoft.DotNet.Cli.Extensions; using NuGet.Versioning; namespace Microsoft.DotNet.Cli.Commands.Pack; @@ -19,41 +19,37 @@ internal static class PackCommandParser Arity = ArgumentArity.ZeroOrMore }; - public static readonly Option OutputOption = new ForwardedOption("--output", "-o") + public static readonly Option OutputOption = new Option("--output", "-o") { Description = CliCommandStrings.PackCmdOutputDirDescription, HelpName = CliCommandStrings.PackCmdOutputDir }.ForwardAsSingle(o => $"-property:PackageOutputPath={CommandDirectoryContext.GetFullPath(o)}"); - public static readonly Option NoBuildOption = new ForwardedOption("--no-build") + public static readonly Option NoBuildOption = new Option("--no-build") { Description = CliCommandStrings.CmdNoBuildOptionDescription, Arity = ArgumentArity.Zero }.ForwardAs("-property:NoBuild=true"); - public static readonly Option IncludeSymbolsOption = new ForwardedOption("--include-symbols") + public static readonly Option IncludeSymbolsOption = new Option("--include-symbols") { Description = CliCommandStrings.CmdIncludeSymbolsDescription, Arity = ArgumentArity.Zero }.ForwardAs("-property:IncludeSymbols=true"); - public static readonly Option IncludeSourceOption = new ForwardedOption("--include-source") + public static readonly Option IncludeSourceOption = new Option("--include-source") { Description = CliCommandStrings.CmdIncludeSourceDescription, Arity = ArgumentArity.Zero }.ForwardAs("-property:IncludeSource=true"); - public static readonly Option ServiceableOption = new ForwardedOption("--serviceable", "-s") + public static readonly Option ServiceableOption = new Option("--serviceable", "-s") { Description = CliCommandStrings.CmdServiceableDescription, Arity = ArgumentArity.Zero }.ForwardAs("-property:Serviceable=true"); - public static readonly Option NoLogoOption = new ForwardedOption("--nologo") - { - Description = CliCommandStrings.PackCmdNoLogo, - Arity = ArgumentArity.Zero - }.ForwardAs("-nologo"); + public static readonly Option NoLogoOption = CommonOptions.NoLogoOption(); public static readonly Option NoRestoreOption = CommonOptions.NoRestoreOption; @@ -63,7 +59,7 @@ internal static class PackCommandParser public static readonly Option VerbosityOption = BuildCommandParser.VerbosityOption; public static Option VersionOption = - new ForwardedOption("--version") + new Option("--version") { Description = CliCommandStrings.PackCmdVersionDescription, HelpName = CliCommandStrings.PackCmdVersion, @@ -90,7 +86,10 @@ public static Command GetCommand() private static Command ConstructCommand() { - var command = new DocumentedCommand("pack", DocsLink, CliCommandStrings.PackAppFullName); + var command = new Command("pack", CliCommandStrings.PackAppFullName) + { + DocsLink = DocsLink + }; command.Arguments.Add(SlnOrProjectOrFileArgument); command.Options.Add(OutputOption); diff --git a/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs b/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs index dc94f892abd1..a463165828cb 100644 --- a/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs +++ b/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs @@ -9,8 +9,10 @@ using Microsoft.DotNet.Cli.Commands.MSBuild; using Microsoft.DotNet.Cli.Commands.NuGet; using Microsoft.DotNet.Cli.Commands.Run; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.FileBasedPrograms; using NuGet.ProjectModel; namespace Microsoft.DotNet.Cli.Commands.Package.Add; @@ -89,7 +91,7 @@ private static void GetProjectDependencyGraph(string projectFilePath, string dgF $"-property:RestoreDotnetCliToolReferences=false", // Output should not include MSBuild version header - "-nologo", + "--nologo", // Set verbosity to quiet to avoid cluttering the output for this 'inner' build "-v:quiet" diff --git a/src/Cli/dotnet/Commands/Package/Add/PackageAddCommandParser.cs b/src/Cli/dotnet/Commands/Package/Add/PackageAddCommandParser.cs index 1d24fd9c10de..ad5c1270896f 100644 --- a/src/Cli/dotnet/Commands/Package/Add/PackageAddCommandParser.cs +++ b/src/Cli/dotnet/Commands/Package/Add/PackageAddCommandParser.cs @@ -4,7 +4,8 @@ using System.CommandLine; using System.CommandLine.Completions; using System.CommandLine.Parsing; -using Microsoft.DotNet.Cli.Extensions; +using System.CommandLine.StaticCompletions; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.Extensions.EnvironmentAbstractions; using NuGet.Versioning; @@ -12,7 +13,7 @@ namespace Microsoft.DotNet.Cli.Commands.Package.Add; public static class PackageAddCommandParser { - public static readonly Option PrereleaseOption = new ForwardedOption("--prerelease") + public static readonly Option PrereleaseOption = new Option("--prerelease") { Description = CliStrings.CommandPrereleaseOptionDescription, Arity = ArgumentArity.Zero @@ -26,10 +27,11 @@ public static class PackageAddCommandParser return QueryNuGet(context.WordToComplete, allowPrerelease, CancellationToken.None).Result.Select(packageId => new CompletionItem(packageId)); }); - public static readonly Option VersionOption = new DynamicForwardedOption("--version", "-v") + public static readonly Option VersionOption = new Option("--version", "-v") { Description = CliCommandStrings.CmdVersionDescription, - HelpName = CliCommandStrings.CmdVersion + HelpName = CliCommandStrings.CmdVersion, + IsDynamic = true }.ForwardAsSingle(o => $"--version {o}") .AddCompletions((context) => { @@ -48,7 +50,7 @@ public static class PackageAddCommandParser } }); - public static readonly Option FrameworkOption = new ForwardedOption("--framework", "-f") + public static readonly Option FrameworkOption = new Option("--framework", "-f") { Description = CliCommandStrings.PackageAddCmdFrameworkDescription, HelpName = CliCommandStrings.PackageAddCmdFramework @@ -60,13 +62,13 @@ public static class PackageAddCommandParser Arity = ArgumentArity.Zero }; - public static readonly Option SourceOption = new ForwardedOption("--source", "-s") + public static readonly Option SourceOption = new Option("--source", "-s") { Description = CliCommandStrings.PackageAddCmdSourceDescription, HelpName = CliCommandStrings.PackageAddCmdSource }.ForwardAsSingle(o => $"--source {o}"); - public static readonly Option PackageDirOption = new ForwardedOption("--package-directory") + public static readonly Option PackageDirOption = new Option("--package-directory") { Description = CliCommandStrings.CmdPackageDirectoryDescription, HelpName = CliCommandStrings.CmdPackageDirectory diff --git a/src/Cli/dotnet/Commands/Package/List/PackageListCommand.cs b/src/Cli/dotnet/Commands/Package/List/PackageListCommand.cs index 27520377cad2..4ff46b804d2c 100644 --- a/src/Cli/dotnet/Commands/Package/List/PackageListCommand.cs +++ b/src/Cli/dotnet/Commands/Package/List/PackageListCommand.cs @@ -6,6 +6,7 @@ using System.CommandLine; using Microsoft.DotNet.Cli.Commands.Hidden.List; using Microsoft.DotNet.Cli.Commands.NuGet; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using System.Globalization; diff --git a/src/Cli/dotnet/Commands/Package/List/PackageListCommandParser.cs b/src/Cli/dotnet/Commands/Package/List/PackageListCommandParser.cs index 6c6970fcb6c1..2ce93a3c7e80 100644 --- a/src/Cli/dotnet/Commands/Package/List/PackageListCommandParser.cs +++ b/src/Cli/dotnet/Commands/Package/List/PackageListCommandParser.cs @@ -4,68 +4,68 @@ #nullable disable using System.CommandLine; -using Microsoft.DotNet.Cli.Extensions; +using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Cli.Commands.Package.List; internal static class PackageListCommandParser { - public static readonly Option OutdatedOption = new ForwardedOption("--outdated") + public static readonly Option OutdatedOption = new Option("--outdated") { Description = CliCommandStrings.CmdOutdatedDescription, Arity = ArgumentArity.Zero }.ForwardAs("--outdated"); - public static readonly Option DeprecatedOption = new ForwardedOption("--deprecated") + public static readonly Option DeprecatedOption = new Option("--deprecated") { Description = CliCommandStrings.CmdDeprecatedDescription, Arity = ArgumentArity.Zero }.ForwardAs("--deprecated"); - public static readonly Option VulnerableOption = new ForwardedOption("--vulnerable") + public static readonly Option VulnerableOption = new Option("--vulnerable") { Description = CliCommandStrings.CmdVulnerableDescription, Arity = ArgumentArity.Zero }.ForwardAs("--vulnerable"); - public static readonly Option FrameworkOption = new ForwardedOption>("--framework", "-f") + public static readonly Option FrameworkOption = new Option>("--framework", "-f") { Description = CliCommandStrings.PackageListCmdFrameworkDescription, HelpName = CliCommandStrings.PackageListCmdFramework }.ForwardAsManyArgumentsEachPrefixedByOption("--framework") .AllowSingleArgPerToken(); - public static readonly Option TransitiveOption = new ForwardedOption("--include-transitive") + public static readonly Option TransitiveOption = new Option("--include-transitive") { Description = CliCommandStrings.CmdTransitiveDescription, Arity = ArgumentArity.Zero }.ForwardAs("--include-transitive"); - public static readonly Option PrereleaseOption = new ForwardedOption("--include-prerelease") + public static readonly Option PrereleaseOption = new Option("--include-prerelease") { Description = CliCommandStrings.CmdPrereleaseDescription, Arity = ArgumentArity.Zero }.ForwardAs("--include-prerelease"); - public static readonly Option HighestPatchOption = new ForwardedOption("--highest-patch") + public static readonly Option HighestPatchOption = new Option("--highest-patch") { Description = CliCommandStrings.CmdHighestPatchDescription, Arity = ArgumentArity.Zero }.ForwardAs("--highest-patch"); - public static readonly Option HighestMinorOption = new ForwardedOption("--highest-minor") + public static readonly Option HighestMinorOption = new Option("--highest-minor") { Description = CliCommandStrings.CmdHighestMinorDescription, Arity = ArgumentArity.Zero }.ForwardAs("--highest-minor"); - public static readonly Option ConfigOption = new ForwardedOption("--config", "--configfile") + public static readonly Option ConfigOption = new Option("--config", "--configfile") { Description = CliCommandStrings.CmdConfigDescription, HelpName = CliCommandStrings.CmdConfig }.ForwardAsMany(o => ["--config", o]); - public static readonly Option SourceOption = new ForwardedOption>("--source", "-s") + public static readonly Option SourceOption = new Option>("--source", "-s") { Description = CliCommandStrings.PackageListCmdSourceDescription, HelpName = CliCommandStrings.PackageListCmdSource @@ -80,18 +80,18 @@ internal static class PackageListCommandParser Arity = ArgumentArity.Zero }; - public static readonly Option VerbosityOption = new ForwardedOption("--verbosity", "-v") + public static readonly Option VerbosityOption = new Option("--verbosity", "-v") { Description = CliStrings.VerbosityOptionDescription, HelpName = CliStrings.LevelArgumentName }.ForwardAsSingle(o => $"--verbosity:{o}"); - public static readonly Option FormatOption = new ForwardedOption("--format") + public static readonly Option FormatOption = new Option("--format") { Description = CliCommandStrings.CmdFormatDescription }.ForwardAsSingle(o => $"--format:{o}"); - public static readonly Option OutputVersionOption = new ForwardedOption("--output-version") + public static readonly Option OutputVersionOption = new Option("--output-version") { Description = CliCommandStrings.CmdOutputVersionDescription }.ForwardAsSingle(o => $"--output-version:{o}"); diff --git a/src/Cli/dotnet/Commands/Package/PackageCommandParser.cs b/src/Cli/dotnet/Commands/Package/PackageCommandParser.cs index a6e44f5bccbe..16eb5873794d 100644 --- a/src/Cli/dotnet/Commands/Package/PackageCommandParser.cs +++ b/src/Cli/dotnet/Commands/Package/PackageCommandParser.cs @@ -7,6 +7,7 @@ using Microsoft.DotNet.Cli.Commands.Package.Remove; using Microsoft.DotNet.Cli.Commands.Package.Search; using Microsoft.DotNet.Cli.Commands.Run; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using Command = System.CommandLine.Command; @@ -37,7 +38,10 @@ internal class PackageCommandParser public static Command GetCommand() { - Command command = new DocumentedCommand("package", DocsLink); + Command command = new Command("package") + { + DocsLink = DocsLink + }; command.SetAction((parseResult) => parseResult.HandleMissingCommand()); command.Subcommands.Add(PackageSearchCommandParser.GetCommand()); command.Subcommands.Add(PackageAddCommandParser.GetCommand()); diff --git a/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommand.cs b/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommand.cs index 2706bed56b21..e90099382273 100644 --- a/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommand.cs +++ b/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommand.cs @@ -5,8 +5,9 @@ using System.Diagnostics; using Microsoft.DotNet.Cli.Commands.NuGet; using Microsoft.DotNet.Cli.Commands.Run; -using Microsoft.DotNet.Cli.Extensions; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.FileBasedPrograms; namespace Microsoft.DotNet.Cli.Commands.Package.Remove; diff --git a/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommandParser.cs b/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommandParser.cs index e1b020f7695f..20354b4f0658 100644 --- a/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommandParser.cs +++ b/src/Cli/dotnet/Commands/Package/Remove/PackageRemoveCommandParser.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; -using Microsoft.DotNet.Cli.Extensions; +using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Cli.Commands.Package.Remove; diff --git a/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommand.cs b/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommand.cs index 4317f96329be..d26a96fab237 100644 --- a/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommand.cs +++ b/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommand.cs @@ -4,7 +4,7 @@ #nullable disable using Microsoft.DotNet.Cli.Commands.NuGet; -using Microsoft.DotNet.Cli.Extensions; +using Microsoft.DotNet.Cli.CommandLine; using System.CommandLine; namespace Microsoft.DotNet.Cli.Commands.Package.Search; diff --git a/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommandParser.cs b/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommandParser.cs index 52e15de7089e..54b0749cca3d 100644 --- a/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommandParser.cs +++ b/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommandParser.cs @@ -4,6 +4,7 @@ #nullable disable using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; namespace Microsoft.DotNet.Cli.Commands.Package.Search; @@ -17,26 +18,26 @@ internal static class PackageSearchCommandParser Arity = ArgumentArity.ZeroOrOne }; - public static readonly Option Sources = new ForwardedOption>("--source") + public static readonly Option Sources = new Option>("--source") { Description = CliCommandStrings.SourceDescription, HelpName = CliCommandStrings.SourceArgumentName }.ForwardAsManyArgumentsEachPrefixedByOption("--source") .AllowSingleArgPerToken(); - public static readonly Option Take = new ForwardedOption("--take") + public static readonly Option Take = new Option("--take") { Description = CliCommandStrings.PackageSearchTakeDescription, HelpName = CliCommandStrings.PackageSearchTakeArgumentName }.ForwardAsSingle(o => $"--take:{o}"); - public static readonly Option Skip = new ForwardedOption("--skip") + public static readonly Option Skip = new Option("--skip") { Description = CliCommandStrings.PackageSearchSkipDescription, HelpName = CliCommandStrings.PackageSearchSkipArgumentName }.ForwardAsSingle(o => $"--skip:{o}"); - public static readonly Option ExactMatch = new ForwardedOption("--exact-match") + public static readonly Option ExactMatch = new Option("--exact-match") { Description = CliCommandStrings.ExactMatchDescription, Arity = ArgumentArity.Zero @@ -44,25 +45,25 @@ internal static class PackageSearchCommandParser public static readonly Option Interactive = CommonOptions.InteractiveOption().ForwardIfEnabled("--interactive"); - public static readonly Option Prerelease = new ForwardedOption("--prerelease") + public static readonly Option Prerelease = new Option("--prerelease") { Description = CliCommandStrings.PackageSearchPrereleaseDescription, Arity = ArgumentArity.Zero }.ForwardAs("--prerelease"); - public static readonly Option ConfigFile = new ForwardedOption("--configfile") + public static readonly Option ConfigFile = new Option("--configfile") { Description = CliCommandStrings.ConfigFileDescription, HelpName = CliCommandStrings.ConfigFileArgumentName }.ForwardAsSingle(o => $"--configfile:{o}"); - public static readonly Option Format = new ForwardedOption("--format") + public static readonly Option Format = new Option("--format") { Description = CliCommandStrings.FormatDescription, HelpName = CliCommandStrings.FormatArgumentName }.ForwardAsSingle(o => $"--format:{o}"); - public static readonly Option Verbosity = new ForwardedOption("--verbosity") + public static readonly Option Verbosity = new Option("--verbosity") { Description = CliCommandStrings.VerbosityDescription, HelpName = CliCommandStrings.VerbosityArgumentName diff --git a/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs b/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs index f1bc3ab7d655..e41367a34fd0 100644 --- a/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs +++ b/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs @@ -7,6 +7,7 @@ using Microsoft.Build.Evaluation; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.FileBasedPrograms; using Microsoft.TemplateEngine.Cli.Commands; namespace Microsoft.DotNet.Cli.Commands.Project.Convert; @@ -30,7 +31,7 @@ public override int Execute() // Find directives (this can fail, so do this before creating the target directory). var sourceFile = SourceFile.Load(file); - var directives = VirtualProjectBuildingCommand.FindDirectives(sourceFile, reportAllErrors: !_force, DiagnosticBag.ThrowOnFirst()); + var directives = FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: !_force, DiagnosticBag.ThrowOnFirst()); // Create a project instance for evaluation. var projectCollection = new ProjectCollection(); diff --git a/src/Cli/dotnet/Commands/Publish/PublishCommand.cs b/src/Cli/dotnet/Commands/Publish/PublishCommand.cs index 45dc32c84300..424a2250c25a 100644 --- a/src/Cli/dotnet/Commands/Publish/PublishCommand.cs +++ b/src/Cli/dotnet/Commands/Publish/PublishCommand.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Restore; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Extensions; @@ -57,7 +58,7 @@ public static CommandBase FromParseResult(ParseResult parseResult, string? msbui noRestore: noRestore, msbuildPath: msbuildPath ), - [CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, PublishCommandParser.TargetOption, PublishCommandParser.VerbosityOption], + [CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, PublishCommandParser.TargetOption, PublishCommandParser.VerbosityOption, PublishCommandParser.NoLogoOption], parseResult, msbuildPath, (msbuildArgs) => diff --git a/src/Cli/dotnet/Commands/Publish/PublishCommandParser.cs b/src/Cli/dotnet/Commands/Publish/PublishCommandParser.cs index 7be9784f5a15..2c2b1b20e36f 100644 --- a/src/Cli/dotnet/Commands/Publish/PublishCommandParser.cs +++ b/src/Cli/dotnet/Commands/Publish/PublishCommandParser.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Build; using Microsoft.DotNet.Cli.Commands.Restore; using Microsoft.DotNet.Cli.Extensions; @@ -18,30 +19,26 @@ internal static class PublishCommandParser Arity = ArgumentArity.ZeroOrMore }; - public static readonly Option OutputOption = new ForwardedOption("--output", "-o") + public static readonly Option OutputOption = new Option("--output", "-o") { Description = CliCommandStrings.PublishOutputOptionDescription, HelpName = CliCommandStrings.PublishOutputOption }.ForwardAsOutputPath("PublishDir"); - public static readonly Option> ManifestOption = new ForwardedOption>("--manifest") + public static readonly Option> ManifestOption = new Option>("--manifest") { Description = CliCommandStrings.ManifestOptionDescription, HelpName = CliCommandStrings.ManifestOption }.ForwardAsSingle(o => $"-property:TargetManifestFiles={string.Join("%3B", o.Select(CommandDirectoryContext.GetFullPath))}") .AllowSingleArgPerToken(); - public static readonly Option NoBuildOption = new ForwardedOption("--no-build") + public static readonly Option NoBuildOption = new Option("--no-build") { Description = CliCommandStrings.NoBuildOptionDescription, Arity = ArgumentArity.Zero }.ForwardAs("-property:NoBuild=true"); - public static readonly Option NoLogoOption = new ForwardedOption("--nologo") - { - Description = CliCommandStrings.PublishCmdNoLogo, - Arity = ArgumentArity.Zero - }.ForwardAs("-nologo"); + public static readonly Option NoLogoOption = CommonOptions.NoLogoOption(); public static readonly Option NoRestoreOption = CommonOptions.NoRestoreOption; @@ -67,7 +64,10 @@ public static Command GetCommand() private static Command ConstructCommand() { - var command = new DocumentedCommand("publish", DocsLink, CliCommandStrings.PublishAppDescription); + var command = new Command("publish", CliCommandStrings.PublishAppDescription) + { + DocsLink = DocsLink + }; command.Arguments.Add(SlnOrProjectOrFileArgument); RestoreCommandParser.AddImplicitRestoreOptions(command, includeRuntimeOption: false, includeNoDependenciesOption: true); diff --git a/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommand.cs b/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommand.cs index b60050fb12cc..10340392c0db 100644 --- a/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommand.cs +++ b/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommand.cs @@ -5,8 +5,8 @@ using System.CommandLine; using Microsoft.Build.Evaluation; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Package; -using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using NuGet.Frameworks; diff --git a/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommandParser.cs b/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommandParser.cs index 5e5690fdd655..777f74585018 100644 --- a/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommandParser.cs +++ b/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommandParser.cs @@ -4,6 +4,8 @@ #nullable disable using System.CommandLine; +using System.CommandLine.StaticCompletions; +using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Cli.Commands.Reference.Add; @@ -21,12 +23,13 @@ internal static class ReferenceAddCommandParser } }; - public static readonly Option FrameworkOption = new DynamicOption("--framework", "-f") + public static readonly Option FrameworkOption = new Option("--framework", "-f") { Description = CliCommandStrings.ReferenceAddCmdFrameworkDescription, - HelpName = CliStrings.CommonCmdFramework - - }.AddCompletions(CliCompletion.TargetFrameworksFromProjectFile); + HelpName = CliStrings.CommonCmdFramework, + IsDynamic = true, + } + .AddCompletions(CliCompletion.TargetFrameworksFromProjectFile); public static readonly Option InteractiveOption = CommonOptions.InteractiveOption(); diff --git a/src/Cli/dotnet/Commands/Reference/List/ReferenceListCommand.cs b/src/Cli/dotnet/Commands/Reference/List/ReferenceListCommand.cs index 55d54b3d1103..cbd7a84e3a73 100644 --- a/src/Cli/dotnet/Commands/Reference/List/ReferenceListCommand.cs +++ b/src/Cli/dotnet/Commands/Reference/List/ReferenceListCommand.cs @@ -8,8 +8,8 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Exceptions; using Microsoft.Build.Execution; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Hidden.List; -using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; namespace Microsoft.DotNet.Cli.Commands.Reference.List; diff --git a/src/Cli/dotnet/Commands/Reference/ReferenceCommandParser.cs b/src/Cli/dotnet/Commands/Reference/ReferenceCommandParser.cs index 7756b35edf04..e26cd6cd8c35 100644 --- a/src/Cli/dotnet/Commands/Reference/ReferenceCommandParser.cs +++ b/src/Cli/dotnet/Commands/Reference/ReferenceCommandParser.cs @@ -7,6 +7,7 @@ using Microsoft.DotNet.Cli.Commands.Reference.Add; using Microsoft.DotNet.Cli.Commands.Reference.List; using Microsoft.DotNet.Cli.Commands.Reference.Remove; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Extensions; namespace Microsoft.DotNet.Cli.Commands.Reference; @@ -30,7 +31,10 @@ public static Command GetCommand() private static Command ConstructCommand() { - var command = new DocumentedCommand("reference", DocsLink, CliCommandStrings.NetRemoveCommand); + var command = new Command("reference", CliCommandStrings.NetRemoveCommand) + { + DocsLink = DocsLink + }; command.Subcommands.Add(ReferenceAddCommandParser.GetCommand()); command.Subcommands.Add(ReferenceListCommandParser.GetCommand()); diff --git a/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommand.cs b/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommand.cs index 64db0bec340d..3a71f6b761ac 100644 --- a/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommand.cs +++ b/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommand.cs @@ -5,8 +5,8 @@ using System.CommandLine; using Microsoft.Build.Evaluation; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Package; -using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; namespace Microsoft.DotNet.Cli.Commands.Reference.Remove; diff --git a/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommandParser.cs b/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommandParser.cs index 9cb48cdfff5f..03e67bdbb37c 100644 --- a/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommandParser.cs +++ b/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommandParser.cs @@ -4,15 +4,19 @@ #nullable disable using System.CommandLine; +using System.CommandLine.StaticCompletions; + +using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Cli.Commands.Reference.Remove; internal static class ReferenceRemoveCommandParser { - public static readonly Argument> ProjectPathArgument = new DynamicArgument>(CliCommandStrings.ReferenceRemoveProjectPathArgumentName) + public static readonly Argument> ProjectPathArgument = new Argument>(CliCommandStrings.ReferenceRemoveProjectPathArgumentName) { Description = CliCommandStrings.ReferenceRemoveProjectPathArgumentDescription, Arity = ArgumentArity.OneOrMore, + IsDynamic = true, }.AddCompletions(CliCompletion.ProjectReferencesFromProjectFile); public static readonly Option FrameworkOption = new("--framework", "-f") diff --git a/src/Cli/dotnet/Commands/Restore/RestoreCommand.cs b/src/Cli/dotnet/Commands/Restore/RestoreCommand.cs index 6eb650b0e261..5a9ff6a37056 100644 --- a/src/Cli/dotnet/Commands/Restore/RestoreCommand.cs +++ b/src/Cli/dotnet/Commands/Restore/RestoreCommand.cs @@ -40,7 +40,7 @@ public static CommandBase FromParseResult(ParseResult result, string? msbuildPat { return CreateForwarding(msbuildArgs, msbuildPath); }, - [CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, RestoreCommandParser.TargetOption, RestoreCommandParser.VerbosityOption], + [CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, RestoreCommandParser.TargetOption, RestoreCommandParser.VerbosityOption, RestoreCommandParser.NoLogoOption], result, msbuildPath ); diff --git a/src/Cli/dotnet/Commands/Restore/RestoreCommandParser.cs b/src/Cli/dotnet/Commands/Restore/RestoreCommandParser.cs index 5477584f5734..f32a0b1545b7 100644 --- a/src/Cli/dotnet/Commands/Restore/RestoreCommandParser.cs +++ b/src/Cli/dotnet/Commands/Restore/RestoreCommandParser.cs @@ -2,7 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; -using Microsoft.DotNet.Cli.Extensions; +using System.CommandLine.StaticCompletions; +using Microsoft.DotNet.Cli.CommandLine; using NuGet.Packaging; namespace Microsoft.DotNet.Cli.Commands.Restore; @@ -17,7 +18,7 @@ internal static class RestoreCommandParser Arity = ArgumentArity.ZeroOrMore }; - public static readonly Option> SourceOption = new ForwardedOption>("--source", "-s") + public static readonly Option> SourceOption = new Option>("--source", "-s") { Description = CliCommandStrings.CmdSourceOptionDescription, HelpName = CliCommandStrings.CmdSourceOption @@ -26,36 +27,36 @@ internal static class RestoreCommandParser public static readonly Option TargetOption = CommonOptions.RequiredMSBuildTargetOption("Restore"); public static readonly Option VerbosityOption = CommonOptions.VerbosityOption(Utils.VerbosityOptions.minimal); - - private static IEnumerable - public static ForwardedOption?> ForwardAsMSBuildProperty(this ForwardedOption?> option) => option - .SetForwardingFunction(propertyDict => ForwardedMSBuildPropertyValues(propertyDict, option.Name)); + public static Option?> ForwardAsMSBuildProperty(this Option?> option) => + option.SetForwardingFunction(propertyDict => ForwardedMSBuildPropertyValues(propertyDict, option.Name)); private static IEnumerable ForwardedMSBuildPropertyValues(ReadOnlyDictionary? properties, string optionName) { @@ -92,152 +58,4 @@ private static IEnumerable ForwardedMSBuildPropertyValues(ReadOnlyDictio return properties.Select(kv => $"{optionName}:{kv.Key}={kv.Value}"); } - - public static Option ForwardAsMany(this ForwardedOption option, Func> format) => option.SetForwardingFunction(format); - - public static Option> ForwardAsManyArgumentsEachPrefixedByOption(this ForwardedOption> option, string alias) => option.ForwardAsMany(o => ForwardedArguments(alias, o)); - - /// - /// Calls the forwarding functions for all options that implement in the provided . - /// - /// - /// If not provided, uses the 's . - /// - public static IEnumerable OptionValuesToBeForwarded(this ParseResult parseResult, Command? command = null) => - (command ?? parseResult.CommandResult.Command).Options - .OfType() - .Select(o => o.GetForwardingFunction()) - .SelectMany(f => f is not null ? f(parseResult) : []); - - public static IEnumerable ForwardedOptionValues(this ParseResult parseResult, Command command, string alias) - { - var func = command.Options? - .Where(o => o.Name.Equals(alias) || o.Aliases.Contains(alias))? - .OfType()? - .FirstOrDefault()? - .GetForwardingFunction(); - return func?.Invoke(parseResult) ?? []; - } - - /// - /// Forces an option that represents a collection-type to only allow a single - /// argument per instance of the option. This means that you'd have to - /// use the option multiple times to pass multiple values. - /// This prevents ambiguity in parsing when argument tokens may appear after the option. - /// - /// - /// - /// - public static T AllowSingleArgPerToken(this T option) where T : Option - { - option.AllowMultipleArgumentsPerToken = false; - return option; - } - - - public static T AggregateRepeatedTokens(this T option) where T : Option - { - option.AllowMultipleArgumentsPerToken = true; - return option; - } - - public static Option Hide(this Option option) - { - option.Hidden = true; - return option; - } - - private static IEnumerable ForwardedArguments(string alias, IEnumerable? arguments) - { - foreach (string arg in arguments ?? []) - { - yield return alias; - yield return arg; - } - } -} - -public interface IForwardedOption -{ - Func> GetForwardingFunction(); -} - -public class ForwardedOption : Option, IForwardedOption -{ - private Func> ForwardingFunction; - - public ForwardedOption(string name, params string[] aliases) : base(name, aliases) - { - ForwardingFunction = _ => []; - } - - public ForwardedOption(string name, Func parseArgument, string? description = null) - : base(name) - { - CustomParser = parseArgument; - Description = description; - ForwardingFunction = _ => []; - } - - public ForwardedOption SetForwardingFunction(Func> func) - { - ForwardingFunction = GetForwardingFunction(func); - return this; - } - - public ForwardedOption SetForwardingFunction(Func format) - { - ForwardingFunction = GetForwardingFunction((o) => [format(o)]); - return this; - } - - public ForwardedOption SetForwardingFunction(Func> func) - { - ForwardingFunction = (ParseResult parseResult) => - { - if (parseResult.GetResult(this) is OptionResult argresult && argresult.GetValue(this) is T validValue) - { - return func(validValue, parseResult) ?? []; - } - else - { - return []; - } - }; - return this; - } - - public Func> GetForwardingFunction(Func> func) - { - return (ParseResult parseResult) => - { - if (parseResult.GetResult(this) is OptionResult r) - { - if (r.GetValueOrDefault() is T value) - { - return func(value); - } - else - { - return []; - } - } - return []; - }; - } - - public Func> GetForwardingFunction() - { - return ForwardingFunction; - } -} - -public class DynamicForwardedOption : ForwardedOption, IDynamicOption -{ - public DynamicForwardedOption(string name, Func parseArgument, string? description = null) - : base(name, parseArgument, description) - { - } - - public DynamicForwardedOption(string name, params string[] aliases) : base(name, aliases) { } } diff --git a/src/Cli/dotnet/Extensions/ParseResultExtensions.cs b/src/Cli/dotnet/Extensions/ParseResultExtensions.cs index 508a1abd1374..6bcf5f84ff8c 100644 --- a/src/Cli/dotnet/Extensions/ParseResultExtensions.cs +++ b/src/Cli/dotnet/Extensions/ParseResultExtensions.cs @@ -5,7 +5,7 @@ using System.CommandLine.Parsing; using System.Diagnostics; using System.Text.RegularExpressions; -using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils.Extensions; @@ -237,71 +237,4 @@ public static void HandleDebugSwitch(this ParseResult parseResult) DebugHelper.WaitForDebugger(); } } - - /// - /// Only returns the value for this option if the option is present and there are no parse errors for that option. - /// This allows cross-cutting code like the telemetry filters to safely get the value without throwing on null-ref errors. - /// If you are inside a command handler or 'normal' System.CommandLine code then you don't need this - the parse error handling - /// will have covered these cases. - /// - public static T? SafelyGetValueForOption(this ParseResult parseResult, Option optionToGet) - { - if (parseResult.GetResult(optionToGet) is OptionResult optionResult && - !parseResult.Errors.Any(e => e.SymbolResult == optionResult)) - { - return optionResult.GetValue(optionToGet); - } - else - { - return default; - } - } - public static T? SafelyGetValueForOption(this ParseResult parseResult, string name) - { - if (parseResult.GetResult(name) is OptionResult optionResult && // only return a value if there _is_ a value - default or otherwise - !parseResult.Errors.Any(e => e.SymbolResult == optionResult) // only return a value if this isn't a parsing error - && optionResult.Option.ValueType.IsAssignableTo(typeof(T))) // only return a value if coercing the type won't error - { - // shouldn't happen because of the above checks, but we can be safe since this is only used in telemetry, and should - // be resistant to errors - try - { - return optionResult.GetValue(name); - } - catch - { - return default; - } - } - else - { - return default; - } - } - - /// - /// Checks if the option is present and not implicit (i.e. not set by default). - /// This is useful for checking if the user has explicitly set an option, as opposed to it being set by default. - /// - public static bool HasOption(this ParseResult parseResult, Option option) => parseResult.GetResult(option) is OptionResult or && !or.Implicit; - - /// - /// Checks if the option with given name is present and not implicit (i.e. not set by default). - /// This is useful for checking if the user has explicitly set an option, as opposed to it being set by default. - /// - public static bool HasOption(this ParseResult parseResult, string name) - => parseResult.GetResult(name) is OptionResult or && !or.Implicit; - - /// - /// Checks if the option is present and not implicit (i.e. not set by default). - /// This is useful for checking if the user has explicitly set an option, as opposed to it being set by default. - /// - public static bool HasOption(this SymbolResult symbolResult, Option option) => symbolResult.GetResult(option) is OptionResult or && !or.Implicit; - - /// - /// Checks if the option with given name is present and not implicit (i.e. not set by default). - /// This is useful for checking if the user has explicitly set an option, as opposed to it being set by default. - /// - public static bool HasOption(this SymbolResult symbolResult, string name) - => symbolResult.GetResult(name) is OptionResult or && !or.Implicit; } diff --git a/src/Cli/dotnet/InteractiveConsole.cs b/src/Cli/dotnet/InteractiveConsole.cs index 2b6a6fdcbd58..1ce4d4f8535e 100644 --- a/src/Cli/dotnet/InteractiveConsole.cs +++ b/src/Cli/dotnet/InteractiveConsole.cs @@ -4,6 +4,7 @@ using System.CommandLine; using System.Diagnostics.CodeAnalysis; using Microsoft.DotNet.Cli.Commands; +using Microsoft.DotNet.Cli.Utils; namespace Microsoft.DotNet.Cli; @@ -30,6 +31,8 @@ public static class InteractiveConsole return null; } + using var _ = Activities.Source.StartActivity("confirm-run-from-source"); + Console.Write(AddPromptOptions(message)); while (true) diff --git a/src/Cli/dotnet/Program.cs b/src/Cli/dotnet/Program.cs index e57fe3a5635f..71feefd8e7f9 100644 --- a/src/Cli/dotnet/Program.cs +++ b/src/Cli/dotnet/Program.cs @@ -9,6 +9,7 @@ using System.Runtime.InteropServices; using Microsoft.DotNet.Cli.CommandFactory; using Microsoft.DotNet.Cli.CommandFactory.CommandResolution; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Commands.Workload; using Microsoft.DotNet.Cli.Extensions; diff --git a/src/Cli/dotnet/Telemetry/EnvironmentDetectionRule.cs b/src/Cli/dotnet/Telemetry/EnvironmentDetectionRule.cs index ebdf6321ddd7..079b8d287d3a 100644 --- a/src/Cli/dotnet/Telemetry/EnvironmentDetectionRule.cs +++ b/src/Cli/dotnet/Telemetry/EnvironmentDetectionRule.cs @@ -97,7 +97,7 @@ public EnvironmentDetectionRuleWithResult(T result, EnvironmentDetectionRule rul public T? GetResult() { return _rule.IsMatch() - ? _result + ? _result : null; } -} +} \ No newline at end of file diff --git a/src/Cli/dotnet/Telemetry/ILLMEnvironmentDetector.cs b/src/Cli/dotnet/Telemetry/ILLMEnvironmentDetector.cs index e2ee21591567..ed7db2596f88 100644 --- a/src/Cli/dotnet/Telemetry/ILLMEnvironmentDetector.cs +++ b/src/Cli/dotnet/Telemetry/ILLMEnvironmentDetector.cs @@ -14,4 +14,4 @@ internal interface ILLMEnvironmentDetector /// Returns true if the current environment is detected to be an LLM/agentic environment, false otherwise. /// bool IsLLMEnvironment(); -} +} \ No newline at end of file diff --git a/src/Cli/dotnet/Telemetry/LLMEnvironmentDetectorForTelemetry.cs b/src/Cli/dotnet/Telemetry/LLMEnvironmentDetectorForTelemetry.cs index b37f9b5d0830..78111c753f30 100644 --- a/src/Cli/dotnet/Telemetry/LLMEnvironmentDetectorForTelemetry.cs +++ b/src/Cli/dotnet/Telemetry/LLMEnvironmentDetectorForTelemetry.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Linq; + namespace Microsoft.DotNet.Cli.Telemetry; internal class LLMEnvironmentDetectorForTelemetry : ILLMEnvironmentDetector @@ -27,4 +30,4 @@ internal class LLMEnvironmentDetectorForTelemetry : ILLMEnvironmentDetector /// public bool IsLLMEnvironment() => !string.IsNullOrEmpty(GetLLMEnvironment()); -} +} \ No newline at end of file diff --git a/src/Cli/dotnet/Telemetry/TelemetryFilter.cs b/src/Cli/dotnet/Telemetry/TelemetryFilter.cs index 349285a497a9..f410cd70c2fa 100644 --- a/src/Cli/dotnet/Telemetry/TelemetryFilter.cs +++ b/src/Cli/dotnet/Telemetry/TelemetryFilter.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.Globalization; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Build; using Microsoft.DotNet.Cli.Commands.Clean; using Microsoft.DotNet.Cli.Commands.Hidden.InternalReportInstallSuccess; diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs index 4ed187cc92dc..72366d0126e6 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs @@ -66,9 +66,11 @@ protected override NuGetVersion DownloadAndExtractPackage( string? folderToDeleteOnFailure = null; return TransactionalAction.Run(() => { + var _downloadActivity = Activities.Source.StartActivity("download-tool"); + _downloadActivity?.DisplayName = $"Downloading tool {packageId}@{packageVersion}"; var packagePath = nugetPackageDownloader.DownloadPackageAsync(packageId, packageVersion, packageSourceLocation, includeUnlisted: includeUnlisted, downloadFolder: new DirectoryPath(packagesRootPath)).ConfigureAwait(false).GetAwaiter().GetResult(); - + _downloadActivity?.Stop(); folderToDeleteOnFailure = Path.GetDirectoryName(packagePath); // look for package on disk and read the version @@ -81,6 +83,7 @@ protected override NuGetVersion DownloadAndExtractPackage( var packageHash = Convert.ToBase64String(new CryptoHashProvider("SHA512").CalculateHash(reader.GetNuspec())); var hashPath = versionFolderPathResolver.GetHashPath(packageId.ToString(), version); + Directory.CreateDirectory(Path.GetDirectoryName(hashPath)!); File.WriteAllText(hashPath, packageHash); } @@ -90,8 +93,10 @@ protected override NuGetVersion DownloadAndExtractPackage( Reporter.Output.WriteLine($"Extracting package {packageId}@{packageVersion} to {packagePath}"); } // Extract the package + var _extractActivity = Activities.Source.StartActivity("extract-tool"); var nupkgDir = versionFolderPathResolver.GetInstallPath(packageId.ToString(), version); nugetPackageDownloader.ExtractPackageAsync(packagePath, new DirectoryPath(nupkgDir)).ConfigureAwait(false).GetAwaiter().GetResult(); + _extractActivity?.Stop(); return version; }, rollback: () => diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageDownloaderBase.cs b/src/Cli/dotnet/ToolPackage/ToolPackageDownloaderBase.cs index 28a8e7973846..134d02d19b53 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageDownloaderBase.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageDownloaderBase.cs @@ -187,8 +187,10 @@ protected IToolPackage InstallGlobalToolPackageInternal( // Create parent directory in global tool store, for example dotnet\tools\.store\powershell _fileSystem.Directory.CreateDirectory(toolStoreTargetDirectory.GetParentPath().Value); + var _moveContentActivity = Activities.Source.StartActivity("move-global-tool-content"); // Move tool files from stage to final location FileAccessRetrier.RetryOnMoveAccessFailure(() => _fileSystem.Directory.Move(_globalToolStageDir.Value, toolStoreTargetDirectory.Value)); + _moveContentActivity?.Dispose(); rollbackDirectory = toolStoreTargetDirectory.Value; @@ -374,6 +376,7 @@ protected void UpdateRuntimeConfig( ToolPackageInstance toolPackageInstance ) { + using var _updateRuntimeConfigActivity = Activities.Source.StartActivity("update-runtimeconfig"); var runtimeConfigFilePath = Path.ChangeExtension(toolPackageInstance.Command.Executable.Value, ".runtimeconfig.json"); // Update the runtimeconfig.json file diff --git a/src/Cli/dotnet/dotnet.csproj b/src/Cli/dotnet/dotnet.csproj index 2e63f9a3c5a8..3cdb3b20beeb 100644 --- a/src/Cli/dotnet/dotnet.csproj +++ b/src/Cli/dotnet/dotnet.csproj @@ -24,7 +24,6 @@ - @@ -48,6 +47,7 @@ + @@ -101,10 +101,8 @@ $([System.IO.Path]::Combine($(TestHostDotNetRoot), "sdk", $(Version))) - + + diff --git a/src/Cli/dotnet/xlf/CliStrings.cs.xlf b/src/Cli/dotnet/xlf/CliStrings.cs.xlf index a55342a500c5..65d8392b66f2 100644 --- a/src/Cli/dotnet/xlf/CliStrings.cs.xlf +++ b/src/Cli/dotnet/xlf/CliStrings.cs.xlf @@ -100,15 +100,16 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Nastavuje hodnotu proměnné prostředí. -Pokud proměnná neexistuje, vytvoří ji, a pokud existuje, přepíše ji. + Nastaví hodnotu proměnné prostředí. +Pokud proměnná neexistuje, vytvoří ji, a pokud existuje, přepíše ji. +Tímto se vynutí spuštění testů v izolovaném procesu. Tento argument je možné zadat vícekrát a určit tak více proměnných. -Examples: --e VARIABLE=abc --e VARIABLE="hodnota s mezerami" --e VARIABLE="hodnota;oddělená;pomocí;středníků" --e VAR1=abc -e VAR2=def -e VAR3=ghi +Příklady: +-e PROMĚNNÁ=abc +-e PROMĚNNÁ="hodnota s mezerami" +-e PROMĚNNÁ="hodnota;oddělená;pomocí;středníků" +-e PROM1=abc -e PROM2=def -e PROM3=ghi @@ -129,16 +130,16 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Nastaví hodnotu proměnné prostředí. -Pokud proměnná neexistuje, vytvoří ji, a pokud existuje, přepíše ji. -Tímto se vynutí spuštění testů v izolovaném procesu. -Tento argument je možné zadat vícekrát a určit tak více proměnných. + Sets the value of an environment variable. +Creates the variable if it does not exist, overrides if it does. +This will force the tests to be run in an isolated process. +This argument can be specified multiple times to provide multiple variables. -Příklady: --e PROMĚNNÁ=abc --e PROMĚNNÁ="hodnota s mezerami" --e PROMĚNNÁ="hodnota;oddělená;pomocí;středníků" --e PROM1=abc -e PROM2=def -e PROM3=ghi +Examples: +-e VARIABLE=abc +-e VARIABLE="value with spaces" +-e VARIABLE="value;seperated with;semicolons" +-e VAR1=abc -e VAR2=def -e VAR3=ghi @@ -187,16 +188,6 @@ Příklady: CONFIGURATION - - Could not find any project in `{0}`. - V {0} se nenašel žádný projekt. - - - - Could not find project or directory `{0}`. - Nenašel se projekt ani adresář {0}. - - Specified solution file {0} does not exist, or there is no solution file in the directory. Zadaný soubor řešení {0} neexistuje nebo v adresáři není soubor řešení. @@ -683,11 +674,6 @@ setx PATH "%PATH%;{0}" Je k dispozici více než jedno zabalené překrytí: {0}. - - Found more than one project in `{0}`. Specify which one to use. - V {0} se našlo několik projektů. Vyberte, který z nich chcete použít. - - Found more than one solution file in {0}. Specify which one to use. V {0} se našlo několik souborů řešení. Vyberte, který z nich chcete použít. @@ -784,7 +770,7 @@ setx PATH "%PATH%;{0}" Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. - Odkaz na balíček ve formě identifikátoru balíčku, jako je „{0}“, nebo identifikátor balíčku a verze oddělené znakem @, například „{0}@{1}“. + Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. @@ -1033,6 +1019,11 @@ Výchozí hodnota je false. Pokud však cílíte na .NET 7 nebo nižší a je za Zadaný soubor řešení {0} neexistuje nebo v adresáři není soubor řešení. + + Solution folder '{0}' already contains a project with the filename '{1}'. + Solution folder '{0}' already contains a project with the filename '{1}'. + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. Soubor projektu nebo řešení, se kterým se má operace provést. Pokud soubor není zadaný, příkaz ho bude hledat v aktuálním adresáři. diff --git a/src/Cli/dotnet/xlf/CliStrings.de.xlf b/src/Cli/dotnet/xlf/CliStrings.de.xlf index 92adccab4830..d79a4f07b422 100644 --- a/src/Cli/dotnet/xlf/CliStrings.de.xlf +++ b/src/Cli/dotnet/xlf/CliStrings.de.xlf @@ -100,7 +100,7 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Legt den Wert einer Umgebungsvariablen fest. + Legt den Wert einer Umgebungsvariablen fest. Erstellt die Variable, wenn Sie nicht vorhanden ist, und setzt sie andernfalls außer Kraft. Dieses Argument kann mehrmals angegeben werden, um mehrere Variablen bereitzustellen. @@ -129,15 +129,15 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Legt den Wert einer Umgebungsvariablen fest. -Erstellt die Variable, wenn Sie nicht vorhanden ist, und setzt sie andernfalls außer Kraft. -Dadurch wird die Ausführung der Tests in einem isolierten Prozess erzwungen. -Dieses Argument kann mehrmals angegeben werden, um mehrere Variablen bereitzustellen. + Sets the value of an environment variable. +Creates the variable if it does not exist, overrides if it does. +This will force the tests to be run in an isolated process. +This argument can be specified multiple times to provide multiple variables. -Beispiele: +Examples: -e VARIABLE=abc --e VARIABLE="wert mit leerzeichen" --e VARIABLE="wert;getrennt durch;semikolons" +-e VARIABLE="value with spaces" +-e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi @@ -187,16 +187,6 @@ Beispiele: CONFIGURATION - - Could not find any project in `{0}`. - In "{0}" wurde kein Projekt gefunden. - - - - Could not find project or directory `{0}`. - Das Projekt oder Verzeichnis "{0}" wurde nicht gefunden. - - Specified solution file {0} does not exist, or there is no solution file in the directory. Die angegebene Projektmappendatei "{0}" ist nicht vorhanden, oder das Verzeichnis enthält keine Projektmappendatei. @@ -683,11 +673,6 @@ setx PATH "%PATH%;{0}" Mehr als ein paketierter Shim verfügbar: {0}. - - Found more than one project in `{0}`. Specify which one to use. - In "{0}" wurden mehrere Projekte gefunden. Geben Sie an, welches davon verwendet werden soll. - - Found more than one solution file in {0}. Specify which one to use. In "{0}" wurden mehrere Projektmappendateien gefunden. Geben Sie an, welche davon verwendet werden soll. @@ -784,7 +769,7 @@ setx PATH "%PATH%;{0}" Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. - Paketverweis in Form eines Paketbezeichners wie {0} oder Paketbezeichner und -version getrennt durch „@“ wie „{0}@{1}“. + Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. @@ -1033,6 +1018,11 @@ Der Standardwert lautet FALSE. Wenn sie jedoch auf .NET 7 oder niedriger abziele Die angegebene Projektmappendatei "{0}" ist nicht vorhanden, oder das Verzeichnis enthält keine Projektmappendatei. + + Solution folder '{0}' already contains a project with the filename '{1}'. + Solution folder '{0}' already contains a project with the filename '{1}'. + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. Das Projekt oder die Projektmappendatei, die verwendet werden soll. Wenn keine Datei angegeben ist, durchsucht der Befehl das aktuelle Verzeichnis nach einer Datei. diff --git a/src/Cli/dotnet/xlf/CliStrings.es.xlf b/src/Cli/dotnet/xlf/CliStrings.es.xlf index db4dd74c1447..c72c86d6fddb 100644 --- a/src/Cli/dotnet/xlf/CliStrings.es.xlf +++ b/src/Cli/dotnet/xlf/CliStrings.es.xlf @@ -100,7 +100,7 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Establece el valor de una variable de entorno. + Establece el valor de una variable de entorno. Crea la variable si no existe o la reemplaza en caso de que exista. Este argumento se puede especificar varias veces para proporcionar múltiples variables. @@ -129,12 +129,12 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Establece el valor de una variable de entorno. -Crea la variable si no existe o la reemplaza en caso de que exista. -Esto forzará la ejecución de las pruebas en un proceso aislado. -Este argumento se puede especificar varias veces para proporcionar múltiples variables. + Sets the value of an environment variable. +Creates the variable if it does not exist, overrides if it does. +This will force the tests to be run in an isolated process. +This argument can be specified multiple times to provide multiple variables. -Ejemplos: +Examples: -e VARIABLE=abc -e VARIABLE="value with spaces" -e VARIABLE="value;seperated with;semicolons" @@ -187,16 +187,6 @@ Ejemplos: CONFIGURATION - - Could not find any project in `{0}`. - No se encuentra ningún proyecto en "{0}". - - - - Could not find project or directory `{0}`. - No se encuentra el proyecto o directorio "{0}". - - Specified solution file {0} does not exist, or there is no solution file in the directory. El archivo de solución {0} especificado no existe, o bien no hay ningún archivo de solución en el directorio. @@ -683,11 +673,6 @@ setx PATH "%PATH%;{0}" Hay más de una opción de correcciones de compatibilidad (shim) empaquetadas: {0}. - - Found more than one project in `{0}`. Specify which one to use. - Se han encontrado varios proyectos en "{0}". Especifique el que debe usarse. - - Found more than one solution file in {0}. Specify which one to use. Se han encontrado varios archivos de solución en {0}. Especifique el que debe usarse. @@ -784,7 +769,7 @@ setx PATH "%PATH%;{0}" Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. - Referencia de paquete en forma de identificador de paquete como "{0}" o identificador de paquete y versión separados por "@", como "{0}@{1}". + Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. @@ -1033,6 +1018,11 @@ El valor predeterminado es "false." Sin embargo, cuando el destino es .NET 7 o i El archivo de solución {0} especificado no existe, o bien no hay ningún archivo de solución en el directorio. + + Solution folder '{0}' already contains a project with the filename '{1}'. + Solution folder '{0}' already contains a project with the filename '{1}'. + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. El archivo de proyecto o solución donde operar. Si no se especifica un archivo, el comando buscará uno en el directorio actual. diff --git a/src/Cli/dotnet/xlf/CliStrings.fr.xlf b/src/Cli/dotnet/xlf/CliStrings.fr.xlf index f9b5582e4302..ffce7f406825 100644 --- a/src/Cli/dotnet/xlf/CliStrings.fr.xlf +++ b/src/Cli/dotnet/xlf/CliStrings.fr.xlf @@ -100,14 +100,15 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Définit la valeur d’une variable d’environnement. -Crée la variable si elle n’existe pas, et la remplace si elle existe. + Définit la valeur d'une variable d'environnement. +Crée la variable si elle n'existe pas, et la remplace si elle existe. +Cela entraîne l'exécution forcée des tests dans un processus isolé. Vous pouvez spécifier cet argument plusieurs fois pour fournir plusieurs variables. Exemples : -e VARIABLE=abc --e VARIABLE="value with spaces" --e VARIABLE="value;seperated with;semicolons" +-e VARIABLE="valeur avec des espaces" +-e VARIABLE="valeur;séparée;par;des;points;virgules" -e VAR1=abc -e VAR2=def -e VAR3=ghi @@ -129,15 +130,15 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Définit la valeur d'une variable d'environnement. -Crée la variable si elle n'existe pas, et la remplace si elle existe. -Cela entraîne l'exécution forcée des tests dans un processus isolé. -Vous pouvez spécifier cet argument plusieurs fois pour fournir plusieurs variables. + Sets the value of an environment variable. +Creates the variable if it does not exist, overrides if it does. +This will force the tests to be run in an isolated process. +This argument can be specified multiple times to provide multiple variables. -Exemples : +Examples: -e VARIABLE=abc --e VARIABLE="valeur avec des espaces" --e VARIABLE="valeur;séparée;par;des;points;virgules" +-e VARIABLE="value with spaces" +-e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi @@ -187,16 +188,6 @@ Exemples : CONFIGURATION - - Could not find any project in `{0}`. - Projet introuvable dans '{0}'. - - - - Could not find project or directory `{0}`. - Projet ou répertoire '{0}' introuvable. - - Specified solution file {0} does not exist, or there is no solution file in the directory. Le fichier solution spécifié {0} n'existe pas ou il n'y a pas de fichier solution dans le répertoire. @@ -683,11 +674,6 @@ setx PATH "%PATH%;{0}" Plusieurs shims empaquetés sont disponibles : {0}. - - Found more than one project in `{0}`. Specify which one to use. - Plusieurs projets dans '{0}'. Spécifiez celui à utiliser. - - Found more than one solution file in {0}. Specify which one to use. Plusieurs fichiers solution dans {0}. Spécifiez celui à utiliser. @@ -784,7 +770,7 @@ setx PATH "%PATH%;{0}" Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. - Référence de package sous la forme d’un identificateur de package tel que « {0} » ou d’un identificateur de package et d’une version séparés par « @ », comme « {0}@{1} ». + Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. @@ -1033,6 +1019,11 @@ La valeur par défaut est « false ». Toutefois, lorsque vous ciblez .NET 7 o Le fichier solution spécifié {0} n'existe pas ou il n'y a pas de fichier solution dans le répertoire. + + Solution folder '{0}' already contains a project with the filename '{1}'. + Solution folder '{0}' already contains a project with the filename '{1}'. + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. Fichier projet ou solution à utiliser. Si vous ne spécifiez pas de fichier, la commande en recherche un dans le répertoire actuel. diff --git a/src/Cli/dotnet/xlf/CliStrings.it.xlf b/src/Cli/dotnet/xlf/CliStrings.it.xlf index 8e5fb8a55e03..f4bb209f3056 100644 --- a/src/Cli/dotnet/xlf/CliStrings.it.xlf +++ b/src/Cli/dotnet/xlf/CliStrings.it.xlf @@ -100,7 +100,7 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Imposta il valore di una variabile di ambiente. + Imposta il valore di una variabile di ambiente. Crea la variabile se non esiste e la sostituisce se esiste. È possibile specificare più volte l'argomento per fornire più variabili. @@ -129,15 +129,15 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Imposta il valore di una variabile di ambiente. -Crea la variabile se non esiste e la sostituisce se esiste. -In questo modo forza l'esecuzione dei test in un processo isolato. -È possibile specificare più volte questo argomento per fornire più variabili. + Sets the value of an environment variable. +Creates the variable if it does not exist, overrides if it does. +This will force the tests to be run in an isolated process. +This argument can be specified multiple times to provide multiple variables. -Esempi: +Examples: -e VARIABLE=abc --e VARIABLE="valore con spazi" --e VARIABLE="valore;delimitato da;punti e virgola" +-e VARIABLE="value with spaces" +-e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi @@ -187,16 +187,6 @@ Esempi: CONFIGURATION - - Could not find any project in `{0}`. - Non è stato trovato alcun progetto in `{0}`. - - - - Could not find project or directory `{0}`. - Non sono stati trovati progetti o directory `{0}`. - - Specified solution file {0} does not exist, or there is no solution file in the directory. Il file di soluzione specificato {0} non esiste oppure nella directory non è presente alcun file di soluzione. @@ -683,11 +673,6 @@ setx PATH "%PATH%;{0}" Sono disponibili più shim inclusi nel pacchetto: {0}. - - Found more than one project in `{0}`. Specify which one to use. - Sono stati trovati più progetti in `{0}`. Specificare quello da usare. - - Found more than one solution file in {0}. Specify which one to use. Sono stati trovati più file di soluzione in {0}. Specificare quello da usare. @@ -784,7 +769,7 @@ setx PATH "%PATH%;{0}" Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. - Riferimento al pacchetto sotto forma di identificatore di pacchetto, ad esempio '{0}', oppure identificatore e versione di pacchetto separati da '@', ad esempio '{0}@{1}'. + Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. @@ -1033,6 +1018,11 @@ Il valore predefinito è 'false'. Tuttavia, quando la destinazione è .NET 7 o u Il file di soluzione specificato {0} non esiste oppure nella directory non è presente alcun file di soluzione. + + Solution folder '{0}' already contains a project with the filename '{1}'. + Solution folder '{0}' already contains a project with the filename '{1}'. + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. File di progetto o di soluzione su cui intervenire. Se non si specifica un file, il comando ne cercherà uno nella directory corrente. diff --git a/src/Cli/dotnet/xlf/CliStrings.ja.xlf b/src/Cli/dotnet/xlf/CliStrings.ja.xlf index 2504e04d917d..025a2a04c987 100644 --- a/src/Cli/dotnet/xlf/CliStrings.ja.xlf +++ b/src/Cli/dotnet/xlf/CliStrings.ja.xlf @@ -100,7 +100,7 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - 環境変数の値を設定します。 + 環境変数の値を設定します。 変数が存在しない場合は作成され、存在する場合はオーバーライドされます。 この引数は、複数の変数を指定するために複数回指定できます。 @@ -129,12 +129,12 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - 環境変数の値を設定します。 -変数が存在しない場合は作成され、存在する場合はオーバーライドされます。 -これにより、テストは強制的に分離プロセスで実行されます。 -この引数は、複数の変数を指定するために複数回指定できます。 + Sets the value of an environment variable. +Creates the variable if it does not exist, overrides if it does. +This will force the tests to be run in an isolated process. +This argument can be specified multiple times to provide multiple variables. -例: +Examples: -e VARIABLE=abc -e VARIABLE="value with spaces" -e VARIABLE="value;seperated with;semicolons" @@ -187,16 +187,6 @@ Examples: CONFIGURATION - - Could not find any project in `{0}`. - `{0}` にプロジェクトが見つかりませんでした。 - - - - Could not find project or directory `{0}`. - プロジェクトまたはディレクトリ `{0}` が見つかりませんでした。 - - Specified solution file {0} does not exist, or there is no solution file in the directory. 指定したソリューション ファイル {0} が存在しないか、ディレクトリにソリューション ファイルがありません。 @@ -683,11 +673,6 @@ setx PATH "%PATH%;{0}" 複数のパッケージ化された shim が利用可能です: {0}。 - - Found more than one project in `{0}`. Specify which one to use. - `{0}` に複数のプロジェクトが見つかりました。使用するプロジェクトを指定してください。 - - Found more than one solution file in {0}. Specify which one to use. {0} に複数のソリューション ファイルが見つかりました。使用するファイルを指定してください。 @@ -784,7 +769,7 @@ setx PATH "%PATH%;{0}" Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. - '{0}' のようなパッケージ ID の形式のパッケージ参照、または '{0}@{1}' のように '@' で区切られたパッケージ ID とバージョンです。 + Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. @@ -1033,6 +1018,11 @@ The default is 'false.' However, when targeting .NET 7 or lower, the default is 指定したソリューション ファイル {0} が存在しないか、ディレクトリにソリューション ファイルがありません。 + + Solution folder '{0}' already contains a project with the filename '{1}'. + Solution folder '{0}' already contains a project with the filename '{1}'. + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. 利用するプロジェクト ファイルまたはソリューション ファイル。指定しない場合、コマンドは現在のディレクトリを検索します。 diff --git a/src/Cli/dotnet/xlf/CliStrings.ko.xlf b/src/Cli/dotnet/xlf/CliStrings.ko.xlf index af4f6dc21b68..0718276866cf 100644 --- a/src/Cli/dotnet/xlf/CliStrings.ko.xlf +++ b/src/Cli/dotnet/xlf/CliStrings.ko.xlf @@ -100,7 +100,7 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - 환경 변수의 값을 설정합니다. + 환경 변수의 값을 설정합니다. 변수가 없는 경우 변수를 만들고, 변수가 있으면 재정의합니다. 이 인수를 여러 번 지정하여 여러 변수를 제공할 수 있습니다. @@ -129,12 +129,12 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - 환경 변수의 값을 설정합니다. -변수가 없는 경우 변수를 만들고, 변수가 있으면 재정의합니다. -이는 테스트가 격리된 프로세스에서 강제로 실행되도록 합니다. -이 인수를 여러 번 지정하여 여러 변수를 제공할 수 있습니다. + Sets the value of an environment variable. +Creates the variable if it does not exist, overrides if it does. +This will force the tests to be run in an isolated process. +This argument can be specified multiple times to provide multiple variables. -예: +Examples: -e VARIABLE=abc -e VARIABLE="value with spaces" -e VARIABLE="value;seperated with;semicolons" @@ -187,16 +187,6 @@ Examples: CONFIGURATION - - Could not find any project in `{0}`. - '{0}'에서 프로젝트를 찾을 수 없습니다. - - - - Could not find project or directory `{0}`. - 프로젝트 또는 디렉터리 {0}을(를) 찾을 수 없습니다. - - Specified solution file {0} does not exist, or there is no solution file in the directory. 지정한 솔루션 파일 {0}이(가) 없거나 디렉터리에 솔루션 파일이 없습니다. @@ -683,11 +673,6 @@ setx PATH "%PATH%;{0}" 패키지된 shim을 하나 이상 사용할 수 있습니다. {0} - - Found more than one project in `{0}`. Specify which one to use. - '{0}'에서 프로젝트를 두 개 이상 찾았습니다. 사용할 프로젝트를 지정하세요. - - Found more than one solution file in {0}. Specify which one to use. {0}에서 솔루션 파일을 두 개 이상 찾았습니다. 사용할 파일을 지정하세요. @@ -784,7 +769,7 @@ setx PATH "%PATH%;{0}" Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. - '{0}'과 같은 패키지 식별자 또는 '{0}@{1}'과 같이 '@'로 구분된 패키지 식별자 및 버전 형식의 패키지 참조입니다. + Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. @@ -1033,6 +1018,11 @@ The default is 'false.' However, when targeting .NET 7 or lower, the default is 지정한 솔루션 파일 {0}이(가) 없거나 디렉터리에 솔루션 파일이 없습니다. + + Solution folder '{0}' already contains a project with the filename '{1}'. + Solution folder '{0}' already contains a project with the filename '{1}'. + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. 수행할 프로젝트 또는 솔루션 파일입니다. 파일을 지정하지 않으면 명령이 현재 디렉토리에서 파일을 검색합니다. diff --git a/src/Cli/dotnet/xlf/CliStrings.pl.xlf b/src/Cli/dotnet/xlf/CliStrings.pl.xlf index b92f633f89d0..49e51a8dab95 100644 --- a/src/Cli/dotnet/xlf/CliStrings.pl.xlf +++ b/src/Cli/dotnet/xlf/CliStrings.pl.xlf @@ -100,7 +100,7 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Ustawia wartość zmiennej środowiskowej. + Ustawia wartość zmiennej środowiskowej. Jeśli zmienna nie istnieje, tworzy ją, a jeśli istnieje, przesłania. Ten argument można określić wiele razy w celu podania wielu wartości. @@ -129,16 +129,16 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Ustawia wartość zmiennej środowiskowej. -Jeśli zmienna nie istnieje, tworzy ją, a jeśli istnieje, przesłania. -Wymusi to uruchamianie testów w izolowanym procesie. -Ten argument można określić wiele razy w celu podania wielu wartości. + Sets the value of an environment variable. +Creates the variable if it does not exist, overrides if it does. +This will force the tests to be run in an isolated process. +This argument can be specified multiple times to provide multiple variables. -Przykłady: --e ZMIENNA=abc --e ZMIENNA="wartość ze spacjami" --e ZMIENNA="wartości;rozdzielone;średnikami" --e ZM1=abc -e ZM2=def -e ZM3=ghi +Examples: +-e VARIABLE=abc +-e VARIABLE="value with spaces" +-e VARIABLE="value;seperated with;semicolons" +-e VAR1=abc -e VAR2=def -e VAR3=ghi @@ -187,16 +187,6 @@ Przykłady: CONFIGURATION - - Could not find any project in `{0}`. - Nie można odnaleźć żadnego projektu w lokalizacji „{0}”. - - - - Could not find project or directory `{0}`. - Nie można odnaleźć projektu ani katalogu „{0}”. - - Specified solution file {0} does not exist, or there is no solution file in the directory. Określony plik rozwiązania {0} nie istnieje bądź w katalogu nie ma żadnego pliku rozwiązania. @@ -683,11 +673,6 @@ setx PATH "%PATH%;{0}" Dostępna jest więcej niż jedna spakowana podkładka: {0}. - - Found more than one project in `{0}`. Specify which one to use. - Znaleziono więcej niż jeden projekt w lokalizacji „{0}”. Określ, który ma zostać użyty. - - Found more than one solution file in {0}. Specify which one to use. Znaleziono więcej niż jeden plik rozwiązania w lokalizacji {0}. Określ, który ma zostać użyty. @@ -784,7 +769,7 @@ setx PATH "%PATH%;{0}" Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. - Odwołanie do pakietu w formie identyfikatora pakietu, takiego jak „{0}” lub identyfikatora pakietu i wersji, rozdzielonych znakiem „@”, np. „{0}@{1}”. + Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. @@ -1033,6 +1018,11 @@ Wartość domyślna to „false”. Jednak w przypadku określania wartości doc Określony plik rozwiązania {0} nie istnieje bądź w katalogu nie ma żadnego pliku rozwiązania. + + Solution folder '{0}' already contains a project with the filename '{1}'. + Solution folder '{0}' already contains a project with the filename '{1}'. + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. Plik projektu lub rozwiązania, dla którego ma zostać wykonana operacja. Jeśli plik nie zostanie podany, polecenie wyszuka go w bieżącym katalogu. diff --git a/src/Cli/dotnet/xlf/CliStrings.pt-BR.xlf b/src/Cli/dotnet/xlf/CliStrings.pt-BR.xlf index 48cd96b70fad..18d6646955a9 100644 --- a/src/Cli/dotnet/xlf/CliStrings.pt-BR.xlf +++ b/src/Cli/dotnet/xlf/CliStrings.pt-BR.xlf @@ -100,9 +100,10 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Define o valor de uma variável de ambiente. -Cria a variável se ela não existir, substitui se existir. -Este argumento pode ser especificado várias vezes para fornecer múltiplas variáveis. + Define o valor de uma variável de ambiente. +Criará a variável quando ela não existir e a substituirá quando existir. +Isso forçará a execução dos testes em um processo isolado. +Esse argumento pode ser especificado várias vezes para fornecer várias variáveis. Exemplos: -e VARIABLE=abc @@ -129,15 +130,15 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Define o valor de uma variável de ambiente. -Criará a variável quando ela não existir e a substituirá quando existir. -Isso forçará a execução dos testes em um processo isolado. -Esse argumento pode ser especificado várias vezes para fornecer várias variáveis. + Sets the value of an environment variable. +Creates the variable if it does not exist, overrides if it does. +This will force the tests to be run in an isolated process. +This argument can be specified multiple times to provide multiple variables. -Exemplos: +Examples: -e VARIABLE=abc --e VARIABLE="valor com espaços" --e VARIABLE="valor;separado com;ponto e vírgula" +-e VARIABLE="value with spaces" +-e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi @@ -187,16 +188,6 @@ Exemplos: CONFIGURATION - - Could not find any project in `{0}`. - Não foi possível encontrar nenhum projeto em ‘{0}’. - - - - Could not find project or directory `{0}`. - Não foi possível encontrar o projeto ou diretório ‘{0}’. - - Specified solution file {0} does not exist, or there is no solution file in the directory. O arquivo de solução {0} especificado não existe ou não há um arquivo de solução no diretório. @@ -683,11 +674,6 @@ setx PATH "%PATH%;{0}" Há mais de um shim empacotado disponível: {0}. - - Found more than one project in `{0}`. Specify which one to use. - Foi encontrado mais de um projeto em ‘{0}’. Especifique qual deve ser usado. - - Found more than one solution file in {0}. Specify which one to use. Foi encontrado mais de um arquivo de solução em {0}. Especifique qual deve ser usado. @@ -784,7 +770,7 @@ setx PATH "%PATH%;{0}" Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. - Referência de pacote na forma de um identificador de pacote como '{0}' ou identificador de pacote e versão separados por '@', como '{0}@{1}'. + Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. @@ -1033,6 +1019,11 @@ O padrão é 'false.' No entanto, ao direcionar para .NET 7 ou inferior, o padr O arquivo de solução {0} especificado não existe ou não há um arquivo de solução no diretório. + + Solution folder '{0}' already contains a project with the filename '{1}'. + Solution folder '{0}' already contains a project with the filename '{1}'. + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. O arquivo de solução ou projeto para operar. Se um arquivo não for especificado, o comando pesquisará um no diretório atual. diff --git a/src/Cli/dotnet/xlf/CliStrings.ru.xlf b/src/Cli/dotnet/xlf/CliStrings.ru.xlf index 774a83ee7bd9..341f9d41bc55 100644 --- a/src/Cli/dotnet/xlf/CliStrings.ru.xlf +++ b/src/Cli/dotnet/xlf/CliStrings.ru.xlf @@ -100,9 +100,10 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Устанавливает значение переменной среды. -Если переменной среды не существует, она создается, если существует — переопределяется. -Этот аргумент может быть указан несколько раз для указания нескольких переменных. + Устанавливает значение переменной среды. +Если переменной среды не существует, она создается. Если переменная среды существует, она переопределяется. +Этот аргумент подразумевает принудительное выполнение тестов в изолированном процессе. +Этот аргумент может быть указан несколько раз для нескольких переменных среды. Примеры: -e VARIABLE=abc @@ -129,15 +130,15 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Устанавливает значение переменной среды. -Если переменной среды не существует, она создается. Если переменная среды существует, она переопределяется. -Этот аргумент подразумевает принудительное выполнение тестов в изолированном процессе. -Этот аргумент может быть указан несколько раз для нескольких переменных среды. + Sets the value of an environment variable. +Creates the variable if it does not exist, overrides if it does. +This will force the tests to be run in an isolated process. +This argument can be specified multiple times to provide multiple variables. -Примеры: +Examples: -e VARIABLE=abc --e VARIABLE="значение с пробелами" --e VARIABLE="значение;разделенное;точками;с;запятыми" +-e VARIABLE="value with spaces" +-e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi @@ -187,16 +188,6 @@ Examples: CONFIGURATION - - Could not find any project in `{0}`. - Не удалось найти проекты в "{0}". - - - - Could not find project or directory `{0}`. - Не удалось найти проект или каталог "{0}". - - Specified solution file {0} does not exist, or there is no solution file in the directory. Указанный файл решения "{0}" не существует, или в каталоге нет файла решения. @@ -683,11 +674,6 @@ setx PATH "%PATH%;{0}" Доступно несколько упакованных оболочек совместимости: {0}. - - Found more than one project in `{0}`. Specify which one to use. - Найдено несколько проектов в "{0}". Выберите один. - - Found more than one solution file in {0}. Specify which one to use. Найдено несколько файлов решений в {0}. Выберите один. @@ -784,7 +770,7 @@ setx PATH "%PATH%;{0}" Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. - Ссылка на пакет в виде идентификатора пакета, например "{0}", или идентификатора пакета и версии, разделенных "@", например "{0}@{1}". + Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. @@ -1033,6 +1019,11 @@ The default is 'false.' However, when targeting .NET 7 or lower, the default is Указанный файл решения "{0}" не существует, или в каталоге нет файла решения. + + Solution folder '{0}' already contains a project with the filename '{1}'. + Solution folder '{0}' already contains a project with the filename '{1}'. + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. Файл проекта или решения. Если файл не указан, команда будет искать его в текущем каталоге. diff --git a/src/Cli/dotnet/xlf/CliStrings.tr.xlf b/src/Cli/dotnet/xlf/CliStrings.tr.xlf index e3a3c95596ea..26da8f61300c 100644 --- a/src/Cli/dotnet/xlf/CliStrings.tr.xlf +++ b/src/Cli/dotnet/xlf/CliStrings.tr.xlf @@ -100,7 +100,7 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Bir ortam değişkeninin değerini ayarlar. + Bir ortam değişkeninin değerini ayarlar. Değişken yoksa oluşturur, varsa değişkeni geçersiz kılar. Bu bağımsız değişken, birden çok değişken sağlamak için birden çok kez belirtilebilir. @@ -129,16 +129,16 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - Bir ortam değişkeninin değerini ayarlar. -Değişken yoksa oluşturur, varsa değişkeni geçersiz kılar. -Bu, testleri yalıtılmış bir işlemde çalıştırılmaya zorlar. -Bu bağımsız değişken, birden çok değişken sağlamak için birden çok kez belirtilebilir. + Sets the value of an environment variable. +Creates the variable if it does not exist, overrides if it does. +This will force the tests to be run in an isolated process. +This argument can be specified multiple times to provide multiple variables. -Örnek: --e DEĞİŞKEN=abc --e DEĞİŞKEN="boşluk içeren değerler" --e DEĞİŞKEN="noktalı virgülle;ayrılmış;değerler" --e DEĞ1=abc -e DEĞ2=def -e DEĞ3=ghi +Examples: +-e VARIABLE=abc +-e VARIABLE="value with spaces" +-e VARIABLE="value;seperated with;semicolons" +-e VAR1=abc -e VAR2=def -e VAR3=ghi @@ -187,16 +187,6 @@ Bu bağımsız değişken, birden çok değişken sağlamak için birden çok ke CONFIGURATION - - Could not find any project in `{0}`. - `{0}` içinde proje bulunamadı. - - - - Could not find project or directory `{0}`. - `{0}` projesi veya dizini bulunamadı. - - Specified solution file {0} does not exist, or there is no solution file in the directory. Belirtilen {0} çözüm dosyası yok veya dizinde bir çözüm dosyası yok. @@ -683,11 +673,6 @@ setx PATH "%PATH%;{0}" Birden fazla paketlenmiş dolgu var: {0}. - - Found more than one project in `{0}`. Specify which one to use. - `{0}` içinde birden fazla proje bulundu. Hangisinin kullanılacağını belirtin. - - Found more than one solution file in {0}. Specify which one to use. {0} içinde birden fazla çözüm dosyası bulundu. Hangisinin kullanılacağını belirtin. @@ -784,7 +769,7 @@ setx PATH "%PATH%;{0}" Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. - '{0}' gibi bir paket tanımlayıcısı veya '{0}@{1}' gibi '@' ile ayrılmış paket tanımlayıcısı ve sürümü şeklinde paket başvurusu. + Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. @@ -1033,6 +1018,11 @@ Varsayılan değer 'false.' Ancak çalışma zamanı tanımlayıcısı belirtild Belirtilen {0} çözüm dosyası yok veya dizinde bir çözüm dosyası yok. + + Solution folder '{0}' already contains a project with the filename '{1}'. + Solution folder '{0}' already contains a project with the filename '{1}'. + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. Üzerinde işlem yapılacak proje veya çözüm dosyası. Bir dosya belirtilmezse komut geçerli dizinde dosya arar. diff --git a/src/Cli/dotnet/xlf/CliStrings.zh-Hans.xlf b/src/Cli/dotnet/xlf/CliStrings.zh-Hans.xlf index 12a34a5c5fae..380a74f4c834 100644 --- a/src/Cli/dotnet/xlf/CliStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/xlf/CliStrings.zh-Hans.xlf @@ -100,8 +100,9 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - 设置环境变量的值。 -如果该变量不存在,则创建它; 如果它已存在,则替代它。 + 设置环境变量的值。 +如果该变量不存在,则创建它;如果它已存在,则替代它。 +这将在隔离的进程中强制运行测试。 可多次指定此参数来提供多个变量。 示例: @@ -129,12 +130,12 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - 设置环境变量的值。 -如果该变量不存在,则创建它;如果它已存在,则替代它。 -这将在隔离的进程中强制运行测试。 -可多次指定此参数来提供多个变量。 + Sets the value of an environment variable. +Creates the variable if it does not exist, overrides if it does. +This will force the tests to be run in an isolated process. +This argument can be specified multiple times to provide multiple variables. -示例: +Examples: -e VARIABLE=abc -e VARIABLE="value with spaces" -e VARIABLE="value;seperated with;semicolons" @@ -187,16 +188,6 @@ Examples: CONFIGURATION - - Could not find any project in `{0}`. - “{0}”中找不到任何项目。 - - - - Could not find project or directory `{0}`. - 找不到项目或目录“{0}”。 - - Specified solution file {0} does not exist, or there is no solution file in the directory. 指定的解决方案文件 {0} 不存在,或目录中没有解决方案文件。 @@ -683,11 +674,6 @@ setx PATH "%PATH%;{0}" 多个包装填充码可用: {0}。 - - Found more than one project in `{0}`. Specify which one to use. - 在“{0}”中找到多个项目。请指定使用哪一个。 - - Found more than one solution file in {0}. Specify which one to use. 在 {0} 中找到多个解决方案文件。请指定使用哪一个。 @@ -784,7 +770,7 @@ setx PATH "%PATH%;{0}" Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. - 包引用的格式为包标识符,如 ‘{0}’,或由 ‘@’ 分隔的包标识符和版本,如 ‘{0}@{1}’。 + Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. @@ -1033,6 +1019,11 @@ The default is 'false.' However, when targeting .NET 7 or lower, the default is 指定的解决方案文件 {0} 不存在,或目录中没有解决方案文件。 + + Solution folder '{0}' already contains a project with the filename '{1}'. + Solution folder '{0}' already contains a project with the filename '{1}'. + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. 要操作的项目或解决方案文件。如果没有指定文件,则命令将在当前目录里搜索一个文件。 diff --git a/src/Cli/dotnet/xlf/CliStrings.zh-Hant.xlf b/src/Cli/dotnet/xlf/CliStrings.zh-Hant.xlf index 7666715348c8..c7a96f081838 100644 --- a/src/Cli/dotnet/xlf/CliStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/xlf/CliStrings.zh-Hant.xlf @@ -100,7 +100,7 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - 設定環境變數的值。 + 設定環境變數的值。 若變數不存在,則加以建立; 若有,則予以覆寫。 此引數可多次指定,以提供多項變數。 @@ -129,12 +129,12 @@ Examples: -e VARIABLE="value;seperated with;semicolons" -e VAR1=abc -e VAR2=def -e VAR3=ghi - 設定環境變數的值。 -若變數不存在,則加以建立; 若有,則予以覆寫。 -這會強制在隔離流程中執行測試。 -此引數可多次指定,以提供多項變數。 + Sets the value of an environment variable. +Creates the variable if it does not exist, overrides if it does. +This will force the tests to be run in an isolated process. +This argument can be specified multiple times to provide multiple variables. -範例: +Examples: -e VARIABLE=abc -e VARIABLE="value with spaces" -e VARIABLE="value;seperated with;semicolons" @@ -187,16 +187,6 @@ Examples: CONFIGURATION - - Could not find any project in `{0}`. - 在 `{0}` 中找不到任何專案。 - - - - Could not find project or directory `{0}`. - 找不到專案或目錄 `{0}`。 - - Specified solution file {0} does not exist, or there is no solution file in the directory. 指定的方案檔 {0} 不存在,或目錄中沒有方案檔。 @@ -683,11 +673,6 @@ setx PATH "%PATH%;{0}" 有多個經過封裝的填充碼可用: {0}。 - - Found more than one project in `{0}`. Specify which one to use. - 在 `{0}` 中找到多個專案。請指定要使用的專案。 - - Found more than one solution file in {0}. Specify which one to use. 在 {0} 中找到多個解決方案檔。請指定要使用的檔案。 @@ -784,7 +769,7 @@ setx PATH "%PATH%;{0}" Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. - 套件參考的格式為套件識別碼,例如 '{0}',或是以 '@' 分隔的套件識別碼和版本,例如 '{0}@{1}'。 + Package reference in the form of a package identifier like '{0}' or package identifier and version separated by '@' like '{0}@{1}'. @@ -1033,6 +1018,11 @@ The default is 'false.' However, when targeting .NET 7 or lower, the default is 指定的方案檔 {0} 不存在,或目錄中沒有方案檔。 + + Solution folder '{0}' already contains a project with the filename '{1}'. + Solution folder '{0}' already contains a project with the filename '{1}'. + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. 要操作的專案或解決方案。若未指定檔案,命令就會在目前的目錄中搜尋一個檔案。 diff --git a/src/Compatibility/ApiDiff/Microsoft.DotNet.ApiDiff/SyntaxRewriter/GlobalPrefixRemover.cs b/src/Compatibility/ApiDiff/Microsoft.DotNet.ApiDiff/SyntaxRewriter/GlobalPrefixRemover.cs index 74b1fdd5d3b1..0cd75f738168 100644 --- a/src/Compatibility/ApiDiff/Microsoft.DotNet.ApiDiff/SyntaxRewriter/GlobalPrefixRemover.cs +++ b/src/Compatibility/ApiDiff/Microsoft.DotNet.ApiDiff/SyntaxRewriter/GlobalPrefixRemover.cs @@ -11,15 +11,8 @@ internal class GlobalPrefixRemover : CSharpSyntaxRewriter { public static readonly GlobalPrefixRemover Singleton = new(); - private const string GlobalPrefix = "global"; - - public override SyntaxNode? VisitQualifiedName(QualifiedNameSyntax node) - { - if (node.Left is AliasQualifiedNameSyntax alias && - alias.Alias.Identifier.Text == GlobalPrefix) - { - node = SyntaxFactory.QualifiedName(alias.Name, node.Right).WithTriviaFrom(node); - } - return base.VisitQualifiedName(node); - } + public override SyntaxNode? VisitAliasQualifiedName(AliasQualifiedNameSyntax node) + => node.Alias.Identifier.IsKind(SyntaxKind.GlobalKeyword) + ? node.Name.WithTriviaFrom(node) + : base.VisitAliasQualifiedName(node); } diff --git a/src/Layout/VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers/VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers.proj b/src/Layout/VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers/VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers.proj index d0a01f20c9cb..2ef5859a9c0d 100644 --- a/src/Layout/VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers/VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers.proj +++ b/src/Layout/VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers/VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers.proj @@ -11,7 +11,6 @@ - @@ -30,20 +29,15 @@ - - %(RecursiveDir) - - %(RecursiveDir) - @@ -51,28 +45,13 @@ - + - - - <_VsixNamespace> - - - - - - diff --git a/src/Layout/pkg/windows/Directory.Build.props b/src/Layout/pkg/windows/Directory.Build.props index ef56e23437cf..428856d328c1 100644 --- a/src/Layout/pkg/windows/Directory.Build.props +++ b/src/Layout/pkg/windows/Directory.Build.props @@ -1,5 +1,5 @@ - + @@ -25,4 +25,5 @@ $(DefineConstants);InstallerVersion=200 $(DefineConstants);InstallerVersion=500 + diff --git a/src/Layout/pkg/windows/Directory.Build.targets b/src/Layout/pkg/windows/Directory.Build.targets index f3abf2a95721..80acafc74fc4 100644 --- a/src/Layout/pkg/windows/Directory.Build.targets +++ b/src/Layout/pkg/windows/Directory.Build.targets @@ -1,5 +1,5 @@ - + @@ -76,4 +76,5 @@ + diff --git a/src/Layout/pkg/windows/bundles/sdk/bundle.wixproj b/src/Layout/pkg/windows/bundles/sdk/bundle.wixproj index 1523debde918..4bd8000361b6 100644 --- a/src/Layout/pkg/windows/bundles/sdk/bundle.wixproj +++ b/src/Layout/pkg/windows/bundles/sdk/bundle.wixproj @@ -1,4 +1,3 @@ - diff --git a/src/Layout/pkg/windows/bundles/sdk/bundle.wxs b/src/Layout/pkg/windows/bundles/sdk/bundle.wxs index 2503b1dac02e..3e3724cad1ef 100644 --- a/src/Layout/pkg/windows/bundles/sdk/bundle.wxs +++ b/src/Layout/pkg/windows/bundles/sdk/bundle.wxs @@ -141,7 +141,7 @@ + The finalizer is not an installation package. It's detected based on the bundle's installation state (WixBundleInstalled). + + User action | Install | Repair | Modify | Uninstall | Uninstall (Upgrade) + WixBundleInstalled | FALSE | TRUE | TRUE | TRUE | TRUE + WixBundleAction | 6 | 8 | 7 | 4 | 4 + Finalizer (Plan) | None | Execute | Execute | Execute | Execute + + Setting an InstallCondition will cause Burn to remove the package if it evaluates to FALSE and + the bundle is being installed, repaired, or modified. This breaks upgrades. We cannot use + WixBundleAction in the DetectCondition because it's not finalized until the planning phase completes (after + the detect phase). See https://github.com/orgs/wixtoolset/discussions/9017 for more detail. + + The finalizer also takes the bundle action as a parameter to ensure it no-ops, but logs the action. --> + @@ -14,4 +14,5 @@ true + diff --git a/src/Layout/pkg/windows/msis/placeholder/placeholder.wixproj b/src/Layout/pkg/windows/msis/placeholder/placeholder.wixproj index 2554820cc89f..f0d1494fcbc9 100644 --- a/src/Layout/pkg/windows/msis/placeholder/placeholder.wixproj +++ b/src/Layout/pkg/windows/msis/placeholder/placeholder.wixproj @@ -1,5 +1,5 @@ - - + + @@ -19,4 +19,5 @@ $(DefineConstants);DependencyKeyName=NetCore_SdkPlaceholder + diff --git a/src/Layout/pkg/windows/msis/templates/templates.wixproj b/src/Layout/pkg/windows/msis/templates/templates.wixproj index 0a9530517b6a..68559665ab44 100644 --- a/src/Layout/pkg/windows/msis/templates/templates.wixproj +++ b/src/Layout/pkg/windows/msis/templates/templates.wixproj @@ -1,5 +1,5 @@ - + @@ -31,4 +31,5 @@ $(DefineConstants);DependencyKeyName=$(DependencyKeyName) + diff --git a/src/Layout/pkg/windows/msis/toolset/toolset.wixproj b/src/Layout/pkg/windows/msis/toolset/toolset.wixproj index f16de2fb83b5..3131b34c4043 100644 --- a/src/Layout/pkg/windows/msis/toolset/toolset.wixproj +++ b/src/Layout/pkg/windows/msis/toolset/toolset.wixproj @@ -1,5 +1,5 @@ - + @@ -32,4 +32,5 @@ $(DefineConstants);DependencyKeyName=Dotnet_CLI + diff --git a/src/Layout/redist/targets/BundledSdks.targets b/src/Layout/redist/targets/BundledSdks.targets index edcf0a6b474d..5723d50fce8f 100644 --- a/src/Layout/redist/targets/BundledSdks.targets +++ b/src/Layout/redist/targets/BundledSdks.targets @@ -1,8 +1,8 @@ + - diff --git a/src/Layout/redist/targets/GenerateBundledVersions.targets b/src/Layout/redist/targets/GenerateBundledVersions.targets index f7b382246dc8..d7930e4ad125 100644 --- a/src/Layout/redist/targets/GenerateBundledVersions.targets +++ b/src/Layout/redist/targets/GenerateBundledVersions.targets @@ -84,6 +84,14 @@ <_NETStandardLibraryPackageVersion>$(NETStandardLibraryRefPackageVersion) <_NETCorePlatformsPackageVersion>$(MicrosoftNETCorePlatformsPackageVersion) + + <_NET100RuntimePackVersion>10.0.100-preview.7.25380.108 + <_NET100TargetingPackVersion>10.0.100-preview.7.25380.108 + <_WindowsDesktop100RuntimePackVersion>10.0.0-preview.7.25380.108 + <_WindowsDesktop100TargetingPackVersion>10.0.0-preview.7.25380.108 + <_AspNet100RuntimePackVersion>10.0.0-preview.7.25380.108 + <_AspNet100TargetingPackVersion>10.0.0-preview.7.25380.108 + <_NET90RuntimePackVersion>9.0.$(VersionFeature90) <_NET90TargetingPackVersion>9.0.$(VersionFeature90) <_WindowsDesktop90RuntimePackVersion>9.0.$(VersionFeature90) @@ -244,9 +252,21 @@ android-x64; " /> + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + GenerateSdkMsi; @@ -146,7 +146,7 @@ OutputPath=$([System.IO.Path]::GetDirectoryName(%(MSIInstallerFile))); OutputName=$([System.IO.Path]::GetFileNameWithoutExtension(%(MSIInstallerFile))); DependencyKeyName=%(DependencyKeyName);TemplateLayoutDirectoryToHarvest=%(LayoutPath); - BrandName=%(BrandName);UpgradeCode=%(UpgradeCode); + BrandName=%(BrandName);UpgradeCode=%(UpgradeCode); DotnetSrc=%(LayoutPath)"/> diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.CSharp.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/CSharpCollapseMultiplePathOperations.Fixer.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.CSharp.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/CSharpCollapseMultiplePathOperations.Fixer.cs new file mode 100644 index 000000000000..72ea0760b201 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.CSharp.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/CSharpCollapseMultiplePathOperations.Fixer.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Composition; +using Analyzer.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.NetCore.Analyzers; +using Microsoft.NetCore.Analyzers.Performance; + +namespace Microsoft.NetCore.CSharp.Analyzers.Performance +{ + [ExportCodeFixProvider(LanguageNames.CSharp), Shared] + public sealed class CSharpCollapseMultiplePathOperationsFixer : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(CollapseMultiplePathOperationsAnalyzer.RuleId); + + public override FixAllProvider GetFixAllProvider() + => WellKnownFixAllProviders.BatchFixer; + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var document = context.Document; + var diagnostic = context.Diagnostics[0]; + var root = await document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var node = root.FindNode(context.Span, getInnermostNodeForTie: true); + + if (node is not InvocationExpressionSyntax invocation || + await document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false) is not { } semanticModel || + semanticModel.Compilation.GetTypeByMetadataName(WellKnownTypeNames.SystemIOPath) is not { } pathType) + { + return; + } + + // Get the method name from diagnostic properties + if (!diagnostic.Properties.TryGetValue(CollapseMultiplePathOperationsAnalyzer.MethodNameKey, out var methodName)) + { + methodName = "Path"; + } + + context.RegisterCodeFix( + CodeAction.Create( + string.Format(MicrosoftNetCoreAnalyzersResources.CollapseMultiplePathOperationsCodeFixTitle, methodName), + createChangedDocument: cancellationToken => CollapsePathOperationAsync(document, root, invocation, pathType, semanticModel, cancellationToken), + equivalenceKey: nameof(MicrosoftNetCoreAnalyzersResources.CollapseMultiplePathOperationsCodeFixTitle)), + diagnostic); + } + + private static Task CollapsePathOperationAsync(Document document, SyntaxNode root, InvocationExpressionSyntax invocation, INamedTypeSymbol pathType, SemanticModel semanticModel, CancellationToken cancellationToken) + { + // Collect all arguments by recursively unwrapping nested Path.Combine/Join calls + var allArguments = CollectAllArguments(invocation, pathType, semanticModel); + + // Create new argument list with all collected arguments + var newArgumentList = SyntaxFactory.ArgumentList( + SyntaxFactory.SeparatedList(allArguments)); + + // Create the new invocation with all arguments + var newInvocation = invocation.WithArgumentList(newArgumentList) + .WithTriviaFrom(invocation); + + var newRoot = root.ReplaceNode(invocation, newInvocation); + + return Task.FromResult(document.WithSyntaxRoot(newRoot)); + } + + private static ArgumentSyntax[] CollectAllArguments(InvocationExpressionSyntax invocation, INamedTypeSymbol pathType, SemanticModel semanticModel) + { + var arguments = ImmutableArray.CreateBuilder(); + + foreach (var argument in invocation.ArgumentList.Arguments) + { + if (argument.Expression is InvocationExpressionSyntax nestedInvocation && + IsPathCombineOrJoin(nestedInvocation, pathType, semanticModel, out var methodName) && + IsPathCombineOrJoin(invocation, pathType, semanticModel, out var outerMethodName) && + methodName == outerMethodName) + { + // Recursively collect arguments from nested invocation + arguments.AddRange(CollectAllArguments(nestedInvocation, pathType, semanticModel)); + } + else + { + arguments.Add(argument); + } + } + + return arguments.ToArray(); + } + + private static bool IsPathCombineOrJoin(InvocationExpressionSyntax invocation, INamedTypeSymbol pathType, SemanticModel semanticModel, out string methodName) + { + if (semanticModel.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && + SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, pathType) && + methodSymbol.Name is "Combine" or "Join") + { + methodName = methodSymbol.Name; + return true; + } + + methodName = string.Empty; + return false; + } + } +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.md index facf822421c6..4679d5c1c12c 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -948,6 +948,18 @@ This rule detects usage of platform-specific intrinsics that can be replaced wit |CodeFix|True| --- +## [CA1517](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1517): Use 'ReadOnlySpan\' or 'ReadOnlyMemory\' instead of 'Span\' or 'Memory\' + +Using 'ReadOnlySpan\' or 'ReadOnlyMemory\' instead of 'Span\' or 'Memory\' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + +|Item|Value| +|-|-| +|Category|Maintainability| +|Enabled|True| +|Severity|Info| +|CodeFix|True| +--- + ## [CA1700](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1700): Do not name enum values 'Reserved' This rule assumes that an enumeration member that has a name that contains "reserved" is not currently used but is a placeholder to be renamed or removed in a future version. Renaming or removing a member is a breaking change. @@ -1358,7 +1370,7 @@ Enumerable.Count() potentially enumerates the sequence while a Length/Count prop ## [CA1830](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1830): Prefer strongly-typed Append and Insert method overloads on StringBuilder -StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. +StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)). |Item|Value| |-|-| @@ -1908,6 +1920,30 @@ In many situations, logging is disabled or set to a log level that results in an |CodeFix|True| --- +## [CA1876](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1876): Do not use 'AsParallel' in 'foreach' + +Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Info| +|CodeFix|False| +--- + +## [CA1877](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1877): Collapse consecutive Path.Combine or Path.Join operations + +When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Info| +|CodeFix|True| +--- + ## [CA2000](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope If a disposable object is not explicitly disposed before all references to it are out of scope, the object will be disposed at some indeterminate time when the garbage collector runs the finalizer of the object. Because an exceptional event might occur that will prevent the finalizer of the object from running, the object should be explicitly disposed instead. @@ -2154,6 +2190,30 @@ Unawaited tasks that use 'IDisposable' instances may use those instances long af |CodeFix|False| --- +## [CA2026](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2026): Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + +JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + +|Item|Value| +|-|-| +|Category|Reliability| +|Enabled|True| +|Severity|Info| +|CodeFix|True| +--- + +## [CA2027](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2027): Cancel Task.Delay after Task.WhenAny completes + +When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + +|Item|Value| +|-|-| +|Category|Reliability| +|Enabled|True| +|Severity|Info| +|CodeFix|False| +--- + ## [CA2100](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2100): Review SQL queries for security vulnerabilities SQL queries that directly use user input can be vulnerable to SQL injection attacks. Review this SQL query for potential vulnerabilities, and consider using a parameterized SQL query. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.sarif index a3e0dd7859aa..9be6d8f8f7d0 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -5,7 +5,7 @@ { "tool": { "name": "Microsoft.CodeAnalysis.CSharp.NetAnalyzers", - "version": "10.0.100", + "version": "10.0.200", "language": "en-US" }, "rules": { @@ -708,7 +708,7 @@ { "tool": { "name": "Microsoft.CodeAnalysis.NetAnalyzers", - "version": "10.0.100", + "version": "10.0.200", "language": "en-US" }, "rules": { @@ -2182,6 +2182,26 @@ ] } }, + "CA1517": { + "id": "CA1517", + "shortDescription": "Use 'ReadOnlySpan' or 'ReadOnlyMemory' instead of 'Span' or 'Memory'", + "fullDescription": "Using 'ReadOnlySpan' or 'ReadOnlyMemory' instead of 'Span' or 'Memory' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1517", + "properties": { + "category": "Maintainability", + "isEnabledByDefault": true, + "typeName": "PreferReadOnlySpanOverSpanAnalyzer", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA1700": { "id": "CA1700", "shortDescription": "Do not name enum values 'Reserved'", @@ -2789,7 +2809,7 @@ "CA1830": { "id": "CA1830", "shortDescription": "Prefer strongly-typed Append and Insert method overloads on StringBuilder", - "fullDescription": "StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload.", + "fullDescription": "StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)).", "defaultLevel": "note", "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1830", "properties": { @@ -3507,6 +3527,46 @@ ] } }, + "CA1876": { + "id": "CA1876", + "shortDescription": "Do not use 'AsParallel' in 'foreach'", + "fullDescription": "Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1876", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "DoNotUseAsParallelInForEachLoopAnalyzer", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, + "CA1877": { + "id": "CA1877", + "shortDescription": "Collapse consecutive Path.Combine or Path.Join operations", + "fullDescription": "When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1877", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "CollapseMultiplePathOperationsAnalyzer", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2000": { "id": "CA2000", "shortDescription": "Dispose objects before losing scope", @@ -3850,6 +3910,46 @@ ] } }, + "CA2026": { + "id": "CA2026", + "shortDescription": "Prefer JsonElement.Parse over JsonDocument.Parse().RootElement", + "fullDescription": "JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2026", + "properties": { + "category": "Reliability", + "isEnabledByDefault": true, + "typeName": "PreferJsonElementParse", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, + "CA2027": { + "id": "CA2027", + "shortDescription": "Cancel Task.Delay after Task.WhenAny completes", + "fullDescription": "When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2027", + "properties": { + "category": "Reliability", + "isEnabledByDefault": true, + "typeName": "DoNotUseNonCancelableTaskDelayWithWhenAny", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2100": { "id": "CA2100", "shortDescription": "Review SQL queries for security vulnerabilities", @@ -6503,7 +6603,7 @@ { "tool": { "name": "Microsoft.CodeAnalysis.VisualBasic.NetAnalyzers", - "version": "10.0.100", + "version": "10.0.200", "language": "en-US" }, "rules": { diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/AnalyzerReleases.Unshipped.md b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/AnalyzerReleases.Unshipped.md index 0ecf6597797e..caed74ac159d 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/AnalyzerReleases.Unshipped.md +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/AnalyzerReleases.Unshipped.md @@ -4,9 +4,14 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------- +CA1517 | Maintainability | Info | PreferReadOnlySpanOverSpanAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/CA1516) CA1873 | Performance | Info | AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873) CA1874 | Performance | Info | UseRegexMembers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1874) CA1875 | Performance | Info | UseRegexMembers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1875) +CA1876 | Performance | Info | DoNotUseAsParallelInForEachLoopAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1876) +CA1877 | Performance | Info | CollapseMultiplePathOperationsAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/cA1877) CA2023 | Reliability | Warning | LoggerMessageDefineAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2023) CA2024 | Reliability | Warning | DoNotUseEndOfStreamInAsyncMethods, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2024) CA2025 | Reliability | Disabled | DoNotPassDisposablesIntoUnawaitedTasksAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2025) +CA2026 | Reliability | Info | PreferJsonElementParse, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2026) +CA2027 | Reliability | Info | DoNotUseNonCancelableTaskDelayWithWhenAny, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2027) diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/MicrosoftCodeQualityAnalyzersResources.resx b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/MicrosoftCodeQualityAnalyzersResources.resx index 77c8ad39994e..ced35802d544 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/MicrosoftCodeQualityAnalyzersResources.resx +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/MicrosoftCodeQualityAnalyzersResources.resx @@ -1329,40 +1329,40 @@ This rule detects usage of platform-specific intrinsics that can be replaced with an equivalent cross-platform intrinsic instead. - + The addition operator should be preferred - + The bitwise-and operator should be preferred - + The bitwise-or operator should be preferred - + The division operator should be preferred - + The exclusive-or operator should be preferred - + The left-shift operator should be preferred - + The multiply operator should be preferred - + The ones-complement operator should be preferred - + The right-shift operator should be preferred - + The subtraction operator should be preferred - + The unary-negation operator should be preferred - + The unsigned right-shift operator should be preferred \ No newline at end of file diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.cs.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.cs.xlf index bf1a086a41e3..993d27a7e27a 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.cs.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.cs.xlf @@ -447,62 +447,62 @@ Toto pravidlo detekuje použití vnitřních hodnot specifických pro platformu, které lze místo toho nahradit ekvivalentními vnitřními hodnotami pro různé platformy. - + The addition operator should be preferred Upřednostňuje se operátor sčítání. - + The bitwise-and operator should be preferred Měl by být upřednostňován bitový operátor. - + The bitwise-or operator should be preferred Měl by být upřednostňován operátor bitwise-or. - + The division operator should be preferred Upřednostňuje se operátor dělení. - + The exclusive-or operator should be preferred Měl by být upřednostňován operátor exclusive-or. - + The left-shift operator should be preferred Měl by být upřednostňován operátor levého posunu. - + The multiply operator should be preferred Měl by být upřednostňován operátor násobení. - + The ones-complement operator should be preferred Upřednostňuje se operátor doplňku ones. - + The right-shift operator should be preferred Měl by být upřednostňován operátor pravého posunu. - + The subtraction operator should be preferred Upřednostňuje se operátor odčítání. - + The unary-negation operator should be preferred Upřednostňuje se operátor unární negace. - + The unsigned right-shift operator should be preferred Měl by být upřednostňován nepodepsaný operátor pravého posunu. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.de.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.de.xlf index 323e7b5c589a..2d72ce86724e 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.de.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.de.xlf @@ -447,62 +447,62 @@ Diese Regel erkennt die Verwendung plattformspezifischer systeminterner Funktionen, die durch ein entsprechendes plattformübergreifendes Äquivalent ersetzt werden können. - + The addition operator should be preferred Der Additionsoperator sollte bevorzugt verwendet werden. - + The bitwise-and operator should be preferred Der bitweise UND-Operator sollte bevorzugt verwendet werden. - + The bitwise-or operator should be preferred Der bitweise ODER-Operator sollte bevorzugt verwendet werden. - + The division operator should be preferred Der Divisionsoperator sollte bevorzugt verwendet werden. - + The exclusive-or operator should be preferred Der exklusive ODER-Operator sollte bevorzugt verwendet werden. - + The left-shift operator should be preferred Der Links-Shift-Operator sollte bevorzugt verwendet werden. - + The multiply operator should be preferred Der Multiplikationsoperator sollte bevorzugt verwendet werden. - + The ones-complement operator should be preferred Der Einerkomplement-Operator sollte bevorzugt verwendet werden. - + The right-shift operator should be preferred Der Rechts-Shift-Operator sollte bevorzugt verwendet werden. - + The subtraction operator should be preferred Der Subtraktionsoperator sollte bevorzugt verwendet werden. - + The unary-negation operator should be preferred Der Operator für die unäre Negation sollte bevorzugt verwendet werden. - + The unsigned right-shift operator should be preferred Der unsignierte Rechts-Shift-Operator sollte bevorzugt verwendet werden. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.es.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.es.xlf index 4d3eda5f7624..d0c894d545bd 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.es.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.es.xlf @@ -447,62 +447,62 @@ Esta regla detecta el uso de intrínsecos específicos de la plataforma que se pueden reemplazar por un intrínseco multiplataforma equivalente en su lugar. - + The addition operator should be preferred Se debe preferir el operador de suma - + The bitwise-and operator should be preferred Se debe preferir el operador y bit a bit - + The bitwise-or operator should be preferred Se debe preferir el operador bit a bit OR. - + The division operator should be preferred Se debe preferir el operador de división - + The exclusive-or operator should be preferred Se debe preferir el operador exclusivo o - + The left-shift operator should be preferred Se debe preferir el operador de desplazamiento a la izquierda - + The multiply operator should be preferred Se debe preferir el operador de multiplicación - + The ones-complement operator should be preferred Se debe preferir el operador de complemento a uno - + The right-shift operator should be preferred Se debe preferir el operador de desplazamiento a la derecha - + The subtraction operator should be preferred Se debe preferir el operador de resta - + The unary-negation operator should be preferred Se debe preferir el operador unario-negación - + The unsigned right-shift operator should be preferred Se debe preferir el operador de desplazamiento a la derecha sin signo diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.fr.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.fr.xlf index 913ebe7bd680..c0d87275f1f8 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.fr.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.fr.xlf @@ -447,62 +447,62 @@ Cette règle détecte l’utilisation d’intrinsèques spécifiques à la plateforme qui peuvent être remplacés par un équivalent multiplateforme. - + The addition operator should be preferred L’opérateur d’addition doit être préféré - + The bitwise-and operator should be preferred L’opérateur ET binaire est à privilégier - + The bitwise-or operator should be preferred L’opérateur OU binaire (|) est à privilégier - + The division operator should be preferred L’opérateur de division doit être préféré - + The exclusive-or operator should be preferred L’opérateur OU exclusif (^) est à privilégier - + The left-shift operator should be preferred L’opérateur de décalage à gauche doit être préféré - + The multiply operator should be preferred L’opérateur de multiplication doit être préféré - + The ones-complement operator should be preferred L’opérateur complément à un (~) est à privilégier - + The right-shift operator should be preferred L’opérateur de décalage vers la droite doit être préféré - + The subtraction operator should be preferred L’opérateur de soustraction doit être préféré - + The unary-negation operator should be preferred L’opérateur de négation unaire doit être préféré - + The unsigned right-shift operator should be preferred L’opérateur de décalage vers la droite non signé doit être préféré diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.it.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.it.xlf index 4f3aab2a46e2..0d3f77a35113 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.it.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.it.xlf @@ -447,62 +447,62 @@ Questa regola rileva l'utilizzo di intrinseci specifici della piattaforma che possono essere sostituiti con un intrinseco equivalente multipiattaforma. - + The addition operator should be preferred È consigliabile preferire l'operatore di addizione - + The bitwise-and operator should be preferred È consigliabile usare l'operatore AND bit per bit - + The bitwise-or operator should be preferred È consigliabile usare l'operatore OR bit per bit - + The division operator should be preferred È consigliabile preferire l'operatore di divisione - + The exclusive-or operator should be preferred È consigliabile usare l'operatore OR esclusivo - + The left-shift operator should be preferred È consigliabile utilizzare l'operatore di spostamento a sinistra - + The multiply operator should be preferred È consigliabile utilizzare l'operatore di moltiplicazione - + The ones-complement operator should be preferred È consigliabile preferire l'operatore di complemento a uno - + The right-shift operator should be preferred È consigliabile preferire l'operatore di spostamento a destra - + The subtraction operator should be preferred È consigliabile utilizzare l'operatore di sottrazione - + The unary-negation operator should be preferred È consigliabile utilizzare l'operatore di negazione unario - + The unsigned right-shift operator should be preferred È consigliabile preferire l'operatore di spostamento a destra senza segno diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ja.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ja.xlf index 5646c9537ad4..2598ba3ea59a 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ja.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ja.xlf @@ -447,62 +447,62 @@ このルールは、代わりに同等のクロスプラットフォーム組み込み関数に置き換えることができるプラットフォーム固有の組み込み関数の使用を検出します。 - + The addition operator should be preferred 加算演算子を優先する必要があります - + The bitwise-and operator should be preferred ビット論理積演算子を優先する必要があります - + The bitwise-or operator should be preferred ビット論理和演算子を優先する必要があります - + The division operator should be preferred 除算演算子を優先する必要があります - + The exclusive-or operator should be preferred 排他的論理和演算子を優先する必要があります - + The left-shift operator should be preferred 左シフト演算子を優先する必要があります - + The multiply operator should be preferred 乗算演算子を優先する必要があります - + The ones-complement operator should be preferred 1 の補数演算子を優先する必要があります - + The right-shift operator should be preferred 右シフト演算子を優先する必要があります - + The subtraction operator should be preferred 減算演算子を優先する必要があります - + The unary-negation operator should be preferred 単項否定演算子を優先する必要があります - + The unsigned right-shift operator should be preferred 符号なし右シフト演算子を優先する必要があります diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ko.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ko.xlf index 768c9eb887bb..3e9bb3c7c02b 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ko.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ko.xlf @@ -447,62 +447,62 @@ 이 규칙은 플랫폼별 내장 기능의 사용을 감지하며, 이를 동일한 플랫폼 간 내장으로 대체할 수 있습니다. - + The addition operator should be preferred 더하기 연산자를 선호해야 합니다. - + The bitwise-and operator should be preferred 비트 AND 연산자를 선호해야 합니다. - + The bitwise-or operator should be preferred 비트 또는 연산자를 선호해야 합니다. - + The division operator should be preferred 빼기 연산자를 선호해야 합니다. - + The exclusive-or operator should be preferred 배타적 OR 연산자를 선호해야 합니다. - + The left-shift operator should be preferred 왼쪽 시프트 연산자를 선호해야 합니다. - + The multiply operator should be preferred 곱하기 연산자를 선호해야 합니다. - + The ones-complement operator should be preferred 1의 보수 연산자를 선호해야 합니다. - + The right-shift operator should be preferred 오른쪽 시프트 연산자를 선호해야 합니다. - + The subtraction operator should be preferred 빼기 연산자를 선호해야 합니다. - + The unary-negation operator should be preferred 단항 부정 연산자를 선호해야 합니다. - + The unsigned right-shift operator should be preferred 부호 없는 오른쪽 시프트 연산자를 선호해야 합니다. diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.pl.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.pl.xlf index edd4f40771cf..946f8a34a065 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.pl.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.pl.xlf @@ -447,62 +447,62 @@ Ta reguła wykrywa użycie funkcji wewnętrznych specyficznych dla platformy, które można zastąpić równoważną wewnętrzną funkcją międzyplatformową. - + The addition operator should be preferred Preferowany powinien być operator addition - + The bitwise-and operator should be preferred Preferowany powinien być operator bitwise-and - + The bitwise-or operator should be preferred Preferowany powinien być operator bitwise-or - + The division operator should be preferred Preferowany powinien być operator division - + The exclusive-or operator should be preferred Preferowany powinien być operator exclusive-or - + The left-shift operator should be preferred Preferowany powinien być operator left-shift - + The multiply operator should be preferred Preferowany powinien być operator multiply - + The ones-complement operator should be preferred Preferowany powinien być operator ones-complement - + The right-shift operator should be preferred Preferowany powinien być operator right-shift - + The subtraction operator should be preferred Preferowany powinien być operator subtraction - + The unary-negation operator should be preferred Preferowany powinien być operator unary-negation - + The unsigned right-shift operator should be preferred Preferowany powinien być operator bez podpisu right-shift diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.pt-BR.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.pt-BR.xlf index 834cbc58d45c..efc329512fb9 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.pt-BR.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.pt-BR.xlf @@ -447,62 +447,62 @@ Esta regra detecta o uso de intrínsecos específicos da plataforma que podem ser substituídos por um intrínseco multiplataforma equivalente. - + The addition operator should be preferred Deve-se dar preferência ao operador de adição - + The bitwise-and operator should be preferred Deve-se dar preferência ao operador E bit a bit - + The bitwise-or operator should be preferred Deve-se dar preferência ao operador OU bit a bit - + The division operator should be preferred Deve-se dar preferência ao operador de divisão - + The exclusive-or operator should be preferred Deve-se dar preferência ao operador OU exclusivo - + The left-shift operator should be preferred Deve-se dar preferência ao operador de deslocamento à esquerda - + The multiply operator should be preferred Deve-se dar preferência ao operador de multiplicação - + The ones-complement operator should be preferred Deve-se dar preferência ao operador de complemento de um - + The right-shift operator should be preferred Deve-se dar preferência ao operador de deslocamento à direita - + The subtraction operator should be preferred Deve-se dar preferência ao operador de subtração - + The unary-negation operator should be preferred Deve-se dar preferência ao operador de negação unária - + The unsigned right-shift operator should be preferred Deve-se dar preferência ao operador de deslocamento à direita sem sinal diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ru.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ru.xlf index e80b15ed3896..92e30c889f10 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ru.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ru.xlf @@ -447,62 +447,62 @@ Это правило обнаруживает использование встроенных функций, специфичных для платформы, которые можно заменить эквивалентной кроссплатформенной встроенной функцией. - + The addition operator should be preferred Следует предпочитать оператор сложения - + The bitwise-and operator should be preferred Следует предпочитать побитовый оператор И - + The bitwise-or operator should be preferred Следует предпочитать побитовый оператор ИЛИ - + The division operator should be preferred Следует предпочитать оператор деления - + The exclusive-or operator should be preferred Следует предпочитать оператор исключающего ИЛИ - + The left-shift operator should be preferred Следует предпочитать оператор левого сдвига - + The multiply operator should be preferred Следует предпочитать оператор умножения - + The ones-complement operator should be preferred Следует предпочитать оператор дополнения до единицы - + The right-shift operator should be preferred Следует предпочитать оператор правого сдвига - + The subtraction operator should be preferred Следует предпочитать оператор вычитания - + The unary-negation operator should be preferred Следует предпочитать оператор унарного отрицания - + The unsigned right-shift operator should be preferred Следует предпочитать оператор беззнакового правого сдвига diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.tr.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.tr.xlf index 1d7a316e5164..ecbf6c8b0b96 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.tr.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.tr.xlf @@ -447,62 +447,62 @@ Bu kural, eşdeğer platformlar arası iç öğe ile değiştirilebilecek, platforma özgü iç öğe kullanımını algılar. - + The addition operator should be preferred Toplama işleci tercih edilmelidir - + The bitwise-and operator should be preferred Bit düzeyinde VE işleci tercih edilmelidir - + The bitwise-or operator should be preferred Bit düzeyinde VEYA işleci tercih edilmelidir - + The division operator should be preferred Bölme işleci tercih edilmelidir - + The exclusive-or operator should be preferred Münhasır VEYA işleci tercih edilmelidir - + The left-shift operator should be preferred Sol kaydırma işleci tercih edilmelidir - + The multiply operator should be preferred Çarpma işleci tercih edilmelidir - + The ones-complement operator should be preferred Bire tümleme işleci tercih edilmelidir - + The right-shift operator should be preferred Sağ kaydırma işleci tercih edilmelidir - + The subtraction operator should be preferred Çıkarma işleci tercih edilmelidir - + The unary-negation operator should be preferred Birli olumsuzlama işleci tercih edilmelidir - + The unsigned right-shift operator should be preferred İşaretsiz sağ kaydırma işleci tercih edilmelidir diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hans.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hans.xlf index 5fe63f83419d..e37314f611f9 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hans.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hans.xlf @@ -447,62 +447,62 @@ 此规则检测特定于平台的内部函数的使用情况,这些内部函数可以替换为等效的跨平台内部函数。 - + The addition operator should be preferred 应优先使用加法运算符 - + The bitwise-and operator should be preferred 应优先使用按位和运算符 - + The bitwise-or operator should be preferred 应优先使用按位或运算符 - + The division operator should be preferred 应优先使用除法运算符 - + The exclusive-or operator should be preferred 应优先使用异或运算符 - + The left-shift operator should be preferred 应优先使用左移运算符 - + The multiply operator should be preferred 应优先使用乘法运算符 - + The ones-complement operator should be preferred 应优先使用一的补码运算符 - + The right-shift operator should be preferred 应优先使用右移运算符 - + The subtraction operator should be preferred 应优先使用减法运算符 - + The unary-negation operator should be preferred 应优先使用一元否定运算符 - + The unsigned right-shift operator should be preferred 应优先使用无符号右移运算符 diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hant.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hant.xlf index 6cc21ffd8a4b..2133b933c138 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hant.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hant.xlf @@ -447,62 +447,62 @@ 此規則會偵測平台特定的內建使用情況,這些內建可以被對應的跨平台內建所取代。 - + The addition operator should be preferred 應優先使用加法運算子 - + The bitwise-and operator should be preferred 應優先使用位元 AND 運算子 - + The bitwise-or operator should be preferred 應優先使用位元 OR 運算子 - + The division operator should be preferred 應優先使用除法運算子 - + The exclusive-or operator should be preferred 應優先使用排除 OR 運算子 - + The left-shift operator should be preferred 應優先使用左移運算子 - + The multiply operator should be preferred 應優先使用乘法運算子 - + The ones-complement operator should be preferred 應優先使用一補位運算子 - + The right-shift operator should be preferred 應優先使用右移運算子 - + The subtraction operator should be preferred 應優先使用減法運算子 - + The unary-negation operator should be preferred 應優先使用一元否定運算子 - + The unsigned right-shift operator should be preferred 應優先使用未簽署右移運算子 diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 55948fcbc88d..7e9e3af97bf2 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -195,6 +195,15 @@ Do not use Count() or LongCount() when Any() can be used + + Do not use 'AsParallel' in 'foreach' + + + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + + + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' @@ -1287,14 +1296,17 @@ Prefer strongly-typed Append and Insert method overloads on StringBuilder - StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. + StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)). - Remove the ToString call in order to use a strongly-typed StringBuilder overload + Prefer strongly-typed StringBuilder overload Remove the ToString call + + Use StringBuilder.Append(char, int) overload + Calls to 'string.IndexOf' where the result is used to check for the presence/absence of a substring can be replaced by 'string.Contains'. @@ -1634,6 +1646,15 @@ Replace 'WhenAll' call with argument + + Cancel Task.Delay after Task.WhenAny completes + + + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + + + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + Use 'string.Equals' @@ -2054,6 +2075,18 @@ Widening and user defined conversions are not supported with generic types. Use '{0}.{1}' + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + Use 'JsonElement.Parse' + Using concrete types avoids virtual or interface call overhead and enables inlining. @@ -2216,4 +2249,28 @@ Widening and user defined conversions are not supported with generic types. Replace obsolete call + + Collapse consecutive Path.Combine or Path.Join operations + + + Multiple consecutive Path.{0} operations can be collapsed into a single operation + + + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + + + Collapse into single Path.{0} operation + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + Change to '{0}' + diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/AvoidSingleUseOfLocalJsonSerializerOptions.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/AvoidSingleUseOfLocalJsonSerializerOptions.cs index 1744f9226673..2bb19c228248 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/AvoidSingleUseOfLocalJsonSerializerOptions.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/AvoidSingleUseOfLocalJsonSerializerOptions.cs @@ -1,15 +1,15 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -using System.Diagnostics.CodeAnalysis; -using System; -using System.Collections.Generic; -using System.Diagnostics; namespace Microsoft.NetCore.Analyzers.Performance { @@ -60,6 +60,12 @@ private static void OnCompilationStart(CompilationStartAnalysisContext context) INamedTypeSymbol? typeSymbol = operation.Constructor?.ContainingType; if (SymbolEqualityComparer.Default.Equals(typeSymbol, jsonSerializerOptionsSymbol)) { + // Don't report diagnostic for top-level statements as caching there is less impactful + if (context.ContainingSymbol is IMethodSymbol method && method.IsTopLevelStatementsEntryPointMethod()) + { + return; + } + if (IsCtorUsedAsArgumentForJsonSerializer(operation, jsonSerializerSymbol) || IsLocalUsedAsArgumentForJsonSerializerOnly(operation, jsonSerializerSymbol, jsonSerializerOptionsSymbol)) { diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/CollapseMultiplePathOperations.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/CollapseMultiplePathOperations.cs new file mode 100644 index 000000000000..8a122ca18107 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/CollapseMultiplePathOperations.cs @@ -0,0 +1,307 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + using static MicrosoftNetCoreAnalyzersResources; + + /// + /// CA1877: + /// Detects nested Path.Combine or Path.Join calls that can be collapsed into a single call. + /// Example: Path.Combine(Path.Combine(a, b), c) -> Path.Combine(a, b, c) + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class CollapseMultiplePathOperationsAnalyzer : DiagnosticAnalyzer + { + internal const string RuleId = "CA1877"; + internal const string MethodNameKey = "MethodName"; + internal const string ArgumentCountKey = "ArgumentCount"; + + internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( + id: RuleId, + title: CreateLocalizableResourceString(nameof(CollapseMultiplePathOperationsTitle)), + messageFormat: CreateLocalizableResourceString(nameof(CollapseMultiplePathOperationsMessage)), + category: DiagnosticCategory.Performance, + ruleLevel: RuleLevel.IdeSuggestion, + description: CreateLocalizableResourceString(nameof(CollapseMultiplePathOperationsDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + context.RegisterCompilationStartAction(compilationContext => + { + var compilation = compilationContext.Compilation; + + // Get the Path type + if (!compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIOPath, out var pathType)) + { + return; + } + + // Get Span types (may be null if not available in the target framework) + compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemSpan1, out var spanType); + compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1, out var readOnlySpanType); + + // Get Combine and Join methods + var combineMethods = ImmutableArray.CreateBuilder(); + var joinMethods = ImmutableArray.CreateBuilder(); + + foreach (var member in pathType.GetMembers()) + { + if (member is IMethodSymbol method && method.IsStatic) + { + if (method.Name == "Combine" && IsStringReturningMethod(method)) + { + combineMethods.Add(method); + } + else if (method.Name == "Join" && IsStringReturningMethod(method)) + { + joinMethods.Add(method); + } + } + } + + if (combineMethods.Count == 0 && joinMethods.Count == 0) + { + return; + } + + var combineMethodsArray = combineMethods.ToImmutable(); + var joinMethodsArray = joinMethods.ToImmutable(); + + compilationContext.RegisterOperationAction(operationContext => + { + var invocation = (IInvocationOperation)operationContext.Operation; + AnalyzeInvocation(operationContext, invocation, pathType, spanType, readOnlySpanType, combineMethodsArray, joinMethodsArray); + }, OperationKind.Invocation); + }); + } + + private static bool IsStringReturningMethod(IMethodSymbol method) + { + return method.ReturnType.SpecialType == SpecialType.System_String; + } + + private static void AnalyzeInvocation( + OperationAnalysisContext context, + IInvocationOperation invocation, + INamedTypeSymbol pathType, + INamedTypeSymbol? spanType, + INamedTypeSymbol? readOnlySpanType, + ImmutableArray combineMethods, + ImmutableArray joinMethods) + { + var targetMethod = invocation.TargetMethod; + + // Early check: must have arguments, be a static method, and be on System.IO.Path + if (invocation.Arguments.IsEmpty || + !targetMethod.IsStatic || + !SymbolEqualityComparer.Default.Equals(targetMethod.ContainingType, pathType)) + { + return; + } + + string? methodName = null; + ImmutableArray methodsToCheck = default; + + // Check if this is a Combine or Join call + foreach (var method in combineMethods) + { + if (SymbolEqualityComparer.Default.Equals(targetMethod.OriginalDefinition, method.OriginalDefinition)) + { + methodName = "Combine"; + methodsToCheck = combineMethods; + break; + } + } + + if (methodName == null) + { + foreach (var method in joinMethods) + { + if (SymbolEqualityComparer.Default.Equals(targetMethod.OriginalDefinition, method.OriginalDefinition)) + { + methodName = "Join"; + methodsToCheck = joinMethods; + break; + } + } + } + + if (methodName == null) + { + return; + } + + // Check if this invocation is itself an argument to another Path.Combine/Join call + // If so, skip it - we'll report on the outermost call only + if (IsNestedInSimilarCall(invocation, methodsToCheck)) + { + return; + } + + // Check if any argument is itself a Path.Combine/Join call of the same method + foreach (var argument in invocation.Arguments) + { + if (argument.Value is IInvocationOperation nestedInvocation) + { + var nestedMethod = nestedInvocation.TargetMethod; + + foreach (var method in methodsToCheck) + { + if (SymbolEqualityComparer.Default.Equals(nestedMethod.OriginalDefinition, method.OriginalDefinition)) + { + // Found a nested call that can potentially be collapsed + // Count total arguments to ensure we don't exceed available overloads + int totalArgs = CountTotalArguments(invocation, methodsToCheck); + + // For Combine and Join with string parameters, we can use params overload for any count + // Check if target framework has the overloads we need + if (CanCollapse(invocation, spanType, readOnlySpanType, totalArgs)) + { + var properties = ImmutableDictionary.CreateBuilder(); + properties.Add(MethodNameKey, methodName); + properties.Add(ArgumentCountKey, totalArgs.ToString()); + + context.ReportDiagnostic(invocation.CreateDiagnostic(Rule, properties.ToImmutable(), methodName)); + return; + } + } + } + } + } + } + + private static bool IsNestedInSimilarCall(IInvocationOperation invocation, ImmutableArray methodsToCheck) + { + // Walk up the tree to see if this invocation is an argument to another Path.Combine/Join call + var current = invocation.Parent; + while (current != null) + { + // Check if we're inside an argument + if (current is IArgumentOperation) + { + // Get the invocation that contains this argument + var grandParent = current.Parent; + if (grandParent is IInvocationOperation parentInvocation) + { + var parentMethod = parentInvocation.TargetMethod; + foreach (var method in methodsToCheck) + { + if (SymbolEqualityComparer.Default.Equals(parentMethod.OriginalDefinition, method.OriginalDefinition)) + { + return true; + } + } + } + } + current = current.Parent; + } + return false; + } + + private static int CountTotalArguments(IInvocationOperation invocation, ImmutableArray methodsToCheck) + { + int count = 0; + + foreach (var argument in invocation.Arguments) + { + if (argument.Value is IInvocationOperation nestedInvocation) + { + var nestedMethod = nestedInvocation.TargetMethod; + bool isNestedPathMethod = false; + + foreach (var method in methodsToCheck) + { + if (SymbolEqualityComparer.Default.Equals(nestedMethod.OriginalDefinition, method.OriginalDefinition)) + { + isNestedPathMethod = true; + break; + } + } + + if (isNestedPathMethod) + { + // Recursively count arguments from nested call + count += CountTotalArguments(nestedInvocation, methodsToCheck); + } + else + { + count++; + } + } + else + { + count++; + } + } + + return count; + } + + private static bool CanCollapse(IInvocationOperation invocation, INamedTypeSymbol? spanType, INamedTypeSymbol? readOnlySpanType, int totalArgs) + { + // We can collapse if there's a params overload available + // Path.Combine(params string[]) and Path.Join(params string[]) exist in supported frameworks + // The only constraint is that we need at least 2 arguments total + + // However, if any of the parameters are spans, we can't use the params overload + // Check if any argument involves span types + if (HasSpanArguments(invocation, spanType, readOnlySpanType)) + { + // With span arguments, we're limited to the non-params overloads + // which support up to 4 arguments for Join(ReadOnlySpan) + return totalArgs >= 2 && totalArgs <= 4; + } + + return totalArgs >= 2; + } + + private static bool HasSpanArguments(IInvocationOperation invocation, INamedTypeSymbol? spanType, INamedTypeSymbol? readOnlySpanType) + { + foreach (var argument in invocation.Arguments) + { + if (IsSpanType(argument.Value.Type, spanType, readOnlySpanType)) + { + return true; + } + + // Check nested invocations + if (argument.Value is IInvocationOperation nestedInvocation) + { + if (HasSpanArguments(nestedInvocation, spanType, readOnlySpanType)) + { + return true; + } + } + } + + return false; + } + + private static bool IsSpanType(ITypeSymbol? type, INamedTypeSymbol? spanType, INamedTypeSymbol? readOnlySpanType) + { + if (type is not INamedTypeSymbol namedType) + { + return false; + } + + // Use symbol comparison with the span types looked up from compilation + var originalDefinition = namedType.OriginalDefinition; + return (spanType != null && SymbolEqualityComparer.Default.Equals(originalDefinition, spanType)) || + (readOnlySpanType != null && SymbolEqualityComparer.Default.Equals(originalDefinition, readOnlySpanType)); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/DoNotUseAsParallelInForEachLoop.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/DoNotUseAsParallelInForEachLoop.cs new file mode 100644 index 000000000000..c7e41f770684 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/DoNotUseAsParallelInForEachLoop.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Linq; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + using static MicrosoftNetCoreAnalyzersResources; + + /// + /// CA1876: + /// Analyzer to detect misuse of AsParallel() when used directly in a foreach loop. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class DoNotUseAsParallelInForEachLoopAnalyzer : DiagnosticAnalyzer + { + internal const string RuleId = "CA1876"; + + internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(DoNotUseAsParallelInForEachLoopTitle)), + CreateLocalizableResourceString(nameof(DoNotUseAsParallelInForEachLoopMessage)), + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + description: CreateLocalizableResourceString(nameof(DoNotUseAsParallelInForEachLoopDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private static void OnCompilationStart(CompilationStartAnalysisContext context) + { + var typeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation); + + // Get the ParallelEnumerable type + var parallelEnumerableType = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemLinqParallelEnumerable); + + if (parallelEnumerableType == null) + { + return; + } + + // Get all AsParallel methods - use SymbolEqualityComparer for proper comparison + var asParallelMethods = ImmutableHashSet.CreateRange( + SymbolEqualityComparer.Default, + parallelEnumerableType.GetMembers("AsParallel").OfType()); + + if (asParallelMethods.IsEmpty) + { + return; + } + + context.RegisterOperationAction(ctx => AnalyzeForEachLoop(ctx, asParallelMethods), OperationKind.Loop); + } + + private static void AnalyzeForEachLoop(OperationAnalysisContext context, ImmutableHashSet asParallelMethods) + { + if (context.Operation is not IForEachLoopOperation forEachLoop) + { + return; + } + + // Check if the collection is a direct result of AsParallel() + var collection = forEachLoop.Collection; + + // Walk up conversions to find the actual operation + while (collection is IConversionOperation conversion) + { + collection = conversion.Operand; + } + + // Check if this is an invocation of AsParallel + if (collection is IInvocationOperation invocation) + { + var targetMethod = invocation.TargetMethod; + + // For extension methods, we need to check the ReducedFrom or the original method + var methodToCheck = targetMethod.ReducedFrom ?? targetMethod; + + if (asParallelMethods.Contains(methodToCheck.OriginalDefinition)) + { + // Report diagnostic on the AsParallel call + context.ReportDiagnostic(invocation.CreateDiagnostic(Rule)); + } + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpan.Fixer.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpan.Fixer.cs new file mode 100644 index 000000000000..65f20128bec2 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpan.Fixer.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Composition; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + /// + /// CA1517: Use ReadOnlySpan<T> or ReadOnlyMemory<T> instead of Span<T> or Memory<T> + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PreferReadOnlySpanOverSpanFixer))] + [Shared] + public sealed class PreferReadOnlySpanOverSpanFixer : CodeFixProvider + { + public sealed override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(PreferReadOnlySpanOverSpanAnalyzer.RuleId); + + public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var node = root.FindNode(context.Span, getInnermostNodeForTie: true); + var semanticModel = await context.Document.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + + if (semanticModel.GetDeclaredSymbol(node, context.CancellationToken) is IParameterSymbol parameterSymbol && + GetReadOnlyTypeName(parameterSymbol.Type) is { } targetTypeName) + { + var title = string.Format(MicrosoftNetCoreAnalyzersResources.PreferReadOnlySpanOverSpanCodeFixTitle, targetTypeName); + + context.RegisterCodeFix( + CodeAction.Create( + title: title, + createChangedDocument: c => ChangeParameterTypeAsync(context.Document, node, c), + equivalenceKey: title), + context.Diagnostics[0]); + } + } + + private static string? GetReadOnlyTypeName(ITypeSymbol typeSymbol) => + typeSymbol is INamedTypeSymbol namedType && namedType.OriginalDefinition.Name is "Span" or "Memory" ? + $"ReadOnly{typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}" : + null; + + private static async Task ChangeParameterTypeAsync( + Document document, + SyntaxNode node, + CancellationToken cancellationToken) + { + var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var generator = editor.Generator; + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + // Get the parameter symbol to construct the correct type + var parameterSymbol = semanticModel.GetDeclaredSymbol(node, cancellationToken) as IParameterSymbol; + if (parameterSymbol?.Type is INamedTypeSymbol namedType && namedType.TypeArguments.Length == 1) + { + // Get the compilation to find the readonly types + var compilation = semanticModel.Compilation; + var typeName = namedType.OriginalDefinition.Name; + + INamedTypeSymbol? readOnlyType = + typeName is "Span" ? compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1) : + typeName is "Memory" ? compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlyMemory1) : + null; + + if (readOnlyType is not null) + { + // Construct the generic type with the same type argument + var newTypeNode = generator.TypeExpression( + readOnlyType.Construct(namedType.TypeArguments[0])); + + // Replace the parameter's type + editor.ReplaceNode(node, (currentNode, gen) => gen.WithType(currentNode, newTypeNode)); + + return editor.GetChangedDocument(); + } + } + + return document; + } + } +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpan.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpan.cs new file mode 100644 index 000000000000..d8c5086f65cf --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpan.cs @@ -0,0 +1,459 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Concurrent; +using System.Collections.Immutable; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + using static MicrosoftNetCoreAnalyzersResources; + + /// + /// CA1517: + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class PreferReadOnlySpanOverSpanAnalyzer : DiagnosticAnalyzer + { + internal const string RuleId = "CA1517"; + + internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(PreferReadOnlySpanOverSpanTitle)), + CreateLocalizableResourceString(nameof(PreferReadOnlySpanOverSpanMessage)), + DiagnosticCategory.Maintainability, + RuleLevel.IdeSuggestion, + description: CreateLocalizableResourceString(nameof(PreferReadOnlySpanOverSpanDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private static void OnCompilationStart(CompilationStartAnalysisContext context) + { + var compilation = context.Compilation; + + var span = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemSpan1); + var readOnlySpan = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1); + var memory = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemMemory1); + var readOnlyMemory = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlyMemory1); + var memoryExtensions = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemMemoryExtensions); + + if (span is null || readOnlySpan is null || memory is null || readOnlyMemory is null || memoryExtensions is null) + { + return; + } + + context.RegisterOperationBlockStartAction(blockStartContext => + { + // Skip methods that can't be changed (virtual, override, interface, etc.) + if (blockStartContext.OwningSymbol is not IMethodSymbol methodSymbol || + methodSymbol.IsVirtual || + methodSymbol.IsOverride || + methodSymbol.ContainingType.TypeKind is TypeKind.Interface || + methodSymbol.IsImplementationOfAnyInterfaceMember() || + !blockStartContext.Options.MatchesConfiguredVisibility(Rule, methodSymbol, compilation, defaultRequiredVisibility: SymbolVisibilityGroup.Internal | SymbolVisibilityGroup.Private)) + { + return; + } + + // Find candidate Span/Memory parameters + ConcurrentDictionary? candidateParameters = null; + ConcurrentDictionary parameterReferenceCounts = new(SymbolEqualityComparer.Default); + foreach (var parameter in methodSymbol.Parameters) + { + if (IsConvertibleSpanOrMemoryParameter(parameter, span, memory, readOnlySpan, readOnlyMemory, out var readOnlyType) && readOnlyType is not null) + { + candidateParameters ??= new ConcurrentDictionary(SymbolEqualityComparer.Default); + candidateParameters.TryAdd(parameter, readOnlyType); + parameterReferenceCounts.TryAdd(parameter, 0); + } + } + + if (candidateParameters is not null) + { + // Walk up from each relevant parameter reference looking to see whether it invalidates a read-only conversion. + blockStartContext.RegisterOperationAction(operationContext => + { + IParameterSymbol parameter = ((IParameterReferenceOperation)operationContext.Operation).Parameter; + if (candidateParameters.ContainsKey(parameter)) + { + parameterReferenceCounts.AddOrUpdate(parameter, 1, (_, count) => count + 1); + if (!IsUsageSafe(operationContext.Operation, parameter, candidateParameters, span, memory, readOnlySpan, readOnlyMemory, memoryExtensions, methodSymbol)) + { + candidateParameters.TryRemove(parameter, out _); + } + } + }, OperationKind.ParameterReference); + + // At the end, raise a diagnostic for any candidate parameters that weren't invalidated. + blockStartContext.RegisterOperationBlockEndAction(blockEndContext => + { + foreach (var kvp in candidateParameters) + { + var parameter = kvp.Key; + if (parameterReferenceCounts.TryGetValue(parameter, out var refCount) && + refCount is > 0) + { + blockEndContext.ReportDiagnostic(parameter.CreateDiagnostic( + Rule, + parameter.Name, + kvp.Value.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), + parameter.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat))); + } + } + }); + } + }); + } + + /// + /// Walks up the parent chain from a parameter reference to determine if usage is safe for conversion to its read-only counterpart. + /// + private static bool IsUsageSafe( + IOperation reference, + IParameterSymbol parameter, + ConcurrentDictionary candidateParameters, + INamedTypeSymbol span, + INamedTypeSymbol memory, + INamedTypeSymbol readOnlySpan, + INamedTypeSymbol readOnlyMemory, + INamedTypeSymbol memoryExtensions, + IMethodSymbol containingMethod) + { + if ((reference.GetValueUsageInfo(containingMethod) & ValueUsageInfo.WritableReference) == ValueUsageInfo.WritableReference) + { + return false; + } + + // Walk up the parent chain to find how this reference is being used. + for (var current = reference.Parent; current is not null; current = current.Parent) + { + switch (current) + { + // Check method calls off of the span/memory parameter. + case IInvocationOperation invocation: + // If this isn't invoked off of the parameter, skip it. + if (invocation.Instance is not IParameterReferenceOperation instParamRef || + !SymbolEqualityComparer.Default.Equals(instParamRef.Parameter, parameter)) + { + return false; + } + + if (!IsInstanceMethodSafe(invocation, span, memory, readOnlySpan, readOnlyMemory) || + invocation.TargetMethod.ReturnsByRef) + { + return false; + } + + // If the method returns Span/Memory and is passed to a method expecting writable type, it's unsafe + if (invocation.Type is INamedTypeSymbol invocationResultType && + IsSpanOrMemory(invocationResultType.OriginalDefinition, span, memory)) + { + // Check if the invocation is used as an argument + if (invocation.Parent is IArgumentOperation arg) + { + return IsArgumentSafe(parameter, arg, readOnlySpan, readOnlyMemory, memoryExtensions); + } + + // Continue checking parent chain; invalid consumption of a writeable result + // such as assignment to a writeable local will be caught higher up, e.g. by + // variable declaration checks. + continue; + } + + // Method doesn't return Span/Memory, it's safe. + return true; + + // Check property accesses off of the span/memory parameter. + case IPropertyReferenceOperation propRef: + if (propRef.Instance is IParameterReferenceOperation propParamRef && + SymbolEqualityComparer.Default.Equals(propParamRef.Parameter, parameter)) + { + if (IsPropertyAccessSafe(propRef, containingMethod)) + { + // If the property returns Span/Memory (like range indexer), only safe if used as argument. + if (propRef.Type is INamedTypeSymbol resultType && + IsSpanOrMemory(resultType.OriginalDefinition, span, memory)) + { + return propRef.Parent is IArgumentOperation arg && IsArgumentSafe(parameter, arg, readOnlySpan, readOnlyMemory, memoryExtensions); + } + + // Property access is safe. + return true; + } + + // Propery access isn't safe. + return false; + } + + continue; + + case IArgumentOperation argument: + return IsArgumentSafe(parameter, argument, readOnlySpan, readOnlyMemory, memoryExtensions); + + case IReturnOperation returnOp: + return IsReturnSafe(containingMethod, readOnlySpan, readOnlyMemory); + + case ISimpleAssignmentOperation assignment: + return IsAssignmentTargetSafe(parameter, assignment, readOnlySpan, readOnlyMemory); + + // Walk up through any passthrough operations. + case IAwaitOperation: + case IBlockOperation: + case IConditionalAccessOperation: + case IConstantPatternOperation: + case IDeclarationPatternOperation: + case IExpressionStatementOperation: + case IIsPatternOperation: + case IPropertySubpatternOperation: + case IRangeOperation: + case IRecursivePatternOperation: + case IRelationalPatternOperation: + case ISwitchExpressionArmOperation: + case ISwitchExpressionOperation: + case IVariableDeclarationGroupOperation: + case IVariableDeclarationOperation: + continue; + + // These operation just read. They're safe and we can stop walking as the span/memory consumption ends in these constructs. + case IBinaryOperation: + case ICoalesceOperation: + case IConversionOperation conversion: + case IForEachLoopOperation: + case IInterpolatedStringOperation: + case IUnaryOperation: + return true; + + // Anything else treat as unsafe. + default: + return false; + } + } + + // If we walked all the way up without finding any usage, it's safe (just reading). + return true; + } + + private static bool IsInstanceMethodSafe( + IInvocationOperation invocation, + INamedTypeSymbol span, + INamedTypeSymbol memory, + INamedTypeSymbol readOnlySpan, + INamedTypeSymbol readOnlyMemory) + { + if (invocation.Instance?.Type is not INamedTypeSymbol instanceType) + { + return true; + } + + // Check if this is a Span/Memory method + if (!IsSpanOrMemory(instanceType.OriginalDefinition, span, memory)) + { + return true; + } + + // Find the readonly counterpart + var readOnlyCounterpart = SymbolEqualityComparer.Default.Equals(instanceType.OriginalDefinition, span) ? + readOnlySpan : + readOnlyMemory; + + // Check if the method exists on the readonly version + return readOnlyCounterpart.Construct(instanceType.TypeArguments.ToArray()) + .GetMembers(invocation.TargetMethod.Name) + .OfType() + .Any(m => m.ParametersAreSame(invocation.TargetMethod)); + } + + private static bool IsPropertyAccessSafe(IPropertyReferenceOperation propRef, IMethodSymbol containingMethod) + { + // Unsafe if indexer is being written to + if (propRef.Parent is IAssignmentOperation assignment && assignment.Target == propRef) + { + return false; + } + + // Unsafe if indexer is part of increment/decrement operation (e.g., data[i]++) + if (propRef.Parent is IIncrementOrDecrementOperation) + { + return false; + } + + // Unsafe if indexer result is passed as ref/out + if (propRef.Parent is IArgumentOperation argument && + argument.Parameter?.RefKind is RefKind.Ref or RefKind.Out) + { + return false; + } + + // Unsafe if stored in ref local + // Check both direct parent and through initializer (ref int x = ref data[0]) + IVariableDeclaratorOperation? declarator = propRef.Parent as IVariableDeclaratorOperation; + if (declarator is null && propRef.Parent is IVariableInitializerOperation initializer) + { + declarator = initializer.Parent as IVariableDeclaratorOperation; + } + + if (declarator is not null && declarator.Symbol.RefKind != RefKind.None) + { + return false; + } + + // Unsafe if returned as ref (indexer result from readonly span doesn't support ref returns) + if (propRef.Parent is IReturnOperation && containingMethod.ReturnsByRef) + { + return false; + } + + return true; + } + + private static bool IsArgumentSafe( + IParameterSymbol parameter, + IArgumentOperation argument, + INamedTypeSymbol readOnlySpan, + INamedTypeSymbol readOnlyMemory, + INamedTypeSymbol memoryExtensions) + { + // Special-case Span/Memory-accepting methods we know don't mutate any parameters. + if (argument.Parent is IInvocationOperation invocation && + SymbolEqualityComparer.Default.Equals(invocation.TargetMethod.ContainingType, memoryExtensions)) + { + if (invocation.TargetMethod.Name.Contains("BinarySearch", StringComparison.Ordinal) || + invocation.TargetMethod.Name.Contains("Contains", StringComparison.Ordinal) || + invocation.TargetMethod.Name.Contains("ContainsAny", StringComparison.Ordinal) || + invocation.TargetMethod.Name.Contains("Count", StringComparison.Ordinal) || + invocation.TargetMethod.Name.Equals("EndsWith", StringComparison.Ordinal) || + invocation.TargetMethod.Name.Contains("IndexOf", StringComparison.Ordinal) || + invocation.TargetMethod.Name.Contains("IndexOfAny", StringComparison.Ordinal) || + invocation.TargetMethod.Name.Equals("StartsWith", StringComparison.Ordinal) || + invocation.TargetMethod.Name.Equals("SequenceEqual", StringComparison.Ordinal) || + invocation.TargetMethod.Name.Equals("Trim", StringComparison.Ordinal)) + { + return true; + } + } + + // Special-case when the argument is for the same parameter we're analyzing, e.g. a recursive call, + // since by definition that usage is safe. + if (SymbolEqualityComparer.Default.Equals(argument.Parameter, parameter)) + { + return true; + } + + // If the associated parameter is read-only, it's safe. + if (argument.Parameter?.Type is INamedTypeSymbol paramType) + { + return IsReadOnlySpanOrMemory(paramType.OriginalDefinition, readOnlySpan, readOnlyMemory); + } + + // Otherwise, we have to assume the method could mutate it. + return false; + } + + private static bool IsReturnSafe( + IMethodSymbol containingMethod, + INamedTypeSymbol readOnlySpan, + INamedTypeSymbol readOnlyMemory) + { + // Unsafe if ref returning. + if (containingMethod.ReturnsByRef) + { + return false; + } + + // Need to be able to get the return type. + if (containingMethod.ReturnType is not INamedTypeSymbol returnType) + { + return false; + } + + // If the return type is a primitive, it can't have smuggled out the reference. + // And if it's ReadOnlySpan/ReadOnlyMemory, then converting it is fine. + return + returnType.OriginalDefinition.IsPrimitiveType() || + IsReadOnlySpanOrMemory(returnType.OriginalDefinition, readOnlySpan, readOnlyMemory); + } + + private static bool IsAssignmentTargetSafe( + IParameterSymbol parameter, + ISimpleAssignmentOperation assignment, + INamedTypeSymbol readOnlySpan, + INamedTypeSymbol readOnlyMemory) + { + if (assignment.Target.Type is INamedTypeSymbol targetType) + { + // Safe if assigning to read-only type + if (IsReadOnlySpanOrMemory(targetType.OriginalDefinition, readOnlySpan, readOnlyMemory)) + { + return true; + } + + // Safe if assigning back to same parameter (e.g. param = param.Slice(...)), as in that + // case we're already tracking the parameter, and if we change the type of the parameter, + // we're also changing the type of the destination. + if (assignment.Target is IParameterReferenceOperation paramRef && + SymbolEqualityComparer.Default.Equals(paramRef.Parameter, parameter)) + { + return true; + } + } + + return false; + } + + private static bool IsConvertibleSpanOrMemoryParameter( + IParameterSymbol parameter, + INamedTypeSymbol span, + INamedTypeSymbol memory, + INamedTypeSymbol readOnlySpan, + INamedTypeSymbol readOnlyMemory, + out INamedTypeSymbol? readOnlyType) + { + if (parameter.RefKind is RefKind.None && + parameter.Type is INamedTypeSymbol namedType) + { + var originalDefinition = namedType.OriginalDefinition; + + if (SymbolEqualityComparer.Default.Equals(originalDefinition, span)) + { + if (namedType.TypeArguments.Length is 1) + { + readOnlyType = readOnlySpan.Construct(namedType.TypeArguments[0]); + return true; + } + } + else if (SymbolEqualityComparer.Default.Equals(originalDefinition, memory)) + { + if (namedType.TypeArguments.Length is 1) + { + readOnlyType = readOnlyMemory.Construct(namedType.TypeArguments[0]); + return true; + } + } + } + + readOnlyType = null; + return false; + } + + private static bool IsSpanOrMemory(INamedTypeSymbol target, INamedTypeSymbol span, INamedTypeSymbol memory) => + SymbolEqualityComparer.Default.Equals(target, span) || + SymbolEqualityComparer.Default.Equals(target, memory); + + private static bool IsReadOnlySpanOrMemory(INamedTypeSymbol target, INamedTypeSymbol readOnlySpan, INamedTypeSymbol readOnlyMemory) => + SymbolEqualityComparer.Default.Equals(target, readOnlySpan) || + SymbolEqualityComparer.Default.Equals(target, readOnlyMemory); + } +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs new file mode 100644 index 000000000000..7dddc3acbc5a --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.Fixer.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + /// + /// Fixer for . + /// + [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] + public sealed class PreferJsonElementParseFixer : CodeFixProvider + { + public sealed override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(PreferJsonElementParse.RuleId); + + public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + Document doc = context.Document; + SemanticModel model = await doc.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + SyntaxNode root = await doc.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + if (root.FindNode(context.Span, getInnermostNodeForTie: true) is not SyntaxNode node || + model.GetOperation(node, context.CancellationToken) is not IPropertyReferenceOperation propertyReference || + propertyReference.Property.Name != "RootElement" || + propertyReference.Instance is not IInvocationOperation invocation || + invocation.TargetMethod.Name != "Parse") + { + return; + } + + string title = MicrosoftNetCoreAnalyzersResources.PreferJsonElementParseFix; + context.RegisterCodeFix( + CodeAction.Create( + title, + createChangedDocument: async ct => + { + DocumentEditor editor = await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(false); + SyntaxGenerator generator = editor.Generator; + + // Get the JsonElement type + INamedTypeSymbol? jsonElementType = model.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextJsonJsonElement); + if (jsonElementType == null) + { + return doc; + } + + // Create the replacement: JsonElement.Parse(...) + // We need to use the same arguments that were passed to JsonDocument.Parse + var arguments = invocation.Arguments.Select(arg => arg.Syntax).ToArray(); + + SyntaxNode memberAccess = generator.MemberAccessExpression( + generator.TypeExpressionForStaticMemberAccess(jsonElementType), + "Parse"); + + SyntaxNode replacement = generator.InvocationExpression(memberAccess, arguments); + + // Replace the entire property reference (JsonDocument.Parse(...).RootElement) with JsonElement.Parse(...) + editor.ReplaceNode(propertyReference.Syntax, replacement.WithTriviaFrom(propertyReference.Syntax)); + + return editor.GetChangedDocument(); + }, + equivalenceKey: title), + context.Diagnostics); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs new file mode 100644 index 000000000000..2e509f70dfda --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParse.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Linq; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + using static MicrosoftNetCoreAnalyzersResources; + + /// + /// CA2026: Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class PreferJsonElementParse : DiagnosticAnalyzer + { + internal const string RuleId = "CA2026"; + + internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(PreferJsonElementParseTitle)), + CreateLocalizableResourceString(nameof(PreferJsonElementParseMessage)), + DiagnosticCategory.Reliability, + RuleLevel.IdeSuggestion, + CreateLocalizableResourceString(nameof(PreferJsonElementParseDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + context.RegisterCompilationStartAction(context => + { + // Get the JsonDocument and JsonElement types + INamedTypeSymbol? jsonDocumentType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextJsonJsonDocument); + INamedTypeSymbol? jsonElementType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextJsonJsonElement); + + if (jsonDocumentType is null || jsonElementType is null) + { + return; + } + + // Get all JsonElement.Parse overloads for matching + var jsonElementParseOverloads = jsonElementType.GetMembers("Parse") + .OfType() + .Where(m => m.IsStatic) + .ToImmutableArray(); + + // Check if JsonElement.Parse exists + if (jsonElementParseOverloads.IsEmpty || + !jsonDocumentType.GetMembers("Parse").Any(m => m is IMethodSymbol { IsStatic: true })) + { + return; + } + + // Get the RootElement property + IPropertySymbol? rootElementProperty = jsonDocumentType.GetMembers("RootElement") + .OfType() + .FirstOrDefault(); + + if (rootElementProperty is null) + { + return; + } + + context.RegisterOperationAction(context => + { + var propertyReference = (IPropertyReferenceOperation)context.Operation; + + // Check if this is accessing the RootElement property and the instance is a direct call to JsonDocument.Parse + if (!SymbolEqualityComparer.Default.Equals(propertyReference.Property, rootElementProperty) || + propertyReference.Instance is not IInvocationOperation invocation || + !SymbolEqualityComparer.Default.Equals(invocation.TargetMethod.ContainingType, jsonDocumentType) || + invocation.TargetMethod.Name != "Parse") + { + return; + } + + // Now we have the pattern: JsonDocument.Parse(...).RootElement + // Check if there's a matching JsonElement.Parse overload with the same parameter types + var jsonDocumentParseMethod = invocation.TargetMethod; + + foreach (var elementParseOverload in jsonElementParseOverloads) + { + if (elementParseOverload.Parameters.Length != jsonDocumentParseMethod.Parameters.Length) + { + continue; + } + + bool parametersMatch = true; + for (int i = 0; i < elementParseOverload.Parameters.Length; i++) + { + if (!SymbolEqualityComparer.Default.Equals(elementParseOverload.Parameters[i].Type, jsonDocumentParseMethod.Parameters[i].Type)) + { + parametersMatch = false; + break; + } + } + + if (parametersMatch) + { + context.ReportDiagnostic(propertyReference.CreateDiagnostic(Rule)); + break; + } + } + }, OperationKind.PropertyReference); + }); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferTypedStringBuilderAppendOverloads.Fixer.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferTypedStringBuilderAppendOverloads.Fixer.cs index 1f2a6bfe7ee3..7b3d67aaf86b 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferTypedStringBuilderAppendOverloads.Fixer.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferTypedStringBuilderAppendOverloads.Fixer.cs @@ -29,26 +29,59 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) SyntaxNode root = await doc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); if (root.FindNode(context.Span) is SyntaxNode expression) { - string title = MicrosoftNetCoreAnalyzersResources.PreferTypedStringBuilderAppendOverloadsRemoveToString; - context.RegisterCodeFix( - CodeAction.Create(title, - async ct => - { - SemanticModel model = await doc.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - if (model.GetOperationWalkingUpParentChain(expression, cancellationToken) is IArgumentOperation arg && - arg.Value is IInvocationOperation invoke && - invoke.Instance?.Syntax is SyntaxNode replacement) + SemanticModel model = await doc.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var operation = model.GetOperationWalkingUpParentChain(expression, cancellationToken); + + // Handle ToString() case + if (operation is IArgumentOperation arg && + arg.Value is IInvocationOperation invoke && + invoke.Instance?.Syntax is SyntaxNode replacement) + { + string title = MicrosoftNetCoreAnalyzersResources.PreferTypedStringBuilderAppendOverloadsRemoveToString; + context.RegisterCodeFix( + CodeAction.Create(title, + async ct => { DocumentEditor editor = await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(false); editor.ReplaceNode(expression, editor.Generator.Argument(replacement)); return editor.GetChangedDocument(); - } + }, + equivalenceKey: title), + context.Diagnostics); + } + // Handle new string(char, int) case (only for Append, not Insert) + else if (operation is IArgumentOperation argOp && + argOp.Value is IObjectCreationOperation objectCreation && + objectCreation.Arguments.Length == 2 && + argOp.Parent is IInvocationOperation invocationOp && + invocationOp.TargetMethod.Name == "Append") + { + string title = MicrosoftNetCoreAnalyzersResources.PreferTypedStringBuilderAppendOverloadsReplaceStringConstructor; + context.RegisterCodeFix( + CodeAction.Create(title, + async ct => + { + DocumentEditor editor = await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(false); - return doc; - }, - equivalenceKey: title), - context.Diagnostics); + // Get the char and int arguments from the string constructor + var charArgSyntax = objectCreation.Arguments[0].Value.Syntax; + var intArgSyntax = objectCreation.Arguments[1].Value.Syntax; + + // Append(new string(c, count)) -> Append(c, count) + SyntaxNode newInvocation = editor.Generator.InvocationExpression( + editor.Generator.MemberAccessExpression( + invocationOp.Instance!.Syntax, + "Append"), + editor.Generator.Argument(charArgSyntax), + editor.Generator.Argument(intArgSyntax)); + + editor.ReplaceNode(invocationOp.Syntax, newInvocation); + return editor.GetChangedDocument(); + }, + equivalenceKey: title), + context.Diagnostics); + } } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferTypedStringBuilderAppendOverloads.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferTypedStringBuilderAppendOverloads.cs index 632356ea00b0..af60c5b9dc03 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferTypedStringBuilderAppendOverloads.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Runtime/PreferTypedStringBuilderAppendOverloads.cs @@ -65,6 +65,16 @@ public sealed override void Initialize(AnalysisContext context) s.Parameters[1].Type.SpecialType != SpecialType.System_Object && s.Parameters[1].Type.TypeKind != TypeKind.Array); + // Get the Append(char, int) overload for the string constructor pattern. + // Note: There is no Insert(int, char, int) overload, so we only handle Append. + var appendCharIntMethod = stringBuilderType + .GetMembers("Append") + .OfType() + .FirstOrDefault(s => + s.Parameters.Length == 2 && + s.Parameters[0].Type.SpecialType == SpecialType.System_Char && + s.Parameters[1].Type.SpecialType == SpecialType.System_Int32); + // Get the StringBuilder.Append(string)/Insert(int, string) method, for comparison purposes. var appendStringMethod = appendMethods.FirstOrDefault(s => s.Parameters[0].Type.SpecialType == SpecialType.System_String); @@ -97,28 +107,43 @@ public sealed override void Initialize(AnalysisContext context) return; } - // We're only interested if the string argument is a "string ToString()" call. if (invocation.Arguments.Length != stringParamIndex + 1 || - invocation.Arguments[stringParamIndex] is not IArgumentOperation argument || - argument.Value is not IInvocationOperation toStringInvoke || - toStringInvoke.TargetMethod.Name != "ToString" || - toStringInvoke.Type?.SpecialType != SpecialType.System_String || - !toStringInvoke.TargetMethod.Parameters.IsEmpty) + invocation.Arguments[stringParamIndex] is not IArgumentOperation argument) { return; } - // We're only interested if the receiver type of that ToString call has a corresponding strongly-typed overload. - IMethodSymbol? stronglyTypedAppend = - (stringParamIndex == 0 ? appendMethods : insertMethods) - .FirstOrDefault(s => s.Parameters[stringParamIndex].Type.Equals(toStringInvoke.TargetMethod.ReceiverType)); - if (stronglyTypedAppend is null) + // Check if the string argument is a "string ToString()" call. + if (argument.Value is IInvocationOperation toStringInvoke && + toStringInvoke.TargetMethod.Name == "ToString" && + toStringInvoke.Type?.SpecialType == SpecialType.System_String && + toStringInvoke.TargetMethod.Parameters.IsEmpty) { - return; - } + // We're only interested if the receiver type of that ToString call has a corresponding strongly-typed overload. + IMethodSymbol? stronglyTypedAppend = + (stringParamIndex == 0 ? appendMethods : insertMethods) + .FirstOrDefault(s => s.Parameters[stringParamIndex].Type.Equals(toStringInvoke.TargetMethod.ReceiverType)); + if (stronglyTypedAppend is null) + { + return; + } - // Warn. - operationContext.ReportDiagnostic(toStringInvoke.CreateDiagnostic(Rule)); + // Warn. + operationContext.ReportDiagnostic(toStringInvoke.CreateDiagnostic(Rule)); + } + // Check if the string argument is a "new string(char, int)" constructor call. + // Note: This optimization only applies to Append, not Insert, as there's no Insert(int, char, int) overload. + else if (stringParamIndex == 0 && + argument.Value is IObjectCreationOperation objectCreation && + objectCreation.Type?.SpecialType == SpecialType.System_String && + objectCreation.Arguments.Length == 2 && + objectCreation.Arguments[0].Value?.Type?.SpecialType == SpecialType.System_Char && + objectCreation.Arguments[1].Value?.Type?.SpecialType == SpecialType.System_Int32 && + appendCharIntMethod is not null) + { + // Warn. + operationContext.ReportDiagnostic(objectCreation.CreateDiagnostic(Rule)); + } }, OperationKind.Invocation); }); } diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Tasks/DoNotUseNonCancelableTaskDelayWithWhenAny.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Tasks/DoNotUseNonCancelableTaskDelayWithWhenAny.cs new file mode 100644 index 000000000000..d8c17a1adca0 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Tasks/DoNotUseNonCancelableTaskDelayWithWhenAny.cs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Analyzer.Utilities.Lightup; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using static Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; + +namespace Microsoft.NetCore.Analyzers.Tasks +{ + /// + /// CA2027: + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class DoNotUseNonCancelableTaskDelayWithWhenAny : DiagnosticAnalyzer + { + internal const string RuleId = "CA2027"; + + internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(DoNotUseNonCancelableTaskDelayWithWhenAnyTitle)), + CreateLocalizableResourceString(nameof(DoNotUseNonCancelableTaskDelayWithWhenAnyMessage)), + DiagnosticCategory.Reliability, + RuleLevel.IdeSuggestion, + CreateLocalizableResourceString(nameof(DoNotUseNonCancelableTaskDelayWithWhenAnyDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + context.RegisterCompilationStartAction(context => + { + var compilation = context.Compilation; + + if (!compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask, out var taskType) || + !compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingCancellationToken, out var cancellationTokenType)) + { + return; + } + + context.RegisterOperationAction(context => + { + var invocation = (IInvocationOperation)context.Operation; + + // Check if this is a call to Task.WhenAny + var method = invocation.TargetMethod; + if (!SymbolEqualityComparer.Default.Equals(method.ContainingType, taskType) || + !method.IsStatic || + method.Name != nameof(Task.WhenAny)) + { + return; + } + + // Count the total number of tasks passed to WhenAny + int taskCount = 0; + List? taskDelayOperations = null; + + // Task.WhenAny has params parameters, so arguments are often implicitly wrapped in an array + // We need to check inside the array initializer or collection expression + for (int i = 0; i < invocation.Arguments.Length; i++) + { + var argument = invocation.Arguments[i].Value.WalkDownConversion(); + + // Check if this is an array creation + if (argument is IArrayCreationOperation { Initializer: not null } arrayCreation) + { + // Check each element in the array + foreach (var element in arrayCreation.Initializer.ElementValues) + { + taskCount++; + if (IsNonCancelableTaskDelay(element, taskType, cancellationTokenType)) + { + (taskDelayOperations ??= []).Add(element); + } + } + } + else if (ICollectionExpressionOperationWrapper.IsInstance(argument)) + { + // Check each element in the collection expression + var collectionExpression = ICollectionExpressionOperationWrapper.FromOperation(argument); + foreach (var element in collectionExpression.Elements) + { + taskCount++; + if (IsNonCancelableTaskDelay(element, taskType, cancellationTokenType)) + { + (taskDelayOperations ??= []).Add(element); + } + } + } + else + { + // Direct argument (not params or array) + taskCount++; + if (IsNonCancelableTaskDelay(argument, taskType, cancellationTokenType)) + { + (taskDelayOperations ??= []).Add(argument); + } + } + } + + // Only report diagnostics if there are at least 2 tasks total + // (avoid flagging Task.WhenAny(Task.Delay(...)) which may be used to avoid exceptions) + if (taskCount >= 2 && taskDelayOperations is not null) + { + foreach (var operation in taskDelayOperations) + { + context.ReportDiagnostic(operation.CreateDiagnostic(Rule)); + } + } + }, OperationKind.Invocation); + }); + } + + private static bool IsNonCancelableTaskDelay(IOperation operation, INamedTypeSymbol taskType, INamedTypeSymbol cancellationTokenType) + { + operation = operation.WalkDownConversion(); + + if (operation is not IInvocationOperation invocation) + { + return false; + } + + // Check if this is Task.Delay + var method = invocation.TargetMethod; + if (!SymbolEqualityComparer.Default.Equals(method.ContainingType, taskType) || + !method.IsStatic || + method.Name != nameof(Task.Delay)) + { + return false; + } + + // Check if any parameter is a CancellationToken, in which case we consider it cancelable + foreach (var parameter in method.Parameters) + { + if (SymbolEqualityComparer.Default.Equals(parameter.Type, cancellationTokenType)) + { + return false; + } + } + + return true; // Task.Delay without CancellationToken + } + } +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index d5f92baf02e5..c216888b4e06 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -237,6 +237,26 @@ Metody Dispose by měly volat SuppressFinalize + + Collapse into single Path.{0} operation + Collapse into single Path.{0} operation + + + + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + + + + Multiple consecutive Path.{0} operations can be collapsed into a single operation + Multiple consecutive Path.{0} operations can be collapsed into a single operation + + + + Collapse consecutive Path.Combine or Path.Join operations + Collapse consecutive Path.Combine or Path.Join operations + + , , @@ -1008,6 +1028,21 @@ Rozšíření a uživatelem definované převody se u obecných typů nepodporuj Pokud chcete využívat jemně odstupňované řízení přístupu a zásady přístupu na úrovni kontejneru, místo SAS účtu použijte SAS služby. + + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + + + + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + + + + Do not use 'AsParallel' in 'foreach' + Do not use 'AsParallel' in 'foreach' + + Do Not Use Broken Cryptographic Algorithms Nepoužívejte prolomené kryptografické algoritmy @@ -1148,6 +1183,21 @@ Rozšíření a uživatelem definované převody se u obecných typů nepodporuj {0} je nezabezpečený generátor náhodných čísel. Pokud se pro zabezpečení vyžaduje náhodnost, používejte kryptograficky zabezpečené generátory náhodných čísel. + + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + + + + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + + + + Cancel Task.Delay after Task.WhenAny completes + Cancel Task.Delay after Task.WhenAny completes + + Do not use obsolete key derivation function Nepoužívat zastaralou funkci pro odvození klíče @@ -2238,6 +2288,26 @@ Rozšíření a uživatelem definované převody se u obecných typů nepodporuj Upřednostňovat IsEmpty před Count + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. Upřednostňujte použití vlastnosti IsEmpty, Count nebo Length podle toho, která je k dispozici, namísto volání metody Enumerable.Any(). Záměr je jasnější a je výkonnější než použití rozšiřující metody Enumerable.Any(). @@ -2258,6 +2328,26 @@ Rozšíření a uživatelem definované převody se u obecných typů nepodporuj Upřednostňujte porovnání vlastnosti Length s 0 místo použití metody Any(), a to jak pro přehlednost, tak pro výkon. + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. Stream má přetížení ReadAsync, které jako první argument přijímá Memory<Byte>, a přetížení WriteAsync, které jako první argument přijímá ReadOnlyMemory<Byte>. Upřednostňujte volání přetížení založených na paměti, která jsou efektivnější. @@ -2294,13 +2384,13 @@ Rozšíření a uživatelem definované převody se u obecných typů nepodporuj - StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. - StringBuilder.Append a StringBuilder.Insert nabízejí přetížení kromě System.String i několika dalším typům. Kdykoli je to možné, upřednostňujte před metodou ToString() přetížení silného typu a přetížení založené na řetězcích. + StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)). + StringBuilder.Append a StringBuilder.Insert nabízejí přetížení kromě System.String i několika dalším typům. Kdykoli je to možné, upřednostňujte před metodou ToString() přetížení silného typu a přetížení založené na řetězcích. - Remove the ToString call in order to use a strongly-typed StringBuilder overload - Odeberte volání ToString, aby se používalo přetížení StringBuilder silného typu. + Prefer strongly-typed StringBuilder overload + Odeberte volání ToString, aby se používalo přetížení StringBuilder silného typu. @@ -2308,6 +2398,11 @@ Rozšíření a uživatelem definované převody se u obecných typů nepodporuj Odeberte volání ToString. + + Use StringBuilder.Append(char, int) overload + Use StringBuilder.Append(char, int) overload + + Prefer strongly-typed Append and Insert method overloads on StringBuilder Upřednostňovat pro StringBuilder přetížení metod Append a Insert silného typu diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index f63f006d064d..f73bb6806b4b 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -237,6 +237,26 @@ Dispose-Methoden müssen SuppressFinalize aufrufen + + Collapse into single Path.{0} operation + Collapse into single Path.{0} operation + + + + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + + + + Multiple consecutive Path.{0} operations can be collapsed into a single operation + Multiple consecutive Path.{0} operations can be collapsed into a single operation + + + + Collapse consecutive Path.Combine or Path.Join operations + Collapse consecutive Path.Combine or Path.Join operations + + , , @@ -1008,6 +1028,21 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type Für detailliertere Zugriffssteuerung und Zugriffsrichtlinie auf Containerebene die Dienst-SAS anstelle der Konto-SAS verwenden + + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + + + + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + + + + Do not use 'AsParallel' in 'foreach' + Do not use 'AsParallel' in 'foreach' + + Do Not Use Broken Cryptographic Algorithms Keine beschädigten kryptografischen Algorithmen verwenden @@ -1148,6 +1183,21 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type "{0}" ist ein unsicherer Zufallszahlen-Generator. Verwenden Sie kryptografisch sichere Zufallszahlen-Generatoren, wenn Zufallszahlen für die Sicherheit erforderlich sind. + + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + + + + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + + + + Cancel Task.Delay after Task.WhenAny completes + Cancel Task.Delay after Task.WhenAny completes + + Do not use obsolete key derivation function Keine veraltete Schlüsselableitungsfunktion verwenden @@ -2238,6 +2288,26 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type Ziehen Sie "IsEmpty" gegenüber "Count" vor + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. Verwenden Sie lieber die Eigenschaften „IsEmpty“, „Count“ oder „Length“, sofern verfügbar, statt „Enumerable.Any()“ aufzurufen. Die Absicht ist klarer und das Ausführungsverhalten besser als bei Verwendung der Erweiterungsmethode „Enumerable.Any()“. @@ -2258,6 +2328,26 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type Sowohl aus Gründen der Klarheit als auch der Leistung ist der Vergleich von „Length“ mit 0 der Verwendung von „Any()“ vorzuziehen + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. Stream verfügt über eine Überladung "ReadAsync", die "Memory<Byte>" als erstes Argument akzeptiert, sowie über eine Überladung "WriteAsync", die "ReadOnlyMemory<Byte>" als erstes Argument akzeptiert. Rufen Sie möglichst arbeitsspeicherbasierte Überladungen auf, da diese effizienter sind. @@ -2294,13 +2384,13 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type - StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. - StringBuilder.Append und StringBuilder.Insert stellen Überladungen für verschiedene Typen über System.String hinaus bereit. Sofern möglich, geben Sie den stark typisierten Überladungen Vorrang vor einer Verwendung von ToString() und zeichenfolgenbasierten Überladungen. + StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)). + StringBuilder.Append und StringBuilder.Insert stellen Überladungen für verschiedene Typen über System.String hinaus bereit. Sofern möglich, geben Sie den stark typisierten Überladungen Vorrang vor einer Verwendung von ToString() und zeichenfolgenbasierten Überladungen. - Remove the ToString call in order to use a strongly-typed StringBuilder overload - Entfernen Sie den Aufruf von "ToString", um eine stark typisierte StringBuilder-Überladung zu verwenden. + Prefer strongly-typed StringBuilder overload + Entfernen Sie den Aufruf von "ToString", um eine stark typisierte StringBuilder-Überladung zu verwenden. @@ -2308,6 +2398,11 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type ToString-Aufruf entfernen + + Use StringBuilder.Append(char, int) overload + Use StringBuilder.Append(char, int) overload + + Prefer strongly-typed Append and Insert method overloads on StringBuilder Stark typisierte Append- und Insert-Methodenüberladungen für StringBuilder bevorzugen diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index a03a7a0fa4d0..fa11e49487f4 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -237,6 +237,26 @@ Los métodos Dispose deberían llamar a SuppressFinalize + + Collapse into single Path.{0} operation + Collapse into single Path.{0} operation + + + + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + + + + Multiple consecutive Path.{0} operations can be collapsed into a single operation + Multiple consecutive Path.{0} operations can be collapsed into a single operation + + + + Collapse consecutive Path.Combine or Path.Join operations + Collapse consecutive Path.Combine or Path.Join operations + + , , @@ -1008,6 +1028,21 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip Use la SAS de servicio en lugar de la SAS de cuenta para el control de acceso detallado y la directiva de acceso de nivel de contenedor + + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + + + + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + + + + Do not use 'AsParallel' in 'foreach' + Do not use 'AsParallel' in 'foreach' + + Do Not Use Broken Cryptographic Algorithms No usar algoritmos criptográficos dañados @@ -1148,6 +1183,21 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip {0} es un generador de números aleatorios no seguro. Use generadores de números aleatorios que sean criptográficamente seguros cuando se requiera aleatoriedad por seguridad. + + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + + + + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + + + + Cancel Task.Delay after Task.WhenAny completes + Cancel Task.Delay after Task.WhenAny completes + + Do not use obsolete key derivation function No utilizar la función de derivación de claves obsoleta @@ -2238,6 +2288,26 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip Elegir IsEmpty en vez de Count + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. Es preferible usar las propiedades "IsEmpty", "Count" o "Length", si hay alguna disponible, en lugar de llamar a "Enumerable.Any()". La intención es más clara y tiene un mejor rendimiento que usar el método de extensión "Enumerable.Any()". @@ -2258,6 +2328,26 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip Es preferible comparar "Length" con 0 en lugar de usar "Any()", tanto por claridad como por rendimiento. + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. "Stream" tiene una sobrecarga "ReadAsync" que toma "Memory<Byte>" como primer argumento y una sobrecarga "WriteAsync" que toma "ReadOnlyMemory<Byte>" como primer argumento. Es preferible llamar a las sobrecargas basadas en memory, que son más eficaces. @@ -2294,13 +2384,13 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip - StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. - StringBuilder.Append y StringBuilder.Insert proporcionan sobrecargas para varios tipos aparte de System.String. Cuando sea posible, elija preferentemente las sobrecargas fuertemente tipadas frente al uso de ToString() y la sobrecarga basada en cadenas. + StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)). + StringBuilder.Append y StringBuilder.Insert proporcionan sobrecargas para varios tipos aparte de System.String. Cuando sea posible, elija preferentemente las sobrecargas fuertemente tipadas frente al uso de ToString() y la sobrecarga basada en cadenas. - Remove the ToString call in order to use a strongly-typed StringBuilder overload - Quite la llamada a ToString para usar una sobrecarga StringBuilder fuertemente tipada. + Prefer strongly-typed StringBuilder overload + Quite la llamada a ToString para usar una sobrecarga StringBuilder fuertemente tipada. @@ -2308,6 +2398,11 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip Quitar la llamada a ToString + + Use StringBuilder.Append(char, int) overload + Use StringBuilder.Append(char, int) overload + + Prefer strongly-typed Append and Insert method overloads on StringBuilder Prefiera las sobrecargas de método Append e Insert fuertemente tipadas en StringBuilder diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 40c160bb5b5f..c1e56691e81d 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -237,6 +237,26 @@ Les méthodes Dispose doivent appeler SuppressFinalize + + Collapse into single Path.{0} operation + Collapse into single Path.{0} operation + + + + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + + + + Multiple consecutive Path.{0} operations can be collapsed into a single operation + Multiple consecutive Path.{0} operations can be collapsed into a single operation + + + + Collapse consecutive Path.Combine or Path.Join operations + Collapse consecutive Path.Combine or Path.Join operations + + , , @@ -1008,6 +1028,21 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en Utiliser un SAP de service à la place d'un SAP de compte pour appliquer un contrôle d'accès de granularité fine et une stratégie d'accès au niveau du conteneur + + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + + + + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + + + + Do not use 'AsParallel' in 'foreach' + Do not use 'AsParallel' in 'foreach' + + Do Not Use Broken Cryptographic Algorithms Ne pas utiliser d'algorithmes de chiffrement cassés @@ -1148,6 +1183,21 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en {0} est un générateur de nombres aléatoires non sécurisé. Utilisez des générateurs de nombres aléatoires sécurisés de manière chiffrée quand une sélection aléatoire est nécessaire pour la sécurité. + + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + + + + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + + + + Cancel Task.Delay after Task.WhenAny completes + Cancel Task.Delay after Task.WhenAny completes + + Do not use obsolete key derivation function Ne pas utiliser de fonction de dérivation de clés obsolète @@ -2238,6 +2288,26 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en Préférer IsEmpty à Count + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. Préférez utiliser les propriétés 'IsEmpty', 'Count' ou 'Length' selon la disponibilité, plutôt que d’appeler 'Enumerable.Any()'. L’intention est plus claire et plus performante que l’utilisation de la méthode d’extension 'Enumerable.Any()'. @@ -2258,6 +2328,26 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en Préférez comparer 'Length' à 0 au lieu d’utiliser 'Any()', à la fois pour plus de clarté et pour des performances + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream’ a une surcharge ’ReadAsync’ qui prend un ’Memory<Byte>' comme premier argument et une surcharge ’WriteAsync’ qui prend un ’ReadOnlyMemory<Byte>' comme premier argument. Préférez l'appel des surcharges basées sur la mémoire, car elles sont plus efficaces. @@ -2294,13 +2384,13 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en - StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. - StringBuilder.Append et StringBuilder.Insert fournissent des surcharges pour plusieurs types au-delà de System.String. Quand cela est possible, préférez les surcharges fortement typées à l'utilisation de ToString() et de la surcharge basée sur une chaîne. + StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)). + StringBuilder.Append et StringBuilder.Insert fournissent des surcharges pour plusieurs types au-delà de System.String. Quand cela est possible, préférez les surcharges fortement typées à l'utilisation de ToString() et de la surcharge basée sur une chaîne. - Remove the ToString call in order to use a strongly-typed StringBuilder overload - Supprimez l'appel de ToString pour utiliser une surcharge StringBuilder fortement typée + Prefer strongly-typed StringBuilder overload + Supprimez l'appel de ToString pour utiliser une surcharge StringBuilder fortement typée @@ -2308,6 +2398,11 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en Supprimer l'appel de ToString + + Use StringBuilder.Append(char, int) overload + Use StringBuilder.Append(char, int) overload + + Prefer strongly-typed Append and Insert method overloads on StringBuilder Préférez les surcharges de méthode Append et Insert fortement typées sur StringBuilder diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 924a482d9f5d..c7ee78af1adb 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -237,6 +237,26 @@ I metodi Dispose devono chiamare SuppressFinalize + + Collapse into single Path.{0} operation + Collapse into single Path.{0} operation + + + + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + + + + Multiple consecutive Path.{0} operations can be collapsed into a single operation + Multiple consecutive Path.{0} operations can be collapsed into a single operation + + + + Collapse consecutive Path.Combine or Path.Join operations + Collapse consecutive Path.Combine or Path.Join operations + + , , @@ -1008,6 +1028,21 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi Usare la firma di accesso condiviso del service anziché quella dell'account per criteri più specifici di accesso a livello di contenitore e di controllo di accesso + + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + + + + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + + + + Do not use 'AsParallel' in 'foreach' + Do not use 'AsParallel' in 'foreach' + + Do Not Use Broken Cryptographic Algorithms Non usare algoritmi di crittografia violati @@ -1148,6 +1183,21 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi {0} è un generatore di numeri casuali non sicuro. Usare generatori di numeri casuali sicuri dal punto di vista della crittografia quando per motivi di sicurezza è richiesta la casualità. + + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + + + + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + + + + Cancel Task.Delay after Task.WhenAny completes + Cancel Task.Delay after Task.WhenAny completes + + Do not use obsolete key derivation function Non usare la funzione di derivazione di chiave obsoleta @@ -2238,6 +2288,26 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi Preferire IsEmpty a Count + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. Preferire l’uso delle proprietà 'IsEmpty', 'Count' o 'Length' se disponibili, anziché chiamare 'Enumerable.Any()'. La finalità è più chiara ed è più efficiente rispetto all'uso del metodo di estensione 'Enumerable.Any()'. @@ -2258,6 +2328,26 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi Preferire il confronto 'Length' con 0 anziché usare 'Any()', sia per chiarezza che per prestazioni + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream' contiene un overload 'ReadAsync' che accetta 'Memory<Byte>' come primo argomento e un overload 'WriteAsync' che accetta 'ReadOnlyMemory<Byte>' come primo argomento. Per la chiamata preferire gli overload basati su Memory, che sono più efficaci. @@ -2294,13 +2384,13 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi - StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. - StringBuilder.Append e StringBuilder.Insert forniscono overload per più tipi oltre System.String. Quando possibile, preferire gli overload fortemente tipizzati invece di usare ToString() e l'overload basato su stringhe. + StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)). + StringBuilder.Append e StringBuilder.Insert forniscono overload per più tipi oltre System.String. Quando possibile, preferire gli overload fortemente tipizzati invece di usare ToString() e l'overload basato su stringhe. - Remove the ToString call in order to use a strongly-typed StringBuilder overload - Rimuovere la chiamata ToString per usare un overload di StringBuilder fortemente tipizzato + Prefer strongly-typed StringBuilder overload + Rimuovere la chiamata ToString per usare un overload di StringBuilder fortemente tipizzato @@ -2308,6 +2398,11 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi Rimuovere la chiamata a ToString + + Use StringBuilder.Append(char, int) overload + Use StringBuilder.Append(char, int) overload + + Prefer strongly-typed Append and Insert method overloads on StringBuilder Preferire gli overload di metodi Append e Insert fortemente tipizzati su StringBuilder diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 6c3fbfeccd9a..dd773f57b467 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -237,6 +237,26 @@ Dispose メソッドは、SuppressFinalize を呼び出す必要があります + + Collapse into single Path.{0} operation + Collapse into single Path.{0} operation + + + + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + + + + Multiple consecutive Path.{0} operations can be collapsed into a single operation + Multiple consecutive Path.{0} operations can be collapsed into a single operation + + + + Collapse consecutive Path.Combine or Path.Join operations + Collapse consecutive Path.Combine or Path.Join operations + + , , @@ -1008,6 +1028,21 @@ Enumerable.OfType<T> で使用されるジェネリック型チェック ( 詳細に設定されたアクセス制御とコンテナーレベルのアクセス ポリシーには、アカウント SAS の代わりにサービス SAS を使用してください + + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + + + + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + + + + Do not use 'AsParallel' in 'foreach' + Do not use 'AsParallel' in 'foreach' + + Do Not Use Broken Cryptographic Algorithms 破られた暗号アルゴリズムを使用しない @@ -1148,6 +1183,21 @@ Enumerable.OfType<T> で使用されるジェネリック型チェック ( {0} は安全でない乱数ジェネレーターです。セキュリティにランダム度が必要な場合に、暗号化によってセキュリティで保護された乱数ジェネレーターを使用します。 + + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + + + + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + + + + Cancel Task.Delay after Task.WhenAny completes + Cancel Task.Delay after Task.WhenAny completes + + Do not use obsolete key derivation function 非推奨のキー派生関数を使用しないでください @@ -2238,6 +2288,26 @@ Enumerable.OfType<T> で使用されるジェネリック型チェック ( Count より IsEmpty を優先する + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. 'Enumerable.Any()' を呼び出すのではなく、'IsEmpty'、'Count'、または 'Length' のいずれか使用可能なプロパティの使用を優先してください。この方が、'Enumerable.Any()' 拡張メソッドを使用するよりも意図が明確で、パフォーマンスが向上します。 @@ -2258,6 +2328,26 @@ Enumerable.OfType<T> で使用されるジェネリック型チェック ( 明確性とパフォーマンスの両方のために、'Any()' を使用するのではなく、'Length' を 0 と比較することを優先してください + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream' には、最初の引数として 'Memory<Byte>' を取る 'ReadAsync' オーバーロードと、最初の引数として 'ReadOnlyMemory<Byte>' を取る 'WriteAsync' オーバーロードがあります。より効率的なメモリ ベースのオーバーロードを呼び出すことをお勧めします。 @@ -2294,13 +2384,13 @@ Enumerable.OfType<T> で使用されるジェネリック型チェック ( - StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. - StringBuilder.Append および StringBuilder.Insert では、System.String 以外の複数の型に対してオーバーロードを提供します。可能であれば、ToString () と文字列ベースのオーバーロードを使用するよりも、厳密に型指定されたオーバーロードを優先して使用することをお勧めします。 + StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)). + StringBuilder.Append および StringBuilder.Insert では、System.String 以外の複数の型に対してオーバーロードを提供します。可能であれば、ToString () と文字列ベースのオーバーロードを使用するよりも、厳密に型指定されたオーバーロードを優先して使用することをお勧めします。 - Remove the ToString call in order to use a strongly-typed StringBuilder overload - 厳密に型指定された StringBuilder のオーバーロードを使用するには、ToString 呼び出しを削除してください + Prefer strongly-typed StringBuilder overload + 厳密に型指定された StringBuilder のオーバーロードを使用するには、ToString 呼び出しを削除してください @@ -2308,6 +2398,11 @@ Enumerable.OfType<T> で使用されるジェネリック型チェック ( ToString 呼び出しを削除する + + Use StringBuilder.Append(char, int) overload + Use StringBuilder.Append(char, int) overload + + Prefer strongly-typed Append and Insert method overloads on StringBuilder StringBuilder での厳密に型指定された Append および Insert メソッドのオーバーロードを推奨 diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index f0706403c0d4..56b19a3576d0 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -237,6 +237,26 @@ Dispose 메서드는 SuppressFinalize를 호출해야 합니다. + + Collapse into single Path.{0} operation + Collapse into single Path.{0} operation + + + + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + + + + Multiple consecutive Path.{0} operations can be collapsed into a single operation + Multiple consecutive Path.{0} operations can be collapsed into a single operation + + + + Collapse consecutive Path.Combine or Path.Join operations + Collapse consecutive Path.Combine or Path.Join operations + + , , @@ -1008,6 +1028,21 @@ Enumerable.OfType<T>에서 사용하는 제네릭 형식 검사(C# 'is' 세분화된 액세스 제어와 컨테이너 수준 액세스 정책을 위해 계정 SAS가 아닌 서비스 SAS 사용 + + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + + + + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + + + + Do not use 'AsParallel' in 'foreach' + Do not use 'AsParallel' in 'foreach' + + Do Not Use Broken Cryptographic Algorithms 손상된 암호화 알고리즘을 사용하지 마세요. @@ -1148,6 +1183,21 @@ Enumerable.OfType<T>에서 사용하는 제네릭 형식 검사(C# 'is' {0}은(는) 비보안 난수 생성기입니다. 보안을 위해 임의성이 필요한 경우 암호화된 보안 난수 생성기를 사용하세요. + + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + + + + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + + + + Cancel Task.Delay after Task.WhenAny completes + Cancel Task.Delay after Task.WhenAny completes + + Do not use obsolete key derivation function 사용되지 않는 키 파생 함수 사용 안 함 @@ -2238,6 +2288,26 @@ Enumerable.OfType<T>에서 사용하는 제네릭 형식 검사(C# 'is' Count 대신 IsEmpty 사용 + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. 'Enumerable.Any()'를 호출하는 것보다 사용 가능한 'IsEmpty', 'Count' 또는 'Length' 속성을 사용하는 것이 좋습니다. 의도가 더 명확하고 'Enumerable.Any()' 확장 방법을 사용하는 것보다 더 성능이 좋습니다. @@ -2258,6 +2328,26 @@ Enumerable.OfType<T>에서 사용하는 제네릭 형식 검사(C# 'is' 명확성과 성능을 위해 'Any()'를 사용하는 것보다 'Length'를 0과 비교하는 것이 좋습니다. + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream'에 첫 번째 인수로 'Memory<Byte>'를 사용하는 'ReadAsync' 오버로드와 첫 번째 인수로 'ReadOnlyMemory<Byte>'를 사용하는 'WriteAsync' 오버로드가 있습니다. 더 효율적인 메모리 기반 오버로드를 호출하는 것이 좋습니다. @@ -2294,13 +2384,13 @@ Enumerable.OfType<T>에서 사용하는 제네릭 형식 검사(C# 'is' - StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. - StringBuilder.Append 및 StringBuilder.Insert는 System.String 외의 여러 형식에 대한 오버로드를 제공합니다. 가능한 경우 ToString() 및 문자열 기반 오버로드를 사용하는 대신 강력한 형식의 오버로드를 사용하세요. + StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)). + StringBuilder.Append 및 StringBuilder.Insert는 System.String 외의 여러 형식에 대한 오버로드를 제공합니다. 가능한 경우 ToString() 및 문자열 기반 오버로드를 사용하는 대신 강력한 형식의 오버로드를 사용하세요. - Remove the ToString call in order to use a strongly-typed StringBuilder overload - 강력한 형식의 StringBuilder 오버로드를 사용하려면 ToString 호출을 제거하세요. + Prefer strongly-typed StringBuilder overload + 강력한 형식의 StringBuilder 오버로드를 사용하려면 ToString 호출을 제거하세요. @@ -2308,6 +2398,11 @@ Enumerable.OfType<T>에서 사용하는 제네릭 형식 검사(C# 'is' ToString 호출 제거 + + Use StringBuilder.Append(char, int) overload + Use StringBuilder.Append(char, int) overload + + Prefer strongly-typed Append and Insert method overloads on StringBuilder StringBuilder 대신 강력한 형식의 Append 및 Insert 메서드 오버로드를 사용 diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 51f4dee3e919..7ce9f977ec86 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -237,6 +237,26 @@ Metoda Dispose powinna wywoływać metodę SuppressFinalize + + Collapse into single Path.{0} operation + Collapse into single Path.{0} operation + + + + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + + + + Multiple consecutive Path.{0} operations can be collapsed into a single operation + Multiple consecutive Path.{0} operations can be collapsed into a single operation + + + + Collapse consecutive Path.Combine or Path.Join operations + Collapse consecutive Path.Combine or Path.Join operations + + , , @@ -1008,6 +1028,21 @@ Konwersje poszerzane i zdefiniowane przez użytkownika nie są obsługiwane w pr Użyj sygnatury dostępu współdzielonego usługi zamiast sygnatury dostępu współdzielonego konta w celu uzyskania szczegółowej kontroli dostępu i zasad dostępu na poziomie kontenera + + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + + + + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + + + + Do not use 'AsParallel' in 'foreach' + Do not use 'AsParallel' in 'foreach' + + Do Not Use Broken Cryptographic Algorithms Nie używaj złamanych algorytmów kryptograficznych @@ -1148,6 +1183,21 @@ Konwersje poszerzane i zdefiniowane przez użytkownika nie są obsługiwane w pr {0} to niezabezpieczony generator liczb losowych. Użyj kryptograficznie zabezpieczonego generatora liczb losowych, gdy losowość jest wymagana ze względów bezpieczeństwa. + + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + + + + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + + + + Cancel Task.Delay after Task.WhenAny completes + Cancel Task.Delay after Task.WhenAny completes + + Do not use obsolete key derivation function Nie używaj przestarzałej funkcji wyprowadzenia klucza @@ -2238,6 +2288,26 @@ Konwersje poszerzane i zdefiniowane przez użytkownika nie są obsługiwane w pr Preferuj właściwość IsEmpty przed Count + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. Preferuj używanie właściwości „IsEmpty”, „Count” lub „Length” w zależności od dostępności, zamiast wywoływać metodę „Enumerable.Any()”. Intencja jest bardziej przejrzysta i wydajniejsza niż użycie metody rozszerzenia „Enumerable.Any()”. @@ -2258,6 +2328,26 @@ Konwersje poszerzane i zdefiniowane przez użytkownika nie są obsługiwane w pr Preferuj porównywanie wartości „Length” z wartością 0 zamiast używania elementu „Any()”, zarówno w celu zapewnienia przejrzystości, jak i wydajności + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. Element „Stream” ma przeciążenie „ReadAsync”, które przyjmuje „Memory<Byte>” jako pierwszy argument i przeciążenie „WriteAsync”, które przyjmuje „ReadOnlyMemory<Byte>” jako pierwszy argument. Preferuj wywoływanie przeciążeń opartych na pamięci, co jest bardziej wydajne. @@ -2294,13 +2384,13 @@ Konwersje poszerzane i zdefiniowane przez użytkownika nie są obsługiwane w pr - StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. - Metody StringBuilder.Append i StringBuilder.Insert zapewniają przeciążenia dla wielu typów oprócz typu System.String. Jeśli to możliwe, preferuj silnie typizowane przeciążenia zamiast używania metody ToString() i przeciążenia opartego na ciągu. + StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)). + Metody StringBuilder.Append i StringBuilder.Insert zapewniają przeciążenia dla wielu typów oprócz typu System.String. Jeśli to możliwe, preferuj silnie typizowane przeciążenia zamiast używania metody ToString() i przeciążenia opartego na ciągu. - Remove the ToString call in order to use a strongly-typed StringBuilder overload - Usuń wywołanie metody ToString, aby użyć silnie typizowanego przeciążenia elementu StringBuilder + Prefer strongly-typed StringBuilder overload + Usuń wywołanie metody ToString, aby użyć silnie typizowanego przeciążenia elementu StringBuilder @@ -2308,6 +2398,11 @@ Konwersje poszerzane i zdefiniowane przez użytkownika nie są obsługiwane w pr Usuń wywołanie metody ToString + + Use StringBuilder.Append(char, int) overload + Use StringBuilder.Append(char, int) overload + + Prefer strongly-typed Append and Insert method overloads on StringBuilder Preferuj silnie typizowane metody Append i Insert w elemencie StringBuilder diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index cb9dea8edaf1..f3e06eabc499 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -237,6 +237,26 @@ Os métodos Dispose devem chamar SuppressFinalize + + Collapse into single Path.{0} operation + Collapse into single Path.{0} operation + + + + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + + + + Multiple consecutive Path.{0} operations can be collapsed into a single operation + Multiple consecutive Path.{0} operations can be collapsed into a single operation + + + + Collapse consecutive Path.Combine or Path.Join operations + Collapse consecutive Path.Combine or Path.Join operations + + , , @@ -1008,6 +1028,21 @@ As ampliação e conversões definidas pelo usuário não são compatíveis com Usar o Serviço SAS em vez da SAS de Conta para o controle de acesso refinado e política de acesso de nível de contêiner + + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + + + + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + + + + Do not use 'AsParallel' in 'foreach' + Do not use 'AsParallel' in 'foreach' + + Do Not Use Broken Cryptographic Algorithms Não usar algoritmos de criptografia desfeitos @@ -1148,6 +1183,21 @@ As ampliação e conversões definidas pelo usuário não são compatíveis com {0} é um gerador de números aleatórios não seguro. Use geradores de números aleatórios criptograficamente seguros quando a aleatoriedade for necessária para segurança. + + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + + + + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + + + + Cancel Task.Delay after Task.WhenAny completes + Cancel Task.Delay after Task.WhenAny completes + + Do not use obsolete key derivation function Não use a função de derivação de chave obsoleta @@ -2238,6 +2288,26 @@ As ampliação e conversões definidas pelo usuário não são compatíveis com Preferir IsEmpty em vez de Count + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. Prefira usar as propriedades 'IsEmpty', 'Count' ou 'Length', conforme disponível, em vez de chamar 'Enumerable.Any()'. A intenção é mais clara e tem mais desempenho do que usar o método de extensão 'Enumerable.Any()'. @@ -2258,6 +2328,26 @@ As ampliação e conversões definidas pelo usuário não são compatíveis com Prefira comparar 'Length' com 0 em vez de usar 'Any()', tanto para clareza quanto para desempenho + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream' tem uma sobrecarga 'ReadAsync' que recebe um 'Memory<Byte>' como o primeiro argumento e uma sobrecarga 'WriteAsync' que recebe um 'ReadOnlyMemory<Byte>' como o primeiro argumento. Prefira chamar as sobrecargas com base na memória, que são mais eficientes. @@ -2294,13 +2384,13 @@ As ampliação e conversões definidas pelo usuário não são compatíveis com - StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. - StringBuilder.Append e StringBuilder.Insert fornecem sobrecargas para vários tipos além de System.String. Quando possível, prefira as sobrecargas fortemente tipadas usando ToString() e a sobrecarga baseada em cadeia de caracteres. + StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)). + StringBuilder.Append e StringBuilder.Insert fornecem sobrecargas para vários tipos além de System.String. Quando possível, prefira as sobrecargas fortemente tipadas usando ToString() e a sobrecarga baseada em cadeia de caracteres. - Remove the ToString call in order to use a strongly-typed StringBuilder overload - Remova a chamada ToString para usar uma sobrecarga de StringBuilder fortemente tipada + Prefer strongly-typed StringBuilder overload + Remova a chamada ToString para usar uma sobrecarga de StringBuilder fortemente tipada @@ -2308,6 +2398,11 @@ As ampliação e conversões definidas pelo usuário não são compatíveis com Remover a chamada ToString + + Use StringBuilder.Append(char, int) overload + Use StringBuilder.Append(char, int) overload + + Prefer strongly-typed Append and Insert method overloads on StringBuilder Prefira sobrecargas de método Append e Insert fortemente tipadas no StringBuilder diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 57538a5cb231..ab312221b9d3 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -237,6 +237,26 @@ Методы Dispose должны вызывать SuppressFinalize + + Collapse into single Path.{0} operation + Collapse into single Path.{0} operation + + + + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + + + + Multiple consecutive Path.{0} operations can be collapsed into a single operation + Multiple consecutive Path.{0} operations can be collapsed into a single operation + + + + Collapse consecutive Path.Combine or Path.Join operations + Collapse consecutive Path.Combine or Path.Join operations + + , , @@ -1008,6 +1028,21 @@ Widening and user defined conversions are not supported with generic types.Используйте SAS службы вместо SAS учетной записи для детализированного контроля доступа и политики доступа на уровне контейнеров. + + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + + + + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + + + + Do not use 'AsParallel' in 'foreach' + Do not use 'AsParallel' in 'foreach' + + Do Not Use Broken Cryptographic Algorithms Не используйте взломанные алгоритмы шифрования @@ -1148,6 +1183,21 @@ Widening and user defined conversions are not supported with generic types.{0} является небезопасным генератором случайных чисел. Если случайные числа требуются для обеспечения безопасности, используйте криптографически безопасные генераторы случайных чисел. + + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + + + + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + + + + Cancel Task.Delay after Task.WhenAny completes + Cancel Task.Delay after Task.WhenAny completes + + Do not use obsolete key derivation function Не используйте устаревшую функцию формирования ключа. @@ -2238,6 +2288,26 @@ Widening and user defined conversions are not supported with generic types.Старайтесь использовать "IsEmpty" вместо "Count" + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. Старайтесь использовать свойства 'IsEmpty', 'Count' или 'Length' в зависимости от доступности вместо вызова 'Enumerable.Any()'. Намерение является более четким и более эффективным по сравнению с использованием метода расширения 'Enumerable.Any()'. @@ -2258,6 +2328,26 @@ Widening and user defined conversions are not supported with generic types.Для ясности и для обеспечения производительности старайтесь сравнивать 'Length' с 0 вместо того, чтобы использовать 'Any()' + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. "Stream" содержит перегрузку "ReadAsync", которая принимает в качестве первого аргумента "Memory<Byte>", и перегрузку "WriteAsync", которая принимает в качестве первого аргумента "ReadOnlyMemory<Byte>". Старайтесь использовать перегрузки на основе памяти, которые являются более эффективными. @@ -2294,13 +2384,13 @@ Widening and user defined conversions are not supported with generic types. - StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. - StringBuilder.Append и StringBuilder.Insert предоставляют перегрузки для нескольких типов наряду с System.String. Если это возможно, рекомендуется использовать строго типизированные перегрузки и перегрузки на основе строк вместо метода ToString(). + StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)). + StringBuilder.Append и StringBuilder.Insert предоставляют перегрузки для нескольких типов наряду с System.String. Если это возможно, рекомендуется использовать строго типизированные перегрузки и перегрузки на основе строк вместо метода ToString(). - Remove the ToString call in order to use a strongly-typed StringBuilder overload - Удалите вызов ToString и используйте строго типизированную перегрузку StringBuilder. + Prefer strongly-typed StringBuilder overload + Удалите вызов ToString и используйте строго типизированную перегрузку StringBuilder. @@ -2308,6 +2398,11 @@ Widening and user defined conversions are not supported with generic types.Удалите вызов ToString + + Use StringBuilder.Append(char, int) overload + Use StringBuilder.Append(char, int) overload + + Prefer strongly-typed Append and Insert method overloads on StringBuilder Рекомендация использовать строго типизированные перегрузки методов Append и Insert в StringBuilder diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index de2ea9f502e1..afb612d95902 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -237,6 +237,26 @@ Dispose yöntemleri tarafından SuppressFinalize çağrılmalıdır + + Collapse into single Path.{0} operation + Collapse into single Path.{0} operation + + + + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + + + + Multiple consecutive Path.{0} operations can be collapsed into a single operation + Multiple consecutive Path.{0} operations can be collapsed into a single operation + + + + Collapse consecutive Path.Combine or Path.Join operations + Collapse consecutive Path.Combine or Path.Join operations + + , , @@ -1008,6 +1028,21 @@ Genel türlerde genişletme ve kullanıcı tanımlı dönüştürmeler desteklen Ayrıntılı erişim denetimi ve kapsayıcı düzeyinde erişim ilkesi için Hesap SAS'si yerine Hizmet SAS'sini kullanın + + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + + + + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + + + + Do not use 'AsParallel' in 'foreach' + Do not use 'AsParallel' in 'foreach' + + Do Not Use Broken Cryptographic Algorithms Bozuk Kriptografik Algoritmalar Kullanma @@ -1148,6 +1183,21 @@ Genel türlerde genişletme ve kullanıcı tanımlı dönüştürmeler desteklen {0}, güvenli olmayan bir rastgele sayı üreticidir. Güvenlik için rastgelelik gerekli olduğunda şifreli olarak güvenli rastgele sayı üreticileri kullanın. + + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + + + + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + + + + Cancel Task.Delay after Task.WhenAny completes + Cancel Task.Delay after Task.WhenAny completes + + Do not use obsolete key derivation function Artık kullanılmayan anahtar türetme işlevini kullanma @@ -2238,6 +2288,26 @@ Genel türlerde genişletme ve kullanıcı tanımlı dönüştürmeler desteklen Count yerine IsEmpty tercih edin + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. 'Enumerable.Any()' metodunu çağırmak yerine hangisi mevcutsa 'IsEmpty', 'Count' veya 'Length' özelliklerinden birini kullanmayı tercih edin. Amaç daha kolay anlaşılır ve 'Enumerable.Any()' genişletme metoduna göre daha yüksek performans sunar. @@ -2258,6 +2328,26 @@ Genel türlerde genişletme ve kullanıcı tanımlı dönüştürmeler desteklen Hem kolay anlaşılırlık hem de performans için 'Length' değerini 'Any()' yerine 0 ile karşılaştırmayı tercih edin + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream', ilk bağımsız değişken olarak 'Memory<Byte>' alan bir 'ReadAsync' aşırı yüklemesine ve ilk bağımsız değişken olarak 'ReadOnlyMemory<Byte>' alan bir 'WriteAsync' aşırı yüklemesine sahip. Daha verimli olan bellek tabanlı aşırı yüklemeleri çağırmayı tercih edin. @@ -2294,13 +2384,13 @@ Genel türlerde genişletme ve kullanıcı tanımlı dönüştürmeler desteklen - StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. - StringBuilder.Append ve StringBuilder.Insert, System.String'in ötesinde birden çok türe yönelik aşırı yükleme sağlar. Mümkün olduğunda, ToString() ve dize tabanlı aşırı yüklemeyi kullanmak yerine kesin tür belirtilmiş aşırı yüklemeleri tercih edin. + StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)). + StringBuilder.Append ve StringBuilder.Insert, System.String'in ötesinde birden çok türe yönelik aşırı yükleme sağlar. Mümkün olduğunda, ToString() ve dize tabanlı aşırı yüklemeyi kullanmak yerine kesin tür belirtilmiş aşırı yüklemeleri tercih edin. - Remove the ToString call in order to use a strongly-typed StringBuilder overload - Kesin tür belirtilmiş bir StringBuilder aşırı yüklemesi kullanmak için ToString çağrısını kaldırın + Prefer strongly-typed StringBuilder overload + Kesin tür belirtilmiş bir StringBuilder aşırı yüklemesi kullanmak için ToString çağrısını kaldırın @@ -2308,6 +2398,11 @@ Genel türlerde genişletme ve kullanıcı tanımlı dönüştürmeler desteklen ToString çağrısını kaldırın + + Use StringBuilder.Append(char, int) overload + Use StringBuilder.Append(char, int) overload + + Prefer strongly-typed Append and Insert method overloads on StringBuilder StringBuilder'da kesin tür belirtilmiş Append ve Insert metodu aşırı yüklemelerini tercih edin diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 68f722d64e4f..e33938817ac6 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -237,6 +237,26 @@ Dispose 方法应调用 SuppressFinalize + + Collapse into single Path.{0} operation + Collapse into single Path.{0} operation + + + + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + + + + Multiple consecutive Path.{0} operations can be collapsed into a single operation + Multiple consecutive Path.{0} operations can be collapsed into a single operation + + + + Collapse consecutive Path.Combine or Path.Join operations + Collapse consecutive Path.Combine or Path.Join operations + + , , @@ -1008,6 +1028,21 @@ Enumerable.OfType<T> 使用的泛型类型检查(C# 'is' operator/IL 'isin 使用服务 SAS 而不是帐户 SAS 实现精细访问控制和容器级访问策略 + + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + + + + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + + + + Do not use 'AsParallel' in 'foreach' + Do not use 'AsParallel' in 'foreach' + + Do Not Use Broken Cryptographic Algorithms 不要使用损坏的加密算法 @@ -1148,6 +1183,21 @@ Enumerable.OfType<T> 使用的泛型类型检查(C# 'is' operator/IL 'isin {0} 是不安全的随机数生成器。当需要随机性以确保安全性时,请使用加密的安全随机数生成器。 + + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + + + + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + + + + Cancel Task.Delay after Task.WhenAny completes + Cancel Task.Delay after Task.WhenAny completes + + Do not use obsolete key derivation function 请勿使用已过时的密钥派生功能 @@ -2238,6 +2288,26 @@ Enumerable.OfType<T> 使用的泛型类型检查(C# 'is' operator/IL 'isin 最好使用 "IsEmpty" (而不是 "Count") + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. 首选使用可用的 'IsEmpty'、'Count' 或 'Length' 属性,而不是调用 'Enumerable.Any()'。意向更清晰,其性能高于使用 'Enumerable.Any() 扩展方法。 @@ -2258,6 +2328,26 @@ Enumerable.OfType<T> 使用的泛型类型检查(C# 'is' operator/IL 'isin 为了清楚起见和提高性能,首选将 'Length'与 0 进行比较,而不是使用 'Any()'。 + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. "Stream" 有一个将 "Memory<Byte>" 作为第一个参数的 "ReadAsync" 重载和一个将 "Memory<Byte>" 作为第一个参数的 "WriteAsync" 重载。首选调用基于内存的重载,它们的效率更高。 @@ -2294,13 +2384,13 @@ Enumerable.OfType<T> 使用的泛型类型检查(C# 'is' operator/IL 'isin - StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. - StringBuilder.Append 和 StringBuilder.Insert 为 System.String 之外的多种类型提供重载。在可能情况下,尽量使用强类型重载而非使用 ToString() 和基于字符串的重载。 + StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)). + StringBuilder.Append 和 StringBuilder.Insert 为 System.String 之外的多种类型提供重载。在可能情况下,尽量使用强类型重载而非使用 ToString() 和基于字符串的重载。 - Remove the ToString call in order to use a strongly-typed StringBuilder overload - 删除 ToString 调用以使用强类型 StringBuilder 重载 + Prefer strongly-typed StringBuilder overload + 删除 ToString 调用以使用强类型 StringBuilder 重载 @@ -2308,6 +2398,11 @@ Enumerable.OfType<T> 使用的泛型类型检查(C# 'is' operator/IL 'isin 删除 ToString 调用 + + Use StringBuilder.Append(char, int) overload + Use StringBuilder.Append(char, int) overload + + Prefer strongly-typed Append and Insert method overloads on StringBuilder 最好使用 StringBuilder 的强类型 Append 和 Insert 方法重载 diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 43db515c0d4b..e2ebc14d4c99 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -237,6 +237,26 @@ Dispose 方法應該呼叫 SuppressFinalize + + Collapse into single Path.{0} operation + Collapse into single Path.{0} operation + + + + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + When multiple Path.Combine or Path.Join operations are nested, they can be collapsed into a single operation for better performance and readability. + + + + Multiple consecutive Path.{0} operations can be collapsed into a single operation + Multiple consecutive Path.{0} operations can be collapsed into a single operation + + + + Collapse consecutive Path.Combine or Path.Join operations + Collapse consecutive Path.Combine or Path.Join operations + + , , @@ -1008,6 +1028,21 @@ Enumerable.OfType<T> 使用的一般型別檢查 (C# 'is' operator/IL 'isi 請使用服務 SAS 而非帳戶 SAS,以執行微調的存取控制和容器層級存取原則 + + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + Using 'AsParallel()' directly in a 'foreach' loop has no effect. The 'foreach' statement iterates serially through the collection regardless. To parallelize LINQ operations, call 'AsParallel()' earlier in the query chain before other LINQ operators. To parallelize the loop itself, use 'Parallel.ForEach' instead. + + + + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + Using 'AsParallel()' directly in a 'foreach' loop has no effect and the loop is not parallelized + + + + Do not use 'AsParallel' in 'foreach' + Do not use 'AsParallel' in 'foreach' + + Do Not Use Broken Cryptographic Algorithms 請勿使用損壞的密碼編譯演算法 @@ -1148,6 +1183,21 @@ Enumerable.OfType<T> 使用的一般型別檢查 (C# 'is' operator/IL 'isi {0} 是不安全的亂數產生器。當安全性需要隨機性時,請使用密碼編譯安全性亂數產生器。 + + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + When Task.Delay is used with Task.WhenAny to implement a timeout, the timer created by Task.Delay continues to run even after WhenAny completes, wasting resources. If your target framework supports Task.WaitAsync, use that instead as it has built-in timeout support without leaving timers running. Otherwise, pass a CancellationToken to Task.Delay that can be canceled when the operation completes. + + + + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources + + + + Cancel Task.Delay after Task.WhenAny completes + Cancel Task.Delay after Task.WhenAny completes + + Do not use obsolete key derivation function 請勿使用已淘汰的金鑰衍生函數 @@ -2238,6 +2288,26 @@ Enumerable.OfType<T> 使用的一般型別檢查 (C# 'is' operator/IL 'isi 建議使用 IsEmpty,而不要使用 Count + + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + JsonDocument implements IDisposable and needs to be properly disposed. When only the RootElement is needed, prefer JsonElement.Parse which doesn't require disposal. + + + + Use 'JsonElement.Parse' + Use 'JsonElement.Parse' + + + + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks + + + + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + Prefer JsonElement.Parse over JsonDocument.Parse().RootElement + + Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rather than calling 'Enumerable.Any()'. The intent is clearer and it is more performant than using 'Enumerable.Any()' extension method. 偏好使用 'IsEmpty'、'Count' 或 'Length' 屬性 (以可用者為准),而不是呼叫 'Enumerable.Any()'。意圖更清楚,而且比使用 'Enumerable.Any()' 擴充方法更具效能。 @@ -2258,6 +2328,26 @@ Enumerable.OfType<T> 使用的一般型別檢查 (C# 'is' operator/IL 'isi 為了清楚明瞭和為了提升效能,偏好比較 'Length' 與 0,而不是使用 'Any()' + + Change to '{0}' + Change to '{0}' + + + + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance. + + + + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + Parameter '{0}' can be declared as '{1}' instead of as '{2}' + + + + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' + + 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. 'Stream' 具有採用 'Memory<Byte>' 作為第一個引數的 'ReadAsync' 多載,以及採用 'ReadOnlyMemory<Byte>' 作為第一個引數的 'WriteAsync' 多載。建議呼叫採用記憶體的多載,較有效率。 @@ -2294,13 +2384,13 @@ Enumerable.OfType<T> 使用的一般型別檢查 (C# 'is' operator/IL 'isi - StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. - StringBuilder.Append 與 StringBuilder.Insert 可為 System.String 以外的多種類型提供多載。建議盡可能使用強型別多載,而非 ToString() 與字串式多載。 + StringBuilder.Append and StringBuilder.Insert provide overloads for multiple types beyond System.String. When possible, prefer the strongly-typed overloads over using ToString() and the string-based overload. Additionally, prefer Append(char, int) over Append(new string(char, int)). + StringBuilder.Append 與 StringBuilder.Insert 可為 System.String 以外的多種類型提供多載。建議盡可能使用強型別多載,而非 ToString() 與字串式多載。 - Remove the ToString call in order to use a strongly-typed StringBuilder overload - 移除 ToString 呼叫,以使用強型別 StringBuilder 多載 + Prefer strongly-typed StringBuilder overload + 移除 ToString 呼叫,以使用強型別 StringBuilder 多載 @@ -2308,6 +2398,11 @@ Enumerable.OfType<T> 使用的一般型別檢查 (C# 'is' operator/IL 'isi 移除 ToString 呼叫 + + Use StringBuilder.Append(char, int) overload + Use StringBuilder.Append(char, int) overload + + Prefer strongly-typed Append and Insert method overloads on StringBuilder 建議在 StringBuilder 上使用強型別 Append 及 Insert 方法多載 diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 84d171e834bd..beb472935026 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -12,13 +12,13 @@ Design: CA2210, CA1000-CA1070 Globalization: CA2101, CA1300-CA1311 Mobility: CA1600-CA1601 -Performance: HA, CA1800-CA1875 +Performance: HA, CA1800-CA1877 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5405 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2265 Naming: CA1700-CA1727 Interoperability: CA1400-CA1422 -Maintainability: CA1500-CA1515 -Reliability: CA9998-CA9999, CA2000-CA2025 +Maintainability: CA1500-CA1517 +Reliability: CA9998-CA9999, CA2000-CA2027 Documentation: CA1200-CA1200 # Microsoft CodeAnalysis API rules diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/WellKnownTypeNames.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/WellKnownTypeNames.cs index a733c059885b..c17ecb225358 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/WellKnownTypeNames.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/WellKnownTypeNames.cs @@ -276,6 +276,7 @@ internal static class WellKnownTypeNames public const string SystemLinqEnumerable = "System.Linq.Enumerable"; public const string SystemLinqExpressionsExpression1 = "System.Linq.Expressions.Expression`1"; public const string SystemLinqIOrderedEnumerable1 = "System.Linq.IOrderedEnumerable`1"; + public const string SystemLinqParallelEnumerable = "System.Linq.ParallelEnumerable"; public const string SystemLinqQueryable = "System.Linq.Queryable"; public const string SystemMarshalByRefObject = "System.MarshalByRefObject"; public const string SystemMemory1 = "System.Memory`1"; @@ -438,6 +439,8 @@ internal static class WellKnownTypeNames public const string SystemSystemException = "System.SystemException"; public const string SystemTextCompositeFormat = "System.Text.CompositeFormat"; public const string SystemTextEncoding = "System.Text.Encoding"; + public const string SystemTextJsonJsonDocument = "System.Text.Json.JsonDocument"; + public const string SystemTextJsonJsonElement = "System.Text.Json.JsonElement"; public const string SystemTextJsonJsonSerializerOptions = "System.Text.Json.JsonSerializerOptions"; public const string SystemTextJsonJsonSerializer = "System.Text.Json.JsonSerializer"; public const string SystemTextRegularExpressionsGroup = "System.Text.RegularExpressions.Group"; diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/CSharpUseCrossPlatformIntrinsicsTests.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/CSharpUseCrossPlatformIntrinsicsTests.cs index 31a6c36af1bd..9f3cdaa5d4b9 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/CSharpUseCrossPlatformIntrinsicsTests.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/CSharpUseCrossPlatformIntrinsicsTests.cs @@ -13,6 +13,18 @@ namespace Microsoft.CodeQuality.Analyzers.Maintainability.UnitTests public partial class CSharpUseCrossPlatformIntrinsicsTests { + [Fact] + public void DiagnosticDescriptors_HaveCorrectTitleAndDescription() + { + foreach (var rule in Rules) + { + Assert.Equal(RuleId, rule.Id); + Assert.NotEmpty(rule.Title.ToString()); + Assert.NotEmpty(rule.Description.ToString()); + Assert.NotEmpty(rule.MessageFormat.ToString()); + } + } + [Fact] public async Task Fixer_InnerNodeReplacedAsync() { diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Performance/AvoidSingleUseOfLocalJsonSerializerOptionsTests.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Performance/AvoidSingleUseOfLocalJsonSerializerOptionsTests.cs index fad3c65d7cb3..3a7a7a7d0a66 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Performance/AvoidSingleUseOfLocalJsonSerializerOptionsTests.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Performance/AvoidSingleUseOfLocalJsonSerializerOptionsTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Xunit; using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< @@ -820,6 +821,81 @@ Shared Function Serialize(Of T)(values As T()) As String End Function End Class """); + + [Fact] + public Task CS_TopLevelStatements_UseNewOptionsAsArgument_NoWarn() + { + var test = new VerifyCS.Test + { + TestState = + { + OutputKind = OutputKind.ConsoleApplication, + Sources = + { + """ + using System.Text.Json; + + string json = JsonSerializer.Serialize(new[] { 1, 2, 3 }, new JsonSerializerOptions { AllowTrailingCommas = true }); + """ + } + }, + LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp9 + }; + return test.RunAsync(); + } + + [Fact] + public Task CS_TopLevelStatements_UseNewLocalOptionsAsArgument_NoWarn() + { + var test = new VerifyCS.Test + { + TestState = + { + OutputKind = OutputKind.ConsoleApplication, + Sources = + { + """ + using System.Text.Json; + + JsonSerializerOptions options = new() + { + PropertyNameCaseInsensitive = true, + ReadCommentHandling = JsonCommentHandling.Skip + }; + + var output = JsonSerializer.Deserialize("[1,2,3]", options); + """ + } + }, + LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp9 + }; + return test.RunAsync(); + } + + [Fact] + public Task CS_TopLevelStatements_UseNewLocalOptionsAsArgument_Assignment_NoWarn() + { + var test = new VerifyCS.Test + { + TestState = + { + OutputKind = OutputKind.ConsoleApplication, + Sources = + { + """ + using System.Text.Json; + + JsonSerializerOptions options; + options = new JsonSerializerOptions(); + + var output = JsonSerializer.Deserialize("[1,2,3]", options); + """ + } + }, + LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp9 + }; + return test.RunAsync(); + } #endregion } } diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Performance/CollapseMultiplePathOperationsTests.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Performance/CollapseMultiplePathOperationsTests.cs new file mode 100644 index 000000000000..fc3b1a4301b5 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Performance/CollapseMultiplePathOperationsTests.cs @@ -0,0 +1,401 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.CollapseMultiplePathOperationsAnalyzer, + Microsoft.NetCore.CSharp.Analyzers.Performance.CSharpCollapseMultiplePathOperationsFixer>; + +namespace Microsoft.NetCore.Analyzers.Performance.UnitTests +{ + public class CollapseMultiplePathOperationsTests + { + [Fact] + public async Task NoDiagnostic_SingleCombineCall() + { + var csCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = Path.Combine("a", "b"); + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(csCode); + } + + [Fact] + public async Task NoDiagnostic_SingleJoinCall() + { + var csCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = Path.Join("a", "b"); + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(csCode); + } + + [Fact] + public async Task Diagnostic_NestedCombineCalls() + { + var csCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = [|Path.Combine(Path.Combine("a", "b"), "c")|]; + } + } + """; + var fixedCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = Path.Combine("a", "b", "c"); + } + } + """; + await VerifyCS.VerifyCodeFixAsync(csCode, fixedCode); + } + + [Fact] + public async Task Diagnostic_NestedJoinCalls() + { + var csCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = [|Path.Join(Path.Join("a", "b"), "c")|]; + } + } + """; + var fixedCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = Path.Join("a", "b", "c"); + } + } + """; + await VerifyCS.VerifyCodeFixAsync(csCode, fixedCode); + } + + [Fact] + public async Task Diagnostic_DeeplyNestedCombineCalls() + { + var csCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = [|Path.Combine(Path.Combine(Path.Combine("a", "b"), "c"), "d")|]; + } + } + """; + var fixedCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = Path.Combine("a", "b", "c", "d"); + } + } + """; + await VerifyCS.VerifyCodeFixAsync(csCode, fixedCode); + } + + [Fact] + public async Task Diagnostic_FullyQualifiedName() + { + var csCode = """ + public class Test + { + public void M() + { + string path = [|System.IO.Path.Combine(System.IO.Path.Combine("a", "b"), "c")|]; + } + } + """; + var fixedCode = """ + public class Test + { + public void M() + { + string path = System.IO.Path.Combine("a", "b", "c"); + } + } + """; + await VerifyCS.VerifyCodeFixAsync(csCode, fixedCode); + } + + [Fact] + public async Task NoDiagnostic_DifferentMethods() + { + var csCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = Path.Combine(Path.Join("a", "b"), "c"); + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(csCode); + } + + [Fact] + public async Task Diagnostic_WithMultipleArgumentsInInnerCall() + { + var csCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = [|Path.Combine(Path.Combine("a", "b", "c"), "d")|]; + } + } + """; + var fixedCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = Path.Combine("a", "b", "c", "d"); + } + } + """; + await VerifyCS.VerifyCodeFixAsync(csCode, fixedCode); + } + + [Fact] + public async Task Diagnostic_MultipleNestedLevels() + { + var csCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = [|Path.Combine(Path.Combine("a", "b"), Path.Combine("c", "d"))|]; + } + } + """; + var fixedCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = Path.Combine("a", "b", "c", "d"); + } + } + """; + await VerifyCS.VerifyCodeFixAsync(csCode, fixedCode); + } + + [Fact] + public async Task Diagnostic_LargeNumberOfArguments() + { + var csCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = [|Path.Combine( + Path.Combine( + Path.Combine("a", "b"), + Path.Combine("c", "d")), + Path.Combine( + Path.Combine("e", "f"), + Path.Combine("g", "h")))|]; + } + } + """; + var fixedCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = Path.Combine("a", "b", "c", "d", "e", "f", "g", "h"); + } + } + """; + await VerifyCS.VerifyCodeFixAsync(csCode, fixedCode); + } + + [Fact] + public async Task Diagnostic_NestedInMiddlePosition() + { + var csCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = [|Path.Combine("a", Path.Combine("b", "c"), "d")|]; + } + } + """; + var fixedCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = Path.Combine("a", "b", "c", "d"); + } + } + """; + await VerifyCS.VerifyCodeFixAsync(csCode, fixedCode); + } + + [Fact] + public async Task Diagnostic_NestedInLastPosition() + { + var csCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = [|Path.Combine("a", "b", Path.Combine("c", "d"))|]; + } + } + """; + var fixedCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = Path.Combine("a", "b", "c", "d"); + } + } + """; + await VerifyCS.VerifyCodeFixAsync(csCode, fixedCode); + } + + [Fact] + public async Task Diagnostic_WithNonConstArguments() + { + var csCode = """ + using System.IO; + + public class Test + { + private string GetPath() => "path"; + private string MyProperty => "prop"; + + public void M(bool condition) + { + string path = [|Path.Combine(Path.Combine(GetPath(), MyProperty), condition ? "a" : "b")|]; + } + } + """; + var fixedCode = """ + using System.IO; + + public class Test + { + private string GetPath() => "path"; + private string MyProperty => "prop"; + + public void M(bool condition) + { + string path = Path.Combine(GetPath(), MyProperty, condition ? "a" : "b"); + } + } + """; + await VerifyCS.VerifyCodeFixAsync(csCode, fixedCode); + } + + [Fact] + public async Task NoDiagnostic_JoinNestedInCombine() + { + var csCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = Path.Join(Path.Combine("a", "b"), "c"); + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(csCode); + } + + [Fact] + public async Task NoDiagnostic_CombineNestedInJoinMultipleLevels() + { + var csCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = Path.Combine(Path.Join(Path.Combine("a", "b"), "c"), "d"); + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(csCode); + } + + [Fact] + public async Task NoDiagnostic_DeeplyMixedCombineAndJoinNesting() + { + var csCode = """ + using System.IO; + + public class Test + { + public void M() + { + string path = Path.Combine(Path.Join(Path.Combine(Path.Combine("a", "b"), "c"), "d"), "e"); + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(csCode); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotUseAsParallelInForEachLoopTests.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotUseAsParallelInForEachLoopTests.cs new file mode 100644 index 000000000000..2f24cc06480e --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Performance/DoNotUseAsParallelInForEachLoopTests.cs @@ -0,0 +1,244 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Test.Utilities; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.DoNotUseAsParallelInForEachLoopAnalyzer, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.DoNotUseAsParallelInForEachLoopAnalyzer, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + +namespace Microsoft.NetCore.Analyzers.Performance.UnitTests +{ + public class DoNotUseAsParallelInForEachLoopTests + { + [Fact] + public Task CSharp_AsParallelDirectlyInForeachLoop_ReportsDiagnostic() + { + const string code = """ + using System.Collections.Generic; + using System.Linq; + + public class Tests + { + public void M() + { + var list = new List { 1, 2, 3 }; + foreach (var item in [|list.AsParallel()|]) + { + } + } + } + """; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task VB_AsParallelDirectlyInForeachLoop_ReportsDiagnostic() + { + const string code = """ + Imports System.Collections.Generic + Imports System.Linq + + Public Class Tests + Public Sub M() + Dim list = New List(Of Integer) From {1, 2, 3} + For Each item In [|list.AsParallel()|] + Next + End Sub + End Class + """; + + return VerifyVB.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task CSharp_AsParallelWithSelectInForeachLoop_ReportsDiagnostic() + { + const string code = """ + using System.Collections.Generic; + using System.Linq; + + public class Tests + { + public void M() + { + var list = new List { 1, 2, 3 }; + foreach (var item in [|list.Select(x => x * 2).AsParallel()|]) + { + } + } + } + """; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task CSharp_AsParallelWithWhereInForeachLoop_ReportsDiagnostic() + { + const string code = """ + using System.Collections.Generic; + using System.Linq; + + public class Tests + { + public void M() + { + var list = new List { 1, 2, 3 }; + foreach (var item in [|list.Where(x => x > 1).AsParallel()|]) + { + } + } + } + """; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task CSharp_AsParallelEarlyInChain_NoDiagnostic() + { + const string code = """ + using System.Collections.Generic; + using System.Linq; + + public class Tests + { + public void M() + { + var list = new List { 1, 2, 3 }; + var result = list.AsParallel().Select(x => x * 2).ToList(); + } + } + """; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task CSharp_AsParallelWithToList_NoDiagnostic() + { + const string code = """ + using System.Collections.Generic; + using System.Linq; + + public class Tests + { + public void M() + { + var list = new List { 1, 2, 3 }; + var result = list.Select(x => x * 2).AsParallel().ToList(); + } + } + """; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task CSharp_AsParallelWithToArray_NoDiagnostic() + { + const string code = """ + using System.Collections.Generic; + using System.Linq; + + public class Tests + { + public void M() + { + var list = new List { 1, 2, 3 }; + var result = list.Select(x => x * 2).AsParallel().ToArray(); + } + } + """; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task CSharp_RegularForeachWithoutAsParallel_NoDiagnostic() + { + const string code = """ + using System.Collections.Generic; + using System.Linq; + + public class Tests + { + public void M() + { + var list = new List { 1, 2, 3 }; + foreach (var item in list) + { + } + } + } + """; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task CSharp_AsParallelOnArrayDirectlyInForeachLoop_ReportsDiagnostic() + { + const string code = """ + using System.Collections.Generic; + using System.Linq; + + public class Tests + { + public void M() + { + var array = new int[] { 1, 2, 3 }; + foreach (var item in [|array.AsParallel()|]) + { + } + } + } + """; + + return VerifyCS.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task VB_AsParallelWithSelectInForeachLoop_ReportsDiagnostic() + { + const string code = """ + Imports System.Collections.Generic + Imports System.Linq + + Public Class Tests + Public Sub M() + Dim list = New List(Of Integer) From {1, 2, 3} + For Each item In [|list.Select(Function(x) x * 2).AsParallel()|] + Next + End Sub + End Class + """; + + return VerifyVB.VerifyAnalyzerAsync(code); + } + + [Fact] + public Task VB_AsParallelWithToList_NoDiagnostic() + { + const string code = """ + Imports System.Collections.Generic + Imports System.Linq + + Public Class Tests + Public Sub M() + Dim list = New List(Of Integer) From {1, 2, 3} + Dim result = list.Select(Function(x) x * 2).AsParallel().ToList() + End Sub + End Class + """; + + return VerifyVB.VerifyAnalyzerAsync(code); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpanTests.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpanTests.cs new file mode 100644 index 000000000000..2e2b198e5a05 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpanTests.cs @@ -0,0 +1,2209 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Testing; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.PreferReadOnlySpanOverSpanAnalyzer, + Microsoft.NetCore.Analyzers.Performance.PreferReadOnlySpanOverSpanFixer>; + +namespace Microsoft.NetCore.Analyzers.Performance.UnitTests +{ + public class PreferReadOnlySpanOverSpanTests + { + [Fact] + public async Task SpanParameter_NotWritten_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|data|]) + { + var length = data.Length; + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan data) + { + var length = data.Length; + } + } + """); + } + + [Fact] + public async Task SpanParameter_ReassignedToOwnSliceLoop_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|data|]) + { + while (!data.IsEmpty) + { + int idx = data.IndexOf((byte)0); + if (idx < 0) + break; + data = data.Slice(idx + 1); + } + _ = data.Length; + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan data) + { + while (!data.IsEmpty) + { + int idx = data.IndexOf((byte)0); + if (idx < 0) + break; + data = data.Slice(idx + 1); + } + _ = data.Length; + } + } + """); + } + + [Fact] + public async Task SpanParameter_ReassignedToOwnSliceLoopThenConsumedByWritableApi_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private static void Consume(Span span) { } + + private void M(Span data) + { + while (!data.IsEmpty) + { + int idx = data.IndexOf((byte)1); + if (idx < 0) + break; + data = data.Slice(idx + 1); + } + // Passed to writable Span API makes parameter unsafe for conversion + Consume(data); + } + } + """); + } + + [Fact] + public async Task SpanParameter_ReassignedToOwnSliceLoopThenWritten_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + while (!data.IsEmpty) + { + int idx = data.IndexOf((byte)2); + if (idx < 0) + break; + data = data.Slice(idx + 1); + } + // Write to the parameter after slicing keeps it writable-only + if (!data.IsEmpty) + { + data[0] = 42; + } + } + } + """); + } + + [Fact] + public async Task SpanParameter_PassedToMethodReadOnly_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|data|]) + { + Helper(data); + } + + private void Helper(ReadOnlySpan data) + { + var length = data.Length; + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan data) + { + Helper(data); + } + + private void Helper(ReadOnlySpan data) + { + var length = data.Length; + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedInForEachLoop_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|data|]) + { + foreach (var b in data) + { + Console.WriteLine(b); + } + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan data) + { + foreach (var b in data) + { + Console.WriteLine(b); + } + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedInForLoop_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|data|]) + { + for (int i = 0; i < data.Length; i++) + { + byte b = data[i]; + Console.WriteLine(b); + } + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan data) + { + for (int i = 0; i < data.Length; i++) + { + byte b = data[i]; + Console.WriteLine(b); + } + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedInLinqQuery_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + using System.Linq; + + class C + { + private void M(Span [|data|]) + { + var sum = data.ToArray().Sum(); + } + } + """, """ + using System; + using System.Linq; + + class C + { + private void M(ReadOnlySpan data) + { + var sum = data.ToArray().Sum(); + } + } + """); + } + + [Fact] + public async Task SpanParameter_ReadThroughIndexer_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|data|]) + { + var first = data[0]; + var last = data[data.Length - 1]; + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan data) + { + var first = data[0]; + var last = data[data.Length - 1]; + } + } + """); + } + + [Fact] + public async Task SpanParameter_InTernaryExpression_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|data|], bool condition) + { + var length = condition ? data.Length : 0; + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan data, bool condition) + { + var length = condition ? data.Length : 0; + } + } + """); + } + + [Fact] + public async Task SpanParameter_PassedToGenericMethod_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|data|]) + { + Helper(data); + } + + private void Helper(ReadOnlySpan data) + { + Console.WriteLine(data.Length); + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan data) + { + Helper(data); + } + + private void Helper(ReadOnlySpan data) + { + Console.WriteLine(data.Length); + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedInReturnStatement_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private ReadOnlySpan M(Span [|data|]) + { + return data; + } + } + """, """ + using System; + + class C + { + private ReadOnlySpan M(ReadOnlySpan data) + { + return data; + } + } + """); + } + + [Fact] + public async Task SpanParameter_ConditionalAccess_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|data|]) + { + var result = data.IsEmpty ? 0 : data[0]; + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan data) + { + var result = data.IsEmpty ? 0 : data[0]; + } + } + """); + } + + [Fact] + public async Task SpanParameter_MultipleReferences_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|data|]) + { + Console.WriteLine(data.Length); + Console.WriteLine(data[0]); + Console.WriteLine(data[1]); + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan data) + { + Console.WriteLine(data.Length); + Console.WriteLine(data[0]); + Console.WriteLine(data[1]); + } + } + """); + } + + [Fact] + public async Task SpanParameter_CopyTo_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|data|]) + { + Span destination = stackalloc byte[10]; + data.CopyTo(destination); + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan data) + { + Span destination = stackalloc byte[10]; + data.CopyTo(destination); + } + } + """); + } + + [Fact] + public async Task SpanParameter_TryCopyTo_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|data|]) + { + Span destination = stackalloc byte[10]; + data.TryCopyTo(destination); + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan data) + { + Span destination = stackalloc byte[10]; + data.TryCopyTo(destination); + } + } + """); + } + + [Fact] + public async Task SpanParameter_ReturnedAsReadOnlySpan_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private ReadOnlySpan M(Span [|data|]) + { + return data; // Returning as readonly + } + } + """, """ + using System; + + class C + { + private ReadOnlySpan M(ReadOnlySpan data) + { + return data; // Returning as readonly + } + } + """); + } + + [Fact] + public async Task MemoryParameter_StoredInReadOnlyMemoryField_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private ReadOnlyMemory _field; + + private void M(Memory [|data|]) + { + _field = data; + } + } + """, """ + using System; + + class C + { + private ReadOnlyMemory _field; + + private void M(ReadOnlyMemory data) + { + _field = data; + } + } + """); + } + + [Fact] + public async Task MemoryParameter_StoredInReadOnlyMemoryArray_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Memory [|data|]) + { + var array = new ReadOnlyMemory[] { data }; + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlyMemory data) + { + var array = new ReadOnlyMemory[] { data }; + } + } + """); + } + + [Fact] + public async Task SpanParameter_StoredInReadOnlySpanProperty_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + ref struct Container + { + public ReadOnlySpan Data { get; set; } + } + + class C + { + private void M(Span [|data|]) + { + var container = new Container { Data = data }; + } + } + """, """ + using System; + + ref struct Container + { + public ReadOnlySpan Data { get; set; } + } + + class C + { + private void M(ReadOnlySpan data) + { + var container = new Container { Data = data }; + } + } + """); + } + + [Fact] + public async Task MemoryParameter_PassedToMethodExpectingReadOnly_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Memory [|data|]) + { + Helper(data); + } + + private void Helper(ReadOnlyMemory data) + { + Console.WriteLine(data.Length); + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlyMemory data) + { + Helper(data); + } + + private void Helper(ReadOnlyMemory data) + { + Console.WriteLine(data.Length); + } + } + """); + } + + [Fact] + public async Task MemoryParameter_NotWritten_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Memory data) + { + var span = data.Span; // .Span stores result in local - can't verify safe usage + var length = span.Length; + } + } + """); + } + + [Fact] + public async Task SpanParameter_Written_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + data[0] = 1; + } + } + """); + } + + [Fact] + public async Task SpanParameter_WrittenViaIndexer_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + for (int i = 0; i < data.Length; i++) + { + data[i] = i; + } + } + } + """); + } + + [Fact] + public async Task SpanParameter_PassedAsRefParameter_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + Helper(ref data); + } + + private void Helper(ref Span data) + { + data[0] = 1; + } + } + """); + } + + [Fact] + public async Task PublicMethod_DefaultConfig_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + public class C + { + public void M(Span data) + { + var length = data.Length; + } + } + """); + } + + [Fact] + public async Task OverrideMethod_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + public class Base + { + public virtual void M(Span data) { } + } + + public class Derived : Base + { + public override void M(Span data) + { + var length = data.Length; + } + } + """); + } + + [Fact] + public async Task InterfaceImplementation_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + public interface I + { + void M(Span data); + } + + public class C : I + { + public void M(Span data) + { + var length = data.Length; + } + } + """); + } + + [Fact] + public async Task ReadOnlySpanParameter_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(ReadOnlySpan data) + { + var length = data.Length; + } + } + """); + } + + [Fact] + public async Task SpanParameter_SlicedButNotWritten_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + var slice = data.Slice(0, 10); + var length = slice.Length; + } + } + """); + } + + [Fact] + public async Task MultipleParameters_MixedUsage() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|readOnlyData|], Span writableData) + { + var length = readOnlyData.Length; + writableData[0] = 1; + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan readOnlyData, Span writableData) + { + var length = readOnlyData.Length; + writableData[0] = 1; + } + } + """); + } + + [Fact] + public async Task SpanParameter_PassedToWritableSpanMethod_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + Helper(data); + } + + private void Helper(Span data) + { + data[0] = 1; + } + } + """); + } + + [Fact] + public async Task SpanParameter_CopiedToLocal_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + var copy = data; + Console.WriteLine(copy.Length); + } + } + """); + } + + [Fact] + public async Task SpanParameter_PassedAsOutArgument_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + Helper(out data); + } + + private void Helper(out Span data) + { + data = default; + } + } + """); + } + + [Fact] + public async Task MemoryParameter_AccessSpanProperty_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Memory data) + { + var s = data.Span; // .Span stores result in local - can't verify safe usage + Console.WriteLine(s[0]); + } + } + """); + } + + [Fact] + public async Task MemoryParameter_SliceAndRead_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Memory data) + { + var slice = data.Slice(1, 5); // Slice stored in local - can't verify safe usage + Console.WriteLine(slice.Length); + } + } + """); + } + + [Fact] + public async Task SpanParameter_RangeOperator_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + var slice = data[1..5]; + Console.WriteLine(slice.Length); + } + } + """); + } + + [Fact] + public async Task SpanParameter_RangeFromEndOperator_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + var slice = data[^3..^1]; + Console.WriteLine(slice.Length); + } + } + """); + } + + [Fact] + public async Task SpanParameter_ReturnedFromMethod_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private Span M(Span data) + { + return data; + } + } + """); + } + + [Fact] + public async Task SpanParameter_ExpressionReturnedFromMethod_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private Span M(Span data) => data; + } + """); + } + + [Fact] + public async Task SpanParameter_StoredInRefParameter_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + + using System; + + class C + { + private void M(Span data, ref Span output) + { + output = data; + } + } + + """); + } + + [Fact] + public async Task SpanParameter_StoredInOutParameter_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data, out Span output) + { + output = data; + } + } + """); + } + + [Fact] + public async Task MemoryParameter_StoredInField_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private Memory _field; + + private void M(Memory data) + { + _field = data; + } + } + """); + } + + [Fact] + public async Task MemoryParameter_StoredInArray_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Memory data) + { + var array = new Memory[] { data }; + } + } + """); + } + + [Fact] + public async Task SpanParameter_StoredInProperty_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + ref struct Container + { + public Span Data { get; set; } + } + + class C + { + private void M(Span data) + { + var container = new Container { Data = data }; + } + } + """); + } + + [Fact] + public async Task SpanParameter_MultipleReferencesOneWrite_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + Console.WriteLine(data.Length); + Console.WriteLine(data[0]); + data[1] = 5; // Write + Console.WriteLine(data[2]); + } + } + """); + } + + [Fact] + public async Task SpanParameter_PassedAsRefArgument_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class Test + { + private static void IntroSort(Span keys, int depthLimit) + { + if (keys.Length == 2) + { + SwapIfGreater(ref keys[0], ref keys[1]); + return; + } + } + + private static void SwapIfGreater(ref int a, ref int b) + { + if (a > b) + { + int temp = a; + a = b; + b = temp; + } + } + } + """); + } + + [Fact] + public async Task SpanParameter_RefVariableDeclaration_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class Test + { + private void Method(Span data) + { + // Taking a ref to an indexed element requires writability + ref int firstElement = ref data[0]; + firstElement = 42; + } + } + """); + } + + [Fact] + public async Task SpanParameter_RefReturn_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class Test + { + // Method returns ref, so parameter must be writable + private ref int GetFirst(Span data) + { + return ref data[0]; + } + } + """); + } + + [Fact] + public async Task MemoryParameter_PassedToMethodViaSlice_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + using System.Threading; + using System.Threading.Tasks; + + class Test + { + // buffer is passed via Slice to a method that expects writable Memory + internal async ValueTask ReadBlockAsyncInternal(Memory buffer, CancellationToken cancellationToken) + { + int n = 0, i; + do + { + i = await ReadAsyncInternal(buffer.Slice(n), cancellationToken).ConfigureAwait(false); + n += i; + } while (i > 0 && n < buffer.Length); + + return n; + } + + private ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken) + { + // Implementation that writes to buffer + if (buffer.Length > 0) + buffer.Span[0] = 'x'; + return ValueTask.FromResult(buffer.Length); + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedInFixed_NoDiagnostic() + { + var source = """ + using System; + + class Test + { + private unsafe void ProcessData(Span data) + { + fixed (byte* ptr = data) + { + // Use pointer + *ptr = 42; + } + } + } + """; + + await new VerifyCS.Test + { + TestCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + SolutionTransforms = + { + (solution, projectId) => solution.WithProjectCompilationOptions(projectId, + ((CSharpCompilationOptions)solution.GetProject(projectId)!.CompilationOptions!).WithAllowUnsafe(true)) + } + }.RunAsync(); + } + + [Fact] + public async Task SpanParameter_IndexerWithDecrementOperator_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class Test + { + private void DecrementLast(Span buffer) + { + int length = buffer.Length; + buffer[length - 1]--; + } + } + """); + } + + [Fact] + public async Task SpanParameter_SliceAssignedToLocalAndWritten_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class Test + { + internal static ReadOnlySpan CopyRuntimeTypeHandles(int[] inHandles, Span stackScratch) + { + if (inHandles == null || inHandles.Length == 0) + { + return default; + } + + Span outHandles = inHandles.Length <= stackScratch.Length ? + stackScratch.Slice(0, inHandles.Length) : + new IntPtr[inHandles.Length]; + for (int i = 0; i < inHandles.Length; i++) + { + outHandles[i] = (IntPtr)inHandles[i]; + } + return outHandles; + } + } + """); + } + + [Fact] + public async Task SpanParameter_ChainedSliceWithIncrementOperator_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class Test + { + private void Method(Span span) + { + span.Slice(1, 4).Slice(1, 2)[0]++; + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedInSwitchExpression_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private int M(Span [|data|], int selector) + { + return selector switch + { + 0 => data.Length, + 1 => data[0], + _ => data.IsEmpty ? 0 : data[data.Length - 1] + }; + } + } + """, """ + using System; + + class C + { + private int M(ReadOnlySpan data, int selector) + { + return selector switch + { + 0 => data.Length, + 1 => data[0], + _ => data.IsEmpty ? 0 : data[data.Length - 1] + }; + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedInPatternMatching_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private bool M(Span [|data|]) + { + return data switch + { + { Length: > 0 } => data[0] == 42, + _ => false + }; + } + } + """, """ + using System; + + class C + { + private bool M(ReadOnlySpan data) + { + return data switch + { + { Length: > 0 } => data[0] == 42, + _ => false + }; + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedInNullCoalescingOperator_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private int M(Span data, bool condition) + { + Span local = condition ? data : default; + return local.Length; + } + } + """); + } + + [Fact] + public async Task SpanParameter_MultipleParametersSameType_OnlyReadOnlyOnesMarked() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|source|], Span destination) + { + source.CopyTo(destination); + destination[0] = 42; + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan source, Span destination) + { + source.CopyTo(destination); + destination[0] = 42; + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedInConditionalExpression_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private ReadOnlySpan M(Span data1, Span data2, bool condition) + { + return condition ? data1 : data2; + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedInInterpolatedString_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private string M(Span [|data|]) + { + return $"Length: {data.Length}, First: {(data.IsEmpty ? 0 : data[0])}"; + } + } + """, """ + using System; + + class C + { + private string M(ReadOnlySpan data) + { + return $"Length: {data.Length}, First: {(data.IsEmpty ? 0 : data[0])}"; + } + } + """); + } + + [Fact] + public async Task MemoryParameter_OnlyAccessedViaLength_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private int M(Memory [|data|]) + { + return data.Length + data.IsEmpty.GetHashCode(); + } + } + """, """ + using System; + + class C + { + private int M(ReadOnlyMemory data) + { + return data.Length + data.IsEmpty.GetHashCode(); + } + } + """); + } + + [Fact] + public async Task SpanParameter_ComparedWithOtherSpan_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private bool M(Span [|data1|], Span [|data2|]) + { + return data1.SequenceEqual(data2); + } + } + """, """ + using System; + + class C + { + private bool M(ReadOnlySpan data1, ReadOnlySpan data2) + { + return data1.SequenceEqual(data2); + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedInLocalFunction_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + LocalFunc(data); + + static void LocalFunc(Span d) + { + Console.WriteLine(d.Length); + } + } + } + """); + } + + [Fact] + public async Task SpanParameter_PassedToStaticMethodWithReadOnlyOverload_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + Helper(data); + } + + private static void Helper(ReadOnlySpan data) => Console.WriteLine(data.Length); + private static void Helper(Span data) => throw new NotImplementedException(); + } + """); + } + + [Fact] + public async Task SpanParameter_WithDefaultParameter_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data, int offset = 0) + { + var slice = data.Slice(offset); + var length = slice.Length; + } + } + """); + } + + [Fact] + public async Task SpanParameter_NullableReferenceTypeContext_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + #nullable enable + using System; + + class C + { + private void M(Span [|data|]) + { + foreach (var s in data) + { + Console.WriteLine(s?.Length ?? 0); + } + } + } + """, """ + #nullable enable + using System; + + class C + { + private void M(ReadOnlySpan data) + { + foreach (var s in data) + { + Console.WriteLine(s?.Length ?? 0); + } + } + } + """); + } + + [Fact] + public async Task SpanParameter_NestedGenericType_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + using System.Collections.Generic; + + class C + { + private void M(Span> [|data|]) + { + var count = data.Length; + } + } + """, """ + using System; + using System.Collections.Generic; + + class C + { + private void M(ReadOnlySpan> data) + { + var count = data.Length; + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedInThrowExpression_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|data|]) + { + _ = data.Length > 0 ? data[0] : throw new InvalidOperationException(); + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan data) + { + _ = data.Length > 0 ? data[0] : throw new InvalidOperationException(); + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedInRecursiveMethod_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private int Sum(Span [|data|]) + { + if (data.IsEmpty) return 0; + return data[0] + Sum(data.Slice(1)); + } + } + """, """ + using System; + + class C + { + private int Sum(ReadOnlySpan data) + { + if (data.IsEmpty) return 0; + return data[0] + Sum(data.Slice(1)); + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedWithMemoryExtensionsIndexOf_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private int M(Span [|data|]) + { + return data.IndexOf((byte)42); + } + } + """, """ + using System; + + class C + { + private int M(ReadOnlySpan data) + { + return data.IndexOf((byte)42); + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedWithMemoryExtensionsContains_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private bool M(Span [|data|]) + { + return data.Contains('x'); + } + } + """, """ + using System; + + class C + { + private bool M(ReadOnlySpan data) + { + return data.Contains('x'); + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedWithStartsWith_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private bool M(Span [|data|], ReadOnlySpan prefix) + { + return data.StartsWith(prefix); + } + } + """, """ + using System; + + class C + { + private bool M(ReadOnlySpan data, ReadOnlySpan prefix) + { + return data.StartsWith(prefix); + } + } + """); + } + + [Fact] + public async Task SpanParameter_AccessedViaExplicitInterfaceCast_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + using System.Collections.Generic; + + class C + { + private void M(Span [|data|]) + { + IEnumerable enumerable = data.ToArray(); + foreach (var b in enumerable) { } + } + } + """, """ + using System; + using System.Collections.Generic; + + class C + { + private void M(ReadOnlySpan data) + { + IEnumerable enumerable = data.ToArray(); + foreach (var b in enumerable) { } + } + } + """); + } + + [Fact] + public async Task SpanParameter_PassedToGenericMethodConstrainedToSpan_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + Helper(data); + } + + private void Helper(Span data) + { + data[0] = default; + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedInUsingStatement_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + var copy = data; // Local copy prevents analysis + Console.WriteLine(copy.Length); + } + } + """); + } + + [Fact] + public async Task SpanParameter_WrittenViaCompoundAssignment_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + if (data.Length > 0) + { + data[0] += 5; + } + } + } + """); + } + + [Fact] + public async Task SpanParameter_WrittenViaPrefixIncrement_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + if (data.Length > 0) + { + ++data[0]; + } + } + } + """); + } + + [Fact] + public async Task SpanParameter_WrittenViaPostfixIncrement_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + if (data.Length > 0) + { + data[0]++; + } + } + } + """); + } + + [Fact] + public async Task SpanParameter_Clear_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + data.Clear(); + } + } + """); + } + + [Fact] + public async Task SpanParameter_Fill_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + data.Fill(0); + } + } + """); + } + + [Fact] + public async Task SpanParameter_Reverse_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + data.Reverse(); + } + } + """); + } + + [Fact] + public async Task SpanParameter_Sort_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Span data) + { + data.Sort(); + } + } + """); + } + + [Fact] + public async Task MemoryParameter_AccessSpanAndWrite_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private void M(Memory data) + { + var span = data.Span; + span[0] = 42; + } + } + """); + } + + [Fact] + public async Task SpanParameter_ImplicitOperatorToReadOnlySpan_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|data|]) + { + ReadOnlySpan ros = data; + Console.WriteLine(ros.Length); + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan data) + { + ReadOnlySpan ros = data; + Console.WriteLine(ros.Length); + } + } + """); + } + + [Fact] + public async Task SpanParameter_UsedInConstructor_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + ref struct SpanWrapper + { + public ReadOnlySpan Data; + + public SpanWrapper(ReadOnlySpan data) + { + Data = data; + } + } + + class C + { + private SpanWrapper M(Span [|data|]) + { + return new SpanWrapper(data); + } + } + """, """ + using System; + + ref struct SpanWrapper + { + public ReadOnlySpan Data; + + public SpanWrapper(ReadOnlySpan data) + { + Data = data; + } + } + + class C + { + private SpanWrapper M(ReadOnlySpan data) + { + return new SpanWrapper(data); + } + } + """); + } + + [Fact] + public async Task SpanParameter_TryCopyToWithoutWrite_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private bool M(Span [|data|]) + { + Span destination = stackalloc byte[10]; + return data.TryCopyTo(destination); + } + } + """, """ + using System; + + class C + { + private bool M(ReadOnlySpan data) + { + Span destination = stackalloc byte[10]; + return data.TryCopyTo(destination); + } + } + """); + } + + [Fact] + public async Task SpanParameter_GetPinnableReference_NoDiagnostic() + { + await VerifyAnalyzerAsync(""" + using System; + + class C + { + private unsafe void M(Span data) + { + ref byte r = ref data.GetPinnableReference(); + byte b = r; + } + } + """); + } + + [Fact] + public async Task SpanParameter_AssignedBackToItself_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private void M(Span [|data|]) + { + data = data.Slice(1); + Console.WriteLine(data.Length); + } + } + """, """ + using System; + + class C + { + private void M(ReadOnlySpan data) + { + data = data.Slice(1); + Console.WriteLine(data.Length); + } + } + """); + } + + [Fact] + public async Task SpanParameter_OverloadedOperatorEquals_ProducesDiagnostic() + { + await VerifyFixerAsync(""" + using System; + + class C + { + private bool M(Span [|data1|], Span [|data2|]) + { + return data1 == data2; // Uses overloaded == operator + } + } + """, """ + using System; + + class C + { + private bool M(ReadOnlySpan data1, ReadOnlySpan data2) + { + return data1 == data2; // Uses overloaded == operator + } + } + """); + } + + [Fact] + public async Task MemoryParameter_PinMethod_ProducesDiagnostic() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + using System.Buffers; + + class C + { + private unsafe void M(Memory [|data|]) + { + using (MemoryHandle handle = data.Pin()) + { + byte* ptr = (byte*)handle.Pointer; + if (ptr != null) + { + *ptr = 42; + } + } + } + } + """, + FixedCode = """ + using System; + using System.Buffers; + + class C + { + private unsafe void M(ReadOnlyMemory data) + { + using (MemoryHandle handle = data.Pin()) + { + byte* ptr = (byte*)handle.Pointer; + if (ptr != null) + { + *ptr = 42; + } + } + } + } + """, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + SolutionTransforms = + { + (solution, projectId) => solution.WithProjectCompilationOptions(projectId, + ((CSharpCompilationOptions)solution.GetProject(projectId)!.CompilationOptions!).WithAllowUnsafe(true)) + } + }.RunAsync(); + } + + private static async Task VerifyAnalyzerAsync( + [StringSyntax("C#-test")] string source, + LanguageVersion languageVersion = LanguageVersion.Default) + { + await new VerifyCS.Test + { + TestCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + LanguageVersion = languageVersion, + }.RunAsync(); + } + + private static async Task VerifyFixerAsync( + [StringSyntax("C#-test")] string source, + [StringSyntax("C#-test")] string fixedSource, + LanguageVersion languageVersion = LanguageVersion.Default) + { + await new VerifyCS.Test + { + TestCode = source, + FixedCode = fixedSource, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + LanguageVersion = languageVersion, + }.RunAsync(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParseTests.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParseTests.cs new file mode 100644 index 000000000000..aed9241165a0 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferJsonElementParseTests.cs @@ -0,0 +1,661 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.PreferJsonElementParse, + Microsoft.NetCore.Analyzers.Runtime.PreferJsonElementParseFixer>; + +namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests +{ + public class PreferJsonElementParseTests + { + [Fact] + public async Task NoDiagnostic_WhenJsonElementParseNotAvailable() + { + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement { } + } + + class Test + { + void M() + { + JsonElement element = JsonDocument.Parse("json").RootElement; + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task Diagnostic_WhenUsingJsonDocumentParseRootElement() + { + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M() + { + JsonElement element = [|JsonDocument.Parse("json").RootElement|]; + } + } + """; + var fixedSource = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M() + { + JsonElement element = JsonElement.Parse("json"); + } + } + """; + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task Diagnostic_WhenUsingJsonDocumentParseRootElement_WithMultipleArguments() + { + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json, JsonDocumentOptions options) => default; + } + + public struct JsonDocumentOptions { } + } + + class Test + { + void M() + { + JsonElement element = [|JsonDocument.Parse("json", default).RootElement|]; + } + } + """; + var fixedSource = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json, JsonDocumentOptions options) => default; + } + + public struct JsonDocumentOptions { } + } + + class Test + { + void M() + { + JsonElement element = JsonElement.Parse("json", default); + } + } + """; + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task NoDiagnostic_WhenJsonDocumentProperlyDisposed_UsingStatement() + { + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M() + { + using (var doc = JsonDocument.Parse("json")) + { + JsonElement element = doc.RootElement; + } + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task NoDiagnostic_WhenJsonDocumentProperlyDisposed_UsingDeclaration() + { + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M() + { + using var doc = JsonDocument.Parse("json"); + JsonElement element = doc.RootElement; + } + } + """; + await new VerifyCS.Test + { + TestCode = source, + LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp8 + }.RunAsync(); + } + + [Fact] + public async Task Diagnostic_InlineAccess_DirectAssignment() + { + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M(string json) + { + var element = [|JsonDocument.Parse(json).RootElement|]; + } + } + """; + var fixedSource = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M(string json) + { + var element = JsonElement.Parse(json); + } + } + """; + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task Diagnostic_ReturnStatement() + { + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + JsonElement M(string json) + { + return [|JsonDocument.Parse(json).RootElement|]; + } + } + """; + var fixedSource = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + JsonElement M(string json) + { + return JsonElement.Parse(json); + } + } + """; + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task Diagnostic_ExpressionBodiedMember() + { + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + JsonElement M(string json) => [|JsonDocument.Parse(json).RootElement|]; + } + """; + var fixedSource = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + JsonElement M(string json) => JsonElement.Parse(json); + } + """; + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task NoDiagnostic_WhenJsonElementParseHasNoMatchingOverload() + { + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + // JsonElement.Parse only has single string parameter, not the overload with options + public static JsonElement Parse(string json) => default; + } + + public struct JsonDocumentOptions { } + } + + class Test + { + void M() + { + // This should NOT produce a diagnostic because JsonElement.Parse doesn't have an overload with JsonDocumentOptions + JsonElement element = JsonDocument.Parse("json", default(JsonDocumentOptions)).RootElement; + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task Diagnostic_WhenChainedWithOtherOperations() + { + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + public JsonValueKind ValueKind => default; + } + + public enum JsonValueKind { } + } + + class Test + { + void M() + { + // Should produce diagnostic even when chained with property access + var kind = [|JsonDocument.Parse("json").RootElement|].ValueKind; + } + } + """; + var fixedSource = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + public JsonValueKind ValueKind => default; + } + + public enum JsonValueKind { } + } + + class Test + { + void M() + { + // Should produce diagnostic even when chained with property access + var kind = JsonElement.Parse("json").ValueKind; + } + } + """; + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task NoDiagnostic_WhenRootElementAccessedViaVariable() + { + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M() + { + // Not the pattern we're looking for - JsonDocument is stored in a variable + var doc = JsonDocument.Parse("json"); + JsonElement element = doc.RootElement; + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task Diagnostic_MultipleParametersWithMatchingOverload() + { + var source = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json, JsonDocumentOptions options) => default; + } + + public struct JsonDocumentOptions { } + } + + class Test + { + void M() + { + JsonElement element = [|JsonDocument.Parse("json", default).RootElement|]; + } + } + """; + var fixedSource = """ + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json, JsonDocumentOptions options) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json, JsonDocumentOptions options) => default; + } + + public struct JsonDocumentOptions { } + } + + class Test + { + void M() + { + JsonElement element = JsonElement.Parse("json", default); + } + } + """; + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task Diagnostic_InLambdaExpression() + { + var source = """ + using System; + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M() + { + Func parser = json => [|JsonDocument.Parse(json).RootElement|]; + } + } + """; + var fixedSource = """ + using System; + using System.Text.Json; + + namespace System.Text.Json + { + public sealed class JsonDocument : System.IDisposable + { + public static JsonDocument Parse(string json) => null; + public JsonElement RootElement => default; + public void Dispose() { } + } + + public struct JsonElement + { + public static JsonElement Parse(string json) => default; + } + } + + class Test + { + void M() + { + Func parser = json => JsonElement.Parse(json); + } + } + """; + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferTypedStringBuilderAppendOverloadsTests.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferTypedStringBuilderAppendOverloadsTests.cs index ec32a3f8cbfa..04064c09f233 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferTypedStringBuilderAppendOverloadsTests.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Runtime/PreferTypedStringBuilderAppendOverloadsTests.cs @@ -260,5 +260,82 @@ private static void Append(string value) { } private static void Append(int value) { } }"); } + + [Fact] + public async Task Diagnostic_StringConstructorInAppend_CSharpAsync() + { + await VerifyCS.VerifyCodeFixAsync(@" + using System.Text; + + class C + { + public void M() + { + var sb = new StringBuilder(); + sb.Append([|new string('c', 5)|]); + } + } + ", @" + using System.Text; + + class C + { + public void M() + { + var sb = new StringBuilder(); + sb.Append('c', 5); + } + } + "); + } + + [Fact] + public async Task Diagnostic_StringConstructorWithVariable_CSharpAsync() + { + await VerifyCS.VerifyCodeFixAsync(@" + using System.Text; + + class C + { + public void M() + { + var sb = new StringBuilder(); + char c = 'a'; + int count = 3; + sb.Append([|new string(c, count)|]); + } + } + ", @" + using System.Text; + + class C + { + public void M() + { + var sb = new StringBuilder(); + char c = 'a'; + int count = 3; + sb.Append(c, count); + } + } + "); + } + + [Fact] + public async Task NoDiagnostic_StringConstructorWithCharArray_CSharpAsync() + { + await VerifyCS.VerifyAnalyzerAsync(@" + using System.Text; + + class C + { + public void M() + { + var sb = new StringBuilder(); + char[] chars = new char[] { 'a', 'b', 'c' }; + sb.Append(new string(chars)); + } + }"); + } } } diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Tasks/DoNotUseNonCancelableTaskDelayWithWhenAnyTests.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Tasks/DoNotUseNonCancelableTaskDelayWithWhenAnyTests.cs new file mode 100644 index 000000000000..097305b84af0 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Tasks/DoNotUseNonCancelableTaskDelayWithWhenAnyTests.cs @@ -0,0 +1,362 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Tasks.DoNotUseNonCancelableTaskDelayWithWhenAny, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Tasks.DoNotUseNonCancelableTaskDelayWithWhenAny, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + +namespace Microsoft.NetCore.Analyzers.Tasks.UnitTests +{ + public class DoNotUseNonCancelableTaskDelayWithWhenAnyTests + { + [Fact] + public async Task NoDiagnostic_TaskDelayWithCancellationToken_CSharp() + { + await VerifyCS.VerifyAnalyzerAsync(""" + using System; + using System.Threading; + using System.Threading.Tasks; + + class C + { + async Task M(CancellationToken ct) + { + var task = CreateTask(); + + // Should not trigger - Task.Delay has CancellationToken + await Task.WhenAny(task, Task.Delay(1000, ct)); + await Task.WhenAny(Task.Delay(1000, ct), task); + await Task.WhenAny(Task.Delay(TimeSpan.FromSeconds(1), ct), task); + } + + Task CreateTask() => Task.CompletedTask; + } + """); + } + + [Fact] + public async Task NoDiagnostic_TaskDelayWithCancellationToken_VB() + { + await VerifyVB.VerifyAnalyzerAsync(""" + Imports System + Imports System.Threading + Imports System.Threading.Tasks + + Class C + Async Function M(ct As CancellationToken) As Task + Dim task = CreateTask() + + ' Should not trigger - Task.Delay has CancellationToken + Await Task.WhenAny(task, Task.Delay(1000, ct)) + Await Task.WhenAny(Task.Delay(1000, ct), task) + Await Task.WhenAny(Task.Delay(TimeSpan.FromSeconds(1), ct), task) + End Function + + Function CreateTask() As Task + Return Task.CompletedTask + End Function + End Class + """); + } + + [Fact] + public async Task NoDiagnostic_WhenAnyWithoutTaskDelay_CSharp() + { + await VerifyCS.VerifyAnalyzerAsync(""" + using System.Threading.Tasks; + + class C + { + async Task M() + { + var task1 = CreateTask(); + var task2 = CreateTask(); + + // Should not trigger - no Task.Delay + await Task.WhenAny(task1, task2); + await Task.WhenAny(CreateTask(), CreateTask()); + } + + Task CreateTask() => Task.CompletedTask; + } + """); + } + + [Fact] + public async Task Diagnostic_TaskDelayWithoutCancellationToken_CSharp() + { + await VerifyCS.VerifyAnalyzerAsync(""" + using System; + using System.Threading.Tasks; + + class C + { + async Task M() + { + var task = CreateTask(); + + // Should trigger - Task.Delay without CancellationToken + await Task.WhenAny(task, {|CA2027:Task.Delay(1000)|}); + await Task.WhenAny({|CA2027:Task.Delay(1000)|}, task); + await Task.WhenAny({|CA2027:Task.Delay(TimeSpan.FromSeconds(1))|}, task); + } + + Task CreateTask() => Task.CompletedTask; + } + """); + } + + [Fact] + public async Task Diagnostic_TaskDelayWithoutCancellationToken_VB() + { + await VerifyVB.VerifyAnalyzerAsync(""" + Imports System + Imports System.Threading.Tasks + + Class C + Async Function M() As Task + Dim task = CreateTask() + + ' Should trigger - Task.Delay without CancellationToken + Await Task.WhenAny(task, {|CA2027:Task.Delay(1000)|}) + Await Task.WhenAny({|CA2027:Task.Delay(1000)|}, task) + Await Task.WhenAny({|CA2027:Task.Delay(TimeSpan.FromSeconds(1))|}, task) + End Function + + Function CreateTask() As Task + Return Task.CompletedTask + End Function + End Class + """); + } + + [Fact] + public async Task Diagnostic_MultipleTaskDelays_CSharp() + { + await VerifyCS.VerifyAnalyzerAsync(""" + using System.Threading.Tasks; + + class C + { + async Task M() + { + var task = CreateTask(); + + // Should trigger on both Task.Delay calls + await Task.WhenAny( + {|CA2027:Task.Delay(1000)|}, + {|CA2027:Task.Delay(2000)|}, + task); + } + + Task CreateTask() => Task.CompletedTask; + } + """); + } + + [Fact] + public async Task Diagnostic_MixedTaskDelays_CSharp() + { + await VerifyCS.VerifyAnalyzerAsync(""" + using System.Threading; + using System.Threading.Tasks; + + class C + { + async Task M(CancellationToken ct) + { + var task = CreateTask(); + + // Should trigger only on Task.Delay without CancellationToken + await Task.WhenAny( + {|CA2027:Task.Delay(1000)|}, + Task.Delay(2000, ct), + task); + } + + Task CreateTask() => Task.CompletedTask; + } + """); + } + + [Fact] + public async Task Diagnostic_NestedInvocation_CSharp() + { + await VerifyCS.VerifyAnalyzerAsync(""" + using System.Threading.Tasks; + + class C + { + async Task M() + { + var task = CreateTask(); + + // Should trigger + var result = await Task.WhenAny(task, {|CA2027:Task.Delay(1000)|}); + } + + Task CreateTask() => Task.CompletedTask; + } + """); + } + + [Fact] + public async Task NoDiagnostic_NotSystemTask_CSharp() + { + await VerifyCS.VerifyAnalyzerAsync(""" + using System.Threading.Tasks; + + namespace CustomTasks + { + public static class Task + { + public static System.Threading.Tasks.Task Delay(int milliseconds) => + System.Threading.Tasks.Task.CompletedTask; + + public static System.Threading.Tasks.Task WhenAny(params System.Threading.Tasks.Task[] tasks) => + System.Threading.Tasks.Task.CompletedTask; + } + } + + class C + { + async System.Threading.Tasks.Task M() + { + var task = CreateTask(); + + // Should not trigger - not System.Threading.Tasks.Task.WhenAny + await CustomTasks.Task.WhenAny(task, CustomTasks.Task.Delay(1000)); + } + + System.Threading.Tasks.Task CreateTask() => System.Threading.Tasks.Task.CompletedTask; + } + """); + } + + [Fact] + public async Task Diagnostic_GenericTask_CSharp() + { + await VerifyCS.VerifyAnalyzerAsync(""" + using System.Threading.Tasks; + + class C + { + async Task M() + { + var task = CreateTask(); + + // Should trigger - works with Task too + await Task.WhenAny(task, {|CA2027:Task.Delay(1000)|}); + } + + Task CreateTask() => Task.FromResult(42); + } + """); + } + + [Fact] + public async Task NoDiagnostic_SingleTaskDelay_CSharp() + { + await VerifyCS.VerifyAnalyzerAsync(""" + using System.Threading.Tasks; + + class C + { + async Task M() + { + // Should not trigger - single task may be used to avoid exception + await Task.WhenAny(Task.Delay(1000)); + } + } + """); + } + + [Fact] + public async Task Diagnostic_ExplicitArray_CSharp() + { + await VerifyCS.VerifyAnalyzerAsync(""" + using System.Threading.Tasks; + + class C + { + async Task M() + { + var task = CreateTask(); + + // Should trigger - explicit array creation + await Task.WhenAny(new[] { task, {|CA2027:Task.Delay(1000)|} }); + } + + Task CreateTask() => Task.CompletedTask; + } + """); + } + + [Fact] + public async Task Diagnostic_CollectionExpression_CSharp() + { + await new VerifyCS.Test + { + LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp12, + TestCode = """ + using System.Threading.Tasks; + + class C + { + async Task M() + { + var task = CreateTask(); + + // Should trigger - collection expression + await Task.WhenAny([task, {|CA2027:Task.Delay(1000)|}]); + } + + Task CreateTask() => Task.CompletedTask; + } + """, + }.RunAsync(); + } + + [Fact] + public async Task NoDiagnostic_EmptyCollectionExpression_CSharp() + { + await new VerifyCS.Test + { + LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp12, + TestCode = """ + using System.Threading.Tasks; + + class C + { + async Task M() + { + await Task.WhenAny(); + } + } + """, + }.RunAsync(); + } + + [Fact] + public async Task NoDiagnostic_CollectionExpression_SingleTask_CSharp() + { + await new VerifyCS.Test + { + LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp12, + TestCode = """ + using System.Threading.Tasks; + + class C + { + async Task M() + { + await Task.WhenAny([Task.Delay(1000)]); + } + } + """, + }.RunAsync(); + } + } +} diff --git a/src/Microsoft.Net.Sdk.AnalyzerRedirecting/AnalyzerRedirectingPackage.cs b/src/Microsoft.Net.Sdk.AnalyzerRedirecting/AnalyzerRedirectingPackage.cs deleted file mode 100644 index 8a4a84e9fc81..000000000000 --- a/src/Microsoft.Net.Sdk.AnalyzerRedirecting/AnalyzerRedirectingPackage.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Microsoft.VisualStudio.Shell; - -namespace Microsoft.Net.Sdk.AnalyzerRedirecting; - -[Guid("ef89a321-14da-4de4-8f71-9bf1feea15aa")] -public sealed class AnalyzerRedirectingPackage : AsyncPackage; diff --git a/src/Microsoft.Net.Sdk.AnalyzerRedirecting/Microsoft.Net.Sdk.AnalyzerRedirecting.csproj b/src/Microsoft.Net.Sdk.AnalyzerRedirecting/Microsoft.Net.Sdk.AnalyzerRedirecting.csproj deleted file mode 100644 index 20d248f368aa..000000000000 --- a/src/Microsoft.Net.Sdk.AnalyzerRedirecting/Microsoft.Net.Sdk.AnalyzerRedirecting.csproj +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - net472 - false - true - - - - RoslynDev - true - true - true - true - false - 42.42.42.4242424 - false - - - - - - - - - - - - - - - - $(VersionPrefix).$(VersionSuffixDateStamp)$(VersionSuffixBuildOfTheDayPadded) - - - - - - - - diff --git a/src/Microsoft.Net.Sdk.AnalyzerRedirecting/SdkAnalyzerAssemblyRedirector.cs b/src/Microsoft.Net.Sdk.AnalyzerRedirecting/SdkAnalyzerAssemblyRedirector.cs deleted file mode 100644 index 23736deeb43e..000000000000 --- a/src/Microsoft.Net.Sdk.AnalyzerRedirecting/SdkAnalyzerAssemblyRedirector.cs +++ /dev/null @@ -1,184 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Immutable; -using System.ComponentModel.Composition; -using System.Text.Json; -using Microsoft.CodeAnalysis.Workspaces.AnalyzerRedirecting; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; - -// Example: -// FullPath: "C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.8\analyzers\dotnet\System.Windows.Forms.Analyzers.dll" -// ProductVersion: "8.0.8" -// PathSuffix: "analyzers\dotnet" -using AnalyzerInfo = (string FullPath, string ProductVersion, string PathSuffix); - -namespace Microsoft.Net.Sdk.AnalyzerRedirecting; - -/// -/// See documentation/general/analyzer-redirecting.md. -/// -[Export(typeof(IAnalyzerAssemblyRedirector))] -public sealed class SdkAnalyzerAssemblyRedirector : IAnalyzerAssemblyRedirector -{ - private readonly IVsActivityLog? _log; - - private readonly bool _enabled; - - private readonly string? _insertedAnalyzersDirectory; - - /// - /// Map from analyzer assembly name (file name without extension) to a list of matching analyzers. - /// - private readonly ImmutableDictionary> _analyzerMap; - - [ImportingConstructor] - public SdkAnalyzerAssemblyRedirector(SVsServiceProvider serviceProvider) : this( - Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"CommonExtensions\Microsoft\DotNet")), - serviceProvider.GetService()) - { - } - - // Internal for testing. - internal SdkAnalyzerAssemblyRedirector(string? insertedAnalyzersDirectory, IVsActivityLog? log = null) - { - _log = log; - var enable = Environment.GetEnvironmentVariable("DOTNET_ANALYZER_REDIRECTING"); - _enabled = !"0".Equals(enable, StringComparison.OrdinalIgnoreCase) && !"false".Equals(enable, StringComparison.OrdinalIgnoreCase); - _insertedAnalyzersDirectory = insertedAnalyzersDirectory; - _analyzerMap = CreateAnalyzerMap(); - } - - private ImmutableDictionary> CreateAnalyzerMap() - { - if (!_enabled) - { - Log("Analyzer redirecting is disabled."); - return ImmutableDictionary>.Empty; - } - - var metadataFilePath = Path.Combine(_insertedAnalyzersDirectory, "metadata.json"); - if (!File.Exists(metadataFilePath)) - { - Log($"File does not exist: {metadataFilePath}"); - return ImmutableDictionary>.Empty; - } - - var versions = JsonSerializer.Deserialize>(File.ReadAllText(metadataFilePath)); - if (versions is null || versions.Count == 0) - { - Log($"Versions are empty: {metadataFilePath}"); - return ImmutableDictionary>.Empty; - } - - var builder = ImmutableDictionary.CreateBuilder>(StringComparer.OrdinalIgnoreCase); - - // Expects layout like: - // VsInstallDir\DotNetRuntimeAnalyzers\WindowsDesktopAnalyzers\analyzers\dotnet\System.Windows.Forms.Analyzers.dll - // ~~~~~~~~~~~~~~~~~~~~~~~ = topLevelDirectory - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ = analyzerPath - - foreach (string topLevelDirectory in Directory.EnumerateDirectories(_insertedAnalyzersDirectory)) - { - foreach (string analyzerPath in Directory.EnumerateFiles(topLevelDirectory, "*.dll", SearchOption.AllDirectories)) - { - if (!analyzerPath.StartsWith(topLevelDirectory, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - string subsetName = Path.GetFileName(topLevelDirectory); - if (!versions.TryGetValue(subsetName, out string version)) - { - continue; - } - - string analyzerName = Path.GetFileNameWithoutExtension(analyzerPath); - string pathSuffix = analyzerPath.Substring(topLevelDirectory.Length + 1 /* slash */); - pathSuffix = Path.GetDirectoryName(pathSuffix); - - AnalyzerInfo analyzer = new() { FullPath = analyzerPath, ProductVersion = version, PathSuffix = pathSuffix }; - - if (builder.TryGetValue(analyzerName, out var existing)) - { - existing.Add(analyzer); - } - else - { - builder.Add(analyzerName, [analyzer]); - } - } - } - - Log($"Loaded analyzer map ({builder.Count}): {_insertedAnalyzersDirectory}"); - - return builder.ToImmutable(); - } - - public string? RedirectPath(string fullPath) - { - if (_enabled && _analyzerMap.TryGetValue(Path.GetFileNameWithoutExtension(fullPath), out var analyzers)) - { - foreach (AnalyzerInfo analyzer in analyzers) - { - var directoryPath = Path.GetDirectoryName(fullPath); - - // Note that both paths we compare here are normalized via netfx's Path.GetDirectoryName. - if (directoryPath.EndsWith(analyzer.PathSuffix, StringComparison.OrdinalIgnoreCase) && - majorAndMinorVersionsMatch(directoryPath, analyzer.PathSuffix, analyzer.ProductVersion)) - { - return analyzer.FullPath; - } - } - } - - return null; - - static bool majorAndMinorVersionsMatch(string directoryPath, string pathSuffix, string version) - { - // Find the version number in the directory path - it is in the directory name before the path suffix. - // Example: - // "C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.8\analyzers\dotnet\" = directoryPath - // ~~~~~~~~~~~~~~~~ = pathSuffix - // ~~~~~ = directoryPathVersion - // This can match also a NuGet package because the version number is at the same position: - // "C:\.nuget\packages\Microsoft.WindowsDesktop.App.Ref\8.0.8\analyzers\dotnet\" - - int index = directoryPath.LastIndexOf(pathSuffix, StringComparison.OrdinalIgnoreCase); - if (index < 0) - { - return false; - } - - string directoryPathVersion = Path.GetFileName(Path.GetDirectoryName(directoryPath.Substring(0, index))); - - return areVersionMajorMinorPartEqual(directoryPathVersion, version); - } - - static bool areVersionMajorMinorPartEqual(string version1, string version2) - { - int firstDotIndex = version1.IndexOf('.'); - if (firstDotIndex < 0) - { - return false; - } - - int secondDotIndex = version1.IndexOf('.', firstDotIndex + 1); - if (secondDotIndex < 0) - { - return false; - } - - return 0 == string.Compare(version1, 0, version2, 0, secondDotIndex, StringComparison.OrdinalIgnoreCase); - } - } - - private void Log(string message) - { - _log?.LogEntry( - (uint)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, - nameof(SdkAnalyzerAssemblyRedirector), - message); - } -} diff --git a/src/Microsoft.Net.Sdk.AnalyzerRedirecting/source.extension.vsixmanifest b/src/Microsoft.Net.Sdk.AnalyzerRedirecting/source.extension.vsixmanifest deleted file mode 100644 index cfa767fb3ca0..000000000000 --- a/src/Microsoft.Net.Sdk.AnalyzerRedirecting/source.extension.vsixmanifest +++ /dev/null @@ -1,23 +0,0 @@ - - - - - .NET SDK Analyzer Redirecting - .NET SDK Analyzer Redirecting Package. - - - - - - - - - - - - - - amd64 - - - diff --git a/src/RazorSdk/Tool/GenerateCommand.cs b/src/RazorSdk/Tool/GenerateCommand.cs index c12f59063555..635eefd6be75 100644 --- a/src/RazorSdk/Tool/GenerateCommand.cs +++ b/src/RazorSdk/Tool/GenerateCommand.cs @@ -433,7 +433,7 @@ private class StaticTagHelperFeature : RazorEngineFeatureBase, ITagHelperFeature { public IReadOnlyList TagHelpers { get; set; } - public IReadOnlyList GetDescriptors() => TagHelpers; + public IReadOnlyList GetDescriptors(CancellationToken cancellationToken) => TagHelpers; } } } diff --git a/src/System.CommandLine.StaticCompletions/DynamicSymbolExtensions.cs b/src/System.CommandLine.StaticCompletions/DynamicSymbolExtensions.cs new file mode 100644 index 000000000000..c59d18c7bec1 --- /dev/null +++ b/src/System.CommandLine.StaticCompletions/DynamicSymbolExtensions.cs @@ -0,0 +1,55 @@ +namespace System.CommandLine.StaticCompletions; + +/// +/// Extensions for marking options or arguments require dynamic completions. Such symbols get special handling +/// in the static completion generation logic. +/// +public static class DynamicSymbolExtensions +{ + /// + /// The state that is used to track which symbols are dynamic. + /// + private static readonly Dictionary s_dynamicSymbols = []; + + extension(Option option) + { + /// + /// Indicates whether this option requires a dynamic call into the dotnet process to compute completions. + /// + public bool IsDynamic + { + get => s_dynamicSymbols.GetValueOrDefault(option, false); + set => s_dynamicSymbols[option] = value; + } + + /// + /// Mark this option as requiring dynamic completions. + /// + /// + public Option RequiresDynamicCompletion() + { + option.IsDynamic = true; + return option; + } + } + + extension(Argument argument) + { + /// Indicates whether this argument requires a dynamic call into the dotnet process to compute completions. + public bool IsDynamic + { + get => s_dynamicSymbols.GetValueOrDefault(argument, false); + set => s_dynamicSymbols[argument] = value; + } + + /// + /// Mark this argument as requiring dynamic completions. + /// + /// + public Argument RequiresDynamicCompletion() + { + argument.IsDynamic = true; + return argument; + } + } +} diff --git a/src/System.CommandLine.StaticCompletions/HelpGenerationExtensions.cs b/src/System.CommandLine.StaticCompletions/HelpGenerationExtensions.cs index a78e854e690e..193234dc60db 100644 --- a/src/System.CommandLine.StaticCompletions/HelpGenerationExtensions.cs +++ b/src/System.CommandLine.StaticCompletions/HelpGenerationExtensions.cs @@ -111,8 +111,5 @@ private static IEnumerable - + @@ -28,7 +26,7 @@ true - + $(BaseOutputPath) $(DefineConstants);EXTENSIONS diff --git a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/Microsoft.NET.Build.Tasks.UnitTests.csproj b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/Microsoft.NET.Build.Tasks.UnitTests.csproj index bf4a545e84a5..62eb66041be9 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/Microsoft.NET.Build.Tasks.UnitTests.csproj +++ b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/Microsoft.NET.Build.Tasks.UnitTests.csproj @@ -1,6 +1,4 @@ - - - + Tests\$(MSBuildProjectName) diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/Microsoft.NET.Build.Tasks.csproj b/src/Tasks/Microsoft.NET.Build.Tasks/Microsoft.NET.Build.Tasks.csproj index d32804d3ee51..7d201deeac9d 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/Microsoft.NET.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.NET.Build.Tasks/Microsoft.NET.Build.Tasks.csproj @@ -140,7 +140,6 @@ - ..\%(Stage0SdkFile.SdkName)\%(Stage0SdkFile.RecursiveDir)%(Stage0SdkFile.Filename)%(Stage0SdkFile.Extension) diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/PrepareForReadyToRunCompilation.cs b/src/Tasks/Microsoft.NET.Build.Tasks/PrepareForReadyToRunCompilation.cs index 51c7baf196f3..b990120d246e 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/PrepareForReadyToRunCompilation.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/PrepareForReadyToRunCompilation.cs @@ -210,7 +210,7 @@ private void ProcessInputFileList( // an input to the ReadyToRunCompiler task TaskItem r2rCompilationEntry = new(file); r2rCompilationEntry.SetMetadata(MetadataKeys.OutputR2RImage, outputR2RImage); - if (outputPDBImage != null && ReadyToRunUseCrossgen2 && !_crossgen2IsVersion5) + if (outputPDBImage != null && ReadyToRunUseCrossgen2 && !_crossgen2IsVersion5 && EmitSymbols) { r2rCompilationEntry.SetMetadata(MetadataKeys.EmitSymbols, "true"); r2rCompilationEntry.SetMetadata(MetadataKeys.OutputPDBImage, outputPDBImage); diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/ProcessFrameworkReferences.cs b/src/Tasks/Microsoft.NET.Build.Tasks/ProcessFrameworkReferences.cs index 7b5b7bc0da15..a119f005fe80 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/ProcessFrameworkReferences.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/ProcessFrameworkReferences.cs @@ -1295,4 +1295,4 @@ public KnownRuntimePack(ITaskItem item) public NuGetFramework TargetFramework { get; } } } -} +} \ No newline at end of file diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.CrossGen.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.CrossGen.targets index d3530276ad12..60115b373731 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.CrossGen.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.CrossGen.targets @@ -24,6 +24,7 @@ Copyright (c) .NET Foundation. All rights reserved. false true 1 + false $(DefaultItemExcludes);$(BaseIntermediateOutputPath)/** + + $(DefaultItemExcludes);$(PublishDir)/** $(DefaultItemExcludes);**/*.user diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FSharp.props b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FSharp.props index 9906eae613c5..dcb4e86edce5 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FSharp.props +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FSharp.props @@ -12,13 +12,6 @@ Copyright (c) .NET Foundation. All rights reserved. - - - - false - true - - @@ -26,6 +19,6 @@ Copyright (c) .NET Foundation. All rights reserved. $(MSBuildToolsPath)\FSharp\Microsoft.FSharp.NetSdk.props $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.NetSdk.props - + diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FSharp.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FSharp.targets index 7f9117372510..0f0a6f61011e 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FSharp.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FSharp.targets @@ -22,6 +22,6 @@ Copyright (c) .NET Foundation. All rights reserved. $(MSBuildToolsPath)\FSharp\Microsoft.FSharp.Overrides.NetSdk.targets $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Overrides.NetSdk.targets - + diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FSharpTargetsShim.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FSharpTargetsShim.targets index ca5225dd2b84..ca29dc7221d4 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FSharpTargetsShim.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FSharpTargetsShim.targets @@ -14,11 +14,11 @@ Copyright (c) .NET Foundation. All rights reserved. - + Properties - + $(Configuration.ToUpperInvariant()) + + diff --git a/test/Common/Empty.targets b/test/Common/Empty.targets index 1b661f64dd0e..9ba198ac6a7d 100644 --- a/test/Common/Empty.targets +++ b/test/Common/Empty.targets @@ -1,5 +1,5 @@ - - + + diff --git a/test/Directory.Build.targets b/test/Directory.Build.targets index 7574c07a19fc..14e3ac1e7dc6 100644 --- a/test/Directory.Build.targets +++ b/test/Directory.Build.targets @@ -1,4 +1,3 @@ - diff --git a/test/Microsoft.AspNetCore.Watch.BrowserRefresh.Tests/BrowserRefreshMiddlewareTest.cs b/test/Microsoft.AspNetCore.Watch.BrowserRefresh.Tests/BrowserRefreshMiddlewareTest.cs index 7dc38ed6d896..d04585a7113d 100644 --- a/test/Microsoft.AspNetCore.Watch.BrowserRefresh.Tests/BrowserRefreshMiddlewareTest.cs +++ b/test/Microsoft.AspNetCore.Watch.BrowserRefresh.Tests/BrowserRefreshMiddlewareTest.cs @@ -183,6 +183,30 @@ public void IsBrowserDocumentRequest_ReturnsTrue_IfRequestFetchMetadataRequestHe [Theory] [InlineData("frame")] [InlineData("iframe")] + public void IsBrowserDocumentRequest_ReturnsTrue_IfRequestFetchMetadataRequestHeaderIsFrame(string headerValue) + { + // Arrange + var context = new DefaultHttpContext + { + Request = + { + Method = "Post", + Headers = + { + ["Accept"] = "text/html", + ["Sec-Fetch-Dest"] = headerValue, + }, + }, + }; + + // Act + var result = BrowserRefreshMiddleware.IsBrowserDocumentRequest(context); + + // Assert + Assert.True(result); + } + + [Theory] [InlineData("serviceworker")] public void IsBrowserDocumentRequest_ReturnsFalse_IfRequestFetchMetadataRequestHeaderIsNotDocument(string headerValue) { diff --git a/test/Microsoft.NET.Build.Containers.UnitTests/RegistryTests.cs b/test/Microsoft.NET.Build.Containers.UnitTests/RegistryTests.cs index 0c41f7ddb1b2..5ee29863abe7 100644 --- a/test/Microsoft.NET.Build.Containers.UnitTests/RegistryTests.cs +++ b/test/Microsoft.NET.Build.Containers.UnitTests/RegistryTests.cs @@ -10,6 +10,7 @@ using Microsoft.NET.Build.Containers.Resources; using System.Net.Sockets; using Moq; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.NET.Build.Containers.UnitTests; @@ -687,5 +688,34 @@ public string GetCommandPathFromRootPath(string rootPath, string commandName, pa public string GetCommandPathFromRootPath(string rootPath, string commandName, IEnumerable extensions) => throw new NotImplementedException(); + + public bool TryGetEnvironmentVariable(string name, [NotNullWhen(true)] out string? value) => _environmentVariables.TryGetValue(name, out value); + public bool TryGetEnvironmentVariableAsBool(string name, [NotNullWhen(true)] out bool value) + { + if (TryGetEnvironmentVariable(name, out string? strValue) && bool.TryParse(strValue, out bool boolValue)) + { + value = boolValue; + return true; + } + else + { + value = false; + return false; + } + } + + public bool TryGetEnvironmentVariableAsInt(string name, [NotNullWhen(true)] out int value) + { + if (TryGetEnvironmentVariable(name, out string? strValue) && int.TryParse(strValue, out int intValue)) + { + value = intValue; + return true; + } + else + { + value = 0; + return false; + } + } } } diff --git a/test/Microsoft.NET.Build.Tests/GivenThereAreDefaultItems.cs b/test/Microsoft.NET.Build.Tests/GivenThereAreDefaultItems.cs index 958b7cd48f66..d9fb34f84b41 100644 --- a/test/Microsoft.NET.Build.Tests/GivenThereAreDefaultItems.cs +++ b/test/Microsoft.NET.Build.Tests/GivenThereAreDefaultItems.cs @@ -854,6 +854,35 @@ public void It_does_not_include_Windows_App_SDK_items_if_Windows_App_SDK_is_abse .BeEmpty(); } + [Fact] + public void It_excludes_items_in_publish_directory() + { + Action setup = getValuesCommand => + { + // Create a PublishDir with a JSON file (simulating a previous publish) + string publishDir = Path.Combine(getValuesCommand.ProjectRootPath, "artifacts", "TestLibrary"); + WriteFile(Path.Combine(publishDir, "appsettings.json"), + "{ \"Setting\": \"Value\" }"); + + WriteFile(Path.Combine(getValuesCommand.ProjectRootPath, "Code", "Class1.cs"), + "public class Class1 {}"); + }; + + Action projectChanges = project => + { + var ns = project.Root.Name.Namespace; + + var propertyGroup = new XElement(ns + "PropertyGroup"); + project.Root.Add(propertyGroup); + propertyGroup.Add(new XElement(ns + "PublishDir", "artifacts\\TestLibrary\\")); + }; + + var noneItems = GivenThatWeWantToBuildALibrary.GetValuesFromTestLibrary(Log, _testAssetsManager, "None", setup, projectChanges: projectChanges); + + // The appsettings.json file in the PublishDir should not be included in None items + noneItems.Should().NotContain(item => item.Contains("appsettings.json")); + } + void RemoveGeneratedCompileItems(List compileItems) { // Remove auto-generated compile items. diff --git a/test/Microsoft.NET.Build.Tests/Microsoft.NET.Build.Tests.csproj b/test/Microsoft.NET.Build.Tests/Microsoft.NET.Build.Tests.csproj index cda2e95e19e4..42734c6a8cd7 100644 --- a/test/Microsoft.NET.Build.Tests/Microsoft.NET.Build.Tests.csproj +++ b/test/Microsoft.NET.Build.Tests/Microsoft.NET.Build.Tests.csproj @@ -1,6 +1,5 @@ - - - + + false Tests\$(MSBuildProjectName) diff --git a/test/Microsoft.NET.Clean.Tests/Microsoft.NET.Clean.Tests.csproj b/test/Microsoft.NET.Clean.Tests/Microsoft.NET.Clean.Tests.csproj index a5cc5adbeff4..12adb846f20a 100644 --- a/test/Microsoft.NET.Clean.Tests/Microsoft.NET.Clean.Tests.csproj +++ b/test/Microsoft.NET.Clean.Tests/Microsoft.NET.Clean.Tests.csproj @@ -1,6 +1,5 @@ - - - + + false Tests\$(MSBuildProjectName) @@ -16,7 +15,7 @@ testSdkClean - + diff --git a/test/Microsoft.NET.Publish.Tests/Microsoft.NET.Publish.Tests.csproj b/test/Microsoft.NET.Publish.Tests/Microsoft.NET.Publish.Tests.csproj index 9f9b0aaf1538..7c819fa72e6f 100644 --- a/test/Microsoft.NET.Publish.Tests/Microsoft.NET.Publish.Tests.csproj +++ b/test/Microsoft.NET.Publish.Tests/Microsoft.NET.Publish.Tests.csproj @@ -35,7 +35,7 @@ - + diff --git a/test/Microsoft.NET.Rebuild.Tests/Microsoft.NET.Rebuild.Tests.csproj b/test/Microsoft.NET.Rebuild.Tests/Microsoft.NET.Rebuild.Tests.csproj index c160073157bc..214a424d8485 100644 --- a/test/Microsoft.NET.Rebuild.Tests/Microsoft.NET.Rebuild.Tests.csproj +++ b/test/Microsoft.NET.Rebuild.Tests/Microsoft.NET.Rebuild.Tests.csproj @@ -1,6 +1,4 @@ - - - + false diff --git a/test/Microsoft.NET.Restore.Tests/Microsoft.NET.Restore.Tests.csproj b/test/Microsoft.NET.Restore.Tests/Microsoft.NET.Restore.Tests.csproj index ba4c241ee8e7..0c8d9d66d319 100644 --- a/test/Microsoft.NET.Restore.Tests/Microsoft.NET.Restore.Tests.csproj +++ b/test/Microsoft.NET.Restore.Tests/Microsoft.NET.Restore.Tests.csproj @@ -1,6 +1,4 @@ - - - + false @@ -17,7 +15,7 @@ testSdkRestore - + diff --git a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/Build_DoesNotGenerateManifestJson_IncludesJSModulesOnBlazorBootJsonManifest.Build.staticwebassets.json b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/Build_DoesNotGenerateManifestJson_IncludesJSModulesOnBlazorBootJsonManifest.Build.staticwebassets.json index fe73bd7957a6..5d636f268355 100644 --- a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/Build_DoesNotGenerateManifestJson_IncludesJSModulesOnBlazorBootJsonManifest.Build.staticwebassets.json +++ b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/Build_DoesNotGenerateManifestJson_IncludesJSModulesOnBlazorBootJsonManifest.Build.staticwebassets.json @@ -37077,4 +37077,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/Build_SatelliteAssembliesAreCopiedToBuildOutput.Build.staticwebassets.json b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/Build_SatelliteAssembliesAreCopiedToBuildOutput.Build.staticwebassets.json index ddbbbfe0dbae..0d4ffdb77e44 100644 --- a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/Build_SatelliteAssembliesAreCopiedToBuildOutput.Build.staticwebassets.json +++ b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/Build_SatelliteAssembliesAreCopiedToBuildOutput.Build.staticwebassets.json @@ -44791,4 +44791,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/JSModules_ManifestIncludesModuleTargetPaths.Build.staticwebassets.json b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/JSModules_ManifestIncludesModuleTargetPaths.Build.staticwebassets.json index 62c87d9c4926..52fc6dd25e4f 100644 --- a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/JSModules_ManifestIncludesModuleTargetPaths.Build.staticwebassets.json +++ b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/JSModules_ManifestIncludesModuleTargetPaths.Build.staticwebassets.json @@ -38698,4 +38698,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/JsModules_CanCustomizeBlazorInitialization.Publish.staticwebassets.json b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/JsModules_CanCustomizeBlazorInitialization.Publish.staticwebassets.json index 056f5a7cfad6..5c3a75892317 100644 --- a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/JsModules_CanCustomizeBlazorInitialization.Publish.staticwebassets.json +++ b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/JsModules_CanCustomizeBlazorInitialization.Publish.staticwebassets.json @@ -22812,4 +22812,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/JsModules_CanHaveDifferentBuildAndPublishModules.Publish.staticwebassets.json b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/JsModules_CanHaveDifferentBuildAndPublishModules.Publish.staticwebassets.json index 364bf71213a5..674aa5b63f63 100644 --- a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/JsModules_CanHaveDifferentBuildAndPublishModules.Publish.staticwebassets.json +++ b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/JsModules_CanHaveDifferentBuildAndPublishModules.Publish.staticwebassets.json @@ -22522,4 +22522,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/JsModules_Hosted_CanCustomizeBlazorInitialization.Publish.staticwebassets.json b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/JsModules_Hosted_CanCustomizeBlazorInitialization.Publish.staticwebassets.json index a32e09aa5828..cd2dcfb64a5d 100644 --- a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/JsModules_Hosted_CanCustomizeBlazorInitialization.Publish.staticwebassets.json +++ b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/JsModules_Hosted_CanCustomizeBlazorInitialization.Publish.staticwebassets.json @@ -24872,4 +24872,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/Publish_DoesNotGenerateManifestJson_IncludesJSModulesOnBlazorBootJsonManifest.Publish.staticwebassets.json b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/Publish_DoesNotGenerateManifestJson_IncludesJSModulesOnBlazorBootJsonManifest.Publish.staticwebassets.json index 8a5ffd77118f..13282815d1a8 100644 --- a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/Publish_DoesNotGenerateManifestJson_IncludesJSModulesOnBlazorBootJsonManifest.Publish.staticwebassets.json +++ b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/Publish_DoesNotGenerateManifestJson_IncludesJSModulesOnBlazorBootJsonManifest.Publish.staticwebassets.json @@ -22522,4 +22522,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_BuildMinimal_Works.Build.staticwebassets.json b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_BuildMinimal_Works.Build.staticwebassets.json index 002f2f85ed28..e9b43d705f33 100644 --- a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_BuildMinimal_Works.Build.staticwebassets.json +++ b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_BuildMinimal_Works.Build.staticwebassets.json @@ -37717,4 +37717,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_Build_Hosted_Works.Build.staticwebassets.json b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_Build_Hosted_Works.Build.staticwebassets.json index df1cf77076b2..b6acaab3023b 100644 --- a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_Build_Hosted_Works.Build.staticwebassets.json +++ b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_Build_Hosted_Works.Build.staticwebassets.json @@ -38316,4 +38316,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_PublishMinimal_Works.Publish.staticwebassets.json b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_PublishMinimal_Works.Publish.staticwebassets.json index f5c4925a8860..6ef898ff6713 100644 --- a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_PublishMinimal_Works.Publish.staticwebassets.json +++ b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_PublishMinimal_Works.Publish.staticwebassets.json @@ -23600,4 +23600,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_Publish_DoesNotIncludeXmlDocumentationFiles_AsAssets.Publish.staticwebassets.json b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_Publish_DoesNotIncludeXmlDocumentationFiles_AsAssets.Publish.staticwebassets.json index de7af2fbe716..e75503f6b2a3 100644 --- a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_Publish_DoesNotIncludeXmlDocumentationFiles_AsAssets.Publish.staticwebassets.json +++ b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_Publish_DoesNotIncludeXmlDocumentationFiles_AsAssets.Publish.staticwebassets.json @@ -24819,4 +24819,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_Publish_Hosted_Works.Publish.staticwebassets.json b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_Publish_Hosted_Works.Publish.staticwebassets.json index de7af2fbe716..e75503f6b2a3 100644 --- a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_Publish_Hosted_Works.Publish.staticwebassets.json +++ b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/StaticWebAssetsBaselines/StaticWebAssets_Publish_Hosted_Works.Publish.staticwebassets.json @@ -24819,4 +24819,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmBuildIntegrationTest.cs b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmBuildIntegrationTest.cs index 875dd0088db6..9c46a02f30b6 100644 --- a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmBuildIntegrationTest.cs +++ b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmBuildIntegrationTest.cs @@ -697,6 +697,23 @@ public class TestReference fileInWwwroot.Should().Exist(); } + [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")] + public void Restore_WithRuntime_Works() + { + var testInstance = CreateAspNetSdkTestAsset("BlazorHosted"); + + var nugetRestorePath = Path.Combine(testInstance.TestRoot, ".nuget"); + + new DotnetRestoreCommand(Log, "-bl:msbuild-restore.binlog", "-r", "linux-x64") + .WithWorkingDirectory(Path.Combine(testInstance.TestRoot, "blazorhosted")) + .WithEnvironmentVariable("NUGET_PACKAGES", nugetRestorePath) + .Execute() + .Should().Pass(); + + new DirectoryInfo(Path.Combine(nugetRestorePath, "microsoft.netcore.app.runtime.mono.linux-x64")) + .Should().NotExist(); + } + [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")] public void Build_WithReference_Works() { diff --git a/test/Microsoft.NET.Sdk.Razor.Tests/content/ExternalStaticAssets.targets b/test/Microsoft.NET.Sdk.Razor.Tests/content/ExternalStaticAssets.targets index a0083f3f9a49..64049b7cbd1e 100644 --- a/test/Microsoft.NET.Sdk.Razor.Tests/content/ExternalStaticAssets.targets +++ b/test/Microsoft.NET.Sdk.Razor.Tests/content/ExternalStaticAssets.targets @@ -1,5 +1,4 @@ - - + @@ -19,7 +18,7 @@ - + @@ -66,4 +65,5 @@ + diff --git a/test/Microsoft.NET.Sdk.Razor.Tool.Tests/Microsoft.NET.Sdk.Razor.Tool.Tests.csproj b/test/Microsoft.NET.Sdk.Razor.Tool.Tests/Microsoft.NET.Sdk.Razor.Tool.Tests.csproj index 0cabc4330a7c..67d5060c3a72 100644 --- a/test/Microsoft.NET.Sdk.Razor.Tool.Tests/Microsoft.NET.Sdk.Razor.Tool.Tests.csproj +++ b/test/Microsoft.NET.Sdk.Razor.Tool.Tests/Microsoft.NET.Sdk.Razor.Tool.Tests.csproj @@ -1,6 +1,4 @@ - - - + false diff --git a/test/Microsoft.NET.Sdk.StaticWebAssets.Tests/content/ExternalStaticAssets.targets b/test/Microsoft.NET.Sdk.StaticWebAssets.Tests/content/ExternalStaticAssets.targets index 8a2b8976b8d4..64049b7cbd1e 100644 --- a/test/Microsoft.NET.Sdk.StaticWebAssets.Tests/content/ExternalStaticAssets.targets +++ b/test/Microsoft.NET.Sdk.StaticWebAssets.Tests/content/ExternalStaticAssets.targets @@ -1,5 +1,4 @@ - - + @@ -66,4 +65,5 @@ + diff --git a/test/Microsoft.NET.TestFramework/Assertions/StringAssertionsExtensions.cs b/test/Microsoft.NET.TestFramework/Assertions/StringAssertionsExtensions.cs index 83367ee3df6c..ce3f01f6c99c 100644 --- a/test/Microsoft.NET.TestFramework/Assertions/StringAssertionsExtensions.cs +++ b/test/Microsoft.NET.TestFramework/Assertions/StringAssertionsExtensions.cs @@ -35,7 +35,10 @@ public static AndConstraint BeVisuallyEquivalentTo( if (!areSame) { var diff = UnidiffRenderer.GenerateUnidiff(oldText: normalizedExpected, newText: normalizedActual, oldFileName: "expected", newFileName: "actual", ignoreWhitespace: true); - areSame.Should().Be(true, because: string.IsNullOrEmpty(because) ? $"The input strings are not visually equivalent. Diff is:\n" + diff : because, becauseArgs: [.. becauseArgs, diff]); + // diff may contain braces which will be interpreted as format items in the because string, + // so we need to escape them. + var formatSafeDiff = diff.Replace("{", "{{").Replace("}", "}}"); + areSame.Should().Be(true, because: string.IsNullOrEmpty(because) ? $"The input strings are not visually equivalent. Diff is:\n" + formatSafeDiff : because, becauseArgs: [.. becauseArgs, formatSafeDiff]); } return new AndConstraint(assertions); diff --git a/test/Microsoft.NET.TestFramework/Microsoft.NET.TestFramework.csproj b/test/Microsoft.NET.TestFramework/Microsoft.NET.TestFramework.csproj index 7829d0829246..7f206f2291f6 100644 --- a/test/Microsoft.NET.TestFramework/Microsoft.NET.TestFramework.csproj +++ b/test/Microsoft.NET.TestFramework/Microsoft.NET.TestFramework.csproj @@ -1,6 +1,4 @@ - - - + false diff --git a/test/Microsoft.NET.TestFramework/SetupTestRoot.targets b/test/Microsoft.NET.TestFramework/SetupTestRoot.targets index 9167a88eaa01..4c917cc6a6d4 100644 --- a/test/Microsoft.NET.TestFramework/SetupTestRoot.targets +++ b/test/Microsoft.NET.TestFramework/SetupTestRoot.targets @@ -1,4 +1,3 @@ - diff --git a/test/Microsoft.NET.ToolPack.Tests/Microsoft.NET.ToolPack.Tests.csproj b/test/Microsoft.NET.ToolPack.Tests/Microsoft.NET.ToolPack.Tests.csproj index eff7863617a0..c5d593181bd2 100644 --- a/test/Microsoft.NET.ToolPack.Tests/Microsoft.NET.ToolPack.Tests.csproj +++ b/test/Microsoft.NET.ToolPack.Tests/Microsoft.NET.ToolPack.Tests.csproj @@ -1,6 +1,4 @@ - - - + false diff --git a/test/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests.csproj b/test/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests.csproj deleted file mode 100644 index 3867729d2172..000000000000 --- a/test/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - net472 - - - - - - - - diff --git a/test/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests/SdkAnalyzerAssemblyRedirectorTests.cs b/test/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests/SdkAnalyzerAssemblyRedirectorTests.cs deleted file mode 100644 index 956161b134a8..000000000000 --- a/test/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests/SdkAnalyzerAssemblyRedirectorTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Text.Json; - -namespace Microsoft.Net.Sdk.AnalyzerRedirecting.Tests; - -public class SdkAnalyzerAssemblyRedirectorTests(ITestOutputHelper log) : SdkTest(log) -{ - [Theory] - [InlineData("9.0.0-preview.5.24306.11", "9.0.0-preview.7.24406.2")] - [InlineData("9.0.0-preview.5.24306.11", "9.0.1-preview.7.24406.2")] - [InlineData("9.0.100", "9.0.0-preview.7.24406.2")] - [InlineData("9.0.100", "9.0.200")] - [InlineData("9.0.100", "9.0.101")] - public void SameMajorMinorVersion(string a, string b) - { - TestDirectory testDir = _testAssetsManager.CreateTestDirectory(identifier: "RuntimeAnalyzers"); - - var vsDir = Path.Combine(testDir.Path, "vs"); - Metadata(vsDir, new() { { "AspNetCoreAnalyzers", a } }); - var vsAnalyzerPath = FakeDll(vsDir, @$"AspNetCoreAnalyzers\analyzers\dotnet\cs", "Microsoft.AspNetCore.App.Analyzers"); - var sdkAnalyzerPath = FakeDll(testDir.Path, @$"sdk\packs\Microsoft.AspNetCore.App.Ref\{b}\analyzers\dotnet\cs", "Microsoft.AspNetCore.App.Analyzers"); - - var resolver = new SdkAnalyzerAssemblyRedirector(vsDir); - var redirected = resolver.RedirectPath(sdkAnalyzerPath); - redirected.Should().Be(vsAnalyzerPath); - } - - [Fact] - public void DifferentPathSuffix() - { - TestDirectory testDir = _testAssetsManager.CreateTestDirectory(identifier: "RuntimeAnalyzers"); - - var vsDir = Path.Combine(testDir.Path, "vs"); - Metadata(vsDir, new() { { "AspNetCoreAnalyzers", "9.0.0-preview.5.24306.11" } }); - FakeDll(vsDir, @"AspNetCoreAnalyzers\analyzers\dotnet\cs", "Microsoft.AspNetCore.App.Analyzers"); - var sdkAnalyzerPath = FakeDll(testDir.Path, @"sdk\packs\Microsoft.AspNetCore.App.Ref\9.0.0-preview.7.24406.2\analyzers\dotnet\vb", "Microsoft.AspNetCore.App.Analyzers"); - - var resolver = new SdkAnalyzerAssemblyRedirector(vsDir); - var redirected = resolver.RedirectPath(sdkAnalyzerPath); - redirected.Should().BeNull(); - } - - [Theory] - [InlineData("8.0.100", "9.0.0-preview.7.24406.2")] - [InlineData("9.1.100", "9.0.0-preview.7.24406.2")] - [InlineData("9.1.0-preview.5.24306.11", "9.0.0-preview.7.24406.2")] - [InlineData("9.0.100", "9.1.100")] - [InlineData("9.0.100", "10.0.100")] - [InlineData("9.9.100", "9.10.100")] - public void DifferentMajorMinorVersion(string a, string b) - { - TestDirectory testDir = _testAssetsManager.CreateTestDirectory(identifier: "RuntimeAnalyzers"); - - var vsDir = Path.Combine(testDir.Path, "vs"); - Metadata(vsDir, new() { { "AspNetCoreAnalyzers", a } }); - FakeDll(vsDir, @$"AspNetCoreAnalyzers\analyzers\dotnet\cs", "Microsoft.AspNetCore.App.Analyzers"); - var sdkAnalyzerPath = FakeDll(testDir.Path, @$"sdk\packs\Microsoft.AspNetCore.App.Ref\{b}\analyzers\dotnet\cs", "Microsoft.AspNetCore.App.Analyzers"); - - var resolver = new SdkAnalyzerAssemblyRedirector(vsDir); - var redirected = resolver.RedirectPath(sdkAnalyzerPath); - redirected.Should().BeNull(); - } - - private static string FakeDll(string root, string subdir, string name) - { - var dllPath = Path.Combine(root, subdir, $"{name}.dll"); - Directory.CreateDirectory(Path.GetDirectoryName(dllPath)); - File.WriteAllText(dllPath, ""); - return dllPath; - } - - private static void Metadata(string root, Dictionary versions) - { - var metadataFilePath = Path.Combine(root, "metadata.json"); - Directory.CreateDirectory(Path.GetDirectoryName(metadataFilePath)); - File.WriteAllText(metadataFilePath, JsonSerializer.Serialize(versions)); - } -} diff --git a/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.Create_GetAllSuggestions.verified.txt b/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.Create_GetAllSuggestions.verified.txt index 610ddc1db75b..15251849856f 100644 --- a/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.Create_GetAllSuggestions.verified.txt +++ b/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.Create_GetAllSuggestions.verified.txt @@ -25,7 +25,7 @@ Kind: Value, SortText: console, InsertText: console, - Documentation: A project for creating a command-line application that can run on .NET on Windows, Linux and macOS + Documentation: A project for creating an empty command-line application that can run on .NET on Windows, Linux and macOS }, { Label: editorconfig, diff --git a/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.RootCommand_GetAllSuggestions.verified.txt b/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.RootCommand_GetAllSuggestions.verified.txt index b06accd6268a..5377b175a814 100644 --- a/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.RootCommand_GetAllSuggestions.verified.txt +++ b/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.RootCommand_GetAllSuggestions.verified.txt @@ -25,7 +25,7 @@ Kind: Value, SortText: console, InsertText: console, - Documentation: A project for creating a command-line application that can run on .NET on Windows, Linux and macOS + Documentation: A project for creating an empty command-line application that can run on .NET on Windows, Linux and macOS }, { Label: editorconfig, diff --git a/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.RootCommand_GetStartsWtihSuggestions.verified.txt b/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.RootCommand_GetStartsWtihSuggestions.verified.txt index d14d71aa40ea..cb330b8b8fee 100644 --- a/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.RootCommand_GetStartsWtihSuggestions.verified.txt +++ b/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.RootCommand_GetStartsWtihSuggestions.verified.txt @@ -11,7 +11,7 @@ Kind: Value, SortText: console, InsertText: console, - Documentation: A project for creating a command-line application that can run on .NET on Windows, Linux and macOS + Documentation: A project for creating an empty command-line application that can run on .NET on Windows, Linux and macOS }, { Label: create, diff --git a/test/System.CommandLine.StaticCompletions.Tests/TestHelpers.cs b/test/System.CommandLine.StaticCompletions.Tests/TestHelpers.cs deleted file mode 100644 index 2bbfd95b1c29..000000000000 --- a/test/System.CommandLine.StaticCompletions.Tests/TestHelpers.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable disable - -namespace System.CommandLine.StaticCompletions.Tests; - -public class DynamicOption : Option, IDynamicOption -{ - public DynamicOption(string name) : base(name) - { - } -} - -public class DynamicArgument : Argument, IDynamicArgument -{ - public DynamicArgument(string name) : base(name) - { - } -} diff --git a/test/System.CommandLine.StaticCompletions.Tests/ZshShellProviderTests.cs b/test/System.CommandLine.StaticCompletions.Tests/ZshShellProviderTests.cs index a7d8ea436a73..5e2ec51d0b03 100644 --- a/test/System.CommandLine.StaticCompletions.Tests/ZshShellProviderTests.cs +++ b/test/System.CommandLine.StaticCompletions.Tests/ZshShellProviderTests.cs @@ -42,9 +42,15 @@ public async Task GenericCompletions() [Fact] public async Task DynamicCompletionsGeneration() { - var staticOption = new DynamicOption("--static"); + var staticOption = new Option("--static") + { + IsDynamic = true + }; staticOption.AcceptOnlyFromAmong("1", "2", "3"); - var dynamicArg = new DynamicArgument("--dynamic"); + var dynamicArg = new Argument("--dynamic") + { + IsDynamic = true + }; dynamicArg.CompletionSources.Add((context) => { return [ diff --git a/test/TestAssets/TestProjects/ArtifactsSdkTest/PackageReference/Program.cs b/test/TestAssets/TestProjects/ArtifactsSdkTest/PackageReference/Program.cs index 3751555cbd32..1bc52a60a8ba 100644 --- a/test/TestAssets/TestProjects/ArtifactsSdkTest/PackageReference/Program.cs +++ b/test/TestAssets/TestProjects/ArtifactsSdkTest/PackageReference/Program.cs @@ -1,2 +1 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +Console.WriteLine("Hello, World!"); diff --git a/test/TestAssets/TestProjects/ClassLibraryWithIsTestProjectAndOtherTestProjects/OtherTestProject/Program.cs b/test/TestAssets/TestProjects/ClassLibraryWithIsTestProjectAndOtherTestProjects/OtherTestProject/Program.cs index 5785dc473c5b..1b9b5e92af36 100644 --- a/test/TestAssets/TestProjects/ClassLibraryWithIsTestProjectAndOtherTestProjects/OtherTestProject/Program.cs +++ b/test/TestAssets/TestProjects/ClassLibraryWithIsTestProjectAndOtherTestProjects/OtherTestProject/Program.cs @@ -1,6 +1,4 @@ -//See https://aka.ms/new-console-template for more information - -//Opt -out telemetry +//Opt -out telemetry using Microsoft.Testing.Platform.Builder; using Microsoft.Testing.Platform.Capabilities.TestFramework; diff --git a/test/TestAssets/TestProjects/EndToEndTestApp/Program.cs b/test/TestAssets/TestProjects/EndToEndTestApp/Program.cs index 3751555cbd32..1bc52a60a8ba 100644 --- a/test/TestAssets/TestProjects/EndToEndTestApp/Program.cs +++ b/test/TestAssets/TestProjects/EndToEndTestApp/Program.cs @@ -1,2 +1 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +Console.WriteLine("Hello, World!"); diff --git a/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/OtherTestProject/Program.cs b/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/OtherTestProject/Program.cs index 5785dc473c5b..1b9b5e92af36 100644 --- a/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/OtherTestProject/Program.cs +++ b/test/TestAssets/TestProjects/HybridTestRunnerTestProjects/OtherTestProject/Program.cs @@ -1,6 +1,4 @@ -//See https://aka.ms/new-console-template for more information - -//Opt -out telemetry +//Opt -out telemetry using Microsoft.Testing.Platform.Builder; using Microsoft.Testing.Platform.Capabilities.TestFramework; diff --git a/test/TestAssets/TestProjects/SolutionWithAppAndDcProj/SampleApp/Program.cs b/test/TestAssets/TestProjects/SolutionWithAppAndDcProj/SampleApp/Program.cs index 3751555cbd32..1bc52a60a8ba 100644 --- a/test/TestAssets/TestProjects/SolutionWithAppAndDcProj/SampleApp/Program.cs +++ b/test/TestAssets/TestProjects/SolutionWithAppAndDcProj/SampleApp/Program.cs @@ -1,2 +1 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +Console.WriteLine("Hello, World!"); diff --git a/test/TestAssets/TestProjects/TestAppWithSlnUsingPublishReleaseConflictingCasing/App2/Program.cs b/test/TestAssets/TestProjects/TestAppWithSlnUsingPublishReleaseConflictingCasing/App2/Program.cs index 3751555cbd32..1bc52a60a8ba 100644 --- a/test/TestAssets/TestProjects/TestAppWithSlnUsingPublishReleaseConflictingCasing/App2/Program.cs +++ b/test/TestAssets/TestProjects/TestAppWithSlnUsingPublishReleaseConflictingCasing/App2/Program.cs @@ -1,2 +1 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +Console.WriteLine("Hello, World!"); diff --git a/test/TestAssets/TestProjects/TestProjectSolutionWithTestsAndArtifacts/TestProject/Program.cs b/test/TestAssets/TestProjects/TestProjectSolutionWithTestsAndArtifacts/TestProject/Program.cs index 560c3d3ce712..8191677e5472 100644 --- a/test/TestAssets/TestProjects/TestProjectSolutionWithTestsAndArtifacts/TestProject/Program.cs +++ b/test/TestAssets/TestProjects/TestProjectSolutionWithTestsAndArtifacts/TestProject/Program.cs @@ -1,6 +1,4 @@ -//See https://aka.ms/new-console-template for more information - -//Opt -out telemetry +//Opt -out telemetry using Microsoft.Testing.Extensions; using Microsoft.Testing.Platform.Builder; diff --git a/test/TestAssets/dotnet-format/for_code_formatter/suppressor_project/.editorconfig b/test/TestAssets/dotnet-format/for_code_formatter/suppressor_project/.editorconfig new file mode 100644 index 000000000000..4df5362d2540 --- /dev/null +++ b/test/TestAssets/dotnet-format/for_code_formatter/suppressor_project/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[*.cs] +dotnet_diagnostic.IDE0051.severity = warning # Remove unused private member diff --git a/test/TestAssets/dotnet-format/for_code_formatter/suppressor_project/Program.cs b/test/TestAssets/dotnet-format/for_code_formatter/suppressor_project/Program.cs new file mode 100644 index 000000000000..d5679ea6cfab --- /dev/null +++ b/test/TestAssets/dotnet-format/for_code_formatter/suppressor_project/Program.cs @@ -0,0 +1,26 @@ +using System; +using UnityEngine; + +namespace for_code_formatter +{ + class Program : MonoBehaviour + { + // This method should trigger IDE0051 (remove unused private member) in a regular project. + // But given we simulate a Unity MonoBehavior and we include Microsoft.Unity.Analyzers nuget, + // given Update is a well-known Unity message, this IDE0051 should be suppressed by USP0003. + // see https://github.com/microsoft/Microsoft.Unity.Analyzers/blob/main/doc/USP0003.md + void Update() + { + + } + } +} + +namespace UnityEngine +{ + public class MonoBehaviour + { + // This is a placeholder for the Unity MonoBehaviour class. + // In a real Unity project, this would be part of the Unity engine. + } +} diff --git a/test/TestAssets/dotnet-format/for_code_formatter/suppressor_project/suppressor_project.csproj b/test/TestAssets/dotnet-format/for_code_formatter/suppressor_project/suppressor_project.csproj new file mode 100644 index 000000000000..6f11089c1bad --- /dev/null +++ b/test/TestAssets/dotnet-format/for_code_formatter/suppressor_project/suppressor_project.csproj @@ -0,0 +1,12 @@ + + + + Library + netstandard2.0 + + + + + + + diff --git a/test/UnitTests.proj b/test/UnitTests.proj index 6c396845af1d..f043119e1b05 100644 --- a/test/UnitTests.proj +++ b/test/UnitTests.proj @@ -16,12 +16,8 @@ - + - - net472 - net472 - diff --git a/test/dotnet-format.UnitTests/CodeFormatterTests.cs b/test/dotnet-format.UnitTests/CodeFormatterTests.cs index c74bc433ea8c..84b3c02b0481 100644 --- a/test/dotnet-format.UnitTests/CodeFormatterTests.cs +++ b/test/dotnet-format.UnitTests/CodeFormatterTests.cs @@ -39,6 +39,9 @@ public class CodeFormatterTests private static readonly string s_generatorSolutionPath = Path.Combine("for_code_formatter", "generator_solution"); private static readonly string s_generatorSolutionFileName = "generator_solution.sln"; + private static readonly string s_suppressorProjectPath = Path.Combine("for_code_formatter", "suppressor_project"); + private static readonly string s_suppressorProjectFilePath = Path.Combine(s_suppressorProjectPath, "suppressor_project.csproj"); + private static string[] EmptyFilesList => Array.Empty(); private Regex FindFormattingLogLine => new Regex(@"((.*)\(\d+,\d+\): (.*))\r|((.*)\(\d+,\d+\): (.*))"); @@ -627,6 +630,21 @@ await TestFormatWorkspaceAsync( } } + [MSBuildFact] + public async Task SuppressorsHandledInProject() + { + await TestFormatWorkspaceAsync( + s_suppressorProjectFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 0, + expectedFileCount: 3, + codeStyleSeverity: DiagnosticSeverity.Warning, + fixCategory: FixCategory.CodeStyle); + } + internal async Task TestFormatWorkspaceAsync( string workspaceFilePath, string[] include, diff --git a/test/dotnet-new.IntegrationTests/Approvals/AotVariants.console.cs.verified/MyProject/Program.cs b/test/dotnet-new.IntegrationTests/Approvals/AotVariants.console.cs.verified/MyProject/Program.cs index 3751555cbd32..1bc52a60a8ba 100644 --- a/test/dotnet-new.IntegrationTests/Approvals/AotVariants.console.cs.verified/MyProject/Program.cs +++ b/test/dotnet-new.IntegrationTests/Approvals/AotVariants.console.cs.verified/MyProject/Program.cs @@ -1,2 +1 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +Console.WriteLine("Hello, World!"); diff --git a/test/dotnet-new.IntegrationTests/Approvals/AotVariants.console.vb.verified/MyProject/Program.cs b/test/dotnet-new.IntegrationTests/Approvals/AotVariants.console.vb.verified/MyProject/Program.cs index 3751555cbd32..1bc52a60a8ba 100644 --- a/test/dotnet-new.IntegrationTests/Approvals/AotVariants.console.vb.verified/MyProject/Program.cs +++ b/test/dotnet-new.IntegrationTests/Approvals/AotVariants.console.vb.verified/MyProject/Program.cs @@ -1,2 +1 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +Console.WriteLine("Hello, World!"); diff --git a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewHelpTests.CanShowHelpForTemplate_MatchOnLanguage.verified.txt b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewHelpTests.CanShowHelpForTemplate_MatchOnLanguage.verified.txt index 87260c7bdb85..5dfcc3d92e4d 100644 --- a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewHelpTests.CanShowHelpForTemplate_MatchOnLanguage.verified.txt +++ b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewHelpTests.CanShowHelpForTemplate_MatchOnLanguage.verified.txt @@ -1,6 +1,6 @@ Console App (F#) Author: Microsoft -Description: A project for creating a command-line application that can run on .NET on Windows, Linux and macOS +Description: A project for creating an empty command-line application that can run on .NET on Windows, Linux and macOS Usage: dotnet new console [options] [template options] diff --git a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewHelpTests.CanShowHelpForTemplate_MatchOnNonChoiceParam.verified.txt b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewHelpTests.CanShowHelpForTemplate_MatchOnNonChoiceParam.verified.txt index ab5fdcab62f6..e585bde447d0 100644 --- a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewHelpTests.CanShowHelpForTemplate_MatchOnNonChoiceParam.verified.txt +++ b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewHelpTests.CanShowHelpForTemplate_MatchOnNonChoiceParam.verified.txt @@ -1,6 +1,6 @@ Console App (C#) Author: Microsoft -Description: A project for creating a command-line application that can run on .NET on Windows, Linux and macOS +Description: A project for creating an empty command-line application that can run on .NET on Windows, Linux and macOS Usage: dotnet new console [options] [template options] diff --git a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewHelpTests.CanShowHelpForTemplate_console.verified.txt b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewHelpTests.CanShowHelpForTemplate_console.verified.txt index ab5fdcab62f6..e585bde447d0 100644 --- a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewHelpTests.CanShowHelpForTemplate_console.verified.txt +++ b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewHelpTests.CanShowHelpForTemplate_console.verified.txt @@ -1,6 +1,6 @@ Console App (C#) Author: Microsoft -Description: A project for creating a command-line application that can run on .NET on Windows, Linux and macOS +Description: A project for creating an empty command-line application that can run on .NET on Windows, Linux and macOS Usage: dotnet new console [options] [template options] diff --git a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewInstallTests.CanInstallPackageAvailableFromBuiltInsWithForce.verified.txt b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewInstallTests.CanInstallPackageAvailableFromBuiltInsWithForce.verified.txt index 144ea5cc69e7..9ee051e49339 100644 --- a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewInstallTests.CanInstallPackageAvailableFromBuiltInsWithForce.verified.txt +++ b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewInstallTests.CanInstallPackageAvailableFromBuiltInsWithForce.verified.txt @@ -3,7 +3,7 @@ Installing the template package(s) will override the available template package(s). The following template package(s) are already available: - Microsoft.DotNet.Common.ItemTemplates::%VERSION% + Microsoft.DotNet.Common.ItemTemplates@%VERSION% Success: Microsoft.DotNet.Common.ItemTemplates::6.0.100 installed the following templates: Template Name Short Name Language Tags diff --git a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewInstallTests.CannotInstallPackageAvailableFromBuiltIns.verified.txt b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewInstallTests.CannotInstallPackageAvailableFromBuiltIns.verified.txt index d8b4586ef0eb..1006e1eb33c6 100644 --- a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewInstallTests.CannotInstallPackageAvailableFromBuiltIns.verified.txt +++ b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewInstallTests.CannotInstallPackageAvailableFromBuiltIns.verified.txt @@ -1,6 +1,6 @@ Installing the template package(s) will override the available template package(s). The following template package(s) are already available: - Microsoft.DotNet.Common.ItemTemplates::%VERSION% + Microsoft.DotNet.Common.ItemTemplates@%VERSION% To install the template package(s) anyway, apply '--force' option: dotnet new install Microsoft.DotNet.Common.ItemTemplates::6.0.100 --force diff --git a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewInstantiateTests.CanShowWarningIfPackageIsAvailableFromBuiltInSources.verified.txt b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewInstantiateTests.CanShowWarningIfPackageIsAvailableFromBuiltInSources.verified.txt index a847f252abcc..0c3ad4b27637 100644 --- a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewInstantiateTests.CanShowWarningIfPackageIsAvailableFromBuiltInSources.verified.txt +++ b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewInstantiateTests.CanShowWarningIfPackageIsAvailableFromBuiltInSources.verified.txt @@ -1,9 +1,9 @@ The template "dotnet gitignore file" was created successfully. -An update for template package 'Microsoft.DotNet.Common.ItemTemplates::6.0.100' is available. +An update for template package 'Microsoft.DotNet.Common.ItemTemplates@6.0.100' is available. To update the package use: - dotnet new install Microsoft.DotNet.Common.ItemTemplates::%VERSION% + dotnet new install Microsoft.DotNet.Common.ItemTemplates@%VERSION% -An update for template package 'Microsoft.DotNet.Common.ItemTemplates::%VERSION%' is available in the '.NET SDK' provider. +An update for template package 'Microsoft.DotNet.Common.ItemTemplates@%VERSION%' is available in the '.NET SDK' provider. To use built-in template package, uninstall manually installed template package using: dotnet new uninstall Microsoft.DotNet.Common.ItemTemplates \ No newline at end of file diff --git a/test/dotnet-new.IntegrationTests/Approvals/FeaturesSupport.console.Nullable-True#TopLevel-True#ImplicitUsings-False#FileScopedNs-False#cs.verified/MyProject/Program.cs b/test/dotnet-new.IntegrationTests/Approvals/FeaturesSupport.console.Nullable-True#TopLevel-True#ImplicitUsings-False#FileScopedNs-False#cs.verified/MyProject/Program.cs index ea0a451f4903..26e5e42eee3b 100644 --- a/test/dotnet-new.IntegrationTests/Approvals/FeaturesSupport.console.Nullable-True#TopLevel-True#ImplicitUsings-False#FileScopedNs-False#cs.verified/MyProject/Program.cs +++ b/test/dotnet-new.IntegrationTests/Approvals/FeaturesSupport.console.Nullable-True#TopLevel-True#ImplicitUsings-False#FileScopedNs-False#cs.verified/MyProject/Program.cs @@ -1,4 +1,3 @@ -// See https://aka.ms/new-console-template for more information -using System; +using System; Console.WriteLine("Hello, World!"); diff --git a/test/dotnet-new.IntegrationTests/Approvals/FeaturesSupport.console.Nullable-True#TopLevel-True#ImplicitUsings-True#FileScopedNs-True#cs#NoLangVer.verified/MyProject/Program.cs b/test/dotnet-new.IntegrationTests/Approvals/FeaturesSupport.console.Nullable-True#TopLevel-True#ImplicitUsings-True#FileScopedNs-True#cs#NoLangVer.verified/MyProject/Program.cs index 3751555cbd32..1bc52a60a8ba 100644 --- a/test/dotnet-new.IntegrationTests/Approvals/FeaturesSupport.console.Nullable-True#TopLevel-True#ImplicitUsings-True#FileScopedNs-True#cs#NoLangVer.verified/MyProject/Program.cs +++ b/test/dotnet-new.IntegrationTests/Approvals/FeaturesSupport.console.Nullable-True#TopLevel-True#ImplicitUsings-True#FileScopedNs-True#cs#NoLangVer.verified/MyProject/Program.cs @@ -1,2 +1 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +Console.WriteLine("Hello, World!"); diff --git a/test/dotnet-new.IntegrationTests/Approvals/FeaturesSupport.console.Nullable-True#TopLevel-True#ImplicitUsings-True#FileScopedNs-True#cs.verified/MyProject/Program.cs b/test/dotnet-new.IntegrationTests/Approvals/FeaturesSupport.console.Nullable-True#TopLevel-True#ImplicitUsings-True#FileScopedNs-True#cs.verified/MyProject/Program.cs index 3751555cbd32..1bc52a60a8ba 100644 --- a/test/dotnet-new.IntegrationTests/Approvals/FeaturesSupport.console.Nullable-True#TopLevel-True#ImplicitUsings-True#FileScopedNs-True#cs.verified/MyProject/Program.cs +++ b/test/dotnet-new.IntegrationTests/Approvals/FeaturesSupport.console.Nullable-True#TopLevel-True#ImplicitUsings-True#FileScopedNs-True#cs.verified/MyProject/Program.cs @@ -1,2 +1 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +Console.WriteLine("Hello, World!"); diff --git a/test/dotnet-new.IntegrationTests/DotnetNewInstallTests.Approval.cs b/test/dotnet-new.IntegrationTests/DotnetNewInstallTests.Approval.cs index f7911dc484a9..9b068c679759 100644 --- a/test/dotnet-new.IntegrationTests/DotnetNewInstallTests.Approval.cs +++ b/test/dotnet-new.IntegrationTests/DotnetNewInstallTests.Approval.cs @@ -22,7 +22,7 @@ public Task CannotInstallPackageAvailableFromBuiltIns() return Verify(commandResult.StdErr) .AddScrubber(output => { - output.ScrubByRegex(" Microsoft\\.DotNet\\.Common\\.ItemTemplates::[A-Za-z0-9.-]+", " Microsoft.DotNet.Common.ItemTemplates::%VERSION%"); + output.ScrubByRegex(" Microsoft\\.DotNet\\.Common\\.ItemTemplates@[A-Za-z0-9.-]+", " Microsoft.DotNet.Common.ItemTemplates@%VERSION%"); }); } @@ -41,7 +41,8 @@ public Task CanInstallPackageAvailableFromBuiltInsWithForce() return Verify(commandResult.StdOut) .AddScrubber(output => { - output.ScrubByRegex(" Microsoft.DotNet.Common.ItemTemplates::[A-Za-z0-9.-]+", " Microsoft.DotNet.Common.ItemTemplates::%VERSION%"); + output.ScrubByRegex(" Microsoft\\.DotNet\\.Common\\.ItemTemplates::[A-Za-z0-9.-]+", " Microsoft.DotNet.Common.ItemTemplates::%VERSION%"); + output.ScrubByRegex(" Microsoft\\.DotNet\\.Common\\.ItemTemplates@[A-Za-z0-9.-]+", " Microsoft.DotNet.Common.ItemTemplates@%VERSION%"); }); } @@ -60,7 +61,7 @@ public Task CannotInstallMultiplePackageAvailableFromBuiltIns() return Verify(commandResult.StdErr) .AddScrubber(output => { - output.ScrubByRegex(" Microsoft\\.DotNet\\.Common\\.ItemTemplates::[A-Za-z0-9.-]+", " Microsoft.DotNet.Common.ItemTemplates@%VERSION%"); + output.ScrubByRegex(" Microsoft\\.DotNet\\.Common\\.ItemTemplates@[A-Za-z0-9.-]+", " Microsoft.DotNet.Common.ItemTemplates@%VERSION%"); }); } diff --git a/test/dotnet-new.IntegrationTests/DotnetNewInstantiateTests.Approval.cs b/test/dotnet-new.IntegrationTests/DotnetNewInstantiateTests.Approval.cs index 4122766c2f1f..866a93af2825 100644 --- a/test/dotnet-new.IntegrationTests/DotnetNewInstantiateTests.Approval.cs +++ b/test/dotnet-new.IntegrationTests/DotnetNewInstantiateTests.Approval.cs @@ -512,8 +512,8 @@ public Task CanShowWarningIfPackageIsAvailableFromBuiltInSources() return Verify(commandResult.StdOut) .AddScrubber(output => { - output.ScrubByRegex("'Microsoft\\.DotNet\\.Common\\.ItemTemplates::[A-Za-z0-9.-]+' is available in", "'Microsoft.DotNet.Common.ItemTemplates::%VERSION%' is available in"); - output.ScrubByRegex("install Microsoft\\.DotNet\\.Common\\.ItemTemplates::[A-Za-z0-9.-]+", "install Microsoft.DotNet.Common.ItemTemplates::%VERSION%"); + output.ScrubByRegex("'Microsoft\\.DotNet\\.Common\\.ItemTemplates@[A-Za-z0-9.-]+' is available in", "'Microsoft.DotNet.Common.ItemTemplates@%VERSION%' is available in"); + output.ScrubByRegex("install Microsoft\\.DotNet\\.Common\\.ItemTemplates@[A-Za-z0-9.-]+", "install Microsoft.DotNet.Common.ItemTemplates@%VERSION%"); }); } diff --git a/test/dotnet-new.IntegrationTests/DotnetNewUpdateTests.cs b/test/dotnet-new.IntegrationTests/DotnetNewUpdateTests.cs index d69773a417eb..61eaf09f8815 100644 --- a/test/dotnet-new.IntegrationTests/DotnetNewUpdateTests.cs +++ b/test/dotnet-new.IntegrationTests/DotnetNewUpdateTests.cs @@ -43,8 +43,8 @@ public void CanCheckForUpdate(string testCase) .And.HaveStdOutMatching("Package\\s+Current\\s+Latest") .And.HaveStdOutMatching("Microsoft.DotNet.Common.ProjectTemplates.5.0\\s+5.0.0\\s+([\\d\\.a-z-])+") .And.HaveStdOutContaining("To update the package use:") - .And.HaveStdOutContaining(" dotnet new install ::") - .And.HaveStdOutMatching(" dotnet new install Microsoft\\.DotNet\\.Common\\.ProjectTemplates\\.5\\.0::([\\d\\.a-z-])+"); + .And.HaveStdOutContaining(" dotnet new install @") + .And.HaveStdOutMatching(" dotnet new install Microsoft\\.DotNet\\.Common\\.ProjectTemplates\\.5\\.0@([\\d\\.a-z-])+"); } [Fact] @@ -132,9 +132,9 @@ public void PrintInfoOnUpdateOnCreation() .And .NotHaveStdErr() .And.HaveStdOutContaining("The template \"Console Application\" was created successfully.") - .And.HaveStdOutContaining("An update for template package 'Microsoft.DotNet.Common.ProjectTemplates.5.0::5.0.0' is available") + .And.HaveStdOutContaining("An update for template package 'Microsoft.DotNet.Common.ProjectTemplates.5.0@5.0.0' is available") .And.HaveStdOutContaining("To update the package use:") - .And.HaveStdOutMatching(" dotnet new install Microsoft.DotNet.Common.ProjectTemplates.5.0::([\\d\\.a-z-])+"); + .And.HaveStdOutMatching(" dotnet new install Microsoft.DotNet.Common.ProjectTemplates.5.0@([\\d\\.a-z-])+"); } [Fact] @@ -162,9 +162,9 @@ public void DoesNotPrintUpdateInfoOnCreation_WhenNoUpdateCheckOption() .And .NotHaveStdErr() .And.HaveStdOutContaining("The template \"Console Application\" was created successfully.") - .And.NotHaveStdOutContaining("An update for template package 'Microsoft.DotNet.Common.ProjectTemplates.5.0::5.0.0' is available") + .And.NotHaveStdOutContaining("An update for template package 'Microsoft.DotNet.Common.ProjectTemplates.5.0@5.0.0' is available") .And.NotHaveStdOutContaining("To update the package use:") - .And.NotHaveStdOutContaining(" dotnet new --install Microsoft.DotNet.Common.ProjectTemplates.5.0::([\\d\\.a-z-])+"); + .And.NotHaveStdOutContaining(" dotnet new --install Microsoft.DotNet.Common.ProjectTemplates.5.0@([\\d\\.a-z-])+"); new DotnetNewCommand(_log, "console", "-o", "update-check") .WithCustomHive(home).WithoutBuiltInTemplates() @@ -175,9 +175,9 @@ public void DoesNotPrintUpdateInfoOnCreation_WhenNoUpdateCheckOption() .And .NotHaveStdErr() .And.HaveStdOutContaining("The template \"Console Application\" was created successfully.") - .And.HaveStdOutContaining("An update for template package 'Microsoft.DotNet.Common.ProjectTemplates.5.0::5.0.0' is available") + .And.HaveStdOutContaining("An update for template package 'Microsoft.DotNet.Common.ProjectTemplates.5.0@5.0.0' is available") .And.HaveStdOutContaining("To update the package use:") - .And.HaveStdOutMatching(" dotnet new install Microsoft.DotNet.Common.ProjectTemplates.5.0::([\\d\\.a-z-])+"); + .And.HaveStdOutMatching(" dotnet new install Microsoft.DotNet.Common.ProjectTemplates.5.0@([\\d\\.a-z-])+"); } [Fact] @@ -205,9 +205,9 @@ public void DoesNotPrintUpdateInfoOnCreation_WhenLatestVersionIsInstalled() .And .NotHaveStdErr() .And.HaveStdOutContaining("The template \"Console App\" was created successfully.") - .And.NotHaveStdOutMatching("An update for template package 'Microsoft.DotNet.Common.ProjectTemplates.5.0::([\\d\\.a-z-])+' is available") + .And.NotHaveStdOutMatching("An update for template package 'Microsoft.DotNet.Common.ProjectTemplates.5.0@([\\d\\.a-z-])+' is available") .And.NotHaveStdOutContaining("To update the package use:") - .And.NotHaveStdOutMatching(" dotnet new install Microsoft.DotNet.Common.ProjectTemplates.5.0::([\\d\\.a-z-])+"); + .And.NotHaveStdOutMatching(" dotnet new install Microsoft.DotNet.Common.ProjectTemplates.5.0@([\\d\\.a-z-])+"); } [Fact] @@ -275,8 +275,8 @@ public void CanApplyUpdates(string testCase) .And.HaveStdOutMatching("Package\\s+Current\\s+Latest") .And.HaveStdOutMatching("Microsoft.DotNet.Common.ProjectTemplates.5.0\\s+5.0.0\\s+([\\d\\.a-z-])+") .And.HaveStdOutContaining("To update the package use:") - .And.HaveStdOutContaining(" dotnet new install ::") - .And.HaveStdOutMatching(" dotnet new install Microsoft\\.DotNet\\.Common\\.ProjectTemplates\\.5\\.0::([\\d\\.a-z-])+"); + .And.HaveStdOutContaining(" dotnet new install @") + .And.HaveStdOutMatching(" dotnet new install Microsoft\\.DotNet\\.Common\\.ProjectTemplates\\.5\\.0@([\\d\\.a-z-])+"); new DotnetNewCommand(_log, testCase) .WithCustomHive(home).WithoutBuiltInTemplates() diff --git a/test/dotnet-new.IntegrationTests/TestDirectoryBuildFiles/Directory.Build.props b/test/dotnet-new.IntegrationTests/TestDirectoryBuildFiles/Directory.Build.props index b5f441385aef..635af35f8509 100644 --- a/test/dotnet-new.IntegrationTests/TestDirectoryBuildFiles/Directory.Build.props +++ b/test/dotnet-new.IntegrationTests/TestDirectoryBuildFiles/Directory.Build.props @@ -1,8 +1,8 @@ - - + false + diff --git a/test/dotnet-new.IntegrationTests/TestDirectoryBuildFiles/Directory.Build.targets b/test/dotnet-new.IntegrationTests/TestDirectoryBuildFiles/Directory.Build.targets index 39af617d4232..058246e40862 100644 --- a/test/dotnet-new.IntegrationTests/TestDirectoryBuildFiles/Directory.Build.targets +++ b/test/dotnet-new.IntegrationTests/TestDirectoryBuildFiles/Directory.Build.targets @@ -1,4 +1 @@ - - - - + diff --git a/test/dotnet-watch.Tests/Browser/BrowserTests.cs b/test/dotnet-watch.Tests/Browser/BrowserTests.cs index d1c302337036..17e21166bf3f 100644 --- a/test/dotnet-watch.Tests/Browser/BrowserTests.cs +++ b/test/dotnet-watch.Tests/Browser/BrowserTests.cs @@ -65,7 +65,7 @@ public async Task BrowserDiagnostics() await App.WaitForOutputLineContaining("Do you want to restart your app?"); await App.WaitUntilOutputContains($$""" - 🧪 Received: {"type":"HotReloadDiagnosticsv1","diagnostics":[{{jsonErrorMessage}}]} + 🧪 Received: {"type":"ReportDiagnostics","diagnostics":[{{jsonErrorMessage}}]} """); // auto restart next time: @@ -76,7 +76,7 @@ await App.WaitUntilOutputContains($$""" // browser page was reloaded after the app restarted: await App.WaitUntilOutputContains(""" - 🧪 Received: Reload + 🧪 Received: {"type":"Reload"} """); // no other browser message sent: @@ -93,14 +93,14 @@ await App.WaitUntilOutputContains(""" await App.WaitForOutputLineContaining("[auto-restart] " + errorMessage); await App.WaitUntilOutputContains($$""" - 🧪 Received: {"type":"HotReloadDiagnosticsv1","diagnostics":["Restarting application to apply changes ..."]} + 🧪 Received: {"type":"ReportDiagnostics","diagnostics":["Restarting application to apply changes ..."]} """); await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); // browser page was reloaded after the app restarted: await App.WaitUntilOutputContains(""" - 🧪 Received: Reload + 🧪 Received: {"type":"Reload"} """); // no other browser message sent: @@ -114,11 +114,11 @@ await App.WaitUntilOutputContains(""" await App.WaitForOutputLineContaining(MessageDescriptor.HotReloadSucceeded); await App.WaitUntilOutputContains($$""" - 🧪 Received: {"type":"HotReloadDiagnosticsv1","diagnostics":[]} + 🧪 Received: {"type":"ReportDiagnostics","diagnostics":[]} """); await App.WaitUntilOutputContains($$""" - 🧪 Received: {"type":"AspNetCoreHotReloadApplied"} + 🧪 Received: {"type":"RefreshBrowser"} """); // no other browser message sent: diff --git a/test/dotnet-watch.Tests/CommandLine/CommandLineOptionsTests.cs b/test/dotnet-watch.Tests/CommandLine/CommandLineOptionsTests.cs index 768eb47b262b..d98140e48889 100644 --- a/test/dotnet-watch.Tests/CommandLine/CommandLineOptionsTests.cs +++ b/test/dotnet-watch.Tests/CommandLine/CommandLineOptionsTests.cs @@ -481,16 +481,19 @@ public void ForwardedBuildOptions_Run(string[] args, string[] buildArgs, string[ } [Theory] - [InlineData(new[] { "--property:b=1" }, new[] { "--property:b=1" }, Skip = "https://github.com/dotnet/sdk/issues/44655")] - [InlineData(new[] { "--property", "b=1" }, new[] { "--property", "b=1" }, Skip = "https://github.com/dotnet/sdk/issues/44655")] - [InlineData(new[] { "/p:b=1" }, new[] { "/p:b=1" }, Skip = "https://github.com/dotnet/sdk/issues/44655")] + [InlineData(new[] { "--property:b=1" }, new[] { "--property:b=1" })] + [InlineData(new[] { "--property", "b=1" }, new[] { "--property:b=1" })] + [InlineData(new[] { "/p:b=1" }, new[] { "--property:b=1" })] [InlineData(new[] { "/bl" }, new[] { "/bl" })] [InlineData(new[] { "--binaryLogger:LogFile=output.binlog;ProjectImports=None" }, new[] { "--binaryLogger:LogFile=output.binlog;ProjectImports=None" })] public void ForwardedBuildOptions_Test(string[] args, string[] commandArgs) { + var isProperty = args[0].Contains("-p") || args[0].Contains("/p"); var runOptions = VerifyOptions(["test", .. args]); - AssertEx.SequenceEqual(["--property:NuGetInteractive=false", "--target:VSTest", .. commandArgs], runOptions.BuildArguments); - AssertEx.SequenceEqual(commandArgs, runOptions.CommandArguments); + string[] expected = isProperty + ? ["--property:VSTestNoLogo=true", "--property:NuGetInteractive=false", .. commandArgs, "--target:VSTest"] + : ["--property:VSTestNoLogo=true", "--property:NuGetInteractive=false", "--target:VSTest", .. commandArgs]; + AssertEx.SequenceEqual(expected, runOptions.BuildArguments); } [Fact] diff --git a/test/dotnet-watch.Tests/Directory.Build.targets b/test/dotnet-watch.Tests/Directory.Build.targets index 6ae79f4c0f1b..03f8eb02206d 100644 --- a/test/dotnet-watch.Tests/Directory.Build.targets +++ b/test/dotnet-watch.Tests/Directory.Build.targets @@ -1,5 +1,5 @@ - + diff --git a/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs b/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs index 501ce980b20f..833b2bc5a6c8 100644 --- a/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs +++ b/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs @@ -440,7 +440,7 @@ public async Task AutoRestartOnRudeEdit(bool nonInteractive) await App.WaitForOutputLineContaining(MessageDescriptor.HotReloadSucceeded); } - [Theory] + [Theory(Skip = "https://github.com/dotnet/sdk/issues/51469")] [CombinatorialData] public async Task AutoRestartOnRuntimeRudeEdit(bool nonInteractive) { @@ -755,7 +755,7 @@ class AppUpdateHandler App.DotnetWatchArgs.Clear(); } - App.Start(testAsset, [], testFlags: TestFlags.ElevateWaitingForChangesMessageSeverity); + App.Start(testAsset, []); await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); diff --git a/test/dotnet-watch.Tests/HotReload/CompilationHandlerTests.cs b/test/dotnet-watch.Tests/HotReload/CompilationHandlerTests.cs index 9ac5030c4eba..4081a1e2102c 100644 --- a/test/dotnet-watch.Tests/HotReload/CompilationHandlerTests.cs +++ b/test/dotnet-watch.Tests/HotReload/CompilationHandlerTests.cs @@ -24,7 +24,8 @@ public async Task ReferenceOutputAssembly_False() var reporter = new TestReporter(Logger); var loggerFactory = new LoggerFactory(reporter); var logger = loggerFactory.CreateLogger("Test"); - var projectGraph = ProjectGraphUtilities.TryLoadProjectGraph(options.ProjectPath, globalOptions: [], logger, projectGraphRequired: false, CancellationToken.None); + var factory = new ProjectGraphFactory(globalOptions: []); + var projectGraph = factory.TryLoadProjectGraph(options.ProjectPath, logger, projectGraphRequired: false, CancellationToken.None); var handler = new CompilationHandler(logger, processRunner); await handler.Workspace.UpdateProjectConeAsync(hostProject, CancellationToken.None); diff --git a/test/dotnet.Tests/CliSchemaTests.cs b/test/dotnet.Tests/CliSchemaTests.cs index 18cccbd90296..78f3d668607e 100644 --- a/test/dotnet.Tests/CliSchemaTests.cs +++ b/test/dotnet.Tests/CliSchemaTests.cs @@ -182,12 +182,17 @@ public CliSchemaTests(ITestOutputHelper log) : base(log) "required": false, "recursive": false }, - "--nologo": { + "--no-logo": { "description": "Do not display the startup banner or the copyright message.", "hidden": false, + "aliases": [ + "--nologo", + "-nologo", + "/nologo" + ], "valueType": "System.Boolean", "hasDefaultValue": true, - "defaultValue": false, + "defaultValue": true, "arity": { "minimum": 0, "maximum": 0 @@ -964,12 +969,17 @@ public CliSchemaTests(ITestOutputHelper log) : base(log) "required": false, "recursive": false }, - "--no-restore": { - "description": "Do not restore the project before building.", + "--no-logo": { + "description": "Do not display the startup banner or the copyright message.", "hidden": false, + "aliases": [ + "--nologo", + "-nologo", + "/nologo" + ], "valueType": "System.Boolean", "hasDefaultValue": true, - "defaultValue": false, + "defaultValue": true, "arity": { "minimum": 0, "maximum": 0 @@ -977,8 +987,8 @@ public CliSchemaTests(ITestOutputHelper log) : base(log) "required": false, "recursive": false }, - "--no-self-contained": { - "description": "Publish your application as a framework dependent application. A compatible .NET runtime must be installed on the target machine to run your application.", + "--no-restore": { + "description": "Do not restore the project before building.", "hidden": false, "valueType": "System.Boolean", "hasDefaultValue": true, @@ -990,8 +1000,8 @@ public CliSchemaTests(ITestOutputHelper log) : base(log) "required": false, "recursive": false }, - "--nologo": { - "description": "Do not display the startup banner or the copyright message.", + "--no-self-contained": { + "description": "Publish your application as a framework dependent application. A compatible .NET runtime must be installed on the target machine to run your application.", "hidden": false, "valueType": "System.Boolean", "hasDefaultValue": true, diff --git a/test/dotnet.Tests/CommandTests/Build/GivenDotnetBuildBuildsCsproj.cs b/test/dotnet.Tests/CommandTests/Build/GivenDotnetBuildBuildsCsproj.cs index 2660810b616b..1130d1471133 100644 --- a/test/dotnet.Tests/CommandTests/Build/GivenDotnetBuildBuildsCsproj.cs +++ b/test/dotnet.Tests/CommandTests/Build/GivenDotnetBuildBuildsCsproj.cs @@ -47,7 +47,7 @@ public void ItBuildsOnlyTheSpecifiedTarget() .WithSource(); new DotnetBuildCommand(Log, testInstance.Path) - .Execute("--no-restore", "--nologo", "/t:PrintMessage") + .Execute("--no-restore", "--no-logo", "/t:PrintMessage") .Should() .Pass() .And @@ -176,7 +176,7 @@ public void DotnetBuildDoesNotPrintCopyrightInfo() var cmd = new DotnetBuildCommand(Log) .WithWorkingDirectory(testInstance.Path) - .Execute("--nologo"); + .Execute("--no-logo"); cmd.Should().Pass(); diff --git a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetBuildInvocation.cs b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetBuildInvocation.cs index 8b6c7de5b074..07218d519ca8 100644 --- a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetBuildInvocation.cs +++ b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetBuildInvocation.cs @@ -11,7 +11,7 @@ namespace Microsoft.DotNet.Cli.MSBuild.Tests [Collection(TestConstants.UsesStaticTelemetryState)] public class GivenDotnetBuildInvocation : IClassFixture { - string[] ExpectedPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "-nologo"]; + string[] ExpectedPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "--nologo"]; public static string[] RestoreExpectedPrefixForImplicitRestore = [.. RestoringCommand.RestoreOptimizationProperties.Select(kvp => $"--restoreProperty:{kvp.Key}={kvp.Value}")]; public static string[] RestoreExpectedPrefixForSeparateRestore = [.. RestoringCommand.RestoreOptimizationProperties.Select(kvp => $"--property:{kvp.Key}={kvp.Value}")]; @@ -92,7 +92,7 @@ public void NoRestoreMeansNoSeparateRestoreCommand() new string[] { "--target:Restore", "-tlp:verbosity=quiet", "--verbosity:diag", "--property:OutputPath=myoutput", "--property:_CommandLineDefinedOutputPath=true", "/ArbitrarySwitchForMSBuild" }, new string[] { "--property:TargetFramework=tfm", "--verbosity:diag", "--property:OutputPath=myoutput", "--property:_CommandLineDefinedOutputPath=true", "/ArbitrarySwitchForMSBuild" })] [InlineData(new string[] { "-f", "tfm", "-getItem:Compile", "-getProperty:TargetFramework", "-getTargetResult:Build" }, - new string[] { "--target:Restore", "-tlp:verbosity=quiet", "-nologo", "--verbosity:quiet" }, + new string[] { "--target:Restore", "-tlp:verbosity=quiet", "--nologo", "--verbosity:quiet" }, new string[] { "--property:TargetFramework=tfm", "--getItem:Compile", "--getProperty:TargetFramework", "--getTargetResult:Build" })] public void MsbuildInvocationIsCorrectForSeparateRestore( string[] args, diff --git a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetCleanInvocation.cs b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetCleanInvocation.cs index 270c2d49091b..2f0fc032a657 100644 --- a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetCleanInvocation.cs +++ b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetCleanInvocation.cs @@ -9,7 +9,7 @@ namespace Microsoft.DotNet.Cli.MSBuild.Tests public class GivenDotnetCleanInvocation : IClassFixture { private const string NugetInteractiveProperty = "--property:NuGetInteractive=false"; - private static readonly string[] ExpectedPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "-nologo", "--verbosity:normal", "--target:Clean", NugetInteractiveProperty]; + private static readonly string[] ExpectedPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "--nologo", "--verbosity:normal", "--target:Clean", NugetInteractiveProperty]; private static readonly string WorkingDirectory = diff --git a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetMSBuildInvocation.cs b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetMSBuildInvocation.cs index 10df57407a78..5c79115e1dc3 100644 --- a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetMSBuildInvocation.cs +++ b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetMSBuildInvocation.cs @@ -8,23 +8,19 @@ namespace Microsoft.DotNet.Cli.MSBuild.Tests [Collection(TestConstants.UsesStaticTelemetryState)] public class GivenDotnetMSBuildInvocation : IClassFixture { - private const string ExpectedPrefix = "-maxcpucount --verbosity:m -tlp:default=auto"; + private static readonly string[] ExpectedPrefix = [ "-maxcpucount", "--verbosity:m", "-tlp:default=auto" ]; private static readonly string WorkingDirectory = TestPathUtilities.FormatAbsolutePath(nameof(GivenDotnetPackInvocation)); [Theory] - [InlineData(new string[] { "--disable-build-servers" }, "--property:UseRazorBuildServer=false --property:UseSharedCompilation=false /nodeReuse:false")] - public void MsbuildInvocationIsCorrect(string[] args, string expectedAdditionalArgs) + [InlineData(new string[] { "--disable-build-servers" }, new string[] { "--property:UseRazorBuildServer=false", "--property:UseSharedCompilation=false", "/nodeReuse:false" })] + public void MsbuildInvocationIsCorrect(string[] args, string[] expectedAdditionalArgs) { CommandDirectoryContext.PerformActionWithBasePath(WorkingDirectory, () => { - expectedAdditionalArgs = - (string.IsNullOrEmpty(expectedAdditionalArgs) ? "" : $" {expectedAdditionalArgs}") - .Replace("", WorkingDirectory); - var msbuildPath = ""; var command = MSBuildCommand.FromArgs(args, msbuildPath); - command.GetArgumentsToMSBuild().Should().Be($"{ExpectedPrefix}{expectedAdditionalArgs}"); + command.GetArgumentTokensToMSBuild().Should().BeEquivalentTo([..ExpectedPrefix, ..expectedAdditionalArgs]); }); } } diff --git a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetOsArchOptions.cs b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetOsArchOptions.cs index 4b98db3a7b58..e001863d51a6 100644 --- a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetOsArchOptions.cs +++ b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetOsArchOptions.cs @@ -14,7 +14,7 @@ public GivenDotnetOsArchOptions(ITestOutputHelper log) : base(log) { } - private static readonly string[] ExpectedPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "-nologo"]; + private static readonly string[] ExpectedPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "--nologo"]; private const string NugetInteractiveProperty = "--property:NuGetInteractive=false"; private static readonly string[] DefaultArgs = ["-restore", "-consoleloggerparameters:Summary", NugetInteractiveProperty]; diff --git a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetPackInvocation.cs b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetPackInvocation.cs index 7fd85cf6e177..073c66228a72 100644 --- a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetPackInvocation.cs +++ b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetPackInvocation.cs @@ -8,8 +8,8 @@ namespace Microsoft.DotNet.Cli.MSBuild.Tests [Collection(TestConstants.UsesStaticTelemetryState)] public class GivenDotnetPackInvocation : IClassFixture { - private static readonly string[] ExpectedPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "-nologo", "-restore", "--target:Pack"]; - private static readonly string[] ExpectedNoBuildPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "-nologo", "--target:Pack"]; + private static readonly string[] ExpectedPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "--nologo", "-restore", "--target:Pack"]; + private static readonly string[] ExpectedNoBuildPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "--nologo", "--target:Pack"]; private readonly string[] ExpectedProperties = ["--property:_IsPacking=true", "--property:NuGetInteractive=false"]; private static readonly string WorkingDirectory = diff --git a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetPublishInvocation.cs b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetPublishInvocation.cs index 3b08a994c3c9..31df36e59e74 100644 --- a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetPublishInvocation.cs +++ b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetPublishInvocation.cs @@ -17,7 +17,7 @@ public GivenDotnetPublishInvocation(ITestOutputHelper output) this.output = output; } - private static readonly string[] ExpectedPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "-nologo"]; + private static readonly string[] ExpectedPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "--nologo"]; private static readonly string[] ExpectedProperties = ["--property:_IsPublishing=true"]; private static readonly string NuGetDisabledProperty = "--property:NuGetInteractive=false"; diff --git a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetRestoreInvocation.cs b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetRestoreInvocation.cs index 89c9c3dbccc2..9b4dca228442 100644 --- a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetRestoreInvocation.cs +++ b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetRestoreInvocation.cs @@ -9,7 +9,7 @@ namespace Microsoft.DotNet.Cli.MSBuild.Tests [Collection(TestConstants.UsesStaticTelemetryState)] public class GivenDotnetRestoreInvocation : IClassFixture { - private static readonly string[] ExpectedPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "-nologo", "--target:Restore"]; + private static readonly string[] ExpectedPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "--nologo", "--target:Restore"]; private static readonly string NuGetDisabledProperty = "--property:NuGetInteractive=false"; private static readonly string WorkingDirectory = TestPathUtilities.FormatAbsolutePath(nameof(GivenDotnetRestoreInvocation)); diff --git a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetRunInvocation.cs b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetRunInvocation.cs index 36c8fe45bc19..0bd410308b59 100644 --- a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetRunInvocation.cs +++ b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetRunInvocation.cs @@ -9,7 +9,7 @@ namespace Microsoft.DotNet.Cli.MSBuild.Tests [Collection(TestConstants.UsesStaticTelemetryState)] public class GivenDotnetRunInvocation : IClassFixture { - private static readonly string[] ConstantRestoreArgs = ["-nologo", "--verbosity:quiet"]; + private static readonly string[] ConstantRestoreArgs = ["--nologo", "--verbosity:quiet"]; private static readonly string NuGetDisabledProperty = "--property:NuGetInteractive=false"; public ITestOutputHelper Log { get; } @@ -43,7 +43,7 @@ public void MsbuildInvocationIsCorrect(string[] args, string[] expectedArgs) var command = RunCommand.FromArgs(args); command.MSBuildArgs .Should() - .BeEquivalentTo(MSBuildArgs.AnalyzeMSBuildArguments([.. ConstantRestoreArgs, .. expectedArgs, NuGetDisabledProperty ], CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, CommonOptions.MSBuildTargetOption(), RunCommandParser.VerbosityOption)); + .BeEquivalentTo(MSBuildArgs.AnalyzeMSBuildArguments([.. ConstantRestoreArgs, .. expectedArgs, NuGetDisabledProperty ], CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, CommonOptions.MSBuildTargetOption(), RunCommandParser.VerbosityOption, CommonOptions.NoLogoOption())); }); } finally diff --git a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetStoreInvocation.cs b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetStoreInvocation.cs index f13c7258f291..c8e6f93bdbb9 100644 --- a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetStoreInvocation.cs +++ b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetStoreInvocation.cs @@ -8,7 +8,7 @@ namespace Microsoft.DotNet.Cli.MSBuild.Tests [Collection(TestConstants.UsesStaticTelemetryState)] public class GivenDotnetStoreInvocation : IClassFixture { - string[] ExpectedPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "-nologo", "--target:ComposeStore", ""]; + string[] ExpectedPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "--nologo", "--target:ComposeStore", ""]; static readonly string[] ArgsPrefix = ["--manifest", ""]; private static readonly string WorkingDirectory = TestPathUtilities.FormatAbsolutePath(nameof(GivenDotnetStoreInvocation)); diff --git a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetTestInvocation.cs b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetTestInvocation.cs index 601c1e5d2c0d..c017646949b0 100644 --- a/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetTestInvocation.cs +++ b/test/dotnet.Tests/CommandTests/MSBuild/GivenDotnetTestInvocation.cs @@ -8,7 +8,7 @@ namespace Microsoft.DotNet.Cli.MSBuild.Tests [Collection(TestConstants.UsesStaticTelemetryState)] public class GivenDotnetTestInvocation : IClassFixture { - private static readonly string[] ExpectedPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "-nologo", "-restore", "-nologo", "-target:VSTest", "-property:NuGetInteractive=false"]; + private static readonly string[] ExpectedPrefix = ["-maxcpucount", "--verbosity:m", "-tlp:default=auto", "--nologo", "-restore", "-target:VSTest", "-property:NuGetInteractive=false"]; private static readonly string WorkingDirectory = TestPathUtilities.FormatAbsolutePath(nameof(GivenDotnetTestInvocation)); diff --git a/test/dotnet.Tests/CommandTests/MSBuild/MSBuildArgumentCommandLineParserTests.cs b/test/dotnet.Tests/CommandTests/MSBuild/MSBuildArgumentCommandLineParserTests.cs index 5c88f074a5c5..f84a1847007d 100644 --- a/test/dotnet.Tests/CommandTests/MSBuild/MSBuildArgumentCommandLineParserTests.cs +++ b/test/dotnet.Tests/CommandTests/MSBuild/MSBuildArgumentCommandLineParserTests.cs @@ -1,12 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Frozen; using System.Collections.ObjectModel; using System.CommandLine; using Microsoft.DotNet.Cli; using Microsoft.DotNet.Cli.Commands.Restore; -using Microsoft.DotNet.Cli.Extensions; +using Microsoft.DotNet.Cli.CommandLine; using BuildCommand = Microsoft.DotNet.Cli.Commands.Build.BuildCommand; using PublishCommand = Microsoft.DotNet.Cli.Commands.Publish.PublishCommand; @@ -54,7 +53,7 @@ public void MSBuildArgumentsAreForwardedCorrectly(string[] arguments, bool build [InlineData(new string[] { "-p:RuntimeIdentifiers=linux-x64;linux-arm64" }, new string[] { "--property:RuntimeIdentifiers=linux-x64;linux-arm64" })] public void Can_pass_msbuild_properties_safely(string[] tokens, string[] forwardedTokens) { - var forwardingFunction = (CommonOptions.PropertiesOption as ForwardedOption?>)!.GetForwardingFunction(); + var forwardingFunction = CommonOptions.PropertiesOption.ForwardingFunction!; var result = new RootCommand() { CommonOptions.PropertiesOption }.Parse(tokens); var parsedTokens = forwardingFunction(result); parsedTokens.Should().BeEquivalentTo(forwardedTokens); diff --git a/test/dotnet.Tests/CommandTests/Package/List/ListPackageParserTests.cs b/test/dotnet.Tests/CommandTests/Package/List/ListPackageParserTests.cs index 01aed95e9eb9..dea7d9c699cd 100644 --- a/test/dotnet.Tests/CommandTests/Package/List/ListPackageParserTests.cs +++ b/test/dotnet.Tests/CommandTests/Package/List/ListPackageParserTests.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.DotNet.Cli.Commands.Hidden.List.Package; -using Microsoft.DotNet.Cli.Extensions; +using Microsoft.DotNet.Cli.CommandLine; using Parser = Microsoft.DotNet.Cli.Parser; namespace Microsoft.DotNet.Tests.ParserTests diff --git a/test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs b/test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs index 72a600549d62..8532c6231b9f 100644 --- a/test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs +++ b/test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs @@ -9,6 +9,7 @@ using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Run.Tests; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.FileBasedPrograms; namespace Microsoft.DotNet.Cli.Project.Convert.Tests; @@ -394,13 +395,14 @@ public void NestedDirectory() } /// - /// Default items like None or Content are copied over. + /// Default items like None or Content are copied over if non-default SDK is used. /// [Fact] - public void DefaultItems() + public void DefaultItems_NonDefaultSdk() { var testInstance = _testAssetsManager.CreateTestDirectory(); File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """ + #:sdk Microsoft.NET.Sdk.Web Console.WriteLine(); """); File.WriteAllText(Path.Join(testInstance.Path, "my.json"), ""); @@ -433,6 +435,8 @@ public void DefaultItems_MoreIncluded() var testInstance = _testAssetsManager.CreateTestDirectory(); File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """ #:property EnableDefaultCompileItems=true + #:property EnableDefaultEmbeddedResourceItems=true + #:property EnableDefaultNoneItems=true Console.WriteLine(); """); File.WriteAllText(Path.Join(testInstance.Path, "my.json"), ""); @@ -487,6 +491,8 @@ public void DefaultItems_ExcludedViaMetadata() { var testInstance = _testAssetsManager.CreateTestDirectory(); File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """ + #:property EnableDefaultEmbeddedResourceItems=true + #:property EnableDefaultNoneItems=true Console.WriteLine(); """); File.WriteAllText(Path.Join(testInstance.Path, "my.json"), ""); @@ -522,6 +528,7 @@ public void DefaultItems_ImplicitBuildFileInDirectory() { var testInstance = _testAssetsManager.CreateTestDirectory(); File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """ + #:sdk Microsoft.NET.Sdk.Web Console.WriteLine(Util.GetText()); """); File.WriteAllText(Path.Join(testInstance.Path, "Util.cs"), """ @@ -677,6 +684,8 @@ public void DefaultItems_AlongsideProj([CombinatorialValues("sln", "slnx", "cspr var testInstance = _testAssetsManager.CreateTestDirectory(); File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """ + #:property EnableDefaultEmbeddedResourceItems=true + #:property EnableDefaultNoneItems=true Console.WriteLine(); """); File.WriteAllText(Path.Join(testInstance.Path, "my.json"), ""); @@ -715,7 +724,7 @@ public void ProcessingFails() .WithWorkingDirectory(testInstance.Path) .Execute() .Should().Fail() - .And.HaveStdErrContaining(RunFileTests.DirectiveError(filePath, 1, CliCommandStrings.UnrecognizedDirective, "invalid")); + .And.HaveStdErrContaining(RunFileTests.DirectiveError(filePath, 1, FileBasedProgramsResources.UnrecognizedDirective, "invalid")); new DirectoryInfo(Path.Join(testInstance.Path)) .EnumerateDirectories().Should().BeEmpty(); @@ -1128,7 +1137,7 @@ public void Directives_Unknown(string directive) #:sdk Test #:{directive} Test """, - expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 2, CliCommandStrings.UnrecognizedDirective, directive)); + expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 2, FileBasedProgramsResources.UnrecognizedDirective, directive)); } [Fact] @@ -1139,7 +1148,7 @@ public void Directives_Empty() #: #:sdk Test """, - expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 1, CliCommandStrings.UnrecognizedDirective, "")); + expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 1, FileBasedProgramsResources.UnrecognizedDirective, "")); } [Theory, CombinatorialData] @@ -1151,7 +1160,7 @@ public void Directives_EmptyName( inputCSharp: $""" #:{directive}{value} """, - expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 1, CliCommandStrings.MissingDirectiveName, directive)); + expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 1, FileBasedProgramsResources.MissingDirectiveName, directive)); } [Theory] @@ -1195,7 +1204,7 @@ public void Directives_EmptyValue(string value) inputCSharp: $""" #:project{value} """, - expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 1, CliCommandStrings.MissingDirectiveName, "project")); + expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 1, FileBasedProgramsResources.MissingDirectiveName, "project")); } [Fact] @@ -1205,7 +1214,7 @@ public void Directives_MissingPropertyValue() inputCSharp: """ #:property Test """, - expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 1, CliCommandStrings.PropertyDirectiveMissingParts)); + expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 1, FileBasedProgramsResources.PropertyDirectiveMissingParts)); } [Fact] @@ -1215,7 +1224,7 @@ public void Directives_InvalidPropertyName() inputCSharp: """ #:property 123Name=Value """, - expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 1, CliCommandStrings.PropertyDirectiveInvalidName, """ + expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 1, FileBasedProgramsResources.PropertyDirectiveInvalidName, """ Name cannot begin with the '1' character, hexadecimal value 0x31. """)); } @@ -1234,7 +1243,7 @@ public void Directives_InvalidName(string directiveKind, string expectedSeparato { VerifyConversionThrows( inputCSharp: $"#:{directiveKind} Abc{actualSeparator}Xyz", - expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 1, CliCommandStrings.InvalidDirectiveName, directiveKind, expectedSeparator)); + expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 1, FileBasedProgramsResources.InvalidDirectiveName, directiveKind, expectedSeparator)); } [Fact] @@ -1277,11 +1286,11 @@ public void Directives_Escaping() """, expectedErrors: [ - (1, CliCommandStrings.QuoteInDirective), - (2, CliCommandStrings.QuoteInDirective), - (3, CliCommandStrings.QuoteInDirective), - (4, string.Format(CliCommandStrings.PropertyDirectiveInvalidName, "The ''' character, hexadecimal value 0x27, cannot be included in a name.")), - (5, CliCommandStrings.QuoteInDirective), + (1, FileBasedProgramsResources.QuoteInDirective), + (2, FileBasedProgramsResources.QuoteInDirective), + (3, FileBasedProgramsResources.QuoteInDirective), + (4, string.Format(FileBasedProgramsResources.PropertyDirectiveInvalidName, "The ''' character, hexadecimal value 0x27, cannot be included in a name.")), + (5, FileBasedProgramsResources.QuoteInDirective), ]); } @@ -1321,7 +1330,7 @@ public void Directives_Whitespace() """, expectedErrors: [ - (3, CliCommandStrings.QuoteInDirective), + (3, FileBasedProgramsResources.QuoteInDirective), ]); } @@ -1389,7 +1398,7 @@ public void Directives_AfterToken() VerifyConversionThrows( inputCSharp: source, - expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 5, CliCommandStrings.CannotConvertDirective)); + expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 5, FileBasedProgramsResources.CannotConvertDirective)); VerifyConversion( inputCSharp: source, @@ -1436,7 +1445,7 @@ public void Directives_AfterIf() VerifyConversionThrows( inputCSharp: source, - expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 5, CliCommandStrings.CannotConvertDirective)); + expectedWildcardPattern: RunFileTests.DirectiveError("/app/Program.cs", 5, FileBasedProgramsResources.CannotConvertDirective)); VerifyConversion( inputCSharp: source, @@ -1525,7 +1534,7 @@ public void Directives_Duplicate() """, expectedErrors: [ - (2, string.Format(CliCommandStrings.DuplicateDirective, "#:property Prop")), + (2, string.Format(FileBasedProgramsResources.DuplicateDirective, "#:property Prop")), ]); VerifyDirectiveConversionErrors( @@ -1537,8 +1546,8 @@ public void Directives_Duplicate() """, expectedErrors: [ - (2, string.Format(CliCommandStrings.DuplicateDirective, "#:sdk Name")), - (3, string.Format(CliCommandStrings.DuplicateDirective, "#:sdk Name")), + (2, string.Format(FileBasedProgramsResources.DuplicateDirective, "#:sdk Name")), + (3, string.Format(FileBasedProgramsResources.DuplicateDirective, "#:sdk Name")), ]); VerifyDirectiveConversionErrors( @@ -1550,8 +1559,8 @@ public void Directives_Duplicate() """, expectedErrors: [ - (2, string.Format(CliCommandStrings.DuplicateDirective, "#:package Name")), - (3, string.Format(CliCommandStrings.DuplicateDirective, "#:package Name")), + (2, string.Format(FileBasedProgramsResources.DuplicateDirective, "#:package Name")), + (3, string.Format(FileBasedProgramsResources.DuplicateDirective, "#:package Name")), ]); VerifyDirectiveConversionErrors( @@ -1570,8 +1579,8 @@ public void Directives_Duplicate() """, expectedErrors: [ - (2, string.Format(CliCommandStrings.DuplicateDirective, "#:property Prop")), - (4, string.Format(CliCommandStrings.DuplicateDirective, "#:property Prop")), + (2, string.Format(FileBasedProgramsResources.DuplicateDirective, "#:property Prop")), + (4, string.Format(FileBasedProgramsResources.DuplicateDirective, "#:property Prop")), ]); VerifyDirectiveConversionErrors( @@ -1581,7 +1590,7 @@ public void Directives_Duplicate() """, expectedErrors: [ - (2, string.Format(CliCommandStrings.DuplicateDirective, "#:property prop")), + (2, string.Format(FileBasedProgramsResources.DuplicateDirective, "#:property prop")), ]); } @@ -1621,7 +1630,7 @@ private static void Convert(string inputCSharp, out string actualProject, out st var sourceFile = new SourceFile(filePath ?? programPath, SourceText.From(inputCSharp, Encoding.UTF8)); actualDiagnostics = null; var diagnosticBag = collectDiagnostics ? DiagnosticBag.Collect(out actualDiagnostics) : DiagnosticBag.ThrowOnFirst(); - var directives = VirtualProjectBuildingCommand.FindDirectives(sourceFile, reportAllErrors: !force, diagnosticBag); + var directives = FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: !force, diagnosticBag); var projectWriter = new StringWriter(); VirtualProjectBuildingCommand.WriteProjectFile(projectWriter, directives, isVirtualProject: false); actualProject = projectWriter.ToString(); @@ -1650,7 +1659,7 @@ private static void VerifyConversionThrows(string inputCSharp, string expectedWi private static void VerifyDirectiveConversionErrors(string inputCSharp, IEnumerable<(int LineNumber, string Message)> expectedErrors) { var sourceFile = new SourceFile(programPath, SourceText.From(inputCSharp, Encoding.UTF8)); - VirtualProjectBuildingCommand.FindDirectives(sourceFile, reportAllErrors: true, DiagnosticBag.Collect(out var diagnostics)); + FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: true, DiagnosticBag.Collect(out var diagnostics)); VerifyErrors(diagnostics, expectedErrors); } diff --git a/test/dotnet.Tests/CommandTests/Restore/RestoreParserTests.cs b/test/dotnet.Tests/CommandTests/Restore/RestoreParserTests.cs index 5a65cbdb98b9..aedcae11a0c3 100644 --- a/test/dotnet.Tests/CommandTests/Restore/RestoreParserTests.cs +++ b/test/dotnet.Tests/CommandTests/Restore/RestoreParserTests.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.DotNet.Cli.Commands.Restore; -using Microsoft.DotNet.Cli.Extensions; +using Microsoft.DotNet.Cli.CommandLine; using Parser = Microsoft.DotNet.Cli.Parser; namespace Microsoft.DotNet.Tests.CommandLineParserTests diff --git a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs index 371b29d7f314..f6b181615b72 100644 --- a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.DotNet.Cli.Commands.Run; +using Microsoft.DotNet.FileBasedPrograms; namespace Microsoft.DotNet.Cli.Run.Tests; diff --git a/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs b/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs index 54c5c8454936..5d7b53c65a3e 100644 --- a/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs @@ -10,6 +10,7 @@ using Microsoft.DotNet.Cli.Commands; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.FileBasedPrograms; namespace Microsoft.DotNet.Cli.Run.Tests; @@ -135,7 +136,7 @@ private static string PrepareOutOfTreeBaseDirectory() internal static string DirectiveError(string path, int line, string messageFormat, params ReadOnlySpan args) { - return $"{path}({line}): {CliCommandStrings.DirectiveError}: {string.Format(messageFormat, args)}"; + return $"{path}({line}): {FileBasedProgramsResources.DirectiveError}: {string.Format(messageFormat, args)}"; } /// @@ -1363,7 +1364,7 @@ public void Verbosity_CompilationDiagnostics() } /// - /// Default projects include embedded resources by default. + /// File-based projects using the default SDK do not include embedded resources by default. /// [Fact] public void EmbeddedResource() @@ -1372,6 +1373,35 @@ public void EmbeddedResource() File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_programReadingEmbeddedResource); File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), s_resx); + // By default, with the default SDK, embedded resources are not included. + new DotnetCommand(Log, "run", "Program.cs") + .WithWorkingDirectory(testInstance.Path) + .Execute() + .Should().Pass() + .And.HaveStdOut(""" + Resource not found + """); + + // This behavior can be overridden to enable embedded resources. + File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), $""" + #:property EnableDefaultEmbeddedResourceItems=true + {s_programReadingEmbeddedResource} + """); + + new DotnetCommand(Log, "run", "Program.cs") + .WithWorkingDirectory(testInstance.Path) + .Execute() + .Should().Pass() + .And.HaveStdOut(""" + [MyString, TestValue] + """); + + // When using a non-default SDK, embedded resources are included by default. + File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), $""" + #:sdk Microsoft.NET.Sdk.Web + {s_programReadingEmbeddedResource} + """); + new DotnetCommand(Log, "run", "Program.cs") .WithWorkingDirectory(testInstance.Path) .Execute() @@ -1380,9 +1410,9 @@ public void EmbeddedResource() [MyString, TestValue] """); - // This behavior can be overridden. + // When using the default SDK explicitly, embedded resources are not included. File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), $""" - #:property EnableDefaultEmbeddedResourceItems=false + #:sdk Microsoft.NET.Sdk {s_programReadingEmbeddedResource} """); @@ -1405,7 +1435,10 @@ public void EmbeddedResource_AlongsideProj([CombinatorialValues("sln", "slnx", " bool considered = ext is "sln" or "slnx" or "csproj"; var testInstance = _testAssetsManager.CreateTestDirectory(); - File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_programReadingEmbeddedResource); + File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), $""" + #:property EnableDefaultEmbeddedResourceItems=true + {s_programReadingEmbeddedResource} + """); File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), s_resx); File.WriteAllText(Path.Join(testInstance.Path, $"repo.{ext}"), ""); @@ -1529,7 +1562,7 @@ public void Restore_StaticGraph_Explicit() .WithWorkingDirectory(testInstance.Path) .Execute() .Should().Fail() - .And.HaveStdErr(DirectiveError(programFile, 1, CliCommandStrings.StaticGraphRestoreNotSupported)); + .And.HaveStdErr(DirectiveError(programFile, 1, FileBasedProgramsResources.StaticGraphRestoreNotSupported)); } [Fact] @@ -2506,8 +2539,8 @@ public void ProjectReference_Errors() .WithWorkingDirectory(testInstance.Path) .Execute() .Should().Fail() - .And.HaveStdErrContaining(DirectiveError(Path.Join(testInstance.Path, "Program.cs"), 1, CliCommandStrings.InvalidProjectDirective, - string.Format(CliStrings.CouldNotFindProjectOrDirectory, Path.Join(testInstance.Path, "wrong.csproj")))); + .And.HaveStdErrContaining(DirectiveError(Path.Join(testInstance.Path, "Program.cs"), 1, FileBasedProgramsResources.InvalidProjectDirective, + string.Format(FileBasedProgramsResources.CouldNotFindProjectOrDirectory, Path.Join(testInstance.Path, "wrong.csproj")))); File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """ #:project dir/ @@ -2518,8 +2551,8 @@ public void ProjectReference_Errors() .WithWorkingDirectory(testInstance.Path) .Execute() .Should().Fail() - .And.HaveStdErrContaining(DirectiveError(Path.Join(testInstance.Path, "Program.cs"), 1, CliCommandStrings.InvalidProjectDirective, - string.Format(CliStrings.CouldNotFindProjectOrDirectory, Path.Join(testInstance.Path, "dir/")))); + .And.HaveStdErrContaining(DirectiveError(Path.Join(testInstance.Path, "Program.cs"), 1, FileBasedProgramsResources.InvalidProjectDirective, + string.Format(FileBasedProgramsResources.CouldNotFindProjectOrDirectory, Path.Join(testInstance.Path, "dir/")))); Directory.CreateDirectory(Path.Join(testInstance.Path, "dir")); @@ -2528,8 +2561,8 @@ public void ProjectReference_Errors() .WithWorkingDirectory(testInstance.Path) .Execute() .Should().Fail() - .And.HaveStdErrContaining(DirectiveError(Path.Join(testInstance.Path, "Program.cs"), 1, CliCommandStrings.InvalidProjectDirective, - string.Format(CliStrings.CouldNotFindAnyProjectInDirectory, Path.Join(testInstance.Path, "dir/")))); + .And.HaveStdErrContaining(DirectiveError(Path.Join(testInstance.Path, "Program.cs"), 1, FileBasedProgramsResources.InvalidProjectDirective, + string.Format(FileBasedProgramsResources.CouldNotFindAnyProjectInDirectory, Path.Join(testInstance.Path, "dir/")))); File.WriteAllText(Path.Join(testInstance.Path, "dir", "proj1.csproj"), ""); File.WriteAllText(Path.Join(testInstance.Path, "dir", "proj2.csproj"), ""); @@ -2539,15 +2572,15 @@ public void ProjectReference_Errors() .WithWorkingDirectory(testInstance.Path) .Execute() .Should().Fail() - .And.HaveStdErrContaining(DirectiveError(Path.Join(testInstance.Path, "Program.cs"), 1, CliCommandStrings.InvalidProjectDirective, - string.Format(CliStrings.MoreThanOneProjectInDirectory, Path.Join(testInstance.Path, "dir/")))); + .And.HaveStdErrContaining(DirectiveError(Path.Join(testInstance.Path, "Program.cs"), 1, FileBasedProgramsResources.InvalidProjectDirective, + string.Format(FileBasedProgramsResources.MoreThanOneProjectInDirectory, Path.Join(testInstance.Path, "dir/")))); } [Theory] // https://github.com/dotnet/aspnetcore/issues/63440 [InlineData(true, null)] - [InlineData(false, null, Skip = "Needs https://github.com/dotnet/aspnetcore/pull/63496")] + [InlineData(false, null)] [InlineData(true, "test-id")] - [InlineData(false, "test-id", Skip = "Needs https://github.com/dotnet/aspnetcore/pull/63496")] + [InlineData(false, "test-id")] public void UserSecrets(bool useIdArg, string? userSecretsId) { var testInstance = _testAssetsManager.CreateTestDirectory(); @@ -2569,6 +2602,10 @@ public void UserSecrets(bool useIdArg, string? userSecretsId) var programPath = Path.Join(testInstance.Path, "Program.cs"); File.WriteAllText(programPath, code); + // Remove artifacts from possible previous runs of this test. + var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); + if (useIdArg) { if (userSecretsId == null) @@ -3147,6 +3184,7 @@ public void UpToDate_DefaultItems(bool optOut) var testInstance = _testAssetsManager.CreateTestDirectory(); var code = $""" {(optOut ? "#:property FileBasedProgramCanSkipMSBuild=false" : "")} + #:property EnableDefaultEmbeddedResourceItems=true {s_programReadingEmbeddedResource} """; File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), code); @@ -3167,43 +3205,12 @@ public void UpToDate_DefaultItems(bool optOut) Build(testInstance, BuildLevel.All, ["--no-cache"], expectedOutput: "[MyString, UpdatedValue]"); } - /// - /// Combination of with optimization. - /// - [Theory, CombinatorialData] // https://github.com/dotnet/sdk/issues/50912 - public void UpToDate_DefaultItems_CscOnly(bool optOut) - { - var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory); - var code = $""" - {(optOut ? "#:property FileBasedProgramCanSkipMSBuild=false" : "")} - {s_programReadingEmbeddedResource} - """; - File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), code); - File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), s_resx); - - Build(testInstance, optOut ? BuildLevel.All : BuildLevel.Csc, expectedOutput: optOut ? "[MyString, TestValue]" : "Resource not found"); - - // Update the RESX file. - File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), s_resx.Replace("TestValue", "UpdatedValue")); - - Build(testInstance, optOut ? BuildLevel.All : BuildLevel.None, expectedOutput: optOut ? "[MyString, UpdatedValue]" : "Resource not found"); - - // Update the C# file. - File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), "//v2\n" + code); - - Build(testInstance, optOut ? BuildLevel.All : BuildLevel.Csc, expectedOutput: optOut ? "[MyString, UpdatedValue]" : "Resource not found"); - - Build(testInstance, BuildLevel.All, ["--no-cache"], expectedOutput: "[MyString, UpdatedValue]"); - - // Update the C# file. - File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), "//v3\n" + code); - - Build(testInstance, optOut ? BuildLevel.All : BuildLevel.Csc, expectedOutput: "[MyString, UpdatedValue]"); - } - /// /// Combination of with optimization. /// + /// + /// Note: we cannot test because that optimization doesn't support neither #:property nor #:sdk which we need to enable default items. + /// [Theory, CombinatorialData] // https://github.com/dotnet/sdk/issues/50912 public void UpToDate_DefaultItems_CscOnly_AfterMSBuild(bool optOut) { @@ -3211,6 +3218,7 @@ public void UpToDate_DefaultItems_CscOnly_AfterMSBuild(bool optOut) var code = $""" #:property Configuration=Release {(optOut ? "#:property FileBasedProgramCanSkipMSBuild=false" : "")} + #:property EnableDefaultEmbeddedResourceItems=true {s_programReadingEmbeddedResource} """; File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), code); @@ -3604,6 +3612,37 @@ Release config """); } + /// + /// See . + /// If hard links are enabled, the bin/app.dll and obj/app.dll files are going to be the same, + /// so our "copy obj to bin" logic must account for that. + /// + [Fact] + public void CscOnly_AfterMSBuild_HardLinks() + { + var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory); + var programPath = Path.Join(testInstance.Path, "Program.cs"); + + var code = $""" + #:property CreateHardLinksForCopyFilesToOutputDirectoryIfPossible=true + #:property CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible=true + {s_program} + """; + + File.WriteAllText(programPath, code); + + // Remove artifacts from possible previous runs of this test. + var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); + + Build(testInstance, BuildLevel.All); + + code = code.Replace("Hello", "Hi"); + File.WriteAllText(programPath, code); + + Build(testInstance, BuildLevel.Csc, expectedOutput: "Hi from Program"); + } + /// /// See . /// This optimization currently does not support #:project references and hence is disabled if those are present. @@ -3861,6 +3900,8 @@ public void Api_Diagnostic_01() true false true + false + false Exe {ToolsetInfo.CurrentTargetFramework} enable @@ -3897,7 +3938,7 @@ public void Api_Diagnostic_01() [{"Location":{ "Path":{{ToJson(programPath)}}, "Span":{"Start":{"Line":1,"Character":0},"End":{"Line":1,"Character":30}{{nop}}}{{nop}}}, - "Message":{{ToJson(CliCommandStrings.CannotConvertDirective)}}}]} + "Message":{{ToJson(FileBasedProgramsResources.CannotConvertDirective)}}}]} """.ReplaceLineEndings("")); } @@ -3929,6 +3970,8 @@ public void Api_Diagnostic_02() true false true + false + false Exe {ToolsetInfo.CurrentTargetFramework} enable @@ -3965,7 +4008,7 @@ public void Api_Diagnostic_02() [{"Location":{ "Path":{{ToJson(programPath)}}, "Span":{"Start":{"Line":0,"Character":0},"End":{"Line":1,"Character":0}{{nop}}}{{nop}}}, - "Message":{{ToJson(string.Format(CliCommandStrings.UnrecognizedDirective, "unknown"))}}}]} + "Message":{{ToJson(string.Format(FileBasedProgramsResources.UnrecognizedDirective, "unknown"))}}}]} """.ReplaceLineEndings("")); } diff --git a/test/dotnet.Tests/CommandTests/Run/RunParserTests.cs b/test/dotnet.Tests/CommandTests/Run/RunParserTests.cs index 3c2897bbe34e..b47f27a41e5a 100644 --- a/test/dotnet.Tests/CommandTests/Run/RunParserTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/RunParserTests.cs @@ -27,5 +27,35 @@ public void RunParserCanGetArgumentFromDoubleDash() var runCommand = RunCommand.FromArgs(new[] { "--project", projectPath, "--", "foo" }); runCommand.ApplicationArgs.Single().Should().Be("foo"); } + + [WindowsOnlyFact] + public void RunParserAcceptsWindowsPathSeparatorsOnWindows() + { + var tam = new TestAssetsManager(output); + var testAsset = tam.CopyTestAsset("HelloWorld").WithSource(); + var newWorkingDir = testAsset.Path; + + Directory.SetCurrentDirectory(newWorkingDir); + var projectPath = @".\HelloWorld.csproj"; + + // Should not throw on Windows + var runCommand = RunCommand.FromArgs(new[] { "--project", projectPath }); + runCommand.ProjectFileFullPath.Should().NotBeNull(); + } + + [UnixOnlyFact] + public void RunParserAcceptsWindowsPathSeparatorsOnLinux() + { + var tam = new TestAssetsManager(output); + var testAsset = tam.CopyTestAsset("HelloWorld").WithSource(); + var newWorkingDir = testAsset.Path; + + Directory.SetCurrentDirectory(newWorkingDir); + var projectPath = @".\HelloWorld.csproj"; + + // Should not throw on Linux with backslash separators + var runCommand = RunCommand.FromArgs(new[] { "--project", projectPath }); + runCommand.ProjectFileFullPath.Should().NotBeNull(); + } } } diff --git a/test/dotnet.Tests/CommandTests/Run/RunTelemetryTests.cs b/test/dotnet.Tests/CommandTests/Run/RunTelemetryTests.cs index 08d323f2cc28..edaf986abdfa 100644 --- a/test/dotnet.Tests/CommandTests/Run/RunTelemetryTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/RunTelemetryTests.cs @@ -7,6 +7,7 @@ using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Commands.Run.LaunchSettings; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.FileBasedPrograms; namespace Microsoft.DotNet.Cli.Run.Tests; diff --git a/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs b/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs index 3f78a0896e87..98fef585e52d 100644 --- a/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs +++ b/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs @@ -294,7 +294,7 @@ public void WhenNestedProjectIsAddedSolutionFoldersAreCreatedBuild(string soluti } - [Theory(Skip = "https://github.com/dotnet/sdk/issues/47859")] + [Theory] [InlineData("sln")] [InlineData("solution")] public void WhenNestedDuplicateProjectIsAddedToASolutionFolder(string solutionCommand) diff --git a/test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTestsWithDifferentOptions.cs b/test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTestsWithDifferentOptions.cs index 4559f8933439..f78f6cdf9301 100644 --- a/test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTestsWithDifferentOptions.cs +++ b/test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTestsWithDifferentOptions.cs @@ -26,7 +26,7 @@ public void RunWithProjectPathWithFailingTests_ShouldReturnExitCodeGenericFailur CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false) .WithWorkingDirectory(testInstance.Path) - .Execute(MicrosoftTestingPlatformOptions.ProjectOption.Name, testProjectPath, + .Execute(MicrosoftTestingPlatformOptions.ProjectOrSolutionOption.Name, testProjectPath, MicrosoftTestingPlatformOptions.ConfigurationOption.Name, configuration); Regex.Matches(result.StdOut!, RegexPatternHelper.GenerateProjectRegexPattern("TestProject", true, configuration, "exec", addVersionAndArchPattern: false)); @@ -34,10 +34,10 @@ public void RunWithProjectPathWithFailingTests_ShouldReturnExitCodeGenericFailur result.ExitCode.Should().Be(ExitCodes.AtLeastOneTestFailed); } - [InlineData(TestingConstants.Debug)] - [InlineData(TestingConstants.Release)] - [Theory] - public void RunWithSolutionPathWithFailingTests_ShouldReturnExitCodeGenericFailure(string configuration) + [Theory, CombinatorialData] + public void RunWithSolutionPathWithFailingTests_ShouldReturnExitCodeAtLeastOneTestFailed( + [CombinatorialValues(TestingConstants.Debug, TestingConstants.Release)] string configuration, + [CombinatorialValues("--solution", "--project")] string projectOrSolution) { TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithTests", Guid.NewGuid().ToString()).WithSource(); @@ -45,7 +45,7 @@ public void RunWithSolutionPathWithFailingTests_ShouldReturnExitCodeGenericFailu CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false) .WithWorkingDirectory(testInstance.Path) - .Execute(MicrosoftTestingPlatformOptions.SolutionOption.Name, testSolutionPath, + .Execute(projectOrSolution, testSolutionPath, MicrosoftTestingPlatformOptions.ConfigurationOption.Name, configuration); Assert.Matches(RegexPatternHelper.GenerateProjectRegexPattern("TestProject", TestingConstants.Failed, true, configuration), result.StdOut); @@ -54,10 +54,10 @@ public void RunWithSolutionPathWithFailingTests_ShouldReturnExitCodeGenericFailu result.ExitCode.Should().Be(ExitCodes.AtLeastOneTestFailed); } - [InlineData(TestingConstants.Debug)] - [InlineData(TestingConstants.Release)] - [Theory] - public void RunWithSolutionFilterPathWithFailingTests_ShouldReturnExitCodeGenericFailure(string configuration) + [Theory, CombinatorialData] + public void RunWithSolutionFilterPathWithFailingTests_ShouldReturnExitCodeGenericFailure( + [CombinatorialValues(TestingConstants.Debug, TestingConstants.Release)] string configuration, + [CombinatorialValues("--solution", "--project")] string projectOrSolution) { TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithTests", Guid.NewGuid().ToString()).WithSource(); @@ -65,7 +65,7 @@ public void RunWithSolutionFilterPathWithFailingTests_ShouldReturnExitCodeGeneri CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false) .WithWorkingDirectory(testInstance.Path) - .Execute(MicrosoftTestingPlatformOptions.SolutionOption.Name, testSolutionPath, + .Execute(projectOrSolution, testSolutionPath, MicrosoftTestingPlatformOptions.ConfigurationOption.Name, configuration); Assert.Matches(RegexPatternHelper.GenerateProjectRegexPattern("TestProject", TestingConstants.Failed, true, configuration), result.StdOut); @@ -102,7 +102,7 @@ public void RunWithInvalidProjectExtension_ShouldReturnExitCodeGenericFailure(st CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false) .WithWorkingDirectory(testInstance.Path) - .Execute(MicrosoftTestingPlatformOptions.ProjectOption.Name, invalidProjectPath, + .Execute(MicrosoftTestingPlatformOptions.ProjectOrSolutionOption.Name, invalidProjectPath, MicrosoftTestingPlatformOptions.ConfigurationOption.Name, configuration); result.StdErr.Should().Contain(string.Format(CliCommandStrings.CmdInvalidProjectFileExtensionErrorDescription, invalidProjectPath)); @@ -113,7 +113,7 @@ public void RunWithInvalidProjectExtension_ShouldReturnExitCodeGenericFailure(st [InlineData(TestingConstants.Debug)] [InlineData(TestingConstants.Release)] [Theory] - public void RunWithDirectoryAsProjectOption_ShouldReturnExitCodeGenericFailure(string configuration) + public void RunWithDirectoryAsProjectOrSolutionOption_ShouldReturnExitCodeGenericFailure(string configuration) { TestAsset testInstance = _testAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithTests", Guid.NewGuid().ToString()).WithSource(); @@ -121,7 +121,7 @@ public void RunWithDirectoryAsProjectOption_ShouldReturnExitCodeGenericFailure(s CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false) .WithWorkingDirectory(testInstance.Path) - .Execute(MicrosoftTestingPlatformOptions.ProjectOption.Name, projectPath, + .Execute(MicrosoftTestingPlatformOptions.ProjectOrSolutionOption.Name, projectPath, MicrosoftTestingPlatformOptions.ConfigurationOption.Name, configuration); // Validate that only TestProject ran @@ -162,7 +162,7 @@ public void RunWithBothProjectAndSolutionOptions_ShouldReturnExitCodeGenericFail CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false) .WithWorkingDirectory(testInstance.Path) - .Execute(MicrosoftTestingPlatformOptions.ProjectOption.Name, testProjectPath, + .Execute(MicrosoftTestingPlatformOptions.ProjectOrSolutionOption.Name, testProjectPath, MicrosoftTestingPlatformOptions.SolutionOption.Name, testSolutionPath, MicrosoftTestingPlatformOptions.ConfigurationOption.Name, configuration); @@ -182,7 +182,7 @@ public void RunWithNonExistentProjectPath_ShouldReturnExitCodeGenericFailure(str CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false) .WithWorkingDirectory(testInstance.Path) - .Execute(MicrosoftTestingPlatformOptions.ProjectOption.Name, testProjectPath, + .Execute(MicrosoftTestingPlatformOptions.ProjectOrSolutionOption.Name, testProjectPath, MicrosoftTestingPlatformOptions.ConfigurationOption.Name, configuration); string fullProjectPath = $"{testInstance.TestRoot}{Path.DirectorySeparatorChar}{testProjectPath}"; @@ -352,7 +352,7 @@ public void RunSpecificCSProjRunsWithNoBuildAndNoRestoreOptions_ShouldReturnExit CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false) .WithWorkingDirectory(testInstance.Path) - .Execute(MicrosoftTestingPlatformOptions.ProjectOption.Name, "TestProject.csproj", + .Execute(MicrosoftTestingPlatformOptions.ProjectOrSolutionOption.Name, "TestProject.csproj", MicrosoftTestingPlatformOptions.ConfigurationOption.Name, configuration, CommonOptions.NoRestoreOption.Name, MicrosoftTestingPlatformOptions.NoBuildOption.Name); @@ -388,7 +388,7 @@ public void RunSpecificCSProjRunsWithMSBuildArgs_ShouldReturnExitCodeSuccess(str CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false) .WithWorkingDirectory(testInstance.Path) - .Execute(MicrosoftTestingPlatformOptions.ProjectOption.Name, "TestProject.csproj", + .Execute(MicrosoftTestingPlatformOptions.ProjectOrSolutionOption.Name, "TestProject.csproj", MicrosoftTestingPlatformOptions.ConfigurationOption.Name, configuration, "--property:WarningLevel=2", $"--property:Configuration={configuration}"); diff --git a/test/dotnet.Tests/CommandTests/Test/MTPHelpSnapshotTests.cs b/test/dotnet.Tests/CommandTests/Test/MTPHelpSnapshotTests.cs new file mode 100644 index 000000000000..bacaee602ac2 --- /dev/null +++ b/test/dotnet.Tests/CommandTests/Test/MTPHelpSnapshotTests.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.DotNet.Cli.Commands.Test; +using Microsoft.DotNet.Cli.Utils; +using CommandResult = Microsoft.DotNet.Cli.Utils.CommandResult; +using ExitCodes = Microsoft.NET.TestFramework.ExitCode; + +namespace Microsoft.DotNet.Cli.Test.Tests; + +public class MTPHelpSnapshotTests : SdkTest +{ + public MTPHelpSnapshotTests(ITestOutputHelper log) : base(log) + { + } + + [Fact] + public async Task VerifyMTPHelpOutput() + { + TestAsset testInstance = _testAssetsManager + .CopyTestAsset("TestProjectSolutionWithTestsAndArtifacts", Guid.NewGuid().ToString()) + .WithSource(); + + CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false) + .WithWorkingDirectory(testInstance.Path) + .Execute(CliConstants.HelpOptionKey); + + result.ExitCode.Should().Be(ExitCodes.Success); + + var helpOutput = result.StdOut; + + // Verify we have MTP mode output (contains Extension Options section) + helpOutput.Should().Contain("Extension Options:"); + + var settings = new VerifySettings(); + if (Environment.GetEnvironmentVariable("USER") is string user && user.Contains("helix", StringComparison.OrdinalIgnoreCase) + || string.IsNullOrEmpty(Environment.GetEnvironmentVariable("USER"))) + { + Log.WriteLine($"CI environment detected, using snapshots directory in the current working directory {Environment.CurrentDirectory}"); + settings.UseDirectory(Path.Combine(Environment.CurrentDirectory, "snapshots")); + } + else + { + Log.WriteLine($"Using snapshots from local repository because $USER {Environment.GetEnvironmentVariable("USER")} is not helix-related"); + settings.UseDirectory("snapshots"); + } + + await Verify(helpOutput, settings); + } +} diff --git a/test/dotnet.Tests/CommandTests/Test/snapshots/MTPHelpSnapshotTests.VerifyMTPHelpOutput.verified.txt b/test/dotnet.Tests/CommandTests/Test/snapshots/MTPHelpSnapshotTests.VerifyMTPHelpOutput.verified.txt new file mode 100644 index 000000000000..333629304880 --- /dev/null +++ b/test/dotnet.Tests/CommandTests/Test/snapshots/MTPHelpSnapshotTests.VerifyMTPHelpOutput.verified.txt @@ -0,0 +1,66 @@ +Description: + .NET Test Command for Microsoft.Testing.Platform (opted-in via 'global.json' file). This only supports Microsoft.Testing.Platform and doesn't support VSTest. For more information, see https://aka.ms/dotnet-test. + +Usage: + dotnet test [options] [platform options] [extension options] + +Options: + --project Defines the path of the project or solution file to test. Use path to the project file, or path to the directory containing the project file. If not specified, it defaults to the current directory. + --solution Defines the path of the solution file to test. Use path to the solution file, or path to the directory containing the solution file. If not specified, it defaults to the current directory. + --test-modules Run tests for the specified test modules. + --root-directory The test modules have the specified root directory. + --results-directory The directory where the test results will be placed. + The specified directory will be created if it does not exist. + --config-file Specifies a testconfig.json file. + --diagnostic-output-directory Output directory of the diagnostic logging. + If not specified the file will be generated inside the default 'TestResults' directory. + --max-parallel-test-modules The max number of test modules that can run in parallel. + --minimum-expected-tests Specifies the minimum number of tests that are expected to run. + -a, --arch The target architecture. + -e, --environment Sets the value of an environment variable. + Creates the variable if it does not exist, overrides if it does. + This argument can be specified multiple times to provide multiple variables. + + Examples: + -e VARIABLE=abc + -e VARIABLE="value with spaces" + -e VARIABLE="value;seperated with;semicolons" + -e VAR1=abc -e VAR2=def -e VAR3=ghi + -c, --configuration The configuration to use for running tests. The default for most projects is 'Debug'. + -f, --framework The target framework to run tests for. The target framework must also be specified in the project file. + --os The target operating system. + -r, --runtime The target runtime to test for. + -v, -verbosity Set the MSBuild verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]. + --no-restore Do not restore the project before building. [default: False] + --no-build Do not build the project before testing. Implies --no-restore. [default: False] + --no-ansi Disable ANSI output. [default: False] + --no-progress Disable progress reporting. [default: False] + --output Verbosity of test output. + --list-tests List the discovered tests instead of running the tests. + --no-launch-profile Do not attempt to use launchSettings.json or [app].run.json to configure the application. [default: False] + --no-launch-profile-arguments Do not use arguments specified in launch profile to run the application. [default: False] + -?, -h, --help Show command line help. + +Waiting for options and extensions... +Platform Options: + --debug Allows to pause execution in order to attach to the process for debug purposes. + --diagnostic Enable the diagnostic logging. The default log level is 'Trace'. + The file will be written in the output directory with the name log_[yyMMddHHmmssfff].diag + --diagnostic-file-prefix Prefix for the log file name that will replace '[log]_.' + --diagnostic-synchronous-write Force the built-in file logger to write the log synchronously. + Useful for scenario where you don't want to lose any log (i.e. in case of crash). + Note that this is slowing down the test execution. + --diagnostic-verbosity Define the level of the verbosity for the --diagnostic. + The available values are 'Trace', 'Debug', 'Information', 'Warning', 'Error', and 'Critical'. + --exit-on-process-exit Exit the test process if dependent process exits. PID must be provided. + --filter-uid Provides a list of test node UIDs to filter by. + --help Show the command line help. + --ignore-exit-code Do not report non successful exit value for specific exit codes + (e.g. '--ignore-exit-code 8;9' ignore exit code 8 and 9 and will return 0 in these case) + --info Display .NET test application information. + --timeout A global test execution timeout. + Takes one argument as string in the format [h|m|s] where 'value' is float. + +Extension Options: + --report-trx Enable generating TRX report + --report-trx-filename The name of the generated TRX report \ No newline at end of file diff --git a/test/dotnet.Tests/CommandTests/Tool/Install/ToolInstallGlobalOrToolPathCommandTests.cs b/test/dotnet.Tests/CommandTests/Tool/Install/ToolInstallGlobalOrToolPathCommandTests.cs index 0d80b9d9d133..65c0dd06d35c 100644 --- a/test/dotnet.Tests/CommandTests/Tool/Install/ToolInstallGlobalOrToolPathCommandTests.cs +++ b/test/dotnet.Tests/CommandTests/Tool/Install/ToolInstallGlobalOrToolPathCommandTests.cs @@ -89,7 +89,7 @@ public void WhenPassingRestoreActionConfigOptions() { var parseResult = Parser.Parse($"dotnet tool install -g {PackageId} --ignore-failed-sources"); var toolInstallCommand = new ToolInstallGlobalOrToolPathCommand(parseResult); - toolInstallCommand.restoreActionConfig.IgnoreFailedSources.Should().BeTrue(); + toolInstallCommand._restoreActionConfig.IgnoreFailedSources.Should().BeTrue(); } [Fact] diff --git a/test/dotnet.Tests/CommandTests/Tool/Update/ToolUpdateGlobalOrToolPathCommandTests.cs b/test/dotnet.Tests/CommandTests/Tool/Update/ToolUpdateGlobalOrToolPathCommandTests.cs index 846a2c423d9a..7bc758e8fd07 100644 --- a/test/dotnet.Tests/CommandTests/Tool/Update/ToolUpdateGlobalOrToolPathCommandTests.cs +++ b/test/dotnet.Tests/CommandTests/Tool/Update/ToolUpdateGlobalOrToolPathCommandTests.cs @@ -108,7 +108,7 @@ public void WhenPassingRestoreActionConfigOptions() { var parseResult = Parser.Parse($"dotnet tool update -g {_packageId} --ignore-failed-sources"); var toolUpdateCommand = new ToolUpdateGlobalOrToolPathCommand(parseResult); - toolUpdateCommand._toolInstallGlobalOrToolPathCommand.restoreActionConfig.IgnoreFailedSources.Should().BeTrue(); + toolUpdateCommand._toolInstallGlobalOrToolPathCommand._restoreActionConfig.IgnoreFailedSources.Should().BeTrue(); } [Fact] diff --git a/test/dotnet.Tests/CompletionTests/snapshots/bash/DotnetCliSnapshotTests.VerifyCompletions.verified.sh b/test/dotnet.Tests/CompletionTests/snapshots/bash/DotnetCliSnapshotTests.VerifyCompletions.verified.sh index c6b13b332377..653744929d21 100644 --- a/test/dotnet.Tests/CompletionTests/snapshots/bash/DotnetCliSnapshotTests.VerifyCompletions.verified.sh +++ b/test/dotnet.Tests/CompletionTests/snapshots/bash/DotnetCliSnapshotTests.VerifyCompletions.verified.sh @@ -144,7 +144,7 @@ _testhost_build() { prev="${COMP_WORDS[COMP_CWORD-1]}" COMPREPLY=() - opts="--use-current-runtime --framework --configuration --runtime --version-suffix --no-restore --interactive --verbosity --debug --output --artifacts-path --no-incremental --no-dependencies --nologo --self-contained --no-self-contained --arch --os --disable-build-servers --help" + opts="--use-current-runtime --framework --configuration --runtime --version-suffix --no-restore --interactive --verbosity --debug --output --artifacts-path --no-incremental --no-dependencies --no-logo --self-contained --no-self-contained --arch --os --disable-build-servers --help" if [[ $COMP_CWORD == "$1" ]]; then COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) @@ -229,7 +229,7 @@ _testhost_clean() { prev="${COMP_WORDS[COMP_CWORD-1]}" COMPREPLY=() - opts="--framework --runtime --configuration --interactive --verbosity --output --artifacts-path --nologo --disable-build-servers --help" + opts="--framework --runtime --configuration --interactive --verbosity --output --artifacts-path --no-logo --disable-build-servers --help" if [[ $COMP_CWORD == "$1" ]]; then COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) @@ -970,7 +970,7 @@ _testhost_pack() { prev="${COMP_WORDS[COMP_CWORD-1]}" COMPREPLY=() - opts="--output --artifacts-path --no-build --include-symbols --include-source --serviceable --nologo --interactive --no-restore --verbosity --version-suffix --version --configuration --disable-build-servers --use-current-runtime --runtime --help" + opts="--output --artifacts-path --no-build --include-symbols --include-source --serviceable --no-logo --interactive --no-restore --verbosity --version-suffix --version --configuration --disable-build-servers --use-current-runtime --runtime --help" if [[ $COMP_CWORD == "$1" ]]; then COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) @@ -1210,7 +1210,7 @@ _testhost_publish() { prev="${COMP_WORDS[COMP_CWORD-1]}" COMPREPLY=() - opts="--use-current-runtime --output --artifacts-path --manifest --no-build --self-contained --no-self-contained --nologo --framework --runtime --configuration --version-suffix --interactive --no-restore --verbosity --arch --os --disable-build-servers --help" + opts="--use-current-runtime --output --artifacts-path --manifest --no-build --self-contained --no-self-contained --no-logo --framework --runtime --configuration --version-suffix --interactive --no-restore --verbosity --arch --os --disable-build-servers --help" if [[ $COMP_CWORD == "$1" ]]; then COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) @@ -1347,7 +1347,7 @@ _testhost_restore() { prev="${COMP_WORDS[COMP_CWORD-1]}" COMPREPLY=() - opts="--disable-build-servers --source --packages --use-current-runtime --disable-parallel --configfile --no-http-cache --ignore-failed-sources --force --runtime --no-dependencies --verbosity --interactive --artifacts-path --use-lock-file --locked-mode --lock-file-path --force-evaluate --arch --os --help" + opts="--disable-build-servers --source --packages --use-current-runtime --disable-parallel --configfile --no-http-cache --ignore-failed-sources --force --runtime --no-dependencies --verbosity --interactive --artifacts-path --use-lock-file --locked-mode --lock-file-path --force-evaluate --no-logo --arch --os --help" if [[ $COMP_CWORD == "$1" ]]; then COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) @@ -1541,7 +1541,7 @@ _testhost_store() { prev="${COMP_WORDS[COMP_CWORD-1]}" COMPREPLY=() - opts="--manifest --framework-version --output --working-dir --skip-optimization --skip-symbols --framework --runtime --verbosity --use-current-runtime --disable-build-servers --help" + opts="--manifest --framework-version --output --working-dir --skip-optimization --skip-symbols --framework --runtime --verbosity --use-current-runtime --disable-build-servers --no-logo --help" if [[ $COMP_CWORD == "$1" ]]; then COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) @@ -1573,7 +1573,7 @@ _testhost_test() { prev="${COMP_WORDS[COMP_CWORD-1]}" COMPREPLY=() - opts="--settings --list-tests --environment --filter --test-adapter-path --logger --output --artifacts-path --diag --no-build --results-directory --collect --blame --blame-crash --blame-crash-dump-type --blame-crash-collect-always --blame-hang --blame-hang-dump-type --blame-hang-timeout --nologo --configuration --framework --runtime --no-restore --interactive --verbosity --arch --os --disable-build-servers --help" + opts="--settings --list-tests --environment --filter --test-adapter-path --logger --output --artifacts-path --diag --no-build --results-directory --collect --blame --blame-crash --blame-crash-dump-type --blame-crash-collect-always --blame-hang --blame-hang-dump-type --blame-hang-timeout --no-logo --configuration --framework --runtime --no-restore --interactive --verbosity --arch --os --disable-build-servers --help" if [[ $COMP_CWORD == "$1" ]]; then COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) @@ -2309,4 +2309,4 @@ _testhost_completions_script() { -complete -F _testhost testhost +complete -F _testhost testhost \ No newline at end of file diff --git a/test/dotnet.Tests/CompletionTests/snapshots/pwsh/DotnetCliSnapshotTests.VerifyCompletions.verified.ps1 b/test/dotnet.Tests/CompletionTests/snapshots/pwsh/DotnetCliSnapshotTests.VerifyCompletions.verified.ps1 index 7a9a85fb4e49..b040d0120188 100644 --- a/test/dotnet.Tests/CompletionTests/snapshots/pwsh/DotnetCliSnapshotTests.VerifyCompletions.verified.ps1 +++ b/test/dotnet.Tests/CompletionTests/snapshots/pwsh/DotnetCliSnapshotTests.VerifyCompletions.verified.ps1 @@ -84,7 +84,10 @@ Register-ArgumentCompleter -Native -CommandName 'testhost' -ScriptBlock { [CompletionResult]::new('--artifacts-path', '--artifacts-path', [CompletionResultType]::ParameterName, "The artifacts path. All output from the project, including build, publish, and pack output, will go in subfolders under the specified path.") [CompletionResult]::new('--no-incremental', '--no-incremental', [CompletionResultType]::ParameterName, "Do not use incremental building.") [CompletionResult]::new('--no-dependencies', '--no-dependencies', [CompletionResultType]::ParameterName, "Do not build project-to-project references and only build the specified project.") - [CompletionResult]::new('--nologo', '--nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '--no-logo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '--nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '-nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '/nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") [CompletionResult]::new('--self-contained', '--self-contained', [CompletionResultType]::ParameterName, "Publish the .NET runtime with your application so the runtime doesn`'t need to be installed on the target machine. The default is `'false.`' However, when targeting .NET 7 or lower, the default is `'true`' if a runtime identifier is specified.") [CompletionResult]::new('--self-contained', '--sc', [CompletionResultType]::ParameterName, "Publish the .NET runtime with your application so the runtime doesn`'t need to be installed on the target machine. The default is `'false.`' However, when targeting .NET 7 or lower, the default is `'true`' if a runtime identifier is specified.") [CompletionResult]::new('--no-self-contained', '--no-self-contained', [CompletionResultType]::ParameterName, "Publish your application as a framework dependent application. A compatible .NET runtime must be installed on the target machine to run your application.") @@ -132,7 +135,10 @@ Register-ArgumentCompleter -Native -CommandName 'testhost' -ScriptBlock { [CompletionResult]::new('--output', '--output', [CompletionResultType]::ParameterName, "The directory containing the build artifacts to clean.") [CompletionResult]::new('--output', '-o', [CompletionResultType]::ParameterName, "The directory containing the build artifacts to clean.") [CompletionResult]::new('--artifacts-path', '--artifacts-path', [CompletionResultType]::ParameterName, "The artifacts path. All output from the project, including build, publish, and pack output, will go in subfolders under the specified path.") - [CompletionResult]::new('--nologo', '--nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '--no-logo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '--nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '-nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '/nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") [CompletionResult]::new('--disable-build-servers', '--disable-build-servers', [CompletionResultType]::ParameterName, "Force the command to ignore any persistent build servers.") [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, "Show command line help.") [CompletionResult]::new('--help', '-h', [CompletionResultType]::ParameterName, "Show command line help.") @@ -550,7 +556,10 @@ Register-ArgumentCompleter -Native -CommandName 'testhost' -ScriptBlock { [CompletionResult]::new('--include-source', '--include-source', [CompletionResultType]::ParameterName, "Include PDBs and source files. Source files go into the `'src`' folder in the resulting nuget package.") [CompletionResult]::new('--serviceable', '--serviceable', [CompletionResultType]::ParameterName, "Set the serviceable flag in the package. See https://aka.ms/nupkgservicing for more information.") [CompletionResult]::new('--serviceable', '-s', [CompletionResultType]::ParameterName, "Set the serviceable flag in the package. See https://aka.ms/nupkgservicing for more information.") - [CompletionResult]::new('--nologo', '--nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '--no-logo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '--nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '-nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '/nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") [CompletionResult]::new('--interactive', '--interactive', [CompletionResultType]::ParameterName, "Allows the command to stop and wait for user input or action (for example to complete authentication).") [CompletionResult]::new('--no-restore', '--no-restore', [CompletionResultType]::ParameterName, "Do not restore the project before building.") [CompletionResult]::new('--verbosity', '--verbosity', [CompletionResultType]::ParameterName, "Set the MSBuild verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic].") @@ -715,7 +724,10 @@ Register-ArgumentCompleter -Native -CommandName 'testhost' -ScriptBlock { [CompletionResult]::new('--self-contained', '--self-contained', [CompletionResultType]::ParameterName, "Publish the .NET runtime with your application so the runtime doesn`'t need to be installed on the target machine. The default is `'false.`' However, when targeting .NET 7 or lower, the default is `'true`' if a runtime identifier is specified.") [CompletionResult]::new('--self-contained', '--sc', [CompletionResultType]::ParameterName, "Publish the .NET runtime with your application so the runtime doesn`'t need to be installed on the target machine. The default is `'false.`' However, when targeting .NET 7 or lower, the default is `'true`' if a runtime identifier is specified.") [CompletionResult]::new('--no-self-contained', '--no-self-contained', [CompletionResultType]::ParameterName, "Publish your application as a framework dependent application. A compatible .NET runtime must be installed on the target machine to run your application.") - [CompletionResult]::new('--nologo', '--nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '--no-logo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '--nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '-nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '/nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") [CompletionResult]::new('--framework', '--framework', [CompletionResultType]::ParameterName, "The target framework to publish for. The target framework has to be specified in the project file.") [CompletionResult]::new('--framework', '-f', [CompletionResultType]::ParameterName, "The target framework to publish for. The target framework has to be specified in the project file.") [CompletionResult]::new('--runtime', '--runtime', [CompletionResultType]::ParameterName, "The target runtime to publish for. This is used when creating a self-contained deployment. The default is to publish a framework-dependent application.") @@ -814,6 +826,10 @@ Register-ArgumentCompleter -Native -CommandName 'testhost' -ScriptBlock { [CompletionResult]::new('--locked-mode', '--locked-mode', [CompletionResultType]::ParameterName, "Don`'t allow updating project lock file.") [CompletionResult]::new('--lock-file-path', '--lock-file-path', [CompletionResultType]::ParameterName, "Output location where project lock file is written. By default, this is `'PROJECT_ROOT\packages.lock.json`'.") [CompletionResult]::new('--force-evaluate', '--force-evaluate', [CompletionResultType]::ParameterName, "Forces restore to reevaluate all dependencies even if a lock file already exists.") + [CompletionResult]::new('--no-logo', '--no-logo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '--nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '-nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '/nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") [CompletionResult]::new('--arch', '--arch', [CompletionResultType]::ParameterName, "The target architecture.") [CompletionResult]::new('--arch', '-a', [CompletionResultType]::ParameterName, "The target architecture.") [CompletionResult]::new('--os', '--os', [CompletionResultType]::ParameterName, "The target operating system.") @@ -831,7 +847,7 @@ Register-ArgumentCompleter -Native -CommandName 'testhost' -ScriptBlock { [CompletionResult]::new('--framework', '-f', [CompletionResultType]::ParameterName, "The target framework to run for. The target framework must also be specified in the project file.") [CompletionResult]::new('--runtime', '--runtime', [CompletionResultType]::ParameterName, "The target runtime to run for.") [CompletionResult]::new('--runtime', '-r', [CompletionResultType]::ParameterName, "The target runtime to run for.") - [CompletionResult]::new('--project', '--project', [CompletionResultType]::ParameterName, "The path to the project file to run (defaults to the current directory if there is only one project).") + [CompletionResult]::new('--project', '--project', [CompletionResultType]::ParameterName, "Defines the path of the project file to run. Use path to the project file, or path to the directory containing the project file. If not specified, it defaults to the current directory.") [CompletionResult]::new('--file', '--file', [CompletionResultType]::ParameterName, "The path to the file-based app to run (can be also passed as the first argument if there is no project in the current directory).") [CompletionResult]::new('--launch-profile', '--launch-profile', [CompletionResultType]::ParameterName, "The name of the launch profile (if any) to use when launching the application.") [CompletionResult]::new('--launch-profile', '-lp', [CompletionResultType]::ParameterName, "The name of the launch profile (if any) to use when launching the application.") @@ -935,6 +951,10 @@ Register-ArgumentCompleter -Native -CommandName 'testhost' -ScriptBlock { [CompletionResult]::new('--use-current-runtime', '--use-current-runtime', [CompletionResultType]::ParameterName, "Use current runtime as the target runtime.") [CompletionResult]::new('--use-current-runtime', '--ucr', [CompletionResultType]::ParameterName, "Use current runtime as the target runtime.") [CompletionResult]::new('--disable-build-servers', '--disable-build-servers', [CompletionResultType]::ParameterName, "Force the command to ignore any persistent build servers.") + [CompletionResult]::new('--no-logo', '--no-logo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '--nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '-nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") + [CompletionResult]::new('--no-logo', '/nologo', [CompletionResultType]::ParameterName, "Do not display the startup banner or the copyright message.") [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, "Show command line help.") [CompletionResult]::new('--help', '-h', [CompletionResultType]::ParameterName, "Show command line help.") ) @@ -968,7 +988,10 @@ Register-ArgumentCompleter -Native -CommandName 'testhost' -ScriptBlock { [CompletionResult]::new('--blame-hang', '--blame-hang', [CompletionResultType]::ParameterName, "Run the tests in blame mode and enables collecting hang dump when test exceeds the given timeout.") [CompletionResult]::new('--blame-hang-dump-type', '--blame-hang-dump-type', [CompletionResultType]::ParameterName, "The type of crash dump to be collected. The supported values are full (default), mini, and none. When `'none`' is used then test host is terminated on timeout, but no dump is collected. Implies --blame-hang.") [CompletionResult]::new('--blame-hang-timeout', '--blame-hang-timeout', [CompletionResultType]::ParameterName, "Per-test timeout, after which hang dump is triggered and the testhost process is terminated. Default is 1h. The timeout value is specified in the following format: 1.5h / 90m / 5400s / 5400000ms. When no unit is used (e.g. 5400000), the value is assumed to be in milliseconds. When used together with data driven tests, the timeout behavior depends on the test adapter used. For xUnit, NUnit and MSTest 2.2.4+ the timeout is renewed after every test case, For MSTest before 2.2.4, the timeout is used for all testcases.") - [CompletionResult]::new('--nologo', '--nologo', [CompletionResultType]::ParameterName, "Run test(s), without displaying Microsoft Testplatform banner") + [CompletionResult]::new('--no-logo', '--no-logo', [CompletionResultType]::ParameterName, "Run test(s), without displaying Microsoft Testplatform banner") + [CompletionResult]::new('--no-logo', '--nologo', [CompletionResultType]::ParameterName, "Run test(s), without displaying Microsoft Testplatform banner") + [CompletionResult]::new('--no-logo', '-nologo', [CompletionResultType]::ParameterName, "Run test(s), without displaying Microsoft Testplatform banner") + [CompletionResult]::new('--no-logo', '/nologo', [CompletionResultType]::ParameterName, "Run test(s), without displaying Microsoft Testplatform banner") [CompletionResult]::new('--configuration', '--configuration', [CompletionResultType]::ParameterName, "The configuration to use for running tests. The default for most projects is `'Debug`'.") [CompletionResult]::new('--configuration', '-c', [CompletionResultType]::ParameterName, "The configuration to use for running tests. The default for most projects is `'Debug`'.") [CompletionResult]::new('--framework', '--framework', [CompletionResultType]::ParameterName, "The target framework to run tests for. The target framework must also be specified in the project file.") @@ -1210,7 +1233,7 @@ Register-ArgumentCompleter -Native -CommandName 'testhost' -ScriptBlock { [CompletionResult]::new('install', 'install', [CompletionResultType]::ParameterValue, "Install one or more workloads.") [CompletionResult]::new('update', 'update', [CompletionResultType]::ParameterValue, "Update all installed workloads.") [CompletionResult]::new('list', 'list', [CompletionResultType]::ParameterValue, "List workloads available.") - [CompletionResult]::new('search', 'search', [CompletionResultType]::ParameterValue, "Search for available workloads.") + [CompletionResult]::new('search', 'search', [CompletionResultType]::ParameterValue, "Search for available workloads or workload versions.") [CompletionResult]::new('uninstall', 'uninstall', [CompletionResultType]::ParameterValue, "Uninstall one or more workloads.") [CompletionResult]::new('repair', 'repair', [CompletionResultType]::ParameterValue, "Repair workload installations.") [CompletionResult]::new('restore', 'restore', [CompletionResultType]::ParameterValue, "Restore workloads required for a project.") diff --git a/test/dotnet.Tests/CompletionTests/snapshots/zsh/DotnetCliSnapshotTests.VerifyCompletions.verified.zsh b/test/dotnet.Tests/CompletionTests/snapshots/zsh/DotnetCliSnapshotTests.VerifyCompletions.verified.zsh index 0718ee370c8f..caa0d8d97a29 100644 --- a/test/dotnet.Tests/CompletionTests/snapshots/zsh/DotnetCliSnapshotTests.VerifyCompletions.verified.zsh +++ b/test/dotnet.Tests/CompletionTests/snapshots/zsh/DotnetCliSnapshotTests.VerifyCompletions.verified.zsh @@ -58,7 +58,10 @@ _testhost() { '--artifacts-path=[The artifacts path. All output from the project, including build, publish, and pack output, will go in subfolders under the specified path.]:ARTIFACTS_DIR: ' \ '--no-incremental[Do not use incremental building.]' \ '--no-dependencies[Do not build project-to-project references and only build the specified project.]' \ + '--no-logo[Do not display the startup banner or the copyright message.]' \ '--nologo[Do not display the startup banner or the copyright message.]' \ + '-nologo[Do not display the startup banner or the copyright message.]' \ + '/nologo[Do not display the startup banner or the copyright message.]' \ '--self-contained=[Publish the .NET runtime with your application so the runtime doesn'\''t need to be installed on the target machine. The default is '\''false.'\'' However, when targeting .NET 7 or lower, the default is '\''true'\'' if a runtime identifier is specified.]: :((False\:"False" True\:"True" ))' \ '--sc=[Publish the .NET runtime with your application so the runtime doesn'\''t need to be installed on the target machine. The default is '\''false.'\'' However, when targeting .NET 7 or lower, the default is '\''true'\'' if a runtime identifier is specified.]: :((False\:"False" True\:"True" ))' \ '--no-self-contained[Publish your application as a framework dependent application. A compatible .NET runtime must be installed on the target machine to run your application.]' \ @@ -121,7 +124,10 @@ _testhost() { '--output=[The directory containing the build artifacts to clean.]:OUTPUT_DIR: ' \ '-o=[The directory containing the build artifacts to clean.]:OUTPUT_DIR: ' \ '--artifacts-path=[The artifacts path. All output from the project, including build, publish, and pack output, will go in subfolders under the specified path.]:ARTIFACTS_DIR: ' \ + '--no-logo[Do not display the startup banner or the copyright message.]' \ '--nologo[Do not display the startup banner or the copyright message.]' \ + '-nologo[Do not display the startup banner or the copyright message.]' \ + '/nologo[Do not display the startup banner or the copyright message.]' \ '--disable-build-servers[Force the command to ignore any persistent build servers.]' \ '--help[Show command line help.]' \ '-h[Show command line help.]' \ @@ -538,7 +544,10 @@ _testhost() { '--include-source[Include PDBs and source files. Source files go into the '\''src'\'' folder in the resulting nuget package.]' \ '--serviceable[Set the serviceable flag in the package. See https\://aka.ms/nupkgservicing for more information.]' \ '-s[Set the serviceable flag in the package. See https\://aka.ms/nupkgservicing for more information.]' \ + '--no-logo[Do not display the startup banner or the copyright message.]' \ '--nologo[Do not display the startup banner or the copyright message.]' \ + '-nologo[Do not display the startup banner or the copyright message.]' \ + '/nologo[Do not display the startup banner or the copyright message.]' \ '--interactive=[Allows the command to stop and wait for user input or action (for example to complete authentication).]: :((False\:"False" True\:"True" ))' \ '--no-restore[Do not restore the project before building.]' \ '--verbosity=[Set the MSBuild verbosity level. Allowed values are q\[uiet\], m\[inimal\], n\[ormal\], d\[etailed\], and diag\[nostic\].]:LEVEL:((d\:"d" detailed\:"detailed" diag\:"diag" diagnostic\:"diagnostic" m\:"m" minimal\:"minimal" n\:"n" normal\:"normal" q\:"q" quiet\:"quiet" ))' \ @@ -723,7 +732,10 @@ _testhost() { '--self-contained=[Publish the .NET runtime with your application so the runtime doesn'\''t need to be installed on the target machine. The default is '\''false.'\'' However, when targeting .NET 7 or lower, the default is '\''true'\'' if a runtime identifier is specified.]: :((False\:"False" True\:"True" ))' \ '--sc=[Publish the .NET runtime with your application so the runtime doesn'\''t need to be installed on the target machine. The default is '\''false.'\'' However, when targeting .NET 7 or lower, the default is '\''true'\'' if a runtime identifier is specified.]: :((False\:"False" True\:"True" ))' \ '--no-self-contained[Publish your application as a framework dependent application. A compatible .NET runtime must be installed on the target machine to run your application.]' \ + '--no-logo[Do not display the startup banner or the copyright message.]' \ '--nologo[Do not display the startup banner or the copyright message.]' \ + '-nologo[Do not display the startup banner or the copyright message.]' \ + '/nologo[Do not display the startup banner or the copyright message.]' \ '--framework=[The target framework to publish for. The target framework has to be specified in the project file.]:FRAMEWORK:->dotnet_dynamic_complete' \ '-f=[The target framework to publish for. The target framework has to be specified in the project file.]:FRAMEWORK:->dotnet_dynamic_complete' \ '--runtime=[The target runtime to publish for. This is used when creating a self-contained deployment. The default is to publish a framework-dependent application.]:RUNTIME_IDENTIFIER:->dotnet_dynamic_complete' \ @@ -849,6 +861,10 @@ _testhost() { '--locked-mode[Don'\''t allow updating project lock file.]' \ '--lock-file-path=[Output location where project lock file is written. By default, this is '\''PROJECT_ROOT\packages.lock.json'\''.]:LOCK_FILE_PATH: ' \ '--force-evaluate[Forces restore to reevaluate all dependencies even if a lock file already exists.]' \ + '--no-logo[Do not display the startup banner or the copyright message.]' \ + '--nologo[Do not display the startup banner or the copyright message.]' \ + '-nologo[Do not display the startup banner or the copyright message.]' \ + '/nologo[Do not display the startup banner or the copyright message.]' \ '--arch=[The target architecture.]:ARCH: ' \ '-a=[The target architecture.]:ARCH: ' \ '--os=[The target operating system.]:OS: ' \ @@ -875,7 +891,7 @@ _testhost() { '-f=[The target framework to run for. The target framework must also be specified in the project file.]:FRAMEWORK:->dotnet_dynamic_complete' \ '--runtime=[The target runtime to run for.]:RUNTIME_IDENTIFIER:->dotnet_dynamic_complete' \ '-r=[The target runtime to run for.]:RUNTIME_IDENTIFIER:->dotnet_dynamic_complete' \ - '--project=[The path to the project file to run (defaults to the current directory if there is only one project).]:PROJECT_PATH: ' \ + '--project=[Defines the path of the project file to run. Use path to the project file, or path to the directory containing the project file. If not specified, it defaults to the current directory.]:PROJECT_PATH: ' \ '--file=[The path to the file-based app to run (can be also passed as the first argument if there is no project in the current directory).]:FILE_PATH: ' \ '--launch-profile=[The name of the launch profile (if any) to use when launching the application.]:LAUNCH_PROFILE: ' \ '-lp=[The name of the launch profile (if any) to use when launching the application.]:LAUNCH_PROFILE: ' \ @@ -989,6 +1005,10 @@ _testhost() { '--use-current-runtime[Use current runtime as the target runtime.]' \ '--ucr[Use current runtime as the target runtime.]' \ '--disable-build-servers[Force the command to ignore any persistent build servers.]' \ + '--no-logo[Do not display the startup banner or the copyright message.]' \ + '--nologo[Do not display the startup banner or the copyright message.]' \ + '-nologo[Do not display the startup banner or the copyright message.]' \ + '/nologo[Do not display the startup banner or the copyright message.]' \ '--help[Show command line help.]' \ '-h[Show command line help.]' \ '*::argument: ' \ @@ -1031,7 +1051,10 @@ _testhost() { '--blame-hang[Run the tests in blame mode and enables collecting hang dump when test exceeds the given timeout.]' \ '--blame-hang-dump-type=[The type of crash dump to be collected. The supported values are full (default), mini, and none. When '\''none'\'' is used then test host is terminated on timeout, but no dump is collected. Implies --blame-hang.]:DUMP_TYPE:((full\:"full" mini\:"mini" none\:"none" ))' \ '--blame-hang-timeout=[Per-test timeout, after which hang dump is triggered and the testhost process is terminated. Default is 1h. The timeout value is specified in the following format\: 1.5h / 90m / 5400s / 5400000ms. When no unit is used (e.g. 5400000), the value is assumed to be in milliseconds. When used together with data driven tests, the timeout behavior depends on the test adapter used. For xUnit, NUnit and MSTest 2.2.4+ the timeout is renewed after every test case, For MSTest before 2.2.4, the timeout is used for all testcases.]:TIMESPAN: ' \ + '--no-logo[Run test(s), without displaying Microsoft Testplatform banner]' \ '--nologo[Run test(s), without displaying Microsoft Testplatform banner]' \ + '-nologo[Run test(s), without displaying Microsoft Testplatform banner]' \ + '/nologo[Run test(s), without displaying Microsoft Testplatform banner]' \ '--configuration=[The configuration to use for running tests. The default for most projects is '\''Debug'\''.]:CONFIGURATION:->dotnet_dynamic_complete' \ '-c=[The configuration to use for running tests. The default for most projects is '\''Debug'\''.]:CONFIGURATION:->dotnet_dynamic_complete' \ '--framework=[The target framework to run tests for. The target framework must also be specified in the project file.]:FRAMEWORK:->dotnet_dynamic_complete' \ @@ -1961,7 +1984,7 @@ _testhost__workload_commands() { 'install:Install one or more workloads.' \ 'update:Update all installed workloads.' \ 'list:List workloads available.' \ - 'search:Search for available workloads.' \ + 'search:Search for available workloads or workload versions.' \ 'uninstall:Uninstall one or more workloads.' \ 'repair:Repair workload installations.' \ 'restore:Restore workloads required for a project.' \ diff --git a/test/dotnet.Tests/ShellShimTests/WindowsEnvironmentPathTests.cs b/test/dotnet.Tests/ShellShimTests/WindowsEnvironmentPathTests.cs index 482f1977d298..cbffb315f254 100644 --- a/test/dotnet.Tests/ShellShimTests/WindowsEnvironmentPathTests.cs +++ b/test/dotnet.Tests/ShellShimTests/WindowsEnvironmentPathTests.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Diagnostics.CodeAnalysis; using Microsoft.DotNet.Cli; using Microsoft.DotNet.Cli.ShellShim; using Microsoft.DotNet.Cli.Utils; @@ -190,6 +191,10 @@ private static string Expand(string path) { return path?.Replace("%USERPROFILE%", @"C:\Users\username"); } + + public bool TryGetEnvironmentVariable(string name, [NotNullWhen(true)] out string value) => throw new NotImplementedException(); + public bool TryGetEnvironmentVariableAsBool(string name, [NotNullWhen(true)] out bool value) => throw new NotImplementedException(); + public bool TryGetEnvironmentVariableAsInt(string name, [NotNullWhen(true)] out int value) => throw new NotImplementedException(); } private class MockEnvironmentPathEditor : IWindowsRegistryEnvironmentPathEditor diff --git a/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs b/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs index 4e28b92479d7..ae1ced7c696d 100644 --- a/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs +++ b/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs @@ -195,7 +195,8 @@ public void CanDetectLLMStatusForEnvVars(Dictionary? envVars, st { try { - if (envVars is not null){ + if (envVars is not null) + { foreach (var (key, value) in envVars) { Environment.SetEnvironmentVariable(key, value); @@ -214,7 +215,7 @@ public void CanDetectLLMStatusForEnvVars(Dictionary? envVars, st } } } - + [Theory] [InlineData("dummySessionId")] [InlineData(null)] @@ -253,17 +254,17 @@ public void TelemetryCommonPropertiesShouldContainSessionId(string? sessionId) { new Dictionary { { "CI", "true"} }, true }, { new Dictionary { { "TRAVIS", "true"} }, true }, { new Dictionary { { "CIRCLECI", "true"} }, true }, -{ new Dictionary { { "CODEBUILD_BUILD_ID", "hi" }, { "AWS_REGION", "hi" } }, true }, + { new Dictionary { { "CODEBUILD_BUILD_ID", "hi" }, { "AWS_REGION", "hi" } }, true }, { new Dictionary { { "CODEBUILD_BUILD_ID", "hi" } }, false }, { new Dictionary { { "BUILD_ID", "hi" }, { "BUILD_URL", "hi" } }, true }, { new Dictionary { { "BUILD_ID", "hi" } }, false }, { new Dictionary { { "BUILD_ID", "hi" }, { "PROJECT_ID", "hi" } }, true }, { new Dictionary { { "BUILD_ID", "hi" } }, false }, -{ new Dictionary { { "TEAMCITY_VERSION", "hi" } }, true }, + { new Dictionary { { "TEAMCITY_VERSION", "hi" } }, true }, { new Dictionary { { "TEAMCITY_VERSION", "" } }, false }, { new Dictionary { { "JB_SPACE_API_URL", "hi" } }, true }, { new Dictionary { { "JB_SPACE_API_URL", "" } }, false }, -{ new Dictionary { { "SomethingElse", "hi" } }, false }, + { new Dictionary { { "SomethingElse", "hi" } }, false }, }; private class NothingCache : IUserLevelCacheWriter diff --git a/test/dotnet.Tests/dotnet.Tests.csproj b/test/dotnet.Tests/dotnet.Tests.csproj index 1b1529f68ee6..b249467a662b 100644 --- a/test/dotnet.Tests/dotnet.Tests.csproj +++ b/test/dotnet.Tests/dotnet.Tests.csproj @@ -26,6 +26,7 @@ +