diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index dd415c86cc845..f7222d9948a42 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -92,7 +92,6 @@ When starting any task or answering any question about this repo: ### Doc Update Obligation Every task that changes code must end with a doc pass: -- Added or moved files? → Update `.github/memory/FILE_MAP.md` (top-level) and the matching `.github/instructions/.instructions.md` (directory detail). - Changed a public interface, diagnostic ID, or API? → Update the relevant `.github/instructions/.instructions.md` and `PublicAPI.Unshipped.txt`. - Hit something surprising or undocumented? → Ask the user how they want it documented. - Established a new pattern? → Repo-wide → `.github/memory/CONVENTIONS.md`; layer-specific → the matching `.github/instructions/.instructions.md`. @@ -124,6 +123,8 @@ Write the plan to `plan.md` in your session folder (see the session context) and **Post the plan and wait for approval before writing the implementing diff** — the plan is meant to be reviewed now, not after a large diff already exists. +If a pull request already exists when work pauses for plan approval, update its description or add a comment that clearly states the implementation is **not complete**, summarizes what remains, and links to the Copilot session containing the plan and approval request so the user can provide further instructions. + Then implement, keeping the diff **scoped and reviewable** — prefer the smallest change that fully addresses the task over a broad refactor. If the plan changes materially while implementing, update it rather than silently diverging. Only after the plan's **Acceptance** and **Validation** are satisfied (and the Definition of Done below passes) is the work "done." Trivial changes don't need a written plan — go straight to the Definition of Done. diff --git a/.github/instructions/IDE.instructions.md b/.github/instructions/IDE.instructions.md index 12a3115d48505..b96a454a7d689 100644 --- a/.github/instructions/IDE.instructions.md +++ b/.github/instructions/IDE.instructions.md @@ -15,6 +15,7 @@ Roslyn uses a **layered service architecture** built on MEF (Managed Extensibili - **LanguageServer** (`src/LanguageServer/`): Shared LSP protocol implementation and Roslyn LSP executable (`roslyn-language-server`) - **EditorFeatures** (`src/EditorFeatures/`): VS Editor integration and text manipulation - **VisualStudio** (`src/VisualStudio/`): Visual Studio-specific implementations +- **VisualStudio integration-test harness** (`src/VisualStudio/IntegrationTest/Harness/`): shared integration-test infrastructure - **EditorConfig templates** (`src/VisualStudio/EditorConfig/`): item templates, generation wizard, context-menu command, VSIX projects, and Visual Studio insertion setup - The setup insertion component is `Templates.Editorconfig.Setup`, but its SWR package identity must remain `Templates.Editorconfig.SolutionFile.Setup` because existing Visual Studio template packages depend on that ID. @@ -104,3 +105,4 @@ var methodDecl = generator.MethodDeclaration("MyMethod", ...); - **ImportingConstructor must be marked `[Obsolete]`** with `MefConstruction.ImportingConstructorMessage` - **Language services must be exported with a specific language name** — don't use generic exports for both C#/VB - **Workspace changes must use immutable updates** — `Workspace.SetCurrentSolution()` +- **MSBuild project extensions are stored with a leading `.`.** `ProjectFileExtensionRegistry` accepts registration and lookup values with or without the dot, but its enumeration API returns the canonical dot-prefixed form. diff --git a/.github/instructions/Razor.instructions.md b/.github/instructions/Razor.instructions.md index 39d949aa8ce67..d011147d2f8a1 100644 --- a/.github/instructions/Razor.instructions.md +++ b/.github/instructions/Razor.instructions.md @@ -36,6 +36,9 @@ their original sub-tree layout fields through the constructor. When adding a new field, thread it through every existing `With*` method. Prefer computing derived data via extension methods (e.g., `GetUnusedDirectives()`) rather than storing computed results as fields. +- **Razor engine concurrency**: Hosts can process multiple documents concurrently through one + `RazorProjectEngine`. Phase and pass instances are shared, so keep per-document state in locals + or an execution context rather than mutable instance fields. - **Razor documents in Roslyn**: Stored as additional documents. Resolve via `solution.GetDocumentIdsWithFilePath(filePath)` then `solution.GetAdditionalDocument(documentId)`. - **Razor documents with virtual URIs**: Remote Razor document classification preserves the full @@ -72,7 +75,7 @@ When adding a new `IRemote*Service` and `Remote*Service`: 3. Register in `src\Razor\src\Razor\src\Microsoft.CodeAnalysis.Razor.Workspaces\Remote\RazorServices.cs` (add to `MessagePackServices` or `JsonServices`). -4. **Add an entry to `eng\targets\RazorServices.props`** (at the Roslyn repo root, not under +4. **Add an entry to the repo-root `eng\targets\RazorServices.props` file** (not under `src\Razor`): `Include="Microsoft.VisualStudio.Razor.{ShortName}"` with `ClassName="{FullTypeName}+Factory"`. The `ShortName` is your interface name with diff --git a/.github/memory/API_MAP.md b/.github/memory/API_MAP.md index 33c342fa441ce..200c5d8de515d 100644 --- a/.github/memory/API_MAP.md +++ b/.github/memory/API_MAP.md @@ -13,6 +13,7 @@ Repo-wide entry points and the formal public-API tracking rules. Layer-specific | Command | Purpose | |---------|---------| | `build.sh` / `Build.cmd` | Full solution build (Arcade). | +| `dotnet restore ` | Restores dependencies and, on the first restore in a clone, validates checkout line endings and Windows long-path support. A successful validation is cached at `artifacts/developer-settings-validated.txt`. | | `dotnet build Compilers.slnf` | Compiler-only build. | | `dotnet build Ide.slnf` | IDE-only build. | | `dotnet build Razor.slnf` | Razor compiler & tooling-only build. | diff --git a/.github/memory/CONVENTIONS.md b/.github/memory/CONVENTIONS.md index a02d617d3b403..03ad842b25712 100644 --- a/.github/memory/CONVENTIONS.md +++ b/.github/memory/CONVENTIONS.md @@ -47,6 +47,12 @@ var symbolInfo = semanticModel.GetSymbolInfo(expression, cancellationToken); - Judge change size by cognitive load and validation boundaries, not an arbitrary line count; generated and mechanical updates may be large while still representing one focused change. - A change is complete only after applicable formatting, analyzers, affected builds, targeted tests, generated/resource/API updates, final diff review, and documentation freshness work are complete. The canonical ordered checklist is the **Definition of Done** in `.github/copilot-instructions.md`. +### CodeAnalysis testing-library dependencies + +- Compatible internal repository build and test projects reference the testing-library projects under `src/RoslynSdk/Microsoft.CodeAnalysis.Testing` so source changes are exercised directly. +- The testing-library projects do not copy NuGet runtime dependencies into their .NET Framework output directories. Final test projects resolve and copy the unified dependency graph. +- Roslyn SDK samples and Visual Studio SDK project templates retain NuGet package references because they model standalone consumers outside the repository source graph. + ## Patterns Explicitly Avoided - **No `TODO` or `TODO2` comments** — CI correctness leg flags `TODO`. Track follow-up work as a GitHub issue and link it in code (e.g. `// https://github.com/dotnet/roslyn/issues/NNNN`). Existing `TODO2` markers are a frozen baseline from when enforcement started, not a pattern to follow. diff --git a/.github/memory/FILE_MAP.md b/.github/memory/FILE_MAP.md index a336e0be03676..db070369037ee 100644 --- a/.github/memory/FILE_MAP.md +++ b/.github/memory/FILE_MAP.md @@ -23,7 +23,7 @@ This file is a **top-level map only**. For per-area directory detail, read the m | `Features/`, `EditorFeatures/` | ide | IDE feature logic and editor integration. | | `Analyzers/`, `CodeStyle/` | ide | IDE0xxx code-style analyzers & fixes. | | `LanguageServer/` | ide | LSP server. | -| `VisualStudio/` | ide | VS language services, UI, and EditorConfig template/wizard/command packaging under `EditorConfig/`. | +| `VisualStudio/` | ide | VS language services, UI, EditorConfig template/wizard/command packaging under `EditorConfig/`, and integration-test infrastructure under `IntegrationTest/`. | | `Razor/src/` | razor | Razor compiler + tooling (own sub-tree layout). | | `Scripting/`, `Interactive/` | — | C#/VB scripting engine and REPL. | | `RoslynAnalyzers/` | — | Shipping `Microsoft.CodeAnalysis.*` analyzer packages. | @@ -34,6 +34,6 @@ This file is a **top-level map only**. For per-area directory detail, read the m | Path | Status | Purpose | |------|--------|---------| -| `eng/` | Config / Generated | Arcade build engineering. Pipeline definitions and templates live in `eng/pipelines/`; `eng/common/` is DARC-synced and must not be hand-edited. `eng/generate-compiler-code.cs` regenerates compiler code. | +| `eng/` | Config / Generated | Arcade build engineering. Shared project and solution build targets live in `eng/targets/`; pipeline definitions and templates live in `eng/pipelines/`; `eng/common/` is DARC-synced and must not be hand-edited. `eng/generate-compiler-code.cs` regenerates compiler code. | | `docs/` | Active | Contributor & design docs. New docs use kebab-case filenames in the right subdirectory. | | Root | Config | Entry points & solution filters: `build.sh`/`Build.cmd`, `test.sh`/`Test.cmd`, `Roslyn.slnx`, `Compilers.slnf`, `Ide.slnf`, `Razor.slnf`, `global.json`, `Directory.*.props/targets`, `Directory.Packages.props`. | diff --git a/.github/memory/TESTING_STRATEGY.md b/.github/memory/TESTING_STRATEGY.md index aa0c0f49770b5..eb448bc9826d7 100644 --- a/.github/memory/TESTING_STRATEGY.md +++ b/.github/memory/TESTING_STRATEGY.md @@ -26,6 +26,11 @@ Frameworks: xUnit with Roslyn test utilities. - Prefer raw string literals (`"""..."""`) over verbatim strings for test source code. - Keep tests focused: use `.Single()` rather than asserting a count then indexing. +- Analyzer testing-library tests should use `ReferenceAssemblies.Default` for + the stable .NET Core 3.1 reference surface. Use an explicit + `ReferenceAssemblies` value, such as `ReferenceAssemblies.Net.Net100`, when + the scenario intentionally depends on a newer or specific framework API + surface. - For issue-linked changes, add a `WorkItem` attribute next to the test attribute, e.g. `[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/1234")]` or `[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/1234")]`. diff --git a/.github/memory/testing/ide.md b/.github/memory/testing/ide.md index 7da57bf11ffd8..2f32170883d79 100644 --- a/.github/memory/testing/ide.md +++ b/.github/memory/testing/ide.md @@ -35,3 +35,7 @@ public class MyTests test source code. - Keep tests focused — avoid unnecessary intermediary assertions; use `.Single()` rather than asserting a count then indexing. +- Language Server orchestration tests can pass additional MEF parts to + `LanguageServerTestComposition.GetSharedExportProvider`. A controllable + `PartNotDiscoverable` project loader can provide deterministic design-time + build timing and results without invoking MSBuild. diff --git a/.github/skills/code-review/SKILL.md b/.github/skills/code-review/SKILL.md index 780e96c9e3a3a..0c1cddd9cc5d9 100644 --- a/.github/skills/code-review/SKILL.md +++ b/.github/skills/code-review/SKILL.md @@ -19,6 +19,12 @@ Use this skill when: ## Review Process +### Review Progress Updates + +1. Print your plan before beginning +2. After each step of your plan, print a quick result and your next step +3. Print something at least every few minutes so humans can audit activity + ### Step 0: Gather Code Context (No PR Narrative Yet) Before analyzing anything, collect as much relevant **code** context as you can. **Critically, do NOT read the PR description, linked issues, or existing review comments yet.** You must form your own independent assessment of what the code does, why it might be needed, what problems it has, and whether the approach is sound — before being exposed to the author's framing. Reading the author's narrative first anchors your judgment and makes you less likely to find real problems. diff --git a/.github/skills/update-agent-docs/SKILL.md b/.github/skills/update-agent-docs/SKILL.md index 265d704dd1105..bda46c73b9dd0 100644 --- a/.github/skills/update-agent-docs/SKILL.md +++ b/.github/skills/update-agent-docs/SKILL.md @@ -12,13 +12,11 @@ Run at the end of every task that changes code. This is not optional. ## Checklist -**Files added or moved?** → Update `.github/memory/FILE_MAP.md` (top-level area) and the matching `.github/instructions/.instructions.md` (directory detail). - **Memory file added, removed, renamed, or had its purpose change?** → Update `.github/memory/INDEX.md` and any memory files that reference it. -**Public API changed?** → Update the matching `.github/instructions/.instructions.md` (Compiler/IDE) and the owning project's `PublicAPI.Unshipped.txt` (RS0016 enforces this). `API_MAP.md` covers only repo-wide entry points. +**Public API changed?** → Update the owning project's `PublicAPI.Unshipped.txt` (RS0016 enforces this). `API_MAP.md` covers only repo-wide entry points. -**New compiler error code, IDE diagnostic ID, or resource string added?** → Reflect it in the matching `.github/instructions/.instructions.md`; ensure `ErrorCode.cs` / `IDEDiagnosticIds.cs` / `.resx` (+ `/t:UpdateXlf`) are consistent. +**New compiler error code, IDE diagnostic ID, or resource string added?** → Ensure `ErrorCode.cs` / `IDEDiagnosticIds.cs` / `.resx` (+ `/t:UpdateXlf`) are consistent. **New pattern established?** → If repo-wide, add to `.github/memory/CONVENTIONS.md`; if layer-specific, add to the matching `.github/instructions/.instructions.md`. diff --git a/Directory.Solution.targets b/Directory.Solution.targets new file mode 100644 index 0000000000000..d80e355640d85 --- /dev/null +++ b/Directory.Solution.targets @@ -0,0 +1,3 @@ + + + diff --git a/Ide.slnf b/Ide.slnf index ac6262769bd96..23f2469411bad 100644 --- a/Ide.slnf +++ b/Ide.slnf @@ -232,7 +232,6 @@ "src\\VisualStudio\\Setup\\Roslyn.VisualStudio.Setup.csproj", "src\\VisualStudio\\TestUtilities2\\Microsoft.VisualStudio.LanguageServices.Test.Utilities2.vbproj", "src\\VisualStudio\\VisualBasic\\Impl\\Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj", - "src\\VisualStudio\\VisualStudioDiagnosticsToolWindow\\Roslyn.VisualStudio.DiagnosticsWindow.csproj", "src\\VisualStudio\\Xaml\\Impl\\Microsoft.VisualStudio.LanguageServices.Xaml.csproj", "src\\Workspaces\\CSharpTest\\Microsoft.CodeAnalysis.CSharp.Workspaces.UnitTests.csproj", "src\\Workspaces\\CSharp\\Portable\\Microsoft.CodeAnalysis.CSharp.Workspaces.csproj", diff --git a/Roslyn.slnx b/Roslyn.slnx index cf192af52ef08..1ed1f08313c37 100644 --- a/Roslyn.slnx +++ b/Roslyn.slnx @@ -462,7 +462,6 @@ - @@ -497,7 +496,6 @@ - @@ -544,6 +542,8 @@ + + diff --git a/azure-pipelines-integration.yml b/azure-pipelines-integration.yml index d44cf5ab3a721..bab19587cae2c 100644 --- a/azure-pipelines-integration.yml +++ b/azure-pipelines-integration.yml @@ -8,7 +8,6 @@ trigger: - release/* - features/* - demos/* - - dev/* exclude: # Since the version of VS on the integration VM images are a moving target, # we are unable to reliably run integration tests on servicing branches. @@ -26,6 +25,7 @@ pr: - release/* - features/* - demos/* + - dev/* exclude: # Since the version of VS on the integration VM images are a moving target, # we are unable to reliably run integration tests on servicing branches. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8c10056daa80f..1293fe6449606 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -6,7 +6,6 @@ trigger: - release/* - features/* - demos/* -- dev/* # Branches that trigger builds on PR pr: @@ -18,6 +17,7 @@ pr: - release/* - features/* - demos/* + - dev/* paths: exclude: - docs/* @@ -540,7 +540,7 @@ stages: steps: - template: eng/pipelines/checkout-unix-task.yml - - script: ./eng/build.sh --solution Roslyn.slnx --restore --build --configuration Debug --prepareMachine --ci --binaryLog --runanalyzers --warnaserror /p:RoslynEnforceCodeStyle=true + - script: ./eng/build.sh --solution Roslyn.slnx --restore --build --configuration Debug --prepareMachine --ci --binaryLog --runanalyzers --warnaserror displayName: Build with analyzers - template: eng/pipelines/publish-logs.yml diff --git a/eng/Packages.props b/eng/Packages.props index 4cfb755db62ed..6f1eb97d5afb4 100644 --- a/eng/Packages.props +++ b/eng/Packages.props @@ -1,7 +1,6 @@ - 1.1.3-beta1.26059.1 <_BasicReferenceAssembliesVersion>1.8.8 5.9.0 @@ -184,6 +183,7 @@ --> + @@ -287,13 +287,7 @@ - - - - - - @@ -349,8 +343,6 @@ vs-extension-testing --> - - diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 6a80f6308c817..7700a4d142f0e 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -23,7 +23,7 @@ This file should be imported by eng/Versions.props 10.0.10 10.0.10 10.0.10 - 3.0.0-rc.2.26453.107 + 3.0.0-rc.2.26458.109 10.0.10 10.0.10 10.0.10 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index d0d8f38d8c7bf..f69acc6ec967c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,15 +1,15 @@ - + https://github.com/dotnet/roslyn 92a36efa9ae1a26a14872f87798526a1c3dd050b - + https://github.com/dotnet/dotnet - a12253cf4283fcf4a54a7d2732aae2e8821f833a + c65c25f0c9889e06a5d70e275c741d8430fa353a diff --git a/eng/build.ps1 b/eng/build.ps1 index f8f3e55816a65..8558dbbc08f39 100644 --- a/eng/build.ps1 +++ b/eng/build.ps1 @@ -659,7 +659,6 @@ function Deploy-VsixViaTool() { "Microsoft.VisualStudio.RazorExtension.Dependencies.vsix", "Microsoft.VisualStudio.RazorExtension.vsix", "ExpressionEvaluatorPackage.vsix", - "Roslyn.VisualStudio.DiagnosticsWindow.vsix", "Microsoft.VisualStudio.IntegrationTest.Setup.vsix") foreach ($vsixFileName in $orderedVsixFileNames) { diff --git a/eng/config/PublishData.json b/eng/config/PublishData.json index 855c1f0fcaeac..b98f756aba9e4 100644 --- a/eng/config/PublishData.json +++ b/eng/config/PublishData.json @@ -42,6 +42,7 @@ "Microsoft.Net.Compilers.Toolset": "arcade", "Microsoft.Net.Compilers.Toolset.Arm64": "arcade", "Microsoft.Net.Compilers.Toolset.Framework": "arcade", + "Microsoft.NET.ProjectData.Tasks": "arcade", "Microsoft.CodeAnalysis.BuildClient": "arcade", "Microsoft.CodeAnalysis.Contracts": "arcade", "Microsoft.CodeAnalysis.Debugging": "arcade", diff --git a/eng/config/test/Desktop/app.config b/eng/config/test/Desktop/app.config index 5d5bfd354f04b..07353f558f6a5 100644 --- a/eng/config/test/Desktop/app.config +++ b/eng/config/test/Desktop/app.config @@ -2,6 +2,18 @@ + + + + + + + + + diff --git a/eng/make-bootstrap.ps1 b/eng/make-bootstrap.ps1 index 950d3cf3f8dbd..3adeca20c6548 100644 --- a/eng/make-bootstrap.ps1 +++ b/eng/make-bootstrap.ps1 @@ -57,7 +57,7 @@ try { # MSBuildAdditionalCommandLineArgs= $args = "/p:TreatWarningsAsErrors=true /warnaserror /nologo /nodeReuse:false /p:Configuration=$configuration /v:m"; $args += " /p:RunAnalyzersDuringBuild=false /bl:$binaryLogFilePath" - $args += " /t:Pack /p:RoslynEnforceCodeStyle=false /p:DotNetUseShippingVersions=true /p:InitialDefineConstants=BOOTSTRAP" + $args += " /t:Pack /p:DotNetUseShippingVersions=true /p:InitialDefineConstants=BOOTSTRAP" $args += " /p:PackageOutputPath=$output /p:NgenOptimization=false /p:PublishWindowsPdb=false" if ($ci) { diff --git a/eng/targets/DeveloperSettings.targets b/eng/targets/DeveloperSettings.targets new file mode 100644 index 0000000000000..71785bedc9111 --- /dev/null +++ b/eng/targets/DeveloperSettings.targets @@ -0,0 +1,32 @@ + + + + + + <_RoslynDeveloperSettingsValidationCacheFile>$(MSBuildThisFileDirectory)..\..\artifacts\developer-settings-validated.txt + + + + + <_RoslynBuildRunsOnWindows>$([MSBuild]::IsOSPlatform('Windows')) + <_RoslynSourceHasCarriageReturns>$([System.IO.File]::ReadAllText('$(MSBuildThisFileFullPath)').Contains($([System.Char]::ConvertFromUtf32(13)))) + <_RoslynLongPathsEnabled Condition="'$(_RoslynBuildRunsOnWindows)' == 'True'">$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\FileSystem', 'LongPathsEnabled', null, RegistryView.Registry64, RegistryView.Registry32)) + + + + + + + + + + diff --git a/eng/targets/Imports.targets b/eng/targets/Imports.targets index f3dd6a232b286..a2e902ef623c5 100644 --- a/eng/targets/Imports.targets +++ b/eng/targets/Imports.targets @@ -3,6 +3,7 @@ + + + true + false + + + + + $(WarningsNotAsErrors);IDE0055 + + 512 @@ -45,23 +57,11 @@ impact the correctness of the output, so we avoid the performance overhead where it's easy to do so. --> false - true + true true - - - false - true - - $(NoWarn);Nullable @@ -183,15 +183,7 @@ - - - - - <_RoslynLongPathsEnabled>$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\FileSystem', 'LongPathsEnabled', null, RegistryView.Registry64, RegistryView.Registry32)) - - - - + - $(WarningsNotAsErrors);IDE0055 - - diff --git a/eng/test-build-correctness.ps1 b/eng/test-build-correctness.ps1 index 22d5a79164be3..fe5eb070e9325 100644 --- a/eng/test-build-correctness.ps1 +++ b/eng/test-build-correctness.ps1 @@ -58,7 +58,7 @@ try { } Write-Host "Building Roslyn" - & eng/build.ps1 -restore -build -bootstrapDir:$bootstrapDir -ci:$ci -prepareMachine:$prepareMachine -runAnalyzers:$true -configuration:$configuration -pack -binaryLog -useGlobalNuGetCache:$false -warnAsError:$true -properties:"/p:RoslynEnforceCodeStyle=true" + & eng/build.ps1 -restore -build -bootstrapDir:$bootstrapDir -ci:$ci -prepareMachine:$prepareMachine -runAnalyzers:$true -configuration:$configuration -pack -binaryLog -useGlobalNuGetCache:$false -warnAsError:$true Test-LastExitCode Subst-TempDir diff --git a/eng/test-determinism.ps1 b/eng/test-determinism.ps1 index 5e890f395b32f..08a9f2afa1933 100644 --- a/eng/test-determinism.ps1 +++ b/eng/test-determinism.ps1 @@ -28,9 +28,6 @@ $script:skipList = @( # Added to work around https://github.com/dotnet/roslyn/issues/48417 "Microsoft.CodeAnalysis.EditorFeatures2.UnitTests.dll", - # Work around XLF issues https://github.com/dotnet/roslyn/issues/58840 - "Roslyn.VisualStudio.DiagnosticsWindow.dll.key", - # Work around the same XLF ResXFileRef determinism issue in Razor's VSIX package resources. "Microsoft.VisualStudio.RazorExtension.dll.key", @@ -319,4 +316,3 @@ catch { finally { Pop-Location } - diff --git a/eng/test-rebuild.ps1 b/eng/test-rebuild.ps1 index 459be1ffeb953..5cc95502e21ca 100644 --- a/eng/test-rebuild.ps1 +++ b/eng/test-rebuild.ps1 @@ -58,7 +58,6 @@ try { " --exclude net472\Microsoft.VisualStudio.LanguageServices.Implementation.dll" + " --exclude net472\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll" + " --exclude net472\Roslyn.Hosting.Diagnostics.dll" + - " --exclude net472\Roslyn.VisualStudio.DiagnosticsWindow.dll" + # Match Roslyn's existing WindowsDesktop VS package exclusions, which still rebuild with output differences. " --exclude net472\Microsoft.VisualStudio.RazorExtension.dll" + # BuildValidator cannot reliably replay Roslyn SDK WindowsDesktop VS extension builds because diff --git a/eng/validate-roslyn-sdk-samples.ps1 b/eng/validate-roslyn-sdk-samples.ps1 index 0f6cfbc3e27e7..f5fb2456a580d 100644 --- a/eng/validate-roslyn-sdk-samples.ps1 +++ b/eng/validate-roslyn-sdk-samples.ps1 @@ -62,7 +62,7 @@ $buildArgs = @( "-c", $configuration "--no-incremental" "--warnaserror" - "/p:RoslynEnforceCodeStyle=true" + "/p:RunAnalyzersDuringBuild=true" ) if ($ci) { diff --git a/eng/validate-rules-missing-documentation.ps1 b/eng/validate-rules-missing-documentation.ps1 index b38dfb2cedeee..4452b625e6504 100644 --- a/eng/validate-rules-missing-documentation.ps1 +++ b/eng/validate-rules-missing-documentation.ps1 @@ -13,7 +13,7 @@ try { $prepareMachine = $ci $projectFilePath = Join-Path $RepoRoot "src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj" - Exec-DotNet "build $projectFilePath -t:GenerateRulesMissingDocumentation -p:RoslynEnforceCodeStyle=false -p:RunAnalyzersDuringBuild=false -p:ContinuousIntegrationBuild=$ci -c Release" + Exec-DotNet "build $projectFilePath -t:GenerateRulesMissingDocumentation -p:RunAnalyzersDuringBuild=false -p:ContinuousIntegrationBuild=$ci -c Release" } catch { Write-Host $_ diff --git a/src/Analyzers/CSharp/Tests/RemoveUnnecessaryCast/RemoveUnnecessaryCastTests.cs b/src/Analyzers/CSharp/Tests/RemoveUnnecessaryCast/RemoveUnnecessaryCastTests.cs index c1a03e26f0954..b76a4effb16e8 100644 --- a/src/Analyzers/CSharp/Tests/RemoveUnnecessaryCast/RemoveUnnecessaryCastTests.cs +++ b/src/Analyzers/CSharp/Tests/RemoveUnnecessaryCast/RemoveUnnecessaryCastTests.cs @@ -13668,4 +13668,34 @@ static void Main(string[] args) LanguageVersion = LanguageVersion.CSharp14, ReferenceAssemblies = ReferenceAssemblies.Net.Net90, }.RunAsync(); + +#if NET + [Fact] + public Task RemoveNativeIntegerCastsWithNet100References() + => new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net100, + TestCode = """ + using System; + + public class C + { + public nuint FromIntPtr(IntPtr x) => (nuint)[|(nint)|]x; + public int ToInt(IntPtr x) => (int)[|(nint)|]x; + public nint FromUIntPtr(UIntPtr x) => (nint)[|(nuint)|]x; + } + """, + FixedCode = """ + using System; + + public class C + { + public nuint FromIntPtr(IntPtr x) => (nuint)x; + public int ToInt(IntPtr x) => (int)x; + public nint FromUIntPtr(UIntPtr x) => (nint)x; + } + """, + LanguageVersion = LanguageVersion.CSharp9, + }.RunAsync(); +#endif } diff --git a/src/CodeStyle/CSharp/Tests/Microsoft.CodeAnalysis.CSharp.CodeStyle.UnitTests.csproj b/src/CodeStyle/CSharp/Tests/Microsoft.CodeAnalysis.CSharp.CodeStyle.UnitTests.csproj index 8ba99a0fbfa50..6f87d4e66a2fe 100644 --- a/src/CodeStyle/CSharp/Tests/Microsoft.CodeAnalysis.CSharp.CodeStyle.UnitTests.csproj +++ b/src/CodeStyle/CSharp/Tests/Microsoft.CodeAnalysis.CSharp.CodeStyle.UnitTests.csproj @@ -14,7 +14,6 @@ - @@ -24,6 +23,7 @@ + diff --git a/src/CodeStyle/Core/Tests/UnitTestUtilities/Microsoft.CodeAnalysis.CodeStyle.UnitTestUtilities.csproj b/src/CodeStyle/Core/Tests/UnitTestUtilities/Microsoft.CodeAnalysis.CodeStyle.UnitTestUtilities.csproj index 45847a5466ee8..2407f394aad88 100644 --- a/src/CodeStyle/Core/Tests/UnitTestUtilities/Microsoft.CodeAnalysis.CodeStyle.UnitTestUtilities.csproj +++ b/src/CodeStyle/Core/Tests/UnitTestUtilities/Microsoft.CodeAnalysis.CodeStyle.UnitTestUtilities.csproj @@ -37,11 +37,11 @@ - - - - - + + + + + diff --git a/src/CodeStyle/VisualBasic/Tests/Microsoft.CodeAnalysis.VisualBasic.CodeStyle.UnitTests.vbproj b/src/CodeStyle/VisualBasic/Tests/Microsoft.CodeAnalysis.VisualBasic.CodeStyle.UnitTests.vbproj index ccbbabaa7aa1c..1530dcc0a6c9b 100644 --- a/src/CodeStyle/VisualBasic/Tests/Microsoft.CodeAnalysis.VisualBasic.CodeStyle.UnitTests.vbproj +++ b/src/CodeStyle/VisualBasic/Tests/Microsoft.CodeAnalysis.VisualBasic.CodeStyle.UnitTests.vbproj @@ -14,14 +14,12 @@ - - - + diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/LocalDataFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/LocalDataFlowPass.cs index fec058592d072..fafb90711f92c 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/LocalDataFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/LocalDataFlowPass.cs @@ -61,7 +61,8 @@ protected LocalDataFlowPass( protected abstract int AddVariable(VariableIdentifier identifier); - /// + /// Get the slot for a variable, if the slot already exists. + /// /// Locals are given slots when their declarations are encountered. We only need give slots /// to local variables, out parameters, and the "this" variable of a struct constructs. /// Other variables are not given slots, and are therefore not tracked by the analysis. This @@ -71,7 +72,7 @@ protected LocalDataFlowPass( /// variables that occur before the variable is declared, as those are reported in an /// earlier phase as "use before declaration". That allows us to avoid giving slots to local /// variables before processing their declarations. - /// + /// protected int VariableSlot(Symbol symbol, int containingSlot = 0) { // Skip LocalStoreTracker from data flow analysis. diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 8eeb55385009e..bd3db42417777 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -568,6 +568,8 @@ protected override bool ConvertInsufficientExecutionStackExceptionToCancelledByS protected override bool TryGetVariable(VariableIdentifier identifier, out int slot) { + // Only the owning symbol should be used to look up the slot. + Debug.Assert(GetOwningSymbolAndContainingSlot(identifier.Symbol, identifier.ContainingSlot) == ((object)identifier.Symbol, identifier.ContainingSlot)); return _variables.TryGetValue(identifier, out slot); } @@ -757,6 +759,10 @@ void enforceMemberNotNull(SyntaxNode? syntaxOpt, LocalState state) void checkMemberStateOnConstructorExit(MethodSymbol constructor, Symbol member, LocalState state, int thisSlot, Location? exitLocation, ImmutableArray membersWithStateEnforcedByRequiredMembers, bool forcePropertyAnalysis) { +#if DEBUG + var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; + Debug.Assert(AccessCheck.IsSymbolAccessible(member, within: constructor.ContainingType, ref discardedUseSiteInfo)); +#endif var isStatic = !constructor.RequiresInstanceReceiver(); if (member.IsStatic != isStatic) { @@ -855,6 +861,10 @@ void makeNotNullMembersMaybeNull() { foreach (var member in getMembersNeedingDefaultInitialState()) { +#if DEBUG + var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; + Debug.Assert(AccessCheck.IsSymbolAccessible(member, within: method.ContainingType, ref discardedUseSiteInfo)); +#endif if (member.IsStatic != method.IsStatic) { continue; @@ -863,7 +873,9 @@ void makeNotNullMembersMaybeNull() var memberToInitialize = member; switch (member) { - case PropertySymbol { IsRequired: true }: + case PropertySymbol { IsRequired: true } requiredProperty when GetAccessibleBackingField(requiredProperty, method.ContainingType) is null: + // Visit a required property, unless it has a backing field which is accessible in the current context, + // in which case we expect the field to be visited in another iteration of this loop. break; case PropertySymbol: // skip any manually implemented non-required properties. @@ -967,19 +979,20 @@ static ImmutableArray membersToBeInitialized(NamedTypeSymbol containingT (includeAllMembers: false, includeCurrentTypeRequiredMembers: true, includeBaseRequiredMembers: false) => containingType.GetMembersUnordered().SelectManyAsArray( - predicate: SymbolExtensions.IsRequired, - selector: symbol => getAllMembersToBeDefaulted(symbol, filterOverridingProperties: true)), + predicate: static (symbol, _) => symbol.IsRequired(), + selector: static (symbol, containingType) => getAllMembersToBeDefaulted(containingType, symbol, filterOverridingProperties: true), + containingType), (includeAllMembers: false, includeCurrentTypeRequiredMembers: true, includeBaseRequiredMembers: true) - => containingType.AllRequiredMembers.SelectManyAsArray(static kvp => getAllMembersToBeDefaulted(kvp.Value, filterOverridingProperties: true)), + => containingType.AllRequiredMembers.SelectManyAsArray(static (kvp, containingType) => getAllMembersToBeDefaulted(containingType, kvp.Value, filterOverridingProperties: true), arg: containingType), (includeAllMembers: true, includeCurrentTypeRequiredMembers: _, includeBaseRequiredMembers: false) => containingType.GetMembersUnordered().SelectManyAsArray( - selector: symbol => + selector: static (symbol, containingType) => { - var symbolToInitialize = getFieldSymbolToBeInitialized(symbol); + var symbolToInitialize = getFieldSymbolToBeInitialized(containingType, symbol); var prop = symbolToInitialize as PropertySymbol ?? (symbolToInitialize as FieldSymbol)?.AssociatedSymbol as PropertySymbol; - if (prop is not null && isFilterableOverrideOfAbstractProperty(prop)) + if (prop is not null && isFilterableOverrideOfAbstractProperty(containingType, prop)) { return OneOrMany.Empty; } @@ -987,7 +1000,8 @@ static ImmutableArray membersToBeInitialized(NamedTypeSymbol containingT { return OneOrMany.Create(symbolToInitialize); } - }), + }, + arg: containingType), (includeAllMembers: true, includeCurrentTypeRequiredMembers: true, includeBaseRequiredMembers: true) => getAllTypeAndRequiredMembers(containingType), @@ -996,7 +1010,7 @@ static ImmutableArray membersToBeInitialized(NamedTypeSymbol containingT => throw ExceptionUtilities.Unreachable(), }; - static ImmutableArray getAllTypeAndRequiredMembers(TypeSymbol containingType) + static ImmutableArray getAllTypeAndRequiredMembers(NamedTypeSymbol containingType) { var members = containingType.GetMembersUnordered(); var baseRequiredMembers = containingType.BaseTypeNoUseSiteDiagnostics?.AllRequiredMembers ?? ImmutableSegmentedDictionary.Empty; @@ -1018,19 +1032,19 @@ static ImmutableArray getAllTypeAndRequiredMembers(TypeSymbol containing if (requiredMember is PropertySymbol { IsAbstract: true } abstractProperty) { if (members.FirstOrDefault(static (thisMember, baseMember) => thisMember.IsOverride && (object)thisMember.GetOverriddenMember() == baseMember, requiredMember) is { } overridingMember - && isFilterableOverrideOfAbstractProperty((PropertySymbol)overridingMember)) + && isFilterableOverrideOfAbstractProperty(containingType, (PropertySymbol)overridingMember)) { continue; } } - builder.AddRange(getAllMembersToBeDefaulted(requiredMember, filterOverridingProperties: false)); + builder.AddRange(getAllMembersToBeDefaulted(containingType, requiredMember, filterOverridingProperties: false)); } return builder.ToImmutableAndFree(); } - static OneOrMany getAllMembersToBeDefaulted(Symbol requiredMember, bool filterOverridingProperties) + static OneOrMany getAllMembersToBeDefaulted(NamedTypeSymbol containingType, Symbol requiredMember, bool filterOverridingProperties) { Debug.Assert(requiredMember.IsRequired()); @@ -1042,19 +1056,23 @@ static OneOrMany getAllMembersToBeDefaulted(Symbol requiredMember, bool { var property = (PropertySymbol)requiredMember; - if (filterOverridingProperties && isFilterableOverrideOfAbstractProperty(property)) + if (filterOverridingProperties && isFilterableOverrideOfAbstractProperty(containingType, property)) { return OneOrMany.Empty; } - var @return = OneOrMany.Create(getFieldSymbolToBeInitialized(property)); + var @return = OneOrMany.Create(getFieldSymbolToBeInitialized(containingType, property)); // If the set method is null (ie missing), that's an error, but we'll recover as best we can + var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; foreach (var notNullMemberName in (property.SetMethod?.NotNullMembers ?? property.NotNullMembers)) { foreach (var member in property.ContainingType.GetMembers(notNullMemberName)) { - @return = @return.Add(getFieldSymbolToBeInitialized(member)); + if (AccessCheck.IsSymbolAccessible(member, containingType, ref discardedUseSiteInfo)) + { + @return = @return.Add(getFieldSymbolToBeInitialized(containingType, member)); + } } } @@ -1062,10 +1080,10 @@ static OneOrMany getAllMembersToBeDefaulted(Symbol requiredMember, bool } } - static Symbol getFieldSymbolToBeInitialized(Symbol requiredMember) - => requiredMember is SourcePropertySymbolBase { BackingField: { } backingField } ? backingField : requiredMember; + static Symbol getFieldSymbolToBeInitialized(NamedTypeSymbol containingType, Symbol requiredMember) + => requiredMember is PropertySymbol property && GetAccessibleBackingField(property, containingType) is { } backingField ? backingField : requiredMember; - static bool isFilterableOverrideOfAbstractProperty(PropertySymbol property) + static bool isFilterableOverrideOfAbstractProperty(NamedTypeSymbol containingType, PropertySymbol property) { // If this is an override of an abstract property, and the overridden property has the same nullable // annotation as us, we can skip default-initializing the property because the chained constructor @@ -1075,8 +1093,8 @@ static bool isFilterableOverrideOfAbstractProperty(PropertySymbol property) return false; } - var symbolAnnotations = property is SourcePropertySymbolBase { UsesFieldKeyword: true, BackingField: { } field } - ? field!.FlowAnalysisAnnotations + var symbolAnnotations = GetAccessibleBackingField(property, containingType) is { } field + ? field.FlowAnalysisAnnotations : property.GetFlowAnalysisAnnotations(); var symbolType = ApplyUnconditionalAnnotations(property.TypeWithAnnotations, symbolAnnotations); if (!symbolType.NullableAnnotation.IsNotAnnotated()) @@ -1093,6 +1111,25 @@ static bool isFilterableOverrideOfAbstractProperty(PropertySymbol property) } } + /// + /// A property's backing field should only influence behavior of nullable analysis when the field is accessible. + /// For example, when the member being analyzed has the same containing type as the property. + /// + /// Note: this refers to accessibility only in the "accessibility modifier" sense. + /// This method may return a non-null symbol even if the field cannot be referenced by name in the current context. + /// + /// Containing type of the member currently being analyzed. + private static SynthesizedBackingFieldSymbol? GetAccessibleBackingField(PropertySymbol property, NamedTypeSymbol containingType) + { + if (property is not SourcePropertySymbolBase { BackingField: { } backingField }) + return null; + + var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; + return AccessCheck.IsSymbolAccessible(backingField, within: containingType, ref discardedUseSiteInfo) + ? backingField + : null; + } + private void EnforceMemberNotNullOnMember(SyntaxNode? syntaxOpt, LocalState state, MethodSymbol method, string memberName) { foreach (var member in method.ContainingType.GetMembers(memberName)) @@ -2267,6 +2304,16 @@ protected override int GetOrCreateSlot(Symbol symbol, int containingSlot = 0, bo if (containingSlot > 0 && !IsSlotMember(containingSlot, symbol)) return -1; + (symbol, containingSlot) = GetOwningSymbolAndContainingSlot(symbol, containingSlot); + return base.GetOrCreateSlot(symbol, containingSlot, forceSlotEvenIfEmpty, createIfMissing); + } + + /// + /// If the incoming symbol+containingSlot pair shares a slot, + /// returns the owning pair. Otherwise returns the arguments. + /// + private (Symbol symbol, int containingSlot) GetOwningSymbolAndContainingSlot(Symbol symbol, int containingSlot) + { // Primary constructor parameter and its backing field share the slot when // we are dealing with 'this' instance. if (symbol is ParameterSymbol { ContainingSymbol: SynthesizedPrimaryConstructor primaryConstructor } parameter && @@ -2291,6 +2338,7 @@ protected override int GetOrCreateSlot(Symbol symbol, int containingSlot = 0, bo } // Share a slot between backing field and associated property/event in the context of a constructor which owns initialization of that backing field. + // https://github.com/dotnet/roslyn/issues/84987: extend this sharing to more methods where the backing field is accessible if (this._symbol is MethodSymbol constructor && constructor.IsConstructor() && constructor.IsStatic == symbol.IsStatic) @@ -2304,17 +2352,25 @@ protected override int GetOrCreateSlot(Symbol symbol, int containingSlot = 0, bo // - property initializer on normal auto-property // - property assignment on getter-only auto-property. // Example test: NullableReferenceTypesTests.ConstructorUsesStateFromInitializers will fail without this. - if (symbol is SynthesizedBackingFieldSymbol { AssociatedSymbol: SourcePropertySymbolBase { UsesFieldKeyword: false } property }) - symbol = property; - // If symbol is a property that uses field keyword, then use field to determine initial state and to own the slot. - else if (symbol is SourcePropertySymbolBase { UsesFieldKeyword: true, BackingField: { } backingField }) - symbol = backingField; + if (symbol is SynthesizedBackingFieldSymbol { AssociatedProperty.UsesFieldKeyword: false } backingField) + { + symbol = backingField.AssociatedProperty; + } + // If symbol is a property that uses field keyword, and property is not null-resilient, then use field to determine initial state and to own the slot. + else if (symbol is SourcePropertySymbolBase { UsesFieldKeyword: true } property + && GetAccessibleBackingField(property, constructor.ContainingType) is { } accessibleBackingField + && !IsAssociatedPropertyNullResilient(accessibleBackingField)) + { + symbol = accessibleBackingField; + } else if (symbol is SourceEventFieldSymbol eventField) + { symbol = eventField.AssociatedSymbol; + } } } - return base.GetOrCreateSlot(symbol, containingSlot, forceSlotEvenIfEmpty, createIfMissing); + return (symbol, containingSlot); } private void VisitAndUnsplitAll(ImmutableArray nodes) where T : BoundNode @@ -11307,6 +11363,13 @@ private void VisitThisOrBaseReference(BoundExpression node) return null; } + private bool IsAssociatedPropertyNullResilient(SynthesizedBackingFieldSymbol backingField) + { + // Only possible to answer this question if a null resilience inference is not currently in progress. + Debug.Assert(_getterNullResilienceData is null); + return backingField.InfersNullableAnnotation && backingField.GetInferredNullableAnnotation() == NullableAnnotation.Annotated; + } + private bool IsPropertyOutputMoreStrictThanInput(PropertySymbol property) { var type = property.TypeWithAnnotations; diff --git a/src/Compilers/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.csproj b/src/Compilers/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.csproj index 9dccec2deeff3..29a1ac30d47ca 100644 --- a/src/Compilers/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.csproj +++ b/src/Compilers/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.csproj @@ -84,6 +84,7 @@ + diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index ec03351c26000..3b941779cfaf1 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -875,8 +875,8 @@ private bool IsPossibleNamespaceMemberDeclaration() case SyntaxKind.NamespaceKeyword: return true; case SyntaxKind.IdentifierToken: - // `allowMembers: false`: A type member such as 'partial int M()' cannot start a namespace body. - return this.IsPartialModifierInDeclarationHead(allowMembers: false); + // `onlyForTypeDeclarations: true`: A type member such as 'partial int M()' cannot start a namespace body. + return this.IsCurrentTokenDefinitelyPartialModifier(onlyForTypeDeclarations: true); default: return IsPossibleStartOfTypeDeclaration(this.CurrentToken.Kind); } @@ -1369,9 +1369,9 @@ private void ParseModifiers(SyntaxListBuilder tokens, bool forTopLevelStatements switch (newMod) { case DeclarationModifiers.Partial: - // `allowMembers: true`: ParseModifiers is shared by types and members, such as + // `onlyForTypeDeclarations: false`: ParseModifiers is shared by types and members, such as // 'partial class C' and 'partial void M()'. - if (!this.IsPartialModifierInDeclarationHead(allowMembers: true)) + if (!this.IsCurrentTokenDefinitelyPartialModifier(onlyForTypeDeclarations: false)) return; modTok = ConvertToKeyword(this.EatToken()); @@ -1399,18 +1399,6 @@ private void ParseModifiers(SyntaxListBuilder tokens, bool forTopLevelStatements break; } - case DeclarationModifiers.File: - if (!parseAsModifier(MessageID.IDS_FeatureFileTypes, out modTok)) - return; - - break; - - case DeclarationModifiers.Closed: - if (!parseAsModifier(MessageID.IDS_FeatureClosedClasses, out modTok)) - return; - - break; - case DeclarationModifiers.Async: if (!ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: false)) { @@ -1420,17 +1408,33 @@ private void ParseModifiers(SyntaxListBuilder tokens, bool forTopLevelStatements modTok = ConvertToKeyword(this.EatToken()); break; + case DeclarationModifiers.File: + case DeclarationModifiers.Closed: case DeclarationModifiers.Required: - if (!parseAsModifier(MessageID.IDS_FeatureRequiredMembers, out modTok)) - return; - - break; - case DeclarationModifiers.Safe: - if (!parseAsModifier(MessageID.IDS_FeatureUnsafeEvolution, out modTok)) - return; + { + var requiredFeature = newMod switch + { + DeclarationModifiers.File => MessageID.IDS_FeatureFileTypes, + DeclarationModifiers.Closed => MessageID.IDS_FeatureClosedClasses, + DeclarationModifiers.Required => MessageID.IDS_FeatureRequiredMembers, + DeclarationModifiers.Safe => MessageID.IDS_FeatureUnsafeEvolution, + _ => throw ExceptionUtilities.UnexpectedValue(newMod), + }; + + // Outside top-level statements, an enabled contextual modifier is unambiguous. + // Otherwise, it may be an identifier, so use the usual contextual-keyword heuristic. + var needsDisambiguation = !IsFeatureEnabled(requiredFeature) || forTopLevelStatements; + if (needsDisambiguation && + !ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: false)) + { + return; + } - break; + // LangVersion errors for contextual modifiers are given during binding. + modTok = ConvertToKeyword(EatToken()); + break; + } default: modTok = this.EatToken(); @@ -1477,23 +1481,6 @@ bool shouldConsumeRefAtTopLevel() SyntaxKind.RecordKeyword or SyntaxKind.UnionKeyword or SyntaxKind.ExtensionKeyword || this.IsTypeDeclarationStart(); } - - bool parseAsModifier(MessageID requiredFeature, [NotNullWhen(true)] out SyntaxToken? modTok) - { - // When 'requiredFeature' is enabled, the associated contextual keyword is always a keyword if not escaped. Otherwise, we reuse the async detection - // machinery to make a conservative guess as to whether the user meant it to be a keyword, so that they get a good langver - // diagnostic and all the machinery to upgrade their project kicks in. The only exception to this rule is top level statements, - // where the user could conceivably have a local with the same name as the modifier. For these locations, we need to disambiguate as well. - if ((!IsFeatureEnabled(requiredFeature) || forTopLevelStatements) && !ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: false)) - { - modTok = null; - return false; - } - - // LangVersion errors for contextual modifiers are given during binding. - modTok = ConvertToKeyword(EatToken()); - return true; - } } private void ParseAccessorModifiers(SyntaxListBuilder tokens) @@ -1533,10 +1520,10 @@ private bool ShouldContextualKeywordBeTreatedAsModifier(bool parsingStatementNot // If 'partial' starts a declaration, the preceding token is also a modifier, // as in 'closed partial ref struct'. - // `allowMembers: true`: The preceding modifier may belong to either a type or a member, such as + // `onlyForTypeDeclarations: false`: The preceding modifier may belong to either a type or a member, such as // 'public partial class C' or 'public partial void M()'. if (!parsingStatementNotDeclaration && - this.IsPartialModifierInDeclarationHead(allowMembers: true)) + this.IsCurrentTokenDefinitelyPartialModifier(onlyForTypeDeclarations: false)) { return true; } @@ -1605,16 +1592,9 @@ private bool ShouldContextualKeywordBeTreatedAsModifier(bool parsingStatementNot return true; } - // "TOKEN TypeName class". In this case, we just have an incomplete member before - // an existing type declaration. Treat this 'TOKEN' as a keyword. - if (IsTypeDeclarationStart()) - { - return true; - } - - // "TOKEN TypeName namespace". In this case, we just have an incomplete member before - // an existing namespace declaration. Treat this 'TOKEN' as a keyword. - if (currentTokenKind == SyntaxKind.NamespaceKeyword) + // "TOKEN TypeName class" or "TOKEN TypeName namespace". In this case, we just have + // an incomplete member before an existing declaration. Treat this 'TOKEN' as a keyword. + if (this.IsTypeOrNamespaceDeclarationStart()) { return true; } @@ -1628,6 +1608,9 @@ private bool ShouldContextualKeywordBeTreatedAsModifier(bool parsingStatementNot return false; } + private bool IsTypeOrNamespaceDeclarationStart() + => this.IsTypeDeclarationStart() || this.CurrentToken.Kind == SyntaxKind.NamespaceKeyword; + private static bool IsNonContextualModifier(SyntaxToken nextToken) { return !SyntaxFacts.IsContextualKeyword(nextToken.ContextualKind) && GetModifierExcludingScoped(nextToken) != DeclarationModifiers.None; @@ -1648,9 +1631,10 @@ private bool IsEnabledRecordOrUnionKeyword(SyntaxToken token) } /// - /// Classifies partial in a declaration head, including misplaced forms for binding to diagnose. + /// Determines whether the current token is definitely a partial modifier, + /// including misplaced forms for binding to diagnose. /// - private bool IsPartialModifierInDeclarationHead(bool allowMembers) + private bool IsCurrentTokenDefinitelyPartialModifier(bool onlyForTypeDeclarations) { if (this.CurrentToken.ContextualKind != SyntaxKind.PartialKeyword) return false; @@ -1660,92 +1644,83 @@ private bool IsPartialModifierInDeclarationHead(bool allowMembers) if (this.IsUnambiguousAnonymousFunctionModifierListFollowedByOpenParen()) return false; - using var _ = this.GetDisposableResetPoint(resetOnDispose: true); + return isPartialModifierInTypeOrNamespaceDeclaration() || + isPartialModifierInMemberDeclaration(); - // Consume the 'partial' being classified before scanning the declaration head. - // Otherwise, scanning that same token as a possible type would recurse through IsTrueIdentifier. - this.EatToken(); // partial + bool isPartialModifierInTypeOrNamespaceDeclaration() + { + using var _ = this.GetDisposableResetPoint(resetOnDispose: true); - var partialConstructorsEnabled = IsFeatureEnabled(MessageID.IDS_FeaturePartialEventsAndConstructors); + Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword); - // Look through following modifiers for the declaration head. A contextual modifier may - // instead begin the member itself, such as 'ref' in 'partial ref int M()'. - while (GetModifierExcludingScoped(this.CurrentToken) != DeclarationModifiers.None) - { - // For example, 'async' starts the return type in 'partial async M()'. - if (shouldStopSkippingModifiers()) - return true; + // Type and namespace declarations are straightforward: skip the modifier list and + // require a well-known declaration keyword such as 'class', 'struct', or 'namespace'. + while (GetModifierExcludingScoped(this.CurrentToken) != DeclarationModifiers.None) + this.EatToken(); - this.EatToken(); + return this.IsTypeOrNamespaceDeclarationStart(); } - // 'partial public class C' reaches 'class' here. - if (this.IsTypeDeclarationStart()) - return true; + bool isPartialModifierInMemberDeclaration() + { + if (onlyForTypeDeclarations) + return false; - // Parse 'partial public namespace N' as a namespace so binding can report the misplaced - // modifier instead of producing cascading parser errors. - if (this.CurrentToken.Kind == SyntaxKind.NamespaceKeyword) - return true; + using var _ = this.GetDisposableResetPoint(resetOnDispose: true); - return isMemberDeclarationStart(); + Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword); - bool shouldStopSkippingModifiers() - { - if (!allowMembers) - return false; + // Consume the 'partial' and determine if what follows is definitively a member. + this.EatToken(); - // A non-contextual modifier proves that the initial 'partial' belongs to a member. - if (this.CurrentToken.Kind != SyntaxKind.IdentifierToken) - return true; + // With partial constructors enabled, 'partial Identifier(' starts a constructor. + // In earlier versions, 'partial' is the return type and the identifier is the member name. + if (isIdentifierFollowedByOpenParen(peekIndex: 0)) + return IsFeatureEnabled(MessageID.IDS_FeaturePartialEventsAndConstructors); - // A different contextual modifier may instead start the member's return type, - // such as 'async' in 'partial async M()'. - if (this.CurrentToken.ContextualKind != SyntaxKind.PartialKeyword) - return isMemberDeclarationStart(); + while (GetModifierExcludingScoped(this.CurrentToken) != DeclarationModifiers.None) + { + // Before a non-contextual modifier, as in 'partial static', the initial + // 'partial' is unambiguously a modifier. + if (this.CurrentToken.Kind != SyntaxKind.IdentifierToken) + return true; - // In C# 14, prefer the duplicate-modifier constructor interpretation of - // 'partial partial C()' over treating the second 'partial' as a return type. - if (canStartPartialConstructor(peekIndex: 1)) - return true; + // A contextual modifier followed by 'Identifier(' either starts a method + // return type, as in 'partial async C()', or is another modifier on a partial + // constructor, as in 'partial partial C()'. Either way, the initial 'partial' is + // a modifier. For the latter form in C# 14, scanning the second 'partial' as a type + // reenters this helper and classifies it as a modifier, so IsTypeFollowedByMemberName() + // returns false. + if (isIdentifierFollowedByOpenParen(peekIndex: 1)) + return true; - // Process longer modifier chains one token at a time rather than recursively - // scanning each 'partial' as a possible type. - return this.PeekToken(1).ContextualKind != SyntaxKind.PartialKeyword && - isMemberDeclarationStart(); - } + // A contextual modifier may otherwise be the member's return type, such as + // the second 'partial' in 'partial partial P { get; }'. + if (this.IsTypeFollowedByMemberName()) + return true; - bool isMemberDeclarationStart() - { - // Namespace lookahead must not accept member-only forms such as 'partial int M()'. - if (!allowMembers) - return false; + this.EatToken(); + } - // 'event' cannot begin another member form, so parse 'partial event' as an event in every - // language version. Binding reports the feature diagnostic when necessary. + // No modifier-like token remains, so the current token must start the member itself. + + // 'event' cannot begin another member form, so parse 'partial event' as an event in + // every language version. Binding reports the feature diagnostic when necessary. if (this.CurrentToken.Kind == SyntaxKind.EventKeyword) return true; - // 'implicit' and 'explicit' can only start conversion operators, so 'partial' is a modifier - // even when the operator declaration is incomplete. + // 'implicit' and 'explicit' can only start conversion operators, so 'partial' is a + // modifier even when the operator declaration is incomplete. if (this.CurrentToken.Kind is SyntaxKind.ImplicitKeyword or SyntaxKind.ExplicitKeyword) return true; - // Before partial constructors, 'partial C()' is a method returning 'partial'. Only prefer - // the constructor interpretation when the feature is enabled. - if (canStartPartialConstructor(peekIndex: 0)) - return true; - // Otherwise, require a return type followed by a member name, as in 'partial int M()'. return this.IsTypeFollowedByMemberName(); } - // The surrounding checks establish the preceding 'partial' modifier. This returns true only - // when partial constructors are enabled and the following tokens can start the constructor. - bool canStartPartialConstructor(int peekIndex) + bool isIdentifierFollowedByOpenParen(int peekIndex) { - return partialConstructorsEnabled && - this.PeekToken(peekIndex).Kind == SyntaxKind.IdentifierToken && + return this.PeekToken(peekIndex).Kind == SyntaxKind.IdentifierToken && this.PeekToken(peekIndex + 1).Kind == SyntaxKind.OpenParenToken; } } @@ -6045,7 +6020,7 @@ private bool IsTrueIdentifier() { if (this.CurrentToken.Kind == SyntaxKind.IdentifierToken) { - if (!IsCurrentTokenPartialKeywordOfPartialMemberOrType() && + if (!IsCurrentTokenDefinitelyPartialModifier(onlyForTypeDeclarations: false) && !IsCurrentTokenQueryKeywordInQuery() && !IsCurrentTokenWhereOfConstraintClause()) { @@ -6092,7 +6067,7 @@ private SyntaxToken ParseIdentifierToken(ErrorCode code = ErrorCode.ERR_Identifi // show the correct parameter help in this case. So, when we see "partial" we check if it's being used // as an identifier or as a contextual keyword. If it's the latter then we bail out. See // Bug: vswhidbey/542125 - if (IsCurrentTokenPartialKeywordOfPartialMemberOrType() || IsCurrentTokenQueryKeywordInQuery()) + if (this.IsCurrentTokenDefinitelyPartialModifier(onlyForTypeDeclarations: false) || IsCurrentTokenQueryKeywordInQuery()) { var result = CreateMissingIdentifierToken(); result = this.AddError(result, ErrorCode.ERR_InvalidExprTerm, this.CurrentToken.Text); @@ -6119,13 +6094,6 @@ private bool IsCurrentTokenQueryKeywordInQuery() return this.IsInQuery && this.IsCurrentTokenQueryContextualKeyword; } - private bool IsCurrentTokenPartialKeywordOfPartialMemberOrType() - { - // `allowMembers: true`: Identifier parsing must stop before a type or member declaration, such as - // 'partial class C' or 'partial public void M()'. - return this.IsPartialModifierInDeclarationHead(allowMembers: true); - } - private bool IsCurrentTokenFieldInKeywordContext() { return CurrentToken.ContextualKind == SyntaxKind.FieldKeyword && @@ -6196,7 +6164,7 @@ private TypeParameterSyntax ParseTypeParameter() } if (this.IsCurrentTokenWhereOfConstraintClause() || - this.IsCurrentTokenPartialKeywordOfPartialMemberOrType()) + this.IsCurrentTokenDefinitelyPartialModifier(onlyForTypeDeclarations: false)) { return _syntaxFactory.TypeParameter( attrs, diff --git a/src/Compilers/CSharp/Portable/Symbols/AbstractTypeMap.cs b/src/Compilers/CSharp/Portable/Symbols/AbstractTypeMap.cs index ee43c93bf9e79..1614226389f90 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AbstractTypeMap.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AbstractTypeMap.cs @@ -58,29 +58,34 @@ internal NamedTypeSymbol SubstituteNamedType(NamedTypeSymbol previous) NamedTypeSymbol newConstructedFrom = SubstituteTypeDeclaration(oldConstructedFrom); ImmutableArray oldTypeArguments = previous.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics; - bool changed = !ReferenceEquals(oldConstructedFrom, newConstructedFrom); - var newTypeArguments = ArrayBuilder.GetInstance(oldTypeArguments.Length); + ArrayBuilder newTypeArguments = null; for (int i = 0; i < oldTypeArguments.Length; i++) { var oldArgument = oldTypeArguments[i]; var newArgument = oldArgument.SubstituteType(this); - if (!changed && !oldArgument.IsSameAs(newArgument)) + if (newTypeArguments is null) { - changed = true; + if (oldArgument.IsSameAs(newArgument)) + { + continue; + } + + newTypeArguments = ArrayBuilder.GetInstance(oldTypeArguments.Length); + newTypeArguments.AddRange(oldTypeArguments, i); } newTypeArguments.Add(newArgument); } - if (!changed) + if (newTypeArguments is null && ReferenceEquals(oldConstructedFrom, newConstructedFrom)) { - newTypeArguments.Free(); return previous; } - return newConstructedFrom.ConstructIfGeneric(newTypeArguments.ToImmutableAndFree()).WithTupleDataFrom(previous); + var substitutedArguments = newTypeArguments is null ? oldTypeArguments : newTypeArguments.ToImmutableAndFree(); + return newConstructedFrom.ConstructIfGeneric(substitutedArguments).WithTupleDataFrom(previous); } /// diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs index b22ceb3c73313..169c5532b80c6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs @@ -116,6 +116,9 @@ protected override OneOrMany> GetAttributeDeclar public override Symbol AssociatedSymbol => _property; + internal SourcePropertySymbolBase AssociatedProperty + => _property; + public override ImmutableArray Locations => _property.Locations; diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index f5b63a1c997e7..766d509bbbbc4 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -249,7 +249,7 @@ Invalid '{0}' value: '{1}'. Accepted values are: {2} - Invalid '{0}' value: '{1}'. Accepted values are: {2} + Neplatná hodnota {0}: {1}. Přijímané hodnoty jsou: {2} {2} is a comma-separated list. @@ -2979,7 +2979,7 @@ {0} is not a valid C# coalesce operation - {0} is not a valid C# coalesce operation + {0} není platná operace slučování v jazyce C#. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 555a5bd3f1de2..01a8c487db8dc 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -249,7 +249,7 @@ Invalid '{0}' value: '{1}'. Accepted values are: {2} - Invalid '{0}' value: '{1}'. Accepted values are: {2} + Ungültiger '{0}' Wert: '{1}'. Akzeptierte Werte sind: {2} {2} is a comma-separated list. @@ -2979,7 +2979,7 @@ {0} is not a valid C# coalesce operation - {0} is not a valid C# coalesce operation + {0} ist kein gültiger C#-Zusammenfügungsvorgang diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 652257a064c6b..b9db17e29413e 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -249,7 +249,7 @@ Invalid '{0}' value: '{1}'. Accepted values are: {2} - Invalid '{0}' value: '{1}'. Accepted values are: {2} + Valor de '{0}' no válido: '{1}'. Los valores aceptados son: {2} {2} is a comma-separated list. @@ -2979,7 +2979,7 @@ {0} is not a valid C# coalesce operation - {0} is not a valid C# coalesce operation + {0} no es una operación de fusión de C# válida diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index ad1f71d8a3428..5196c638c90d0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -249,7 +249,7 @@ Invalid '{0}' value: '{1}'. Accepted values are: {2} - Invalid '{0}' value: '{1}'. Accepted values are: {2} + Valeur '{0}' non valide : '{1}'. Les valeurs acceptées sont : {2} {2} is a comma-separated list. @@ -2979,7 +2979,7 @@ {0} is not a valid C# coalesce operation - {0} is not a valid C# coalesce operation + {0} n’est pas une opération valide de coalescence C# diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 0dd09b67a4378..e572ab7630c63 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -249,7 +249,7 @@ Invalid '{0}' value: '{1}'. Accepted values are: {2} - Invalid '{0}' value: '{1}'. Accepted values are: {2} + Valore '{0}' non valido: '{1}'. I valori accettati sono: {2} {2} is a comma-separated list. @@ -2979,7 +2979,7 @@ {0} is not a valid C# coalesce operation - {0} is not a valid C# coalesce operation + {0} non è un'operazione di coalescenza C# valida diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 32c65c1b534b0..191bcc82e540f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -249,7 +249,7 @@ Invalid '{0}' value: '{1}'. Accepted values are: {2} - Invalid '{0}' value: '{1}'. Accepted values are: {2} + 無効な '{0}' 値: '{1}'。指定できる値: {2} {2} is a comma-separated list. @@ -2979,7 +2979,7 @@ {0} is not a valid C# coalesce operation - {0} is not a valid C# coalesce operation + {0} は C# の有効な結合操作ではありません diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 8ee0d06437638..d6bd425297be6 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -249,7 +249,7 @@ Invalid '{0}' value: '{1}'. Accepted values are: {2} - Invalid '{0}' value: '{1}'. Accepted values are: {2} + 잘못된 '{0}' 값: '{1}'. 허용되는 값: {2} {2} is a comma-separated list. @@ -2979,7 +2979,7 @@ {0} is not a valid C# coalesce operation - {0} is not a valid C# coalesce operation + {0}은(는) 유효한 C# 병합 연산이 아님 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 1b18cfe72af46..6532ed31b1956 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -249,7 +249,7 @@ Invalid '{0}' value: '{1}'. Accepted values are: {2} - Invalid '{0}' value: '{1}'. Accepted values are: {2} + Nieprawidłowa wartość '{0}': '{1}'. Akceptowane wartości to: {2} {2} is a comma-separated list. @@ -2979,7 +2979,7 @@ {0} is not a valid C# coalesce operation - {0} is not a valid C# coalesce operation + {0} nie jest prawidłową operacją łączenia języka C# diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 4449727b87478..77bf0656e922c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -249,7 +249,7 @@ Invalid '{0}' value: '{1}'. Accepted values are: {2} - Invalid '{0}' value: '{1}'. Accepted values are: {2} + Valor de '{0}' inválido: '{1}'. Os valores aceitos são: {2} {2} is a comma-separated list. @@ -2979,7 +2979,7 @@ {0} is not a valid C# coalesce operation - {0} is not a valid C# coalesce operation + {0} não é uma operação de união C# válida diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 86f7452dea76a..2d037fefd4de6 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -249,7 +249,7 @@ Invalid '{0}' value: '{1}'. Accepted values are: {2} - Invalid '{0}' value: '{1}'. Accepted values are: {2} + Недопустимое значение '{0}': '{1}'. Допустимые значения: {2} {2} is a comma-separated list. @@ -2979,7 +2979,7 @@ {0} is not a valid C# coalesce operation - {0} is not a valid C# coalesce operation + {0} не является допустимой операцией объединения в C# diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 7b759f9bc123e..b84269e9988df 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -249,7 +249,7 @@ Invalid '{0}' value: '{1}'. Accepted values are: {2} - Invalid '{0}' value: '{1}'. Accepted values are: {2} + Geçersiz '{0}' değeri: '{1}'. Kabul edilen değerler şunlardır: {2} {2} is a comma-separated list. @@ -2979,7 +2979,7 @@ {0} is not a valid C# coalesce operation - {0} is not a valid C# coalesce operation + {0}, geçerli bir C# birleştirme işlemi değil diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 6f96cc3ad60fb..90178d46c3087 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -249,7 +249,7 @@ Invalid '{0}' value: '{1}'. Accepted values are: {2} - Invalid '{0}' value: '{1}'. Accepted values are: {2} + “{0}”值无效:“{1}”。接受的值为: {2} {2} is a comma-separated list. @@ -2979,7 +2979,7 @@ {0} is not a valid C# coalesce operation - {0} is not a valid C# coalesce operation + {0} 不是有效的 C# 联合操作 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index b47af0d003fa2..945b5a29c58d1 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -249,7 +249,7 @@ Invalid '{0}' value: '{1}'. Accepted values are: {2} - Invalid '{0}' value: '{1}'. Accepted values are: {2} + 無效的 '{0}' 值: '{1}'。接受的值為: {2} {2} is a comma-separated list. @@ -2979,7 +2979,7 @@ {0} is not a valid C# coalesce operation - {0} is not a valid C# coalesce operation + {0} 不是有效的 C# 合併作業 diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index ce08e43bc5b85..9f3d6b82c1496 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -12044,6 +12044,30 @@ private struct S CleanupAllGeneratedFiles(dir.Path); } + [Fact] + public void RefOnly_EmbeddedDebugInformation() + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("a.cs"); + src.WriteAllText("public class C { }"); + + var outWriter = new StringWriter(CultureInfo.InvariantCulture); + var csc = CreateCSharpCompiler(null, dir.Path, + new[] { "/nologo", "/target:library", "/out:a.dll", "/refonly", "/debug:embedded", "/deterministic", "a.cs" }); + + Assert.Equal(0, csc.Run(outWriter)); + Assert.Equal("", outWriter.ToString()); + + using (var peReader = new PEReader(File.OpenRead(Path.Combine(dir.Path, "a.dll")))) + { + AssertEx.Equal( + new[] { DebugDirectoryEntryType.Reproducible }, + peReader.ReadDebugDirectory().Select(entry => entry.Type)); + } + + CleanupAllGeneratedFiles(dir.Path); + } + [Fact] public void CompilingCodeWithInvalidPreProcessorSymbolsShouldProvideDiagnostics() { diff --git a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs index 8a48d0e565349..553c03cb74bf4 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs @@ -2709,19 +2709,40 @@ void verifyEmbeddedDebugInfo(MemoryStream stream, DebugDirectoryEntryType[] expe } [Fact] - public void EmitMetadataOnly_DisallowEmbeddingPdb() + public void EmitMetadataOnly_IgnoreEmbeddedPdb() { CSharpCompilation comp = CreateEmptyCompilation("", references: new[] { MscorlibRef }, - options: TestOptions.DebugDll); + options: TestOptions.DebugDll.WithDeterministic(true)); using (var output = new MemoryStream()) { - Assert.Throws(() => comp.Emit(output, + var result = comp.Emit(output, options: EmitOptions.Default.WithEmitMetadataOnly(true) - .WithDebugInformationFormat(DebugInformationFormat.Embedded))); + .WithDebugInformationFormat(DebugInformationFormat.Embedded)); + + Assert.True(result.Success); + using var peReader = new PEReader(output.ToImmutable()); + AssertEx.Equal( + new[] { DebugDirectoryEntryType.Reproducible }, + peReader.ReadDebugDirectory().Select(entry => entry.Type)); } } + [Fact] + public void EmitMetadataOnly_IgnorePdb() + { + CSharpCompilation comp = CreateEmptyCompilation("", references: new[] { MscorlibRef }, + options: TestOptions.DebugDll.WithDeterministic(true)); + + var output = comp.EmitToArray( + EmitOptions.Default.WithEmitMetadataOnly(true).WithDebugInformationFormat(DebugInformationFormat.Pdb)); + + using var peReader = new PEReader(output); + AssertEx.Equal( + new[] { DebugDirectoryEntryType.Reproducible }, + peReader.ReadDebugDirectory().Select(entry => entry.Type)); + } + [Fact] public void EmitMetadata() { diff --git a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs index 16e8f3139539f..395d7688c8675 100644 --- a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs @@ -11444,8 +11444,6 @@ public string Prop [InlineData("#pragma warning disable 8603 // Possible null reference return.")] public void Nullable_Resilient_ChainedConstructor_01(string directive) { - // Since the backing field is nullable, a chained constructor isn't expected to put it into non-null state. - // The property has maybe-null state in "caller" constructor since it shares a slot with the field. var source = $$""" #nullable enable @@ -11454,7 +11452,7 @@ class C public C(bool ignored) { Prop = "a"; } public C() : this(false) { - Prop.ToString(); // 1 + Prop.ToString(); } public string Prop @@ -11466,10 +11464,7 @@ public string Prop """; var comp = CreateCompilation(source); - comp.VerifyEmitDiagnostics( - // (8,9): warning CS8602: Dereference of a possibly null reference. - // Prop.ToString(); // 1 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop").WithLocation(8, 9)); + comp.VerifyEmitDiagnostics(); var prop = comp.GetMember("C.Prop"); Assert.Equal(NullableAnnotation.Annotated, prop.BackingField.GetInferredNullableAnnotation()); @@ -11487,7 +11482,7 @@ class C public C(bool ignored) { Prop = "a"; } public C() : this(false) { - Prop.ToString(); // 1 + Prop.ToString(); } public string Prop @@ -11498,10 +11493,7 @@ public string Prop """; var comp = CreateCompilation(source); - comp.VerifyEmitDiagnostics( - // (8,9): warning CS8602: Dereference of a possibly null reference. - // Prop.ToString(); // 1 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop").WithLocation(8, 9)); + comp.VerifyEmitDiagnostics(); var prop = comp.GetMember("C.Prop"); Assert.Equal(NullableAnnotation.Annotated, prop.BackingField.GetInferredNullableAnnotation()); @@ -12351,10 +12343,12 @@ public string Value2 Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "Value1").WithArguments("S.Value1").WithLocation(23, 16)); } - [Fact] - public void Nullable_Accessor_FieldStateAffectedByAssigningThis() + [Theory] + [InlineData("field")] + [InlineData(@"field ?? ""a""")] + public void Nullable_Accessor_FieldStateAffectedByAssigningThis(string getterReturnValue) { - var source = """ + var source = $$""" #nullable enable public struct S @@ -12366,7 +12360,7 @@ public string Prop field = "a"; this = default; field.ToString(); // 1 - return field; + return {{getterReturnValue}}; } set { @@ -12387,23 +12381,25 @@ public string Prop Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "field").WithLocation(18, 13)); } - [Fact] - public void Nullable_Constructor_FieldStateAffectedByAssigningThis() + [Theory] + [InlineData("field")] + [InlineData(@"field ?? ""a""")] + public void Nullable_Constructor_FieldStateAffectedByAssigningThis(string getterReturnValue) { - var source = """ + var source = $$""" #nullable enable public struct S { public string Prop { - get => field; + get => {{getterReturnValue}}; set => field = value; } public static string StaticProp { - get => field; + get => {{getterReturnValue}}; set => field = value; } = "b"; @@ -12422,13 +12418,20 @@ public S() : this("a") } """; var comp = CreateCompilation(source); - comp.VerifyEmitDiagnostics( - // (26,9): warning CS8602: Dereference of a possibly null reference. - // Prop.ToString(); // 1 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop").WithLocation(26, 9)); + if (getterReturnValue == "field") + { + comp.VerifyEmitDiagnostics( + // (26,9): warning CS8602: Dereference of a possibly null reference. + // Prop.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop").WithLocation(26, 9)); + } + else + { + comp.VerifyEmitDiagnostics(); + } } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77991")] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/84987")] public void Nullable_OrdinaryMethod_FieldStateAffectedByAssigningStruct() { var source = """ @@ -12447,12 +12450,16 @@ public static string StaticProp get => field; set => field = value; } = "b"; - + public void M(S s) { s.Prop.ToString(); StaticProp.ToString(); + s.Prop = null; // 1 + s.Prop.ToString(); // 2 + + s.Prop = null; // 3 s = default; s.Prop.ToString(); // expected warning is missing StaticProp.ToString(); @@ -12460,10 +12467,19 @@ public void M(S s) } """; var comp = CreateCompilation(source); - comp.VerifyEmitDiagnostics(); - } - - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77991")] + comp.VerifyEmitDiagnostics( + // (22,18): warning CS8625: Cannot convert null literal to non-nullable reference type. + // s.Prop = null; // 1 + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(22, 18), + // (23,9): warning CS8602: Dereference of a possibly null reference. + // s.Prop.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s.Prop").WithLocation(23, 9), + // (25,18): warning CS8625: Cannot convert null literal to non-nullable reference type. + // s.Prop = null; // 3 + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(25, 18)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/84987")] public void Nullable_Accessor_OtherPropStateAffectedByAssigningStruct() { var source = """ @@ -12500,7 +12516,7 @@ public static string StaticProp comp.VerifyEmitDiagnostics(); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77991")] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/84987")] public void Nullable_PropInitializer_PropStateAffectedByAssigningStruct() { var source = """ @@ -12549,27 +12565,27 @@ public struct S { public S() { - Resilient.ToString(); // 3 + Resilient.ToString(); NonResilient.ToString(); // 4 } public S(bool ignored) : this() { - Resilient.ToString(); // 5 + Resilient.ToString(); NonResilient.ToString(); } public S((bool, bool) ignored2) { this = new(); - Resilient.ToString(); // 6 + Resilient.ToString(); NonResilient.ToString(); } public S((bool, bool, bool) ignored3) { this = default; - Resilient.ToString(); // 7 + Resilient.ToString(); NonResilient.ToString(); // 8 } @@ -12591,21 +12607,9 @@ public string NonResilient // (6,5): error CS0200: Property or indexer 'S.NonResilient' cannot be assigned to -- it is read only // NonResilient = "b" // 2 Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "NonResilient").WithArguments("S.NonResilient").WithLocation(6, 5), - // (13,9): warning CS8602: Dereference of a possibly null reference. - // Resilient.ToString(); // 3 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Resilient").WithLocation(13, 9), // (14,9): warning CS8602: Dereference of a possibly null reference. // NonResilient.ToString(); // 4 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "NonResilient").WithLocation(14, 9), - // (19,9): warning CS8602: Dereference of a possibly null reference. - // Resilient.ToString(); // 5 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Resilient").WithLocation(19, 9), - // (26,9): warning CS8602: Dereference of a possibly null reference. - // Resilient.ToString(); // 6 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Resilient").WithLocation(26, 9), - // (33,9): warning CS8602: Dereference of a possibly null reference. - // Resilient.ToString(); // 7 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Resilient").WithLocation(33, 9), // (34,9): warning CS8602: Dereference of a possibly null reference. // NonResilient.ToString(); // 8 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "NonResilient").WithLocation(34, 9)); @@ -12729,39 +12733,696 @@ static S() } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77991")] - public void Nullable_Resilient_InitialStateInConstructor() + public void Nullable_Resilient_InitialStateInConstructor_01() + { + var source = """ + #nullable enable + + // empty marker types to allow distinct constructor signatures + public class D1; + public class D2; + public class D3; + public class D4; + public class D5; + public class D6; + + public class C + { + public string Prop1 => field ??= "a"; + public string Prop2 => field; + + public C(bool ignored) + { + Prop1.ToString(); + Prop2.ToString(); // 1 + } + + public C() : this(false) + { + Prop1.ToString(); + Prop2.ToString(); + } + + public C(D1 d1) // 2 + { + } + + public C(D2 d2) : this(false) + { + } + + public C(D3 d3) // 3 + { + Prop1 = null; + Prop2 = null; // 4 + + if (string.Empty == "") + { + Prop1.ToString(); // 5 + Prop2.ToString(); // 6 + } + } + + public C(D4 d4) : this(false) // 7 + { + Prop1 = null; + Prop2 = null; // 8 + + if (string.Empty == "") + { + Prop1.ToString(); // 9 + Prop2.ToString(); // 10 + } + } + + public C(D5 d5) + { + Prop1 = "a"; + Prop2 = "a"; + + Prop1.ToString(); + Prop2.ToString(); + } + + public C(D6 d6) : this(false) + { + Prop1 = "a"; + Prop2 = "a"; + + Prop1.ToString(); + Prop2.ToString(); + } + + public void M() + { + Prop1.ToString(); + Prop2.ToString(); + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (19,9): warning CS8602: Dereference of a possibly null reference. + // Prop2.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop2").WithLocation(19, 9), + // (28,12): warning CS9264: Non-nullable property 'Prop2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier, or declaring the property as nullable, or safely handling the case where 'field' is null in the 'get' accessor. + // public C(D1 d1) // 2 + Diagnostic(ErrorCode.WRN_UninitializedNonNullableBackingField, "C").WithArguments("property", "Prop2").WithLocation(28, 12), + // (36,12): warning CS9264: Non-nullable property 'Prop2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier, or declaring the property as nullable, or safely handling the case where 'field' is null in the 'get' accessor. + // public C(D3 d3) // 3 + Diagnostic(ErrorCode.WRN_UninitializedNonNullableBackingField, "C").WithArguments("property", "Prop2").WithLocation(36, 12), + // (39,17): warning CS8625: Cannot convert null literal to non-nullable reference type. + // Prop2 = null; // 4 + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(39, 17), + // (43,13): warning CS8602: Dereference of a possibly null reference. + // Prop1.ToString(); // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop1").WithLocation(43, 13), + // (44,13): warning CS8602: Dereference of a possibly null reference. + // Prop2.ToString(); // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop2").WithLocation(44, 13), + // (48,12): warning CS9264: Non-nullable property 'Prop2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier, or declaring the property as nullable, or safely handling the case where 'field' is null in the 'get' accessor. + // public C(D4 d4) : this(false) // 7 + Diagnostic(ErrorCode.WRN_UninitializedNonNullableBackingField, "C").WithArguments("property", "Prop2").WithLocation(48, 12), + // (51,17): warning CS8625: Cannot convert null literal to non-nullable reference type. + // Prop2 = null; // 8 + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(51, 17), + // (55,13): warning CS8602: Dereference of a possibly null reference. + // Prop1.ToString(); // 9 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop1").WithLocation(55, 13), + // (56,13): warning CS8602: Dereference of a possibly null reference. + // Prop2.ToString(); // 10 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop2").WithLocation(56, 13)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77991")] + public void Nullable_Resilient_InitialStateInConstructor_02() + { + // Test behaviors of AllowNull attribute on various null-resilient properties. + var source = """ + #nullable enable + using System.Diagnostics.CodeAnalysis; + + // empty marker types to allow distinct constructor signatures + public class D1; + public class D2; + public class D3; + public class D4; + public class D5; + public class D6; + + public class C + { + [AllowNull] + public string Prop1 => field ??= "a"; + + public string Prop2 { get => field ??= "a"; set; } + + [AllowNull] + public string Prop3 { get => field ??= "a"; set; } + + public C(bool ignored) + { + Prop1.ToString(); + Prop2.ToString(); + Prop3.ToString(); + } + + public C() : this(false) + { + Prop1.ToString(); + Prop2.ToString(); + Prop3.ToString(); + } + + public C(D1 d1) + { + } + + public C(D2 d2) : this(false) + { + } + + public C(D3 d3) + { + Prop1 = null; + Prop2 = null; // 1 + Prop3 = null; + + if (string.Empty == "") + { + Prop1.ToString(); + Prop2.ToString(); // 2 + Prop3.ToString(); + } + } + + public C(D4 d4) : this(false) + { + Prop1 = null; + Prop2 = null; // 3 + Prop3 = null; + + if (string.Empty == "") + { + Prop1.ToString(); + Prop2.ToString(); // 4 + Prop3.ToString(); + } + } + + public C(D5 d5) + { + Prop1 = "a"; + Prop2 = "a"; + Prop3 = "a"; + + Prop1.ToString(); + Prop2.ToString(); + Prop3.ToString(); + } + + public C(D6 d6) : this(false) + { + Prop1 = "a"; + Prop2 = "a"; + Prop3 = "a"; + + Prop1.ToString(); + Prop2.ToString(); + Prop3.ToString(); + } + + public void M() + { + Prop1.ToString(); + } + } + """; + var comp = CreateCompilation([source, AllowNullAttributeDefinition]); + comp.VerifyEmitDiagnostics( + // (47,17): warning CS8625: Cannot convert null literal to non-nullable reference type. + // Prop2 = null; // 1 + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(47, 17), + // (53,13): warning CS8602: Dereference of a possibly null reference. + // Prop2.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop2").WithLocation(53, 13), + // (61,17): warning CS8625: Cannot convert null literal to non-nullable reference type. + // Prop2 = null; // 3 + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(61, 17), + // (67,13): warning CS8602: Dereference of a possibly null reference. + // Prop2.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop2").WithLocation(67, 13)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77991")] + public void Nullable_Resilient_InitialStateInConstructor_03() { + // Test behaviors of MaybeNull attribute on a null-resilient property var source = """ #nullable enable + using System.Diagnostics.CodeAnalysis; + + // empty marker types to allow distinct constructor signatures + public class D1; + public class D2; + public class D3; + public class D4; + public class D5; + public class D6; public class C { - public string Prop => field ??= "a"; + [MaybeNull] + public string Prop1 => field ??= "a"; public C(bool ignored) { - Prop.ToString(); // unexpected warning + Prop1.ToString(); // 1 } public C() : this(false) { - Prop.ToString(); // unexpected warning + Prop1.ToString(); // 2 + } + + public C(D1 d1) + { + } + + public C(D2 d2) : this(false) + { + } + + public C(D3 d3) + { + Prop1 = null; + if (string.Empty == "") + { + Prop1.ToString(); // 3 + } + } + + public C(D4 d4) : this(false) + { + Prop1 = null; + if (string.Empty == "") + { + Prop1.ToString(); // 4 + } + } + + public C(D5 d5) + { + Prop1 = "a"; + Prop1.ToString(); + } + + public C(D6 d6) : this(false) + { + Prop1 = "a"; + Prop1.ToString(); } public void M() { + Prop1.ToString(); // 3 + } + } + """; + var comp = CreateCompilation([source, MaybeNullAttributeDefinition]); + comp.VerifyEmitDiagnostics( + // (19,9): warning CS8602: Dereference of a possibly null reference. + // Prop1.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop1").WithLocation(19, 9), + // (24,9): warning CS8602: Dereference of a possibly null reference. + // Prop1.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop1").WithLocation(24, 9), + // (40,13): warning CS8602: Dereference of a possibly null reference. + // Prop1.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop1").WithLocation(40, 13), + // (49,13): warning CS8602: Dereference of a possibly null reference. + // Prop1.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop1").WithLocation(49, 13), + // (67,9): warning CS8602: Dereference of a possibly null reference. + // Prop1.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop1").WithLocation(67, 9)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77991")] + public void Nullable_Resilient_InitialStateInConstructor_04() + { + // Test behaviors of a required null-resilient property + var source = """ + #nullable enable + + // empty marker types to allow distinct constructor signatures + public class D1; + public class D2; + public class D3; + public class D4; + + public class C + { + public required string Prop1 { get => field ??= "a"; set; } + + public C(bool ignored) + { + Prop1.ToString(); + } + + public C() : this(false) + { + Prop1.ToString(); + } + + public C(D1 d) + { + } + + public C(D2 d) : this(false) + { + } + + public C(D3 d) + { + Prop1 = "a"; + + if (string.Empty == "") + { + Prop1.ToString(); + } + } + + public C(D4 d) : this(false) + { + Prop1 = "a"; + + if (string.Empty == "") + { + Prop1.ToString(); + } + } + + public void M() + { + Prop1.ToString(); + } + } + """; + var comp = CreateCompilation([source, RequiredMemberAttribute, CompilerFeatureRequiredAttribute, SetsRequiredMembersAttribute]); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77991")] + public void Nullable_Resilient_InitialStateInConstructor_05() + { + // Test behaviors of a required null-resilient property with SetsRequiredMembers on constructors + var source = """ + #nullable enable + using System.Diagnostics.CodeAnalysis; + + // empty marker types to allow distinct constructor signatures + public class D1; + public class D2; + public class D3; + public class D4; + public class D5; + public class D6; + public class D7; + public class D8; + + public class C + { + public required string Prop1 { get => field ??= "a"; set; } + + [SetsRequiredMembers] + public C(bool ignored) + { + Prop1.ToString(); + } + + public C(int ignored) + { + Prop1.ToString(); + } + + [SetsRequiredMembers] + public C(D1 d) : this(false) + { + Prop1.ToString(); + } + + [SetsRequiredMembers] + public C(D2 d) : this(0) + { + Prop1.ToString(); + } + + [SetsRequiredMembers] + public C(D3 d) + { + } + + [SetsRequiredMembers] + public C(D4 d) : this(false) + { + } + + [SetsRequiredMembers] + public C(D5 d) : this(0) + { + } + + [SetsRequiredMembers] + public C(D6 d) + { + Prop1 = "a"; + } + + [SetsRequiredMembers] + public C(D7 d) : this(false) + { + Prop1 = "a"; + } + + [SetsRequiredMembers] + public C(D8 d) : this(0) + { + Prop1 = "a"; + } + + public void M() + { + Prop1.ToString(); + } + } + """; + var comp = CreateCompilation([source, RequiredMemberAttribute, CompilerFeatureRequiredAttribute, SetsRequiredMembersAttribute]); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/84957")] + public void Repro_84957() + { + var source = """ + #nullable enable + internal class Goo { + private Bar? _bang1; + + public Goo() { + Wiz1 = Bang1; + Wiz2 = Bang2; + Wiz3 = Bang3; + Wiz4 = Bang4; + Wiz5 = Bang5; + } + + public Bar Wiz1 { get; set; } + public Bar Wiz2 { get; set; } + public Bar Wiz3 { get; set; } + public Bar Wiz4 { get; set; } + public Bar Wiz5 { get; set; } + + public Bar Bang1 => _bang1 ??= new Bar(); + + public Bar Bang2 => field ??= new Bar(); + + public Bar Bang3 => (field ??= new Bar())!; + + public Bar Bang4 { + get { + field ??= new Bar(); + return field; + } + } + + public Bar Bang5 { + get { + field = new Bar(); + return field; + } + } + } + + public class Bar { }; + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/84987")] + public void Nullable_InheritedState_01() + { + var source = """ + #nullable enable + struct S + { + public string Prop { get => field; set; } + public void M() + { + this = default; + this.Prop.ToString(); // expected warning missing + } + + public S() + { + this = default; + Prop.ToString(); // 1 + } + + public S(bool ignored1) + { + S s1 = default; + this = s1; + Prop.ToString(); // 2 + } + + public S((bool, bool) ignored2) + { + S s1 = default; + s1.Prop = "a"; + this = s1; Prop.ToString(); } } """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (9,9): warning CS8602: Dereference of a possibly null reference. - // Prop.ToString(); // unexpected warning - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop").WithLocation(9, 9), // (14,9): warning CS8602: Dereference of a possibly null reference. - // Prop.ToString(); // unexpected warning - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop").WithLocation(14, 9)); + // Prop.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop").WithLocation(14, 9), + // (21,9): warning CS8602: Dereference of a possibly null reference. + // Prop.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop").WithLocation(21, 9)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/84987")] + public void Nullable_InheritedState_02() + { + // Slot is only shared between property and backing field of 'this' variable + var source = """ + #nullable enable + struct S() + { + public string Prop { get => field; set; } = "a"; + + public class Inner + { + void M(S s) + { + s.Prop.ToString(); + s = default; + s.Prop.ToString(); // expected warning missing + } + + public Inner(S s) + { + s.Prop.ToString(); + s = default; + s.Prop.ToString(); // expected warning missing + } + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/84987")] + public void Nullable_InheritedState_03() + { + var source = """ + #nullable enable + struct S + { + public T Prop { get => field; set; } = default!; + public static void Use(T t) { } + + void M() + { + Use(this.Prop); + this = default; + Use(this.Prop); // expected warning missing + Use(default(T)); // 1 + } + + public S() + { + Use(this.Prop); + this = default; + Use(default(T)); // 2 + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (12,13): warning CS8604: Possible null reference argument for parameter 't' in 'void S.Use(T t)'. + // Use(default(T)); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "default(T)").WithArguments("t", "void S.Use(T t)").WithLocation(12, 13), + // (19,13): warning CS8604: Possible null reference argument for parameter 't' in 'void S.Use(T t)'. + // Use(default(T)); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "default(T)").WithArguments("t", "void S.Use(T t)").WithLocation(19, 13)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/84987")] + public void Nullable_InheritedState_04() + { + var source = """ + #nullable enable + struct S() + { + public T Prop { get => field; set; } = default!; + + public class Inner + { + public static void Use(T t) { } + + void M(S s) + { + Use(s.Prop); + s = default; + Use(s.Prop); // expected warning missing + Use(default(T)); // 1 + } + + public Inner(S s) + { + Use(s.Prop); + s = default; + Use(s.Prop); // expected warning missing + Use(default(T)); // 2 + } + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (15,17): warning CS8604: Possible null reference argument for parameter 't' in 'void Inner.Use(T t)'. + // Use(default(T)); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "default(T)").WithArguments("t", "void Inner.Use(T t)").WithLocation(15, 17), + // (23,17): warning CS8604: Possible null reference argument for parameter 't' in 'void Inner.Use(T t)'. + // Use(default(T)); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "default(T)").WithArguments("t", "void Inner.Use(T t)").WithLocation(23, 17)); } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/78592")] @@ -12993,9 +13654,8 @@ void verify2(MetadataReference reference) [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80504")] public void SetsRequiredMembersInSubtype() { - var source = """ + var source1 = """ #nullable enable - using System.Diagnostics.CodeAnalysis; public class Foo { public required string Bar { @@ -13005,20 +13665,39 @@ public required string Bar { } } } + """; - public class FooDerivative : Foo { + var source2 = """ + #nullable enable + using System.Diagnostics.CodeAnalysis; + public class FooDerivative : Foo { [SetsRequiredMembers] public FooDerivative() { } } """; - var comp = CreateCompilation(source, targetFramework: TargetFramework.Net100); + var comp = CreateCompilation([source1, source2], targetFramework: TargetFramework.Net100); comp.VerifyEmitDiagnostics( - // (16,12): warning CS8618: Non-nullable property 'Bar' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. + // (6,12): warning CS8618: Non-nullable property 'Bar' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. // public FooDerivative() { - Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "FooDerivative").WithArguments("property", "Bar").WithLocation(16, 12)); + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "FooDerivative").WithArguments("property", "Bar").WithLocation(6, 12)); + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.Net100); + comp1.VerifyEmitDiagnostics(); + + verify2(comp1.ToMetadataReference()); + verify2(comp1.EmitToImageReference()); + + void verify2(MetadataReference reference) + { + var comp2 = CreateCompilation(source2, references: [reference], targetFramework: TargetFramework.Net100); + comp2.VerifyEmitDiagnostics( + // (6,12): warning CS8618: Non-nullable property 'Bar' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. + // public FooDerivative() { + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "FooDerivative").WithArguments("property", "Bar").WithLocation(6, 12)); + } } private class TestAnalyzer1 : DiagnosticAnalyzer diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/RequiredMembersTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/RequiredMembersTests.cs index c543924a079c7..f4a11a725e939 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/RequiredMembersTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/RequiredMembersTests.cs @@ -4845,6 +4845,200 @@ public Derived() comp.VerifyDiagnostics(expectedDiagnostics); } + [Fact, CompilerTrait(CompilerFeature.NullableReferenceTypes)] + [WorkItem(6754, "https://github.com/dotnet/csharplang/issues/6754")] + public void RequiredMemberSuppressesNullabilityWarnings_MemberNotNull_ChainedBaseConstructor_06() + { + // Base required property initializes a private field. + // Derived constructors have SetsRequiredMembers and demonstrate behavior with/without initializing base required property. + var @base = """ +using System.Diagnostics.CodeAnalysis; +#nullable enable +public class Base +{ + private string _field; + public string Field => _field; + public required string Property { get => _field; [MemberNotNull(nameof(_field))] set => _field = value; } + + public Base() { } +} +"""; + + var derived = """ +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +var d = new Derived("property"); +Console.Write(d.Field); +Console.Write(' '); + +d = new Derived(); +try +{ + d.Field.ToString(); +} +catch (NullReferenceException) +{ + Console.Write("NullReferenceException"); +} + +class Derived : Base +{ + [SetsRequiredMembers] + public Derived() + { + } + + [SetsRequiredMembers] + public Derived(string value) + { + Property = value; + } +} +"""; + + var comp = CreateCompilationWithRequiredMembers(new[] { @base, derived, MemberNotNullAttributeDefinition }); + comp.VerifyDiagnostics( + // 1.cs(22,12): warning CS8618: Non-nullable property 'Property' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. + // public Derived() + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Property").WithLocation(22, 12) + ); + CompileAndVerify(comp, expectedOutput: "property NullReferenceException"); + + var baseComp = CreateCompilationWithRequiredMembers(new[] { @base, MemberNotNullAttributeDefinition }); + comp = CreateCompilation(derived, new[] { baseComp.EmitToImageReference() }); + comp.VerifyDiagnostics( + // 1.cs(22,12): warning CS8618: Non-nullable property 'Property' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. + // public Derived() + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Property").WithLocation(22, 12) + ); + CompileAndVerify(comp, expectedOutput: "property NullReferenceException"); + } + + [Fact, CompilerTrait(CompilerFeature.NullableReferenceTypes)] + [WorkItem(6754, "https://github.com/dotnet/csharplang/issues/6754")] + public void RequiredMemberSuppressesNullabilityWarnings_MemberNotNull_ChainedBaseConstructor_07() + { + // Base required property, with nullable type, initializes a private field. + // Derived constructors have SetsRequiredMembers and demonstrate behavior with/without initializing base required property. + // Note: this is a safety hole. However, the user has to go quite off into the weeds to fall into it. + var @base = """ +using System.Diagnostics.CodeAnalysis; +#nullable enable +public class Base +{ + private string _field; + public string Field => _field; + public required string? Property { get => _field; [MemberNotNull(nameof(_field))] set => _field = value ?? "property"; } + + public Base() { } +} +"""; + + var derived = """ +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +var d = new Derived(null); +Console.Write(d.Field); +Console.Write(' '); + +d = new Derived(); +try +{ + d.Field.ToString(); +} +catch (NullReferenceException) +{ + Console.Write("NullReferenceException"); +} + +class Derived : Base +{ + [SetsRequiredMembers] + public Derived() + { + } + + [SetsRequiredMembers] + public Derived(string? value) + { + Property = value; + } +} +"""; + + var comp = CreateCompilationWithRequiredMembers(new[] { @base, derived, MemberNotNullAttributeDefinition }); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "property NullReferenceException"); + + var baseComp = CreateCompilationWithRequiredMembers(new[] { @base, MemberNotNullAttributeDefinition }); + comp = CreateCompilation(derived, new[] { baseComp.EmitToImageReference() }); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "property NullReferenceException"); + } + + [Fact, CompilerTrait(CompilerFeature.NullableReferenceTypes)] + [WorkItem(6754, "https://github.com/dotnet/csharplang/issues/6754")] + public void RequiredMemberSuppressesNullabilityWarnings_MemberNotNull_ChainedBaseConstructor_08() + { + // Base required property initializes a private field. + // Derived is a nested type of the base and therefore can access the base private field. + // Derived constructors have SetsRequiredMembers and demonstrate behavior with/without initializing base required property. + var source = """ +using System; +using System.Diagnostics.CodeAnalysis; +#nullable enable + +var d = new Base.Derived("property"); +Console.Write(d.Field); +Console.Write(' '); + +d = new Base.Derived(); +try +{ + d.Field.ToString(); +} +catch (NullReferenceException) +{ + Console.Write("NullReferenceException"); +} + +public class Base +{ + private string _field; + public string Field => _field; + public required string Property { get => _field; [MemberNotNull(nameof(_field))] set => _field = value; } + + public Base() { } + + public class Derived : Base + { + [SetsRequiredMembers] + public Derived() + { + } + + [SetsRequiredMembers] + public Derived(string value) + { + Property = value; + } + } +} +"""; + + var comp = CreateCompilationWithRequiredMembers(new[] { source, MemberNotNullAttributeDefinition }); + comp.VerifyDiagnostics( + // 0.cs(30,16): warning CS8618: Non-nullable property 'Property' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. + // public Derived() + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Derived").WithArguments("property", "Property").WithLocation(30, 16) + ); + CompileAndVerify(comp, expectedOutput: "property NullReferenceException"); + } + [Fact, CompilerTrait(CompilerFeature.NullableReferenceTypes)] public void RequiredMemberSuppressesNullabilityWarnings_ChainedConstructor_01() { diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/TypeMapTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/TypeMapTests.cs index 221ac874926f2..0b5b9ff2dadf1 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/TypeMapTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/TypeMapTests.cs @@ -11,13 +11,201 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests { public class TypeMapTests : CSharpTestBase { + [Theory] + [InlineData("C")] + [InlineData("C")] + [InlineData("C")] + [InlineData("C")] + public void SubstituteNamedType_NoChange(string type) + { + var (map, previous, _) = CreateSubstitution(type, type); + + Assert.Same(previous, map.SubstituteNamedType(previous)); + } + + [Theory] + [InlineData("C", "C")] + [InlineData("C", "C")] + [InlineData("C", "C")] + [InlineData("C", "C")] + [InlineData("C", "C")] + [InlineData("C>", "C>")] + [InlineData("Outer.C>", "Outer.C>")] + public void SubstituteNamedType_ChangedArguments(string type, string substitutedType) + { + var (map, previous, expected) = CreateSubstitution(type, substitutedType); + + var actual = map.SubstituteNamedType(previous); + + Assert.NotSame(previous, actual); + Assert.True(TypeSymbol.Equals(expected, actual, TypeCompareKind.ConsiderEverything)); + Assert.Same(previous.OriginalDefinition, actual.OriginalDefinition); + Assert.True(TypeSymbol.Equals(actual, map.SubstituteNamedType(actual), TypeCompareKind.ConsiderEverything)); + } + + [Theory] + [InlineData("Outer.C", "Outer.C")] + [InlineData("Outer.C", "Outer.C")] + [InlineData("Outer.C", "Outer.C")] + [InlineData("Outer.C", "Outer.C")] + public void SubstituteNamedType_ContainingTypeOnly(string type, string substitutedType) + { + var (map, previous, expected) = CreateSubstitution(type, substitutedType); + + var actual = map.SubstituteNamedType(previous); + + Assert.NotSame(previous, actual); + Assert.True(TypeSymbol.Equals(expected, actual, TypeCompareKind.ConsiderEverything)); + Assert.Same(previous.OriginalDefinition, actual.OriginalDefinition); + Assert.Equal(SpecialType.System_Int32, actual.ContainingType.TypeArguments().Single().SpecialType); + var oldArguments = previous.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics; + var newArguments = actual.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics; + Assert.Equal(oldArguments.Length, newArguments.Length); + for (int i = 0; i < oldArguments.Length; i++) + { + Assert.True(oldArguments[i].Equals(newArguments[i], TypeCompareKind.ConsiderEverything)); + } + } + + private static (TypeMap map, NamedTypeSymbol previous, NamedTypeSymbol expected) CreateSubstitution(string type, string substitutedType) + { + var compilation = CreateCompilation($$""" + public class C { } + public class C { } + public class C { } + public class C { } + public class Outer + { + public class C { } + public class C { } + public class C { } + public class C { } + } + public class Context + { + public {{type}} Previous { get; set; } + public {{substitutedType}} Expected { get; set; } + } + """); + compilation.VerifyEmitDiagnostics(); + var context = compilation.GetTypeByMetadataName("Context`1"); + var map = new TypeMap(context.TypeParameters, + ImmutableArray.Create(TypeWithAnnotations.Create(compilation.GetSpecialType(SpecialType.System_Int32)))); + var previous = (NamedTypeSymbol)((PropertySymbol)context.GetMembers("Previous").Single()).Type; + var expected = (NamedTypeSymbol)((PropertySymbol)context.GetMembers("Expected").Single()).Type; + return (map, previous, expected); + } + + [Fact] + public void SubstituteNamedType_TupleNamesAndNullableArguments() + { + var compilation = CreateCompilation(""" + #nullable enable + public class C { } + public class Context where T : class + { + public C second)> Previous => throw null!; + public C second)> Expected => throw null!; + } + """, targetFramework: TargetFramework.NetCoreApp); + compilation.VerifyEmitDiagnostics(); + var context = compilation.GetTypeByMetadataName("Context`1"); + var previous = (NamedTypeSymbol)((PropertySymbol)context.GetMembers("Previous").Single()).Type; + var expected = (NamedTypeSymbol)((PropertySymbol)context.GetMembers("Expected").Single()).Type; + var map = new TypeMap(context.TypeParameters, + ImmutableArray.Create(TypeWithAnnotations.Create(compilation.GetSpecialType(SpecialType.System_Object), NullableAnnotation.NotAnnotated))); + + var actual = map.SubstituteNamedType(previous); + + Assert.True(TypeSymbol.Equals(expected, actual, TypeCompareKind.ConsiderEverything)); + var arguments = actual.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics; + Assert.Equal(NullableAnnotation.Annotated, arguments[0].NullableAnnotation); + var tuple = (NamedTypeSymbol)arguments[1].Type; + Assert.Equal(new[] { "first", "second" }, tuple.TupleElementNames); + Assert.Equal(NullableAnnotation.Annotated, tuple.TupleElements[0].TypeWithAnnotations.NullableAnnotation); + Assert.Equal(SpecialType.System_Object, tuple.TupleElements[0].Type.SpecialType); + } + + [Fact] + public void SubstituteNamedType_NullabilityOnly() + { + var compilation = CreateCompilation(""" + #nullable enable + public class C { } + public class Context where T : class + { + public C Previous => throw null!; + public C Expected => throw null!; + } + """); + compilation.VerifyEmitDiagnostics(); + var context = compilation.GetTypeByMetadataName("Context`1"); + var previous = (NamedTypeSymbol)((PropertySymbol)context.GetMembers("Previous").Single()).Type; + var expected = (NamedTypeSymbol)((PropertySymbol)context.GetMembers("Expected").Single()).Type; + var map = new TypeMap(context.TypeParameters, + ImmutableArray.Create(TypeWithAnnotations.Create(context.TypeParameters.Single(), NullableAnnotation.Annotated))); + + var actual = map.SubstituteNamedType(previous); + + Assert.NotSame(previous, actual); + Assert.True(TypeSymbol.Equals(expected, actual, TypeCompareKind.ConsiderEverything)); + var oldArgument = previous.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[1]; + var newArgument = actual.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[1]; + Assert.Same(oldArgument.Type, newArgument.Type); + Assert.Equal(NullableAnnotation.NotAnnotated, oldArgument.NullableAnnotation); + Assert.Equal(NullableAnnotation.Annotated, newArgument.NullableAnnotation); + Assert.False(oldArgument.IsSameAs(newArgument)); + } + + [Fact] + public void SubstituteNamedType_CustomModifierOnly() + { + var compilation = CreateCompilation(""" + public class C { } + public class Modifier { } + """); + compilation.VerifyEmitDiagnostics(); + var definition = compilation.GetTypeByMetadataName("C`2"); + var modifier = compilation.GetTypeByMetadataName("Modifier`1"); + var intType = compilation.GetSpecialType(SpecialType.System_Int32); + var stringType = compilation.GetSpecialType(SpecialType.System_String); + var previous = definition.Construct(ImmutableArray.Create( + TypeWithAnnotations.Create(stringType, NullableAnnotation.Annotated), + TypeWithAnnotations.Create(intType, customModifiers: ImmutableArray.Create( + CSharpCustomModifier.CreateOptional(modifier), + CSharpCustomModifier.CreateRequired(stringType))))); + var map = new TypeMap(modifier.TypeParameters, ImmutableArray.Create(TypeWithAnnotations.Create(intType))); + + var actual = map.SubstituteNamedType(previous); + + Assert.NotSame(previous, actual); + var arguments = actual.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics; + Assert.Same(stringType, arguments[0].Type); + Assert.Equal(NullableAnnotation.Annotated, arguments[0].NullableAnnotation); + Assert.Same(intType, arguments[1].Type); + Assert.Collection(arguments[1].CustomModifiers, + m => + { + Assert.True(m.IsOptional); + Assert.True(TypeSymbol.Equals(modifier.Construct(intType), ((CSharpCustomModifier)m).ModifierSymbol, TypeCompareKind.ConsiderEverything)); + }, + m => + { + Assert.False(m.IsOptional); + Assert.Same(stringType, ((CSharpCustomModifier)m).ModifierSymbol); + }); + Assert.False(previous.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[1].IsSameAs(arguments[1])); + } + // take a type of the form Something and return the type X. private TypeSymbol TypeArg(TypeSymbol t) { diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/ModifierParserRecoveryTests.Partial.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/ModifierParserRecoveryTests.Partial.cs index 07d3b60890056..c0c2ec72f093e 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/ModifierParserRecoveryTests.Partial.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/ModifierParserRecoveryTests.Partial.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; @@ -410,21 +409,6 @@ public void PartialPartial_TopLevel(LanguageVersion languageVersion) EOF(); } - [Theory] - [InlineData(LanguageVersion.CSharp13)] - [InlineData(LanguageVersion.Preview)] - public void ManyPartialModifiers_MakesProgress(LanguageVersion languageVersion) - { - var modifiers = string.Concat(Enumerable.Repeat("partial ", 10_000)); - var source = $"class C {{ {modifiers}int M(); }}"; - - var root = SyntaxFactory.ParseSyntaxTree( - source, - options: TestOptions.Regular.WithLanguageVersion(languageVersion)).GetRoot(); - - Assert.Equal(source, root.ToFullString()); - } - [Fact] public void Partial_ConversionOperators() { diff --git a/src/Compilers/Core/Portable/CodeAnalysisResources.resx b/src/Compilers/Core/Portable/CodeAnalysisResources.resx index 1374a9b50349b..e3b38c6d7101d 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisResources.resx +++ b/src/Compilers/Core/Portable/CodeAnalysisResources.resx @@ -312,9 +312,6 @@ Including private members should not be used when emitting to the secondary assembly output. - - Embedding PDB is not allowed when emitting metadata. - Cannot target net module when emitting ref assembly. diff --git a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs index 788e2defd7fcb..8e8ab1b089384 100644 --- a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs +++ b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @@ -1320,6 +1320,7 @@ private void CompileAndEmit( diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.FTL_InvalidInputFileName, Location.None, finalPdbFilePath)); } + bool emitPdb = Arguments.EmitPdb && !emitOptions.EmitMetadataOnly; var moduleBeingBuilt = compilation.CheckOptionsAndCreateModuleBuilder( diagnostics, Arguments.ManifestResources, @@ -1338,7 +1339,7 @@ private void CompileAndEmit( { success = compilation.CompileMethods( moduleBeingBuilt, - Arguments.EmitPdb, + emitPdb, diagnostics, filterOpt: null, cancellationToken: cancellationToken); @@ -1457,7 +1458,7 @@ private void CompileAndEmit( if (success) { var peStreamProvider = new CompilerEmitStreamProvider(this, finalPeFilePath); - var pdbStreamProviderOpt = Arguments.EmitPdbFile ? new CompilerEmitStreamProvider(this, finalPdbFilePath) : null; + var pdbStreamProviderOpt = emitPdb && Arguments.EmitPdbFile ? new CompilerEmitStreamProvider(this, finalPdbFilePath) : null; string? finalRefPeFilePath = Arguments.OutputRefFilePath; var refPeStreamProviderOpt = finalRefPeFilePath != null ? new CompilerEmitStreamProvider(this, finalRefPeFilePath) : null; diff --git a/src/Compilers/Core/Portable/Compilation/Compilation.cs b/src/Compilers/Core/Portable/Compilation/Compilation.cs index a34131a2d4e91..679c01896f10a 100644 --- a/src/Compilers/Core/Portable/Compilation/Compilation.cs +++ b/src/Compilers/Core/Portable/Compilation/Compilation.cs @@ -2887,12 +2887,6 @@ internal EmitResult Emit( options = options.WithIncludePrivateMembers(true); } - if (options?.DebugInformationFormat == DebugInformationFormat.Embedded && - options?.EmitMetadataOnly == true) - { - throw new ArgumentException(CodeAnalysisResources.EmbeddingPdbUnexpectedWhenEmittingMetadata, nameof(metadataPEStream)); - } - if (this.Options.OutputKind == OutputKind.NetModule) { if (metadataPEStream != null) @@ -2964,6 +2958,7 @@ internal EmitResult Emit( options = options ?? EmitOptions.Default.WithIncludePrivateMembers(metadataPEStream == null); bool embedPdb = options.DebugInformationFormat == DebugInformationFormat.Embedded; + bool emitPdb = !options.EmitMetadataOnly && (pdbStream != null || embedPdb); Debug.Assert(!embedPdb || pdbStream == null); Debug.Assert(metadataPEStream == null || !options.IncludePrivateMembers); // you may not use a secondary stream and include private members together @@ -2989,7 +2984,7 @@ internal EmitResult Emit( { success = CompileMethods( moduleBeingBuilt, - emittingPdb: pdbStream != null || embedPdb, + emittingPdb: emitPdb, diagnostics: diagnostics, filterOpt: null, cancellationToken: cancellationToken); @@ -3038,7 +3033,7 @@ internal EmitResult Emit( moduleBeingBuilt, new SimpleEmitStreamProvider(peStream), (metadataPEStream != null) ? new SimpleEmitStreamProvider(metadataPEStream) : null, - (pdbStream != null) ? new SimpleEmitStreamProvider(pdbStream) : null, + (emitPdb && pdbStream != null) ? new SimpleEmitStreamProvider(pdbStream) : null, rebuildData, testData?.SymWriterFactory, diagnostics, @@ -3271,10 +3266,16 @@ internal bool SerializeToPeStream( // PDB Stream provider should not be given if PDB is to be embedded into the PE file: Debug.Assert(moduleBeingBuilt.DebugInformationFormat != DebugInformationFormat.Embedded || pdbStreamProvider == null); + Debug.Assert(!emitOptions.EmitMetadataOnly || pdbStreamProvider == null); string? pePdbFilePath = emitOptions.PdbFilePath; - if (moduleBeingBuilt.DebugInformationFormat == DebugInformationFormat.Embedded || pdbStreamProvider != null) + if (emitOptions.EmitMetadataOnly) + { + // Metadata-only output has no associated PDB. Clear the path so the PE writer doesn't emit a CodeView entry. + pePdbFilePath = null; + } + else if (moduleBeingBuilt.DebugInformationFormat == DebugInformationFormat.Embedded || pdbStreamProvider != null) { pePdbFilePath = pePdbFilePath ?? FileNameUtilities.ChangeExtension(SourceModule.Name, "pdb"); } diff --git a/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj b/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj index c4b23686d08f1..761608d78563d 100644 --- a/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj +++ b/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj @@ -102,6 +102,7 @@ + diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf index 47767b35f1bfd..d7d9cb838ca13 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf @@ -613,11 +613,6 @@ Při generování do sekundárního výstupu sestavení by se neměly zahrnovat privátní členy. - - Embedding PDB is not allowed when emitting metadata. - Při generování metadat se vkládání PDB nepovoluje. - - Cannot target net module when emitting ref assembly. Když se generuje referenční sestavení, nejde cílit na síťový modul. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf index d8e9d4bf24ef5..beabb1d653dd3 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf @@ -613,11 +613,6 @@ Private Members sollten beim Ausgeben an die sekundäre Assemblyausgabe nicht einbezogen werden. - - Embedding PDB is not allowed when emitting metadata. - Einbetten von PDB ist beim Ausgeben von Metadaten nicht zulässig. - - Cannot target net module when emitting ref assembly. Verweisen auf Netzmodul beim Ausgeben der Referenzassembly nicht möglich. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf index 40e9e72257754..74ac3f6b9974a 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf @@ -613,11 +613,6 @@ No se debe usar la inclusión de números privados al emitir a la salida del ensamblado secundario. - - Embedding PDB is not allowed when emitting metadata. - No se permite insertar PDB al emitir metadatos. - - Cannot target net module when emitting ref assembly. El destino no puede ser el módulo al emitir el ensamblado de referencia. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf index 3859bae112fd2..ad49c217ebb5b 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf @@ -613,11 +613,6 @@ Les membres privés ne doivent pas être inclus durant l'émission vers la sortie de l'assembly secondaire. - - Embedding PDB is not allowed when emitting metadata. - L'incorporation d'un fichier PDB n'est pas autorisée quand des métadonnées sont émises. - - Cannot target net module when emitting ref assembly. Impossible de cibler le module net quand un assembly de référence est émis. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf index b1b4509e20078..11d319088115a 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf @@ -613,11 +613,6 @@ È consigliabile non usare membri privati di inclusione quando si crea l'output secondario dell'assembly. - - Embedding PDB is not allowed when emitting metadata. - L'incorporamento di PDB non è consentito quando si creano metadati. - - Cannot target net module when emitting ref assembly. Non è possibile impostare come destinazione il modulo quando si crea l'assembly di riferimento. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf index 37fbfbfe0b885..01305d4e61281 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf @@ -613,11 +613,6 @@ セカンダリ アセンブリ出力に対して生成している場合、プライベート メンバーのインクルードは使用しないでください。 - - Embedding PDB is not allowed when emitting metadata. - メタデータを生成している場合、PDB を埋め込むことは許可されていません。 - - Cannot target net module when emitting ref assembly. 参照アセンブリを生成している場合、ネット モジュールをターゲットにすることはできません。 diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf index 78ade61016c96..dd9eb42891588 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf @@ -613,11 +613,6 @@ 보조 어셈블리 출력으로 내보낼 때 프라이빗 멤버를 포함해서는 안 됩니다. - - Embedding PDB is not allowed when emitting metadata. - 메타데이터를 내보낼 때 PDB를 포함할 수 없습니다. - - Cannot target net module when emitting ref assembly. 참조 어셈블리를 내보낼 때 NET 모듈을 대상으로 지정할 수 없습니다. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf index 66e1237d97ffc..e0e0da16e9573 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf @@ -613,11 +613,6 @@ Uwzględnianie prywatnych składowych nie powinno być używane podczas emitowania danych wyjściowych pomocniczego zestawu. - - Embedding PDB is not allowed when emitting metadata. - Osadzanie pliku PDB nie jest dozwolone podczas emitowania metadanych. - - Cannot target net module when emitting ref assembly. Nie można ustawić modułu sieciowego jako docelowego podczas emitowania zestawu odwołania. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf index 203be65d6fbbf..efce097f14dfd 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf @@ -613,11 +613,6 @@ A inclusão de membros privados não deve ser usada ao emitir para a saída do assembly secundário. - - Embedding PDB is not allowed when emitting metadata. - Não é permitido inserir PDB ao emitir metadados. - - Cannot target net module when emitting ref assembly. Não é possível destinar o módulo de rede ao emitir o assembly de referência. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf index aaedb7be6d24a..2d5a05badf9b6 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf @@ -613,11 +613,6 @@ Включение частных членов не следует использовать при выводе в выходные данные вторичной сборки. - - Embedding PDB is not allowed when emitting metadata. - Внедрение PDB запрещено при выводе метаданных. - - Cannot target net module when emitting ref assembly. Не удается нацелиться на сетевой модуль при выводе базовой сборки. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf index d6dfc4a987d5e..8aaebede2b6da 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf @@ -613,11 +613,6 @@ İkincil bütünleştirilmiş kod çıktısına yayın yapılırken özel üyeleri ekleme özelliği kullanılmamalıdır. - - Embedding PDB is not allowed when emitting metadata. - Meta veriler yayınlanırken PDB eklemeye izin verilmez. - - Cannot target net module when emitting ref assembly. Başvuru bütünleştirilmiş kodu yayınlanırken net modülü hedeflenemez. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf index cbb13cdc27e3c..efa5aa62e69b0 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf @@ -613,11 +613,6 @@ 发出到辅助程序集输出时不应包含私有成员。 - - Embedding PDB is not allowed when emitting metadata. - 不允许在发出元数据时嵌入 PDB。 - - Cannot target net module when emitting ref assembly. 无法在发出引用程序集时将 Net 模块作为目标。 diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf index 29e049ff35590..c2b58aab49884 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf @@ -613,11 +613,6 @@ 發出至次要組件輸出時,應使用包含私人成員。 - - Embedding PDB is not allowed when emitting metadata. - 發出中繼資料時,不得內嵌 PDB。 - - Cannot target net module when emitting ref assembly. 發出參考組件時,目標不可以是網路模組。 diff --git a/src/Compilers/Test/Core/Compilation/CompilationExtensions.cs b/src/Compilers/Test/Core/Compilation/CompilationExtensions.cs index b3727f092641e..eaa7b2e64eefe 100644 --- a/src/Compilers/Test/Core/Compilation/CompilationExtensions.cs +++ b/src/Compilers/Test/Core/Compilation/CompilationExtensions.cs @@ -61,7 +61,10 @@ internal static ImmutableArray EmitToArray( { var peStream = new MemoryStream(); - if (pdbStream == null && compilation.Options.OptimizationLevel == OptimizationLevel.Debug && options?.DebugInformationFormat != DebugInformationFormat.Embedded) + if (pdbStream == null && + compilation.Options.OptimizationLevel == OptimizationLevel.Debug && + options?.EmitMetadataOnly != true && + options?.DebugInformationFormat != DebugInformationFormat.Embedded) { if (MonoHelpers.IsRunningOnMono() || PathUtilities.IsUnixLikePlatform) { diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.cs.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.cs.xlf index f50abc1d81180..1226630494661 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.cs.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.cs.xlf @@ -224,7 +224,7 @@ {0} is not a valid Visual Basic coalesce operation - {0} is not a valid Visual Basic coalesce operation + {0} není platná operace slučování v jazyce Visual Basic. diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.de.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.de.xlf index aabd158b1e584..a859a59e3600f 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.de.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.de.xlf @@ -224,7 +224,7 @@ {0} is not a valid Visual Basic coalesce operation - {0} is not a valid Visual Basic coalesce operation + {0} ist kein gültiger Visual Basic-Zusammenfügungsvorgang diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.es.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.es.xlf index ba6fadea6bc37..5eb3ce1cd226d 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.es.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.es.xlf @@ -224,7 +224,7 @@ {0} is not a valid Visual Basic coalesce operation - {0} is not a valid Visual Basic coalesce operation + {0} no es una operación de fusión Visual Basic válida diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.fr.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.fr.xlf index 33f5211fed6e6..1d62242df0d9c 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.fr.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.fr.xlf @@ -224,7 +224,7 @@ {0} is not a valid Visual Basic coalesce operation - {0} is not a valid Visual Basic coalesce operation + {0} n’est pas une opération de coalescence Visual Basic valide diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.it.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.it.xlf index 7908fe319642f..0d899670b5b16 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.it.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.it.xlf @@ -224,7 +224,7 @@ {0} is not a valid Visual Basic coalesce operation - {0} is not a valid Visual Basic coalesce operation + {0} non è un'operazione di coalescenza Visual Basic valida diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ja.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ja.xlf index 831690b1f90f1..a1eab34225778 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ja.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ja.xlf @@ -224,7 +224,7 @@ {0} is not a valid Visual Basic coalesce operation - {0} is not a valid Visual Basic coalesce operation + {0} は Visual Basic の有効な結合操作ではありません diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ko.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ko.xlf index f9c2ef238e19a..d0a38324190ee 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ko.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ko.xlf @@ -224,7 +224,7 @@ {0} is not a valid Visual Basic coalesce operation - {0} is not a valid Visual Basic coalesce operation + {0}은(는) 유효한 Visual Basic 병합 연산이 아님 diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.pl.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.pl.xlf index 45c317276520f..c3f1139838126 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.pl.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.pl.xlf @@ -224,7 +224,7 @@ {0} is not a valid Visual Basic coalesce operation - {0} is not a valid Visual Basic coalesce operation + {0} nie jest prawidłową operacją łączenia języka Visual Basic diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.pt-BR.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.pt-BR.xlf index 48970ef562e0f..fdca4d5462665 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.pt-BR.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.pt-BR.xlf @@ -224,7 +224,7 @@ {0} is not a valid Visual Basic coalesce operation - {0} is not a valid Visual Basic coalesce operation + {0} não é uma operação Visual Basic de união válida diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ru.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ru.xlf index 971072c1a59fb..2aa9047f0c94f 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.ru.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.ru.xlf @@ -224,7 +224,7 @@ {0} is not a valid Visual Basic coalesce operation - {0} is not a valid Visual Basic coalesce operation + {0} не является допустимой операцией объединения в Visual Basic diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.tr.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.tr.xlf index 900cfc0083029..98daea33bb0d8 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.tr.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.tr.xlf @@ -224,7 +224,7 @@ {0} is not a valid Visual Basic coalesce operation - {0} is not a valid Visual Basic coalesce operation + {0} geçerli bir Visual Basic birleştirme işlemi değil diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hans.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hans.xlf index e48f95959fad6..195af00295663 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hans.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hans.xlf @@ -224,7 +224,7 @@ {0} is not a valid Visual Basic coalesce operation - {0} is not a valid Visual Basic coalesce operation + {0} 不是有效的 Visual Basic 合并操作 diff --git a/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hant.xlf b/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hant.xlf index 3d9a3dbcb23da..eebaa2718873e 100644 --- a/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hant.xlf +++ b/src/Compilers/VisualBasic/Portable/xlf/VBResources.zh-Hant.xlf @@ -224,7 +224,7 @@ {0} is not a valid Visual Basic coalesce operation - {0} is not a valid Visual Basic coalesce operation + {0} 不是有效的 Visual Basic 合併作業 diff --git a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb index 48659b64f60fe..918abbef341ad 100644 --- a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb +++ b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb @@ -9568,6 +9568,29 @@ a CleanupAllGeneratedFiles(dir.Path) End Sub + + Public Sub RefOnly_EmbeddedDebugInformation() + Dim dir = Temp.CreateDirectory() + Dim src = dir.CreateFile("a.vb") + src.WriteAllText("Public Class C +End Class") + + Dim outWriter = New StringWriter(CultureInfo.InvariantCulture) + Dim vbc = New MockVisualBasicCompiler(Nothing, dir.Path, + {"/define:_MYTYPE=""Empty"" ", "/nologo", "/target:library", "/out:a.dll", "/refonly", "/debug:embedded", "/deterministic", "a.vb"}) + + Assert.Equal(0, vbc.Run(outWriter)) + Assert.Equal("", outWriter.ToString()) + + Using peReader = New PEReader(File.OpenRead(Path.Combine(dir.Path, "a.dll"))) + AssertEx.Equal( + {DebugDirectoryEntryType.Reproducible}, + peReader.ReadDebugDirectory().Select(Function(entry) entry.Type)) + End Using + + CleanupAllGeneratedFiles(dir.Path) + End Sub + 'Output with known but different extension diff --git a/src/Dependencies/Collections/Extensions/IEnumerableExtensions.cs b/src/Dependencies/Collections/Extensions/IEnumerableExtensions.cs index 18ed50a9ef9df..7ad4fc17f689b 100644 --- a/src/Dependencies/Collections/Extensions/IEnumerableExtensions.cs +++ b/src/Dependencies/Collections/Extensions/IEnumerableExtensions.cs @@ -530,6 +530,19 @@ public static ImmutableArray SelectManyAsArray(th return builder.ToImmutableAndFree(); } + public static ImmutableArray SelectManyAsArray(this IReadOnlyCollection? source, Func> selector, TArg arg) + { + if (source is null or { Count: 0 }) + return []; + + // Basic heuristic. Assume each element in the source adds one item to the result. + var builder = ArrayBuilder.GetInstance(source.Count); + foreach (var item in source) + selector(item, arg).AddRangeTo(builder); + + return builder.ToImmutableAndFree(); + } + public static ImmutableArray SelectManyAsArray(this IEnumerable? source, Func> selector) { if (!TryGetBuilder(source, useCountForBuilder: false, out var builder)) diff --git a/src/Dependencies/Collections/Extensions/ImmutableArrayExtensions.cs b/src/Dependencies/Collections/Extensions/ImmutableArrayExtensions.cs index 185e75effe3b5..f88f01a0615f4 100644 --- a/src/Dependencies/Collections/Extensions/ImmutableArrayExtensions.cs +++ b/src/Dependencies/Collections/Extensions/ImmutableArrayExtensions.cs @@ -296,6 +296,29 @@ public static ImmutableArray SelectManyAsArray(this Imm return builder.ToImmutableAndFree(); } + /// + /// Maps and flattens an immutable array to another immutable array. + /// + /// Type of the source array items + /// Type of the argument to pass to the selector. + /// Type of the transformed array items + /// The array to transform + /// A transform function to apply to each element. + /// If the array's length is 0, this will return an empty immutable array. + public static ImmutableArray SelectManyAsArray(this ImmutableArray array, Func> selector, TArg arg) + { + if (array.Length == 0) + return []; + + var builder = ArrayBuilder.GetInstance(); + foreach (var item in array) + { + selector(item, arg).AddRangeTo(builder); + } + + return builder.ToImmutableAndFree(); + } + /// /// Maps and flattens a subset of immutable array to another immutable array. /// diff --git a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs index b64f9dd69b0f1..f099ec8310bc1 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs @@ -3286,71 +3286,6 @@ bool N(object x) Parameter("x"), Keyword("_")); - [Theory, CombinatorialData] - public Task UnusedUnderscoreParameterInLambda(TestHost testHost) - => TestAsync(""" - class X - { - void N() - { - System.Func a = (int _) => 0; - } - } - """, - testHost, - Namespace("System"), - Delegate("Func")); - - [Theory, CombinatorialData] - public Task UsedUnderscoreParameterInLambda(TestHost testHost) - => TestAsync(""" - class X - { - void N() - { - System.Func a = (int _) => _; - } - } - """, - testHost, - Namespace("System"), - Delegate("Func"), - Parameter("_")); - - [Theory, CombinatorialData] - public Task DiscardsInLambda(TestHost testHost) - => TestAsync(""" - class X - { - void N() - { - System.Func a = (int _, int _) => 0; - } - } - """, - testHost, - Namespace("System"), - Delegate("Func"), - Keyword("_"), - Keyword("_")); - - [Theory, CombinatorialData] - public Task DiscardsInLambdaWithInferredType(TestHost testHost) - => TestAsync(""" - class X - { - void N() - { - System.Func a = (_, _) => 0; - } - } - """, - testHost, - Namespace("System"), - Delegate("Func"), - Keyword("_"), - Keyword("_")); - [Theory, CombinatorialData] public Task NativeInteger(TestHost testHost) => TestInMethodAsync( diff --git a/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests_Preprocessor.cs b/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests_Preprocessor.cs index 7c8279ecb7523..6366278342148 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests_Preprocessor.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests_Preprocessor.cs @@ -1102,7 +1102,7 @@ public Task DiscardInLambda(TestHost testHost) => TestInMethodAsync( code: @"x = (_, _) => 1;", testHost: testHost, -expected: Classifications(Identifier("x"), Operators.Equals, Punctuation.OpenParen, Parameter("_"), Punctuation.Comma, Parameter("_"), Punctuation.CloseParen, +expected: Classifications(Identifier("x"), Operators.Equals, Punctuation.OpenParen, Keyword("_"), Punctuation.Comma, Keyword("_"), Punctuation.CloseParen, Operators.EqualsGreaterThan, Number("1"), Punctuation.Semicolon)); [Theory, CombinatorialData] diff --git a/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs index 1ae7cf91f4aec..76da4d3061272 100644 --- a/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs @@ -2942,6 +2942,45 @@ void M() ], actualFormatted); } + [WpfTheory] + [InlineData("System.Action lambda = (_, p) => { };", false)] + [InlineData("System.Action lambda = (int _, int p) => { };", false)] + [InlineData("System.Action lambda = (_, _, p) => { };", true)] + [InlineData("System.Action lambda = (int _, int _, int p) => { };", true)] + public async Task TestTotalClassifier_LambdaDiscards(string declaration, bool isDiscard) + { + using var workspace = EditorTestWorkspace.CreateCSharp($$""" + class C + { + void M() + { + {{declaration}} + } + } + """); + var document = workspace.Documents.First(); + var provider = new TotalClassificationTaggerProvider( + workspace.GetService(), + workspace.GetService()); + + var buffer = document.GetTextBuffer(); + using var tagger = provider.CreateTagger(document.GetTextView(), buffer); + + var listenerProvider = workspace.ExportProvider.GetExportedValue(); + await listenerProvider.GetWaiter(FeatureAttribute.Classification).ExpeditedWaitAsync(); + + var tags = tagger!.GetTags(new NormalizedSnapshotSpanCollection(buffer.CurrentSnapshot.GetFullSpan())); + var actual = tags.OrderBy(tag => tag.Span.Start.Position) + .Where(tag => tag.Span.GetText() is "_" or "p") + .Select(tag => new FormattedClassification(tag.Span.GetText(), tag.Tag.ClassificationType.Classification)); + + AssertEx.Equal( + isDiscard + ? [Keyword("_"), Keyword("_"), Parameter("p")] + : [Parameter("_"), Parameter("p")], + actual); + } + [WpfFact] public void TestCopyPasteClassifier() { diff --git a/src/EditorFeatures/Core/InlineRename/UI/SmartRename/SmartRenameViewModel_Telemetry.cs b/src/EditorFeatures/Core/InlineRename/UI/SmartRename/SmartRenameViewModel_Telemetry.cs index 37f769b0999cf..2615a24523025 100644 --- a/src/EditorFeatures/Core/InlineRename/UI/SmartRename/SmartRenameViewModel_Telemetry.cs +++ b/src/EditorFeatures/Core/InlineRename/UI/SmartRename/SmartRenameViewModel_Telemetry.cs @@ -45,7 +45,7 @@ private void PostTelemetry(bool isCommit) if (_suggestionsPanelTelemetry is not null) { RoslynDebug.Assert(_suggestionsDropdownTelemetry is null); - RoslynTelemetry.Log(FunctionId.Copilot_Rename, KeyValueLogMessage.Create(m => + RoslynTelemetry.Current.Log(FunctionId.Copilot_Rename, KeyValueLogMessage.Create(m => { m[nameof(isCommit)] = isCommit; m["UseSuggestionsPanel"] = true; @@ -61,7 +61,7 @@ private void PostTelemetry(bool isCommit) else { RoslynDebug.Assert(_suggestionsDropdownTelemetry is not null); - RoslynTelemetry.Log(FunctionId.Copilot_Rename, KeyValueLogMessage.Create(m => + RoslynTelemetry.Current.Log(FunctionId.Copilot_Rename, KeyValueLogMessage.Create(m => { m[nameof(isCommit)] = isCommit; m["UseDropDown"] = true; diff --git a/src/EditorFeatures/Core/Logging/FunctionIdOptions.cs b/src/EditorFeatures/Core/Logging/FunctionIdOptions.cs deleted file mode 100644 index 3b120e1b8beae..0000000000000 --- a/src/EditorFeatures/Core/Logging/FunctionIdOptions.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using Microsoft.CodeAnalysis.Options; - -namespace Microsoft.CodeAnalysis.Internal.Log; - -internal static class FunctionIdOptions -{ - private static readonly ConcurrentDictionary> s_options = []; - - private static readonly Func> s_optionCreator = CreateOption; - - private static Option2 CreateOption(FunctionId id) - { - var name = id.ToString(); - - // This local storage location can be set via vsregedit. Which is available on any VS Command Prompt. - // - // To enable logging: - // - // vsregedit set local [hive name] HKCU Roslyn\Internal\Performance\FunctionId [function name] dword 1 - // - // To disable logging - // - // vsregedit delete local [hive name] HKCU Roslyn\Internal\Performance\FunctionId [function name] - // - // If you want to set it for the default hive, use "" as the hive name (i.e. an empty argument) - return new("FunctionIdOptions_" + name, defaultValue: false); - } - - private static IEnumerable GetFunctionIds() - => Enum.GetValues(); - - public static IEnumerable GetOptions() - => GetFunctionIds().Select(GetOption); - - public static Option2 GetOption(FunctionId id) - => s_options.GetOrAdd(id, s_optionCreator); - - public static Func CreateFunctionIsEnabledPredicate(IGlobalOptionService globalOptions) - { - var functionIdOptions = GetFunctionIds().ToDictionary(id => id, id => globalOptions.GetOption(GetOption(id))); - return functionId => functionIdOptions[functionId]; - } -} diff --git a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj index e6c8576a0d315..d05826c64c982 100644 --- a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj +++ b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj @@ -85,7 +85,6 @@ - diff --git a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs index 3b02fefbc3731..eb281e2270527 100644 --- a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs +++ b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs @@ -190,7 +190,7 @@ private async Task DispatchSynchronizeTextChangesAsync( // Update aggregated telemetry with success status of sending the synchronization data. var metricName = wasSynchronized.Value ? SynchronizeTextChangesStatusSucceededMetricName : SynchronizeTextChangesStatusFailedMetricName; - RoslynTelemetry.Count(FunctionId.ChecksumUpdater_SynchronizeTextChangesStatus, metricName, 1); + RoslynTelemetry.Current.Count(FunctionId.ChecksumUpdater_SynchronizeTextChangesStatus, metricName, 1); return; diff --git a/src/EditorFeatures/Core/Suggestions/SuggestedActions/EditorSuggestedAction.cs b/src/EditorFeatures/Core/Suggestions/SuggestedActions/EditorSuggestedAction.cs index 2c16d0f154d9b..c60d1fd45ac8b 100644 --- a/src/EditorFeatures/Core/Suggestions/SuggestedActions/EditorSuggestedAction.cs +++ b/src/EditorFeatures/Core/Suggestions/SuggestedActions/EditorSuggestedAction.cs @@ -101,7 +101,7 @@ private async Task InvokeAsync() { try { - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.SuggestedAction_Application_Summary, $"Total"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.SuggestedAction_Application_Summary, $"Total"); using var token = SourceProvider.OperationListener.BeginAsyncOperation($"{nameof(EditorSuggestedAction)}.{nameof(Invoke)}"); using var context = SourceProvider.UIThreadOperationExecutor.BeginExecute( diff --git a/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource_Async.cs b/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource_Async.cs index 4ac3c48dd1789..ecd7cc98e71a3 100644 --- a/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource_Async.cs +++ b/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource_Async.cs @@ -104,14 +104,14 @@ await document.Project.Solution.Services.GetRequiredService> GetCodeFixesAsync() { - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.SuggestedAction_Summary, $"Total.Pri{priority.GetPriorityInt()}.{nameof(GetCodeFixesAsync)}"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.SuggestedAction_Summary, $"Total.Pri{priority.GetPriorityInt()}.{nameof(GetCodeFixesAsync)}"); if (owner._codeFixService == null || !supportsFeatureService.SupportsCodeFixes(target.SubjectBuffer) || @@ -235,7 +235,7 @@ await document.Project.Solution.Services.GetRequiredService> GetRefactoringsAsync() { - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.SuggestedAction_Summary, $"Total.Pri{priority.GetPriorityInt()}.{nameof(GetRefactoringsAsync)}"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.SuggestedAction_Summary, $"Total.Pri{priority.GetPriorityInt()}.{nameof(GetRefactoringsAsync)}"); if (!selection.HasValue) { diff --git a/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs b/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs index 94c468afe876f..08995922eee8f 100644 --- a/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs +++ b/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs @@ -95,11 +95,11 @@ private DiagnosticIdFilter GetShouldIncludeDiagnosticPredicate( public async Task GetMostSevereFixAsync( TextDocument document, TextSpan range, CodeActionRequestPriority? priority, CancellationToken cancellationToken) { - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.CodeFix_Summary, $"Pri{priority.GetPriorityInt()}.{nameof(GetMostSevereFixAsync)}"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.CodeFix_Summary, $"Pri{priority.GetPriorityInt()}.{nameof(GetMostSevereFixAsync)}"); ImmutableArray allDiagnostics; - using (RoslynTelemetry.RecordBlockTime( + using (RoslynTelemetry.Current.RecordBlockTime( FunctionId.CodeFix_Summary, $"Pri{priority.GetPriorityInt()}.{nameof(GetMostSevereFixAsync)}.{nameof(IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync)}")) { var service = document.Project.Solution.Services.GetRequiredService(); @@ -173,7 +173,7 @@ public async IAsyncEnumerable StreamFixesAsync( CodeActionRequestPriority? priority, [EnumeratorCancellation] CancellationToken cancellationToken) { - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.CodeFix_Summary, $"Pri{priority.GetPriorityInt()}"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.CodeFix_Summary, $"Pri{priority.GetPriorityInt()}"); // We only need to compute suppression/configuration fixes when request priority is // 'CodeActionPriorityRequest.Lowest' or no priority was provided at all (so all providers should run). @@ -191,7 +191,7 @@ public async IAsyncEnumerable StreamFixesAsync( // user-invoked diagnostic requests, for example, user invoked Ctrl + Dot operation for lightbulb. ImmutableArray diagnostics; - using (RoslynTelemetry.RecordBlockTime( + using (RoslynTelemetry.Current.RecordBlockTime( FunctionId.CodeFix_Summary, $"Pri{priority.GetPriorityInt()}.{nameof(IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync)}")) { var service = document.Project.Solution.Services.GetRequiredService(); @@ -286,7 +286,7 @@ private static SortedDictionary> ConvertToMap( { cancellationToken.ThrowIfCancellationRequested(); - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.CodeFix_Summary, $"{nameof(GetDocumentFixAllForIdInSpanAsync)}"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.CodeFix_Summary, $"{nameof(GetDocumentFixAllForIdInSpanAsync)}"); ImmutableArray diagnostics; if (textSpan is null) @@ -295,7 +295,7 @@ private static SortedDictionary> ConvertToMap( textSpan = new TextSpan(0, text.Length); } - using (RoslynTelemetry.RecordBlockTime( + using (RoslynTelemetry.Current.RecordBlockTime( FunctionId.CodeFix_Summary, $"{nameof(GetDocumentFixAllForIdInSpanAsync)}.{nameof(IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync)}")) { var service = document.Project.Solution.Services.GetRequiredService(); @@ -534,7 +534,7 @@ private async IAsyncEnumerable StreamFixesAsync( m[TelemetryKeys.LanguageName] = document.Project.Language; }, (fixerName, document)); - using var _ = RoslynTelemetry.LogBlockTime(FunctionId.CodeFix_Delay, logMessage, CodeFixTelemetryDelay); + using var _ = RoslynTelemetry.Current.LogBlockTime(FunctionId.CodeFix_Delay, logMessage, CodeFixTelemetryDelay); var codeFixCollection = await TryGetFixesOrConfigurationsAsync( document, span, diagnostics, fixAllForInSpan, fixer, diff --git a/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs b/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs index 5db5d1f9817d4..5d4c2f818a009 100644 --- a/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs @@ -183,7 +183,7 @@ public async Task> GetRefactoringsAsync( CodeActionRequestPriority? priority, CancellationToken cancellationToken) { - using (RoslynTelemetry.RecordBlockTime(FunctionId.CodeRefactoring_Summary, $"Pri{priority.GetPriorityInt()}")) + using (RoslynTelemetry.Current.RecordBlockTime(FunctionId.CodeRefactoring_Summary, $"Pri{priority.GetPriorityInt()}")) using (Logger.LogBlock(FunctionId.Refactoring_CodeRefactoringService_GetRefactoringsAsync, cancellationToken)) { using var _ = PooledDictionary.GetInstance(out var providerToIndex); @@ -213,7 +213,7 @@ public async Task> GetRefactoringsAsync( }, (providerName, document)); using (RoslynEventSource.LogInformationalBlock(FunctionId.Refactoring_CodeRefactoringService_GetRefactoringsAsync, providerName, cancellationToken)) - using (RoslynTelemetry.LogBlockTime(FunctionId.CodeRefactoring_Delay, logMessage, CodeRefactoringTelemetryDelay)) + using (RoslynTelemetry.Current.LogBlockTime(FunctionId.CodeRefactoring_Delay, logMessage, CodeRefactoringTelemetryDelay)) { var refactoring = await @this.GetRefactoringFromProviderAsync( document, state, provider, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_GetDiagnosticsForSpan.cs b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_GetDiagnosticsForSpan.cs index fa8deaf1224ee..4330ed9b6fd91 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_GetDiagnosticsForSpan.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_GetDiagnosticsForSpan.cs @@ -91,7 +91,7 @@ public async Task> GetDiagnosticsForSpanInProcess using var _2 = ArrayBuilder.GetInstance(out var semanticSpanBasedAnalyzers); using var _3 = ArrayBuilder.GetInstance(out var semanticDocumentBasedAnalyzers); - using var _4 = RoslynTelemetry.RecordBlockTime(FunctionId.RequestDiagnostics_Summary, $"Pri{priority.GetPriorityInt()}"); + using var _4 = RoslynTelemetry.Current.RecordBlockTime(FunctionId.RequestDiagnostics_Summary, $"Pri{priority.GetPriorityInt()}"); foreach (var analyzer in analyzers) { diff --git a/src/Features/Core/Portable/Diagnostics/Service/DocumentAnalysisExecutor.cs b/src/Features/Core/Portable/Diagnostics/Service/DocumentAnalysisExecutor.cs index 9d2a2c92195ea..8996351b2c725 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/DocumentAnalysisExecutor.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/DocumentAnalysisExecutor.cs @@ -216,7 +216,7 @@ async ValueTask> GetSyntaxDiagnosticsInProcessAsy if (_lazySyntaxDiagnostics == null) { - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.RequestDiagnostics_Summary, $"{nameof(GetSyntaxDiagnosticsInProcessAsync)}.{nameof(GetAnalysisResultInProcessAsync)}"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.RequestDiagnostics_Summary, $"{nameof(GetSyntaxDiagnosticsInProcessAsync)}.{nameof(GetAnalysisResultInProcessAsync)}"); var analysisScope = AnalysisScope.WithAnalyzers(_compilationBasedAnalyzersInAnalysisScope); var syntaxDiagnostics = await GetAnalysisResultInProcessAsync(analysisScope).ConfigureAwait(false); @@ -252,7 +252,7 @@ async ValueTask> GetSemanticDiagnosticsInProcessA if (_lazySemanticDiagnostics == null) { - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.RequestDiagnostics_Summary, $"{nameof(GetSemanticDiagnosticsInProcessAsync)}.{nameof(GetAnalysisResultInProcessAsync)}"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.RequestDiagnostics_Summary, $"{nameof(GetSemanticDiagnosticsInProcessAsync)}.{nameof(GetAnalysisResultInProcessAsync)}"); var analysisScope = AnalysisScope.WithAnalyzers(_compilationBasedAnalyzersInAnalysisScope); var semanticDiagnostics = await GetAnalysisResultInProcessAsync(analysisScope).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj index 76729a862d298..4d99e37d1f090 100644 --- a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj +++ b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj @@ -109,7 +109,6 @@ - diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.ProcessHost.UnitTests/Microsoft.CodeAnalysis.LanguageServer.ProcessHost.UnitTests.csproj b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.ProcessHost.UnitTests/Microsoft.CodeAnalysis.LanguageServer.ProcessHost.UnitTests.csproj index 3da8f26d5ace1..ce5943f87ff0f 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.ProcessHost.UnitTests/Microsoft.CodeAnalysis.LanguageServer.ProcessHost.UnitTests.csproj +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.ProcessHost.UnitTests/Microsoft.CodeAnalysis.LanguageServer.ProcessHost.UnitTests.csproj @@ -25,10 +25,6 @@ - - - - - - - diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/MiscellaneousFiles/CsprojInConeCheckerTests.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/MiscellaneousFiles/CsprojInConeCheckerTests.cs new file mode 100644 index 0000000000000..dd3717c9dfeb2 --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/MiscellaneousFiles/CsprojInConeCheckerTests.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.LanguageServer.FileBasedPrograms; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.FileBasedPrograms; + +public sealed class CsprojInConeCheckerTests : IDisposable +{ + private readonly TempRoot _tempRoot = new(); + + public void Dispose() + => _tempRoot.Dispose(); + + [Fact] + public void UsesCurrentWorkspaceFolders() + { + var initialWorkspace = _tempRoot.CreateDirectory(); + var projectWorkspace = _tempRoot.CreateDirectory(); + projectWorkspace.CreateFile("Project.csproj"); + var sourceFile = projectWorkspace.CreateDirectory("src").CreateFile("Program.cs"); + var initialFolder = CreateWorkspaceFolder(initialWorkspace.Path); + var projectFolder = CreateWorkspaceFolder(projectWorkspace.Path); + var tracker = new WorkspaceFolderTracker(); + tracker.Update([initialFolder], removedFolders: null); + var checker = new CsprojInConeChecker(tracker); + + Assert.False(checker.IsContainedInCsprojCone(sourceFile.Path)); + + tracker.Update([projectFolder], [initialFolder]); + + Assert.True(checker.IsContainedInCsprojCone(sourceFile.Path)); + } + + private static WorkspaceFolder CreateWorkspaceFolder(string path) + => new() + { + DocumentUri = ProtocolConversions.CreateAbsoluteDocumentUri(path), + Name = Path.GetFileName(path), + }; +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/MiscellaneousFiles/FileBasedProgramsEntryPointDiscoveryTests.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/MiscellaneousFiles/FileBasedProgramsEntryPointDiscoveryTests.cs index 72c4c7b86628a..c6898727cdb0f 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/MiscellaneousFiles/FileBasedProgramsEntryPointDiscoveryTests.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/MiscellaneousFiles/FileBasedProgramsEntryPointDiscoveryTests.cs @@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis.FileBasedPrograms; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServer.FileBasedPrograms; +using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -276,13 +277,48 @@ public async Task TestDiscovery_Option_EnableFileBasedPrograms_True() DeferDeleteCacheDirectory(testLspServer, tempDir.Path); var discovery = testLspServer.GetRequiredLspService(); - await discovery.FindAndLoadEntryPointsAsync(); + await discovery.FindAndLoadEntryPointsAsync(CancellationToken.None); await testLspServer.TestWorkspace.GetService().GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync(); var (workspace, document) = await GetRequiredLspWorkspaceAndDocumentAsync(CreateAbsoluteDocumentUri(appFile.Path), testLspServer); Assert.Equal(WorkspaceKind.Host, workspace.Kind); Assert.NotNull(document); } + [Fact] + public async Task TestDiscovery_WorkspaceFoldersChangedRefreshes() + { + var removedWorkspace = _tempRoot.CreateDirectory(); + var addedWorkspace = _tempRoot.CreateDirectory(); + + await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace: false, new InitializationOptions + { + ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer, + OptionUpdater = options => options.SetGlobalOption(LanguageServerProjectSystemOptionsStorage.EnableFileBasedPrograms, true), + WorkspaceFolders = + [ + new() { DocumentUri = CreateAbsoluteDocumentUri(removedWorkspace.Path), Name = "removed" } + ] + }); + DeferDeleteCacheDirectory(testLspServer, removedWorkspace.Path); + DeferDeleteCacheDirectory(testLspServer, addedWorkspace.Path); + await testLspServer.TestWorkspace.GetService().GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync(); + + var appText = "#!/usr/bin/env dotnet"; + var removedApp = removedWorkspace.CreateFile("Removed.cs").WriteAllText(appText); + var addedApp = addedWorkspace.CreateFile("Added.cs").WriteAllText(appText); + var workspaceFolderTracker = testLspServer.GetRequiredLspService(); + workspaceFolderTracker.Update( + [new() { DocumentUri = CreateAbsoluteDocumentUri(addedWorkspace.Path), Name = "added" }], + [new() { DocumentUri = CreateAbsoluteDocumentUri(removedWorkspace.Path), Name = "removed" }]); + + await testLspServer.TestWorkspace.GetService().GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync(); + + AssertDocumentNotPersisted(testLspServer, CreateAbsoluteDocumentUri(removedApp.Path)); + var (workspace, document) = await GetRequiredLspWorkspaceAndDocumentAsync(CreateAbsoluteDocumentUri(addedApp.Path), testLspServer); + Assert.Equal(WorkspaceKind.Host, workspace.Kind); + Assert.NotNull(document); + } + [Fact] public async Task TestDiscovery_Option_EnableFileBasedPrograms_False() { @@ -309,7 +345,7 @@ public async Task TestDiscovery_Option_EnableFileBasedPrograms_False() DeferDeleteCacheDirectory(testLspServer, tempDir.Path); var discovery = testLspServer.GetRequiredLspService(); - await discovery.FindAndLoadEntryPointsAsync(); + await discovery.FindAndLoadEntryPointsAsync(CancellationToken.None); await testLspServer.TestWorkspace.GetService().GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync(); AssertDocumentNotPersisted(testLspServer, CreateAbsoluteDocumentUri(appFile.Path)); } @@ -331,7 +367,7 @@ public async Task TestDiscovery_Option_EnableAutomaticDiscovery_False() await using var testLspServer = await CreateDiscoveryTestServerAsync(tempDir.Path); var discovery = testLspServer.GetRequiredLspService(); - await discovery.FindAndLoadEntryPointsAsync(); + await discovery.FindAndLoadEntryPointsAsync(CancellationToken.None); await testLspServer.TestWorkspace.GetService().GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync(); AssertDocumentNotPersisted(testLspServer, CreateAbsoluteDocumentUri(appFile.Path)); } diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/MiscellaneousFiles/FileBasedProgramsWorkspaceTests.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/MiscellaneousFiles/FileBasedProgramsWorkspaceTests.cs index 4dd3c4ec21418..5aa710d2c89f2 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/MiscellaneousFiles/FileBasedProgramsWorkspaceTests.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/MiscellaneousFiles/FileBasedProgramsWorkspaceTests.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Features.Workspaces; using Microsoft.CodeAnalysis.LanguageServer.FileBasedPrograms; using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.FileWatching; @@ -26,6 +27,44 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.FileBasedPrograms; public sealed class FileBasedProgramsWorkspaceTests(ITestOutputHelper testOutputHelper) : AbstractLspMiscellaneousFilesWorkspaceTests(testOutputHelper) { + [Fact] + public async Task GetOrLoadEntryPointDocument_NormalizesPath() + { + await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace: false, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }); + var projectSystem = (FileBasedProgramsProjectSystem)testLspServer.GetRequiredLspService(); + var sourceFile = CreateTempDirectoryWithGlobalJson().CreateFile("SomeFile.cs"); + var nonCanonicalPath = Path.Combine(Path.GetDirectoryName(sourceFile.Path)!, "directory", "..", Path.GetFileName(sourceFile.Path)); + var sourceText = SourceText.From("Console.WriteLine(\"Hello World!\");"); + var languageInformation = new LanguageInformation(LanguageNames.CSharp, "csx"); + + var documents = await projectSystem.GetOrLoadEntryPointDocumentAsync( + nonCanonicalPath, new SourceTextLoader(sourceText, nonCanonicalPath), languageInformation, SourceHashAlgorithms.Default, doDesignTimeBuild: false); + var document = Assert.Single(documents); + var documentsFromCanonicalPath = await projectSystem.GetOrLoadEntryPointDocumentAsync( + sourceFile.Path, new SourceTextLoader(sourceText, sourceFile.Path), languageInformation, SourceHashAlgorithms.Default, doDesignTimeBuild: false); + + Assert.Equal(sourceFile.Path, document.FilePath); + Assert.Equal(document.Id, Assert.Single(documentsFromCanonicalPath).Id); + } + + [ConditionalFact(typeof(WindowsOnly))] + public async Task GetOrLoadEntryPointDocument_MatchesPathCaseInsensitively() + { + await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace: false, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }); + var projectSystem = (FileBasedProgramsProjectSystem)testLspServer.GetRequiredLspService(); + var sourceFile = CreateTempDirectoryWithGlobalJson().CreateFile("SomeFile.cs"); + var sourceText = SourceText.From("Console.WriteLine(\"Hello World!\");"); + var languageInformation = new LanguageInformation(LanguageNames.CSharp, "csx"); + + var document = Assert.Single(await projectSystem.GetOrLoadEntryPointDocumentAsync( + sourceFile.Path, new SourceTextLoader(sourceText, sourceFile.Path), languageInformation, SourceHashAlgorithms.Default, doDesignTimeBuild: false)); + var pathWithDifferentCasing = sourceFile.Path.ToUpperInvariant(); + var documentsFromDifferentCasing = await projectSystem.GetOrLoadEntryPointDocumentAsync( + pathWithDifferentCasing, new SourceTextLoader(sourceText, pathWithDifferentCasing), languageInformation, SourceHashAlgorithms.Default, doDesignTimeBuild: false); + + Assert.Equal(document.Id, Assert.Single(documentsFromDifferentCasing).Id); + } + [Theory, CombinatorialData] public async Task TestFileBasedProgram_Simple(bool mutatingLspWorkspace) { @@ -108,6 +147,74 @@ public static class Util model.GetDiagnostics().Verify(); } + [Theory, CombinatorialData] + public async Task TestFileBasedProgram_PackageRestoreWithExplicitArtifactsPath(bool mutatingLspWorkspace) + { + var tempDir = TempRoot.CreateDirectory(); + tempDir.CreateFile("Directory.Build.props").WriteAllText(""" + + + $(MSBuildThisFileDirectory) + + + """); + + var sourceText = """ + #:package Newtonsoft.Json@13.0.4 + #:property PublishAot=false + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject("Hello World!")); + """; + var sourceFile = tempDir.CreateFile("App.cs").WriteAllText(sourceText); + var sourceFileUri = ProtocolConversions.CreateAbsoluteDocumentUri(sourceFile.Path); + + await using var testLspServer = await CreateTestLspServerAsync( + string.Empty, + mutatingLspWorkspace, + new InitializationOptions + { + ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer, + ClientCapabilities = new() + { + Workspace = new() + { + DidChangeWatchedFiles = new() { DynamicRegistration = true }, + }, + }, + }); + await testLspServer.OpenDocumentAsync(sourceFileUri, sourceText).ConfigureAwait(false); + await WaitForProjectLoad(sourceFileUri, testLspServer); + + // Simulate VS Code relaying the file change produced by dotnet restore. + var projectAssetsFile = Assert.Single( + Directory.EnumerateFiles(tempDir.Path, "project.assets.json", SearchOption.AllDirectories)); + var fileChangeTask = WaitForFileChangeAsync(projectAssetsFile, testLspServer); + await testLspServer.ExecuteNotificationAsync( + Methods.WorkspaceDidChangeWatchedFilesName, + new DidChangeWatchedFilesParams + { + Changes = + [ + new FileEvent + { + Uri = ProtocolConversions.CreateAbsoluteDocumentUri(projectAssetsFile), + FileChangeType = FileChangeType.Changed, + }, + ], + }); + await fileChangeTask; + await WaitForProjectLoad(sourceFileUri, testLspServer); + + var (workspace, document) = await GetRequiredLspWorkspaceAndDocumentAsync(sourceFileUri, testLspServer).ConfigureAwait(false); + Assert.Equal(WorkspaceKind.Host, workspace.Kind); + Assert.True(document.Project.State.HasAllInformation); + Assert.Contains( + document.Project.MetadataReferences, + static reference => StringComparer.OrdinalIgnoreCase.Equals(Path.GetFileName(reference.Display), "Newtonsoft.Json.dll")); + + var model = await document.GetRequiredSemanticModelAsync(CancellationToken.None); + model.GetDiagnostics().Verify(); + } + [Theory, CombinatorialData] public async Task TestDirectiveWithoutTopLevelStatements_IsMiscellaneousFile(bool mutatingLspWorkspace) { @@ -203,7 +310,7 @@ public async Task TestMultipleFileBasedPrograms_WithWorkspaceDiscovery(bool muta var globalOptions = testLspServer.TestWorkspace.ExportProvider.GetExportedValue(); globalOptions.SetGlobalOption(FileBasedAppsOptionsStorage.EnableAutomaticDiscovery, true); var discovery = testLspServer.GetRequiredLspService(); - await discovery.FindAndLoadEntryPointsAsync(); + await discovery.FindAndLoadEntryPointsAsync(CancellationToken.None); await testLspServer.TestWorkspace.GetService().GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync(); // Verify all FBAs loaded successfully in the host workspace. @@ -439,10 +546,11 @@ class C { } // Adding a top-level statement to a misc file causes it to report semantic errors. var textToInsert = $"""Console.WriteLine("Hello World!");{Environment.NewLine}"""; + var fileChangeTask = WaitForFileChangeAsync(sourceFile.Path, testLspServer); // Write updated content to disk so the project system can pick up the change. sourceFile.WriteAllText($"{textToInsert}{initialText}"); await testLspServer.InsertTextAsync(looseFileUriOne, (Line: 0, Column: 0, Text: textToInsert)); - await Task.Delay(100); + await fileChangeTask; await WaitForProjectLoad(looseFileUriOne, testLspServer); var (workspace, canonicalDocumentTwo) = await GetRequiredLspWorkspaceAndDocumentAsync(looseFileUriOne, testLspServer).ConfigureAwait(false); Assert.Equal(""" @@ -611,6 +719,23 @@ private async ValueTask WaitForProjectLoad(DocumentUri looseFileUri, TestLspServ await testLspServer.TestWorkspace.GetService().GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync(); } + /// + /// Call before triggering the change, then await the result before waiting for the project reload. + /// + private static async Task WaitForFileChangeAsync(string filePath, TestLspServer testLspServer) + { + var fileChangeWatcher = testLspServer.GetRequiredLspService(); + using var fileChangeContext = fileChangeWatcher.CreateContext([new WatchedDirectory(Path.GetDirectoryName(filePath)!, extensionFilters: [])]); + var fileChangeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + fileChangeContext.FileChanged += (_, e) => + { + if (PathUtilities.Comparer.Equals(e.FilePath, filePath)) + fileChangeTcs.TrySetResult(); + }; + + await fileChangeTcs.Task.WaitAsync(TestHelpers.HangMitigatingTimeout); + } + [Theory, CombinatorialData] public async Task TestFileWatchesReleasedOnServerShutdown(bool mutatingLspWorkspace) { @@ -868,22 +993,12 @@ await testLspServer.DeleteTextAsync(appCsUri, (workspace, document) = await GetRequiredLspWorkspaceAndDocumentAsync(appCsUri, testLspServer).ConfigureAwait(false); Assert.Equal(newAppCsText, (await document.GetTextAsync()).ToString()); - // Set up a listener for file change events before writing to disk, so we can wait for - // the FileSystemWatcher to deliver the event (which triggers the project reload enqueue). - var fileChangeWatcher = testLspServer.GetRequiredLspService(); - using var fileChangeContext = fileChangeWatcher.CreateContext([new WatchedDirectory(Path.GetDirectoryName(appCsFile.Path)!, extensionFilters: [])]); - var fileChangeTcs = new TaskCompletionSource(); - fileChangeContext.FileChanged += (_, e) => - { - if (e.FilePath.Equals(appCsFile.Path, StringComparison.OrdinalIgnoreCase)) - fileChangeTcs.TrySetResult(); - }; - + var fileChangeTask = WaitForFileChangeAsync(appCsFile.Path, testLspServer); // Flush the document change to disk to trigger a reload of the FBA project. appCsFile.WriteAllText(newAppCsText); // Wait for the file change event to be delivered, ensuring the reload is enqueued. - await fileChangeTcs.Task.WaitAsync(TestHelpers.HangMitigatingTimeout); + await fileChangeTask; await WaitForProjectLoad(appCsUri, testLspServer); // Now the document is a miscellaneous file @@ -1201,23 +1316,13 @@ internal class Util2 { } ); // Trivial edit+save of the App.cs file will trigger a reload though. - // Subscribe before writing to disk so the workspace waiter cannot finish before - // the FileSystemWatcher has delivered the event that enqueues the reload. - var fileChangeWatcher = testLspServer.GetRequiredLspService(); - using var fileChangeContext = fileChangeWatcher.CreateContext([new WatchedDirectory(Path.GetDirectoryName(appCsFile.Path)!, extensionFilters: [])]); - var fileChangeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - fileChangeContext.FileChanged += (_, e) => - { - if (PathUtilities.Comparer.Equals(e.FilePath, appCsFile.Path)) - fileChangeTcs.TrySetResult(); - }; - + var fileChangeTask = WaitForFileChangeAsync(appCsFile.Path, testLspServer); appCsFile.WriteAllText(appCsText + Environment.NewLine); var appCsSourceText = SourceText.From(appCsText); var appCsEndPosition = appCsSourceText.Lines.GetLinePosition(appCsSourceText.Length); await testLspServer.InsertTextAsync(appCsUri, (Line: appCsEndPosition.Line, Column: appCsEndPosition.Character, Text: Environment.NewLine)); - await fileChangeTcs.Task.WaitAsync(TestHelpers.HangMitigatingTimeout); + await fileChangeTask; await WaitForProjectLoad(appCsUri, testLspServer); // Get the Util.cs document again @@ -1282,7 +1387,7 @@ internal class Util { } var globalOptions = testLspServer.TestWorkspace.ExportProvider.GetExportedValue(); globalOptions.SetGlobalOption(FileBasedAppsOptionsStorage.EnableAutomaticDiscovery, true); var discovery = testLspServer.GetRequiredLspService(); - await discovery.FindAndLoadEntryPointsAsync(); + await discovery.FindAndLoadEntryPointsAsync(CancellationToken.None); await testLspServer.TestWorkspace.GetService().GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync(); // Even though the primary file was never opened in the editor, diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs index c54c5cbdf74be..5e6065a84df5e 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs @@ -22,12 +22,14 @@ public static Task CreateLanguageServerExportProviderAsync( return LanguageServerExportProviderBuilder.CreateExportProviderAsync(TestPaths.GetLanguageServerDirectory(), extensionManager, assemblyLoader, serverConfiguration, cacheDirectory, loggerFactory, CancellationToken.None); } - public static ExportProvider GetSharedExportProvider(ServerConfiguration serverConfiguration, ILoggerFactory loggerFactory) + public static ExportProvider GetSharedExportProvider(ServerConfiguration serverConfiguration, ILoggerFactory loggerFactory, params Type[] additionalParts) { Contract.ThrowIfTrue(serverConfiguration.ExtensionAssemblyPaths.Any(), "Tests that require extension assemblies should use AbstractLanguageServerMefHost instead"); - var exportProvider = serverConfiguration.DevKitDependencyPath != null - ? s_devKit.ExportProviderFactory.CreateExportProvider() - : s_languageServer.ExportProviderFactory.CreateExportProvider(); + var composition = serverConfiguration.DevKitDependencyPath != null ? s_devKit : s_languageServer; + if (additionalParts.Length > 0) + composition = composition.AddParts(additionalParts); + + var exportProvider = composition.ExportProviderFactory.CreateExportProvider(); LanguageServerExportProviderBuilder.TestAccessor.InitializeManualExports(exportProvider, new ExtensionAssemblyManager([], [], []), loggerFactory, serverConfiguration); return exportProvider; diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/CsprojInConeChecker.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/CsprojInConeChecker.cs index ca8a7aad7ae3f..de6b9638a268f 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/CsprojInConeChecker.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/CsprojInConeChecker.cs @@ -1,8 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; using System.Composition; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler; @@ -20,35 +19,28 @@ internal sealed class CsprojInConeCheckerFactory() : ILspServiceFactory { public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { - return new CsprojInConeChecker(); + return new CsprojInConeChecker(lspServices.GetRequiredService()); } } -internal sealed class CsprojInConeChecker : ILspService, IOnInitialized +internal sealed class CsprojInConeChecker(IWorkspaceFolderTracker workspaceFolderTracker) : ILspService { - private ImmutableArray _workspaceFolders; - - public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) - { - var initializeManager = context.GetRequiredService(); - _workspaceFolders = initializeManager.GetRequiredWorkspaceFolderPaths(); - return Task.CompletedTask; - } - public bool IsContainedInCsprojCone(string csFilePath) { // Note: manual perf testing of this check on Windows, in a reasonably complex case, // showed an overhead on the order of 100s of microseconds for this check. // If this overhead becomes problematic, we may want to put a cache in front of it. - Contract.ThrowIfTrue(_workspaceFolders.IsDefault, $"{nameof(OnInitializedAsync)} must be called before {nameof(IsContainedInCsprojCone)}."); - if (_workspaceFolders.IsEmpty) + // Bound the search to workspace folders so opening an arbitrary file outside the workspace does not + // discover and load an unrelated project from one of its ancestor directories. + var workspaceFolders = workspaceFolderTracker.GetRequiredWorkspaceFolderPaths(); + if (workspaceFolders.IsEmpty) return false; if (!PathUtilities.IsAbsolute(csFilePath)) return false; - foreach (var workspaceFolder in _workspaceFolders) + foreach (var workspaceFolder in workspaceFolders) { var directoryName = PathUtilities.GetDirectoryName(csFilePath); while (PathUtilities.IsSameDirectoryOrChildOf(child: directoryName, parent: workspaceFolder)) diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsEntryPointDiscovery.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsEntryPointDiscovery.cs index b23bdc7f74739..72b7f62fba72e 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsEntryPointDiscovery.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsEntryPointDiscovery.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -20,6 +20,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Threading; using Microsoft.Extensions.Logging; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; @@ -38,7 +39,8 @@ public ILspService CreateILspService(LspServices lspServices, WellKnownLspServer globalOptionService, listenerProvider.GetListener(FeatureAttribute.Workspace), lspServices.GetRequiredService().Workspace.Services.GetRequiredService(), - lspServices.GetRequiredService(), + lspServices.GetRequiredService().CreateLogger(), + lspServices.GetRequiredService(), lspServices); } } @@ -47,8 +49,9 @@ internal sealed partial class FileBasedProgramsEntryPointDiscovery( IGlobalOptionService globalOptionService, IAsynchronousOperationListener listener, IFileBasedProgramService fileBasedProgramService, - ILoggerFactory loggerFactory, - LspServices lspServices) : ILspService, IOnInitialized + ILogger logger, + IWorkspaceFolderTracker workspaceFolderTracker, + LspServices lspServices) : ILspService, IOnInitialized, IDisposable { private static readonly StringComparer s_pathComparer = StringComparer.OrdinalIgnoreCase; @@ -61,48 +64,56 @@ internal sealed partial class FileBasedProgramsEntryPointDiscovery( "node_modules" ], StringComparison.OrdinalIgnoreCase); - private readonly ILogger _logger = loggerFactory.CreateLogger(); - private ImmutableArray _workspaceFolders; + private readonly AsyncBatchingWorkQueue _discoveryQueue = new( + TimeSpan.Zero, + cancellationToken => FindAndLoadEntryPointsAsync(globalOptionService, fileBasedProgramService, workspaceFolderTracker, lspServices, logger, cancellationToken), + listener); public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { - var initializeManager = context.GetRequiredService(); - _workspaceFolders = initializeManager.GetRequiredWorkspaceFolderPaths(); - Task.Run(async () => - { - try - { - using var token = listener.BeginAsyncOperation(nameof(FindAndLoadEntryPointsAsync)); - await FindAndLoadEntryPointsAsync(); - } - catch (Exception ex) when (FatalError.ReportAndCatch(ex)) - { - throw ExceptionUtilities.Unreachable(); - } - }, cancellationToken); + workspaceFolderTracker.WorkspaceFoldersChanged += OnWorkspaceFoldersChanged; + _discoveryQueue.AddWork(); return Task.CompletedTask; } - internal async Task FindAndLoadEntryPointsAsync() + private void OnWorkspaceFoldersChanged(object? sender, EventArgs e) + => _discoveryQueue.AddWork(); + + public void Dispose() + { + workspaceFolderTracker.WorkspaceFoldersChanged -= OnWorkspaceFoldersChanged; + _discoveryQueue.Dispose(); + } + + internal ValueTask FindAndLoadEntryPointsAsync(CancellationToken cancellationToken) + => FindAndLoadEntryPointsAsync(globalOptionService, fileBasedProgramService, workspaceFolderTracker, lspServices, logger, cancellationToken); + + private static async ValueTask FindAndLoadEntryPointsAsync( + IGlobalOptionService globalOptionService, + IFileBasedProgramService fileBasedProgramService, + IWorkspaceFolderTracker workspaceFolderTracker, + LspServices lspServices, + ILogger logger, + CancellationToken cancellationToken) { - Contract.ThrowIfTrue(_workspaceFolders.IsDefault, $"{nameof(OnInitializedAsync)} must be called before {nameof(FindAndLoadEntryPointsAsync)}."); + var workspaceFolders = workspaceFolderTracker.GetRequiredWorkspaceFolderPaths(); - if (_workspaceFolders.IsEmpty) + if (workspaceFolders.IsEmpty) { - _logger.LogTrace("No workspace folders to search for file-based apps."); + logger.LogTrace("No workspace folders to search for file-based apps."); return; } if (!globalOptionService.GetOption(LanguageServerProjectSystemOptionsStorage.EnableFileBasedPrograms)) { - _logger.LogTrace(@"""dotnet.projects.enableFileBasedPrograms"" is false. Not discovering entry points."); + logger.LogTrace(@"""dotnet.projects.enableFileBasedPrograms"" is false. Not discovering entry points."); return; } if (!globalOptionService.GetOption(FileBasedAppsOptionsStorage.EnableAutomaticDiscovery)) { - _logger.LogTrace(@"""dotnet.fileBasedApps.enableAutomaticDiscovery"" is false. Not discovering entry points."); + logger.LogTrace(@"""dotnet.fileBasedApps.enableAutomaticDiscovery"" is false. Not discovering entry points."); return; } @@ -111,10 +122,13 @@ internal async Task FindAndLoadEntryPointsAsync() // Note: the overwhelmingly common case is when there is just one workspace folder. // For simplicity we orient our search around one workspace folder at a time. - foreach (var workspaceFolder in _workspaceFolders) + foreach (var workspaceFolder in workspaceFolders) { - foreach (var fileBasedAppPath in FindEntryPoints(workspaceFolder)) + cancellationToken.ThrowIfCancellationRequested(); + + foreach (var fileBasedAppPath in FindEntryPoints(workspaceFolder, fileBasedProgramService, logger)) { + cancellationToken.ThrowIfCancellationRequested(); await fileBasedProgramsProjectSystem.TryBeginLoadingFileBasedAppAsync(fileBasedAppPath); } } @@ -146,6 +160,9 @@ protected override bool ShouldIncludeEntry(ref FileSystemEntry entry) } internal ImmutableArray FindEntryPoints(string workspaceFolder) + => FindEntryPoints(workspaceFolder, fileBasedProgramService, logger); + + private static ImmutableArray FindEntryPoints(string workspaceFolder, IFileBasedProgramService fileBasedProgramService, ILogger logger) { var stopwatch = SharedStopwatch.StartNew(); var cacheDirectory = fileBasedProgramService.GetDiscoveryCacheDirectory(workspaceFolder); @@ -170,7 +187,7 @@ internal ImmutableArray FindEntryPoints(string workspaceFolder) } catch (Exception ex) { - _logger.LogDebug("Could not read cache file: {ex.Message}", ex.Message); + logger.LogDebug("Could not read cache file: {ex.Message}", ex.Message); } cache ??= new Cache(workspaceFolder, DateTimeOffset.MinValue, FileBasedAppFullPaths: [], DirectoriesContainingCsproj: []); @@ -193,10 +210,10 @@ internal ImmutableArray FindEntryPoints(string workspaceFolder) var newFileBasedAppsBuilder = ArrayBuilder.GetInstance(cache.FileBasedAppFullPaths.Length); var directoriesContainingCsprojBuilder = ArrayBuilder.GetInstance(cache.DirectoriesContainingCsproj.Length); - var visitor = new WorkspaceFolderVisitor(cache, newFileBasedAppsBuilder, directoriesContainingCsprojBuilder, _logger); + var visitor = new WorkspaceFolderVisitor(cache, newFileBasedAppsBuilder, directoriesContainingCsprojBuilder, logger); visitor.Visit(); var elapsedMilliseconds = Math.Round(stopwatch.Elapsed.TotalMilliseconds); - _logger.LogInformation("Finished discovery in '{workspaceFolder}' in {elapsedMilliseconds} milliseconds", workspaceFolder, elapsedMilliseconds); + logger.LogInformation("Finished discovery in '{workspaceFolder}' in {elapsedMilliseconds} milliseconds", workspaceFolder, elapsedMilliseconds); // Ensure items go into the cache file in a stable order. // This is useful for manual inspection and allows use of 'BinarySearch' to match directories against the cache. diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsProjectSystem.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsProjectSystem.cs index dd5417c55147c..a6ce15ba35b49 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsProjectSystem.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsProjectSystem.cs @@ -283,13 +283,14 @@ public async ValueTask TryBeginLoadingFileBasedAppAsync(string documentFilePath) public async ValueTask> GetOrLoadEntryPointDocumentAsync(string documentFilePath, TextLoader textLoader, LanguageInformation languageInformation, SourceHashAlgorithm checksumAlgorithm, bool doDesignTimeBuild) { + documentFilePath = NormalizeProjectPath(documentFilePath); var projects = await base.GetOrLoadProjectAsync(documentFilePath, _workspaceFactory.MiscellaneousFilesWorkspaceProjectFactory, CreatePrimordialProjectInfo, doDesignTimeBuild); return projects.Select(p => LookupExistingDocument(p)).WhereNotNull().ToImmutableArray(); TextDocument? LookupExistingDocument(Project project) { - var document = project.Documents.FirstOrDefault(document => document.FilePath == documentFilePath) - ?? project.AdditionalDocuments.FirstOrDefault(document => document.FilePath == documentFilePath); + var document = project.Documents.FirstOrDefault(document => PathUtilities.Comparer.Equals(document.FilePath, documentFilePath)) + ?? project.AdditionalDocuments.FirstOrDefault(document => PathUtilities.Comparer.Equals(document.FilePath, documentFilePath)); if (document is null) { _logger.LogWarning("Could not get a document for '{documentFilePath}' because its project doesn't contain a document for it", documentFilePath); @@ -298,11 +299,11 @@ public async ValueTask> GetOrLoadEntryPointDocument return document; } - ProjectInfo CreatePrimordialProjectInfo(ProjectSystemProjectFactory projectFactory) + ProjectInfo CreatePrimordialProjectInfo(ProjectSystemProjectFactory projectFactory, string normalizedDocumentFilePath) { var enableFileBasedPrograms = GlobalOptionService.GetOption(LanguageServerProjectSystemOptionsStorage.EnableFileBasedPrograms); return MiscellaneousFileUtilities.CreateMiscellaneousProjectInfoForDocument( - projectFactory.Workspace, documentFilePath, textLoader, languageInformation, checksumAlgorithm, projectFactory.Workspace.Services.SolutionServices, [], enableFileBasedPrograms); + projectFactory.Workspace, normalizedDocumentFilePath, textLoader, languageInformation, checksumAlgorithm, projectFactory.Workspace.Services.SolutionServices, [], enableFileBasedPrograms); } } diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.ProjectToLoad.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.ProjectToLoad.cs deleted file mode 100644 index 43ea6af1ec207..0000000000000 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.ProjectToLoad.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics.CodeAnalysis; - -namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; - -internal abstract partial class LanguageServerProjectLoader -{ - /// - /// The project path of the project to load. - /// - private sealed record ProjectToLoad(string Path, WorkDoneProgressTracker? ProgressTracker = null) - { - public static IEqualityComparer Comparer = new ProjectToLoadComparer(); - - private sealed class ProjectToLoadComparer : IEqualityComparer - { - public bool Equals(ProjectToLoad? x, ProjectToLoad? y) - { - return StringComparer.Ordinal.Equals(x?.Path, y?.Path); - } - - public int GetHashCode([DisallowNull] ProjectToLoad obj) - { - return StringComparer.Ordinal.GetHashCode(obj.Path); - } - } - } -} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs index aad7a7c31f8f6..5062d579bb3d2 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.ProjectSystem; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Threading; using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.CommonLanguageServerProtocol.Framework; @@ -25,7 +26,7 @@ internal abstract partial class LanguageServerProjectLoader : IAsyncDisposable { private static readonly string s_razorDesignTimePath = Path.Combine(AppContext.BaseDirectory, "Targets", "Microsoft.NET.Sdk.Razor.DesignTime.targets"); - private readonly AsyncBatchingWorkQueue _projectsToReload; + private readonly AsyncBatchingWorkQueue _projectsToReload; private bool _isDisposed; protected readonly LanguageServerWorkspaceFactory _workspaceFactory; @@ -55,7 +56,7 @@ internal abstract partial class LanguageServerProjectLoader : IAsyncDisposable /// instance is expected to be a no-op, since it's possible we might have had some scheduled asynchronous work /// (a design time build, a file change notification) that might have scheduled and could also be in flight. /// - private readonly Dictionary _loadedProjects = []; + private readonly Dictionary _loadedProjects = new(PathUtilities.Comparer); /// /// Indicates whether loads should report UI progress to the client for this loader. @@ -103,10 +104,10 @@ protected LanguageServerProjectLoader( AdditionalProperties = BuildAdditionalProperties(serverConfigurationFactory.ServerConfiguration); - _projectsToReload = new AsyncBatchingWorkQueue( + _projectsToReload = new AsyncBatchingWorkQueue( TimeSpan.FromMilliseconds(100), ReloadProjectsAsync, - ProjectToLoad.Comparer, + PathUtilities.Comparer, Listener); } @@ -144,7 +145,7 @@ public async Task ReportErrorAsync(LSP.MessageType errorKind, string message, Ca } } - private async ValueTask ReloadProjectsAsync(ImmutableSegmentedList projectsToLoadOrReload, CancellationToken cancellationToken) + private async ValueTask ReloadProjectsAsync(ImmutableSegmentedList projectsToLoadOrReload, CancellationToken cancellationToken) { // TODO: support configuration switching var stopwatch = Stopwatch.StartNew(); @@ -166,21 +167,14 @@ private async ValueTask ReloadProjectsAsync(ImmutableSegmentedList.RunParallelAsync( source: projectsToLoadOrReload, - produceItems: static async (projectToLoad, produceItem, args, cancellationToken) => + produceItems: static async (projectPath, produceItem, args, cancellationToken) => { var (@this, toastErrorReporter, buildHostProcessManager) = args; - try - { - var projectRestorePath = await @this.ReloadProjectAsync( - projectToLoad, toastErrorReporter, buildHostProcessManager, cancellationToken); - - if (projectRestorePath is not null) - produceItem(projectRestorePath); - } - finally - { - projectToLoad.ProgressTracker?.OnItemProcessed(); - } + var projectRestorePath = await @this.ReloadProjectAsync( + projectPath, toastErrorReporter, buildHostProcessManager, cancellationToken); + + if (projectRestorePath is not null) + produceItem(projectRestorePath); }, args: (@this: this, toastErrorReporter, buildHostProcessManager), cancellationToken).ConfigureAwait(false); @@ -223,10 +217,9 @@ internal sealed record RemoteProjectLoadResult => null; /// The project file path that needs a NuGet restore, if any. - private async Task ReloadProjectAsync(ProjectToLoad projectToLoad, ToastErrorReporter toastErrorReporter, BuildHostProcessManager buildHostProcessManager, CancellationToken cancellationToken) + private async Task ReloadProjectAsync(string projectPath, ToastErrorReporter toastErrorReporter, BuildHostProcessManager buildHostProcessManager, CancellationToken cancellationToken) { BuildHostProcessKind? preferredBuildHostKindThatWeDidNotGet = null; - var projectPath = projectToLoad.Path; LoadedProject? loadedProject; // Before doing any work, check if the project has already been unloaded @@ -246,7 +239,7 @@ internal sealed record RemoteProjectLoadResult // - Loading VB projects // - Reloading file-based app projects, where edits were performed to e.g. delete all `#:` directives, // making the file no longer a file-based app entry point. - _logger.LogDebug("Reload of '{projectPath}' was canceled.", projectPath); + _logger.LogDebug("Reload of '{ProjectPath}' was canceled.", projectPath); return null; } @@ -269,9 +262,7 @@ internal sealed record RemoteProjectLoadResult // language in-process. var projectLanguage = loadedProjectInfos.FirstOrDefault()?.Language; if (projectLanguage != null && projectFactory.Workspace.Services.GetLanguageService(projectLanguage) == null) - { return null; - } var applied = await loadedProject.TryApplyLoadedProjectInfosAsync( loadedProjectInfos, @@ -315,6 +306,10 @@ await loadedProject.ReportTelemetryIfNotPreviouslyReportedAsync( return null; } + finally + { + loadedProject.CompleteInitialLoad(); + } async Task LogDiagnosticsAsync(ImmutableArray diagnosticLogItems) { @@ -339,8 +334,9 @@ async Task LogDiagnosticsAsync(ImmutableArray diagnosticLogIt } } - protected async ValueTask> GetOrLoadProjectAsync(string projectPath, ProjectSystemProjectFactory primordialProjectFactory, Func createPrimordialProjectInfo, bool doDesignTimeBuild) + protected async ValueTask> GetOrLoadProjectAsync(string projectPath, ProjectSystemProjectFactory primordialProjectFactory, Func createPrimordialProjectInfo, bool doDesignTimeBuild) { + projectPath = NormalizeProjectPath(projectPath); using (await _gate.DisposableWaitAsync(CancellationToken.None)) { Contract.ThrowIfTrue(_isDisposed, "Project loader is already disposed"); @@ -348,7 +344,7 @@ protected async ValueTask> GetOrLoadProjectAsync(string if (_loadedProjects.TryGetValue(projectPath, out var existingLoadedProject)) return await existingLoadedProject.GetExistingProjectsAsync(); - var primordialProjectInfo = createPrimordialProjectInfo(primordialProjectFactory); + var primordialProjectInfo = createPrimordialProjectInfo(primordialProjectFactory, projectPath); var newLoadedProject = new LoadedProject(projectPath, _fileChangeWatcher); _loadedProjects.Add(projectPath, newLoadedProject); @@ -356,8 +352,12 @@ protected async ValueTask> GetOrLoadProjectAsync(string if (doDesignTimeBuild) { - _projectsToReload.AddWork(new ProjectToLoad(projectPath)); newLoadedProject.NeedsReload += LoadedProject_NeedsReload; + _projectsToReload.AddWork(newLoadedProject.ProjectFilePath); + } + else + { + newLoadedProject.CompleteInitialLoad(); } return [newProject]; @@ -367,8 +367,9 @@ protected async ValueTask> GetOrLoadProjectAsync(string /// /// Begins loading a project. If the project has already begun loading, returns without doing any additional work. /// - protected async Task BeginLoadingProjectAsync(string projectPath, string? projectGuid, WorkDoneProgressTracker? progressTracker = null) + internal async Task BeginLoadingProjectAsync(string projectPath) { + projectPath = NormalizeProjectPath(projectPath); LoadedProject? loadedProject; using (await _gate.DisposableWaitAsync(CancellationToken.None)) @@ -381,13 +382,9 @@ protected async Task BeginLoadingProjectAsync(string projectPath, string? projec loadedProject = new LoadedProject(projectPath, _fileChangeWatcher); _loadedProjects.Add(projectPath, loadedProject); - _projectsToReload.AddWork(new ProjectToLoad(Path: projectPath, progressTracker)); - loadedProject.NeedsReload += LoadedProject_NeedsReload; + _projectsToReload.AddWork(loadedProject.ProjectFilePath); } - - if (projectGuid is not null) - await loadedProject.SetProjectGuidForTelemetryAsync(Guid.Parse(projectGuid)); } // Try to load the contents from the project cache if we have one; we'll do this outside the lock @@ -414,6 +411,8 @@ await loadedProject.TryApplyLoadedProjectInfosAsync( { _logger.LogWarning(e, "Exception encountered while trying to load cached state for {ProjectPath}", projectPath); } + + return loadedProject; } protected void LoadedProject_NeedsReload(object? sender, string triggeringFilePath) @@ -421,11 +420,40 @@ protected void LoadedProject_NeedsReload(object? sender, string triggeringFilePa var loadedProject = (LoadedProject)sender!; _logger.LogTrace("Project {ProjectPath} needs reload due to change in {TriggeringFilePath}", loadedProject.ProjectFilePath, triggeringFilePath); - _projectsToReload.AddWork(new ProjectToLoad(Path: loadedProject.ProjectFilePath, ProgressTracker: null)); + _projectsToReload.AddWork(loadedProject.ProjectFilePath); } protected Task WaitForProjectsToFinishLoadingAsync() => _projectsToReload.WaitUntilCurrentBatchCompletesAsync(); + protected static async Task WaitForProjectLoadsAsync( + ImmutableArray loadedProjects, + WorkDoneProgressTracker? progressTracker = null, + CancellationToken cancellationToken = default) + { + await Task.WhenAll(loadedProjects.SelectAsArray(async loadedProject => + { + try + { + await loadedProject.WaitForLoadAsync(cancellationToken); + } + finally + { + progressTracker?.OnItemProcessed(); + } + })); + } + + internal async Task WaitForAllProjectLoadsAsync(CancellationToken cancellationToken) + { + ImmutableArray loadedProjects; + using (await _gate.DisposableWaitAsync(cancellationToken)) + { + loadedProjects = [.. _loadedProjects.Values]; + } + + await WaitForProjectLoadsAsync(loadedProjects, cancellationToken: cancellationToken); + } + /// Unloads all projects associated with this project loader. internal async ValueTask UnloadAllProjectsAsync() { @@ -457,6 +485,7 @@ public virtual async ValueTask DisposeAsync() internal async ValueTask TryUnloadProjectAsync(string projectPath, ProjectSystemProjectFactory? fromProjectFactory = null) { + projectPath = NormalizeProjectPath(projectPath); using (await _gate.DisposableWaitAsync(CancellationToken.None)) { if (!_loadedProjects.TryGetValue(projectPath, out var loadedProject)) @@ -476,4 +505,7 @@ internal async ValueTask TryUnloadProjectAsync(string projectPath, Project return true; } } + + protected static string NormalizeProjectPath(string projectPath) + => PathUtilities.IsAbsolute(projectPath) ? IOUtilities.PerformIO(() => Path.GetFullPath(projectPath), projectPath) : projectPath; } diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs index 4ecf2e35dbeb9..ecb7eb8b0f3c9 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs @@ -6,9 +6,11 @@ using System.Composition; using System.Runtime.InteropServices; using Microsoft.CodeAnalysis.FileBasedPrograms; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.CommonLanguageServerProtocol.Framework; @@ -159,12 +161,17 @@ public async Task OpenSolutionAsync(string solutionFilePath, IProgress(projects.Length); foreach (var (path, guid) in projects) { - await BeginLoadingProjectAsync(path, guid, progressTracker); + var loadedProject = await BeginLoadingProjectAsync(path); + if (guid is not null) + await loadedProject.SetProjectGuidForTelemetryAsync(Guid.Parse(guid)); + + loadedProjects.Add(loadedProject); } - await WaitForProjectsToFinishLoadingAsync(); + await WaitForProjectLoadsAsync(loadedProjects.MoveToImmutable(), progressTracker); await ProjectInitializationHandler.SendProjectInitializationCompleteNotificationAsync(_clientLanguageServerManager); } @@ -177,15 +184,24 @@ public async Task OpenProjectsAsync(ImmutableArray projectFilePaths, IPr ? new WorkDoneProgressTracker(progressReporter, projectFilePaths.Length) : null; + var loadedProjects = ImmutableArray.CreateBuilder(projectFilePaths.Length); foreach (var path in projectFilePaths) { - await BeginLoadingProjectAsync(NormalizeDriveLetter(path), projectGuid: null, progressTracker); + var loadedProject = await BeginLoadingProjectAsync(NormalizeDriveLetter(path)); + loadedProjects.Add(loadedProject); } - await WaitForProjectsToFinishLoadingAsync(); + await WaitForProjectLoadsAsync(loadedProjects.MoveToImmutable(), progressTracker, CancellationToken.None); await ProjectInitializationHandler.SendProjectInitializationCompleteNotificationAsync(_clientLanguageServerManager); } + internal ImmutableArray GetSupportedProjectFileExtensions() + { + var supportedLanguages = _hostProjectFactory.Workspace.Services.SolutionServices.GetSupportedLanguages(); + return _projectFileExtensionRegistry.GetRegisteredProjectFileExtensions().WhereAsArray( + extension => _projectFileExtensionRegistry.TryGetLanguageNameFromExtension(extension, out var languageName) && supportedLanguages.Contains(languageName)); + } + protected override async Task TryLoadProjectInMSBuildHostAsync( BuildHostProcessManager buildHostProcessManager, string projectPath, CancellationToken cancellationToken) { diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LoadedProject.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LoadedProject.cs index a0106aa7911ff..ba6b5cf3aac85 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LoadedProject.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LoadedProject.cs @@ -49,6 +49,7 @@ internal sealed partial class LoadedProject : IAsyncDisposable private readonly List _targets = []; private (ProjectSystemProjectFactory ProjectFactory, ProjectId Id)? _primordialProjectInfo; + private readonly TaskCompletionSource _initialLoadCompletionSource = new(TaskCreationOptions.RunContinuationsAsynchronously); private bool _reportedTelemetry = false; private Guid? _projectGuidForTelemetry = null; @@ -78,6 +79,26 @@ public LoadedProject(string projectFilePath, IFileChangeWatcher fileWatcher) /// public event EventHandler? NeedsReload; + /// + /// Waits for the initial project load to settle. + /// + /// + /// if the project remains loaded and contains a primordial project or at least one evaluated + /// target; otherwise, . + /// + public async ValueTask WaitForLoadAsync(CancellationToken cancellationToken) + { + await _initialLoadCompletionSource.Task.WaitAsync(cancellationToken); + + using (await _gate.DisposableWaitAsync(cancellationToken)) + { + return !_disposed && (_primordialProjectInfo.HasValue || _targets.Count > 0); + } + } + + public void CompleteInitialLoad() + => _initialLoadCompletionSource.TrySetResult(); + private void ProjectFileChangeContext_FileChanged(object? sender, FileChangedEventArgs e) { NeedsReload?.Invoke(this, e.FilePath); @@ -322,6 +343,8 @@ public async ValueTask DisposeAsync() if (_disposed) return; + _initialLoadCompletionSource.TrySetResult(); + _sourceFileCreatedOrDeletedChangeContext?.Dispose(); _projectFileChangeContext.Dispose(); @@ -340,6 +363,13 @@ public async ValueTask DisposeAsync() } } + internal TestAccessor GetTestAccessor() => new(this); + + internal readonly struct TestAccessor(LoadedProject loadedProject) + { + public void RaiseNeedsReload() => loadedProject.NeedsReload?.Invoke(loadedProject, loadedProject.ProjectFilePath); + } + private sealed class DocumentFileInfoComparer : IEqualityComparer { public static IEqualityComparer Instance = new DocumentFileInfoComparer(); diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/LanguageServerTelemetry.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/LanguageServerTelemetry.cs index ee6b51999b9fa..f4a4ee86e3fe4 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/LanguageServerTelemetry.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/LanguageServerTelemetry.cs @@ -91,8 +91,8 @@ public void InitializeSession(string telemetryLevel, string? sessionId, bool isD var metricSink = new VSMetricSink(session); _registrations = [ - RoslynTelemetry.AddEventSink(TelemetryEventSink.Create(session, logDelta: true)), - RoslynTelemetry.AddMetricSink(metricSink), + RoslynTelemetry.Current.AddEventSink(TelemetryEventSink.Create(session, logDelta: true)), + RoslynTelemetry.Current.AddMetricSink(metricSink), metricSink, ]; @@ -116,7 +116,7 @@ public void Dispose() if (_telemetrySession is { } session) { FeaturesSessionTelemetry.Report(); - RoslynTelemetry.Flush(); + RoslynTelemetry.Current.Flush(); foreach (var registration in _registrations) registration.Dispose(); diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/VSCodeRequestTelemetryLogger.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/VSCodeRequestTelemetryLogger.cs index 9e82a7bb38fb2..6919497428ff3 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/VSCodeRequestTelemetryLogger.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/VSCodeRequestTelemetryLogger.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -43,7 +43,7 @@ public static void ReportProjectLoadStarted() protected override void IncreaseFindDocumentCount(string workspaceCountMetricName) { var projectsLoaded = s_initialProjectLoadCompleted; - RoslynTelemetry.Count(FunctionId.LSP_FindDocumentInWorkspace, workspaceCountMetricName, 1, + RoslynTelemetry.Current.Count(FunctionId.LSP_FindDocumentInWorkspace, workspaceCountMetricName, 1, new("server", ServerTypeName), new("workspace", workspaceCountMetricName), new("projectsLoaded", projectsLoaded)); diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.cs.xlf b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.cs.xlf index a3a104715a398..e6e5f2eed33dc 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.cs.xlf +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.cs.xlf @@ -34,7 +34,7 @@ Completed (re)load of {0} project(s) in {1} - Completed (re)load of {0} project(s) in {1} + Dokončeno (opětovné) načtení {0} projektů v {1} The second placeholder is a time duration like 00:15 diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.de.xlf b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.de.xlf index a5bf084cf179b..d8c931db8ad5c 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.de.xlf +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.de.xlf @@ -34,7 +34,7 @@ Completed (re)load of {0} project(s) in {1} - Completed (re)load of {0} project(s) in {1} + (Neu-)Laden von {0} Projekt(en) in {1} abgeschlossen The second placeholder is a time duration like 00:15 diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.es.xlf b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.es.xlf index e6c76252bcb6f..446a928bc9f68 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.es.xlf +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.es.xlf @@ -34,7 +34,7 @@ Completed (re)load of {0} project(s) in {1} - Completed (re)load of {0} project(s) in {1} + Carga completada (re)de {0} proyectos en {1} The second placeholder is a time duration like 00:15 diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.fr.xlf b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.fr.xlf index e8e6f5665dd7c..75d9417cea16f 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.fr.xlf +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.fr.xlf @@ -34,7 +34,7 @@ Completed (re)load of {0} project(s) in {1} - Completed (re)load of {0} project(s) in {1} + (Re)chargement terminé de {0} projet(s) en {1} The second placeholder is a time duration like 00:15 diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.it.xlf b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.it.xlf index bdc14fa5d1846..3757d70856a7e 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.it.xlf +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.it.xlf @@ -34,7 +34,7 @@ Completed (re)load of {0} project(s) in {1} - Completed (re)load of {0} project(s) in {1} + Completato il (ri)caricamento di {0} progetti in {1} The second placeholder is a time duration like 00:15 diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ja.xlf b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ja.xlf index 63836da19ba6b..7827439a2125a 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ja.xlf +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ja.xlf @@ -34,7 +34,7 @@ Completed (re)load of {0} project(s) in {1} - Completed (re)load of {0} project(s) in {1} + {0} プロジェクトの (再) 読み込みを {1} に完了しました The second placeholder is a time duration like 00:15 diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ko.xlf b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ko.xlf index a2b1d269ec17d..081660751d64e 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ko.xlf +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ko.xlf @@ -34,7 +34,7 @@ Completed (re)load of {0} project(s) in {1} - Completed (re)load of {0} project(s) in {1} + {1} 만에 {0}개 프로젝트의 다시 로드를 완료함 The second placeholder is a time duration like 00:15 diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.pl.xlf b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.pl.xlf index fa446ff90881b..ae9bc013649c4 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.pl.xlf +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.pl.xlf @@ -34,7 +34,7 @@ Completed (re)load of {0} project(s) in {1} - Completed (re)load of {0} project(s) in {1} + Ukończono (ponowne) ładowanie projektów {0} w czasie {1} The second placeholder is a time duration like 00:15 diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.pt-BR.xlf b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.pt-BR.xlf index 0751014a1c3d9..5d77e37f86c34 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.pt-BR.xlf +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.pt-BR.xlf @@ -34,7 +34,7 @@ Completed (re)load of {0} project(s) in {1} - Completed (re)load of {0} project(s) in {1} + (Re)carregamento concluído de {0} projetos em {1} The second placeholder is a time duration like 00:15 diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ru.xlf b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ru.xlf index e0566bc01225e..88ad06e9b301d 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ru.xlf +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.ru.xlf @@ -34,7 +34,7 @@ Completed (re)load of {0} project(s) in {1} - Completed (re)load of {0} project(s) in {1} + Выполнена (повторно) загрузка {0} проектов в {1} The second placeholder is a time duration like 00:15 diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.tr.xlf b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.tr.xlf index 7ee191831b66b..d17a81a5ddd96 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.tr.xlf +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.tr.xlf @@ -34,7 +34,7 @@ Completed (re)load of {0} project(s) in {1} - Completed (re)load of {0} project(s) in {1} + {0} projenin (yeniden) yüklenmesi {1} içinde tamamlandı The second placeholder is a time duration like 00:15 diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.zh-Hans.xlf b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.zh-Hans.xlf index 33bb51e4ceda8..d436014c69730 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.zh-Hans.xlf +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.zh-Hans.xlf @@ -34,7 +34,7 @@ Completed (re)load of {0} project(s) in {1} - Completed (re)load of {0} project(s) in {1} + 已在 {0} 内完成 {1} 个项目的(重新)加载 The second placeholder is a time duration like 00:15 diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.zh-Hant.xlf b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.zh-Hant.xlf index f11c6a47ac6cc..462da9df44eda 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.zh-Hant.xlf +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/xlf/LanguageServerResources.zh-Hant.xlf @@ -34,7 +34,7 @@ Completed (re)load of {0} project(s) in {1} - Completed (re)load of {0} project(s) in {1} + 已在 {1} 內完成 {0} 個專案的重新載入 The second placeholder is a time duration like 00:15 diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/AnalyzerConfigFileFilterTests.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/AnalyzerConfigFileFilterTests.cs new file mode 100644 index 0000000000000..a05aa26bb8d2c --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/AnalyzerConfigFileFilterTests.cs @@ -0,0 +1,167 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; +using Moq; +using Xunit; + +namespace Microsoft.NET.ProjectData.Tasks.Tests; + +public class AnalyzerConfigFileFilterTests +{ + [Theory] + [InlineData("/Sdks/Microsoft.NET.Sdk/analyzers/build/config/analysislevel_10_default.globalconfig")] + [InlineData("/Sdks/Microsoft.NET.Sdk/analyzers/build/config/analysislevel_11_recommended_warnaserror.globalconfig")] + [InlineData("/Sdks/Microsoft.NET.Sdk/codestyle/cs/build/config/analysislevelstyle_default.globalconfig")] + public void IsSdkAnalyzerConfigFilePath_RecognizesSdkGlobalConfigs(string portablePath) + { + Assert.True(AnalyzerConfigFileFilter.IsSdkAnalyzerConfigFilePath(portablePath)); + } + + [Fact] + public void Prepare_FiltersSdkGlobalConfigsWhenPolicyIsAvailable() + { + string projectDir = Path.Combine(Path.GetTempPath(), "proj"); + (CachePathResolver resolver, string dotnetRoot) = MakeSyntheticResolver(projectDir); + string analysisConfig = Path.Combine(dotnetRoot, "sdk", "10.0.202", "Sdks", "Microsoft.NET.Sdk", "analyzers", "build", "config", "analysislevel_10_default.globalconfig"); + string styleConfig = Path.Combine(dotnetRoot, "sdk", "10.0.202", "Sdks", "Microsoft.NET.Sdk", "codestyle", "cs", "build", "config", "analysislevelstyle_default.globalconfig"); + string projectConfig = Path.Combine(projectDir, "Directory.Build.globalconfig"); + + List prepared = AnalyzerConfigFileFilter.Prepare( + [analysisConfig, styleConfig, projectConfig], + resolver, + sourceFiles: null, + filterSdkAnalyzerConfigFiles: true); + + Assert.Single(prepared); + Assert.Equal("Directory.Build.globalconfig", prepared[0]); + } + + [Fact] + public void Prepare_IgnoresNullSourceItems() + { + string projectDir = Path.Combine(Path.GetTempPath(), "proj"); + var resolver = new CachePathResolver(projectDir, [], [], null); + string projectConfig = Path.Combine(projectDir, "Directory.Build.globalconfig"); + + List prepared = AnalyzerConfigFileFilter.Prepare( + [projectConfig], + resolver, + sourceFiles: [null!, MakeItem(Path.Combine(projectDir, "Program.cs"))], + filterSdkAnalyzerConfigFiles: false); + + Assert.Equal(["Directory.Build.globalconfig"], prepared); + } + + [Fact] + public void Prepare_StopsAtNearestRootEditorConfig() + { + string tempDir = Path.Combine(Path.GetTempPath(), "lscache-editorconfig-root-" + Guid.NewGuid().ToString("N")); + try + { + string repoDir = Path.Combine(tempDir, "parent", "repo"); + string nestedWorktreeDir = Path.Combine(repoDir, "worktrees", "nested"); + string projectDir = Path.Combine(nestedWorktreeDir, "src", "App"); + Directory.CreateDirectory(projectDir); + + string parentConfig = Path.Combine(tempDir, "parent", ".editorconfig"); + string repoConfig = Path.Combine(repoDir, ".editorconfig"); + string nestedConfig = Path.Combine(nestedWorktreeDir, ".editorconfig"); + string projectConfig = Path.Combine(projectDir, ".editorconfig"); + string sourceFile = Path.Combine(projectDir, "Program.cs"); + string generatedConfig = Path.Combine(projectDir, "obj", "Debug", "net8.0", "App.GeneratedMSBuildEditorConfig.editorconfig"); + Directory.CreateDirectory(Path.GetDirectoryName(generatedConfig)!); + + File.WriteAllText(parentConfig, "[*.cs]\ndotnet_diagnostic.PARENT9999.severity = error\n"); + File.WriteAllText(repoConfig, "root = true\n\n[*.cs]\ndotnet_diagnostic.REPO0001.severity = warning\n"); + File.WriteAllText(nestedConfig, "root = true\n\n[*.cs]\ndotnet_diagnostic.NESTED0001.severity = silent\n"); + File.WriteAllText(projectConfig, "[*.cs]\ndotnet_diagnostic.PROJECT0001.severity = silent\n"); + File.WriteAllText(sourceFile, "Console.WriteLine(\"Hello\");\n"); + File.WriteAllText(generatedConfig, "is_global = true\n"); + + var resolver = new CachePathResolver(projectDir, [], [], null); + + List prepared = AnalyzerConfigFileFilter.Prepare( + [parentConfig, repoConfig, nestedConfig, projectConfig, generatedConfig], + resolver, + sourceFiles: [MakeItem(sourceFile)], + filterSdkAnalyzerConfigFiles: false); + + Assert.DoesNotContain(resolver.ToPortable(parentConfig), prepared); + Assert.DoesNotContain(resolver.ToPortable(repoConfig), prepared); + Assert.Contains(resolver.ToPortable(nestedConfig), prepared); + Assert.Contains(resolver.ToPortable(projectConfig), prepared); + Assert.Contains(resolver.ToPortable(generatedConfig), prepared); + } + finally + { + try { Directory.Delete(tempDir, recursive: true); } catch { } + } + } + + [Fact] + public void Prepare_KeepsAncestorEditorConfigForLinkedSourceOutsideRoot() + { + string tempDir = Path.Combine(Path.GetTempPath(), "lscache-linked-editorconfig-root-" + Guid.NewGuid().ToString("N")); + try + { + string parentDir = Path.Combine(tempDir, "parent"); + string repoDir = Path.Combine(parentDir, "repo"); + string nestedWorktreeDir = Path.Combine(repoDir, "worktrees", "nested"); + string projectDir = Path.Combine(nestedWorktreeDir, "src", "App"); + string linkedSourceDir = Path.Combine(parentDir, "shared"); + Directory.CreateDirectory(projectDir); + Directory.CreateDirectory(linkedSourceDir); + + string parentConfig = Path.Combine(parentDir, ".editorconfig"); + string repoConfig = Path.Combine(repoDir, ".editorconfig"); + string nestedConfig = Path.Combine(nestedWorktreeDir, ".editorconfig"); + string projectConfig = Path.Combine(projectDir, ".editorconfig"); + string projectSource = Path.Combine(projectDir, "Program.cs"); + string linkedSource = Path.Combine(linkedSourceDir, "Shared.cs"); + + File.WriteAllText(parentConfig, "[*.cs]\ndotnet_diagnostic.PARENT9999.severity = error\n"); + File.WriteAllText(repoConfig, "root = true\n\n[*.cs]\ndotnet_diagnostic.REPO0001.severity = warning\n"); + File.WriteAllText(nestedConfig, "root = true\n\n[*.cs]\ndotnet_diagnostic.NESTED0001.severity = silent\n"); + File.WriteAllText(projectConfig, "[*.cs]\ndotnet_diagnostic.PROJECT0001.severity = silent\n"); + File.WriteAllText(projectSource, "Console.WriteLine(\"Hello\");\n"); + File.WriteAllText(linkedSource, "public class Shared { }\n"); + + var resolver = new CachePathResolver(projectDir, [], [], null); + + List prepared = AnalyzerConfigFileFilter.Prepare( + [parentConfig, repoConfig, nestedConfig, projectConfig], + resolver, + sourceFiles: [MakeItem(projectSource), MakeItem(linkedSource)], + filterSdkAnalyzerConfigFiles: false); + + Assert.Contains(resolver.ToPortable(parentConfig), prepared); + Assert.DoesNotContain(resolver.ToPortable(repoConfig), prepared); + Assert.Contains(resolver.ToPortable(nestedConfig), prepared); + Assert.Contains(resolver.ToPortable(projectConfig), prepared); + } + finally + { + try { Directory.Delete(tempDir, recursive: true); } catch { } + } + } + + private static ITaskItem MakeItem(string identity) + { + var mock = new Mock(); + mock.Setup(i => i.ItemSpec).Returns(identity); + return mock.Object; + } + + private static (CachePathResolver Resolver, string DotNetRoot) MakeSyntheticResolver(string projectDir) + { + string dotnetRoot = Path.Combine(projectDir, "fakedotnet") + Path.DirectorySeparatorChar; + var resolver = new CachePathResolver( + projectDir: projectDir, + nugetFolders: [Path.Combine(projectDir, "fakenuget") + Path.DirectorySeparatorChar], + dotnetRoots: [dotnetRoot], + netFxRefRoot: null); + return (resolver, dotnetRoot); + } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/CachePathResolverTests.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/CachePathResolverTests.cs new file mode 100644 index 0000000000000..149b294cdc3dc --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/CachePathResolverTests.cs @@ -0,0 +1,274 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace Microsoft.NET.ProjectData.Tasks.Tests; + +public class CachePathResolverTests +{ + // Helper: create a resolver with explicit roots so tests are hermetic. + private static CachePathResolver Make( + string projectDir, + string[]? nugetFolders = null, + string[]? dotnetRoots = null, + string? netFxRefRoot = null) + => new CachePathResolver( + projectDir, + nugetFolders ?? [], + dotnetRoots ?? [], + netFxRefRoot); + + private static string TempPath(params string[] parts) + { + string path = Path.GetTempPath(); + foreach (string part in parts) + path = Path.Combine(path, part); + return path; + } + + [Fact] + public void ToPortable_NuGetPath_Substitutes() + { + string nuget = CachePathResolver.NormalizeFolderPath(Path.Combine(Path.GetTempPath(), "packages")); + CachePathResolver resolver = Make(@"C:\project", nugetFolders: [nuget]); + + string result = resolver.ToPortable(Path.Combine(nuget, "Newtonsoft.Json", "13.0.3", "lib", "netstandard2.0", "Newtonsoft.Json.dll").TrimEnd(Path.DirectorySeparatorChar)); + + Assert.StartsWith("/", result); + Assert.Contains("Newtonsoft.Json", result); + Assert.DoesNotContain("\\", result); + } + + [Fact] + public void ToPortable_DotNetRootPath_Substitutes() + { + string dotnet = CachePathResolver.NormalizeFolderPath(TempPath("dotnet")); + CachePathResolver resolver = Make(TempPath("project"), dotnetRoots: [dotnet]); + + string result = resolver.ToPortable(Path.Combine(dotnet, "shared", "Microsoft.NETCore.App", "8.0.0", "System.dll").TrimEnd(Path.DirectorySeparatorChar)); + + Assert.StartsWith("/", result); + Assert.Contains("System.dll", result); + } + + [Fact] + public void TryGetDotNetRootFromHostPath_ReturnsContainingDirectory() + { + string dotnetRoot = TempPath("dotnet"); + string hostPath = Path.Combine(dotnetRoot, OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); + + Assert.Equal(Path.GetFullPath(dotnetRoot), CachePathResolver.TryGetDotNetRootFromHostPath(hostPath)); + } + + [Fact] + public void TryGetDotNetRootFromHostPath_RejectsRelativePath() + { + Assert.Null(CachePathResolver.TryGetDotNetRootFromHostPath("dotnet")); + } + + [Fact] + public void TryGetDotNetRootFromSdkPath_ReturnsParentOfSdkDirectory() + { + string dotnetRoot = TempPath("dotnet"); + string sdkPath = Path.Combine(dotnetRoot, "sdk", "11.0.100"); + + Assert.Equal(Path.GetFullPath(dotnetRoot), CachePathResolver.TryGetDotNetRootFromSdkPath(sdkPath)); + } + + [Fact] + public void TryGetDotNetRootFromSdkPath_AcceptsMixedCaseSdkDirectory() + { + string dotnetRoot = TempPath("dotnet"); + string sdkPath = Path.Combine(dotnetRoot, "SdK", "11.0.100"); + + Assert.Equal(Path.GetFullPath(dotnetRoot), CachePathResolver.TryGetDotNetRootFromSdkPath(sdkPath)); + } + + [Fact] + public void TryGetDotNetRootFromSdkPath_RejectsUnrelatedDirectory() + { + Assert.Null(CachePathResolver.TryGetDotNetRootFromSdkPath(TempPath("project", "bin"))); + } + + [Fact] + public void ToPortable_ProjectRelativePath_ReturnsForwardSlashRelative() + { + string projectDir = TempPath("project", "src"); + CachePathResolver resolver = Make(projectDir); + + string result = resolver.ToPortable(Path.Combine(projectDir, "Program.cs")); + + Assert.Equal("Program.cs", result); + } + + [Fact] + public void ToPortable_ParentRelativePath_ReturnsDoubleDot() + { + string projectDir = TempPath("project", "src"); + CachePathResolver resolver = Make(projectDir); + + string result = resolver.ToPortable(TempPath("project", "Common.cs")); + + Assert.Equal("../Common.cs", result); + } + + [Fact] + public void MakePortable_NoAbsolutePath_Unchanged() + { + CachePathResolver resolver = Make(@"C:\project"); + + string result = resolver.MakePortable("/nologo"); + + Assert.Equal("/nologo", result); + } + + [Fact] + public void MakePortable_RelativeBackslashPath_NormalizedToForwardSlash() + { + // Csc emits relative /out: and /refout: arguments with backslashes on Windows. + // MakePortable must normalize them so the output is cross-platform identical. + CachePathResolver resolver = Make(@"C:\project"); + + string result = resolver.MakePortable(@"/out:obj\Debug\net8.0\MyApp.dll"); + + Assert.Equal("/out:obj/Debug/net8.0/MyApp.dll", result); + } + + [Fact] + public void MakePortable_RefOutBackslashPath_NormalizedToForwardSlash() + { + CachePathResolver resolver = Make(@"C:\project"); + + string result = resolver.MakePortable(@"/refout:obj\Debug\net8.0\refint\MyApp.dll"); + + Assert.Equal("/refout:obj/Debug/net8.0/refint/MyApp.dll", result); + } + + [Fact] + public void MakePortable_PlainBackslashText_NormalizedToForwardSlash() + { + CachePathResolver resolver = Make(@"C:\project"); + + string result = resolver.MakePortable(@"some\relative\path.txt"); + + Assert.Equal("some/relative/path.txt", result); + } + + [Fact] + public void MakePortable_EmbeddedNuGetPath_Substitutes() + { + string nuget = CachePathResolver.NormalizeFolderPath(TempPath("Users", "user", ".nuget", "packages")); + CachePathResolver resolver = Make(TempPath("project"), nugetFolders: [nuget]); + + string result = resolver.MakePortable("/doc:" + Path.Combine(nuget, "foo", "1.0", "lib", "net8.0", "foo.xml").TrimEnd(Path.DirectorySeparatorChar)); + + Assert.StartsWith("/doc:/", result); + } + + [Fact] + public void MakePortable_WindowsDrivePath_EmitsPathSentinel() + { + CachePathResolver resolver = Make(@"C:\project"); + + string result = resolver.MakePortable(@"-out:C:\project\bin\Debug\app.dll"); + + Assert.StartsWith("-out:", result); + Assert.Contains("bin/Debug/app.dll", result); + } + + [Fact] + public void FindSharedDirPrefix_CommonDir_ReturnsWithTrailingSlash() + { + string? prefix = CachePathResolver.FindSharedDirPrefix( + "/newtonsoft.json/13.0.3/lib/net8.0/Newtonsoft.Json.dll", + "/newtonsoft.json/13.0.3/lib/net8.0/Newtonsoft.Json.xml"); + + Assert.Equal("/newtonsoft.json/13.0.3/lib/net8.0/", prefix); + } + + [Fact] + public void FindSharedDirPrefix_NoDirInCommon_ReturnsNull() + { + string? prefix = CachePathResolver.FindSharedDirPrefix( + "/foo/1.0/lib.dll", + "/shared/app.dll"); + + Assert.Null(prefix); + } + + [Fact] + public void MakeRelative_SubDir_ReturnsRelative() + { + string result = CachePathResolver.MakeRelative(@"C:\project\src", @"C:\project\src\Program.cs"); + Assert.Equal("Program.cs", result); + } + + [Fact] + public void MakeRelative_ParentDir_ReturnsDoubleDot() + { + string result = CachePathResolver.MakeRelative(@"C:\project\src", @"C:\project\Shared.cs"); + Assert.Equal("../Shared.cs", result); + } + + [Fact] + public void ToPortable_DotnetSdkPath_RewritesToNetSdk() + { + string dotnet = CachePathResolver.NormalizeFolderPath(Path.Combine(Path.GetTempPath(), "dotnet")); + CachePathResolver resolver = Make(@"C:\project", dotnetRoots: [dotnet]); + + string analyzer = Path.Combine(dotnet, "sdk", "10.0.202", "Sdks", "Microsoft.NET.Sdk", "analyzers", "Microsoft.CodeAnalysis.NetAnalyzers.dll").TrimEnd(Path.DirectorySeparatorChar); + string result = resolver.ToPortable(analyzer); + + // Expect the version segment dropped — anyone reading the cache binds the + // SDK version they care about and the resolver expands against it. + Assert.Equal("/Sdks/Microsoft.NET.Sdk/analyzers/Microsoft.CodeAnalysis.NetAnalyzers.dll", result); + } + + [Fact] + public void ToPortable_DotnetNonSdkPath_StaysUnderDotnetSentinel() + { + // /packs/... and /host/... must NOT be rewritten — they aren't + // the per-version SDK content the rewrite is targeting. + string dotnet = CachePathResolver.NormalizeFolderPath(Path.Combine(Path.GetTempPath(), "dotnet")); + CachePathResolver resolver = Make(@"C:\project", dotnetRoots: [dotnet]); + + string pack = Path.Combine(dotnet, "packs", "Microsoft.NETCore.App.Ref", "10.0.7", "ref", "net10.0", "System.Runtime.dll").TrimEnd(Path.DirectorySeparatorChar); + string result = resolver.ToPortable(pack); + + Assert.StartsWith("/packs/", result); + Assert.DoesNotContain("", result); + } + + [Fact] + public void MakePortable_EmbeddedDotnetSdkPath_RewritesToNetSdk() + { + // Property values and command-line args may embed an absolute SDK path + // (e.g. /globalconfig:/sdk/10.0.202/...). The embedded form + // must be rewritten the same way as standalone paths. + string dotnet = CachePathResolver.NormalizeFolderPath(Path.Combine(Path.GetTempPath(), "dotnet")); + CachePathResolver resolver = Make(@"C:\project", dotnetRoots: [dotnet]); + + string analyzerCfg = Path.Combine(dotnet, "sdk", "10.0.202", "Sdks", "Microsoft.NET.Sdk", "analyzers", "build", "config", "analysislevel_10_default.globalconfig"); + string text = "/globalconfig:" + analyzerCfg; + string result = resolver.MakePortable(text); + + Assert.Equal("/globalconfig:/Sdks/Microsoft.NET.Sdk/analyzers/build/config/analysislevel_10_default.globalconfig", result); + } + + [Fact] + public void RewriteSdkPath_NonSdkPortable_LeavesAlone() + { + Assert.Equal("/packs/Foo/1.0/file", CachePathResolver.RewriteSdkPath("/packs/Foo/1.0/file")); + Assert.Equal("/foo", CachePathResolver.RewriteSdkPath("/foo")); + Assert.Equal("anything", CachePathResolver.RewriteSdkPath("anything")); + } + + [Fact] + public void RewriteSdkPath_SdkPortable_DropsVersion() + { + Assert.Equal("/Sdks/X/Y.dll", + CachePathResolver.RewriteSdkPath("/sdk/10.0.202/Sdks/X/Y.dll")); + } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/ForwardCompatTests.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/ForwardCompatTests.cs new file mode 100644 index 0000000000000..696f975dba2fb --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/ForwardCompatTests.cs @@ -0,0 +1,1096 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text; +using System.Text.RegularExpressions; +using Xunit; + +namespace Microsoft.NET.ProjectData.Tasks.Tests; + +/// +/// Tests for forward/backward compatibility of the .lscache format: an older writer that +/// re-generates a file authored by a newer minor version must carry the unknown data through +/// losslessly (so the file does not churn between team members), while the reader simply ignores it. +/// +/// The tests deliberately use a that injects fake +/// unknown sections / properties / item metadata and a fake higher minor version. There is +/// no real forward-compat field yet, so this is the only way to validate the machinery — and it is +/// the durable harness a future field-addition reuses: when a field becomes "known", flip it out of +/// the builder's unknown set and these same tests guard the transition. +/// +public class ForwardCompatTests +{ + private const int CurrentMajor = 2; + + // --- Reusable fake-future fixture ------------------------------------------------------------ + + /// + /// Synthesizes single- and multi-target .lscache content with optional forward-compatible + /// (unknown-to-the-current-writer) additions. Lines are assembled explicitly so leading-space + /// significant constructs (indentation-compressed paths, @metadata) stay byte-exact. + /// + internal static class FutureCacheBuilder + { + public const string UnknownSectionName = "futureSection"; + public const string UnknownSectionPayload = "future-section-payload"; + public const string UnknownProperty = "FutureProp=onward"; + public const string UnknownMetadata = "@futureMeta=42"; + + /// A self-consistent single-target cache body (headerless, LF-terminated). + public static string SingleTarget( + string version = "version=2", + bool unknownSection = false, + bool unknownProperty = false, + bool unknownMetadata = false) + { + var lines = new List + { + version, + string.Empty, + "[project]", + "project=Sample.csproj", + "language=C#", + "lastDtbSucceeded", + string.Empty, + "[properties]", + "AssemblyName=Sample", + }; + if (unknownProperty) lines.Add(UnknownProperty); + lines.Add("TargetFramework=net8.0"); + lines.Add(string.Empty); + lines.Add("[sourceFiles]"); + lines.Add("Program.cs"); + if (unknownMetadata) lines.Add(" " + UnknownMetadata); + lines.Add("Helpers/"); + lines.Add(" Util.cs"); + if (unknownSection) + { + lines.Add(string.Empty); + lines.Add("[" + UnknownSectionName + "]"); + lines.Add(UnknownSectionPayload); + } + + return string.Join("\n", lines) + "\n"; + } + } + + // Replaces the leading version header line with , mirroring how + // a newer-minor writer stamps a file that carries data this (older) writer does not understand. + private static string BumpPrimaryVersion(string text, string newVersionLine) + { + int nl = text.IndexOf('\n'); + return nl < 0 ? newVersionLine : newVersionLine + text.Substring(nl); + } + + // --- ForwardCompat.PreserveUnknownData: unit tests ------------------------------------------- + + [Fact] + public void Preserves_Unknown_Section() + { + string existing = FutureCacheBuilder.SingleTarget(version: "version=2.3", unknownSection: true); + string candidate = FutureCacheBuilder.SingleTarget(); + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + Assert.Contains("[" + FutureCacheBuilder.UnknownSectionName + "]", merged); + Assert.Contains(FutureCacheBuilder.UnknownSectionPayload, merged); + // Known content is untouched. + Assert.Contains("AssemblyName=Sample", merged); + Assert.Contains("Helpers/\n Util.cs", merged); + } + + // Multiple unknown sections must round-trip in the newer writer's file order, NOT be re-sorted. + // Re-sorting would move sections relative to where the newer minor put them, so an older minor + // and a newer minor would fight over the layout and churn the file on every alternating write. + // Encounter order here is z-before-a (the reverse of ordinal), so a stray re-sort would flip it. + [Fact] + public void Preserves_Unknown_Sections_In_File_Order_Not_Resorted() + { + string existing = string.Join("\n", new[] + { + "version=2.3", + "", + "[project]", + "project=Sample.csproj", + "language=C#", + "lastDtbSucceeded", + "", + "[zSection]", + "z-payload", + "", + "[aSection]", + "a-payload", + }) + "\n"; + string candidate = FutureCacheBuilder.SingleTarget(); + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + int z = merged.IndexOf("[zSection]", StringComparison.Ordinal); + int a = merged.IndexOf("[aSection]", StringComparison.Ordinal); + Assert.True(z >= 0 && a >= 0, "both unknown sections must be preserved"); + Assert.True(z < a, "unknown sections must keep their original file order, not be re-sorted"); + } + + [Fact] + public void Preserves_Unknown_Section_Between_Known_Sections_In_Same_Gap() + { + string existing = string.Join("\n", new[] + { + "version=2.3", + "", + "[project]", + "project=Sample.csproj", + "language=C#", + "", + "[properties]", + "AssemblyName=Sample", + "", + "[futureCopyToOutputItems]", + "content.txt", + "", + "[projectReferences]", + "Referenced.csproj", + "", + "[capabilities]", + "CSharp", + }) + "\n"; + string candidate = string.Join("\n", new[] + { + "version=2", + "", + "[project]", + "project=Sample.csproj", + "language=C#", + "", + "[properties]", + "AssemblyName=Sample", + "", + "[projectReferences]", + "Referenced.csproj", + "", + "[capabilities]", + "CSharp", + }) + "\n"; + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + int properties = merged.IndexOf("[properties]", StringComparison.Ordinal); + int unknown = merged.IndexOf("[futureCopyToOutputItems]", StringComparison.Ordinal); + int projectReferences = merged.IndexOf("[projectReferences]", StringComparison.Ordinal); + int capabilities = merged.IndexOf("[capabilities]", StringComparison.Ordinal); + Assert.True(properties >= 0 && unknown >= 0 && projectReferences >= 0 && capabilities >= 0, "all sections must be present"); + Assert.True(properties < unknown, "the unknown section should remain after its preceding known section"); + Assert.True(unknown < projectReferences, "the unknown section should remain before its following known section instead of moving to the segment end"); + Assert.True(projectReferences < capabilities, "later known sections should not move ahead of the preserved unknown section's original gap"); + } + + [Fact] + public void Preserves_Multiple_Unknown_Sections_In_Same_Gap_In_File_Order() + { + string existing = string.Join("\n", new[] + { + "version=2.3", + "", + "[project]", + "project=Sample.csproj", + "language=C#", + "", + "[properties]", + "AssemblyName=Sample", + "", + "[futureCopyToOutputItems]", + "content.txt", + "", + "[futureUpToDateCheckBuilt]", + "bin/Debug/Sample.dll", + "", + "[projectReferences]", + "Referenced.csproj", + "", + "[capabilities]", + "CSharp", + }) + "\n"; + string candidate = string.Join("\n", new[] + { + "version=2", + "", + "[project]", + "project=Sample.csproj", + "language=C#", + "", + "[properties]", + "AssemblyName=Sample", + "", + "[projectReferences]", + "Referenced.csproj", + "", + "[capabilities]", + "CSharp", + }) + "\n"; + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + int copyToOutputItems = merged.IndexOf("[futureCopyToOutputItems]", StringComparison.Ordinal); + int upToDateCheckBuilt = merged.IndexOf("[futureUpToDateCheckBuilt]", StringComparison.Ordinal); + int projectReferences = merged.IndexOf("[projectReferences]", StringComparison.Ordinal); + Assert.True(copyToOutputItems >= 0 && upToDateCheckBuilt >= 0 && projectReferences >= 0, "all sections must be present"); + Assert.True(copyToOutputItems < upToDateCheckBuilt, "unknown sections in the same gap must keep their original encounter order"); + Assert.True(upToDateCheckBuilt < projectReferences, "all unknown sections in the gap should stay before the following known section"); + } + + [Fact] + public void Preserves_Unknown_Section_Before_First_Known_Section_In_PerSliceSegment() + { + string existing = string.Join("\n", new[] + { + "version=2.3", + "[project]", + "language=C#", + "", + "[sliceDimensions]", + "TargetFramework=net8.0", + "---", + "[futureSection]", + "future-payload", + "", + "[project]", + "language=C#", + "", + "[sliceDimensions]", + "TargetFramework=net9.0", + }) + "\n"; + string candidate = string.Join("\n", new[] + { + "version=2", + "[project]", + "language=C#", + "", + "[sliceDimensions]", + "TargetFramework=net8.0", + "---", + "[project]", + "language=C#", + "", + "[sliceDimensions]", + "TargetFramework=net9.0", + }) + "\n"; + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + int separator = merged.IndexOf("---", StringComparison.Ordinal); + int futureSection = merged.IndexOf("[futureSection]", StringComparison.Ordinal); + int secondProject = merged.IndexOf("[project]", separator, StringComparison.Ordinal); + int secondProjectLanguage = merged.IndexOf("language=C#", secondProject, StringComparison.Ordinal); + Assert.True(separator >= 0 && futureSection >= 0 && secondProject >= 0 && secondProjectLanguage >= 0, "the second slice and unknown section must be present"); + Assert.True(separator < futureSection, "the unknown section should be inserted after the slice separator"); + Assert.True(futureSection < secondProject, "the unknown section should stay before the following known section header"); + Assert.True(secondProject < secondProjectLanguage, "the known section's content should stay under its own header"); + } + + // The last item in the file carries unknown @metadata AND there is an unknown whole section to + // append. Both anchor at the file's last content line; the metadata must stay attached to its + // item, with the appended section AFTER it — otherwise the @metadata would re-parse under the + // appended section on the next read (wrong item/section). + [Fact] + public void Preserves_Last_Item_Metadata_Before_Appended_Unknown_Section() + { + string existing = string.Join("\n", new[] + { + "version=2.1", + "", + "[project]", + "project=Sample.csproj", + "language=C#", + "lastDtbSucceeded", + "", + "[sourceFiles]", + "Program.cs", + " @futureMeta=42", + "", + "[futureSection]", + "future-payload", + }) + "\n"; + string candidate = string.Join("\n", new[] + { + "version=2", + "", + "[project]", + "project=Sample.csproj", + "language=C#", + "lastDtbSucceeded", + "", + "[sourceFiles]", + "Program.cs", + }) + "\n"; + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + int meta = merged.IndexOf("@futureMeta=42", StringComparison.Ordinal); + int section = merged.IndexOf("[futureSection]", StringComparison.Ordinal); + Assert.True(meta >= 0 && section >= 0, "both the metadata and the unknown section must be preserved"); + Assert.True(meta < section, "the item's @metadata must stay before the appended unknown section"); + Assert.Contains("Program.cs\n @futureMeta=42", merged); + } + + // Two unknown @metadata lines on one item, in non-ordinal order (@zMeta before @aMeta). A newer + // writer emits them in its own order; an older writer must preserve that file order, not re-sort + // by content (which would churn the cache in mixed-version teams). + [Fact] + public void Preserves_Unknown_Metadata_In_File_Order_Not_Resorted() + { + string existing = string.Join("\n", new[] + { + "version=2.1", + "", + "[project]", + "project=Sample.csproj", + "language=C#", + "lastDtbSucceeded", + "", + "[sourceFiles]", + "Program.cs", + " @zMeta=1", + " @aMeta=2", + }) + "\n"; + string candidate = string.Join("\n", new[] + { + "version=2", + "", + "[project]", + "project=Sample.csproj", + "language=C#", + "lastDtbSucceeded", + "", + "[sourceFiles]", + "Program.cs", + }) + "\n"; + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + int z = merged.IndexOf("@zMeta=1", StringComparison.Ordinal); + int a = merged.IndexOf("@aMeta=2", StringComparison.Ordinal); + Assert.True(z >= 0 && a >= 0, "both unknown metadata lines must be preserved"); + Assert.True(z < a, "unknown metadata must keep its original file order, not be re-sorted"); + } + + [Fact] + public void Preserves_Unknown_Property_In_Sorted_Position() + { + string existing = FutureCacheBuilder.SingleTarget(version: "version=2.3", unknownProperty: true); + string candidate = FutureCacheBuilder.SingleTarget(); + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + // Inserted into the already-sorted [properties] block between AssemblyName and TargetFramework. + Assert.Contains("AssemblyName=Sample\nFutureProp=onward\nTargetFramework=net8.0", merged); + } + + // Regression: when the candidate has NO [properties] section and the chosen anchor section is + // header-only, its LastLineIndex is -1. Reassemble drops insertions keyed at -1, so the created + // [properties] block must anchor on the header line instead — otherwise the preserved unknown + // property is silently lost. + [Fact] + public void Preserves_Unknown_Property_When_Candidate_Lacks_Properties_And_Anchor_Is_HeaderOnly() + { + string existing = string.Join("\n", new[] + { + "version=2.1", + "", + "[project]", + "project=Sample.csproj", + "language=C#", + "lastDtbSucceeded", + "", + "[properties]", + "FutureProp=onward", + }) + "\n"; + // Degenerate candidate: a header-only [project] (LastLineIndex == -1) and no [properties]. + string candidate = "version=2\n\n[project]\n"; + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + Assert.Contains("[properties]", merged); + Assert.Contains("FutureProp=onward", merged); + } + + // Defensive: a candidate segment with no sections at all must neither throw (the old code indexed + // Sections[Count - 1]) nor drop the preserved property (no anchor → segment last content line). + [Fact] + public void Preserves_Unknown_Property_When_Candidate_Has_No_Sections() + { + string existing = string.Join("\n", new[] + { + "version=2.1", + "", + "[properties]", + "FutureProp=onward", + }) + "\n"; + string candidate = "version=2\n"; + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + Assert.Contains("FutureProp=onward", merged); + } + + [Fact] + public void Preserves_Unknown_Metadata_On_Matching_Item() + { + string existing = FutureCacheBuilder.SingleTarget(version: "version=2.3", unknownMetadata: true); + string candidate = FutureCacheBuilder.SingleTarget(); + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + // Reattached at metadata indentation (one space) directly under Program.cs. + Assert.Contains("Program.cs\n @futureMeta=42", merged); + } + + [Fact] + public void Drops_Unknown_Metadata_When_Item_Removed_From_Candidate() + { + // Existing has @futureMeta on a source file the candidate no longer lists. + string existing = string.Join("\n", new[] + { + "version=2.3", + string.Empty, + "[project]", + "language=C#", + string.Empty, + "[sourceFiles]", + "Gone.cs", + " @futureMeta=42", + }) + "\n"; + string candidate = string.Join("\n", new[] + { + "version=2", + string.Empty, + "[project]", + "language=C#", + string.Empty, + "[sourceFiles]", + "Program.cs", + }) + "\n"; + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + Assert.DoesNotContain("@futureMeta", merged); + Assert.DoesNotContain("Gone.cs", merged); + } + + [Fact] + public void Does_Not_Resurrect_Known_Data_The_Current_Build_Did_Not_Produce() + { + // The cross-environment hazard the preservation design must NOT introduce: env A built the + // project, so its cache lists a generated source (Generated.g.cs) and a known [properties] + // key (RootNamespace). Env B regenerates the cache WITHOUT those outputs — e.g. the project + // was not (fully) built there, so the generated file and the property are legitimately + // absent. Both are data the current writer KNOWS HOW TO EMIT, so the freshly generated + // candidate is authoritative. Preservation only carries forward data the writer CANNOT + // regenerate; it must never splice known-but-currently-absent values back in, or it would + // mask the fact that env B's build produced nothing and resurrect stale outputs. + string existing = string.Join("\n", new[] + { + "version=2", + string.Empty, + "[project]", + "project=Sample.csproj", + "language=C#", + "lastDtbSucceeded", + string.Empty, + "[properties]", + "AssemblyName=Sample", + "RootNamespace=Sample.App", // known property present in A, absent in B + "TargetFramework=net8.0", + string.Empty, + "[sourceFiles]", + "Program.cs", + "Generated.g.cs", // known item: a generated output present only in A + }) + "\n"; + string candidate = string.Join("\n", new[] + { + "version=2", + string.Empty, + "[project]", + "project=Sample.csproj", + "language=C#", + "lastDtbSucceeded", + string.Empty, + "[properties]", + "AssemblyName=Sample", + "TargetFramework=net8.0", + string.Empty, + "[sourceFiles]", + "Program.cs", + }) + "\n"; + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + // Nothing the current writer can emit is resurrected: the candidate wins verbatim (same + // reference, since there is no forward-compatible data to splice). + Assert.Same(candidate, merged); + Assert.DoesNotContain("Generated.g.cs", merged); + Assert.DoesNotContain("RootNamespace", merged); + } + + [Fact] + public void Preserves_Known_Metadata_Name_Reused_On_A_Different_Item_Type() + { + // Forward-compat hazard the per-section metadata gate exists to close: a newer minor reuses + // an EXISTING metadata name (@link — today emitted only on [sourceFiles]/Compile items) on a + // DIFFERENT item type ([metadataReferences]/MetadataReference, which never emits @link). The + // older writer does not produce @link there, so it must treat it as unknown and carry it + // forward. A flattened "known metadata" union would mistake it for regenerable data and drop + // it (resurfacing the exact churn this feature prevents). + string existing = string.Join("\n", new[] + { + "version=2.4", + string.Empty, + "[project]", + "project=Sample.csproj", + "language=C#", + string.Empty, + "[metadataReferences]", + "Ref.dll", + " @link=carried/forward", + }) + "\n"; + string candidate = string.Join("\n", new[] + { + "version=2", + string.Empty, + "[project]", + "project=Sample.csproj", + "language=C#", + string.Empty, + "[metadataReferences]", + "Ref.dll", + }) + "\n"; + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + Assert.Contains("Ref.dll\n @link=carried/forward", merged); + } + + [Fact] + public void Drops_Metadata_That_Is_Known_For_The_Section() + { + // The flip side of the test above: @aliases IS known for [metadataReferences], so an existing + // @aliases the candidate did not regenerate is data the writer KNOWS HOW TO EMIT and must not + // resurrect (the candidate is authoritative — the reference legitimately has no alias now). + string existing = string.Join("\n", new[] + { + "version=2.4", + string.Empty, + "[project]", + "project=Sample.csproj", + "language=C#", + string.Empty, + "[metadataReferences]", + "Ref.dll", + " @aliases=Old", + }) + "\n"; + string candidate = string.Join("\n", new[] + { + "version=2", + string.Empty, + "[project]", + "project=Sample.csproj", + "language=C#", + string.Empty, + "[metadataReferences]", + "Ref.dll", + }) + "\n"; + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + Assert.DoesNotContain("@aliases", merged); + } + + [Fact] + public void Ignores_Unknown_Data_When_Existing_Major_Differs() + { + string existing = FutureCacheBuilder.SingleTarget(version: "version=3.0", unknownSection: true, unknownProperty: true, unknownMetadata: true); + string candidate = FutureCacheBuilder.SingleTarget(); + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + // A different major is an incompatible format: nothing is preserved and the candidate is + // returned unchanged (the writer then overwrites the file wholesale). + Assert.Same(candidate, merged); + } + + [Fact] + public void Returns_Same_Reference_When_Nothing_To_Preserve() + { + string existing = FutureCacheBuilder.SingleTarget(); + string candidate = FutureCacheBuilder.SingleTarget(); + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + Assert.Same(candidate, merged); + } + + [Fact] + public void Carries_Forward_Higher_Minor_Version_Stamp() + { + // Existing is a newer minor with no other unknown data; the stamp must be carried forward so + // the file does not flip-flop minors as different versions open it. + string existing = FutureCacheBuilder.SingleTarget(version: "version=2.5"); + string candidate = FutureCacheBuilder.SingleTarget(version: "version=2"); + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + Assert.StartsWith("version=2.5\n", merged); + } + + [Fact] + public void Does_Not_Downgrade_Version_Stamp() + { + // Existing is an OLDER (or equal) minor: keep the candidate's own stamp. + string existing = FutureCacheBuilder.SingleTarget(version: "version=2", unknownSection: true); + string candidate = FutureCacheBuilder.SingleTarget(version: "version=2"); + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + Assert.StartsWith("version=2\n", merged); + } + + [Theory] + // existing newer minor, same major → gate open (preserve can carry data forward) + [InlineData("version=2.4", "version=2", 2, true)] + [InlineData("version=2.1", "version=2.0", 2, true)] + // same or older minor → gate closed (nothing this writer can't already produce) + [InlineData("version=2", "version=2", 2, false)] + [InlineData("version=2.0", "version=2", 2, false)] + [InlineData("version=2", "version=2.4", 2, false)] + // present-but-unorderable existing minor (multi-part / non-numeric) → gate OPEN, conservatively, + // so a newer writer that stamps a non-integer minor never causes us to skip preservation + [InlineData("version=2.0.5", "version=2", 2, true)] + [InlineData("version=2.3-preview", "version=2", 2, true)] + // different / unparseable major → gate closed (incompatible or no header) + [InlineData("version=3.0", "version=2", 2, false)] + [InlineData("garbage", "version=2", 2, false)] + public void ExistingHasNewerMinor_MirrorsPreservationGate(string existingVersion, string candidateVersion, int currentMajor, bool expected) + { + // The byte-level pre-check must agree with PreserveUnknownData's own version gate: it opens + // (returns true) for exactly — and only — the existing-newer-minor, same-major case. + byte[] existing = Encoding.UTF8.GetBytes(FutureCacheBuilder.SingleTarget(version: existingVersion)); + byte[] candidate = Encoding.UTF8.GetBytes(FutureCacheBuilder.SingleTarget(version: candidateVersion)); + + bool gate = ForwardCompat.ExistingHasNewerMinor(existing, candidate, currentMajor); + + Assert.Equal(expected, gate); + + // Soundness: the gate must never be more restrictive than the actual transform. Whenever the + // gate is closed, PreserveUnknownData must return the candidate unchanged (no data to carry). + if (!gate) + { + string existingText = Encoding.UTF8.GetString(existing); + string candidateText = Encoding.UTF8.GetString(candidate); + Assert.Same(candidateText, ForwardCompat.PreserveUnknownData(existingText, candidateText, currentMajor)); + } + } + + [Fact] + public void ExistingHasNewerMinor_SkipsLeadingCommentLines() + { + // A leading comment line must not hide the version header from the byte probe. + byte[] existing = Encoding.UTF8.GetBytes("# leading comment\nversion=2.4\n"); + byte[] candidate = Encoding.UTF8.GetBytes(FutureCacheBuilder.SingleTarget(version: "version=2")); + + Assert.True(ForwardCompat.ExistingHasNewerMinor(existing, candidate, CurrentMajor)); + } + + [Fact] + public void ExistingHasNewerMinor_SkipsLeadingLegacyHashLine() + { + // A legacy committed cache still leads with a hash= header before version=. The byte probe + // must skip it (like the reader) so a newer-minor legacy file is still recognized for + // preservation instead of being treated as an unparseable header (which would drop data). + byte[] existing = Encoding.UTF8.GetBytes( + "hash=0000000000000000000000000000000000000000000000000000000000000000\nversion=2.4\n"); + byte[] candidate = Encoding.UTF8.GetBytes(FutureCacheBuilder.SingleTarget(version: "version=2")); + + Assert.True(ForwardCompat.ExistingHasNewerMinor(existing, candidate, CurrentMajor)); + } + + [Fact] + public void Preserves_All_Levels_Together_Deterministically() + { + string existing = FutureCacheBuilder.SingleTarget(version: "version=2.4", unknownSection: true, unknownProperty: true, unknownMetadata: true); + string candidate = FutureCacheBuilder.SingleTarget(); + + string first = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + // Feeding the merged output back in as the existing file is a no-op: byte-stable, no churn. + string second = ForwardCompat.PreserveUnknownData(first, candidate, CurrentMajor); + + Assert.Equal(first, second); + Assert.Contains("[futureSection]", first); + Assert.Contains("FutureProp=onward", first); + Assert.Contains("Program.cs\n @futureMeta=42", first); + Assert.StartsWith("version=2.4\n", first); + } + + [Fact] + public void Preserves_Unknown_Data_When_Existing_Minor_Is_Malformed() + { + // A newer writer that stamps a non-integer / multi-part minor (e.g. "2.0.5", "2.3-preview") + // must not cause an older writer to silently drop its unknown data. The gate treats an + // unorderable minor conservatively (preserve), so the future data survives. + string existing = FutureCacheBuilder.SingleTarget(version: "version=2.0.5", unknownSection: true, unknownProperty: true); + string candidate = FutureCacheBuilder.SingleTarget(version: "version=2"); + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + Assert.Contains("[" + FutureCacheBuilder.UnknownSectionName + "]", merged); + Assert.Contains(FutureCacheBuilder.UnknownProperty, merged); + } + + [Fact] + public void Preserves_PerSlice_Metadata_On_The_Correct_Slice_Only() + { + // Multi-TFM attribution: both slices list the same item path (Program.cs) but carry DISTINCT + // unknown metadata. Segments are matched by their [sliceDimensions] identity, so each slice's + // forward-compat metadata must land back on THAT slice's item — never bleed across slices, + // even though the item paths collide. + string slice(string tfm, string? meta) => string.Join("\n", new[] + { + "[project]", + "language=C#", + string.Empty, + "[sliceDimensions]", + $"TargetFramework={tfm}", + string.Empty, + "[sourceFiles]", + "Program.cs", + }.Concat(meta is null ? Array.Empty() : new[] { " " + meta })); + + string existing = "version=2.4\n" + slice("net8.0", "@futureMetaA=8") + "\n---\n" + slice("net9.0", "@futureMetaB=9") + "\n"; + string candidate = "version=2\n" + slice("net8.0", null) + "\n---\n" + slice("net9.0", null) + "\n"; + + string merged = ForwardCompat.PreserveUnknownData(existing, candidate, CurrentMajor); + + // Each metadatum appears exactly once, attached under its own slice. + int metaACount = Regex.Matches(merged, "@futureMetaA").Count; + int metaBCount = Regex.Matches(merged, "@futureMetaB").Count; + Assert.Equal(1, metaACount); + Assert.Equal(1, metaBCount); + + int net8 = merged.IndexOf("TargetFramework=net8.0", StringComparison.Ordinal); + int net9 = merged.IndexOf("TargetFramework=net9.0", StringComparison.Ordinal); + int metaA = merged.IndexOf("@futureMetaA", StringComparison.Ordinal); + int metaB = merged.IndexOf("@futureMetaB", StringComparison.Ordinal); + + // @futureMetaA belongs to the net8.0 segment, @futureMetaB to the net9.0 segment. + Assert.InRange(metaA, net8, net9); + Assert.True(metaB > net9); + } + + // --- End-to-end through the writer (single-TFM path) ----------------------------------------- + + [Fact] + public void AtomicWriteStreamed_PreservesUnknownData_AndStripsLegacyHash() + { + string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputPath = Path.Combine(dir, "test.lscache"); + try + { + Directory.CreateDirectory(dir); + + // On disk: a file a NEWER minor authored, still carrying a legacy hash header. + string newer = FutureCacheBuilder.SingleTarget(version: "version=2.4", unknownSection: true, unknownProperty: true, unknownMetadata: true); + File.WriteAllText(outputPath, $"hash={new string('0', 64)}\n{newer}", new UTF8Encoding(false)); + + // The current (older) writer regenerates the candidate it knows about. + string candidate = FutureCacheBuilder.SingleTarget(); + ProjectDataWriter.AtomicWriteStreamed(outputPath, w => w.Write(candidate)); + + string result = File.ReadAllText(outputPath); + + Assert.DoesNotContain("hash=", result); + Assert.StartsWith("version=2.4\n", result); // newer minor stamp carried forward + Assert.Contains("[futureSection]", result); + Assert.Contains("FutureProp=onward", result); + Assert.Contains("Program.cs\n @futureMeta=42", result); + Assert.Contains("AssemblyName=Sample", result); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public void AtomicWriteStreamed_IsNoOp_OnRepeatedWrite_WithPreservedData() + { + string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputPath = Path.Combine(dir, "test.lscache"); + try + { + Directory.CreateDirectory(dir); + string newer = FutureCacheBuilder.SingleTarget(version: "version=2.4", unknownSection: true, unknownProperty: true, unknownMetadata: true); + File.WriteAllText(outputPath, $"hash={new string('0', 64)}\n{newer}", new UTF8Encoding(false)); + + string candidate = FutureCacheBuilder.SingleTarget(); + ProjectDataWriter.AtomicWriteStreamed(outputPath, w => w.Write(candidate)); // migrates + preserves + DateTime afterMigration = File.GetLastWriteTimeUtc(outputPath); + + Thread.Sleep(1100); + ProjectDataWriter.AtomicWriteStreamed(outputPath, w => w.Write(candidate)); // must skip + + Assert.Equal(afterMigration, File.GetLastWriteTimeUtc(outputPath)); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public void AtomicWriteStreamed_OverwritesIncompatibleMajor() + { + string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputPath = Path.Combine(dir, "test.lscache"); + try + { + Directory.CreateDirectory(dir); + string future = FutureCacheBuilder.SingleTarget(version: "version=3.0", unknownSection: true); + File.WriteAllText(outputPath, future, new UTF8Encoding(false)); + + string candidate = FutureCacheBuilder.SingleTarget(); + ProjectDataWriter.AtomicWriteStreamed(outputPath, w => w.Write(candidate)); + + string result = File.ReadAllText(outputPath); + Assert.Equal(candidate, result); + Assert.DoesNotContain("futureSection", result); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + // --- End-to-end through the merger (multi-TFM path) ------------------------------------------ + + [Fact] + public void Merge_PreservesUnknownData_FromExistingMergedFile() + { + string dir = Path.Combine(Path.GetTempPath(), "lscache-fc-" + Guid.NewGuid().ToString("N")); + try + { + Directory.CreateDirectory(dir); + + string net8Dir = Path.Combine(dir, "obj", "Debug", "net8.0"); + string net9Dir = Path.Combine(dir, "obj", "Debug", "net9.0"); + Directory.CreateDirectory(net8Dir); + Directory.CreateDirectory(net9Dir); + + // Both slices share Program.cs and a shared property, so the merged file has a shared block. + string slice(string tfm) => string.Join("\n", new[] + { + "[project]", + "language=C#", + "[sliceDimensions]", + $"TargetFramework={tfm}", + "[properties]", + "AssemblyName=Sample", + "[sourceFiles]", + "Program.cs", + }) + "\n"; + File.WriteAllText(Path.Combine(net8Dir, "Sample.csproj.slice"), slice("net8.0")); + File.WriteAllText(Path.Combine(net9Dir, "Sample.csproj.slice"), slice("net9.0")); + + string outPath = Path.Combine(dir, "out.lscache"); + + // First merge to capture the canonical merged shape, then inject unknown data into the + // shared block AND bump the stamp to a newer minor — exactly how a newer writer would + // have authored this file (forward-compatible data always rides a newer-minor stamp). + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice"), "net8.0;net9.0"); + string merged = File.ReadAllText(outPath).Replace("\r\n", "\n"); + + string newer = BumpPrimaryVersion(merged, "version=2.4") + .Replace("AssemblyName=Sample", "AssemblyName=Sample\nFutureProp=onward") + .Replace("Program.cs\n", "Program.cs\n @futureMeta=42\n"); + File.WriteAllText(outPath, newer, new UTF8Encoding(false)); + + // Re-merge with the current (older) writer: it must carry the unknown data forward. + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice"), "net8.0;net9.0"); + string result = File.ReadAllText(outPath).Replace("\r\n", "\n"); + + Assert.Contains("FutureProp=onward", result); + Assert.Contains("Program.cs\n @futureMeta=42", result); + Assert.DoesNotContain("hash=", result); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public void Merge_PreservesUnknownSection_InPerTargetSlice() + { + string dir = Path.Combine(Path.GetTempPath(), "lscache-fc-" + Guid.NewGuid().ToString("N")); + try + { + Directory.CreateDirectory(dir); + string net8Dir = Path.Combine(dir, "obj", "Debug", "net8.0"); + string net9Dir = Path.Combine(dir, "obj", "Debug", "net9.0"); + Directory.CreateDirectory(net8Dir); + Directory.CreateDirectory(net9Dir); + + // Distinct command-line arguments keep the slices from collapsing into a shared block, + // so each TargetFramework gets its own segment. + string slice(string tfm, string arg) => string.Join("\n", new[] + { + "[project]", + "language=C#", + "[sliceDimensions]", + $"TargetFramework={tfm}", + "[commandLineArguments]", + arg, + }) + "\n"; + File.WriteAllText(Path.Combine(net8Dir, "Sample.csproj.slice"), slice("net8.0", "/define:NET8")); + File.WriteAllText(Path.Combine(net9Dir, "Sample.csproj.slice"), slice("net9.0", "/define:NET9")); + + string outPath = Path.Combine(dir, "out.lscache"); + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice"), "net8.0;net9.0"); + string merged = File.ReadAllText(outPath).Replace("\r\n", "\n"); + + // Inject an unknown section AND bump the stamp to a newer minor — forward-compatible data + // is only ever authored by a newer-minor writer, which stamps the file accordingly. + int net9Index = merged.IndexOf("TargetFramework=net9.0", StringComparison.Ordinal); + Assert.True(net9Index >= 0); + string newer = BumpPrimaryVersion(merged, "version=2.4") + "\n[futureSection]\nfuture-section-payload\n"; + File.WriteAllText(outPath, newer, new UTF8Encoding(false)); + + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice"), "net8.0;net9.0"); + string result = File.ReadAllText(outPath).Replace("\r\n", "\n"); + + Assert.Contains("[futureSection]", result); + Assert.Contains("future-section-payload", result); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + // --- Guard: the generated .props allow-list must match ProjectProperties.All ---------------- + + [Fact] + public void GeneratedProps_Match_ProjectProperties_All() + { + // The writer's [properties] allow-list is every schema property, generated into BOTH + // ProjectProperties.All (C#, consumed here and by forward-compat) and the committed + // Microsoft.NET.ProjectData.Schema.props (_ProjectDataProperties, consumed by MSBuild). They + // are generated from the same schema by different generators (C# source generator vs the Node + // script), so this guard fails the build if one was regenerated and the other was not — i.e. + // it catches a stale committed .props. + string propsPath = Path.Combine(AppContext.BaseDirectory, "Microsoft.NET.ProjectData.Schema.props"); + Assert.True(File.Exists(propsPath), $"Generated props file not found at {propsPath}."); + + string props = File.ReadAllText(propsPath); + var fromProps = new HashSet( + Regex.Matches(props, "_ProjectDataProperties Include=\"(?[^\"]+)\"") + .Select(m => m.Groups["name"].Value), + StringComparer.Ordinal); + + var all = new HashSet(ProjectProperties.All, StringComparer.Ordinal); + + Assert.NotEmpty(fromProps); + Assert.True( + fromProps.SetEquals(all), + "Microsoft.NET.ProjectData.Schema.props is out of sync with ProjectProperties.All. " + + "Regenerate with `node tools/generate-schema-types.js`.\n" + + $"In .props but not All: {string.Join(", ", fromProps.Except(all))}\n" + + $"In All but not .props: {string.Join(", ", all.Except(fromProps))}"); + } + + // --- Guard: growing the emittable set must bump the writer's version minor ------------------- + + [Fact] + public void AddingEmittableField_RequiresMinorBump() + { + // Soundness guard for the forward-compat scan's minor gate (ForwardCompat.PreserveUnknownData): + // the gate SKIPS the unknown-data scan whenever the existing file's minor <= the writer's + // minor, trusting that a same-or-older minor cannot contain anything the writer does not + // already emit. That holds ONLY if the writer's version minor is bumped every time the + // emittable set (sections / [properties] keys / item @metadata) grows. This test fails the + // moment that set changes without a matching minor bump, so the gate can never silently drop + // forward-compatible data. + // + // If this fails because you intentionally changed the emittable set: bump the minor in the + // cache schema (which flows to CacheFormat.VersionHeader) and update ExpectedMinor + + // ExpectedEmittableHash below to the values printed in the failure message. + const int ExpectedMinor = 2; + const string ExpectedEmittableHash = "4F7E8056D1E53C2805BA30975E889F762D97752B24BD69F77CFFF6233D1604E6"; + + // Metadata is hashed AS item-type:key pairs (not a flattened union) so that reusing an + // existing metadata NAME on a different item type — which leaves ProjectItems.AllMetadata + // unchanged — still changes this hash and forces a minor bump. That closes the gap where the + // per-section preservation gate would otherwise be trusted to skip data it cannot regenerate. + IEnumerable metadataPairs = ProjectItems.MetadataByItemType + .SelectMany(kv => kv.Value.Select(m => $"{kv.Key}:{m}")); + + string joined = string.Join( + "|", + CacheFormat.Sections.All + .Concat(ForwardCompat.KnownPropertyKeys) + .Concat(metadataPairs) + .Select(s => s.ToLowerInvariant()) + .OrderBy(s => s, StringComparer.Ordinal)); + string actualHash = Convert.ToHexString( + System.Security.Cryptography.SHA256.HashData(Encoding.UTF8.GetBytes(joined))); + + Assert.True( + ForwardCompat.TryParseVersion(CacheFormat.VersionHeader, out _, out int actualMinor), + $"CacheFormat.VersionHeader '{CacheFormat.VersionHeader}' is not a valid version header."); + + string detail = $"Emittable set ({joined.Length} chars):\n{joined}\n" + + $"Re-pin with: ExpectedMinor={actualMinor}, ExpectedEmittableHash=\"{actualHash}\"."; + + if (actualHash == ExpectedEmittableHash) + { + Assert.Equal(ExpectedMinor, actualMinor); + return; + } + + Assert.True( + actualMinor > ExpectedMinor, + "The emittable cache field set changed but the writer's version minor was NOT bumped " + + $"(still {actualMinor}). Adding forward-compatible fields REQUIRES a minor bump so older " + + $"writers preserve them instead of silently dropping them.\n{detail}"); + + Assert.Fail( + $"The emittable cache field set changed and the minor was correctly bumped to {actualMinor}. " + + $"Re-pin this guard so steady-state runs pass again.\n{detail}"); + } + + [Fact] + public void EveryEmittedMetadataKey_IsKnownForItsSection() + { + // Forward-compat judges "known @metadata" per cache section via the schema-generated + // CacheFormat.Sections.MetadataBySection (built from each section's itemType link). If a + // future schema change gives metadata to an item type whose section lacks an itemType link, + // that metadata would be absent from MetadataBySection and the section would be treated as + // emitting no metadata. That is the SAFE direction (the metadata is then preserved rather than + // dropped), but this guard makes the omission explicit so the section->itemType links stay + // complete and the per-section gate keeps dropping genuinely-known metadata. + var knownAcrossSections = new HashSet( + CacheFormat.Sections.MetadataBySection.Values.SelectMany(m => m), + StringComparer.OrdinalIgnoreCase); + + string[] missing = ProjectItems.MetadataByItemType.Values + .SelectMany(m => m) + .Where(meta => !knownAcrossSections.Contains(meta)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .OrderBy(s => s, StringComparer.Ordinal) + .ToArray(); + + Assert.True( + missing.Length == 0, + "Metadata key(s) the writer emits are not reachable through any cache section's itemType " + + $"link (CacheFormat.Sections.MetadataBySection): {string.Join(", ", missing)}. Add the " + + "\"itemType\" link to the owning section in server/src/Microsoft.NET.ProjectData.Generators/project-data-schema.json."); + } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/LscacheInvariants.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/LscacheInvariants.cs new file mode 100644 index 0000000000000..64fad41b2058b --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/LscacheInvariants.cs @@ -0,0 +1,217 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace Microsoft.NET.ProjectData.Tasks.Tests; + +/// +/// Invariants that any well-formed .lscache file produced by the +/// ProjectDataBuild targets and ProjectDataWriter must satisfy. +/// Used by writer regression tests to guard against bug shapes that produce +/// internally inconsistent caches, regardless of the specific input that +/// surfaced the bug. +/// +internal static class LscacheInvariants +{ + /// + /// Every line in the [sdkAnalyzerConfigPolicy] section refers to an + /// SDK analyzer pack subfolder (e.g. Microsoft.NET.Sdk/analyzers or + /// Microsoft.NET.Sdk/codestyle/cs|...). For each such line there + /// must be at least one analyzer DLL listed under the matching + /// /Sdks/<sdkPack>/<segment>/ path in + /// [analyzerReferences] — otherwise the cache claims to apply + /// analyzer configuration to DLLs the SDK never produced. + /// + /// + /// Guards against regressing the + /// Gate [sdkAnalyzerConfigPolicy] lines on the SDK's analyzer-pack + /// property gates fix. The fix gated emission on + /// EnableNETAnalyzers and EnforceCodeStyleInBuild to + /// mirror the SDK's own conditions; any future code path that bypasses + /// those gates (in the targets file, the writer, or a new policy type) + /// will produce orphan policy lines that this assertion catches. + /// + public static void AssertNoOrphanAnalyzerPolicyLines(string lscacheContent) + { + IReadOnlyList policyLines = ExtractSection(lscacheContent, "sdkAnalyzerConfigPolicy", preserveIndent: false); + if (policyLines.Count == 0) + { + return; + } + + // [analyzerReferences] encodes paths as an indented tree where children + // share their parent's prefix (e.g. parent `/Sdks/Microsoft.NET.Sdk/`, + // children `analyzers/`, `codestyle/cs/`). Rebuild the flat list of + // full file paths so the invariant can be checked with a simple + // substring match regardless of how the writer compresses prefixes. + IReadOnlyList analyzerReferencePaths = ReconstructIndentedTreePaths( + ExtractSection(lscacheContent, "analyzerReferences", preserveIndent: true)); + string analyzerReferencesFlat = string.Join("\n", analyzerReferencePaths); + + List orphans = []; + foreach (string line in policyLines) + { + // Policy lines look like "Microsoft.NET.Sdk/analyzers" or + // "Microsoft.NET.Sdk/codestyle/cs|AnalysisLevel=...". Strip the + // argument segment after '|' and look for a matching SDK pack + // subfolder in [analyzerReferences]. + int barIndex = line.IndexOf('|'); + string folderKey = barIndex >= 0 ? line[..barIndex] : line; + string expectedFragment = $"/Sdks/{folderKey}/"; + if (!analyzerReferencesFlat.Contains(expectedFragment)) + { + orphans.Add(line); + } + } + + Assert.True( + orphans.Count == 0, + $"Found {orphans.Count} orphan [sdkAnalyzerConfigPolicy] line(s) without matching analyzer DLLs in [analyzerReferences]: {string.Join("; ", orphans)}"); + } + + private static IReadOnlyList ExtractSection(string lscacheContent, string sectionName, bool preserveIndent) + { + string[] lines = lscacheContent.Replace("\r\n", "\n").Split('\n'); + List result = []; + bool inSection = false; + string sectionHeader = $"[{sectionName}]"; + foreach (string line in lines) + { + if (line.StartsWith('[')) + { + inSection = line == sectionHeader; + continue; + } + + if (!inSection || line.Length == 0) + { + continue; + } + + result.Add(preserveIndent ? line : line.TrimStart()); + } + + return result; + } + + private static IReadOnlyList ReconstructIndentedTreePaths(IReadOnlyList indentedLines) + { + // Each line's leading-space count is its depth in the tree. A line ending + // in '/' is a directory: its full path becomes the prefix for any deeper + // lines until we return to that depth or shallower. A line not ending in + // '/' is a file: emit its reconstructed full path. + Stack<(int Depth, string Prefix)> stack = new(); + List filePaths = []; + foreach (string raw in indentedLines) + { + int depth = 0; + while (depth < raw.Length && raw[depth] == ' ') + { + depth++; + } + + string text = raw[depth..]; + while (stack.Count > 0 && stack.Peek().Depth >= depth) + { + stack.Pop(); + } + + string prefix = stack.Count > 0 ? stack.Peek().Prefix : string.Empty; + string full = prefix + text; + if (text.EndsWith('/')) + { + stack.Push((depth, full)); + } + else + { + filePaths.Add(full); + } + } + + return filePaths; + } +} + +public sealed class LscacheInvariantsTests +{ + [Fact] + public void AssertNoOrphanAnalyzerPolicyLines_NoPolicySection_Passes() + { + // Trivial: a cache with no policy section can't have orphan lines. + const string content = "[someOtherSection]\nfoo=bar\n"; + LscacheInvariants.AssertNoOrphanAnalyzerPolicyLines(content); + } + + [Fact] + public void AssertNoOrphanAnalyzerPolicyLines_EveryPolicyLineHasMatchingReference_Passes() + { + const string content = """ + [sdkAnalyzerConfigPolicy] + Microsoft.NET.Sdk/analyzers + Microsoft.NET.Sdk/codestyle/cs|AnalysisLevel=latest + [analyzerReferences] + /Sdks/Microsoft.NET.Sdk/analyzers/ + Microsoft.CodeAnalysis.NetAnalyzers.dll + /Sdks/Microsoft.NET.Sdk/codestyle/cs/ + Microsoft.CodeAnalysis.CSharp.CodeStyle.dll + """; + LscacheInvariants.AssertNoOrphanAnalyzerPolicyLines(content); + } + + [Fact] + public void AssertNoOrphanAnalyzerPolicyLines_OrphanAnalyzerLine_Throws() + { + // Mirrors the exact pre-fix bug shape: policy line exists but the + // corresponding analyzer pack subfolder has no DLLs in [analyzerReferences]. + const string content = """ + [sdkAnalyzerConfigPolicy] + Microsoft.NET.Sdk/analyzers + [analyzerReferences] + /Sdks/Microsoft.NET.Sdk/codestyle/cs/ + Microsoft.CodeAnalysis.CSharp.CodeStyle.dll + """; + Xunit.Sdk.XunitException ex = Assert.ThrowsAny( + () => LscacheInvariants.AssertNoOrphanAnalyzerPolicyLines(content)); + Assert.Contains("Microsoft.NET.Sdk/analyzers", ex.Message); + } + + [Fact] + public void AssertNoOrphanAnalyzerPolicyLines_OrphanCodeStyleLine_Throws() + { + const string content = """ + [sdkAnalyzerConfigPolicy] + Microsoft.NET.Sdk/codestyle/cs|AnalysisLevel=latest + [analyzerReferences] + /Sdks/Microsoft.NET.Sdk/analyzers/ + Microsoft.CodeAnalysis.NetAnalyzers.dll + """; + Xunit.Sdk.XunitException ex = Assert.ThrowsAny( + () => LscacheInvariants.AssertNoOrphanAnalyzerPolicyLines(content)); + Assert.Contains("Microsoft.NET.Sdk/codestyle/cs", ex.Message); + } + + [Fact] + public void AssertNoOrphanAnalyzerPolicyLines_CompressedTreeWithSharedParent_Passes() + { + // The writer compresses common path prefixes in [analyzerReferences]. + // When both analyzers/ and codestyle/cs/ are present under the same SDK + // pack, they are emitted as siblings under a shared parent line. The + // invariant must reconstruct the tree before substring-matching the + // folder key, otherwise it would spuriously fail on this real-world + // output shape. + const string content = """ + [sdkAnalyzerConfigPolicy] + Microsoft.NET.Sdk/analyzers + Microsoft.NET.Sdk/codestyle/cs|AnalysisLevel=latest + [analyzerReferences] + /Sdks/Microsoft.NET.Sdk/ + analyzers/ + Microsoft.CodeAnalysis.NetAnalyzers.dll + codestyle/cs/ + Microsoft.CodeAnalysis.CSharp.CodeStyle.dll + """; + LscacheInvariants.AssertNoOrphanAnalyzerPolicyLines(content); + } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/Microsoft.NET.ProjectData.Tasks.Tests.csproj b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/Microsoft.NET.ProjectData.Tasks.Tests.csproj new file mode 100644 index 0000000000000..1b3772be7fa4b --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/Microsoft.NET.ProjectData.Tasks.Tests.csproj @@ -0,0 +1,26 @@ + + + + Exe + + Microsoft.NET.ProjectData.Tasks.UnitTests + Microsoft.NET.ProjectData.Tasks.Tests + false + false + false + + $(NoWarn);RS0030 + + + + + + + + + + + + + diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/ProjectDataMergerTests.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/ProjectDataMergerTests.cs new file mode 100644 index 0000000000000..0857a898c8c41 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/ProjectDataMergerTests.cs @@ -0,0 +1,748 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace Microsoft.NET.ProjectData.Tasks.Tests; + +public class ProjectDataMergerTests +{ + private const string SliceNet8 = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=net8.0\n"; + private const string SliceNet9 = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=net9.0\n"; + private const string SliceNet472 = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=net472\n"; + + private static string MakeTempDir() + { + string dir = Path.Combine(Path.GetTempPath(), "lscache-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(dir); + return dir; + } + + private static string CreateSliceDir(string baseDir, string tfm) + { + string dir = Path.Combine(baseDir, "obj", "Debug", tfm); + Directory.CreateDirectory(dir); + return dir; + } + + private static int CountOccurrences(string content, string value) + { + int count = 0; + int index = 0; + while ((index = content.IndexOf(value, index, StringComparison.Ordinal)) >= 0) + { + count++; + index += value.Length; + } + + return count; + } + + private static string GetSliceBlock(string content, string targetFramework) + { + foreach (string block in content.Split("\n---\n", StringSplitOptions.None).Skip(1)) + { + if (block.Contains($"TargetFramework={targetFramework}\n", StringComparison.Ordinal)) + return block; + } + + throw new InvalidOperationException($"Could not find slice block for {targetFramework}.\n{content}"); + } + + [Fact] + public void Merge_SingleSlice_CreatesFileWithBanner() + { + string dir = MakeTempDir(); + try + { + string sliceDir = CreateSliceDir(dir, "net8.0"); + File.WriteAllText(Path.Combine(sliceDir, "Sample.csproj.slice"), SliceNet8); + string outPath = Path.Combine(dir, "out.lscache"); + + int count = ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice")); + + Assert.Equal(1, count); + Assert.True(File.Exists(outPath)); + string content = File.ReadAllText(outPath).Replace("\r\n", "\n"); + string[] lines = content.Split('\n'); + Assert.Equal("version=2.2", lines[0]); + Assert.DoesNotContain("hash=", content); + Assert.Contains("# This file caches", content); + Assert.DoesNotContain("TargetFramework=net8.0", content); + Assert.DoesNotContain("\n---\n", content); + Assert.DoesNotContain("[sliceDimensions]", content); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_PreservesSlicesAfterSuccess() + { + string dir = MakeTempDir(); + try + { + string sliceDir = CreateSliceDir(dir, "net8.0"); + string slicePath = Path.Combine(sliceDir, "Sample.csproj.slice"); + File.WriteAllText(slicePath, SliceNet8); + string outPath = Path.Combine(dir, "out.lscache"); + + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice")); + + Assert.True(File.Exists(slicePath), "slice should be preserved so later builds can skip when it is up-to-date"); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_ExplicitSliceFiles_IgnoresUnlistedStaleSlice() + { + string dir = MakeTempDir(); + try + { + string net8Slice = Path.Combine(CreateSliceDir(dir, "net8.0"), "Sample.csproj.slice"); + string staleNet9Slice = Path.Combine(CreateSliceDir(dir, "net9.0"), "Sample.csproj.slice"); + File.WriteAllText(net8Slice, SliceNet8); + File.WriteAllText(staleNet9Slice, SliceNet9); + string outPath = Path.Combine(dir, "out.lscache"); + + int count = ProjectDataMerger.Merge(outPath, [net8Slice], "net8.0"); + + Assert.Equal(1, count); + string content = File.ReadAllText(outPath).Replace("\r\n", "\n"); + Assert.DoesNotContain("\n---\n", content); + Assert.DoesNotContain("TargetFramework=net9.0", content); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_PreserveExistingSlices_KeepsUnevaluatedSliceFromOutput() + { + string dir = MakeTempDir(); + try + { + string net10Slice = Path.Combine(CreateSliceDir(dir, "net10.0"), "Sample.csproj.slice"); + File.WriteAllText(net10Slice, "[project]\nlanguage=C#\nlastDtbSucceeded\n[sliceDimensions]\nTargetFramework=net10.0\n[commandLineArguments]\n/langversion:preview\n"); + string outPath = Path.Combine(dir, "out.lscache"); + File.WriteAllText( + outPath, + """ + hash=0000000000000000000000000000000000000000000000000000000000000000 + version=2 + + # Existing committed cache. + + [project] + project=Sample.csproj + language=C# + lastDtbSucceeded + + [commandLineArguments] + /noconfig + + --- + + [project] + primary + + [sliceDimensions] + TargetFramework=net10.0 + + --- + + [project] + + [sliceDimensions] + TargetFramework=net472 + + [metadataReferences] + /v4.7.2/ + mscorlib.dll + """); + + int count = ProjectDataMerger.Merge(outPath, [net10Slice], "net10.0", preserveExistingSlices: true); + + Assert.Equal(1, count); + string content = File.ReadAllText(outPath).Replace("\r\n", "\n"); + Assert.Contains("TargetFramework=net10.0", content); + Assert.Contains("/langversion:preview", content); + Assert.DoesNotContain("/noconfig", GetSliceBlock(content, "net10.0")); + + string preservedNetFrameworkBlock = GetSliceBlock(content, "net472"); + Assert.Contains("/v4.7.2/", preservedNetFrameworkBlock); + Assert.Contains(" mscorlib.dll", preservedNetFrameworkBlock); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_PreserveExistingSlices_DoesNotPreserveSlicesFromIncompatibleMajorVersion() + { + string dir = MakeTempDir(); + try + { + string net10Slice = Path.Combine(CreateSliceDir(dir, "net10.0"), "Sample.csproj.slice"); + File.WriteAllText(net10Slice, "[project]\nlanguage=C#\nlastDtbSucceeded\n[sliceDimensions]\nTargetFramework=net10.0\n[commandLineArguments]\n/langversion:preview\n"); + string outPath = Path.Combine(dir, "out.lscache"); + + // The existing merged cache is a FUTURE major (version=3): its grammar may differ from this + // writer's, so its slices must NOT be parsed-and-re-emitted under the current version= header. + File.WriteAllText( + outPath, + """ + version=3 + + [project] + project=Sample.csproj + language=C# + lastDtbSucceeded + + --- + + [project] + + [sliceDimensions] + TargetFramework=net472 + + [metadataReferences] + /v4.7.2/ + future_major_payload.dll + """); + + int count = ProjectDataMerger.Merge(outPath, [net10Slice], "net10.0", preserveExistingSlices: true); + + Assert.Equal(1, count); + string content = File.ReadAllText(outPath).Replace("\r\n", "\n"); + Assert.StartsWith("version=2", content); + Assert.Contains("/langversion:preview", content); + // The incompatible-major slice content must be gone — not re-emitted under version=2. + Assert.DoesNotContain("future_major_payload.dll", content); + Assert.DoesNotContain("TargetFramework=net472", content); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_NoSlices_ReturnsZero() + { + string dir = MakeTempDir(); + try + { + Directory.CreateDirectory(Path.Combine(dir, "obj")); + string outPath = Path.Combine(dir, "out.lscache"); + + int count = ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice")); + + Assert.Equal(0, count); + Assert.False(File.Exists(outPath)); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_TwoSlices_AddsSeparator() + { + string dir = MakeTempDir(); + try + { + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net8.0"), "Sample.csproj.slice"), SliceNet8); + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net9.0"), "Sample.csproj.slice"), SliceNet9); + string outPath = Path.Combine(dir, "out.lscache"); + + int count = ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice")); + + Assert.Equal(2, count); + string content = File.ReadAllText(outPath).Replace("\r\n", "\n"); + Assert.Contains("TargetFramework=net8.0", content); + Assert.Contains("TargetFramework=net9.0", content); + + int sepCount = 0; + int idx = 0; + while ((idx = content.IndexOf("\n---\n", idx, StringComparison.Ordinal)) >= 0) + { + sepCount++; + idx += 5; + } + // Shared section + 2 per-TFM sections = 2 separators + Assert.Equal(2, sepCount); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_SortsSlicesByPath() + { + string dir = MakeTempDir(); + try + { + // Write net9.0 first, then net472 — output should sort alphabetically by path + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net9.0"), "Sample.csproj.slice"), SliceNet9 + "MARK_NET9\n"); + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net472"), "Sample.csproj.slice"), SliceNet472 + "MARK_NET472\n"); + string outPath = Path.Combine(dir, "out.lscache"); + + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice")); + + string content = File.ReadAllText(outPath); + int idx472 = content.IndexOf("MARK_NET472", StringComparison.Ordinal); + int idx9 = content.IndexOf("MARK_NET9", StringComparison.Ordinal); + Assert.True(idx472 >= 0 && idx9 >= 0); + Assert.True(idx472 < idx9, "net472 slice should come before net9.0"); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Theory] + [InlineData("net8.0;netstandard2.0", "net8.0", "netstandard2.0")] + [InlineData("netstandard2.0;net8.0", "netstandard2.0", "net8.0")] + public void Merge_PrimaryFollowsFirstNonNetFrameworkTargetFramework(string targetFrameworks, string expectedPrimary, string expectedNonPrimary) + { + string dir = MakeTempDir(); + try + { + string sliceNet8 = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=net8.0\n"; + string sliceNetStandard = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=netstandard2.0\n"; + + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net8.0"), "Sample.csproj.slice"), sliceNet8); + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "netstandard2.0"), "Sample.csproj.slice"), sliceNetStandard); + string outPath = Path.Combine(dir, "out.lscache"); + + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice"), targetFrameworks); + + string content = File.ReadAllText(outPath).Replace("\r\n", "\n"); + Assert.Equal(1, CountOccurrences(content, "\nprimary\n")); + Assert.Contains("\nprimary\n", GetSliceBlock(content, expectedPrimary)); + Assert.DoesNotContain("\nprimary\n", GetSliceBlock(content, expectedNonPrimary)); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_PrimaryPrefersNetCoreAppOverNetFramework() + { + string dir = MakeTempDir(); + try + { + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net472"), "Sample.csproj.slice"), SliceNet472); + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net8.0"), "Sample.csproj.slice"), SliceNet8); + string outPath = Path.Combine(dir, "out.lscache"); + + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice"), "net472;net8.0"); + + string content = File.ReadAllText(outPath).Replace("\r\n", "\n"); + Assert.Equal(1, CountOccurrences(content, "\nprimary\n")); + Assert.Contains("\nprimary\n", GetSliceBlock(content, "net8.0")); + Assert.DoesNotContain("\nprimary\n", GetSliceBlock(content, "net472")); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_PrimaryMarkerOmittedForSingleSliceBecauseReaderFallsBackToFirstSlice() + { + // Single-slice output intentionally omits ``primary``: the data-model reader's + // ``ToProjectDto`` treats ``slices[0]`` as primary when no slice carries the + // marker. Skipping the marker keeps the cache stable when a project transitions + // from multi-targeting to single-targeting. + string dir = MakeTempDir(); + try + { + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net472"), "Sample.csproj.slice"), SliceNet472); + string outPath = Path.Combine(dir, "out.lscache"); + + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice"), "net472"); + + string content = File.ReadAllText(outPath).Replace("\r\n", "\n"); + Assert.Equal(0, CountOccurrences(content, "\nprimary\n")); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_ThreeSlices_TwoSeparators() + { + string dir = MakeTempDir(); + try + { + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net8.0"), "Sample.csproj.slice"), SliceNet8); + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net9.0"), "Sample.csproj.slice"), SliceNet9); + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net472"), "Sample.csproj.slice"), SliceNet472); + string outPath = Path.Combine(dir, "out.lscache"); + + int count = ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice")); + + Assert.Equal(3, count); + string content = File.ReadAllText(outPath).Replace("\r\n", "\n"); + + int sepCount = 0; + int idx = 0; + while ((idx = content.IndexOf("\n---\n", idx, StringComparison.Ordinal)) >= 0) + { + sepCount++; + idx += 5; + } + // Shared section + 3 per-TFM sections = 3 separators + Assert.Equal(3, sepCount); + Assert.Contains("TargetFramework=net472", content); + Assert.Contains("TargetFramework=net8.0", content); + Assert.Contains("TargetFramework=net9.0", content); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_OverwritesExistingOutput() + { + string dir = MakeTempDir(); + try + { + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net8.0"), "Sample.csproj.slice"), SliceNet8); + string outPath = Path.Combine(dir, "out.lscache"); + File.WriteAllText(outPath, "OLD_CONTENT"); + + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice")); + + string content = File.ReadAllText(outPath); + Assert.DoesNotContain("OLD_CONTENT", content); + Assert.Contains("[project]", content); + Assert.DoesNotContain("\n---\n", content); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_DeduplicatesSharedContent() + { + string dir = MakeTempDir(); + try + { + // Two slices with mostly identical content but different TFMs and one different property. + string slice8 = "[project]\nlanguage=C#\nlastDtbSucceeded\n" + + "[sliceDimensions]\nTargetFramework=net8.0\n" + + "[properties]\nAssemblyName=MyApp\nTargetPath=bin/net8.0/MyApp.dll\n" + + "[sourceFiles]\nProgram.cs\nHelper.cs\n" + + "[metadataReferences]\nSystem.Runtime.dll\nSystem.Collections.dll\n"; + string slice9 = "[project]\nlanguage=C#\nlastDtbSucceeded\n" + + "[sliceDimensions]\nTargetFramework=net9.0\n" + + "[properties]\nAssemblyName=MyApp\nTargetPath=bin/net9.0/MyApp.dll\n" + + "[sourceFiles]\nProgram.cs\nHelper.cs\n" + + "[metadataReferences]\nSystem.Runtime.dll\nSystem.Collections.dll\nSystem.Text.Json.dll\n"; + + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net8.0"), "Sample.csproj.slice"), slice8); + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net9.0"), "Sample.csproj.slice"), slice9); + string outPath = Path.Combine(dir, "out.lscache"); + + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice")); + + string content = File.ReadAllText(outPath).Replace("\r\n", "\n"); + + // Shared properties should appear before the first --- separator. + int firstSep = content.IndexOf("\n---\n", StringComparison.Ordinal); + string sharedPart = content.Substring(0, firstSep); + Assert.Contains("AssemblyName=MyApp", sharedPart); + Assert.Contains("Program.cs", sharedPart); + Assert.Contains("Helper.cs", sharedPart); + Assert.Contains("System.Runtime.dll", sharedPart); + Assert.Contains("System.Collections.dll", sharedPart); + Assert.Contains("lastDtbSucceeded", sharedPart); + + // TFM-specific content should only be in per-TFM sections. + Assert.DoesNotContain("TargetFramework=", sharedPart); + string perTfmPart = content.Substring(firstSep); + Assert.Contains("TargetPath=bin/net8.0/MyApp.dll", perTfmPart); + Assert.Contains("TargetPath=bin/net9.0/MyApp.dll", perTfmPart); + Assert.Contains("System.Text.Json.dll", perTfmPart); + + // Shared content should NOT be repeated in per-TFM sections. + // Count occurrences of "Program.cs" in the whole file — should be exactly 1. + int programCount = 0; + int searchIdx = 0; + while ((searchIdx = content.IndexOf("Program.cs", searchIdx, StringComparison.Ordinal)) >= 0) + { + programCount++; + searchIdx += 10; + } + Assert.Equal(1, programCount); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void ParseSlice_ParsesAllSections() + { + string slice = "[project]\nlanguage=C#\nlastDtbSucceeded\nprimary\n" + + "[sliceDimensions]\nTargetFramework=net8.0\n" + + "[properties]\nAssemblyName=Test\nTargetPath=bin/Test.dll\n" + + "[commandLineArguments]\n/noconfig\n/unsafe-\n" + + "[sourceFiles]\nProgram.cs\n" + + "[metadataReferences]\nSystem.dll\n" + + "[analyzerReferences]\nMyAnalyzer.dll\n"; + + ProjectDataMerger.SliceData data = ProjectDataMerger.ParseSlice(slice); + + Assert.Contains("lastDtbSucceeded", data.ProjectLines); + Assert.DoesNotContain("primary", data.ProjectLines); + Assert.True(data.IsPrimary); + Assert.Single(data.SliceDimensions); + Assert.Equal("TargetFramework=net8.0", data.SliceDimensions[0]); + Assert.Equal(2, data.Properties.Count); + Assert.Equal(2, data.ListSections["commandLineArguments"].Count); + Assert.Single(data.ListSections["sourceFiles"]); + Assert.Single(data.ListSections["metadataReferences"]); + Assert.Single(data.ListSections["analyzerReferences"]); + } + + [Fact] + public void Merge_IsStableWhenSharedSdkAnalyzerConfigPolicyIsReParsed() + { + // Round-trip stability guard for the shared block's ``[sdkAnalyzerConfigPolicy]`` + // entries. ``ProjectDataMerger.ParseSlice`` calls ``CanonicalizeSdkAnalyzerConfigPolicyLine`` + // while parsing the merged file's shared block, where ``GetTargetFramework()`` + // returns ``null``. The canonicalizer is currently a no-op under null TFM, so a + // line that lands in the shared block (because every per-TFM slice produced the + // same value) survives a parse/emit round-trip unchanged. Pin that contract — if + // the canonicalizer ever mutates already-canonical data under null TFM, this test + // catches it before the cache starts thrashing on round-trip. + string dir = MakeTempDir(); + try + { + string sharedPolicy = "Microsoft.NET.Sdk/analyzers|AnalysisMode=Default"; + string sliceNet8 = + "[project]\nlanguage=C#\n" + + "[sliceDimensions]\nTargetFramework=net8.0\n" + + "[sdkAnalyzerConfigPolicy]\n" + sharedPolicy + "\n"; + string sliceNet9 = + "[project]\nlanguage=C#\n" + + "[sliceDimensions]\nTargetFramework=net9.0\n" + + "[sdkAnalyzerConfigPolicy]\n" + sharedPolicy + "\n"; + + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net8.0"), "Sample.csproj.slice"), sliceNet8); + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net9.0"), "Sample.csproj.slice"), sliceNet9); + string outPath = Path.Combine(dir, "out.lscache"); + + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice"), "net8.0;net9.0"); + string firstMerge = File.ReadAllText(outPath).Replace("\r\n", "\n"); + + // Sanity-check the shared block actually contains the policy line. + Assert.Contains($"\n{sharedPolicy}\n", firstMerge); + + // The merged file is headerless now (no leading hash line); the whole file is the + // banner + structured content produced by ``WriteMergedContent``. + string firstMergeBody = firstMerge; + + // Round-trip the merged file: parse it back to ``SliceData`` (which canonicalizes + // the shared block under null TFM) and re-emit through ``WriteMergedContent``. + // The result must be byte-identical to the original ``WriteMergedContent`` output. + List reparsed = ProjectDataMerger.ParseMergedContent(firstMerge); + using var sw = new StringWriter { NewLine = "\n" }; + ProjectDataMerger.WriteMergedContent(sw, reparsed, "net8.0;net9.0"); + + Assert.Equal(firstMergeBody, sw.ToString()); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_IndentedSourceFilesUnderTfmSpecificDir_AreNotHoistedToShared() + { + // Regression test for: a child line ' ConsoleApp2.AssemblyInfo.cs' from + // the per-TFM compressed group + // obj/Debug/net10.0/ + // ConsoleApp2.AssemblyInfo.cs + // ConsoleApp2.GlobalUsings.g.cs + // was being hoisted into the shared block (line-level intersection) without + // its parent prefix line, producing a corrupt cache file like + // [sourceFiles] + // ConsoleApp2.AssemblyInfo.cs <-- orphaned indented line + // ConsoleApp2.GlobalUsings.g.cs + // Program.cs + // The fix groups indent-0 lines with their indented continuations and + // intersects at the group level. + string dir = MakeTempDir(); + try + { + string slice10 = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=net10.0\n" + + "[sourceFiles]\nProgram.cs\nobj/Debug/net10.0/\n ConsoleApp2.AssemblyInfo.cs\n ConsoleApp2.GlobalUsings.g.cs\n"; + string slice9 = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=net9.0\n" + + "[sourceFiles]\nProgram.cs\nobj/Debug/net9.0/\n ConsoleApp2.AssemblyInfo.cs\n ConsoleApp2.GlobalUsings.g.cs\n"; + + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net10.0"), "Sample.csproj.slice"), slice10); + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net9.0"), "Sample.csproj.slice"), slice9); + string outPath = Path.Combine(dir, "out.lscache"); + + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice")); + + string content = File.ReadAllText(outPath).Replace("\r\n", "\n"); + int firstSep = content.IndexOf("\n---\n", StringComparison.Ordinal); + string sharedPart = content.Substring(0, firstSep); + string perTfmPart = content.Substring(firstSep); + + // Program.cs is the only line legitimately shared: it has no parent prefix. + Assert.Contains("\nProgram.cs\n", sharedPart); + + // The orphaned indented child lines must not appear in the shared block + // — they would be meaningless without their per-TFM directory header. + Assert.DoesNotContain("\n ConsoleApp2.AssemblyInfo.cs\n", sharedPart); + Assert.DoesNotContain("\n ConsoleApp2.GlobalUsings.g.cs\n", sharedPart); + Assert.DoesNotContain("obj/Debug/net10.0/", sharedPart); + Assert.DoesNotContain("obj/Debug/net9.0/", sharedPart); + + // Each per-TFM block keeps its complete group: prefix line + children together. + Assert.Contains("obj/Debug/net10.0/\n ConsoleApp2.AssemblyInfo.cs\n ConsoleApp2.GlobalUsings.g.cs\n", perTfmPart); + Assert.Contains("obj/Debug/net9.0/\n ConsoleApp2.AssemblyInfo.cs\n ConsoleApp2.GlobalUsings.g.cs\n", perTfmPart); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_IndentedGroupIdenticalAcrossTfms_IsShared() + { + // When both slices share the *same* compressed group (same prefix line + + // same children), the entire group should be hoisted to the shared block. + string dir = MakeTempDir(); + try + { + string sliceA = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=net8.0\n" + + "[metadataReferences]\n/\n foo/1.0/lib/net8.0/Foo.dll\n bar/2.0/lib/net8.0/Bar.dll\n"; + string sliceB = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=net9.0\n" + + "[metadataReferences]\n/\n foo/1.0/lib/net8.0/Foo.dll\n bar/2.0/lib/net8.0/Bar.dll\n"; + + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net8.0"), "Sample.csproj.slice"), sliceA); + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net9.0"), "Sample.csproj.slice"), sliceB); + string outPath = Path.Combine(dir, "out.lscache"); + + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice")); + + string content = File.ReadAllText(outPath).Replace("\r\n", "\n"); + int firstSep = content.IndexOf("\n---\n", StringComparison.Ordinal); + string sharedPart = content.Substring(0, firstSep); + string perTfmPart = content.Substring(firstSep); + + // The whole group (header + both children) appears once, in shared. + Assert.Contains("[metadataReferences]\n/\n foo/1.0/lib/net8.0/Foo.dll\n bar/2.0/lib/net8.0/Bar.dll\n", sharedPart); + Assert.DoesNotContain("[metadataReferences]", perTfmPart); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_FrameworkPacksSection_IsPropagatedAndDeduplicated() + { + string dir = MakeTempDir(); + try + { + string sliceA = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=net8.0\n" + + "[frameworkPacks]\nMicrosoft.NETCore.App.Ref\n"; + string sliceB = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=net9.0\n" + + "[frameworkPacks]\nMicrosoft.NETCore.App.Ref\nMicrosoft.AspNetCore.App.Ref\n"; + + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net8.0"), "Sample.csproj.slice"), sliceA); + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net9.0"), "Sample.csproj.slice"), sliceB); + string outPath = Path.Combine(dir, "out.lscache"); + + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice")); + + string content = File.ReadAllText(outPath).Replace("\r\n", "\n"); + int firstSep = content.IndexOf("\n---\n", StringComparison.Ordinal); + string sharedPart = content.Substring(0, firstSep); + string perTfmPart = content.Substring(firstSep); + + // NETCore is shared; AspNetCore is per-TFM. + Assert.Contains("[frameworkPacks]\nMicrosoft.NETCore.App.Ref\n", sharedPart); + Assert.DoesNotContain("Microsoft.AspNetCore.App.Ref", sharedPart); + Assert.Contains("Microsoft.AspNetCore.App.Ref", perTfmPart); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_NetFxRefMetadataReferences_ArePropagatedAndDeduplicated() + { + string dir = MakeTempDir(); + try + { + string sliceA = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=net472\n" + + "[metadataReferences]\n/v4.7.2/mscorlib.dll\n"; + string sliceB = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=net472-windows\n" + + "[metadataReferences]\n/v4.7.2/mscorlib.dll\n/v4.7.2/System.dll\n"; + + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net472"), "Sample.csproj.slice"), sliceA); + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net472-windows"), "Sample.csproj.slice"), sliceB); + string outPath = Path.Combine(dir, "out.lscache"); + + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice")); + + string content = File.ReadAllText(outPath).Replace("\r\n", "\n"); + int firstSep = content.IndexOf("\n---\n", StringComparison.Ordinal); + string sharedPart = content.Substring(0, firstSep); + string perTfmPart = content.Substring(firstSep); + + Assert.Contains("[metadataReferences]\n/v4.7.2/mscorlib.dll\n", sharedPart); + Assert.DoesNotContain("/v4.7.2/System.dll", sharedPart); + Assert.Contains("/v4.7.2/System.dll", perTfmPart); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_SdkAnalyzerSections_ArePropagatedAndDeduplicated() + { + string dir = MakeTempDir(); + try + { + string sliceA = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=net10.0\n" + + "[sdkAnalyzerPacks]\nMicrosoft.NET.ILLink.Tasks\n" + + "[sdkAnalyzerConfigPolicy]\nMicrosoft.NET.Sdk/analyzers\n"; + string sliceB = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=net11.0\n" + + "[sdkAnalyzerPacks]\nMicrosoft.NET.ILLink.Tasks\nAnother.Sdk.AnalyzerPack\n" + + "[sdkAnalyzerConfigPolicy]\nMicrosoft.NET.Sdk/analyzers\nMicrosoft.NET.Sdk/codestyle/cs\n"; + + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net10.0"), "Sample.csproj.slice"), sliceA); + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net11.0"), "Sample.csproj.slice"), sliceB); + string outPath = Path.Combine(dir, "out.lscache"); + + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice")); + + string content = File.ReadAllText(outPath).Replace("\r\n", "\n"); + int firstSep = content.IndexOf("\n---\n", StringComparison.Ordinal); + string sharedPart = content.Substring(0, firstSep); + string perTfmPart = content.Substring(firstSep); + + Assert.Contains("[sdkAnalyzerPacks]\nMicrosoft.NET.ILLink.Tasks\n", sharedPart); + Assert.DoesNotContain("Another.Sdk.AnalyzerPack", sharedPart); + Assert.Contains("Another.Sdk.AnalyzerPack", perTfmPart); + Assert.Contains("[sdkAnalyzerConfigPolicy]\nMicrosoft.NET.Sdk/analyzers\n", sharedPart); + Assert.DoesNotContain("Microsoft.NET.Sdk/codestyle/cs", sharedPart); + Assert.Contains("Microsoft.NET.Sdk/codestyle/cs", perTfmPart); + } + finally { Directory.Delete(dir, recursive: true); } + } + + [Fact] + public void Merge_SdkAnalyzerConfigPolicy_CanonicalizesNumericDefaultAndLatest() + { + string dir = MakeTempDir(); + try + { + string sliceA = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=net10.0\n" + + "[properties]\nTargetFramework=net10.0\nTargetFrameworkIdentifier=.NETCoreApp\nTargetFrameworkVersion=v10.0\n" + + "[sdkAnalyzerConfigPolicy]\nMicrosoft.NET.Sdk/analyzers|AnalysisLevel=10.0\nMicrosoft.NET.Sdk/codestyle/cs|AnalysisLevel=Latest|AnalysisMode=Default\n"; + string sliceB = "[project]\nlanguage=C#\n[sliceDimensions]\nTargetFramework=net8.0\n" + + "[properties]\nTargetFramework=net8.0\nTargetFrameworkIdentifier=.NETCoreApp\nTargetFrameworkVersion=v8.0\n" + + "[sdkAnalyzerConfigPolicy]\nMicrosoft.NET.Sdk/analyzers\nMicrosoft.NET.Sdk/codestyle/cs\n"; + + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net10.0"), "Sample.csproj.slice"), sliceA); + File.WriteAllText(Path.Combine(CreateSliceDir(dir, "net8.0"), "Sample.csproj.slice"), sliceB); + string outPath = Path.Combine(dir, "out.lscache"); + + ProjectDataMerger.Merge(outPath, Path.Combine(dir, "obj", "**", "Sample.csproj.slice")); + + string content = File.ReadAllText(outPath).Replace("\r\n", "\n"); + Assert.DoesNotContain("AnalysisLevel=10.0", content); + Assert.Contains("Microsoft.NET.Sdk/analyzers\n", content); + Assert.Contains("Microsoft.NET.Sdk/codestyle/cs|AnalysisMode=Default\n", content); + Assert.DoesNotContain("AnalysisLevel=Latest", content); + } + finally { Directory.Delete(dir, recursive: true); } + } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/ProjectDataWriterTests.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/ProjectDataWriterTests.cs new file mode 100644 index 0000000000000..c3100fb6ef2f7 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/ProjectDataWriterTests.cs @@ -0,0 +1,2324 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text; +using Microsoft.Build.Framework; +using Moq; +using Xunit; + +namespace Microsoft.NET.ProjectData.Tasks.Tests; + +public class ProjectDataWriterTests +{ + private static string Build( + string? projectPath = null, + bool writeHeader = true, + bool isPrimary = false, + bool lastDtbSucceeded = false, + ITaskItem[]? sliceDimensions = null, + ITaskItem[]? properties = null, + string[]? commandLineArguments = null, + ITaskItem[]? sourceFiles = null, + ITaskItem[]? metadataReferences = null, + ITaskItem[]? analyzerReferences = null, + string[]? analyzerConfigFiles = null, + string[]? additionalFiles = null, + ITaskItem[]? embeddedResources = null, + ITaskItem[]? projectReferences = null, + string[]? capabilities = null, + ITaskItem[]? sdkKnownAnalyzerPacks = null, + ITaskItem[]? sdkAnalyzerConfigPolicy = null, + Action? duplicateItemReporter = null) + => ProjectDataWriter.BuildContent( + projectPath ?? Path.Combine(Path.GetTempPath(), "projectdata-writer-tests", "App.csproj"), writeHeader, isPrimary, lastDtbSucceeded, + sliceDimensions, properties, commandLineArguments, + sourceFiles, metadataReferences, analyzerReferences, + analyzerConfigFiles, additionalFiles, embeddedResources, projectReferences, capabilities, sdkKnownAnalyzerPacks, sdkAnalyzerConfigPolicy, duplicateItemReporter); + + private static ITaskItem MakeItem( + string identity, + string? value = null, + string? nuGetPackageId = null, + string? nuGetPackageVersion = null, + string? frameworkReferenceName = null, + string? referenceOutputAssembly = null) + { + var mock = new Mock(); + mock.Setup(i => i.ItemSpec).Returns(identity); + mock.Setup(i => i.GetMetadata("Value")).Returns(value ?? string.Empty); + mock.Setup(i => i.GetMetadata("NuGetPackageId")).Returns(nuGetPackageId ?? string.Empty); + mock.Setup(i => i.GetMetadata("NuGetPackageVersion")).Returns(nuGetPackageVersion ?? string.Empty); + mock.Setup(i => i.GetMetadata("FrameworkReferenceName")).Returns(frameworkReferenceName ?? string.Empty); + mock.Setup(i => i.GetMetadata("Aliases")).Returns(string.Empty); + mock.Setup(i => i.GetMetadata("EmbedInteropTypes")).Returns(string.Empty); + mock.Setup(i => i.GetMetadata("Link")).Returns(string.Empty); + mock.Setup(i => i.GetMetadata("ReferenceOutputAssembly")).Returns(referenceOutputAssembly ?? string.Empty); + return mock.Object; + } + + private static ITaskItem MakeSdkKnownAnalyzerPack(string packageId, string targetFramework, string packageVersion) + { + var mock = new Mock(); + mock.Setup(i => i.ItemSpec).Returns(packageId); + mock.Setup(i => i.GetMetadata("PackageId")).Returns(packageId); + mock.Setup(i => i.GetMetadata("PackageVersion")).Returns(packageVersion); + mock.Setup(i => i.GetMetadata("TargetFramework")).Returns(targetFramework); + return mock.Object; + } + + private static ITaskItem MakeSdkAnalyzerConfigPolicy(params (string Name, string Value)[] metadata) + { + Dictionary values = metadata.ToDictionary(static item => item.Name, static item => item.Value, StringComparer.OrdinalIgnoreCase); + Mock mock = new Mock(); + mock.Setup(i => i.ItemSpec).Returns("Microsoft.NET.Sdk"); + mock.Setup(i => i.GetMetadata(It.IsAny())).Returns((string name) => values.TryGetValue(name, out string? value) ? value : string.Empty); + return mock.Object; + } + + private static ITaskItem MakeDefaultSdkAnalyzerConfigPolicy(string analysisLevel = "latest", string effectiveAnalysisLevel = "10.0") + => MakeSdkAnalyzerConfigPolicy( + ("Language", "C#"), + ("EnableNETAnalyzers", "true"), + ("EnforceCodeStyleInBuild", "true"), + ("AnalysisLevel", analysisLevel), + ("AnalysisLevelStyle", analysisLevel), + ("EffectiveAnalysisLevel", effectiveAnalysisLevel), + ("EffectiveAnalysisLevelStyle", effectiveAnalysisLevel), + ("MicrosoftCodeAnalysisNetAnalyzersRulesVersion", effectiveAnalysisLevel.Split('.')[0])); + + [Fact] + public void BuildSdkAnalyzerConfigPolicy_OmitsNetAnalyzersLine_WhenEnableNETAnalyzersFalse() + { + // netstandard2.0 default: EnableNETAnalyzers=false, EnforceCodeStyleInBuild=false. + // SDK does not add NetAnalyzer DLLs to @(Analyzer), so we must not emit the + // `Microsoft.NET.Sdk/analyzers|...` policy line either (orphan policy entry). + ITaskItem item = MakeSdkAnalyzerConfigPolicy( + ("Language", "C#"), + ("EnableNETAnalyzers", "false"), + ("EnforceCodeStyleInBuild", "false"), + ("AnalysisLevel", "latest"), + ("EffectiveAnalysisLevel", "4.0")); + + SortedSet policies = ProjectDataWriter.BuildSdkAnalyzerConfigPolicy([item], new ProjectDataWriter.TargetFramework("netstandard2.0", ".NETStandard", "v2.0")); + + Assert.Empty(policies); + } + + [Fact] + public void BuildSdkAnalyzerConfigPolicy_EmitsNetAnalyzersLineOnly_WhenOnlyEnableNETAnalyzersTrue() + { + // netcoreapp default with codestyle suppressed: only NetAnalyzers policy expected. + ITaskItem item = MakeSdkAnalyzerConfigPolicy( + ("Language", "C#"), + ("EnableNETAnalyzers", "true"), + ("EnforceCodeStyleInBuild", "false"), + ("AnalysisLevel", "latest"), + ("EffectiveAnalysisLevel", "10.0")); + + SortedSet policies = ProjectDataWriter.BuildSdkAnalyzerConfigPolicy([item], new ProjectDataWriter.TargetFramework("net10.0", ".NETCoreApp", "v10.0")); + + Assert.Single(policies); + Assert.Contains(policies, p => p.StartsWith("Microsoft.NET.Sdk/analyzers", StringComparison.Ordinal)); + Assert.DoesNotContain(policies, p => p.StartsWith("Microsoft.NET.Sdk/codestyle/", StringComparison.Ordinal)); + } + + [Fact] + public void BuildSdkAnalyzerConfigPolicy_EmitsCodeStyleLineOnly_WhenOnlyEnforceCodeStyleInBuildTrue() + { + // Repo case (e.g. vs-validation test asset on netstandard2.0): codestyle DLLs included + // via Directory.Build.props setting EnforceCodeStyleInBuild=true, but NetAnalyzers stay + // off because EffectiveAnalysisLevel<5.0 leaves EnableNETAnalyzers=false. + ITaskItem item = MakeSdkAnalyzerConfigPolicy( + ("Language", "C#"), + ("EnableNETAnalyzers", "false"), + ("EnforceCodeStyleInBuild", "true"), + ("AnalysisLevel", "latest"), + ("AnalysisLevelStyle", "latest"), + ("EffectiveAnalysisLevel", "4.0"), + ("EffectiveAnalysisLevelStyle", "4.0")); + + SortedSet policies = ProjectDataWriter.BuildSdkAnalyzerConfigPolicy([item], new ProjectDataWriter.TargetFramework("netstandard2.0", ".NETStandard", "v2.0")); + + Assert.Single(policies); + Assert.DoesNotContain(policies, p => p.StartsWith("Microsoft.NET.Sdk/analyzers", StringComparison.Ordinal)); + Assert.Contains(policies, p => p.StartsWith("Microsoft.NET.Sdk/codestyle/cs", StringComparison.Ordinal)); + } + + [Fact] + public void BuildSdkAnalyzerConfigPolicy_EmitsBothLines_WhenBothPropertiesTrue() + { + // Modern netcoreapp default: both NetAnalyzers and CodeStyle policies. + SortedSet policies = ProjectDataWriter.BuildSdkAnalyzerConfigPolicy( + [MakeDefaultSdkAnalyzerConfigPolicy()], new ProjectDataWriter.TargetFramework("net10.0", ".NETCoreApp", "v10.0")); + + Assert.Equal(2, policies.Count); + Assert.Contains(policies, p => p.StartsWith("Microsoft.NET.Sdk/analyzers", StringComparison.Ordinal)); + Assert.Contains(policies, p => p.StartsWith("Microsoft.NET.Sdk/codestyle/cs", StringComparison.Ordinal)); + } + + [Fact] + public void WriteHeader_EmitsVersionAndBanner() + { + string content = Build(); + + Assert.StartsWith("version=2.2", content); + Assert.Contains("aka.ms/lscache", content); + Assert.Contains("dotnet.projectsystem.cacheInProjectFolder", content); + } + + [Fact] + public void WriteHeader_False_NoVersionLine() + { + string content = Build(writeHeader: false); + + Assert.DoesNotContain("version=2", content); + Assert.StartsWith("[project]", content.TrimStart('\r', '\n')); + } + + [Fact] + public void WriteProjectSection_EmitsProjectPrimaryAndDtbFlags() + { + string content = Build(isPrimary: true, lastDtbSucceeded: true); + + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains("[project]\nproject=App.csproj\nlanguage=C#\n", normalized); + Assert.Contains("\nprimary\n", normalized); + Assert.Contains("\nlastDtbSucceeded\n", normalized); + } + + [Fact] + public void WriteProjectSection_OmitsDtbFlag_WhenFalse() + { + string content = Build(lastDtbSucceeded: false); + + string normalized = content.Replace("\r\n", "\n"); + Assert.DoesNotContain("\nprimary\n", normalized); + Assert.DoesNotContain("\nlastDtbSucceeded\n", normalized); + } + + [Fact] + public void ProjectReferences_EmitsReferenceOutputAssemblyFalseMetadata() + { + string projectPath = Path.Combine(Path.GetTempPath(), "projectdata-writer-tests", "App.csproj"); + string referencePath = Path.Combine(Path.GetDirectoryName(projectPath)!, "BuildOnly", "BuildOnly.csproj"); + string content = Build( + projectPath, + projectReferences: + [ + MakeItem(referencePath, referenceOutputAssembly: "false"), + MakeItem(Path.Combine(Path.GetDirectoryName(projectPath)!, "Library", "Library.csproj")), + ]); + + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains( + "[projectReferences]\nBuildOnly/BuildOnly.csproj\n @ReferenceOutputAssembly=false\nLibrary/Library.csproj\n", + normalized); + Assert.DoesNotContain("@ReferenceOutputAssembly=true", normalized); + } + + [Fact] + public void WritePropertiesSection_SortedOrdinalIgnoreCase() + { + ITaskItem[] props = [ + MakeItem("ZProperty", "z"), + MakeItem("aProperty", "a"), + MakeItem("MProperty", "m"), + ]; + string content = Build(properties: props); + + int aIdx = content.IndexOf("aProperty=", StringComparison.OrdinalIgnoreCase); + int mIdx = content.IndexOf("MProperty=", StringComparison.OrdinalIgnoreCase); + int zIdx = content.IndexOf("ZProperty=", StringComparison.OrdinalIgnoreCase); + Assert.True(aIdx < mIdx && mIdx < zIdx, "Properties should be sorted OrdinalIgnoreCase"); + } + + [Fact] + public void WritePropertiesSection_EmptySkipped() + { + string content = Build(properties: []); + + Assert.DoesNotContain("[properties]", content); + } + + [Fact] + public void WritePropertiesSection_SkipsEmptyAndWhitespaceValues() + { + ITaskItem[] props = [ + MakeItem("HasValue", "real"), + MakeItem("EmptyValue", ""), + MakeItem("WhitespaceValue", " "), + ]; + string content = Build(properties: props); + + Assert.Contains("HasValue=real", content); + Assert.DoesNotContain("EmptyValue=", content); + Assert.DoesNotContain("WhitespaceValue=", content); + } + + [Fact] + public void WritePropertiesSection_SkipsUndefinedSentinel() + { + ITaskItem[] props = [ + MakeItem("AssemblyName", "Foo"), + MakeItem("SolutionPath", "*Undefined*"), + ]; + string content = Build(properties: props); + + Assert.Contains("AssemblyName=Foo", content); + Assert.DoesNotContain("SolutionPath=", content); + } + + [Fact] + public void WritePropertiesSection_ExcludesSolutionPath_EvenWhenValueIsReal() + { + ITaskItem[] props = [ + MakeItem("AssemblyName", "Foo"), + MakeItem("SolutionPath", @"C:\Users\dev\MySolution.sln"), + ]; + string content = Build(properties: props); + + Assert.Contains("AssemblyName=Foo", content); + Assert.DoesNotContain("SolutionPath=", content); + } + + [Fact] + public void WritePropertiesSection_HeaderOmittedWhenAllValuesSkipped() + { + ITaskItem[] props = [ + MakeItem("EmptyA", ""), + MakeItem("EmptyB", " "), + MakeItem("Sentinel", "*Undefined*"), + ]; + string content = Build(properties: props); + + Assert.DoesNotContain("[properties]", content); + } + + [Fact] + public void WriteCommandLineArgSection_FiltersFileArgs() + { + string[] args = [ + "/nologo", + "/langversion:preview", + "/reference:C:\\ref\\System.dll", // excluded + "/analyzer:C:\\Analyzers\\Foo.dll", // excluded + "C:\\project\\Program.cs", // bare path, excluded + "/Users/dev/project/Generated.cs", // Unix absolute source path, excluded + "GeneratedFromRsp.cs", // already-portable source path, excluded + "/doc:obj/Debug/App.xml", // output path, preserved + "/out:obj/Debug/App.dll", // output path, preserved + "/refout:obj/Debug/refint/App.dll", // output path, preserved + "/pdb:obj/Debug/App.pdb", // output path, preserved + ]; + string content = Build(commandLineArguments: args); + + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains("[commandLineArguments]", normalized); + Assert.Contains("/nologo", normalized); + Assert.Contains("/langversion:preview", normalized); + Assert.DoesNotContain("/reference:", normalized); + Assert.DoesNotContain("/analyzer:", normalized); + Assert.DoesNotContain("Program.cs\n", normalized.Substring(normalized.IndexOf("[commandLineArguments]"))); + Assert.DoesNotContain("Generated.cs\n", normalized.Substring(normalized.IndexOf("[commandLineArguments]"))); + Assert.DoesNotContain("GeneratedFromRsp.cs\n", normalized.Substring(normalized.IndexOf("[commandLineArguments]"))); + Assert.Contains("/doc:obj/Debug/App.xml", normalized); + Assert.Contains("/out:obj/Debug/App.dll", normalized); + Assert.Contains("/refout:obj/Debug/refint/App.dll", normalized); + Assert.Contains("/pdb:obj/Debug/App.pdb", normalized); + } + + [Fact] + public void WriteCommandLineArgSection_FiltersPlatformArgs() + { + string content = Build(commandLineArguments: ["/nologo", "/platform:AnyCPU", "-platform:x86", "/langversion:preview"]); + + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains("/nologo", normalized); + Assert.Contains("/langversion:preview", normalized); + Assert.DoesNotContain("/platform:", normalized); + Assert.DoesNotContain("-platform:", normalized); + } + + [Fact] + public void WriteCommandLineArgSection_OrderPreserved() + { + string[] args = ["/langversion:preview", "/nologo", "/nullable+"]; + string content = Build(commandLineArguments: args); + + int lIdx = content.IndexOf("/langversion"); + int nIdx = content.IndexOf("/nologo"); + int qIdx = content.IndexOf("/nullable"); + Assert.True(lIdx < nIdx && nIdx < qIdx, "Argument order should be preserved"); + } + + [Fact] + public void WriteCommandLineArgSection_ExcludesMachineSpecificArgs() + { + string[] args = [ + "/nologo", + "/preferreduilang:en", // machine-specific, excluded + "/langversion:preview", + "-preferreduilang:de", // dash-prefix variant, excluded + "/nullable+", + ]; + string content = Build(commandLineArguments: args); + + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains("/nologo", normalized); + Assert.Contains("/langversion:preview", normalized); + Assert.Contains("/nullable+", normalized); + Assert.DoesNotContain("preferreduilang", normalized); + } + + [Fact] + public void WriteCommandLineArgSection_NormalizesNetCoreAppNoWarn8002() + { + string content = Build( + properties: [MakeItem("TargetFrameworkIdentifier", ".NETCoreApp")], + commandLineArguments: ["/nologo", "/nowarn:1701,1702"]); + + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains("/nowarn:1701,1702,8002", normalized); + } + + [Fact] + public void WriteCommandLineArgSection_DoesNotDuplicateNoWarn8002() + { + string content = Build( + properties: [MakeItem("TargetFrameworkIdentifier", ".NETCoreApp")], + commandLineArguments: ["/nowarn:1701,8002,1702"]); + + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains("/nowarn:1701,8002,1702", normalized); + Assert.DoesNotContain("/nowarn:1701,8002,1702,8002", normalized); + } + + [Fact] + public void WriteCommandLineArgSection_DoesNotNormalizeNonNetCoreAppNoWarn8002() + { + string content = Build( + properties: [MakeItem("TargetFrameworkIdentifier", ".NETStandard")], + commandLineArguments: ["/nowarn:1701,1702"]); + + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains("/nowarn:1701,1702", normalized); + Assert.DoesNotContain("8002", normalized); + } + + [Theory] + [InlineData("/platform:AnyCPU")] + [InlineData("/platform:x86")] + [InlineData("/platform:x64")] + [InlineData("/platform:anycpu32bitpreferred")] + [InlineData("-platform:arm64")] + public void WriteCommandLineArgSection_SkipsNetFrameworkPlatform(string platformArgument) + { + string content = Build( + properties: [MakeItem("TargetFrameworkIdentifier", ".NETFramework")], + commandLineArguments: ["/nologo", platformArgument]); + + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains("/nologo", normalized); + Assert.DoesNotContain(platformArgument, normalized); + } + + [Fact] + public void WriteCommandLineArgSection_SkipsNetCoreAppPlatform() + { + string content = Build( + properties: [MakeItem("TargetFrameworkIdentifier", ".NETCoreApp")], + commandLineArguments: ["/platform:x64"]); + + string normalized = content.Replace("\r\n", "\n"); + Assert.DoesNotContain("/platform:x64", normalized); + } + + [Fact] + public void EmitCompressed_SharedPrefix_CollapsesSingleChildChain() + { + var paths = new List + { + "/foo/1.0/lib/net8.0/a.dll", + "/foo/1.0/lib/net8.0/b.dll", + }; + var sb = new StringBuilder(); + ProjectDataWriter.EmitCompressed(sb, paths, 0); + string result = sb.ToString().Replace("\r\n", "\n"); + + // Single-child directory chain collapses onto one header line; the two + // sibling files nest under it. + string expected = + "/foo/1.0/lib/net8.0/\n" + + " a.dll\n" + + " b.dll\n"; + Assert.Equal(expected, result); + } + + [Fact] + public void EmitCompressed_NoSharedPrefix_EmitsFlatCollapsedSingletonsInOrder() + { + var paths = new List { "/a.dll", "/b.dll" }; + var sb = new StringBuilder(); + ProjectDataWriter.EmitCompressed(sb, paths, 0); + string result = sb.ToString().Replace("\r\n", "\n"); + + // A path that is a single linear directory chain ending in one file + // collapses back to one file line. + string expected = + "/b.dll\n" + + "/a.dll\n"; + Assert.Equal(expected, result); + } + + [Fact] + public void EmitCompressed_SiblingFileAndSubdirectory_EmitsDirectoriesFirstThenFiles() + { + var paths = new List + { + "Contracts/CultureInfoFormatter.cs", + "Contracts/DataModel/DataModelReadiness.cs", + "Contracts/DataModel/Project.cs", + "Contracts/EnvironmentMutationResult.cs", + }; + paths.Sort(StringComparer.OrdinalIgnoreCase); + var sb = new StringBuilder(); + ProjectDataWriter.EmitCompressed(sb, paths, 0); + string result = sb.ToString().Replace("\r\n", "\n"); + + // Single Contracts/ block; directories first then files, with each group sorted. + string expected = + "Contracts/\n" + + " DataModel/\n" + + " DataModelReadiness.cs\n" + + " Project.cs\n" + + " CultureInfoFormatter.cs\n" + + " EnvironmentMutationResult.cs\n"; + Assert.Equal(expected, result); + Assert.Equal(1, CountOccurrences(result, "Contracts/\n")); + } + + [Fact] + public void EmitCompressed_SingleFileDeepPath_CollapsesToFileLine() + { + var paths = new List { "a/b/c/only.cs" }; + var sb = new StringBuilder(); + ProjectDataWriter.EmitCompressed(sb, paths, 0); + string result = sb.ToString().Replace("\r\n", "\n"); + + // Single-child directory chain ending in one file collapses to one file line. + string expected = "a/b/c/only.cs\n"; + Assert.Equal(expected, result); + } + + [Fact] + public void EmitCompressed_CollapsedDirectoryEntries_SortWithDirectories() + { + var paths = new List + { + "/system.drawing.common/10.0.5/lib/net10.0/System.Drawing.Common.dll", + "/system.drawing.common/10.0.5/lib/net10.0/System.Private.Windows.Core.dll", + "/google.protobuf/3.22.5/lib/net5.0/Google.Protobuf.dll", + "/microsoft.dotnet.cecil/0.11.5-preview.26160.112/lib/netstandard2.0/Mono.Cecil.dll", + "/microsoft.dotnet.cecil/0.11.5-preview.26160.112/lib/netstandard2.0/Mono.Cecil.Rocks.dll", + }; + paths.Sort(StringComparer.OrdinalIgnoreCase); + var sb = new StringBuilder(); + ProjectDataWriter.EmitCompressed(sb, paths, 0); + string result = sb.ToString().Replace("\r\n", "\n"); + + string expected = + "/\n" + + " google.protobuf/3.22.5/lib/net5.0/Google.Protobuf.dll\n" + + " microsoft.dotnet.cecil/0.11.5-preview.26160.112/lib/netstandard2.0/\n" + + " Mono.Cecil.dll\n" + + " Mono.Cecil.Rocks.dll\n" + + " system.drawing.common/10.0.5/lib/net10.0/\n" + + " System.Drawing.Common.dll\n" + + " System.Private.Windows.Core.dll\n"; + Assert.Equal(expected, result); + } + + [Fact] + public void EmitCompressed_RemovingFile_DoesNotReshapeOtherBranches() + { + var withExtra = new List + { + "alpha/one.cs", + "alpha/two.cs", + "beta/three.cs", + }; + var withoutExtra = new List + { + "alpha/one.cs", + "beta/three.cs", + }; + withExtra.Sort(StringComparer.OrdinalIgnoreCase); + withoutExtra.Sort(StringComparer.OrdinalIgnoreCase); + + var sb1 = new StringBuilder(); + ProjectDataWriter.EmitCompressed(sb1, withExtra, 0); + var sb2 = new StringBuilder(); + ProjectDataWriter.EmitCompressed(sb2, withoutExtra, 0); + string r1 = sb1.ToString().Replace("\r\n", "\n"); + string r2 = sb2.ToString().Replace("\r\n", "\n"); + + // beta/ has a single file in both shapes and collapses identically. + Assert.Contains("beta/three.cs\n", r1); + Assert.Contains("beta/three.cs\n", r2); + // alpha/ remains grouped while it has two files, and collapses to a file + // line when only one file remains. + Assert.Equal(1, CountOccurrences(r1, "alpha/\n")); + Assert.Contains("alpha/one.cs\n", r2); + } + + [Fact] + public void EmitCompressed_RoundTripsThroughReader() + { + var paths = new List + { + "Contracts/CultureInfoFormatter.cs", + "Contracts/DataModel/DataModelReadiness.cs", + "Contracts/DataModel/Project.cs", + "Contracts/EnvironmentMutationResult.cs", + "Program.cs", + "/foo/1.0/lib/net8.0/Foo.dll", + "/foo/1.0/lib/net8.0/Bar.dll", + }; + paths.Sort(StringComparer.OrdinalIgnoreCase); + var sb = new StringBuilder(); + ProjectDataWriter.EmitCompressed(sb, paths, 0); + string emitted = sb.ToString().Replace("\r\n", "\n"); + + // Inline expansion mirroring CacheFileReader.ExpandCompressedPaths so the + // writer-tests project does not need to depend on the cache-reader assembly. + var stack = new Stack<(int Indent, string Prefix)>(); + var rebuilt = new List(); + foreach (string raw in emitted.Split('\n', StringSplitOptions.RemoveEmptyEntries)) + { + int indent = 0; + while (indent < raw.Length && raw[indent] == ' ') indent++; + string content = raw[indent..]; + while (stack.Count > 0 && stack.Peek().Indent >= indent) stack.Pop(); + string prefix = stack.Count > 0 ? stack.Peek().Prefix : ""; + if (content.Length > 0 && content[^1] == '/') + stack.Push((indent, prefix + content)); + else + rebuilt.Add(prefix + content); + } + rebuilt.Sort(StringComparer.OrdinalIgnoreCase); + Assert.Equal(paths, rebuilt); + } + + private static int CountOccurrences(string haystack, string needle) + { + int count = 0; + int idx = 0; + while ((idx = haystack.IndexOf(needle, idx, StringComparison.Ordinal)) >= 0) + { + count++; + idx += needle.Length; + } + return count; + } + + [Fact] + public void BuildContent_UsesLfLineEndings() + { + string content = Build(commandLineArguments: ["/noconfig"]); + + Assert.Contains("\n", content); + Assert.DoesNotContain("\r\n", content); + } + + [Fact] + public void AtomicWrite_TmpFileGoneAfterSuccess() + { + string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputPath = Path.Combine(dir, "test.lscache"); + try + { + ProjectDataWriter.AtomicWrite(outputPath, "hello"); + + Assert.True(File.Exists(outputPath)); + Assert.Empty(Directory.GetFiles(dir, "*.tmp")); + Assert.Equal("hello", File.ReadAllText(outputPath)); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public void AtomicWrite_PlacesTempFileInRequestedDirectory_NotNextToOutput() + { + string root = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputDir = Path.Combine(root, "src"); + string tempDir = Path.Combine(root, "obj"); // does not exist yet + string outputPath = Path.Combine(outputDir, "test.lscache"); + try + { + Directory.CreateDirectory(outputDir); + + ProjectDataWriter.AtomicWrite(outputPath, "hello", tempDir); + + // The requested temp directory is created and used, so the transient .tmp side-file never + // appears next to the (committed, watched) output file. + Assert.True(Directory.Exists(tempDir)); + Assert.Empty(Directory.GetFiles(outputDir, "*.tmp")); + Assert.Empty(Directory.GetFiles(tempDir, "*.tmp")); + Assert.Equal("hello", File.ReadAllText(outputPath)); + } + finally + { + Directory.Delete(root, recursive: true); + } + } + + [Fact] + public void AtomicWriteStreamed_PlacesTempFileInRequestedDirectory_NotNextToOutput() + { + string root = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputDir = Path.Combine(root, "src"); + string tempDir = Path.Combine(root, "obj"); // does not exist yet + string outputPath = Path.Combine(outputDir, "test.lscache"); + try + { + Directory.CreateDirectory(outputDir); + + ProjectDataWriter.AtomicWriteStreamed(outputPath, writer => writer.Write("version=2\n"), tempDir); + + Assert.True(Directory.Exists(tempDir)); + Assert.Empty(Directory.GetFiles(outputDir, "*.tmp")); + Assert.Empty(Directory.GetFiles(tempDir, "*.tmp")); + Assert.Equal("version=2\n", File.ReadAllText(outputPath)); + } + finally + { + Directory.Delete(root, recursive: true); + } + } + + [Fact] + public void AtomicWrite_FallsBackToOutputDirectory_WhenTempDirectoryUnusable() + { + string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputPath = Path.Combine(dir, "test.lscache"); + try + { + Directory.CreateDirectory(dir); + + // An unusable temp directory (invalid path) must not throw: the write degrades to the + // output directory and still succeeds. + ProjectDataWriter.AtomicWrite(outputPath, "hello", "bad\0dir"); + + Assert.Equal("hello", File.ReadAllText(outputPath)); + Assert.Empty(Directory.GetFiles(dir, "*.tmp")); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public void AtomicWrite_ReplacesExistingFile() + { + string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputPath = Path.Combine(dir, "test.lscache"); + try + { + Directory.CreateDirectory(dir); + File.WriteAllText(outputPath, "old"); + + ProjectDataWriter.AtomicWrite(outputPath, "new"); + + Assert.Empty(Directory.GetFiles(dir, "*.tmp")); + Assert.Equal("new", File.ReadAllText(outputPath)); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public void AtomicWrite_SkipsRewrite_WhenContentMatches() + { + string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputPath = Path.Combine(dir, "test.lscache"); + try + { + ProjectDataWriter.AtomicWrite(outputPath, "same"); + DateTime lastWriteTime = File.GetLastWriteTimeUtc(outputPath); + + Thread.Sleep(1100); + ProjectDataWriter.AtomicWrite(outputPath, "same"); + + Assert.Equal(lastWriteTime, File.GetLastWriteTimeUtc(outputPath)); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public void AtomicWriteStreamed_SkipsRewrite_WhenOnlyMinorVersionIncreases() + { + string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputPath = Path.Combine(dir, "test.lscache"); + const string ExistingContent = "version=2.1\n[project]\nlanguage=C#\n"; + try + { + Directory.CreateDirectory(dir); + File.WriteAllText(outputPath, ExistingContent, new UTF8Encoding(false)); + File.SetLastWriteTimeUtc(outputPath, new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + DateTime lastWriteTime = File.GetLastWriteTimeUtc(outputPath); + + ProjectDataWriter.AtomicWriteStreamed(outputPath, writer => + { + writer.WriteLine("version=2.2"); + writer.WriteLine("[project]"); + writer.WriteLine("language=C#"); + }); + + Assert.Equal(ExistingContent, File.ReadAllText(outputPath)); + Assert.Equal(lastWriteTime, File.GetLastWriteTimeUtc(outputPath)); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public void AtomicWriteStreamed_Rewrites_WhenNewMinorAddsData() + { + string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputPath = Path.Combine(dir, "test.lscache"); + try + { + Directory.CreateDirectory(dir); + File.WriteAllText(outputPath, "version=2.1\n[properties]\nAssemblyName=Sample\n", new UTF8Encoding(false)); + + ProjectDataWriter.AtomicWriteStreamed(outputPath, writer => + { + writer.WriteLine("version=2.2"); + writer.WriteLine("[properties]"); + writer.WriteLine("AssemblyName=Sample"); + writer.WriteLine("IsTestProject=true"); + }); + + Assert.Equal( + "version=2.2\n[properties]\nAssemblyName=Sample\nIsTestProject=true\n", + File.ReadAllText(outputPath)); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public void AtomicWrite_StripsLegacyHashHeader_ThenStable() + { + string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputPath = Path.Combine(dir, "test.lscache"); + try + { + Directory.CreateDirectory(dir); + // A pre-migration file still carries a leading "hash=" line. + File.WriteAllText(outputPath, $"hash={new string('0', 64)}\nsame", new UTF8Encoding(false)); + + // First write strips the legacy header even though the body is unchanged. + ProjectDataWriter.AtomicWrite(outputPath, "same"); + Assert.Equal("same", File.ReadAllText(outputPath)); + + // Second write is a no-op now that the header is gone. + DateTime lastWriteTime = File.GetLastWriteTimeUtc(outputPath); + Thread.Sleep(1100); + ProjectDataWriter.AtomicWrite(outputPath, "same"); + Assert.Equal(lastWriteTime, File.GetLastWriteTimeUtc(outputPath)); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public void AtomicWrite_NormalizesLineEndingsBeforeWriting() + { + string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputPath = Path.Combine(dir, "test.lscache"); + try + { + ProjectDataWriter.AtomicWrite(outputPath, "hello\r\nworld\r\n"); + + Assert.Equal("hello\nworld\n", File.ReadAllText(outputPath)); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public void AtomicWriteStreamed_UsesLfLineEndings_NoHashHeader() + { + string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputPath = Path.Combine(dir, "test.lscache"); + try + { + ProjectDataWriter.AtomicWriteStreamed(outputPath, writer => + { + writer.WriteLine("version=2"); + writer.WriteLine("[project]"); + }); + + Assert.Equal("version=2\n[project]\n", File.ReadAllText(outputPath)); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + [Theory] + [InlineData("a\r\nb\r\nc", "a\nb\nc")] // CRLF + [InlineData("a\rb\rc", "a\nb\nc")] // lone CR + [InlineData("a\r\r\nb", "a\n\nb")] // CR immediately followed by CRLF + [InlineData("\r\n\r\n", "\n\n")] // only line endings + [InlineData("no-endings-here", "no-endings-here")] // fast path: no CR at all + [InlineData("", "")] // empty + public void AtomicWriteStreamed_NormalizesEmbeddedLineEndings(string input, string expected) + { + string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputPath = Path.Combine(dir, "test.lscache"); + try + { + ProjectDataWriter.AtomicWriteStreamed(outputPath, writer => writer.Write(input)); + + Assert.Equal(expected, File.ReadAllText(outputPath)); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public void AtomicWriteStreamed_NormalizesAndGrowsBeyondInitialBuffer() + { + // Render well past the pooled writer's initial capacity so the buffer-growth path is + // exercised alongside in-place line-ending normalization. + var sb = new StringBuilder(); + for (int i = 0; i < 5000; i++) + sb.Append("reference/path/segment/Some.Package.Name.").Append(i).Append(".dll\r\n"); + string input = sb.ToString(); + string expected = input.Replace("\r\n", "\n"); + + string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputPath = Path.Combine(dir, "test.lscache"); + try + { + ProjectDataWriter.AtomicWriteStreamed(outputPath, writer => writer.Write(input)); + + Assert.Equal(expected, File.ReadAllText(outputPath)); + Assert.True(input.Length > 4096, "input should exceed the initial pooled buffer to exercise growth"); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public void AtomicWriteStreamed_StripsLegacyHashHeader() + { + string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string outputPath = Path.Combine(dir, "test.lscache"); + string body = "version=2\n[project]\n"; + try + { + Directory.CreateDirectory(dir); + File.WriteAllText(outputPath, $"hash={new string('0', 64)}\n{body}", new UTF8Encoding(false)); + + ProjectDataWriter.AtomicWriteStreamed(outputPath, writer => + { + writer.WriteLine("version=2"); + writer.WriteLine("[project]"); + }); + + Assert.Equal(body, File.ReadAllText(outputPath)); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public void WriteSourceFileSection_EmitsLinkMetadata() + { + var item = new Mock(); + item.Setup(i => i.ItemSpec).Returns(@"C:\project\Shared\Feature.cs"); + item.Setup(i => i.GetMetadata("Link")).Returns(@"Linked\Feature.cs"); + + string content = Build(sourceFiles: [item.Object]); + string normalized = content.Replace("\r\n", "\n"); + + Assert.Contains("[sourceFiles]", normalized); + Assert.Contains("Shared/Feature.cs\n", normalized); + Assert.Contains(" @link=Linked/Feature.cs\n", normalized); + } + + [Fact] + public void WriteSourceFileSection_OmitsLinkMetadata_WhenLinkIsEmpty() + { + string content = Build(sourceFiles: [MakeItem(@"C:\project\Program.cs")]); + string normalized = content.Replace("\r\n", "\n"); + + Assert.Contains("[sourceFiles]", normalized); + Assert.Contains("Program.cs\n", normalized); + Assert.DoesNotContain("@link=", normalized); + } + + [Fact] + public void WriteSourceFileSection_DedupesItemsCollapsingToSamePortableForm() + { + // MSBuild evaluation can produce two ``ITaskItem``s with different ``ItemSpec``s + // that resolve to the same portable form (e.g. a wildcard ```` + // plus an explicit ```` with custom metadata, or two + // items differing only in casing on a case-insensitive file system). Without + // deduplication the same path is emitted twice — bloating the cache and causing + // the reader to materialize duplicate ``CachedSourceFile`` entries. + string projectPath = Path.Combine(Path.GetTempPath(), "projectdata-writer-tests", "App.csproj"); + string projectDir = Path.GetDirectoryName(projectPath)!; + ITaskItem first = MakeItem(Path.Combine(projectDir, "Program.cs")); + ITaskItem duplicate = MakeItem(Path.Combine(projectDir, "program.cs")); + List diagnostics = []; + + string content = Build(projectPath: projectPath, sourceFiles: [first, duplicate], duplicateItemReporter: diagnostics.Add); + string normalized = content.Replace("\r\n", "\n"); + + int matches = CountOccurrencesInSection(normalized, "[sourceFiles]", "Program.cs"); + Assert.Equal(OperatingSystem.IsLinux() ? 2 : 1, matches); + if (OperatingSystem.IsLinux()) + { + Assert.Empty(diagnostics); + } + else + { + ProjectDataDuplicateItemDiagnostic diagnostic = Assert.Single(diagnostics); + Assert.Equal(projectPath, diagnostic.ProjectFilePath); + Assert.Equal("sourceFiles", diagnostic.Section); + Assert.EndsWith("Program.cs", diagnostic.ItemSpec, StringComparison.OrdinalIgnoreCase); + } + } + + [Fact] + public void WriteMetadataRefSection_DedupesItemsCollapsingToSamePortableForm() + { + // Same concern as the source-file case: two ``MetadataReference`` items that + // collapse to the same portable form should be emitted only once. While + // ``PrepareMetadataRefs`` upstream handles most dedup, ``EmitMetadataRefSection`` + // is the wire-format boundary and must not assume its caller dedups. + string projectDir = Path.GetDirectoryName(Path.Combine(Path.GetTempPath(), "projectdata-writer-tests", "App.csproj"))!; + ITaskItem first = MakeItem(Path.Combine(projectDir, "ref", "Foo.dll")); + ITaskItem duplicate = MakeItem(Path.Combine(projectDir, "ref", "foo.dll")); + + string content = Build(metadataReferences: [first, duplicate]); + string normalized = content.Replace("\r\n", "\n"); + + int matches = CountOccurrencesInSection(normalized, "[metadataReferences]", "Foo.dll"); + Assert.Equal(OperatingSystem.IsLinux() ? 2 : 1, matches); + } + + [Fact] + public void WriteAnalyzerReferenceSection_DedupesItemsCollapsingToSamePortableForm() + { + string projectDir = Path.GetDirectoryName(Path.Combine(Path.GetTempPath(), "projectdata-writer-tests", "App.csproj"))!; + ITaskItem first = MakeItem(Path.Combine(projectDir, "analyzers", "PolyType.SourceGenerator.dll")); + ITaskItem duplicate = MakeItem(Path.Combine(projectDir, "analyzers", "PolyType.SourceGenerator.dll")); + + string content = Build(analyzerReferences: [first, duplicate]); + string normalized = content.Replace("\r\n", "\n"); + + int matches = CountOccurrencesInSection(normalized, "[analyzerReferences]", "PolyType.SourceGenerator.dll"); + Assert.Equal(1, matches); + } + + [Fact] + public void WriteAnalyzerReferenceSection_ReportsDuplicateItems() + { + string projectPath = Path.Combine(Path.GetTempPath(), "projectdata-writer-tests", "App.csproj"); + string projectDir = Path.GetDirectoryName(projectPath)!; + ITaskItem first = MakeItem(Path.Combine(projectDir, "analyzers", "PolyType.SourceGenerator.dll")); + ITaskItem duplicate = MakeItem(Path.Combine(projectDir, "analyzers", "PolyType.SourceGenerator.dll")); + List diagnostics = []; + + Build(projectPath: projectPath, analyzerReferences: [first, duplicate], duplicateItemReporter: diagnostics.Add); + + ProjectDataDuplicateItemDiagnostic diagnostic = Assert.Single(diagnostics); + Assert.Equal(projectPath, diagnostic.ProjectFilePath); + Assert.Equal("analyzerReferences", diagnostic.Section); + Assert.EndsWith("analyzers/PolyType.SourceGenerator.dll", diagnostic.ItemSpec, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void WriteAnalyzerReferenceSection_UsesPlatformPathCaseSensitivityForDedupe() + { + string projectPath = Path.Combine(Path.GetTempPath(), "projectdata-writer-tests", "App.csproj"); + string projectDir = Path.GetDirectoryName(projectPath)!; + ITaskItem first = MakeItem(Path.Combine(projectDir, "analyzers", "CaseSensitive.dll")); + ITaskItem second = MakeItem(Path.Combine(projectDir, "analyzers", "casesensitive.dll")); + List diagnostics = []; + + string content = Build(projectPath: projectPath, analyzerReferences: [first, second], duplicateItemReporter: diagnostics.Add); + string normalized = content.Replace("\r\n", "\n"); + + int matches = CountOccurrencesInSection(normalized, "[analyzerReferences]", "casesensitive.dll"); + if (OperatingSystem.IsLinux()) + { + Assert.Equal(2, matches); + Assert.Empty(diagnostics); + } + else + { + Assert.Equal(1, matches); + Assert.Single(diagnostics); + } + } + + [Fact] + public void WriteAnalyzerReferenceSection_UsesPlatformPathCaseSensitivityForCompressedDirectories() + { + string projectPath = Path.Combine(Path.GetTempPath(), "projectdata-writer-tests", "App.csproj"); + string projectDir = Path.GetDirectoryName(projectPath)!; + ITaskItem first = MakeItem(Path.Combine(projectDir, "Analyzers", "CaseSensitive.dll")); + ITaskItem second = MakeItem(Path.Combine(projectDir, "analyzers", "casesensitive.dll")); + List diagnostics = []; + + string content = Build(projectPath: projectPath, analyzerReferences: [first, second], duplicateItemReporter: diagnostics.Add); + string normalized = content.Replace("\r\n", "\n"); + + Assert.Contains("Analyzers/CaseSensitive.dll\n", normalized); + if (OperatingSystem.IsLinux()) + { + Assert.Contains("analyzers/casesensitive.dll\n", normalized); + Assert.Empty(diagnostics); + } + else + { + Assert.DoesNotContain("analyzers/casesensitive.dll\n", normalized); + Assert.Single(diagnostics); + } + } + + // Counts case-insensitive occurrences of ``needle`` between ``sectionHeader`` and + // the next section header (or end of content). + private static int CountOccurrencesInSection(string content, string sectionHeader, string needle) + { + int sectionStart = content.IndexOf(sectionHeader + "\n", StringComparison.Ordinal); + Assert.NotEqual(-1, sectionStart); + string sectionTail = content[(sectionStart + sectionHeader.Length + 1)..]; + int sectionEnd = sectionTail.IndexOf("\n[", StringComparison.Ordinal); + string section = sectionEnd >= 0 ? sectionTail[..sectionEnd] : sectionTail; + + int count = 0; + int idx = 0; + while ((idx = section.IndexOf(needle, idx, StringComparison.OrdinalIgnoreCase)) >= 0) + { + count++; + idx += needle.Length; + } + return count; + } + + [Fact] + public void WriteMetadataRefSection_EmitsAliases() + { + var item = new Mock(); + item.Setup(i => i.ItemSpec).Returns(@"C:\project\ref\Interop.dll"); + item.Setup(i => i.GetMetadata("Aliases")).Returns("MyAlias"); + item.Setup(i => i.GetMetadata("EmbedInteropTypes")).Returns("false"); + item.Setup(i => i.GetMetadata("Value")).Returns(string.Empty); + + string content = Build(metadataReferences: [item.Object]); + string normalized = content.Replace("\r\n", "\n"); + + Assert.Contains("[metadataReferences]", normalized); + Assert.Contains("@aliases=MyAlias", normalized); + } + + [Fact] + public void WriteMetadataRefSection_EmitsEmbedInteropTypes() + { + var item = new Mock(); + item.Setup(i => i.ItemSpec).Returns(@"C:\project\ref\Interop.dll"); + item.Setup(i => i.GetMetadata("Aliases")).Returns("global"); + item.Setup(i => i.GetMetadata("EmbedInteropTypes")).Returns("true"); + item.Setup(i => i.GetMetadata("Value")).Returns(string.Empty); + + string content = Build(metadataReferences: [item.Object]); + string normalized = content.Replace("\r\n", "\n"); + + Assert.Contains("@embedInteropTypes", normalized); + Assert.DoesNotContain("@aliases=", normalized); // "global" is not emitted + } + + #region Framework packs + + [Fact] + public void TryExtractRefPackName_RecognizesValidPath() + { + Assert.Equal( + "Microsoft.NETCore.App.Ref", + ProjectDataWriter.TryExtractRefPackName("/packs/Microsoft.NETCore.App.Ref/10.0.7/ref/net10.0/System.Runtime.dll")); + } + + [Fact] + public void TryExtractRefPackName_ReturnsNullForNonPackPaths() + { + Assert.Null(ProjectDataWriter.TryExtractRefPackName("/foo/1.0/lib/net10.0/Foo.dll")); + Assert.Null(ProjectDataWriter.TryExtractRefPackName("/microsoft.netcore.app.ref/8.0.26/ref/net8.0/System.Runtime.dll")); + Assert.Null(ProjectDataWriter.TryExtractRefPackName("/sdk/9.0.100/Sdks/Microsoft.NET.Sdk/analyzers/x.dll")); + Assert.Null(ProjectDataWriter.TryExtractRefPackName("/packs/Microsoft.Android.Ref.36/36.0.0/ref/net10.0/Mono.Android.dll")); + Assert.Null(ProjectDataWriter.TryExtractRefPackName("/packs/Microsoft.iOS.Ref.net10.0_26.5/26.5.0/ref/net10.0/Microsoft.iOS.dll")); + Assert.Null(ProjectDataWriter.TryExtractRefPackName("/packs/Microsoft.MacCatalyst.Ref.net10.0_26.5/26.5.0/ref/net10.0/Microsoft.MacCatalyst.dll")); + Assert.Null(ProjectDataWriter.TryExtractRefPackName("/packs/Foo")); // no version segment + Assert.Null(ProjectDataWriter.TryExtractRefPackName("/packs/Foo/1.0")); // no path under version + Assert.Null(ProjectDataWriter.TryExtractRefPackName("")); + Assert.Null(ProjectDataWriter.TryExtractRefPackName(null)); + } + + [Fact] + public void WriteFrameworkPacksSection_EmitsSortedDistinctNames() + { + var sb = new StringBuilder(); + var packs = new SortedSet(StringComparer.OrdinalIgnoreCase) + { + "Microsoft.NETCore.App.Ref", + "Microsoft.AspNetCore.App.Ref", + }; + ProjectDataWriter.WriteFrameworkPacksSection(sb, packs); + string content = sb.ToString().Replace("\r\n", "\n"); + + Assert.Contains("[frameworkPacks]\n", content); + int aspIdx = content.IndexOf("Microsoft.AspNetCore.App.Ref"); + int netIdx = content.IndexOf("Microsoft.NETCore.App.Ref"); + Assert.True(aspIdx > 0 && netIdx > aspIdx, "Pack names must be sorted OrdinalIgnoreCase"); + } + + [Fact] + public void WriteFrameworkPacksSection_EmptyPacks_OmitsSection() + { + var sb = new StringBuilder(); + ProjectDataWriter.WriteFrameworkPacksSection(sb, new SortedSet()); + Assert.Equal(string.Empty, sb.ToString()); + } + + // Builds a resolver wired to a synthetic dotnet root so paths under that root + // get rewritten to /... portable form for testing the prepare helpers. + private static (CachePathResolver Resolver, string DotNetRoot) MakeSyntheticResolver(string projectDir) + { + // Use the project dir's drive root as the synthetic dotnet root: any path under it + // that starts with "\\packs\\..." will be classified as /packs/... + string dotnetRoot = Path.Combine(projectDir, "fakedotnet") + Path.DirectorySeparatorChar; + var resolver = new CachePathResolver( + projectDir: projectDir, + nugetFolders: [Path.Combine(projectDir, "fakenuget") + Path.DirectorySeparatorChar], + dotnetRoots: [dotnetRoot], + netFxRefRoot: null); + return (resolver, dotnetRoot); + } + + private static (CachePathResolver Resolver, string DotNetRoot, string NuGetRoot) MakeSyntheticResolverWithRoots(string projectDir) + { + string dotnetRoot = Path.Combine(projectDir, "fakedotnet") + Path.DirectorySeparatorChar; + string nugetRoot = Path.Combine(projectDir, "fakenuget") + Path.DirectorySeparatorChar; + var resolver = new CachePathResolver( + projectDir: projectDir, + nugetFolders: [nugetRoot], + dotnetRoots: [dotnetRoot], + netFxRefRoot: null); + return (resolver, dotnetRoot, nugetRoot); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void ToPortable_PreservesNullOrEmptyInput(string? inputPath) + { + var resolver = new CachePathResolver(Path.Combine(Path.GetTempPath(), "proj"), [], [], null); + + Assert.Equal(inputPath, resolver.ToPortable(inputPath!)); + } + + [Fact] + public void PrepareMetadataRefs_DivertsPackEntriesAndKeepsOthers() + { + string projectDir = Path.Combine(Path.GetTempPath(), "proj"); + (CachePathResolver resolver, string dotnetRoot) = MakeSyntheticResolver(projectDir); + ITaskItem[] items = + [ + MakeItem(Path.Combine(dotnetRoot, "packs", "Microsoft.NETCore.App.Ref", "10.0.7", "ref", "net10.0", "System.Runtime.dll")), + MakeItem(Path.Combine(dotnetRoot, "packs", "Microsoft.AspNetCore.App.Ref", "10.0.7", "ref", "net10.0", "Microsoft.AspNetCore.dll")), + MakeItem(Path.Combine(projectDir, "bin", "App.dll")), + ]; + var packs = new SortedSet(StringComparer.OrdinalIgnoreCase); + List> prepared = ProjectDataWriter.PrepareMetadataRefs(items, resolver, packs); + + Assert.Equal(2, packs.Count); + Assert.Contains("Microsoft.NETCore.App.Ref", packs); + Assert.Contains("Microsoft.AspNetCore.App.Ref", packs); + Assert.Single(prepared); // only the non-pack ref survives + Assert.DoesNotContain("packs/", prepared[0].Key, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void PrepareMetadataRefs_KeepsWorkloadPackEntriesExplicit() + { + string projectDir = Path.Combine(Path.GetTempPath(), "proj"); + (CachePathResolver resolver, string dotnetRoot) = MakeSyntheticResolver(projectDir); + ITaskItem[] items = + [ + MakeItem(Path.Combine(dotnetRoot, "packs", "Microsoft.Android.Ref.36", "36.0.0", "ref", "net10.0", "Mono.Android.dll")), + MakeItem(Path.Combine(dotnetRoot, "packs", "Microsoft.iOS.Ref.net10.0_26.5", "26.5.0", "ref", "net10.0", "Microsoft.iOS.dll")), + MakeItem(Path.Combine(dotnetRoot, "packs", "Microsoft.MacCatalyst.Ref.net10.0_26.5", "26.5.0", "ref", "net10.0", "Microsoft.MacCatalyst.dll")), + ]; + var packs = new SortedSet(StringComparer.OrdinalIgnoreCase); + + List> prepared = ProjectDataWriter.PrepareMetadataRefs(items, resolver, packs); + + Assert.Empty(packs); + Assert.Equal(3, prepared.Count); + Assert.Contains(prepared, item => item.Key.EndsWith("Mono.Android.dll", StringComparison.OrdinalIgnoreCase)); + Assert.Contains(prepared, item => item.Key.EndsWith("Microsoft.iOS.dll", StringComparison.OrdinalIgnoreCase)); + Assert.Contains(prepared, item => item.Key.EndsWith("Microsoft.MacCatalyst.dll", StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public void PrepareAnalyzerRefs_DivertsPackEntriesAndKeepsOthers() + { + string projectDir = Path.Combine(Path.GetTempPath(), "proj"); + (CachePathResolver resolver, string dotnetRoot) = MakeSyntheticResolver(projectDir); + ITaskItem[] items = + [ + MakeItem(Path.Combine(dotnetRoot, "packs", "Microsoft.NETCore.App.Ref", "10.0.7", "analyzers", "dotnet", "cs", "x.dll")), + MakeItem(Path.Combine(dotnetRoot, "sdk", "9.0.100", "Sdks", "Microsoft.NET.Sdk", "analyzers", "Foo.dll")), + ]; + var packs = new SortedSet(StringComparer.OrdinalIgnoreCase); + List prepared = ProjectDataWriter.PrepareAnalyzerRefs(items, resolver, packs); + + Assert.Single(packs); + Assert.Contains("Microsoft.NETCore.App.Ref", packs); + Assert.Single(prepared); // only the SDK-folder analyzer survives + // The SDK analyzer path is rewritten via the sentinel + // (the version segment is dropped — see CachePathResolver.RewriteSdkPath). + Assert.StartsWith("/", prepared[0]); + Assert.DoesNotContain("9.0.100", prepared[0]); + } + + [Fact] + public void PrepareRefs_UnifiesSdkAndNuGetResolvedFrameworkPacks() + { + // Regression test: the same canonical framework pack must end up in + // [frameworkPacks] regardless of whether MSBuild resolved it from + // /packs/ (SDK install) or / (NuGet download). Otherwise + // the cache contents would depend on which dotnet SDKs are installed + // on the writer's machine, producing environment-dependent churn. + string projectDir = Path.Combine(Path.GetTempPath(), "proj-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(projectDir); + try + { + (CachePathResolver resolver, string dotnetRoot, string nugetRoot) = MakeSyntheticResolverWithRoots(projectDir); + + // One pack resolved from SDK install, another (same canonical name) resolved from NuGet. + string sdkRef = Path.Combine(dotnetRoot, "packs", "Microsoft.AspNetCore.App.Ref", "8.0.20", "ref", "net8.0", "Microsoft.AspNetCore.dll"); + string nugetRef = Path.Combine(nugetRoot, "microsoft.netcore.app.ref", "8.0.26", "ref", "net8.0", "System.Runtime.dll"); + + var frameworkPacks = new SortedSet(StringComparer.OrdinalIgnoreCase); + List> prepared = ProjectDataWriter.PrepareMetadataRefs( + [MakeItem(sdkRef), MakeItem(nugetRef, nuGetPackageId: "Microsoft.NETCore.App.Ref", nuGetPackageVersion: "8.0.26", frameworkReferenceName: "Microsoft.NETCore.App")], + resolver, + frameworkPacks, + new ProjectDataWriter.TargetFramework("net8.0", null, "v8.0")); + + Assert.Equal(2, frameworkPacks.Count); + Assert.Contains("Microsoft.AspNetCore.App.Ref", frameworkPacks); + Assert.Contains("Microsoft.NETCore.App.Ref", frameworkPacks); + Assert.Empty(prepared); + } + finally + { + try { Directory.Delete(projectDir, recursive: true); } catch { } + } + } + + [Fact] + public void PrepareRefs_ClassifiesNuGetResolvedFrameworkPacksAsFrameworkPacks() + { + string projectDir = Path.Combine(Path.GetTempPath(), "proj-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(projectDir); + try + { + (CachePathResolver resolver, _, string nugetRoot) = MakeSyntheticResolverWithRoots(projectDir); + string packageRoot = Path.Combine(nugetRoot, "microsoft.netcore.app.ref", "8.0.26"); + string metadataRef = Path.Combine(packageRoot, "ref", "net8.0", "System.Runtime.dll"); + string analyzerRef = Path.Combine(packageRoot, "analyzers", "dotnet", "cs", "FrameworkAnalyzer.dll"); + string arbitraryNuGetRef = Path.Combine(nugetRoot, "some.package", "1.0.0", "lib", "net8.0", "Some.Package.dll"); + ITaskItem packMetadataRef = MakeItem(metadataRef, nuGetPackageId: "Microsoft.NETCore.App.Ref", nuGetPackageVersion: "8.0.26", frameworkReferenceName: "Microsoft.NETCore.App"); + ITaskItem packAnalyzerRef = MakeItem(analyzerRef, nuGetPackageId: "Microsoft.NETCore.App.Ref", nuGetPackageVersion: "8.0.26", frameworkReferenceName: "Microsoft.NETCore.App"); + + var frameworkPacks = new SortedSet(StringComparer.OrdinalIgnoreCase); + var targetFramework = new ProjectDataWriter.TargetFramework("net8.0", null, "v8.0"); + List> preparedMetadata = ProjectDataWriter.PrepareMetadataRefs( + [packMetadataRef, MakeItem(arbitraryNuGetRef)], + resolver, + frameworkPacks, + targetFramework); + List preparedAnalyzers = ProjectDataWriter.PrepareAnalyzerRefs( + [packAnalyzerRef], + resolver, + frameworkPacks, + new SortedSet(StringComparer.OrdinalIgnoreCase), + null, + targetFramework); + + Assert.Single(frameworkPacks); + Assert.Contains("Microsoft.NETCore.App.Ref", frameworkPacks); + Assert.Single(preparedMetadata); + Assert.Contains("some.package", preparedMetadata[0].Key, StringComparison.OrdinalIgnoreCase); + Assert.Empty(preparedAnalyzers); + } + finally + { + try { Directory.Delete(projectDir, recursive: true); } catch { } + } + } + + [Fact] + public void PrepareRefs_ClassifiesNuGetResolvedFrameworkPacksAsFrameworkPacks_WhenFrameworkReferenceMetadataIsMissing() + { + string projectDir = Path.Combine(Path.GetTempPath(), "proj-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(projectDir); + try + { + (CachePathResolver resolver, _, string nugetRoot) = MakeSyntheticResolverWithRoots(projectDir); + string packageRoot = Path.Combine(nugetRoot, "microsoft.netcore.app.ref", "8.0.26"); + string metadataRef = Path.Combine(packageRoot, "ref", "net8.0", "System.Runtime.dll"); + string arbitraryNuGetRef = Path.Combine(nugetRoot, "some.package", "1.0.0", "lib", "net8.0", "Some.Package.dll"); + + var frameworkPacks = new SortedSet(StringComparer.OrdinalIgnoreCase); + List> preparedMetadata = ProjectDataWriter.PrepareMetadataRefs( + [MakeItem(metadataRef), MakeItem(arbitraryNuGetRef)], + resolver, + frameworkPacks, + new ProjectDataWriter.TargetFramework("net8.0", null, "v8.0")); + + Assert.Single(frameworkPacks); + Assert.Contains("Microsoft.NETCore.App.Ref", frameworkPacks); + Assert.Single(preparedMetadata); + Assert.Contains("some.package", preparedMetadata[0].Key, StringComparison.OrdinalIgnoreCase); + } + finally + { + try { Directory.Delete(projectDir, recursive: true); } catch { } + } + } + + [Fact] + public void PrepareRefs_NormalizesNuGetPackageIdCasingToCanonicalName() + { + // Regression: NuGet preserves the casing from the package's .nuspec, which has + // historically varied across SDK versions and feeds (e.g. lowercase + // `microsoft.netcore.app.ref` on some restore paths vs PascalCase + // `Microsoft.NETCore.App.Ref` on others). Echoing that casing through to the + // cache reintroduces the very environment dependence this PR is eliminating. + // `TryExtractNuGetRefPackName` must always emit the canonical pack id regardless + // of what `NuGetPackageId` metadata supplies. + string projectDir = Path.Combine(Path.GetTempPath(), "proj-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(projectDir); + try + { + (CachePathResolver resolver, _, string nugetRoot) = MakeSyntheticResolverWithRoots(projectDir); + string packageRoot = Path.Combine(nugetRoot, "microsoft.netcore.app.ref", "8.0.26"); + string metadataRef = Path.Combine(packageRoot, "ref", "net8.0", "System.Runtime.dll"); + + var frameworkPacks = new SortedSet(StringComparer.Ordinal); + List> preparedMetadata = ProjectDataWriter.PrepareMetadataRefs( + [ + MakeItem( + metadataRef, + nuGetPackageId: "microsoft.netcore.app.ref", + nuGetPackageVersion: "8.0.26", + frameworkReferenceName: "Microsoft.NETCore.App"), + ], + resolver, + frameworkPacks, + new ProjectDataWriter.TargetFramework("net8.0", null, "v8.0")); + + // The frameworkPacks set is `StringComparer.Ordinal`, so the assertion below + // would fail if we emitted lowercase `microsoft.netcore.app.ref` from the + // metadata casing. + Assert.Single(frameworkPacks); + Assert.Contains("Microsoft.NETCore.App.Ref", frameworkPacks); + Assert.DoesNotContain("microsoft.netcore.app.ref", frameworkPacks); + } + finally + { + try { Directory.Delete(projectDir, recursive: true); } catch { } + } + } + + [Fact] + public void PrepareRefs_ClassifiesNuGetResolvedFrameworkPacksAsFrameworkPacks_WhenTargetFrameworkUsesCompactTfm() + { + string projectDir = Path.Combine(Path.GetTempPath(), "proj-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(projectDir); + try + { + (CachePathResolver resolver, _, string nugetRoot) = MakeSyntheticResolverWithRoots(projectDir); + string packageRoot = Path.Combine(nugetRoot, "microsoft.netcore.app.ref", "8.0.26"); + string metadataRef = Path.Combine(packageRoot, "ref", "net8.0", "System.Runtime.dll"); + + var frameworkPacks = new SortedSet(StringComparer.OrdinalIgnoreCase); + List> preparedMetadata = ProjectDataWriter.PrepareMetadataRefs( + [MakeItem(metadataRef)], + resolver, + frameworkPacks, + new ProjectDataWriter.TargetFramework("net8", null, "v8.0")); + + Assert.Single(frameworkPacks); + Assert.Contains("Microsoft.NETCore.App.Ref", frameworkPacks); + Assert.Empty(preparedMetadata); + } + finally + { + try { Directory.Delete(projectDir, recursive: true); } catch { } + } + } + + [Fact] + public void BuildContent_NuGetResolvedFrameworkPacksAppearInFrameworkPacksSectionBeforeMetadataReferences_AndPackEntriesAreFiltered() + { + string projectDir = Path.Combine(Path.GetTempPath(), "lscache-nuget-fpacks-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(projectDir); + string previousNuGet = Environment.GetEnvironmentVariable("NUGET_PACKAGES") ?? string.Empty; + try + { + string nugetRoot = Path.Combine(projectDir, "nuget"); + Environment.SetEnvironmentVariable("NUGET_PACKAGES", nugetRoot); + string projectFile = Path.Combine(projectDir, "App.csproj"); + string packDll = Path.Combine(nugetRoot, "microsoft.netcore.app.ref", "8.0.26", "ref", "net8.0", "System.Runtime.dll"); + string nugetDll = Path.Combine(nugetRoot, "foo", "1.0.0", "lib", "net8.0", "Foo.dll"); + + string content = ProjectDataWriter.BuildContent( + projectFilePath: projectFile, + writeHeader: true, + isPrimary: false, + lastDtbSucceeded: false, + sliceDimensions: [MakeItem("TargetFramework", "net8.0")], + properties: null, + commandLineArguments: null, + sourceFiles: null, + metadataReferences: [MakeItem(packDll, nuGetPackageId: "Microsoft.NETCore.App.Ref", nuGetPackageVersion: "8.0.26", frameworkReferenceName: "Microsoft.NETCore.App"), MakeItem(nugetDll)], + analyzerReferences: null, + analyzerConfigFiles: null, + additionalFiles: null, + projectReferences: null, + capabilities: null); + + int packsIdx = content.IndexOf("[frameworkPacks]"); + int metaIdx = content.IndexOf("[metadataReferences]"); + Assert.True(packsIdx > 0, "[frameworkPacks] section expected"); + Assert.True(metaIdx > packsIdx, "[frameworkPacks] must precede [metadataReferences]"); + Assert.Contains("Microsoft.NETCore.App.Ref", content); + Assert.DoesNotContain("[nugetFrameworkPacks]", content); + Assert.DoesNotContain("System.Runtime.dll", content); + Assert.DoesNotContain("8.0.26/ref/net8.0", content); + Assert.Contains("Foo.dll", content); + } + finally + { + Environment.SetEnvironmentVariable("NUGET_PACKAGES", previousNuGet); + try { Directory.Delete(projectDir, recursive: true); } catch { } + } + } + + [Fact] + public void PrepareMetadataRefs_CanonicalizesNetFrameworkReferenceAssembliesToNetFxRefMetadata() + { + string projectDir = Path.Combine(Path.GetTempPath(), "proj-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(projectDir); + try + { + string nugetRoot = Path.Combine(projectDir, "fakenuget") + Path.DirectorySeparatorChar; + string netFxRoot = Path.Combine(projectDir, "Reference Assemblies", "Microsoft", "Framework", ".NETFramework") + Path.DirectorySeparatorChar; + var resolver = new CachePathResolver(projectDir, [nugetRoot], [], netFxRoot); + ITaskItem developerPackRef = MakeItem(Path.Combine(netFxRoot, "v4.7.2", "mscorlib.dll")); + ITaskItem nugetRef = MakeItem(Path.Combine(nugetRoot, "microsoft.netframework.referenceassemblies.net472", "1.0.3", "build", ".NETFramework", "v4.7.2", "System.dll")); + ITaskItem packageRef = MakeItem(Path.Combine(nugetRoot, "some.package", "1.0.0", "lib", "net472", "Some.Package.dll")); + + var frameworkPacks = new SortedSet(StringComparer.OrdinalIgnoreCase); + List> prepared = ProjectDataWriter.PrepareMetadataRefs( + [developerPackRef, nugetRef, packageRef], + resolver, + frameworkPacks, + new ProjectDataWriter.TargetFramework("net472", ".NETFramework", "v4.7.2")); + + Assert.Empty(frameworkPacks); + Assert.Equal(["/v4.7.2/mscorlib.dll", "/v4.7.2/System.dll", "/some.package/1.0.0/lib/net472/Some.Package.dll"], prepared.Select(reference => reference.Key)); + } + finally + { + try { Directory.Delete(projectDir, recursive: true); } catch { } + } + } + + [Fact] + public void BuildContent_NetFrameworkReferenceAssembliesAreCanonicalMetadataReferences() + { + string projectDir = Path.Combine(Path.GetTempPath(), "lscache-netfx-refs-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(projectDir); + try + { + string netFxRoot = Path.Combine(projectDir, "refs", ".NETFramework") + Path.DirectorySeparatorChar; + string projectFile = Path.Combine(projectDir, "App.csproj"); + string mscorlib = Path.Combine(netFxRoot, "v4.7.2", "mscorlib.dll"); + string packageRef = Path.Combine(projectDir, "packages", "Some.Package.dll"); + + string content = ProjectDataWriter.BuildContent( + projectFilePath: projectFile, + writeHeader: true, + isPrimary: false, + lastDtbSucceeded: false, + sliceDimensions: [MakeItem("TargetFramework", "net472")], + properties: [MakeItem("TargetFrameworkIdentifier", ".NETFramework"), MakeItem("TargetFrameworkVersion", "v4.7.2")], + commandLineArguments: null, + sourceFiles: null, + metadataReferences: [MakeItem(mscorlib), MakeItem(packageRef)], + analyzerReferences: null, + analyzerConfigFiles: null, + additionalFiles: null, + projectReferences: null, + capabilities: null); + + int metaIdx = content.IndexOf("[metadataReferences]"); + Assert.True(metaIdx > 0, "[metadataReferences] section expected"); + Assert.DoesNotContain("[netFrameworkReferenceAssemblies]", content); + Assert.Contains("/v4.7.2/mscorlib.dll", content); + Assert.DoesNotContain("refs/.NETFramework/v4.7.2/mscorlib.dll", content); + Assert.Contains("Some.Package.dll", content); + } + finally + { + try { Directory.Delete(projectDir, recursive: true); } catch { } + } + } + + [Fact] + public void TryValidateNetFrameworkReferences_RejectsMissingBareFrameworkReferences() + { + string projectDir = Path.Combine(Path.GetTempPath(), "lscache-netfx-validation-" + Guid.NewGuid().ToString("N")); + string projectFile = Path.Combine(projectDir, "App.csproj"); + Directory.CreateDirectory(projectDir); + try + { + bool valid = ProjectDataWriter.TryValidateNetFrameworkReferences( + projectFile, + sliceDimensions: null, + properties: [MakeItem("TargetFrameworkIdentifier", ".NETFramework"), MakeItem("TargetFramework", "net472")], + metadataReferences: [MakeItem("mscorlib.dll")], + out string unsupportedReason); + + Assert.False(valid); + Assert.Equal("MissingNetFrameworkReferenceAssemblies", unsupportedReason); + } + finally + { + try { Directory.Delete(projectDir, recursive: true); } catch { } + } + } + + [Fact] + public void TryValidateNetFrameworkReferences_AcceptsExistingCanonicalReferences() + { + string projectDir = Path.Combine(Path.GetTempPath(), "lscache-netfx-validation-" + Guid.NewGuid().ToString("N")); + string projectFile = Path.Combine(projectDir, "App.csproj"); + string reference = Path.Combine(projectDir, "refs", ".NETFramework", "v4.7.2", "mscorlib.dll"); + Directory.CreateDirectory(Path.GetDirectoryName(reference)!); + File.WriteAllText(reference, string.Empty); + try + { + bool valid = ProjectDataWriter.TryValidateNetFrameworkReferences( + projectFile, + sliceDimensions: null, + properties: [MakeItem("TargetFrameworkIdentifier", ".NETFramework"), MakeItem("TargetFramework", "net472"), MakeItem("TargetFrameworkVersion", "v4.7.2")], + metadataReferences: [MakeItem(reference)], + out string unsupportedReason); + + Assert.True(valid); + Assert.Equal(string.Empty, unsupportedReason); + } + finally + { + try { Directory.Delete(projectDir, recursive: true); } catch { } + } + } + + [Fact] + public void PrepareAnalyzerRefs_DivertsSdkKnownAnalyzerPacks_WithoutRequiringSdkKnownVersionMatch() + { + string projectDir = Path.Combine(Path.GetTempPath(), "proj-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(projectDir); + try + { + (CachePathResolver resolver, _, string nugetRoot) = MakeSyntheticResolverWithRoots(projectDir); + string illinkAnalyzer = Path.Combine(nugetRoot, "microsoft.net.illink.tasks", "10.0.8", "analyzers", "dotnet", "cs", "ILLink.RoslynAnalyzer.dll"); + string otherAnalyzer = Path.Combine(nugetRoot, "some.analyzer", "1.0.0", "analyzers", "dotnet", "cs", "Some.Analyzer.dll"); + ITaskItem illinkItem = MakeItem(illinkAnalyzer, nuGetPackageId: "Microsoft.NET.ILLink.Tasks", nuGetPackageVersion: "10.0.8"); + ITaskItem otherItem = MakeItem(otherAnalyzer, nuGetPackageId: "Some.Analyzer", nuGetPackageVersion: "1.0.0"); + + var frameworkPacks = new SortedSet(StringComparer.OrdinalIgnoreCase); + var sdkAnalyzerPacks = new SortedSet(StringComparer.OrdinalIgnoreCase); + List prepared = ProjectDataWriter.PrepareAnalyzerRefs( + [illinkItem, otherItem], + resolver, + frameworkPacks, + sdkAnalyzerPacks, + [MakeSdkKnownAnalyzerPack("Microsoft.NET.ILLink.Tasks", "net10.0", "10.0.7")], + new ProjectDataWriter.TargetFramework("net10.0", null, "v10.0")); + + Assert.Empty(frameworkPacks); + Assert.Single(sdkAnalyzerPacks); + Assert.Contains("Microsoft.NET.ILLink.Tasks", sdkAnalyzerPacks); + Assert.Single(prepared); + Assert.Contains("some.analyzer", prepared[0], StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("microsoft.net.illink.tasks", prepared[0], StringComparison.OrdinalIgnoreCase); + } + finally + { + try { Directory.Delete(projectDir, recursive: true); } catch { } + } + } + + [Fact] + public void PrepareAnalyzerRefs_KeepsNuGetAnalyzerPackage_WhenNotSdkKnown() + { + string projectDir = Path.Combine(Path.GetTempPath(), "proj-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(projectDir); + try + { + (CachePathResolver resolver, _, string nugetRoot) = MakeSyntheticResolverWithRoots(projectDir); + string analyzer = Path.Combine(nugetRoot, "some.analyzer", "1.0.0", "analyzers", "dotnet", "cs", "Some.Analyzer.dll"); + ITaskItem analyzerItem = MakeItem(analyzer, nuGetPackageId: "Some.Analyzer", nuGetPackageVersion: "1.0.0"); + + var frameworkPacks = new SortedSet(StringComparer.OrdinalIgnoreCase); + var sdkAnalyzerPacks = new SortedSet(StringComparer.OrdinalIgnoreCase); + List prepared = ProjectDataWriter.PrepareAnalyzerRefs( + [analyzerItem], + resolver, + frameworkPacks, + sdkAnalyzerPacks, + [MakeSdkKnownAnalyzerPack("Microsoft.NET.ILLink.Tasks", "net10.0", "10.0.7")], + new ProjectDataWriter.TargetFramework("net10.0", null, "v10.0")); + + Assert.Empty(sdkAnalyzerPacks); + Assert.Single(prepared); + Assert.Contains("some.analyzer", prepared[0], StringComparison.OrdinalIgnoreCase); + } + finally + { + try { Directory.Delete(projectDir, recursive: true); } catch { } + } + } + + [Fact] + public void BuildContent_SdkAnalyzerPacksAppearsBeforeAnalyzerReferences_AndPackEntriesAreFiltered() + { + string projectDir = Path.Combine(Path.GetTempPath(), "lscache-sdk-apacks-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(projectDir); + string previousNuGet = Environment.GetEnvironmentVariable("NUGET_PACKAGES") ?? string.Empty; + try + { + string nugetRoot = Path.Combine(projectDir, "nuget"); + Environment.SetEnvironmentVariable("NUGET_PACKAGES", nugetRoot); + string projectFile = Path.Combine(projectDir, "App.csproj"); + string illinkAnalyzer = Path.Combine(nugetRoot, "microsoft.net.illink.tasks", "10.0.8", "analyzers", "dotnet", "cs", "ILLink.RoslynAnalyzer.dll"); + string otherAnalyzer = Path.Combine(nugetRoot, "some.analyzer", "1.0.0", "analyzers", "dotnet", "cs", "Some.Analyzer.dll"); + + string content = ProjectDataWriter.BuildContent( + projectFilePath: projectFile, + writeHeader: true, + isPrimary: false, + lastDtbSucceeded: false, + sliceDimensions: [MakeItem("TargetFramework", "net10.0")], + properties: [MakeItem("TargetFrameworkIdentifier", ".NETCoreApp"), MakeItem("TargetFrameworkVersion", "v10.0")], + commandLineArguments: null, + sourceFiles: null, + metadataReferences: null, + analyzerReferences: + [ + MakeItem(illinkAnalyzer, nuGetPackageId: "Microsoft.NET.ILLink.Tasks", nuGetPackageVersion: "10.0.8"), + MakeItem(otherAnalyzer, nuGetPackageId: "Some.Analyzer", nuGetPackageVersion: "1.0.0"), + ], + analyzerConfigFiles: null, + additionalFiles: null, + projectReferences: null, + capabilities: null, + sdkKnownAnalyzerPacks: [MakeSdkKnownAnalyzerPack("Microsoft.NET.ILLink.Tasks", "net10.0", "10.0.7")]); + + int packsIdx = content.IndexOf("[sdkAnalyzerPacks]"); + int analyzerIdx = content.IndexOf("[analyzerReferences]"); + Assert.True(packsIdx > 0, "[sdkAnalyzerPacks] section expected"); + Assert.True(analyzerIdx > packsIdx, "[sdkAnalyzerPacks] must precede [analyzerReferences]"); + Assert.Contains("Microsoft.NET.ILLink.Tasks", content); + Assert.DoesNotContain("ILLink.RoslynAnalyzer.dll", content); + Assert.DoesNotContain("10.0.8/analyzers", content); + Assert.Contains("Some.Analyzer.dll", content); + } + finally + { + Environment.SetEnvironmentVariable("NUGET_PACKAGES", previousNuGet); + try { Directory.Delete(projectDir, recursive: true); } catch { } + } + } + + [Fact] + public void BuildContent_SdkAnalyzerConfigPolicyAppearsBeforeAnalyzerConfigFiles() + { + string projectDir = Path.Combine(Path.GetTempPath(), "lscache-sdk-configs-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(projectDir); + string fakeDotnet = Path.Combine(projectDir, "dotnet"); + Directory.CreateDirectory(fakeDotnet); + string previous = Environment.GetEnvironmentVariable("DOTNET_ROOT") ?? string.Empty; + try + { + Environment.SetEnvironmentVariable("DOTNET_ROOT", fakeDotnet); + string projectFile = Path.Combine(projectDir, "App.csproj"); + string sdkConfig = Path.Combine(fakeDotnet, "sdk", "10.0.202", "Sdks", "Microsoft.NET.Sdk", "analyzers", "build", "config", "analysislevel_10_default.globalconfig"); + string styleConfig = Path.Combine( + fakeDotnet, + "sdk", + "10.0.202", + "Sdks", + "Microsoft.NET.Sdk", + "codestyle", + "cs", + "build", + "config", + "analysislevelstyle_default.globalconfig"); + string projectConfig = Path.Combine(projectDir, "Directory.Build.globalconfig"); + + string content = ProjectDataWriter.BuildContent( + projectFilePath: projectFile, + writeHeader: true, + isPrimary: false, + lastDtbSucceeded: false, + sliceDimensions: [MakeItem("TargetFramework", "net10.0")], + properties: [MakeItem("TargetFrameworkIdentifier", ".NETCoreApp"), MakeItem("TargetFrameworkVersion", "v10.0")], + commandLineArguments: null, + sourceFiles: null, + metadataReferences: null, + analyzerReferences: null, + analyzerConfigFiles: [sdkConfig, styleConfig, projectConfig], + additionalFiles: null, + projectReferences: null, + sdkAnalyzerConfigPolicy: [MakeDefaultSdkAnalyzerConfigPolicy()]); + + int sdkConfigIdx = content.IndexOf("[sdkAnalyzerConfigPolicy]"); + int configIdx = content.IndexOf("[analyzerConfigFiles]"); + Assert.True(sdkConfigIdx > 0, "[sdkAnalyzerConfigPolicy] section expected"); + Assert.True(configIdx > sdkConfigIdx, "[sdkAnalyzerConfigPolicy] must precede [analyzerConfigFiles]"); + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains("Microsoft.NET.Sdk/analyzers\n", normalized); + Assert.Contains("Microsoft.NET.Sdk/codestyle/cs\n", normalized); + Assert.DoesNotContain("AnalysisLevel=latest", normalized); + Assert.DoesNotContain("analysislevel_10_default.globalconfig", content); + Assert.DoesNotContain("analysislevelstyle_default.globalconfig", content); + Assert.Contains("Directory.Build.globalconfig", content); + } + finally + { + Environment.SetEnvironmentVariable("DOTNET_ROOT", previous); + try { Directory.Delete(projectDir, recursive: true); } catch { } + } + } + + [Theory] + [InlineData("net10.0", "10.0", "10", ".NETCoreApp", "v10.0")] + [InlineData("net10", "10.0", "10", ".NETCoreApp", "v10.0")] + [InlineData("net8", "8.0", "8", ".NETCoreApp", "v8.0")] + public void BuildContent_SdkAnalyzerConfigPolicyCanonicalizesNumericDefaults(string targetFramework, string numericAnalysisLevel, string shortAnalysisLevel, string tfmIdentifier, string tfmVersion) + { + ITaskItem[] targetFrameworkDimension = [MakeItem("TargetFramework", targetFramework)]; + ITaskItem[] tfmProperties = [MakeItem("TargetFrameworkIdentifier", tfmIdentifier), MakeItem("TargetFrameworkVersion", tfmVersion)]; + + string numeric = Build( + sliceDimensions: targetFrameworkDimension, + properties: tfmProperties, + sdkAnalyzerConfigPolicy: [MakeDefaultSdkAnalyzerConfigPolicy(numericAnalysisLevel, numericAnalysisLevel)]); + string numericShort = Build( + sliceDimensions: targetFrameworkDimension, + properties: tfmProperties, + sdkAnalyzerConfigPolicy: [MakeDefaultSdkAnalyzerConfigPolicy(shortAnalysisLevel, numericAnalysisLevel)]); + + Assert.Equal(numeric, numericShort); + string normalized = numeric.Replace("\r\n", "\n"); + Assert.Contains("[sdkAnalyzerConfigPolicy]\nMicrosoft.NET.Sdk/analyzers\nMicrosoft.NET.Sdk/codestyle/cs\n", normalized); + Assert.DoesNotContain($"AnalysisLevel={numericAnalysisLevel}", normalized); + } + + [Fact] + public void BuildContent_SdkAnalyzerConfigPolicyCanonicalizesLatestWhenEffectiveLevelMatchesTargetFramework() + { + string content = Build( + sliceDimensions: [MakeItem("TargetFramework", "net10.0")], + properties: [MakeItem("TargetFrameworkIdentifier", ".NETCoreApp"), MakeItem("TargetFrameworkVersion", "v10.0")], + sdkAnalyzerConfigPolicy: [MakeDefaultSdkAnalyzerConfigPolicy("latest", "10.0")]); + + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains("Microsoft.NET.Sdk/analyzers\n", normalized); + Assert.Contains("Microsoft.NET.Sdk/codestyle/cs\n", normalized); + Assert.DoesNotContain("AnalysisLevel=latest", normalized); + } + + [Fact] + public void BuildContent_SdkAnalyzerConfigPolicyCanonicalizesCapitalLatestWhenEffectiveLevelMatchesTargetFramework() + { + // Windows SDK evaluation yields AnalysisLevel="Latest" (capital L) + EffectiveAnalysisLevel="10.0" + // for a net10.0 project with Latest in Directory.Build.props. + // Verify this is treated identically to lowercase "latest". + string content = Build( + sliceDimensions: [MakeItem("TargetFramework", "net10.0")], + properties: [MakeItem("TargetFrameworkIdentifier", ".NETCoreApp"), MakeItem("TargetFrameworkVersion", "v10.0")], + sdkAnalyzerConfigPolicy: [MakeDefaultSdkAnalyzerConfigPolicy("Latest", "10.0")]); + + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains("Microsoft.NET.Sdk/analyzers\n", normalized); + Assert.Contains("Microsoft.NET.Sdk/codestyle/cs\n", normalized); + Assert.DoesNotContain("AnalysisLevel=Latest", normalized); + } + + [Fact] + public void BuildContent_SdkAnalyzerConfigPolicyCanonicalizesLatestWhenEffectiveLevelIsNewerThanTargetFramework() + { + // The extension-driven design-time build can evaluate Latest + // on a net10.0 project as EffectiveAnalysisLevel="11.0" when running on a newer SDK. + string content = Build( + sliceDimensions: [MakeItem("TargetFramework", "net10.0")], + properties: [MakeItem("TargetFrameworkIdentifier", ".NETCoreApp"), MakeItem("TargetFrameworkVersion", "v10.0")], + sdkAnalyzerConfigPolicy: [MakeDefaultSdkAnalyzerConfigPolicy("Latest", "11.0")]); + + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains("Microsoft.NET.Sdk/analyzers\n", normalized); + Assert.Contains("Microsoft.NET.Sdk/codestyle/cs\n", normalized); + Assert.DoesNotContain("AnalysisLevel=Latest", normalized); + } + + [Fact] + public void BuildContent_SdkAnalyzerConfigPolicyCanonicalizesLatestWithoutEffectiveLevel() + { + ITaskItem policy = MakeSdkAnalyzerConfigPolicy( + ("Language", "C#"), + ("EnableNETAnalyzers", "true"), + ("EnforceCodeStyleInBuild", "true"), + ("AnalysisLevel", "Latest"), + ("AnalysisLevelStyle", "Latest"), + ("AnalysisMode", "Default"), + ("AnalysisModeStyle", "Default")); + + string content = Build( + sliceDimensions: [MakeItem("TargetFramework", "net10.0")], + properties: [MakeItem("TargetFrameworkIdentifier", ".NETCoreApp"), MakeItem("TargetFrameworkVersion", "v10.0")], + sdkAnalyzerConfigPolicy: [policy]); + + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains("Microsoft.NET.Sdk/analyzers|AnalysisMode=Default\n", normalized); + Assert.Contains("Microsoft.NET.Sdk/codestyle/cs|AnalysisMode=Default\n", normalized); + Assert.DoesNotContain("AnalysisLevel=Latest", normalized); + } + + [Fact] + public void BuildContent_SdkAnalyzerConfigPolicyCanonicalizesLatestWithEffectiveLatest() + { + string content = Build( + properties: [MakeItem("TargetFramework", "net10.0"), MakeItem("TargetFrameworkIdentifier", ".NETCoreApp"), MakeItem("TargetFrameworkVersion", "v10.0")], + sdkAnalyzerConfigPolicy: [MakeDefaultSdkAnalyzerConfigPolicy("Latest", "Latest")]); + + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains("Microsoft.NET.Sdk/analyzers\n", normalized); + Assert.Contains("Microsoft.NET.Sdk/codestyle/cs\n", normalized); + Assert.DoesNotContain("AnalysisLevel=Latest", normalized); + } + + [Fact] + public void BuildContent_SdkAnalyzerConfigPolicyCanonicalizesNetFrameworkLatestWithoutEffectiveLevel() + { + ITaskItem policy = MakeSdkAnalyzerConfigPolicy( + ("Language", "C#"), + ("EnableNETAnalyzers", "true"), + ("EnforceCodeStyleInBuild", "true"), + ("AnalysisLevel", "Latest"), + ("AnalysisLevelStyle", "Latest"), + ("AnalysisMode", "Default"), + ("AnalysisModeStyle", "Default")); + + string content = Build( + sliceDimensions: [MakeItem("TargetFramework", "net472")], + properties: [MakeItem("TargetFrameworkIdentifier", ".NETFramework"), MakeItem("TargetFrameworkVersion", "v4.7.2")], + sdkAnalyzerConfigPolicy: [policy]); + + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains("Microsoft.NET.Sdk/analyzers|AnalysisMode=Default\n", normalized); + Assert.Contains("Microsoft.NET.Sdk/codestyle/cs|AnalysisMode=Default\n", normalized); + Assert.DoesNotContain("AnalysisLevel=Latest", normalized); + } + + [Theory] + [InlineData("preview", "11.0")] + [InlineData("none", "4.0")] + [InlineData("9.0", "9.0")] + public void BuildContent_SdkAnalyzerConfigPolicyPreservesNonDefaultAnalysisLevel(string analysisLevel, string effectiveAnalysisLevel) + { + string content = Build( + sliceDimensions: [MakeItem("TargetFramework", "net10.0")], + sdkAnalyzerConfigPolicy: [MakeDefaultSdkAnalyzerConfigPolicy(analysisLevel, effectiveAnalysisLevel)]); + + Assert.Contains($"AnalysisLevel={analysisLevel}", content); + } + + [Theory] + [InlineData("net472")] + [InlineData("net48")] + public void BuildContent_SdkAnalyzerConfigPolicyPreservesLatestForNetFrameworkTfms(string targetFramework) + { + string content = Build( + sliceDimensions: [MakeItem("TargetFramework", targetFramework)], + sdkAnalyzerConfigPolicy: [MakeDefaultSdkAnalyzerConfigPolicy("latest", "4.8")]); + + Assert.Contains("Microsoft.NET.Sdk/analyzers|AnalysisLevel=latest", content); + Assert.Contains("Microsoft.NET.Sdk/codestyle/cs|AnalysisLevel=latest", content); + } + + [Fact] + public void BuildContent_SdkAnalyzerConfigPolicyPreservesLatestSuffix() + { + ITaskItem policy = MakeSdkAnalyzerConfigPolicy( + ("Language", "C#"), + ("EnableNETAnalyzers", "true"), + ("EnforceCodeStyleInBuild", "true"), + ("AnalysisLevel", "latest-all"), + ("AnalysisLevelStyle", "latest-all"), + ("AnalysisLevelSuffix", "all"), + ("AnalysisLevelSuffixStyle", "all"), + ("EffectiveAnalysisLevel", "10.0"), + ("EffectiveAnalysisLevelStyle", "10.0"), + ("MicrosoftCodeAnalysisNetAnalyzersRulesVersion", "10")); + + string content = Build( + sliceDimensions: [MakeItem("TargetFramework", "net10.0")], + properties: [MakeItem("TargetFrameworkIdentifier", ".NETCoreApp"), MakeItem("TargetFrameworkVersion", "v10.0")], + sdkAnalyzerConfigPolicy: [policy]); + + string normalized = content.Replace("\r\n", "\n"); + Assert.Contains("Microsoft.NET.Sdk/analyzers|AnalysisLevelSuffix=all", normalized); + Assert.Contains("Microsoft.NET.Sdk/codestyle/cs|AnalysisLevelSuffix=all", normalized); + Assert.DoesNotContain("AnalysisLevel=latest-all", normalized); + } + + [Fact] + public void BuildContent_SdkAnalyzerConfigPolicyPreservesStyleLevelWhenItDiffersFromCoreSpelling() + { + ITaskItem policy = MakeSdkAnalyzerConfigPolicy( + ("Language", "C#"), + ("EnableNETAnalyzers", "true"), + ("EnforceCodeStyleInBuild", "true"), + ("AnalysisLevel", "latest"), + ("AnalysisLevelStyle", "10.0"), + ("EffectiveAnalysisLevel", "10.0"), + ("EffectiveAnalysisLevelStyle", "10.0"), + ("MicrosoftCodeAnalysisNetAnalyzersRulesVersion", "10")); + + string content = Build( + sliceDimensions: [MakeItem("TargetFramework", "net10.0")], + properties: [MakeItem("TargetFrameworkIdentifier", ".NETCoreApp"), MakeItem("TargetFrameworkVersion", "v10.0")], + sdkAnalyzerConfigPolicy: [policy]); + + Assert.Contains("Microsoft.NET.Sdk/codestyle/cs|AnalysisLevelStyle=10.0", content); + Assert.DoesNotContain("AnalysisLevel=latest", content); + } + + [Theory] + [InlineData("Microsoft.NET.Sdk/analyzers")] + [InlineData("Microsoft.NET.Sdk/codestyle/cs")] + [InlineData("Microsoft.NET.Sdk/analyzers|AnalysisMode=Default")] + [InlineData("Microsoft.NET.Sdk/codestyle/cs|AnalysisLevelStyle=10.0")] + [InlineData("Microsoft.NET.Sdk/analyzers|AnalysisLevelSuffix=all")] + [InlineData("not-an-sdk-policy-line")] + public void CanonicalizeSdkAnalyzerConfigPolicyLine_IsNoOpWhenTargetFrameworkIsNull(string alreadyCanonicalLine) + { + // ``ProjectDataMerger.ParseSlice`` invokes ``CanonicalizeSdkAnalyzerConfigPolicyLine`` while + // parsing the shared block of a merged ``.lscache`` — at that point ``SliceDimensions`` + // is empty and ``GetTargetFramework()`` returns ``null``. Since lines in the shared + // block already agreed across every per-TFM slice (otherwise they would not have been + // hoisted), the canonicalizer must leave already-canonical input unchanged when the + // TFM context is unavailable. Pin that contract so a future refactor of + // ``CanonicalizeAnalysisLevel``'s null-TFM branch cannot silently mutate shared-block + // data on round-trip. + string result = ProjectDataWriter.CanonicalizeSdkAnalyzerConfigPolicyLine(alreadyCanonicalLine, targetFrameworkIdentifier: null, targetFrameworkVersion: null); + Assert.Equal(alreadyCanonicalLine, result); + } + + [Fact] + public void BuildContent_SdkAnalyzerConfigPolicyIsStableWhenSdkAddsCodeStyleConfig() + { + string projectDir = Path.Combine(Path.GetTempPath(), "lscache-sdk-config-policy-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(projectDir); + string fakeDotnet = Path.Combine(projectDir, "dotnet"); + Directory.CreateDirectory(fakeDotnet); + string previous = Environment.GetEnvironmentVariable("DOTNET_ROOT") ?? string.Empty; + try + { + Environment.SetEnvironmentVariable("DOTNET_ROOT", fakeDotnet); + string projectFile = Path.Combine(projectDir, "App.csproj"); + string sdkConfig = Path.Combine(fakeDotnet, "sdk", "10.0.202", "Sdks", "Microsoft.NET.Sdk", "analyzers", "build", "config", "analysislevel_10_default.globalconfig"); + string styleConfig = Path.Combine( + fakeDotnet, + "sdk", + "10.0.202", + "Sdks", + "Microsoft.NET.Sdk", + "codestyle", + "cs", + "build", + "config", + "analysislevelstyle_default.globalconfig"); + string projectConfig = Path.Combine(projectDir, "Directory.Build.globalconfig"); + ITaskItem policy = MakeDefaultSdkAnalyzerConfigPolicy(); + + string withoutCodeStyleConfig = ProjectDataWriter.BuildContent( + projectFilePath: projectFile, + writeHeader: true, + isPrimary: false, + lastDtbSucceeded: false, + sliceDimensions: [MakeItem("TargetFramework", "net10.0")], + properties: null, + commandLineArguments: null, + sourceFiles: null, + metadataReferences: null, + analyzerReferences: null, + analyzerConfigFiles: [sdkConfig, projectConfig], + additionalFiles: null, + projectReferences: null, + sdkAnalyzerConfigPolicy: [policy]); + + string withCodeStyleConfig = ProjectDataWriter.BuildContent( + projectFilePath: projectFile, + writeHeader: true, + isPrimary: false, + lastDtbSucceeded: false, + sliceDimensions: [MakeItem("TargetFramework", "net10.0")], + properties: null, + commandLineArguments: null, + sourceFiles: null, + metadataReferences: null, + analyzerReferences: null, + analyzerConfigFiles: [sdkConfig, styleConfig, projectConfig], + additionalFiles: null, + projectReferences: null, + sdkAnalyzerConfigPolicy: [policy]); + + Assert.Equal(withoutCodeStyleConfig, withCodeStyleConfig); + } + finally + { + Environment.SetEnvironmentVariable("DOTNET_ROOT", previous); + try { Directory.Delete(projectDir, recursive: true); } catch { } + } + } + + [Fact] + public void PrepareRefs_SharedPackBetweenMetadataAndAnalyzersIsDeduplicated() + { + string projectDir = Path.Combine(Path.GetTempPath(), "proj"); + (CachePathResolver resolver, string dotnetRoot) = MakeSyntheticResolver(projectDir); + ITaskItem[] meta = [MakeItem(Path.Combine(dotnetRoot, "packs", "Microsoft.NETCore.App.Ref", "10.0.7", "ref", "net10.0", "System.Runtime.dll"))]; + ITaskItem[] analyzers = [MakeItem(Path.Combine(dotnetRoot, "packs", "Microsoft.NETCore.App.Ref", "10.0.7", "analyzers", "dotnet", "cs", "x.dll"))]; + + var packs = new SortedSet(StringComparer.OrdinalIgnoreCase); + ProjectDataWriter.PrepareMetadataRefs(meta, resolver, packs); + ProjectDataWriter.PrepareAnalyzerRefs(analyzers, resolver, packs); + + Assert.Single(packs); + } + + [Fact] + public void BuildContent_FrameworkPacksAppearsBeforeMetadataReferences_AndPackEntriesAreFiltered() + { + // Use a custom DOTNET_ROOT so the live writer's resolver picks up our fake pack paths. + string projectDir = Path.Combine(Path.GetTempPath(), "lscache-fpacks-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(projectDir); + string fakeDotnet = Path.Combine(projectDir, "dotnet"); + Directory.CreateDirectory(fakeDotnet); + string previous = Environment.GetEnvironmentVariable("DOTNET_ROOT") ?? string.Empty; + try + { + Environment.SetEnvironmentVariable("DOTNET_ROOT", fakeDotnet); + string projectFile = Path.Combine(projectDir, "App.csproj"); + string packDll = Path.Combine(fakeDotnet, "packs", "Microsoft.NETCore.App.Ref", "10.0.7", "ref", "net10.0", "System.Runtime.dll"); + string nugetDll = @"C:\nuget\foo\1.0\lib\net10.0\Foo.dll"; + + string content = ProjectDataWriter.BuildContent( + projectFilePath: projectFile, + writeHeader: true, + isPrimary: false, + lastDtbSucceeded: false, + sliceDimensions: null, + properties: null, + commandLineArguments: null, + sourceFiles: null, + metadataReferences: [MakeItem(packDll), MakeItem(nugetDll)], + analyzerReferences: null, + analyzerConfigFiles: null, + additionalFiles: null, + projectReferences: null); + + int packsIdx = content.IndexOf("[frameworkPacks]"); + int metaIdx = content.IndexOf("[metadataReferences]"); + Assert.True(packsIdx > 0, "[frameworkPacks] section expected"); + Assert.True(metaIdx > packsIdx, "[frameworkPacks] must precede [metadataReferences]"); + Assert.Contains("Microsoft.NETCore.App.Ref", content); + Assert.DoesNotContain("System.Runtime.dll", content); + // The non-pack ref still appears. + Assert.Contains("Foo.dll", content); + } + finally + { + Environment.SetEnvironmentVariable("DOTNET_ROOT", previous); + try { Directory.Delete(projectDir, recursive: true); } catch { } + } + } + + [Fact] + public void WriteCapabilitiesSection_ExcludesUniversalCapabilities() + { + string content = Build(capabilities: ["Aspire", "CSharp", "TestingPlatformServer", "OutputGroups", "SupportsHotReload"]); + string normalized = content.Replace("\r\n", "\n"); + + Assert.Contains("[capabilities]\nAspire\nSupportsHotReload\nTestingPlatformServer\n", normalized); + Assert.DoesNotContain("\nCSharp\n", normalized); + Assert.DoesNotContain("\nOutputGroups\n", normalized); + } + + [Fact] + public void WriteCapabilitiesSection_ExcludesNewDenylistEntries() + { + string content = Build(capabilities: + [ + "Aspire", "AppServicePublish", "AspNetCoreInProcessHosting", + "BuildWindowsDesktopTarget", "DeclaredSourceItems", "DotNetCoreRazorConfiguration", + "DynamicDependentFile", "DynamicFileNesting", "GenerateDocumentationFile", + "NetSdkOCIImageBuild", "SupportHierarchyContextSvc", "SupportsComputeRunCommand", + "SupportsTypeScriptNuGet", "UserSourceItems", "WebNestingDefaults", + ]); + string normalized = content.Replace("\r\n", "\n"); + + Assert.Contains("[capabilities]\nAspire\n", normalized); + Assert.DoesNotContain("\nAppServicePublish\n", normalized); + Assert.DoesNotContain("\nAspNetCoreInProcessHosting\n", normalized); + Assert.DoesNotContain("\nBuildWindowsDesktopTarget\n", normalized); + Assert.DoesNotContain("\nDeclaredSourceItems\n", normalized); + Assert.DoesNotContain("\nDotNetCoreRazorConfiguration\n", normalized); + Assert.DoesNotContain("\nDynamicDependentFile\n", normalized); + Assert.DoesNotContain("\nDynamicFileNesting\n", normalized); + Assert.DoesNotContain("\nGenerateDocumentationFile\n", normalized); + Assert.DoesNotContain("\nNetSdkOCIImageBuild\n", normalized); + Assert.DoesNotContain("\nSupportHierarchyContextSvc\n", normalized); + Assert.DoesNotContain("\nSupportsComputeRunCommand\n", normalized); + Assert.DoesNotContain("\nSupportsTypeScriptNuGet\n", normalized); + Assert.DoesNotContain("\nUserSourceItems\n", normalized); + Assert.DoesNotContain("\nWebNestingDefaults\n", normalized); + } + + [Fact] + public void WriteCapabilitiesSection_NullCapabilities_OmitsSection() + { + string content = Build(capabilities: null); + Assert.DoesNotContain("[capabilities]", content); + } + + [Fact] + public void WriteCapabilitiesSection_EmptyCapabilities_OmitsSection() + { + string content = Build(capabilities: []); + Assert.DoesNotContain("[capabilities]", content); + } + + [Fact] + public void WriteCapabilitiesSection_SortsAlphabetically() + { + string content = Build(capabilities: ["Zebra", "Alpha", "Middle"]); + string normalized = content.Replace("\r\n", "\n"); + + Assert.Contains("[capabilities]\nAlpha\nMiddle\nZebra\n", normalized); + } + + [Fact] + public void WriteCapabilitiesSection_Deduplicates() + { + string content = Build(capabilities: ["Aspire", "Aspire", "TestContainer"]); + string normalized = content.Replace("\r\n", "\n"); + + Assert.Contains("[capabilities]\nAspire\nTestContainer\n", normalized); + int count = normalized.Split("\nAspire\n").Length - 1; + Assert.Equal(1, count); + } + + [Fact] + public void WriteCapabilitiesSection_CaseInsensitiveExclusion() + { + string content = Build(capabilities: ["csharp", "OUTPUTGROUPS", "Aspire"]); + string normalized = content.Replace("\r\n", "\n"); + + Assert.Contains("[capabilities]\nAspire\n", normalized); + Assert.DoesNotContain("csharp", normalized, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("OUTPUTGROUPS", normalized, StringComparison.OrdinalIgnoreCase); + } + + #endregion + + #region TryRewriteAsNuGetPp + + [Theory] + [InlineData("obj/Debug/net8.0/NuGet/7E7D116BF0B1C551/Nullable/1.3.0/Nullable/NullableAttributes.cs", + "/Nullable/1.3.0/Nullable/NullableAttributes.cs")] + [InlineData("obj/Release/net6.0/NuGet/ABCDEF0123456789/SomePackage/2.0.0/File.cs", + "/SomePackage/2.0.0/File.cs")] + [InlineData("obj/Debug/net8.0/NuGet/abcdef0123456789/Pkg/1.0.0/Dir/File.cs", + "/Pkg/1.0.0/Dir/File.cs")] + public void TryRewriteAsNuGetPp_RewritesToSentinel(string input, string expected) + { + string? result = CachePathResolver.TryRewriteAsNuGetPp(input); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("src/Program.cs")] + [InlineData("obj/Debug/net8.0/SomeFile.cs")] + [InlineData("obj/Debug/net8.0/NuGet/SHORT/Pkg/1.0/File.cs")] // Hash too short + [InlineData("obj/Debug/net8.0/NuGet/ZZZZZZZZZZZZZZZZ/Pkg/1.0/File.cs")] // Non-hex chars + [InlineData("obj/Debug/net8.0/NuGet/7E7D116BF0B1C551")] // No trailing path after hash + public void TryRewriteAsNuGetPp_ReturnsNullForNonMatchingPaths(string input) + { + string? result = CachePathResolver.TryRewriteAsNuGetPp(input); + Assert.Null(result); + } + + #endregion +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/TargetsFileSmokeTests.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/TargetsFileSmokeTests.cs new file mode 100644 index 0000000000000..e37f9850733b7 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/TargetsFileSmokeTests.cs @@ -0,0 +1,3513 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Text; +using Microsoft.NET.ProjectData; +using Xunit; + +namespace Microsoft.NET.ProjectData.Tasks.Tests; + +/// +/// Smoke tests for the Microsoft.NET.ProjectData.targets file. Spawns +/// dotnet msbuild against a fixture project with the targets file wired +/// in via CustomAfterMicrosoftCommonTargets, mirroring how the extension +/// activates the writer at runtime. +/// +public sealed class TargetsFileSmokeTests : IDisposable +{ + private static readonly string TargetsFile = Path.Combine( + AppContext.BaseDirectory, + "Microsoft.NET.ProjectData.targets"); + + /// + /// Safety-net timeout for each spawned dotnet msbuild invocation. If a child MSBuild/restore + /// process (or a lingering MSBuild worker node / build server that inherited the redirected stdout/stderr + /// pipe) fails to exit, the read below would otherwise never reach EOF and the test would hang forever. + /// That escalated a transient child hang into a 180-minute CI job timeout on macOS (the vstest + /// --blame-hang dump collection also hangs on macOS, so it never recovered). Bounding the wait here + /// and killing the whole process tree fails the individual test in minutes with captured output instead. + /// Overridable via PROJECTDATA_SMOKE_TEST_PROCESS_TIMEOUT_SECONDS for slower agents. + /// + private static readonly TimeSpan ProcessTimeout = GetProcessTimeout(); + + private readonly string workDir; + + public TargetsFileSmokeTests() + { + this.workDir = Path.Combine(Path.GetTempPath(), "projectdata-targets-smoke-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(this.workDir); + } + + public void Dispose() + { + try { Directory.Delete(this.workDir, recursive: true); } + catch { /* best-effort cleanup */ } + } + + [Fact] + public void TargetsFileShipsAlongsideTaskAssembly() + { + Assert.True( + File.Exists(TargetsFile), + $"Expected the targets file to be next to the test binary so MSBuild can resolve it. Looked for: {TargetsFile}"); + } + + [Fact] + public async Task SingleTfmProject_EvaluatesProjectDataPathNextToCsproj() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: false, writeAssetsFile: false); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: ["/getProperty:_ProjectDataPath", "/p:EnableProjectDataInProjectFolder=true"]); + + Assert.True(result.ExitCode == 0, result.Output); + + // /getProperty prints the evaluated value on its own line. + string expected = projectFile + ".lscache"; + Assert.Contains(expected, result.Output, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task ExistingProjectFolderCache_ForcesProjectDataPathNextToCsproj() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: false, writeAssetsFile: false); + string expected = projectFile + ".lscache"; + await File.WriteAllTextAsync(expected, "existing cache", TestContext.Current.CancellationToken); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: ["/getProperty:_ProjectDataPath"]); + + Assert.True(result.ExitCode == 0, result.Output); + + // Even though the unset default is user-folder mode, committed/in-project + // caches keep using the project-folder path so they stay up to date. + Assert.Contains(expected, result.Output, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task DefaultMode_DisablesProjectFolderStorage() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: false, writeAssetsFile: false); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: ["/getProperty:EnableProjectDataInProjectFolder"]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.Contains( + result.Output.Replace("\r\n", "\n").Split('\n'), + line => string.Equals(line.Trim(), "false", StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public async Task SingleTfmProject_DTBProducesProjectDataFile() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: false); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + string expected = projectFile + ".lscache"; + Assert.True(File.Exists(expected), $"Expected the writer to produce {expected}.\n{result.Output}"); + AssertNoUnsupportedMarker(projectFile); + + string content = File.ReadAllText(expected); + Assert.Contains("OutputType=Exe", content); + Assert.Contains("[commandLineArguments]", content); + } + + [Fact] + public async Task ProjectDataBuild_PersistsIsTestProjectOptOut() + { + string projectFile = this.WriteProject( + "App.csproj", + multiTargeting: false, + extraProperties: "false"); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + string content = File.ReadAllText(projectFile + ".lscache").Replace("\r\n", "\n"); + Assert.Contains("\nIsTestProject=false\n", content); + } + + [Fact] + public async Task ProjectDataBuild_AfterSdkImport_DoesNotSuppressGeneratedAssemblyInfoAndFiltersItFromCache() + { + string projectFile = this.WriteProject( + "App.csproj", + multiTargeting: false, + extraProperties: "ProjectDataAuditCompany"); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + $"/p:AfterMicrosoftNETSdkTargets={TargetsFile}", + ], + wireProjectDataTargets: false); + + Assert.True(result.ExitCode == 0, result.Output); + string assemblyInfoFile = Path.Combine(this.workDir, "obj", "Debug", "net8.0", "App.AssemblyInfo.cs"); + Assert.True(File.Exists(assemblyInfoFile), $"Expected the SDK to generate {assemblyInfoFile}.\n{result.Output}"); + Assert.Contains("ProjectDataAuditCompany", File.ReadAllText(assemblyInfoFile)); + + string cacheContent = File.ReadAllText(projectFile + ".lscache"); + Assert.DoesNotContain("App.AssemblyInfo.cs", cacheContent); + } + + [Fact] + public async Task ProjectDataBuild_DoesNotFilterUserOwnedCompileItemAtGeneratedAssemblyInfoPath() + { + string projectFile = this.WriteProject( + "App.csproj", + multiTargeting: false, + extraProperties: + """ + false + $(IntermediateOutputPath)Manual.AssemblyInfo.cs + """, + extraXml: + """ + + + + + + + + """); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + string cacheContent = File.ReadAllText(projectFile + ".lscache"); + Assert.Contains("Manual.AssemblyInfo.cs", cacheContent); + } + + [Fact] + public async Task SingleTfmProject_DTBRunsCompileDependsOnTargetsForKeyFile() + { + string keyFile = Path.Combine(this.workDir, "TestKey.snk"); + await File.WriteAllTextAsync(keyFile, "test-key", TestContext.Current.CancellationToken); + string projectFile = this.WriteProject( + "App.csproj", + multiTargeting: false, + extraProperties: + $""" + true + true + {keyFile} + """); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + string content = File.ReadAllText(projectFile + ".lscache").Replace("\r\n", "\n"); + Assert.Contains("/keyfile:", content); + Assert.Contains("TestKey.snk", content); + } + + [Fact] + public async Task SingleTfmProject_ProjectDataBuildCommandLineArgumentsMatchDesignTimeCompile() + { + string baselineArgsFile = Path.Combine(this.workDir, "compile.args"); + string projectFile = this.WriteProject( + "App.csproj", + multiTargeting: false, + extraXml: + $$""" + + + $(DefineConstants);PROJECTDATA_BEFORE_COMPILE + + + + + + + """); + + ProcessResult compileResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:Compile", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:ProvideCommandLineArgs=true", + ]); + + Assert.True(compileResult.ExitCode == 0, compileResult.Output); + Assert.True(File.Exists(baselineArgsFile), $"Expected direct Compile to capture CscCommandLineArgs at {baselineArgsFile}.\n{compileResult.Output}"); + string[] compileArgs = File.ReadAllLines(baselineArgsFile); + + ProcessResult projectDataResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:ProvideCommandLineArgs=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(projectDataResult.ExitCode == 0, projectDataResult.Output); + string[] cacheArgs = ExtractCommandLineArguments(File.ReadAllText(projectFile + ".lscache")); + + string[] expected = NormalizeForStableArgumentParity(compileArgs); + string[] actual = NormalizeForStableArgumentParity(cacheArgs); + Assert.Equal(expected, actual); + Assert.Contains(actual, arg => arg.Contains("PROJECTDATA_BEFORE_COMPILE", StringComparison.Ordinal)); + } + + [Fact] + public async Task SingleTfmProject_DTBProducesProjectDataFile_WhenCoreCompileIsUpToDate() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: false); + string expected = projectFile + ".lscache"; + + ProcessResult firstResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(firstResult.ExitCode == 0, firstResult.Output); + Assert.True(File.Exists(expected), $"Expected the first DTB to produce {expected}.\n{firstResult.Output}"); + + File.Delete(expected); + + ProcessResult secondResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(secondResult.ExitCode == 0, secondResult.Output); + Assert.True(File.Exists(expected), $"Expected the second DTB to produce {expected} even when CoreCompile is up-to-date.\n{secondResult.Output}"); + Assert.Contains("[commandLineArguments]", File.ReadAllText(expected)); + } + + [Fact] + public async Task DirectWriteTarget_DoesNotProduceCacheWithoutCommandLineArguments() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: false); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:_WriteProjectData", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataOnBuild=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.False(File.Exists(projectFile + ".lscache"), $"Direct _WriteProjectData should not silently create a cache without CscCommandLineArgs.\n{result.Output}"); + + // The opportunistic EnableProjectDataOnBuild hook did NOT force CoreCompile, so an empty + // CscCommandLineArgs here means CoreCompile was skipped as up-to-date (its AfterTargets hook + // still fires) — a perfectly good project. The writer must NOT poison the shared cache with a + // spurious unsupported marker; doing so makes projects silently vanish on the next non-forced + // workspace refresh (the aspire-starter regression). + AssertNoUnsupportedMarker(projectFile); + } + + [Fact] + public async Task DirectWriteTarget_WritesUnsupportedMarker_WhenCoreCompileForcedAndArgumentsEmpty() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: false); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:_WriteProjectData", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataOnBuild=true", + "/p:EnableProjectDataInProjectFolder=true", + + // The authoritative ProjectDataBuild graph forces CoreCompile to run, so an empty + // CscCommandLineArgs genuinely means the project produces no C# compilation and should + // be marked unsupported. + "/p:_ProjectDataBuildActive=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.False(File.Exists(projectFile + ".lscache"), $"Direct _WriteProjectData should not silently create a cache without CscCommandLineArgs.\n{result.Output}"); + AssertUnsupportedMarker(projectFile, "CompilerCommandLineArgumentsEmpty"); + } + + [Fact] + public async Task DirectWriteTarget_PreservesExistingCache_WhenNotForcedAndArgumentsEmpty() + { + // Regression test for the aspire-starter "projects vanish" bug. An authoritative ProjectDataBuild + // writes a good `.lscache`; then an ordinary incremental build's opportunistic + // EnableProjectDataOnBuild hook fires AfterTargets="CoreCompile" with empty CscCommandLineArgs + // (CoreCompile was skipped as up-to-date). The writer must leave the good cache in place and NOT + // poison the shared cache with an unsupported marker. + string projectFile = this.WriteProject("App.csproj", multiTargeting: false); + string cache = projectFile + ".lscache"; + + ProcessResult dtbResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(dtbResult.ExitCode == 0, dtbResult.Output); + Assert.True(File.Exists(cache), $"Expected ProjectDataBuild to produce {cache}.\n{dtbResult.Output}"); + string originalContent = File.ReadAllText(cache); + + // Simulate the ordinary incremental build hook: EnableProjectDataOnBuild without the + // ProjectDataBuild graph (so CoreCompile is not forced) and empty CscCommandLineArgs. + ProcessResult hookResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:_WriteProjectData", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataOnBuild=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(hookResult.ExitCode == 0, hookResult.Output); + Assert.True(File.Exists(cache), $"The non-forced empty-args hook must NOT delete the existing good cache {cache}.\n{hookResult.Output}"); + Assert.Equal(originalContent, File.ReadAllText(cache)); + AssertNoUnsupportedMarker(projectFile); + } + + [Fact] + public async Task ProjectDataBuild_NonSdkProject_IsNoOp() + { + string projectFile = this.WriteLegacyProject("LegacyApp.csproj"); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.False(File.Exists(projectFile + ".lscache"), $"Non-SDK projects should not produce project data.\n{result.Output}"); + AssertUnsupportedMarker(projectFile, "UsingMicrosoftNETSdkFalse"); + } + + [Fact] + public async Task ProjectDataBuild_SdkFSharpProject_IsNoOp() + { + string projectFile = this.WriteProject("FSharpApp.fsproj", multiTargeting: false, extraProperties: "F#"); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.False(File.Exists(projectFile + ".lscache"), $"Non-C# projects should not produce project data.\n{result.Output}"); + AssertUnsupportedMarker(projectFile, "LanguageNotCSharp"); + } + + [Fact] + public async Task ProjectDataBuild_NoTargetsSdkIdentity_IsNoOp() + { + // Microsoft.Build.NoTargets sets this property in its Sdk.props. Set the + // identity bit directly so the smoke test does not have to resolve an + // external MSBuild SDK package. + string projectFile = this.WriteProject( + "NoTargets.csproj", + multiTargeting: false, + extraProperties: + """ + true + """); + await File.WriteAllTextAsync(projectFile + ".lscache", "stale", TestContext.Current.CancellationToken); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.False(File.Exists(projectFile + ".lscache"), $"Microsoft.Build.NoTargets projects should not produce or retain project data.\n{result.Output}"); + AssertUnsupportedMarker(projectFile, "MicrosoftBuildNoTargetsSdk"); + } + + [Fact] + public async Task ProjectDataBuild_ExcludedProject_PreservesProjectFolderCache() + { + string projectFile = this.WriteProject( + "Excluded.csproj", + multiTargeting: false, + extraProperties: + """ + true + """); + await File.WriteAllTextAsync(projectFile + ".lscache", "committed cache", TestContext.Current.CancellationToken); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + "/p:OS=Unix", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.Equal("committed cache", File.ReadAllText(projectFile + ".lscache")); + AssertUnsupportedMarker(projectFile, "ExcludeFromBuildTrue"); + } + + [Fact] + public async Task MultiTfmProject_DTBProducesMergedProjectDataFile() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: true); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + string expected = projectFile + ".lscache"; + Assert.True(File.Exists(expected), $"Expected the merge target to produce {expected}.\n{result.Output}"); + + string content = File.ReadAllText(expected); + Assert.Contains("[commandLineArguments]", content); + } + + [Fact] + public async Task MultiTfmProject_ProjectDataBuildWritesSdkLayoutSlices() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: true, targetFrameworks: "net8.0;net9.0"); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.True( + File.Exists(Path.Combine(this.workDir, "obj", "Debug", "net8.0", "App.csproj.slice")), + $"Expected ProjectData to write the net8.0 slice to the deterministic TFM path.\n{result.Output}"); + Assert.True( + File.Exists(Path.Combine(this.workDir, "obj", "Debug", "net9.0", "App.csproj.slice")), + $"Expected ProjectData to write the net9.0 slice to the deterministic TFM path.\n{result.Output}"); + + string expected = projectFile + ".lscache"; + Assert.True(File.Exists(expected), $"Expected the merge target to produce {expected}.\n{result.Output}"); + AssertNoUnsupportedMarker(projectFile); + + string content = File.ReadAllText(expected).Replace("\r\n", "\n"); + Assert.Equal(2, CountOccurrences(content, "\n[sliceDimensions]\n")); + Assert.Contains("TargetFramework=net8.0", content); + Assert.Contains("TargetFramework=net9.0", content); + } + + [Fact] + public async Task MultiTfmUserFolderProject_RegeneratesCacheWhenStampExists() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: true); + string cacheRoot = GetTestCacheRoot(projectFile); + string stampPath = Path.Combine(this.workDir, "obj", "Debug", "App.csproj.lscache.stamp"); + string[] args = + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=false", + ]; + + ProcessResult firstResult = await RunDotnetMsbuildAsync(projectFile, extraArgs: args); + + Assert.True(firstResult.ExitCode == 0, firstResult.Output); + Assert.True(File.Exists(stampPath), $"Expected user-folder multi-TFM merge stamp at {stampPath}.\n{firstResult.Output}"); + string[] cacheFiles = Directory.GetFiles(cacheRoot, "*", SearchOption.AllDirectories); + string cacheFile = Assert.Single(cacheFiles); + Assert.Contains("version=2", File.ReadAllText(cacheFile)); + File.Delete(cacheFile); + + ProcessResult secondResult = await RunDotnetMsbuildAsync(projectFile, extraArgs: args); + + Assert.True(secondResult.ExitCode == 0, secondResult.Output); + string regeneratedCacheFile = Assert.Single(Directory.GetFiles(cacheRoot, "*", SearchOption.AllDirectories)); + Assert.Contains("version=2", File.ReadAllText(regeneratedCacheFile)); + AssertNoUnsupportedMarker(projectFile); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task MultiTfmUserFolderProject_WhenMergeFails_DoesNotRefreshStamp(bool force) + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: true); + string stampPath = Path.Combine(this.workDir, "obj", "Debug", "App.csproj.lscache.stamp"); + string cacheRootFile = Path.Combine(this.workDir, "user-cache-file"); + await File.WriteAllTextAsync(cacheRootFile, "not a directory", TestContext.Current.CancellationToken); + List args = + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=false", + ]; + if (force) + { + args.Add("/p:_ProjectDataBuildForce=true"); + } + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: [.. args], + extraEnv: new() { ["DOTNET_PROJECTDATA_CACHE_DIR"] = cacheRootFile }); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.Contains("ProjectData: failed to merge slices", result.Output); + Assert.False(File.Exists(stampPath), $"A failed user-folder merge must not mark ProjectData as fresh.\n{result.Output}"); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task MultiTfmRidProject_ProjectDataBuildProducesMergedProjectDataFile(bool force) + { + const string runtimeIdentifier = "win-x64"; + string projectFile = this.WriteProject("App.csproj", multiTargeting: true, runtimeIdentifier: runtimeIdentifier); + List args = + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]; + if (force) + { + args.Add("/p:_ProjectDataBuildForce=true"); + } + + ProcessResult result = await RunDotnetMsbuildAsync(projectFile, extraArgs: [.. args]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.True( + File.Exists(Path.Combine(this.workDir, "obj", "Debug", "net8.0", runtimeIdentifier, "App.csproj.slice")), + $"Expected the net8.0 RID-specific inner slice.\n{result.Output}"); + Assert.True( + File.Exists(Path.Combine(this.workDir, "obj", "Debug", "net9.0", runtimeIdentifier, "App.csproj.slice")), + $"Expected the net9.0 RID-specific inner slice.\n{result.Output}"); + Assert.False( + File.Exists(Path.Combine(this.workDir, "obj", "Debug", "net8.0", "App.csproj.slice")), + $"The SDK should place RID-specific slices under the runtime identifier directory.\n{result.Output}"); + + string expected = projectFile + ".lscache"; + Assert.True(File.Exists(expected), $"Expected the merge target to produce {expected}.\n{result.Output}"); + AssertNoUnsupportedMarker(projectFile); + + string content = File.ReadAllText(expected).Replace("\r\n", "\n"); + Assert.Equal(2, CountOccurrences(content, "\n[sliceDimensions]\n")); + Assert.Contains("TargetFramework=net8.0", content); + Assert.Contains("TargetFramework=net9.0", content); + } + + [Fact] + public async Task MultiTfmRidProject_WhenRuntimeIdentifierOutputPathAppendDisabled_ProducesMergedProjectDataFile() + { + const string runtimeIdentifier = "win-x64"; + string projectFile = this.WriteProject( + "App.csproj", + multiTargeting: true, + runtimeIdentifier: runtimeIdentifier, + extraProperties: "false"); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.True( + File.Exists(Path.Combine(this.workDir, "obj", "Debug", "net8.0", "App.csproj.slice")), + $"Expected the net8.0 non-RID inner slice when AppendRuntimeIdentifierToOutputPath=false.\n{result.Output}"); + Assert.True( + File.Exists(Path.Combine(this.workDir, "obj", "Debug", "net9.0", "App.csproj.slice")), + $"Expected the net9.0 non-RID inner slice when AppendRuntimeIdentifierToOutputPath=false.\n{result.Output}"); + Assert.False( + File.Exists(Path.Combine(this.workDir, "obj", "Debug", "net8.0", runtimeIdentifier, "App.csproj.slice")), + $"The SDK should not place slices under the runtime identifier directory when AppendRuntimeIdentifierToOutputPath=false.\n{result.Output}"); + + string expected = projectFile + ".lscache"; + Assert.True(File.Exists(expected), $"Expected the merge target to produce {expected}.\n{result.Output}"); + AssertNoUnsupportedMarker(projectFile); + + string content = File.ReadAllText(expected).Replace("\r\n", "\n"); + Assert.Equal(2, CountOccurrences(content, "\n[sliceDimensions]\n")); + Assert.Contains("TargetFramework=net8.0", content); + Assert.Contains("TargetFramework=net9.0", content); + } + + [Fact] + public async Task MultiTfmProject_ProjectDataBuildWithUnchangedSlicesPreservesMergedCache() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: true); + string cacheFile = projectFile + ".lscache"; + + ProcessResult firstResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(firstResult.ExitCode == 0, firstResult.Output); + Assert.True(File.Exists(cacheFile), $"Expected the first DTB to produce {cacheFile}.\n{firstResult.Output}"); + string expectedContent = File.ReadAllText(cacheFile); + File.SetLastWriteTimeUtc(cacheFile, DateTime.UtcNow.AddMinutes(5)); + + ProcessResult secondResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(secondResult.ExitCode == 0, secondResult.Output); + Assert.Equal(expectedContent, File.ReadAllText(cacheFile)); + AssertNoUnsupportedMarker(projectFile); + } + + [Fact] + public async Task MultiTfmProject_ForceProjectDataBuildRefreshesMergedOutputWithoutDeletingCache() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: true); + string cacheFile = projectFile + ".lscache"; + + ProcessResult firstResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(firstResult.ExitCode == 0, firstResult.Output); + Assert.True(File.Exists(cacheFile), $"Expected the first DTB to produce {cacheFile}.\n{firstResult.Output}"); + + await File.WriteAllTextAsync(cacheFile, "stale-cache-content", TestContext.Current.CancellationToken); + File.SetLastWriteTimeUtc(cacheFile, DateTime.UtcNow.AddMinutes(5)); + + ProcessResult secondResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + "/p:_ProjectDataBuildForce=true", + ]); + + Assert.True(secondResult.ExitCode == 0, secondResult.Output); + string content = File.ReadAllText(cacheFile).Replace("\r\n", "\n"); + Assert.DoesNotContain("stale-cache-content", content); + Assert.Contains("TargetFramework=net8.0", content); + Assert.Contains("TargetFramework=net9.0", content); + } + + [Fact] + public async Task MultiTfmProject_ProjectDataBuildRefreshesCommandLineArgumentChanges() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: true, targetFrameworks: "net8.0;net10.0"); + + ProcessResult firstResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + "/p:NoWarn=1111", + ]); + + Assert.True(firstResult.ExitCode == 0, firstResult.Output); + string content = File.ReadAllText(projectFile + ".lscache"); + Assert.Contains("1111", content); + + ProcessResult secondResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + "/p:NoWarn=2222", + ]); + + Assert.True(secondResult.ExitCode == 0, secondResult.Output); + content = File.ReadAllText(projectFile + ".lscache"); + Assert.DoesNotContain("1111", content); + Assert.Contains("2222", content); + } + + [Fact] + public async Task MultiTfmProject_RemovedTargetFrameworkStaleSliceIsNotMerged() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: true, targetFrameworks: "net8.0;net9.0"); + + ProcessResult firstResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(firstResult.ExitCode == 0, firstResult.Output); + string staleSlice = Path.Combine(this.workDir, "obj", "Debug", "net9.0", "App.csproj.slice"); + Assert.True(File.Exists(staleSlice), $"Expected first build to preserve the net9.0 slice.\n{firstResult.Output}"); + + projectFile = this.WriteProject("App.csproj", multiTargeting: true, targetFrameworks: "net8.0"); + + ProcessResult secondResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + "/p:OS=Windows_NT", + ]); + + Assert.True(secondResult.ExitCode == 0, secondResult.Output); + string content = File.ReadAllText(projectFile + ".lscache").Replace("\r\n", "\n"); + Assert.Contains("TargetFramework=net8.0", content); + Assert.DoesNotContain("TargetFramework=net9.0", content); + } + + [Fact] + public async Task MultiTfmProject_NonWindowsPreservesExistingUnevaluatedSlice() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: true, targetFrameworks: "net8.0;net9.0"); + + ProcessResult firstResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(firstResult.ExitCode == 0, firstResult.Output); + + projectFile = this.WriteProject("App.csproj", multiTargeting: true, targetFrameworks: "net8.0"); + + ProcessResult secondResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + "/p:OS=Unix", + ]); + + Assert.True(secondResult.ExitCode == 0, secondResult.Output); + string content = File.ReadAllText(projectFile + ".lscache").Replace("\r\n", "\n"); + Assert.Contains("TargetFramework=net8.0", content); + Assert.Contains("TargetFramework=net9.0", content); + } + + [Theory] + [InlineData("net8.0;net9.0", "net8.0", "net9.0")] + [InlineData("net9.0;net8.0", "net9.0", "net8.0")] + public async Task MultiTfmProject_DTBMarksFirstTargetFrameworkAsPrimary( + string targetFrameworks, + string expectedPrimary, + string expectedNonPrimary) + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: true, targetFrameworks: targetFrameworks); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + string cacheFile = projectFile + ".lscache"; + Assert.True(File.Exists(cacheFile), $"Expected the merge target to produce {cacheFile}.\n{result.Output}"); + + string content = File.ReadAllText(cacheFile).Replace("\r\n", "\n"); + Assert.Equal(1, CountOccurrences(content, "\nprimary\n")); + Assert.Contains("\nprimary\n", GetSliceBlock(content, expectedPrimary)); + Assert.DoesNotContain("\nprimary\n", GetSliceBlock(content, expectedNonPrimary)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task Compile_WritesProjectDataOnlyWhenBuildHookIsEnabled(bool enableProjectDataOnBuild) + { + await File.WriteAllTextAsync( + Path.Combine(this.workDir, "Program.cs"), + "using System; Console.WriteLine(\"hello\");", + TestContext.Current.CancellationToken); + string projectFile = this.WriteProject( + "App.csproj", + multiTargeting: false, + extraXml: + """ + + + + """); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:Compile", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:UseAppHost=false", + "/p:EnableProjectDataInProjectFolder=true", + $"/p:EnableProjectDataOnBuild={enableProjectDataOnBuild.ToString().ToLowerInvariant()}", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + string cacheFile = projectFile + ".lscache"; + Assert.Equal(enableProjectDataOnBuild, File.Exists(cacheFile)); + } + + [Fact] + public async Task DefaultMode_WritesToUserFolderCacheLocation() + { + // Default user-folder mode writes under DOTNET_PROJECTDATA_CACHE_DIR + // (test-only override) using the same SHA-1 layout the reader uses. + string projectFile = this.WriteProject("App.csproj", multiTargeting: false); + string cacheRoot = Path.Combine(this.workDir, "user-cache"); + Directory.CreateDirectory(cacheRoot); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/v:n", + ], + extraEnv: new() { ["DOTNET_PROJECTDATA_CACHE_DIR"] = cacheRoot }); + + Assert.True(result.ExitCode == 0, result.Output); + + // Confirm at least one cache file appeared somewhere under the override + // root, and nothing was written next to the .csproj. + string[] cacheFiles = Directory.GetFiles(cacheRoot, "*", SearchOption.AllDirectories); + Assert.True(cacheFiles.Length > 0, $"Expected user-folder cache file under {cacheRoot}.\n--- MSBUILD OUTPUT ---\n{result.Output}"); + Assert.False( + File.Exists(projectFile + ".lscache"), + $"Expected the cache to live under the user-folder root, not next to the .csproj.\n{result.Output}"); + } + + [Fact] + public async Task ExplicitUserFolderMode_RefreshesStampOnProjectDataBuild() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: false); + string cacheRoot = Path.Combine(this.workDir, "user-cache"); + Directory.CreateDirectory(cacheRoot); + + string[] args = + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=false", + ]; + + ProcessResult firstResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: args, + extraEnv: new() { ["DOTNET_PROJECTDATA_CACHE_DIR"] = cacheRoot }); + + Assert.True(firstResult.ExitCode == 0, firstResult.Output); + string stampPath = Path.Combine(this.workDir, "obj", "Debug", "net8.0", "App.csproj.lscache.stamp"); + Assert.True(File.Exists(stampPath), $"Expected user-folder stamp at {stampPath}.\n{firstResult.Output}"); + DateTime firstStampTime = File.GetLastWriteTimeUtc(stampPath); + + await Task.Delay(TimeSpan.FromSeconds(1.1), TestContext.Current.CancellationToken); + + ProcessResult secondResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: args, + extraEnv: new() { ["DOTNET_PROJECTDATA_CACHE_DIR"] = cacheRoot }); + + Assert.True(secondResult.ExitCode == 0, secondResult.Output); + Assert.True(File.GetLastWriteTimeUtc(stampPath) > firstStampTime); + } + + [Fact] + public async Task NoAssetsFile_DoesNotIncludeRestoreInDependsOn() + { + // ProjectDataBuild requires a restored evaluation. It must not run Restore + // inside the same MSBuild invocation because generated NuGet imports would + // be written after this project was already evaluated. + string projectFile = this.WriteProject("App.csproj", multiTargeting: false, writeAssetsFile: false); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/getProperty:_ProjectDataBuildDependsOn", + "/p:DesignTimeBuild=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.DoesNotContain("Restore;", result.Output); + Assert.Contains("Compile", result.Output); + } + + [Fact] + public async Task FreshAssetsFile_OmitsRestoreFromDependsOn() + { + // Steady-state path: when obj/project.assets.json exists AND is newer than + // the project file, Restore is dropped from the dependsOn list. The host + // owns restore lifecycle and re-running it on every DTB is wasted work + // (~150-300ms warm). + string projectFile = this.WriteProject("App.csproj", multiTargeting: false); + + // Place a fresh assets file (mtime later than the .csproj). + string objDir = Path.Combine(Path.GetDirectoryName(projectFile)!, "obj"); + Directory.CreateDirectory(objDir); + string assetsFile = Path.Combine(objDir, "project.assets.json"); + await File.WriteAllTextAsync(assetsFile, "{}", TestContext.Current.CancellationToken); + File.SetLastWriteTime(assetsFile, DateTime.Now.AddMinutes(1)); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/getProperty:_ProjectDataBuildDependsOn", + "/p:DesignTimeBuild=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.DoesNotContain("Restore;", result.Output); + // Inner-build target list must still be there. + Assert.Contains("Compile", result.Output); + } + + [Fact] + public async Task UnsupportedSdkProject_UsesMarkerOnlyDependsOn() + { + string projectFile = this.WriteProject("FSharpApp.fsproj", multiTargeting: false, extraProperties: "F#"); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/getProperty:_ProjectDataBuildDependsOn", + "/p:DesignTimeBuild=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.Contains("_DeleteUnsupportedProjectData;_WriteUnsupportedProjectDataMarker", result.Output); + Assert.DoesNotContain("Compile", result.Output); + Assert.DoesNotContain("DispatchToInnerBuilds", result.Output); + } + + [Fact] + public async Task MultiTfmOuterBuild_DispatchesInnerBuildsInDependsOn() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: true); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/getProperty:_ProjectDataBuildDependsOn", + "/p:DesignTimeBuild=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.Contains("DispatchToInnerBuilds", result.Output); + Assert.DoesNotContain("_DeleteUnsupportedProjectData;_WriteUnsupportedProjectDataMarker", result.Output); + } + + [Fact] + public async Task MultiTfmUnsupportedInnerBuild_UsesMarkerOnlyDependsOn() + { + string projectFile = this.WriteProject("FSharpApp.fsproj", multiTargeting: true, extraProperties: "F#"); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/getProperty:_ProjectDataBuildDependsOn", + "/p:DesignTimeBuild=true", + "/p:EnableProjectDataInProjectFolder=true", + "/p:TargetFramework=net8.0", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.Contains("_DeleteUnsupportedProjectData;_WriteUnsupportedProjectDataMarker", result.Output); + Assert.DoesNotContain("Compile", result.Output); + Assert.DoesNotContain("DispatchToInnerBuilds", result.Output); + } + + [Fact] + public async Task StaleAssetsFile_DoesNotIncludeRestoreInDependsOn() + { + // The ProjectDataBuild target must never add Restore to its target graph: + // generated NuGet imports would be written after this project was already + // evaluated. Restore orchestration belongs to the caller. + string projectFile = this.WriteProject("App.csproj", multiTargeting: false); + + // Place an assets file with mtime BEFORE the .csproj (simulate user edit + // after a previous successful restore). + string objDir = Path.Combine(Path.GetDirectoryName(projectFile)!, "obj"); + Directory.CreateDirectory(objDir); + string assetsFile = Path.Combine(objDir, "project.assets.json"); + await File.WriteAllTextAsync(assetsFile, "{}", TestContext.Current.CancellationToken); + File.SetLastWriteTime(assetsFile, DateTime.Now.AddMinutes(-5)); + File.SetLastWriteTime(projectFile, DateTime.Now); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/getProperty:_ProjectDataBuildDependsOn", + "/p:DesignTimeBuild=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.DoesNotContain("Restore;", result.Output); + Assert.Contains("Compile", result.Output); + } + + [Fact] + public async Task ForceIncludeRestore_DoesNotAddRestoreToDependsOn() + { + // Historical compatibility: the old private switch no longer makes + // ProjectDataBuild run Restore inside this evaluation. + string projectFile = this.WriteProject("App.csproj", multiTargeting: false); + string objDir = Path.Combine(Path.GetDirectoryName(projectFile)!, "obj"); + Directory.CreateDirectory(objDir); + string assetsFile = Path.Combine(objDir, "project.assets.json"); + await File.WriteAllTextAsync(assetsFile, "{}", TestContext.Current.CancellationToken); + File.SetLastWriteTime(assetsFile, DateTime.Now.AddMinutes(1)); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/getProperty:_ProjectDataBuildDependsOn", + "/p:DesignTimeBuild=true", + "/p:EnableProjectDataInProjectFolder=true", + "/p:_ProjectDataBuildIncludeRestore=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.DoesNotContain("Restore;", result.Output); + } + + [Theory] + [InlineData(".sln")] + [InlineData(".slnx")] + public async Task SolutionLevel_DispatchesProjectDataBuildToAllProjects(string solutionExtension) + { + // Verifies that running /t:ProjectDataBuild against a solution file dispatches + // the custom target to each project, including multi-TFM outer builds. + string projectA = this.WriteProject("App.csproj", multiTargeting: true); + string projectBDir = Path.Combine(this.workDir, "Lib"); + Directory.CreateDirectory(projectBDir); + string projectB = Path.Combine(projectBDir, "Lib.csproj"); + string stubRef = Path.Combine(this.workDir, "stub-reference.dll"); + File.WriteAllText(projectB, + $""" + + + net8.0 + false + + + + + Microsoft.NETCore.App + + + + + """); + this.WriteProjectAssetsFile(projectB, ["net8.0"]); + + string solutionPath = Path.Combine(this.workDir, "Solution" + solutionExtension); + string receiptDirectory = Path.Combine(this.workDir, "receipts-" + solutionExtension.TrimStart('.')); + string attemptId = Guid.NewGuid().ToString("N"); + string slnRelA = Path.GetRelativePath(this.workDir, projectA).Replace('/', '\\'); + string slnRelB = Path.GetRelativePath(this.workDir, projectB).Replace('/', '\\'); + if (solutionExtension.Equals(".sln", StringComparison.OrdinalIgnoreCase)) + { + File.WriteAllText(solutionPath, + $$""" + Microsoft Visual Studio Solution File, Format Version 12.00 + # Visual Studio Version 17 + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "{{slnRelA}}", "{11111111-1111-1111-1111-111111111111}" + EndProject + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lib", "{{slnRelB}}", "{22222222-2222-2222-2222-222222222222}" + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {11111111-1111-1111-1111-111111111111}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11111111-1111-1111-1111-111111111111}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22222222-2222-2222-2222-222222222222}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22222222-2222-2222-2222-222222222222}.Debug|Any CPU.Build.0 = Debug|Any CPU + EndGlobalSection + EndGlobal + """); + } + else + { + File.WriteAllText(solutionPath, + $$""" + + + + + """); + } + + ProcessResult result = await RunDotnetMsbuildAsync( + solutionPath, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + $"/p:ProjectDataBuildReceiptDirectory={receiptDirectory}", + $"/p:ProjectDataBuildReceiptAttemptId={attemptId}", + GetCompletionLoggerArgument(receiptDirectory, attemptId), + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.True(File.Exists(projectA + ".lscache"), $"Expected lscache for App.csproj.\n{result.Output}"); + Assert.True(File.Exists(projectB + ".lscache"), $"Expected lscache for Lib.csproj.\n{result.Output}"); + Assert.True(ProjectDataBuildReceipt.TryRead(receiptDirectory, attemptId, projectA, out _)); + Assert.True(ProjectDataBuildReceipt.TryRead(receiptDirectory, attemptId, projectB, out _)); + Assert.True(ProjectDataBuildReceipt.TryReadAggregateCompletion(receiptDirectory, attemptId)); + Assert.True(ProjectDataBuildAttemptManifest.TryRead(receiptDirectory, attemptId, out ProjectDataBuildAttemptManifest manifest)); + Assert.True(manifest.BuildFinished); + Assert.True(manifest.BuildSucceeded); + Assert.NotEmpty(manifest.Contexts); + Assert.Contains(manifest.Submissions, submission => submission.Phase == "ProjectDataBuild"); + + string appCacheContent = File.ReadAllText(projectA + ".lscache").Replace("\r\n", "\n"); + Assert.Contains("TargetFramework=net8.0", appCacheContent); + Assert.Contains("TargetFramework=net9.0", appCacheContent); + } + + [Fact] + public async Task ProjectDataBuild_ReceiptProtocol_NormalAggregateFailureHasManifestWithoutProjectCompletion() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: false, writeAssetsFile: false); + string receiptDirectory = Path.Combine(this.workDir, "failure-receipts"); + string attemptId = Guid.NewGuid().ToString("N"); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + $"/p:ProjectDataBuildReceiptDirectory={receiptDirectory}", + $"/p:ProjectDataBuildReceiptAttemptId={attemptId}", + GetCompletionLoggerArgument(receiptDirectory, attemptId), + ]); + + Assert.NotEqual(0, result.ExitCode); + Assert.False(ProjectDataBuildReceipt.TryRead(receiptDirectory, attemptId, projectFile, out _)); + Assert.True(ProjectDataBuildReceipt.TryReadAggregateCompletion(receiptDirectory, attemptId)); + Assert.True(ProjectDataBuildAttemptManifest.TryRead(receiptDirectory, attemptId, out ProjectDataBuildAttemptManifest manifest)); + Assert.True(manifest.BuildFinished); + Assert.False(manifest.BuildSucceeded); + ProjectDataBuildDiagnosticRecord diagnostic = Assert.Single(manifest.Diagnostics, diagnostic => diagnostic.Severity == "Error"); + Assert.True(string.Equals( + projectFile, + diagnostic.ProjectFilePath, + OperatingSystem.IsLinux() ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); + Assert.Contains("project.assets.json", diagnostic.Message, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task ProjectDataBuild_StaticGraphRestorePreservesDirectFailureAttributionAndTrimmedProjectsProduceCaches() + { + string missingFeed = Path.Combine(this.workDir, "missing-private-feed"); + File.WriteAllText( + Path.Combine(this.workDir, "NuGet.Config"), + $""" + + + + + + + + """); + + string broken = WriteRestoreProject( + "Broken", + """ """); + string dependent = WriteRestoreProject( + "Dependent", + """ """); + string healthyA = WriteRestoreProject("HealthyA"); + string healthyB = WriteRestoreProject("HealthyB"); + string fullSolution = WriteSolution("Full.sln", [broken, dependent, healthyA, healthyB]); + string fullReceiptDirectory = Path.Combine(this.workDir, "full-restore-receipts"); + string fullAttemptId = Guid.NewGuid().ToString("N"); + + ProcessResult fullResult = await RunDotnetMsbuildAsync( + fullSolution, + extraArgs: + [ + "/t:ProjectDataBuild", + "/m:1", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + $"/p:ProjectDataBuildReceiptDirectory={fullReceiptDirectory}", + $"/p:ProjectDataBuildReceiptAttemptId={fullAttemptId}", + GetCompletionLoggerArgument(fullReceiptDirectory, fullAttemptId), + ], + useBuildCommand: true); + + Assert.NotEqual(0, fullResult.ExitCode); + Assert.True( + ProjectDataBuildAttemptManifest.TryRead(fullReceiptDirectory, fullAttemptId, out ProjectDataBuildAttemptManifest fullManifest), + fullResult.Output); + ProjectDataBuildDiagnosticRecord directFailure = Assert.Single( + fullManifest.Diagnostics, + diagnostic => + string.Equals(diagnostic.Severity, "Error", StringComparison.OrdinalIgnoreCase) && + string.Equals(diagnostic.Code, "NU1301", StringComparison.OrdinalIgnoreCase) && + string.Equals(diagnostic.ProjectFilePath, broken, StringComparison.OrdinalIgnoreCase)); + Assert.Equal(ProjectDataBuildDiagnosticRecord.FileProjectPathSource, directFailure.ProjectFilePathSource); + Assert.False(File.Exists(healthyA + ".lscache")); + Assert.False(File.Exists(healthyB + ".lscache")); + + string trimmedSolution = WriteSolution("Trimmed.sln", [healthyA, healthyB]); + string trimmedReceiptDirectory = Path.Combine(this.workDir, "trimmed-restore-receipts"); + string trimmedAttemptId = Guid.NewGuid().ToString("N"); + ProcessResult trimmedResult = await RunDotnetMsbuildAsync( + trimmedSolution, + extraArgs: + [ + "/t:ProjectDataBuild", + "/m:1", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + $"/p:ProjectDataBuildReceiptDirectory={trimmedReceiptDirectory}", + $"/p:ProjectDataBuildReceiptAttemptId={trimmedAttemptId}", + GetCompletionLoggerArgument(trimmedReceiptDirectory, trimmedAttemptId), + ], + useBuildCommand: true); + + Assert.True(trimmedResult.ExitCode == 0, trimmedResult.Output); + Assert.True(ProjectDataBuildReceipt.TryRead(trimmedReceiptDirectory, trimmedAttemptId, healthyA, out _)); + Assert.True(ProjectDataBuildReceipt.TryRead(trimmedReceiptDirectory, trimmedAttemptId, healthyB, out _)); + Assert.True(File.Exists(healthyA + ".lscache")); + Assert.True(File.Exists(healthyB + ".lscache")); + Assert.StartsWith("version=2", File.ReadAllText(healthyA + ".lscache")); + Assert.StartsWith("version=2", File.ReadAllText(healthyB + ".lscache")); + + string WriteRestoreProject(string name, string item = "") + { + string directory = Path.Combine(this.workDir, name); + Directory.CreateDirectory(directory); + string projectPath = Path.Combine(directory, $"{name}.csproj"); + File.WriteAllText( + projectPath, + $$""" + + + net10.0 + false + + + {{item}} + + + """); + return projectPath; + } + + string WriteSolution(string fileName, IReadOnlyCollection projects) + { + string solutionPath = Path.Combine(this.workDir, fileName); + string[] projectEntries = projects.Select((project, index) => + { + string relativePath = Path.GetRelativePath(this.workDir, project).Replace('/', '\\'); + string projectName = Path.GetFileNameWithoutExtension(project); + string projectGuid = $"{{00000000-0000-0000-0000-{index + 1:D12}}}"; + return $"Project(\"{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}\") = \"{projectName}\", \"{relativePath}\", \"{projectGuid}\"{Environment.NewLine}EndProject"; + }).ToArray(); + string[] projectConfigurations = projects.SelectMany((_, index) => + { + string projectGuid = $"{{00000000-0000-0000-0000-{index + 1:D12}}}"; + return new[] + { + $" {projectGuid}.Debug|Any CPU.ActiveCfg = Debug|Any CPU", + $" {projectGuid}.Debug|Any CPU.Build.0 = Debug|Any CPU", + }; + }).ToArray(); + File.WriteAllText( + solutionPath, + $""" + Microsoft Visual Studio Solution File, Format Version 12.00 + # Visual Studio Version 17 + {string.Join(Environment.NewLine, projectEntries)} + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {string.Join(Environment.NewLine, projectConfigurations)} + EndGlobalSection + EndGlobal + """); + return solutionPath; + } + } + + [Theory] + [InlineData(1)] + [InlineData(4)] + public async Task ProjectDataBuild_ReceiptProtocol_EvaluationFailuresDoNotDropOtherReferencedProjects(int maxCpuCount) + { + string core = this.WriteGraphProject("Core"); + string broken = this.WriteGraphProject("Broken", ["Core"], failDuringProjectDataEvaluation: true); + string dependent = this.WriteGraphProject("Dependent", ["Broken"]); + string independent = this.WriteGraphProject("Independent", ["Core"]); + string tail = this.WriteGraphProject("Tail", ["Independent"]); + string[] projects = [core, broken, dependent, independent, tail]; + string solutionPath = Path.Combine(this.workDir, $"Graph-{maxCpuCount}.slnx"); + File.WriteAllText( + solutionPath, + $""" + + {string.Join(Environment.NewLine, projects.Select(project => $" "))} + + """); + string receiptDirectory = Path.Combine(this.workDir, $"graph-receipts-{maxCpuCount}"); + string attemptId = Guid.NewGuid().ToString("N"); + + ProcessResult result = await RunDotnetMsbuildAsync( + solutionPath, + extraArgs: + [ + "/t:ProjectDataBuild", + $"/m:{maxCpuCount}", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + $"/p:ProjectDataBuildReceiptDirectory={receiptDirectory}", + $"/p:ProjectDataBuildReceiptAttemptId={attemptId}", + GetCompletionLoggerArgument(receiptDirectory, attemptId), + ]); + + Assert.NotEqual(0, result.ExitCode); + Assert.True(ProjectDataBuildReceipt.TryReadAggregateCompletion(receiptDirectory, attemptId)); + Assert.False(ProjectDataBuildReceipt.TryRead(receiptDirectory, attemptId, broken, out _)); + foreach (string successfulProject in projects.Except([broken], StringComparer.OrdinalIgnoreCase)) + { + Assert.True( + ProjectDataBuildReceipt.TryRead(receiptDirectory, attemptId, successfulProject, out _), + $"Expected completion receipt for {successfulProject}.{Environment.NewLine}{result.Output}"); + Assert.True(File.Exists(successfulProject + ".lscache"), result.Output); + } + + Assert.True(ProjectDataBuildAttemptManifest.TryRead(receiptDirectory, attemptId, out ProjectDataBuildAttemptManifest manifest)); + Assert.Contains(manifest.Diagnostics, diagnostic => + diagnostic.Severity == "Error" && + string.Equals(diagnostic.ProjectFilePath, broken, OperatingSystem.IsLinux() ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public async Task ProjectDataBuild_ReceiptProtocol_LoadsCompletionLoggerFromQuotedDelimiterPath() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: false); + string loggerDirectory = Path.Combine(this.workDir, "logger,with;delimiters"); + Directory.CreateDirectory(loggerDirectory); + string loggerAssemblyPath = CopyAssembly(typeof(ProjectDataBuildCompletionLogger).Assembly.Location, loggerDirectory); + string receiptDirectory = Path.Combine(this.workDir, "delimiter-receipts"); + string attemptId = Guid.NewGuid().ToString("N"); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + $"/p:ProjectDataBuildReceiptDirectory={receiptDirectory}", + $"/p:ProjectDataBuildReceiptAttemptId={attemptId}", + GetCompletionLoggerArgument(receiptDirectory, attemptId, loggerAssemblyPath), + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.True(ProjectDataBuildReceipt.TryReadAggregateCompletion(receiptDirectory, attemptId)); + } + + [Fact] + public async Task ProjectDataBuild_ReceiptProtocol_UnsupportedProjectWritesMarkerAndCompletion() + { + string projectFile = this.WriteProject("FSharpApp.fsproj", multiTargeting: false, extraProperties: "F#"); + string receiptDirectory = Path.Combine(this.workDir, "unsupported-receipts"); + string attemptId = Guid.NewGuid().ToString("N"); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + $"/p:ProjectDataBuildReceiptDirectory={receiptDirectory}", + $"/p:ProjectDataBuildReceiptAttemptId={attemptId}", + GetCompletionLoggerArgument(receiptDirectory, attemptId), + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.True(ProjectDataBuildReceipt.TryRead(receiptDirectory, attemptId, projectFile, out _)); + AssertUnsupportedMarker(projectFile, "LanguageNotCSharp"); + } + + [Fact] + public async Task ProjectDataBuild_ReceiptProtocol_CompletionCanProduceNoOutputOrMarker() + { + string projectFile = this.WriteProject( + "App.csproj", + multiTargeting: false, + extraXml: + """ + + + + """); + string receiptDirectory = Path.Combine(this.workDir, "no-output-receipts"); + string attemptId = Guid.NewGuid().ToString("N"); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + $"/p:ProjectDataBuildReceiptDirectory={receiptDirectory}", + $"/p:ProjectDataBuildReceiptAttemptId={attemptId}", + GetCompletionLoggerArgument(receiptDirectory, attemptId), + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.True(ProjectDataBuildReceipt.TryRead(receiptDirectory, attemptId, projectFile, out _)); + Assert.False(File.Exists(projectFile + ".lscache")); + Assert.False(File.Exists(GetMarkerPath(projectFile))); + } + + [Fact] + public async Task SolutionLevel_ForcedProjectDataBuildPreservesMultiTfmSlices() + { + string projectA = this.WriteProject("App.csproj", multiTargeting: true); + string projectBDir = Path.Combine(this.workDir, "Lib"); + Directory.CreateDirectory(projectBDir); + string projectB = Path.Combine(projectBDir, "Lib.csproj"); + File.WriteAllText(projectB, + """ + + + net8.0 + false + + + """); + this.WriteProjectAssetsFile(projectB, ["net8.0"]); + + string solutionPath = Path.Combine(this.workDir, "Solution.slnx"); + string slnRelA = Path.GetRelativePath(this.workDir, projectA).Replace('/', '\\'); + string slnRelB = Path.GetRelativePath(this.workDir, projectB).Replace('/', '\\'); + File.WriteAllText(solutionPath, + $$""" + + + + + """); + + string cacheFile = projectA + ".lscache"; + await File.WriteAllTextAsync(cacheFile, "stale-cache-content", TestContext.Current.CancellationToken); + File.SetLastWriteTimeUtc(cacheFile, DateTime.UtcNow.AddMinutes(5)); + + // CI agents are firewalled away from api.nuget.org but have credentialed + // access to the org-internal feed. Point restore at that feed so it can + // satisfy the implicit framework-reference downloads + // (Microsoft.NETCore.App.Ref/.AspNetCore.App.Ref/etc) that the multi-TFM + // project requires. An empty source folder does NOT work here -- the + // SDK 10 install only ships the net10 pack, so net8/net9 must come from + // the feed. + string restoreSource = "https://pkgs.dev.azure.com/devdiv/DevDiv/_packaging/vs-green/nuget/v3/index.json"; + + ProcessResult result = await RunDotnetMsbuildAsync( + solutionPath, + extraArgs: + [ + "/restore", + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + "/p:_ProjectDataBuildForce=true", + $"/p:RestoreSources={restoreSource}", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.True(File.Exists(cacheFile), $"Expected lscache for App.csproj.\n{result.Output}"); + + string appCacheContent = File.ReadAllText(cacheFile).Replace("\r\n", "\n"); + Assert.DoesNotContain("stale-cache-content", appCacheContent); + Assert.Equal(2, CountOccurrences(appCacheContent, "\n[sliceDimensions]\n")); + Assert.Contains("TargetFramework=net8.0", appCacheContent); + Assert.Contains("TargetFramework=net9.0", appCacheContent); + } + + [Fact] + public async Task UnsupportedProject_ProjectDataBuildIsNoOp() + { + string projectFile = Path.Combine(this.workDir, "Unsupported.proj"); + File.WriteAllText(projectFile, + """ + + + + """); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.False(File.Exists(projectFile + ".lscache"), $"Unsupported project should not produce lscache.\n{result.Output}"); + AssertUnsupportedMarker(projectFile, "UsingMicrosoftNETSdkFalse"); + } + + [Fact] + public async Task ExcludedProject_ProjectDataBuildIsNoOp() + { + string projectFile = this.WriteProject( + "Excluded.csproj", + multiTargeting: false, + extraProperties: + """ + true + """); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.False(File.Exists(projectFile + ".lscache"), $"Excluded project should not produce lscache.\n{result.Output}"); + AssertUnsupportedMarker(projectFile, "ExcludeFromBuildTrue"); + } + + [Fact] + public async Task ProjectDataBuild_NetFrameworkProject_WithoutReferenceAssemblies_PreservesProjectFolderCacheAndWritesMissingReferenceMarker() + { + string projectFile = this.WriteProject( + "NetFramework.csproj", + multiTargeting: false, + targetFramework: "net472", + extraProperties: this.MissingNetFrameworkReferenceAssembliesProperties()); + await File.WriteAllTextAsync(projectFile + ".lscache", "stale", TestContext.Current.CancellationToken); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.DoesNotContain("CollectFrameworkReferences", result.Output); + Assert.Equal("stale", File.ReadAllText(projectFile + ".lscache")); + AssertUnsupportedMarker(projectFile, "MissingNetFrameworkReferenceAssemblies"); + } + + [Fact] + public async Task DirectWriteTarget_NetFrameworkProjectWithoutCommandLineArguments_PreservesProjectFolderCache() + { + string projectFile = this.WriteProject( + "NetFramework.csproj", + multiTargeting: false, + targetFramework: "net472", + extraProperties: $"{Path.Combine(this.workDir, "missing-netfx-refs")}"); + await File.WriteAllTextAsync(projectFile + ".lscache", "stale", TestContext.Current.CancellationToken); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:_WriteProjectData", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataOnBuild=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.Equal("stale", File.ReadAllText(projectFile + ".lscache")); + AssertUnsupportedMarker(projectFile, "MissingNetFrameworkReferenceAssemblies"); + } + + [Fact] + public async Task ProjectDataBuild_NetFrameworkProject_WithReferenceAssemblies_WritesProjectData() + { + string referenceAssemblyDirectory = this.WriteNet472ReferenceAssemblies(); + string projectFile = this.WriteProject( + "NetFramework.csproj", + multiTargeting: false, + targetFramework: "net472", + extraProperties: $"{referenceAssemblyDirectory}"); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.DoesNotContain("CollectFrameworkReferences", result.Output); + string cacheFile = projectFile + ".lscache"; + Assert.True(File.Exists(cacheFile), $"Expected .NET Framework project data when reference assemblies are available.\n{result.Output}"); + string content = File.ReadAllText(cacheFile).Replace("\r\n", "\n"); + Assert.DoesNotContain("[netFrameworkReferenceAssemblies]\n", content); + Assert.Contains("[metadataReferences]\n", content); + Assert.Contains("/v4.7.2/\n", content); + Assert.Contains(" mscorlib.dll", content); + Assert.Contains(" System.dll", content); + Assert.Contains(" System.Core.dll", content); + Assert.DoesNotContain(referenceAssemblyDirectory.Replace('\\', '/'), content); + AssertNoUnsupportedMarker(projectFile); + } + + [Fact] + public async Task MultiTfmProject_SkipsUnsupportedTargetFrameworkSlices() + { + string projectFile = this.WriteProject( + "Mixed.csproj", + multiTargeting: true, + targetFrameworks: "net8.0;net472", + extraProperties: this.MissingNetFrameworkReferenceAssembliesProperties()); + string staleNet472Slice = Path.Combine(this.workDir, "obj", "Debug", "net472", "Mixed.csproj.slice"); + Directory.CreateDirectory(Path.GetDirectoryName(staleNet472Slice)!); + await File.WriteAllTextAsync( + staleNet472Slice, + """ + version=2 + + [project] + project=Mixed.csproj + language=C# + + [sliceDimensions] + TargetFramework=net472 + + [metadataReferences] + /v4.7.2/ + """, + TestContext.Current.CancellationToken); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + string cacheFile = projectFile + ".lscache"; + Assert.True(File.Exists(cacheFile), $"Expected project data for supported target frameworks.\n{result.Output}"); + string content = File.ReadAllText(cacheFile).Replace("\r\n", "\n"); + Assert.Contains("TargetFramework=net8.0", content); + Assert.DoesNotContain("TargetFramework=net472", content); + Assert.DoesNotContain("", content); + Assert.False(File.Exists(staleNet472Slice), $"Unsupported slices should be deleted before merge.\n{result.Output}"); + AssertNoUnsupportedMarker(projectFile); + } + + [Fact] + public async Task MultiTfmProject_UnsupportedInnerBuild_PreservesMergedCache() + { + string projectFile = this.WriteProject( + "Mixed.csproj", + multiTargeting: true, + targetFrameworks: "net8.0;net472", + extraProperties: this.MissingNetFrameworkReferenceAssembliesProperties()); + string staleCache = projectFile + ".lscache"; + await File.WriteAllTextAsync(staleCache, "existing merged cache", TestContext.Current.CancellationToken); + string staleNet472Slice = Path.Combine(this.workDir, "obj", "Debug", "net472", "Mixed.csproj.slice"); + Directory.CreateDirectory(Path.GetDirectoryName(staleNet472Slice)!); + await File.WriteAllTextAsync(staleNet472Slice, "stale slice", TestContext.Current.CancellationToken); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + "/p:TargetFramework=net472", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.True(File.Exists(staleCache), $"Unsupported inner target framework builds should not delete the merged project cache.\n{result.Output}"); + Assert.Equal("existing merged cache", File.ReadAllText(staleCache)); + Assert.False(File.Exists(staleNet472Slice), $"Unsupported inner target framework builds should delete their stale slice.\n{result.Output}"); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task MultiTfmProject_AllUnsupportedSlices_PreservesStaleCacheAndWritesMarker(bool force) + { + string projectFile = this.WriteProject( + "NetFrameworkOnly.csproj", + multiTargeting: true, + targetFrameworks: "net472", + extraProperties: this.MissingNetFrameworkReferenceAssembliesProperties()); + string staleCache = projectFile + ".lscache"; + await File.WriteAllTextAsync(staleCache, "stale", TestContext.Current.CancellationToken); + string staleNet472Slice = Path.Combine(this.workDir, "obj", "Debug", "net472", "NetFrameworkOnly.csproj.slice"); + Directory.CreateDirectory(Path.GetDirectoryName(staleNet472Slice)!); + await File.WriteAllTextAsync(staleNet472Slice, "stale", TestContext.Current.CancellationToken); + + List args = + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]; + if (force) + { + args.Add("/p:_ProjectDataBuildForce=true"); + } + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: [.. args]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.False(File.Exists(staleNet472Slice), $"Unsupported slices should be deleted before merge.\n{result.Output}"); + Assert.Equal("stale", File.ReadAllText(staleCache)); + AssertUnsupportedMarker(projectFile, "AllTargetFrameworksUnsupported"); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task MultiTfmUserFolderProject_AllUnsupportedSlices_WritesMarker(bool force) + { + string projectFile = this.WriteProject( + "NetFrameworkOnly.csproj", + multiTargeting: true, + targetFrameworks: "net472", + extraProperties: this.MissingNetFrameworkReferenceAssembliesProperties()); + string stampPath = Path.Combine(this.workDir, "obj", "Debug", "NetFrameworkOnly.csproj.lscache.stamp"); + + List args = + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=false", + ]; + if (force) + { + args.Add("/p:_ProjectDataBuildForce=true"); + } + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: [.. args]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.False(File.Exists(projectFile + ".lscache"), $"User-folder mode should not write a project-folder cache.\n{result.Output}"); + Assert.True(File.Exists(stampPath), $"Expected user-folder multi-TFM merge stamp at {stampPath}.\n{result.Output}"); + AssertUnsupportedMarker(projectFile, "AllTargetFrameworksUnsupported"); + } + + [Fact] + public async Task ProjectDataBuild_RestoreSkippedAndAssetsMissing_FailsWithoutWritingCache() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: false, writeAssetsFile: false); + string cacheFile = projectFile + ".lscache"; + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.NotEqual(0, result.ExitCode); + AssertMissingAssetsError(projectFile, result.Output); + Assert.False(File.Exists(cacheFile), $"ProjectDataBuild should not create {cacheFile} without project.assets.json.\n{result.Output}"); + } + + [Fact] + public async Task ProjectDataBuild_RestoreSkippedAndAssetsMissing_PreservesExistingCache() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: false, writeAssetsFile: false); + string cacheFile = projectFile + ".lscache"; + await File.WriteAllTextAsync(cacheFile, "existing-cache", TestContext.Current.CancellationToken); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.NotEqual(0, result.ExitCode); + AssertMissingAssetsError(projectFile, result.Output); + Assert.Equal("existing-cache", File.ReadAllText(cacheFile)); + } + + [Fact] + public async Task ProjectDataBuild_StaleRestoreTimestampsWithResolvedInputs_WritesCache() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: false); + string cacheFile = projectFile + ".lscache"; + await File.WriteAllTextAsync(cacheFile, "existing-cache", TestContext.Current.CancellationToken); + + string objDir = Path.Combine(Path.GetDirectoryName(projectFile)!, "obj"); + string assetsFile = Path.Combine(objDir, "project.assets.json"); + string nugetCacheFile = Path.Combine(objDir, "project.nuget.cache"); + string escapedProjectFile = projectFile.Replace("\\", "\\\\"); + await File.WriteAllTextAsync( + nugetCacheFile, + $$"""{"version":2,"success":true,"projectFilePath":"{{escapedProjectFile}}"}""", + TestContext.Current.CancellationToken); + + DateTime projectTimeUtc = DateTime.UtcNow.AddMinutes(-5); + File.SetLastWriteTimeUtc(assetsFile, projectTimeUtc.AddMinutes(-5)); + File.SetLastWriteTimeUtc(projectFile, projectTimeUtc); + File.SetLastWriteTimeUtc(nugetCacheFile, projectTimeUtc.AddMinutes(-5)); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.True(File.Exists(cacheFile), $"ProjectDataBuild should write {cacheFile} when assets/imports/resolved references are available.\n{result.Output}"); + Assert.NotEqual("existing-cache", File.ReadAllText(cacheFile)); + } + + [Fact] + public async Task ProjectDataBuild_RestoreAndProjectDataBuildSameEvaluation_FailsWithoutWritingCache() + { + string projectFile = this.WriteProject("App.csproj", multiTargeting: false, targetFramework: "net11.0", writeAssetsFile: false); + string cacheFile = projectFile + ".lscache"; + string restoreSource = Path.Combine(this.workDir, "empty-restore-source"); + Directory.CreateDirectory(restoreSource); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:Restore;ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + $"/p:RestoreSources={restoreSource}", + ]); + + Assert.NotEqual(0, result.ExitCode); + Assert.Contains("were not imported into the current project evaluation", result.Output); + Assert.Contains("Do not run Restore and ProjectDataBuild in the same MSBuild evaluation", result.Output); + Assert.False(File.Exists(cacheFile), $"ProjectDataBuild should not create {cacheFile} when Restore ran in the same evaluation.\n{result.Output}"); + } + + [Fact] + public async Task ProjectDataBuild_NoMetadataReferencesResolved_WithoutNoStdLib_FailsWithoutWritingCache() + { + // Force `@(ReferencePathWithRefAssemblies)` to be empty by construction so the + // `_ValidateProjectDataMetadataReferences` target fires deterministically, regardless + // of which SDKs/targeting packs happen to be installed on the host machine. Using an + // "uninstalled TFM" (e.g. net7.0) would be brittle: it would silently regress the day + // someone installs that SDK, and it would exercise the SDK's unknown-framework error + // path rather than our validator. + string projectFile = this.WriteProject( + "NoRefs.csproj", + multiTargeting: false, + targetFramework: "net10.0", + extraXml: + """ + + + false + + + + + + """); + string cacheFile = projectFile + ".lscache"; + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.NotEqual(0, result.ExitCode); + Assert.Contains("no metadata references were resolved", result.Output); + Assert.Contains(projectFile, result.Output); + Assert.False(File.Exists(cacheFile), $"ProjectDataBuild should not create {cacheFile} when no metadata references resolved.\n{result.Output}"); + } + + [Fact] + public async Task ProjectDataBuild_NoMetadataReferencesResolved_WithoutNoStdLib_PreservesExistingCache() + { + string projectFile = this.WriteProject( + "NoRefs.csproj", + multiTargeting: false, + targetFramework: "net10.0", + extraXml: + """ + + + false + + + + + + """); + string cacheFile = projectFile + ".lscache"; + await File.WriteAllTextAsync(cacheFile, "existing-cache", TestContext.Current.CancellationToken); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.NotEqual(0, result.ExitCode); + Assert.Contains("no metadata references were resolved", result.Output); + Assert.Equal("existing-cache", File.ReadAllText(cacheFile)); + } + + [Fact] + public async Task ProjectDataBuild_NoStdLibWithoutMetadataReferences_WritesCache() + { + string projectFile = this.WriteProject( + "NoStdLibCoreLib.csproj", + multiTargeting: false, + targetFramework: "net10.0", + extraProperties: + """ + true + true + """); + string cacheFile = projectFile + ".lscache"; + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.True(File.Exists(cacheFile), $"NoStdLib projects with compiler command-line data should still produce project data.\n{result.Output}"); + string content = File.ReadAllText(cacheFile).Replace("\r\n", "\n"); + Assert.Contains("[commandLineArguments]\n", content); + Assert.Contains("[metadataReferences]\n", content); + AssertNoUnsupportedMarker(projectFile); + } + + [Fact] + public async Task ProjectDataBuild_ProjectReferenceOnlyMetadataReferences_WithoutNoStdLib_PreservesExistingCache() + { + string libraryFile = this.WriteProject("Core.csproj", multiTargeting: false); + string projectFile = this.WriteProject( + "App.csproj", + multiTargeting: false, + extraXml: + $$""" + + + + + + false + + + + + ProjectReference + + + + """); + string cacheFile = projectFile + ".lscache"; + await File.WriteAllTextAsync(cacheFile, "existing-cache", TestContext.Current.CancellationToken); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.NotEqual(0, result.ExitCode); + Assert.Contains("no framework reference assemblies were resolved", result.Output); + Assert.Equal("existing-cache", File.ReadAllText(cacheFile)); + } + + [Fact] + public async Task ProjectDataBuild_NoStdLibProjectReferenceMetadataReferences_WritesCache() + { + string libraryFile = this.WriteProject("Core.csproj", multiTargeting: false); + string projectReferenceAssembly = Path.Combine(this.workDir, "artifacts", "bin", "System.Runtime", "ref", "Debug", "net10.0", "System.Runtime.dll"); + Directory.CreateDirectory(Path.GetDirectoryName(projectReferenceAssembly)!); + await File.WriteAllTextAsync(projectReferenceAssembly, string.Empty, TestContext.Current.CancellationToken); + string projectFile = this.WriteProject( + "App.csproj", + multiTargeting: false, + targetFramework: "net10.0", + extraProperties: + """ + true + true + """, + extraXml: + $$""" + + + + + + + ProjectReference + + + + """); + string cacheFile = projectFile + ".lscache"; + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.True(File.Exists(cacheFile), $"NoStdLib projects should accept project-reference metadata references without FrameworkReferenceName metadata.\n{result.Output}"); + string content = File.ReadAllText(cacheFile).Replace("\r\n", "\n"); + Assert.Contains("[metadataReferences]\n", content); + Assert.Contains("System.Runtime.dll", content); + AssertNoUnsupportedMarker(projectFile); + } + + [Fact] + public async Task ValidateProjectDataMetadataReferences_NetStandardReferenceWithoutFrameworkReferenceName_Succeeds() + { + string netstandardReference = Path.Combine(this.workDir, "netstandard.dll"); + string projectFile = this.WriteProject( + "NetStandardApp.csproj", + multiTargeting: false, + targetFramework: "netstandard2.0", + extraProperties: + """ + true + true + """, + extraXml: + $$""" + + + + + + """); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:_ValidateProjectDataMetadataReferences", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task ProjectDataBuild_AnalyzerPolicy_NeverHasOrphanLines(bool enableNetAnalyzers, bool enforceCodeStyleInBuild) + { + // Writer regression guard for the + // "Gate [sdkAnalyzerConfigPolicy] lines on the SDK's analyzer-pack + // property gates" fix. Exercises the full toolchain (targets → writer → + // cache file) with every combination of the two gating properties and + // asserts the output never contains a policy line for an analyzer pack + // that has no DLLs in [analyzerReferences]. This is robust to *how* a + // future regression might happen — at the targets layer, the writer + // layer, a new policy type, or a new caller — because it asserts an + // invariant on the *output* rather than the writer's internal behavior. + string projectFile = this.WriteProject( + "AnalyzerPolicy.csproj", + multiTargeting: false, + targetFramework: "net10.0", + extraProperties: + $""" + {(enableNetAnalyzers ? "true" : "false")} + {(enforceCodeStyleInBuild ? "true" : "false")} + """); + string cacheFile = projectFile + ".lscache"; + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.True(File.Exists(cacheFile), $"Expected {cacheFile} to exist.\n{result.Output}"); + + string content = File.ReadAllText(cacheFile); + LscacheInvariants.AssertNoOrphanAnalyzerPolicyLines(content); + } + + [Fact] + public async Task ProjectDataBuild_RestoreSkippedAndPackageFilesMissing_FailsWithoutWritingCache() + { + const string packageId = "PackageApp.Dependency"; + const string packageVersion = "1.0.0"; + string projectFile = this.WriteProject( + "PackageApp.csproj", + multiTargeting: false, + extraXml: + $$""" + + + + """, + writeAssetsFile: false); + string packagesPath = Path.Combine(this.workDir, "packages"); + this.WriteProjectAssetsFileWithPackage(projectFile, packageId, packageVersion, "net8.0", packagesPath); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + "/p:ContinueOnError=true", + $"/p:RestorePackagesPath={packagesPath}", + ]); + + Assert.NotEqual(0, result.ExitCode); + Assert.Contains("restore graph does not contain declared PackageReference items", result.Output); + Assert.Contains(packageId, result.Output); + Assert.False(File.Exists(projectFile + ".lscache"), $"ProjectDataBuild should not create a cache when restored package files are missing.\n{result.Output}"); + } + + [Fact] + public async Task ProjectDataBuild_FloatingPackageVersionUsesConcreteRestoredPackageFolder() + { + const string packageId = "Floating.Package"; + const string requestedVersion = "11.0.0-preview.6.*"; + const string resolvedVersion = "11.0.0-preview.6.26359.118"; + const string targetFramework = "net8.0"; + string projectFile = this.WriteProject( + "FloatingPackage.csproj", + multiTargeting: false, + extraXml: + $$""" + + + + """, + writeAssetsFile: false); + string packagesPath = Path.Combine(this.workDir, "packages"); + string packagePath = Path.Combine(packagesPath, packageId.ToLowerInvariant(), resolvedVersion); + string packageAssetPath = Path.Combine(packagePath, "lib", targetFramework, packageId + ".dll"); + Directory.CreateDirectory(Path.GetDirectoryName(packageAssetPath)!); + await File.WriteAllBytesAsync(packageAssetPath, [], TestContext.Current.CancellationToken); + await File.WriteAllTextAsync( + Path.Combine(packagePath, packageId.ToLowerInvariant() + ".nuspec"), + "Floating.Package11.0.0-preview.6.26359.118TestTest", + TestContext.Current.CancellationToken); + await File.WriteAllTextAsync( + Path.Combine(packagePath, $"{packageId.ToLowerInvariant()}.{resolvedVersion}.nupkg.sha512"), + string.Empty, + TestContext.Current.CancellationToken); + this.WriteProjectAssetsFileWithPackage( + projectFile, + packageId, + resolvedVersion, + targetFramework, + packagesPath, + requestedVersion); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:ProjectDataBuild", + "/p:DesignTimeBuild=true", + "/p:BuildingProject=false", + "/p:SkipCompilerExecution=true", + "/p:EnableProjectDataInProjectFolder=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + Assert.True(File.Exists(projectFile + ".lscache"), $"ProjectDataBuild should use the concrete package folder selected by restore.\n{result.Output}"); + } + + [Fact] + public async Task ValidateProjectDataResolvedPackages_MatchesCentrallyManagedReferenceWithoutVersionMetadata() + { + const string packageId = "Central.Package"; + const string resolvedVersion = "2.0.0"; + string packagePath = Path.Combine(this.workDir, "packages", packageId.ToLowerInvariant(), resolvedVersion); + Directory.CreateDirectory(packagePath); + string projectFile = this.WriteProject( + "CentralPackage.csproj", + multiTargeting: false, + extraXml: + $$""" + + + + + + + + <_PackageDependenciesDesignTime Include="{{packageId}}/{{resolvedVersion}}"> + {{packageId}} + {{resolvedVersion}} + {{packagePath}} + + + + """, + extraProperties: "true", + writeAssetsFile: false); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:_ValidateProjectDataResolvedPackages", + "/p:_ProjectDataCanWriteOutput=true", + ]); + + Assert.True(result.ExitCode == 0, result.Output); + } + + [Fact] + public async Task ValidateProjectDataResolvedPackages_ReportsVersionChangeMissingFromRestoreGraph() + { + const string packageId = "Stale.Package"; + const string requestedVersion = "12.0.3"; + const string resolvedVersion = "13.0.1"; + const string targetFramework = "net8.0"; + string projectFile = this.WriteProject( + "StalePackageVersion.csproj", + multiTargeting: false, + extraXml: + $$""" + + + + """, + writeAssetsFile: false); + string packagesPath = Path.Combine(this.workDir, "packages"); + string packagePath = Path.Combine(packagesPath, packageId.ToLowerInvariant(), resolvedVersion); + string packageAssetPath = Path.Combine(packagePath, "lib", targetFramework, packageId + ".dll"); + Directory.CreateDirectory(Path.GetDirectoryName(packageAssetPath)!); + await File.WriteAllBytesAsync(packageAssetPath, [], TestContext.Current.CancellationToken); + await File.WriteAllTextAsync( + Path.Combine(packagePath, packageId.ToLowerInvariant() + ".nuspec"), + "Stale.Package13.0.1TestTest", + TestContext.Current.CancellationToken); + await File.WriteAllTextAsync( + Path.Combine(packagePath, $"{packageId.ToLowerInvariant()}.{resolvedVersion}.nupkg.sha512"), + string.Empty, + TestContext.Current.CancellationToken); + this.WriteProjectAssetsFileWithPackage(projectFile, packageId, resolvedVersion, targetFramework, packagesPath); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:_ValidateProjectDataAssetsFile;ResolvePackageAssets;_ValidateProjectDataResolvedPackages", + "/p:_ProjectDataCanWriteOutput=true", + ]); + + Assert.NotEqual(0, result.ExitCode); + Assert.Contains("declared PackageReference requests differ from the restore graph", result.Output); + Assert.Contains(packageId, result.Output); + Assert.Contains($"current request '{requestedVersion}'", result.Output); + Assert.Contains("restored request", result.Output); + } + + [Theory] + [InlineData("runtime", "compile")] + [InlineData("build", "runtime")] + public async Task ValidateProjectDataResolvedPackages_ReportsAssetSelectionChangeMissingFromRestoreGraph( + string restoredExcludeAssets, + string currentExcludeAssets) + { + const string packageId = "Microsoft.Build.Utilities.Core"; + const string packageVersion = "17.14.0-preview-25119-36"; + + string WriteFilteredProject(string excludeAssets) + => this.WriteProject( + "StalePackageAssets.csproj", + multiTargeting: false, + targetFramework: "net9.0", + extraXml: + $$""" + + + + """, + writeAssetsFile: false); + + string projectFile = WriteFilteredProject(restoredExcludeAssets); + ProcessResult restoreResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:Restore", + $"/p:RestoreConfigFile={Path.Combine(FindRepoRoot(), "NuGet.config")}", + ]); + Assert.True(restoreResult.ExitCode == 0, restoreResult.Output); + + ProcessResult matchingResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:_ValidateProjectDataAssetsFile;ResolvePackageAssets;_ValidateProjectDataResolvedPackages", + "/p:_ProjectDataCanWriteOutput=true", + ]); + Assert.True(matchingResult.ExitCode == 0, matchingResult.Output); + + WriteFilteredProject(currentExcludeAssets); + ProcessResult staleResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:_ValidateProjectDataAssetsFile;ResolvePackageAssets;_ValidateProjectDataResolvedPackages", + "/p:_ProjectDataCanWriteOutput=true", + ]); + + Assert.NotEqual(0, staleResult.ExitCode); + Assert.Contains("declared PackageReference requests differ from the restore graph", staleResult.Output); + Assert.Contains("current assets", staleResult.Output); + Assert.Contains(packageId, staleResult.Output); + } + + [Fact] + public async Task ValidateProjectDataResolvedPackages_ReportsActiveTransitiveCentralPinChangeMissingFromRestoreGraph() + { + const string directPackageId = "Microsoft.Build.Utilities.Core"; + const string directPackageVersion = "17.14.0-preview-25119-36"; + const string transitivePackageId = "Microsoft.NET.StringTools"; + const string restoredPinVersion = "18.9.11"; + const string currentPinVersion = "18.4.0"; + const string targetFramework = "net9.0"; + + string WritePinnedProject(string pinVersion) + { + File.WriteAllText( + Path.Combine(this.workDir, "Directory.Packages.props"), + $$""" + + + true + true + + + + + + + """); + return this.WriteProject( + "TransitivePin.csproj", + multiTargeting: false, + targetFramework: targetFramework, + extraXml: + $$""" + + + + """, + writeAssetsFile: false); + } + + string projectFile = WritePinnedProject(restoredPinVersion); + ProcessResult restoreResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:Restore", + $"/p:RestoreConfigFile={Path.Combine(FindRepoRoot(), "NuGet.config")}", + ]); + Assert.True(restoreResult.ExitCode == 0, restoreResult.Output); + + ProcessResult currentResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:_ValidateProjectDataAssetsFile;ResolvePackageAssets;_ValidateProjectDataResolvedPackages", + "/p:_ProjectDataCanWriteOutput=true", + ]); + Assert.True(currentResult.ExitCode == 0, currentResult.Output); + + WritePinnedProject(currentPinVersion); + ProcessResult staleResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:_ValidateProjectDataAssetsFile;ResolvePackageAssets;_ValidateProjectDataResolvedPackages", + "/p:_ProjectDataCanWriteOutput=true", + ]); + + Assert.NotEqual(0, staleResult.ExitCode); + Assert.Contains("central transitive package version requests differ from the restore graph", staleResult.Output); + Assert.Contains(transitivePackageId, staleResult.Output); + Assert.Contains($"current request '{currentPinVersion}'", staleResult.Output); + Assert.Contains($"restored request '[{restoredPinVersion}, )'", staleResult.Output); + } + + [Theory] + [InlineData(false, false, true)] + [InlineData(true, true, true)] + [InlineData(false, true, false)] + [InlineData(true, false, false)] + public async Task ValidateProjectDataResolvedPackages_ValidatesCentralTransitivePinningModeAgainstRestoreGraph( + bool restoredPinningEnabled, + bool currentPinningEnabled, + bool expectedSuccess) + { + const string directPackageId = "Microsoft.Build.Utilities.Core"; + const string directPackageVersion = "17.14.0-preview-25119-36"; + const string transitivePackageId = "Microsoft.NET.StringTools"; + const string pinVersion = "18.9.11"; + const string targetFramework = "net9.0"; + + string WritePinnedProject(bool pinningEnabled) + { + File.WriteAllText( + Path.Combine(this.workDir, "Directory.Packages.props"), + $$""" + + + true + {{pinningEnabled.ToString().ToLowerInvariant()}} + + + + + + + """); + return this.WriteProject( + "TransitivePinMode.csproj", + multiTargeting: false, + targetFramework: targetFramework, + extraXml: + $$""" + + + + """, + writeAssetsFile: false); + } + + string projectFile = WritePinnedProject(restoredPinningEnabled); + ProcessResult restoreResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:Restore", + $"/p:RestoreConfigFile={Path.Combine(FindRepoRoot(), "NuGet.config")}", + ]); + Assert.True(restoreResult.ExitCode == 0, restoreResult.Output); + + WritePinnedProject(currentPinningEnabled); + ProcessResult staleResult = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:_ValidateProjectDataAssetsFile;ResolvePackageAssets;_ValidateProjectDataResolvedPackages", + "/p:_ProjectDataCanWriteOutput=true", + ]); + + if (expectedSuccess) + { + Assert.True(staleResult.ExitCode == 0, staleResult.Output); + Assert.DoesNotContain("central transitive package pinning mode differs from the restore graph", staleResult.Output); + } + else + { + Assert.NotEqual(0, staleResult.ExitCode); + Assert.Contains("central transitive package pinning mode differs from the restore graph", staleResult.Output); + Assert.Contains($"current '{currentPinningEnabled}'", staleResult.Output, StringComparison.OrdinalIgnoreCase); + Assert.Contains($"restored '{restoredPinningEnabled}'", staleResult.Output, StringComparison.OrdinalIgnoreCase); + } + } + + [Fact] + public async Task ValidateProjectDataResolvedPackages_StillValidatesImplicitPackageDependencyPaths() + { + string missingPackageAsset = Path.Combine(this.workDir, "missing-packages", "implicit.dependency", "1.0.0", "lib", "net8.0", "Implicit.Dependency.dll"); + string projectFile = this.WriteProject( + "ImplicitResolvedPackage.csproj", + multiTargeting: false, + extraXml: + $$""" + + + + + + + <_PackageDependenciesDesignTime Include="Implicit.Dependency/1.0.0"> + {{missingPackageAsset}} + + + + """, + writeAssetsFile: false); + + ProcessResult result = await RunDotnetMsbuildAsync( + projectFile, + extraArgs: + [ + "/t:_ValidateProjectDataResolvedPackages", + "/p:_ProjectDataCanWriteOutput=true", + ]); + + Assert.NotEqual(0, result.ExitCode); + Assert.Contains("package files are missing", result.Output); + Assert.Contains("Implicit.Dependency/1.0.0", result.Output); + } + + [Fact] + public async Task RunProcessWithTimeoutAsync_TerminatesHungProcessInsteadOfHanging() + { + // Regression test for the macOS CI job that hung for 180 minutes: a spawned `dotnet msbuild` + // child never exited, and the old harness awaited it unbounded. The harness must now bound the + // wait, kill the whole process tree, and surface a TimeoutException with the captured output. + ProcessStartInfo psi = CreateLongRunningProcess(); + var timeout = TimeSpan.FromSeconds(1); + + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + TimeoutException ex = await Assert.ThrowsAsync( + () => RunProcessWithTimeoutAsync(psi, timeout, "hung-test-process")); + stopwatch.Stop(); + + // It should fail promptly (well under the process' own ~5 minute lifetime), not hang. + Assert.True( + stopwatch.Elapsed < TimeSpan.FromMinutes(1), + $"Expected the hung process to be terminated promptly but it took {stopwatch.Elapsed}."); + Assert.Contains("did not complete within", ex.Message); + } + + private static ProcessStartInfo CreateLongRunningProcess() + { + // A portable process that blocks for far longer than the test's timeout on every CI OS. + ProcessStartInfo psi = OperatingSystem.IsWindows() + ? new ProcessStartInfo("cmd.exe", "/c \"ping 127.0.0.1 -n 300 > NUL\"") + : new ProcessStartInfo("/bin/sh", "-c \"sleep 300\""); + psi.RedirectStandardOutput = true; + psi.RedirectStandardError = true; + psi.UseShellExecute = false; + psi.CreateNoWindow = true; + return psi; + } + + private string WriteProject( + string fileName, + bool multiTargeting, + string? targetFramework = null, + string? targetFrameworks = null, + string? runtimeIdentifier = null, + string? extraProperties = null, + string? extraXml = null, + bool writeAssetsFile = true) + { + string projectFile = Path.Combine(this.workDir, fileName); + string resolvedTargetFrameworks = targetFrameworks ?? "net8.0;net9.0"; + string[] projectTargetFrameworks = multiTargeting ? resolvedTargetFrameworks.Split(';') : [targetFramework ?? "net8.0"]; + string tfm = multiTargeting + ? $"{resolvedTargetFrameworks}" + : $"{targetFramework ?? "net8.0"}"; + string runtimeIdentifierProperty = runtimeIdentifier is null ? string.Empty : $"{runtimeIdentifier}"; + + // Inject a stub framework-reference-shaped `@(ReferencePathWithRefAssemblies)` + // item so the production `_ValidateProjectDataMetadataReferences` target + // passes during synthetic unit tests. + // + // These smoke tests use a hand-crafted `project.assets.json` with empty + // `packageFolders` and never run a real Restore, so RAR has nothing to + // resolve from. That's an artefact of the hermetic test fixture, not a + // real broken build — none of these tests assert on `[metadataReferences]` + // content; they care about target dispatch, merge behaviour, slice + // management, etc. The stub is the cheapest way to feed the validator + // without standing up a network-dependent restore. + // + // The stub does NOT defeat the validator's purpose in production: the + // injection lives in the synthetic .csproj produced here, not in the + // targets file. Real projects continue to be validated normally. + // + // Two important escape hatches: + // + // * `Condition="'$(DisableImplicitFrameworkReferences)' != 'true'"` + // lets the dedicated regression tests + // (`ProjectDataBuild_NoMetadataReferencesResolved_*`) force-empty + // `@(ReferencePathWithRefAssemblies)` by setting that property — the + // stub naturally opts out and the validator fires as those tests + // require. + // + // * `BeforeTargets="_ValidateProjectDataMetadataReferences"` is enough: + // the stub item is in scope for the writer task too and gets written into `[metadataReferences]`, + // but for `.NETFramework` slices the writer's own + // `TryValidateNetFrameworkReferences` requires a *canonical* ref + // assembly (mscorlib/NETFXREF-shaped), which the stub is not, so the + // graceful `MissingNetFrameworkReferenceAssemblies` skip path still + // fires for net472 inner builds where the targeting pack is absent. + string stubReferenceAssembly = Path.Combine(this.workDir, "stub-reference.dll"); + if (!File.Exists(stubReferenceAssembly)) + { + File.WriteAllBytes(stubReferenceAssembly, []); + } + + File.WriteAllText(projectFile, +$@" + + {tfm} + {runtimeIdentifierProperty} + Exe + false + {extraProperties} + + {extraXml} + + + + Microsoft.NETCore.App + + + +"); + + if (writeAssetsFile) + { + this.WriteProjectAssetsFile(projectFile, projectTargetFrameworks, runtimeIdentifier); + } + + return projectFile; + } + + private string WriteGraphProject(string name, string[]? references = null, bool failDuringProjectDataEvaluation = false) + { + string directory = Path.Combine(this.workDir, name); + Directory.CreateDirectory(directory); + string projectPath = Path.Combine(directory, $"{name}.csproj"); + string projectReferences = string.Join( + Environment.NewLine, + (references ?? []).Select(reference => $""" """)); + string failureProperty = failDuringProjectDataEvaluation + ? """ $([System.String]::MissingProjectDataMethod())""" + : string.Empty; + File.WriteAllText( + projectPath, + $$""" + + + net11.0 + false + {{failureProperty}} + + + {{projectReferences}} + + + """); + this.WriteProjectAssetsFile(projectPath, ["net11.0"]); + return projectPath; + } + + private void WriteProjectAssetsFile(string projectFile, IReadOnlyList targetFrameworks, string? runtimeIdentifier = null) + { + string objDir = Path.Combine(Path.GetDirectoryName(projectFile)!, "obj"); + Directory.CreateDirectory(objDir); + string assetsFile = Path.Combine(objDir, "project.assets.json"); + string projectName = Path.GetFileNameWithoutExtension(projectFile); + string escapedProjectFile = projectFile.Replace("\\", "\\\\"); + string escapedObjDir = objDir.Replace("\\", "\\\\") + "\\\\"; + IEnumerable targetGraphs = runtimeIdentifier is null + ? targetFrameworks + : targetFrameworks.Concat(targetFrameworks.Select(tfm => $"{tfm}/{runtimeIdentifier}")); + string targets = string.Join(",\n ", targetGraphs.Select(tfm => $"\"{tfm}\": {{}}")); + string dependencyGroups = string.Join(",\n ", targetFrameworks.Select(tfm => $"\"{tfm}\": []")); + string restoreFrameworks = string.Join(",\n ", targetFrameworks.Select(tfm => $"\"{tfm}\": {{ \"targetAlias\": \"{tfm}\", \"projectReferences\": {{}} }}")); + string projectFrameworks = string.Join(",\n ", targetFrameworks.Select(tfm => $"\"{tfm}\": {{ \"targetAlias\": \"{tfm}\" }}")); + string projectRuntimes = runtimeIdentifier is null + ? string.Empty + : $$""" + "runtimes": { + "{{runtimeIdentifier}}": { + "#import": [] + } + }, + """; + + File.WriteAllText( + assetsFile, + $$""" + { + "version": 3, + "targets": { + {{targets}} + }, + "libraries": {}, + "projectFileDependencyGroups": { + {{dependencyGroups}} + }, + "packageFolders": {}, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "{{escapedProjectFile}}", + "projectName": "{{projectName}}", + "projectPath": "{{escapedProjectFile}}", + "packagesPath": "", + "outputPath": "{{escapedObjDir}}", + "projectStyle": "PackageReference", + "configFilePaths": [], + "originalTargetFrameworks": [{{string.Join(", ", targetFrameworks.Select(tfm => $"\"{tfm}\""))}}], + "sources": {}, + "frameworks": { + {{restoreFrameworks}} + }, + {{projectRuntimes}} + "warningProperties": { + "warnAsError": [] + } + }, + "frameworks": { + {{projectFrameworks}} + } + } + } + """); + + this.WriteNuGetGeneratedImports(projectFile); + } + + private void WriteProjectAssetsFileWithPackage( + string projectFile, + string packageId, + string packageVersion, + string targetFramework, + string packagesPath, + string? requestedVersion = null) + { + string objDir = Path.Combine(Path.GetDirectoryName(projectFile)!, "obj"); + Directory.CreateDirectory(objDir); + string assetsFile = Path.Combine(objDir, "project.assets.json"); + string projectName = Path.GetFileNameWithoutExtension(projectFile); + string escapedProjectFile = projectFile.Replace("\\", "\\\\"); + string escapedObjDir = objDir.Replace("\\", "\\\\") + "\\\\"; + string escapedPackagesPath = (packagesPath + Path.DirectorySeparatorChar).Replace("\\", "\\\\"); + string packageAsset = $"lib/{targetFramework}/{packageId}.dll"; + string packageLibrary = $"{packageId}/{packageVersion}"; + string packagePath = $"{packageId.ToLowerInvariant()}/{packageVersion}"; + string dependencyVersion = requestedVersion ?? $"[{packageVersion}, )"; + + File.WriteAllText( + assetsFile, + $$""" + { + "version": 3, + "targets": { + "{{targetFramework}}": { + "{{packageLibrary}}": { + "type": "package", + "compile": { + "{{packageAsset}}": {} + }, + "runtime": { + "{{packageAsset}}": {} + } + } + } + }, + "libraries": { + "{{packageLibrary}}": { + "type": "package", + "path": "{{packagePath}}", + "files": [ + "{{packageAsset}}", + "{{packageId.ToLowerInvariant()}}.nuspec" + ] + } + }, + "projectFileDependencyGroups": { + "{{targetFramework}}": [ + "{{packageId}} >= {{packageVersion}}" + ] + }, + "packageFolders": { + "{{escapedPackagesPath}}": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "{{escapedProjectFile}}", + "projectName": "{{projectName}}", + "projectPath": "{{escapedProjectFile}}", + "packagesPath": "{{escapedPackagesPath}}", + "outputPath": "{{escapedObjDir}}", + "projectStyle": "PackageReference", + "configFilePaths": [], + "originalTargetFrameworks": ["{{targetFramework}}"], + "sources": {}, + "frameworks": { + "{{targetFramework}}": { + "targetAlias": "{{targetFramework}}", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [] + } + }, + "frameworks": { + "{{targetFramework}}": { + "targetAlias": "{{targetFramework}}", + "dependencies": { + "{{packageId}}": { + "target": "Package", + "version": "{{dependencyVersion}}" + } + } + } + } + } + } + """); + + this.WriteNuGetGeneratedImports(projectFile); + } + + private void WriteNuGetGeneratedImports(string projectFile) + { + string objDir = Path.Combine(Path.GetDirectoryName(projectFile)!, "obj"); + Directory.CreateDirectory(objDir); + string projectName = Path.GetFileName(projectFile); + File.WriteAllText( + Path.Combine(objDir, projectName + ".nuget.g.props"), + """ + + + True + $(MSBuildThisFileDirectory)project.assets.json + + + """); + File.WriteAllText( + Path.Combine(objDir, projectName + ".nuget.g.targets"), + """ + + + """); + } + + private string WriteNet472ReferenceAssemblies() + { + string referenceAssemblyDirectory = Path.Combine(this.workDir, "refs", ".NETFramework", "v4.7.2"); + Directory.CreateDirectory(referenceAssemblyDirectory); + Directory.CreateDirectory(Path.Combine(referenceAssemblyDirectory, "RedistList")); + + File.WriteAllText(Path.Combine(referenceAssemblyDirectory, "mscorlib.dll"), string.Empty); + File.WriteAllText(Path.Combine(referenceAssemblyDirectory, "System.dll"), string.Empty); + File.WriteAllText(Path.Combine(referenceAssemblyDirectory, "System.Core.dll"), string.Empty); + File.WriteAllText( + Path.Combine(referenceAssemblyDirectory, "RedistList", "FrameworkList.xml"), + """ + + + + + + """); + + return referenceAssemblyDirectory; + } + + private string MissingNetFrameworkReferenceAssembliesProperties() + => $"{Path.Combine(this.workDir, "missing-reference-assemblies")}"; + + private static string FindRepoRoot() + { + DirectoryInfo? directory = new(AppContext.BaseDirectory); + while (directory is not null) + { + if (File.Exists(Path.Combine(directory.FullName, "Roslyn.slnx"))) + { + return directory.FullName; + } + + directory = directory.Parent; + } + + throw new DirectoryNotFoundException($"Could not find the repository root from {AppContext.BaseDirectory}."); + } + + private string WriteLegacyProject(string fileName) + { + string projectFile = Path.Combine(this.workDir, fileName); + File.WriteAllText(projectFile, +""" + + + Exe + v4.7.2 + + + +"""); + + return projectFile; + } + + private static string[] ExtractCommandLineArguments(string cacheContent) + { + string normalized = cacheContent.Replace("\r\n", "\n"); + const string header = "\n[commandLineArguments]\n"; + int start = normalized.IndexOf(header, StringComparison.Ordinal); + Assert.True(start >= 0, $"Could not find [commandLineArguments] section.\n{cacheContent}"); + start += header.Length; + int end = normalized.IndexOf("\n[", start, StringComparison.Ordinal); + if (end < 0) + end = normalized.IndexOf("\n---\n", start, StringComparison.Ordinal); + if (end < 0) + end = normalized.Length; + + return normalized.Substring(start, end - start) + .Split('\n', StringSplitOptions.RemoveEmptyEntries); + } + + private static string[] NormalizeForStableArgumentParity(IEnumerable args) + { + return args + .Where(arg => !IsFileArgument(arg)) + .Where(arg => !IsPathBearingArgument(arg)) + .Select(NormalizeNetCoreAppNoWarn) + .Select(arg => arg.Replace('\\', '/')) + .ToArray(); + } + + private static string NormalizeNetCoreAppNoWarn(string arg) + { + const string noWarn = "/nowarn:"; + if (!arg.StartsWith(noWarn, StringComparison.OrdinalIgnoreCase)) + return arg; + if (arg.Split(',', ';').Any(part => string.Equals(part.Trim(), "8002", StringComparison.OrdinalIgnoreCase))) + return arg; + return arg + ",8002"; + } + + private static bool IsFileArgument(string arg) + { + if (string.IsNullOrWhiteSpace(arg)) return false; + if (!arg.StartsWith("/", StringComparison.Ordinal) && !arg.StartsWith("-", StringComparison.Ordinal)) + return true; + return arg.StartsWith("/reference:", StringComparison.OrdinalIgnoreCase) + || arg.StartsWith("/r:", StringComparison.OrdinalIgnoreCase) + || arg.StartsWith("/analyzer:", StringComparison.OrdinalIgnoreCase) + || arg.StartsWith("/additionalfile:", StringComparison.OrdinalIgnoreCase) + || arg.StartsWith("/analyzerconfig:", StringComparison.OrdinalIgnoreCase) + || arg.StartsWith("/resource:", StringComparison.OrdinalIgnoreCase) + || arg.StartsWith("/linkresource:", StringComparison.OrdinalIgnoreCase) + || arg.StartsWith("/embed:", StringComparison.OrdinalIgnoreCase); + } + + private static bool IsPathBearingArgument(string arg) + { + return arg.Contains(":\\", StringComparison.Ordinal) + || arg.Contains(":/", StringComparison.Ordinal) + || arg.Contains("", StringComparison.Ordinal) + || arg.Contains("", StringComparison.Ordinal) + || arg.Contains("", StringComparison.Ordinal) + || arg.Contains("", StringComparison.Ordinal); + } + + private static int CountOccurrences(string content, string value) + { + int count = 0; + int index = 0; + while ((index = content.IndexOf(value, index, StringComparison.Ordinal)) >= 0) + { + count++; + index += value.Length; + } + + return count; + } + + private static string GetSliceBlock(string content, string targetFramework) + { + foreach (string block in content.Split("\n---\n", StringSplitOptions.None).Skip(1)) + { + if (block.Contains($"TargetFramework={targetFramework}\n", StringComparison.Ordinal)) + return block; + } + + throw new InvalidOperationException($"Could not find slice block for {targetFramework}.\n{content}"); + } + + private static async Task RunDotnetMsbuildAsync( + string projectFile, + string[] extraArgs, + System.Collections.Generic.Dictionary? extraEnv = null, + bool wireProjectDataTargets = true, + bool useBuildCommand = false) + { + var psi = new ProcessStartInfo("dotnet") + { + WorkingDirectory = Path.GetDirectoryName(projectFile)!, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + string command = useBuildCommand ? "build" : "msbuild"; + foreach (string argument in new[] { command, projectFile, "/nologo", "/v:minimal" }.Concat(extraArgs)) + { + psi.ArgumentList.Add(argument); + } + + if (wireProjectDataTargets) + { + // Wire the targets file in the same way the extension does at runtime. + psi.Environment["CustomAfterMicrosoftCommonTargets"] = TargetsFile; + psi.Environment["CustomAfterMicrosoftCommonCrossTargetingTargets"] = TargetsFile; + } + psi.Environment["DOTNET_PROJECTDATA_CACHE_DIR"] = GetTestCacheRoot(projectFile); + // Avoid surprising restore behavior in the smoke test. + psi.Environment["MSBUILDDISABLENODEREUSE"] = "1"; + psi.Environment["DOTNET_CLI_TELEMETRY_OPTOUT"] = "1"; + // Don't let the persistent MSBuild build server linger and keep the redirected + // stdout/stderr pipe open after `dotnet msbuild` returns; that would deadlock the + // stream reads below (observed as an intermittent hang on macOS CI). + psi.Environment["DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER"] = "1"; + + if (extraEnv is not null) + { + foreach (KeyValuePair kvp in extraEnv) + { + psi.Environment[kvp.Key] = kvp.Value; + } + } + + return await RunProcessWithTimeoutAsync(psi, ProcessTimeout, $"dotnet {string.Join(' ', psi.ArgumentList)}"); + } + + /// + /// Starts , reads stdout/stderr concurrently, and waits for the process to exit, + /// but never for longer than . On timeout the entire process tree is killed + /// (so lingering MSBuild worker nodes release the redirected pipe handles) and a + /// is thrown with whatever output was captured, instead of hanging the test host indefinitely. + /// + private static async Task RunProcessWithTimeoutAsync( + ProcessStartInfo psi, + TimeSpan timeout, + string commandDescription) + { + using Process proc = Process.Start(psi)!; + + // Read both streams concurrently so a full stderr buffer can never block stdout (and vice versa). + Task stdoutTask = proc.StandardOutput.ReadToEndAsync(); + Task stderrTask = proc.StandardError.ReadToEndAsync(); + Task completion = Task.WhenAll(stdoutTask, stderrTask, proc.WaitForExitAsync()); + + Task finished = await Task.WhenAny(completion, Task.Delay(timeout)); + if (finished != completion) + { + // Timed out. Kill the whole tree (dotnet + any lingering MSBuild worker nodes) so the redirected + // pipes close, the reads can drain, and we don't leak processes onto the agent. + try { proc.Kill(entireProcessTree: true); } + catch { /* the process may have exited between the timeout check and the kill */ } + + // After the kill the pipe write handles close, so give the reads a bounded chance to drain. + try { await completion.WaitAsync(TimeSpan.FromSeconds(30)); } + catch { /* best-effort drain */ } + + string partialStdout = stdoutTask.IsCompletedSuccessfully ? stdoutTask.Result : string.Empty; + string partialStderr = stderrTask.IsCompletedSuccessfully ? stderrTask.Result : string.Empty; + + throw new TimeoutException( + $"'{commandDescription}' did not complete within {timeout.TotalMinutes:N1} minute(s) and was terminated. " + + "This usually indicates a hung MSBuild/restore child process." + Environment.NewLine + + "--- stdout ---" + Environment.NewLine + partialStdout + Environment.NewLine + + "--- stderr ---" + Environment.NewLine + partialStderr); + } + + await completion; + return new ProcessResult(proc.ExitCode, stdoutTask.Result + Environment.NewLine + stderrTask.Result); + } + + private static TimeSpan GetProcessTimeout() + { + string? raw = Environment.GetEnvironmentVariable("PROJECTDATA_SMOKE_TEST_PROCESS_TIMEOUT_SECONDS"); + if (!string.IsNullOrWhiteSpace(raw) + && int.TryParse(raw, out int seconds) + && seconds > 0) + { + return TimeSpan.FromSeconds(seconds); + } + + return TimeSpan.FromMinutes(5); + } + + private static string GetCompletionLoggerArgument(string receiptDirectory, string attemptId, string? loggerAssemblyPath = null) + { + string encodedReceiptDirectory = Convert.ToBase64String(Encoding.UTF8.GetBytes(receiptDirectory)); + loggerAssemblyPath ??= typeof(ProjectDataBuildCompletionLogger).Assembly.Location; + return $"/logger:{typeof(ProjectDataBuildCompletionLogger).FullName},\"{loggerAssemblyPath}\";{encodedReceiptDirectory};{attemptId}"; + } + + private static string CopyAssembly(string sourcePath, string destinationDirectory) + { + string destinationPath = Path.Combine(destinationDirectory, Path.GetFileName(sourcePath)); + File.Copy(sourcePath, destinationPath, overwrite: true); + return destinationPath; + } + + private sealed record ProcessResult(int ExitCode, string Output); + + private static void AssertMissingAssetsError(string projectFile, string output) + { + Assert.Contains("ProjectData: cannot write project data", output); + Assert.Contains(projectFile, output); + Assert.Contains("project.assets.json", output); + } + + private static void AssertUnsupportedMarker(string projectFile, string expectedReason) + { + string markerPath = GetMarkerPath(projectFile); + Assert.True(File.Exists(markerPath), $"Expected unsupported marker at {markerPath}."); + string content = File.ReadAllText(markerPath); + Assert.Contains($"reason={expectedReason}", content); + } + + private static void AssertNoUnsupportedMarker(string projectFile) + { + string markerPath = GetMarkerPath(projectFile); + Assert.False(File.Exists(markerPath), $"Expected no unsupported marker at {markerPath}."); + } + + private static string GetMarkerPath(string projectFile) + { + string? previous = Environment.GetEnvironmentVariable("DOTNET_PROJECTDATA_CACHE_DIR"); + try + { + Environment.SetEnvironmentVariable("DOTNET_PROJECTDATA_CACHE_DIR", GetTestCacheRoot(projectFile)); + return UnsupportedProjectDataMarker.GetMarkerFilePath(projectFile); + } + finally + { + Environment.SetEnvironmentVariable("DOTNET_PROJECTDATA_CACHE_DIR", previous); + } + } + + private static string GetTestCacheRoot(string projectFile) + => Path.Combine(Path.GetDirectoryName(projectFile)!, ".projectdata-cache"); +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/TaskExecutionTests.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/TaskExecutionTests.cs new file mode 100644 index 0000000000000..aeeb66f0bbbcb --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/TaskExecutionTests.cs @@ -0,0 +1,2040 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using System.Text; +using System.Text.Json; +using Microsoft.Build.Execution; +using Microsoft.Build.Framework; +using Moq; +using Xunit; + +namespace Microsoft.NET.ProjectData.Tasks.Tests; + +/// +/// Tests for the MSBuild task entry points (``MergeProjectDataSlicesTask``, +/// ``WriteProjectDataSliceTask``). Validates how the tasks surface +/// misconfiguration and runtime failures back to MSBuild — specifically that +/// errors are not silently swallowed at ``MessageImportance.Low``, which the +/// default ``dotnet build`` verbosity hides. +/// +public class TaskExecutionTests +{ + [Fact] + public void MergeTask_LogsError_AndReturnsFalse_WhenNeitherInputProvided() + { + var engine = new BuildEngineStub(); + var task = new MergeProjectDataSlicesTask + { + BuildEngine = engine, + // Both OutputPath and ProjectFilePath left empty — neither route to a target path. + }; + + bool result = task.Execute(); + + Assert.False(result); + Assert.Single(engine.Errors); + Assert.Contains("requires either OutputPath or ProjectFilePath", engine.Errors[0].Message); + } + + [Fact] + public void MergeTask_LogsWarning_NotMessage_OnRuntimeIOFailure() + { + // Force a runtime failure inside ``Execute`` by pointing ``OutputPath`` at a + // location that cannot be written (a path under a non-existent drive root on + // Windows, or under an invalid character path on Unix). The catch-all should + // surface the failure as a Warning so it appears under ``-v:minimal``. + var engine = new BuildEngineStub(); + string unwritablePath = OperatingSystem.IsWindows() + ? @"Z:\nonexistent-drive\out.lscache" + : "/nonexistent-root-XYZZY/out.lscache"; + + var task = new MergeProjectDataSlicesTask + { + BuildEngine = engine, + OutputPath = unwritablePath, + SliceGlob = Path.Combine(Path.GetTempPath(), "lscache-tests-no-such-glob", "**", "*.slice"), + }; + + // No slices match the glob, so ``Merge`` returns 0 and ``DeleteOutputPathIfNotProjectFolder`` + // is invoked on the unwritable path. That throws, which exercises the catch-all. + // We don't assert ``Execute`` returns true/false here — the contract is just + // that any runtime failure produces a Warning, not a hidden Low-importance Message. + try + { + task.Execute(); + } + catch + { + // Any uncaught exception would mean the catch-all filter is too narrow — + // also a failure mode worth surfacing, but not under this test. + } + + // Either the warning was logged (caught path) or no work happened (lucky path). + // Assert NOTHING is logged at MessageImportance.Low pretending to be an error — + // that's the bug we're fixing. + foreach (BuildMessageEventArgs message in engine.Messages) + { + Assert.DoesNotContain("failed to merge", message.Message, StringComparison.OrdinalIgnoreCase); + } + } + + [Fact] + public void WriteTask_LogsWarning_NotMessage_OnRuntimeIOFailure() + { + // Same shape as the merge-task test. ``WriteProjectDataSliceTask`` calls + // ``ProjectDataWriter.AtomicWriteStreamed`` which throws on an unwritable + // output path. The catch-all must surface that as a Warning. + var engine = new BuildEngineStub(); + string unwritablePath = OperatingSystem.IsWindows() + ? @"Z:\nonexistent-drive\out.lscache" + : "/nonexistent-root-XYZZY/out.lscache"; + + string fakeProject = Path.Combine(Path.GetTempPath(), "lscache-tests", Guid.NewGuid().ToString("N"), "App.csproj"); + var task = new WriteProjectDataSliceTask + { + BuildEngine = engine, + ProjectFilePath = fakeProject, + OutputPath = unwritablePath, + CommandLineArguments = ["/noconfig"], + }; + + try + { + task.Execute(); + } + catch + { + } + + foreach (BuildMessageEventArgs message in engine.Messages) + { + Assert.DoesNotContain("failed to write", message.Message, StringComparison.OrdinalIgnoreCase); + } + } + + [Fact] + public void UnsupportedMarkerTask_TreatsCacheRootResolutionFailureAsRecoverable() + { + MethodInfo? method = typeof(WriteUnsupportedProjectDataMarkerTask).GetMethod( + "IsRecoverableMarkerWriteException", + BindingFlags.NonPublic | BindingFlags.Static); + + Assert.NotNull(method); + Assert.True((bool)method.Invoke(null, [new InvalidOperationException("Unable to determine cache root.")])!); + } + + [Fact] + public void UnsupportedProjectDataMarker_DeleteTreatsPathResolutionFailuresAsRecoverable() + { + MethodInfo? method = typeof(UnsupportedProjectDataMarker).GetMethod( + "IsRecoverableMarkerException", + BindingFlags.NonPublic | BindingFlags.Static); + + Assert.NotNull(method); + Assert.True((bool)method.Invoke(null, [new InvalidOperationException("Unable to determine cache root.")])!); + UnsupportedProjectDataMarker.Delete(string.Empty); + } + + [Fact] + public void ProjectDataBuildReceipt_RoundTripsOnlyMatchingAttemptAndProject() + { + string tempRoot = CreateTempRoot(); + try + { + string receiptDirectory = Path.Combine(tempRoot, "receipts"); + string projectPath = Path.Combine(tempRoot, "App", "App.csproj"); + string otherProjectPath = Path.Combine(tempRoot, "Other", "Other.csproj"); + string attemptId = "attempt-1"; + + ProjectDataBuildReceipt.Write(receiptDirectory, attemptId, projectPath); + ProjectDataBuildReceipt.WriteAggregateCompletion(receiptDirectory, attemptId); + + Assert.True(ProjectDataBuildReceipt.TryRead(receiptDirectory, attemptId, projectPath, out ProjectDataBuildReceiptData receipt)); + Assert.Equal(Path.GetFullPath(projectPath), receipt.ProjectFilePath); + Assert.True(ProjectDataBuildReceipt.TryReadAggregateCompletion(receiptDirectory, attemptId)); + Assert.False(ProjectDataBuildReceipt.TryRead(receiptDirectory, "attempt-2", projectPath, out _)); + Assert.False(ProjectDataBuildReceipt.TryRead(receiptDirectory, attemptId, otherProjectPath, out _)); + Assert.False(ProjectDataBuildReceipt.TryReadAggregateCompletion(receiptDirectory, "attempt-2")); + + string differentlyCasedProject = projectPath.ToUpperInvariant(); + Assert.Equal( + OperatingSystem.IsLinux() ? false : true, + string.Equals( + ProjectDataBuildReceipt.GetReceiptFilePath(receiptDirectory, projectPath), + ProjectDataBuildReceipt.GetReceiptFilePath(receiptDirectory, differentlyCasedProject), + StringComparison.Ordinal)); + + File.WriteAllText(receipt.ReceiptFilePath, "version=2\nattempt=attempt-1\nproject=wrong\nextra=value\n"); + Assert.False(ProjectDataBuildReceipt.TryRead(receiptDirectory, attemptId, projectPath, out _)); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void WriteProjectDataBuildReceiptTask_WriteFailureIsObservable() + { + string tempRoot = CreateTempRoot(); + try + { + string receiptDirectory = Path.Combine(tempRoot, "receipt-file"); + File.WriteAllText(receiptDirectory, "not a directory"); + BuildEngineStub engine = new(); + WriteProjectDataBuildReceiptTask task = new() + { + BuildEngine = engine, + ReceiptDirectory = receiptDirectory, + AttemptId = "attempt-1", + ProjectFilePath = Path.Combine(tempRoot, "App.csproj"), + }; + + Assert.False(task.Execute()); + BuildErrorEventArgs error = Assert.Single(engine.Errors); + Assert.Contains("failed to write completed receipt", error.Message, StringComparison.OrdinalIgnoreCase); + Assert.Contains("attempt-1", error.Message, StringComparison.Ordinal); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void ProjectDataBuildCompletionLogger_CapsPerProjectAndGlobalDiagnostics() + { + string tempRoot = CreateTempRoot(); + try + { + string receiptDirectory = Path.Combine(tempRoot, "logger-receipts"); + string attemptId = "attempt-1"; + Mock eventSource = new(MockBehavior.Loose); + ProjectDataBuildCompletionLogger logger = new() + { + Parameters = $"{Convert.ToBase64String(Encoding.UTF8.GetBytes(receiptDirectory))};{attemptId}", + }; + logger.Initialize(eventSource.Object); + + for (int projectIndex = 0; projectIndex < 41; projectIndex++) + { + string projectPath = Path.Combine(tempRoot, $"Project{projectIndex}.csproj"); + for (int diagnosticIndex = 0; diagnosticIndex < 6; diagnosticIndex++) + { + BuildWarningEventArgs warning = new( + subcategory: string.Empty, + code: $"W{diagnosticIndex}", + file: projectPath, + lineNumber: diagnosticIndex + 1, + columnNumber: 1, + endLineNumber: diagnosticIndex + 1, + endColumnNumber: 2, + message: "bounded warning", + helpKeyword: string.Empty, + senderName: "test") + { + ProjectFile = projectPath, + }; + eventSource.Raise(source => source.AnyEventRaised += null, warning); + } + } + + eventSource.Raise( + source => source.AnyEventRaised += null, + new BuildFinishedEventArgs("Build finished", string.Empty, succeeded: false)); + logger.Shutdown(); + + Assert.True(ProjectDataBuildAttemptManifest.TryRead(receiptDirectory, attemptId, out ProjectDataBuildAttemptManifest manifest)); + Assert.Equal(200, manifest.Diagnostics.Length); + Assert.Equal(46, manifest.TruncatedDiagnosticCount); + Assert.All( + manifest.Diagnostics.GroupBy(diagnostic => diagnostic.ProjectFilePath), + group => Assert.True(group.Count() <= 5, $"Expected at most 5 diagnostics for {group.Key}.")); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void ProjectDataBuildCompletionLogger_ErrorDisplacesWarningAtProjectCap() + { + string tempRoot = CreateTempRoot(); + try + { + string receiptDirectory = Path.Combine(tempRoot, "severity-aware-cap"); + string attemptId = "attempt-1"; + string projectPath = Path.Combine(tempRoot, "App.csproj"); + Mock eventSource = new(MockBehavior.Loose); + ProjectDataBuildCompletionLogger logger = new() + { + Parameters = $"{Convert.ToBase64String(Encoding.UTF8.GetBytes(receiptDirectory))};{attemptId}", + }; + logger.Initialize(eventSource.Object); + for (int index = 0; index < 5; index++) + { + eventSource.Raise( + source => source.AnyEventRaised += null, + new BuildWarningEventArgs(string.Empty, $"W{index}", projectPath, 0, 0, 0, 0, "warning", string.Empty, "test") + { + ProjectFile = projectPath, + }); + } + eventSource.Raise( + source => source.AnyEventRaised += null, + new BuildErrorEventArgs(string.Empty, "E1", projectPath, 0, 0, 0, 0, "actionable error", string.Empty, "test") + { + ProjectFile = projectPath, + }); + eventSource.Raise( + source => source.AnyEventRaised += null, + new BuildFinishedEventArgs("Build finished", string.Empty, succeeded: false)); + + Assert.True(ProjectDataBuildAttemptManifest.TryRead(receiptDirectory, attemptId, out ProjectDataBuildAttemptManifest manifest)); + Assert.Equal(5, manifest.Diagnostics.Length); + Assert.Equal(4, manifest.Diagnostics.Count(diagnostic => diagnostic.Severity == "Warning")); + Assert.Contains(manifest.Diagnostics, diagnostic => diagnostic.Severity == "Error" && diagnostic.Code == "E1"); + Assert.Equal(1, manifest.TruncatedDiagnosticCount); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void ProjectDataBuildCompletionLogger_ProjectCapDoesNotDisplaceAnotherProjectsWarning() + { + string tempRoot = CreateTempRoot(); + try + { + string receiptDirectory = Path.Combine(tempRoot, "project-cap-isolation"); + string attemptId = "attempt-1"; + string cappedProject = Path.Combine(tempRoot, "Capped.csproj"); + Mock eventSource = new(MockBehavior.Loose); + ProjectDataBuildCompletionLogger logger = new() + { + Parameters = $"{Convert.ToBase64String(Encoding.UTF8.GetBytes(receiptDirectory))};{attemptId}", + }; + logger.Initialize(eventSource.Object); + + for (int index = 0; index < 5; index++) + { + eventSource.Raise( + source => source.AnyEventRaised += null, + new BuildErrorEventArgs(string.Empty, $"E{index}", cappedProject, 0, 0, 0, 0, "error", string.Empty, "test") + { + ProjectFile = cappedProject, + }); + } + for (int projectIndex = 0; projectIndex < 39; projectIndex++) + { + string projectPath = Path.Combine(tempRoot, $"Warning{projectIndex}.csproj"); + for (int diagnosticIndex = 0; diagnosticIndex < 5; diagnosticIndex++) + { + eventSource.Raise( + source => source.AnyEventRaised += null, + new BuildWarningEventArgs(string.Empty, $"W{diagnosticIndex}", projectPath, 0, 0, 0, 0, "warning", string.Empty, "test") + { + ProjectFile = projectPath, + }); + } + } + eventSource.Raise( + source => source.AnyEventRaised += null, + new BuildErrorEventArgs(string.Empty, "E5", cappedProject, 0, 0, 0, 0, "extra error", string.Empty, "test") + { + ProjectFile = cappedProject, + }); + eventSource.Raise( + source => source.AnyEventRaised += null, + new BuildFinishedEventArgs("Build finished", string.Empty, succeeded: false)); + + Assert.True(ProjectDataBuildAttemptManifest.TryRead(receiptDirectory, attemptId, out ProjectDataBuildAttemptManifest manifest)); + Assert.Equal(200, manifest.Diagnostics.Length); + Assert.Equal(5, manifest.Diagnostics.Count(diagnostic => diagnostic.ProjectFilePath == cappedProject)); + Assert.Equal(1, manifest.TruncatedDiagnosticCount); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void ProjectDataBuildCompletionLogger_MalformedParametersAndWriteFailureNeverThrow() + { + string tempRoot = CreateTempRoot(); + try + { + Mock eventSource = new(MockBehavior.Loose); + ProjectDataBuildCompletionLogger malformed = new() { Parameters = "not-base64;attempt-1" }; + Exception? malformedException = Record.Exception(() => malformed.Initialize(eventSource.Object)); + Assert.Null(malformedException); + + string receiptFile = Path.Combine(tempRoot, "not-a-directory"); + File.WriteAllText(receiptFile, string.Empty); + ProjectDataBuildCompletionLogger unwritable = new() + { + Parameters = $"{Convert.ToBase64String(Encoding.UTF8.GetBytes(receiptFile))};attempt-2", + }; + unwritable.Initialize(eventSource.Object); + Exception? eventException = Record.Exception(() => eventSource.Raise( + source => source.AnyEventRaised += null, + new BuildFinishedEventArgs("Build finished", string.Empty, succeeded: false))); + Assert.Null(eventException); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void ProjectDataBuildCompletionLogger_DistinguishesCancellationFromProcessLoss() + { + string tempRoot = CreateTempRoot(); + try + { + string cancelledDirectory = Path.Combine(tempRoot, "cancelled"); + Mock cancelledSource = new(MockBehavior.Loose); + ProjectDataBuildCompletionLogger cancelledLogger = new() + { + Parameters = $"{Convert.ToBase64String(Encoding.UTF8.GetBytes(cancelledDirectory))};cancelled-attempt", + }; + cancelledLogger.Initialize(cancelledSource.Object); + cancelledSource.Raise( + source => source.AnyEventRaised += null, + new BuildCanceledEventArgs("Build cancelled")); + cancelledLogger.Shutdown(); + + Assert.True(ProjectDataBuildAttemptManifest.TryRead(cancelledDirectory, "cancelled-attempt", out ProjectDataBuildAttemptManifest cancelledManifest)); + Assert.True(cancelledManifest.BuildCancelled); + Assert.False(cancelledManifest.BuildFinished); + Assert.True(ProjectDataBuildReceipt.TryReadAggregateCompletion(cancelledDirectory, "cancelled-attempt")); + + string lostDirectory = Path.Combine(tempRoot, "lost"); + Mock lostSource = new(MockBehavior.Loose); + ProjectDataBuildCompletionLogger lostLogger = new() + { + Parameters = $"{Convert.ToBase64String(Encoding.UTF8.GetBytes(lostDirectory))};lost-attempt", + }; + lostLogger.Initialize(lostSource.Object); + lostLogger.Shutdown(); + + Assert.False(ProjectDataBuildAttemptManifest.TryRead(lostDirectory, "lost-attempt", out _)); + Assert.False(ProjectDataBuildReceipt.TryReadAggregateCompletion(lostDirectory, "lost-attempt")); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void ProjectDataBuildCompletionLogger_RecordsSubmissionPhases() + { + string tempRoot = CreateTempRoot(); + try + { + string receiptDirectory = Path.Combine(tempRoot, "phase-evidence"); + string attemptId = "attempt-1"; + Mock eventSource = new(MockBehavior.Loose); + ProjectDataBuildCompletionLogger logger = new() + { + Parameters = $"{Convert.ToBase64String(Encoding.UTF8.GetBytes(receiptDirectory))};{attemptId}", + }; + logger.Initialize(eventSource.Object); + eventSource.Raise( + source => source.AnyEventRaised += null, + new BuildSubmissionStartedEventArgs( + new Dictionary { ["MSBuildIsRestoring"] = "true" }, + [@"C:\repo\App.slnx"], + ["Restore"], + BuildRequestDataFlags.None, + submissionId: 1)); + eventSource.Raise( + source => source.AnyEventRaised += null, + new BuildSubmissionStartedEventArgs( + new Dictionary(), + [@"C:\repo\App.slnx"], + ["ProjectDataBuild"], + BuildRequestDataFlags.None, + submissionId: 2)); + eventSource.Raise( + source => source.AnyEventRaised += null, + new BuildFinishedEventArgs("Build finished", string.Empty, succeeded: true)); + + Assert.True(ProjectDataBuildAttemptManifest.TryRead(receiptDirectory, attemptId, out ProjectDataBuildAttemptManifest manifest)); + Assert.True(manifest.ProjectDataBuildSubmissionObserved); + Assert.Contains(manifest.Submissions, submission => submission.SubmissionId == 1 && submission.Phase == "Restore" && submission.MSBuildIsRestoring); + Assert.Contains(manifest.Submissions, submission => submission.SubmissionId == 2 && submission.Phase == "ProjectDataBuild" && !submission.MSBuildIsRestoring); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Theory] + [InlineData(".csproj")] + [InlineData(".vbproj")] + [InlineData(".fsproj")] + [InlineData(".vcxproj")] + [InlineData(".esproj")] + [InlineData(".proj")] + public void ProjectDataBuildCompletionLogger_UsesStructuredFileWhenNuGetReportsSolutionAsProject(string projectExtension) + { + string tempRoot = CreateTempRoot(); + try + { + string receiptDirectory = Path.Combine(tempRoot, "nuget-diagnostic"); + string attemptId = "attempt-1"; + string solutionPath = Path.Combine(tempRoot, "App.slnx"); + string brokenProjectPath = Path.Combine(tempRoot, "Broken", "Broken" + projectExtension); + Mock eventSource = new(MockBehavior.Loose); + ProjectDataBuildCompletionLogger logger = new() + { + Parameters = $"{Convert.ToBase64String(Encoding.UTF8.GetBytes(receiptDirectory))};{attemptId}", + }; + logger.Initialize(eventSource.Object); + BuildErrorEventArgs error = new( + subcategory: string.Empty, + code: "NU1101", + file: brokenProjectPath, + lineNumber: 0, + columnNumber: 0, + endLineNumber: 0, + endColumnNumber: 0, + message: "Package was not found.", + helpKeyword: string.Empty, + senderName: "NuGet") + { + ProjectFile = solutionPath, + }; + eventSource.Raise(source => source.AnyEventRaised += null, error); + eventSource.Raise( + source => source.AnyEventRaised += null, + new BuildFinishedEventArgs("Build finished", string.Empty, succeeded: false)); + + Assert.True(ProjectDataBuildAttemptManifest.TryRead(receiptDirectory, attemptId, out ProjectDataBuildAttemptManifest manifest)); + ProjectDataBuildDiagnosticRecord diagnostic = Assert.Single(manifest.Diagnostics); + Assert.Equal(brokenProjectPath, diagnostic.ProjectFilePath); + Assert.Equal(ProjectDataBuildDiagnosticRecord.FileProjectPathSource, diagnostic.ProjectFilePathSource); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void WriteTask_RecordsDonorIndex_AfterSuccessfulFinalCacheWrite() + { + string tempRoot = CreateTempRoot(); + try + { + string workspaceRoot = Path.Combine(tempRoot, "worktree"); + string projectFile = Path.Combine(workspaceRoot, "src", "App", "App.csproj"); + string outputPath = projectFile + ".lscache"; + string indexPath = Path.Combine(tempRoot, "index", "lscache-donor-index.json"); + var engine = new BuildEngineStub(); + var task = new WriteProjectDataSliceTask + { + BuildEngine = engine, + ProjectFilePath = projectFile, + OutputPath = outputPath, + DonorCacheIndexPath = indexPath, + DonorCacheWorkspaceRoot = workspaceRoot, + WriteHeader = true, + IsPrimary = true, + LastDtbSucceeded = true, + CommandLineArguments = ["/noconfig"], + }; + + bool result = task.Execute(); + + Assert.True(result); + Assert.True(task.Succeeded); + Assert.True(File.Exists(indexPath), $"Expected donor index at {indexPath}."); + string content = File.ReadAllText(indexPath); + Assert.Contains("\"version\": 2", content); + Assert.Contains(JsonString(Path.GetFullPath(workspaceRoot)), content); + using JsonDocument index = JsonDocument.Parse(content); + Assert.Equal(["version", "entries"], index.RootElement.EnumerateObject().Select(static property => property.Name)); + JsonElement entry = index.RootElement.GetProperty("entries")[0]; + Assert.Equal(["path", "newestMtimeMs", "updatedUtc"], entry.EnumerateObject().Select(static property => property.Name)); + Assert.True(entry.TryGetProperty("newestMtimeMs", out _)); + Assert.True(entry.TryGetProperty("updatedUtc", out _)); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Theory] + [InlineData(false, true)] + [InlineData(true, false)] + public void WriteTask_DoesNotRecordDonorIndex_ForNonFinalCacheWrite(bool writeHeader, bool isPrimary) + { + string tempRoot = CreateTempRoot(); + try + { + string workspaceRoot = Path.Combine(tempRoot, "worktree"); + string indexPath = Path.Combine(tempRoot, "index", "lscache-donor-index.json"); + WriteProjectDataSliceTask task = CreateFinalWriteTask(workspaceRoot, indexPath); + task.WriteHeader = writeHeader; + task.IsPrimary = isPrimary; + + Assert.True(task.Execute()); + Assert.True(task.Succeeded); + Assert.False(File.Exists(indexPath)); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void WriteTask_RecordsSparseEntries_ForDistinctWorkspaces() + { + string tempRoot = CreateTempRoot(); + try + { + string mainWorkspaceRoot = Path.Combine(tempRoot, "main-worktree"); + string capitalizedMainWorkspaceRoot = Path.Combine(tempRoot, "capitalized-main-worktree"); + string indexPath = Path.Combine(tempRoot, "index", "lscache-donor-index.json"); + + Assert.True(CreateFinalWriteTask(mainWorkspaceRoot, indexPath).Execute()); + Assert.True(CreateFinalWriteTask(capitalizedMainWorkspaceRoot, indexPath).Execute()); + + using JsonDocument index = JsonDocument.Parse(File.ReadAllText(indexPath)); + string[] paths = index.RootElement + .GetProperty("entries") + .EnumerateArray() + .Select(entry => entry.GetProperty("path").GetString()!) + .ToArray(); + Assert.Contains(Path.GetFullPath(mainWorkspaceRoot), paths); + Assert.Contains(Path.GetFullPath(capitalizedMainWorkspaceRoot), paths); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void DonorIndex_OutsideGitRepository_DoesNotRecordWrite() + { + string tempRoot = CreateTempRoot(); + try + { + string projectFile = Path.Combine(tempRoot, "worktree", "src", "App", "App.csproj"); + string cacheFile = Path.Combine(tempRoot, "output", "App.csproj.lscache"); + Directory.CreateDirectory(Path.GetDirectoryName(projectFile)!); + Directory.CreateDirectory(Path.GetDirectoryName(cacheFile)!); + File.WriteAllText(projectFile, ""); + File.WriteAllText(cacheFile, "version=2\n"); + + Assert.Null(ProjectDataDonorIndex.TryResolveDefaultIndexPath(projectFile)); + Assert.False(ProjectDataDonorIndex.TryRecordWrite(projectFile, cacheFile, options: null, out string? message)); + Assert.Null(message); + Assert.Empty(Directory.EnumerateFiles(tempRoot, "lscache-donor-index.json", SearchOption.AllDirectories)); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void DonorIndex_Disabled_DoesNotRecordWrite() + { + string tempRoot = CreateTempRoot(); + try + { + string workspaceRoot = Path.Combine(tempRoot, "worktree"); + string projectFile = Path.Combine(workspaceRoot, "src", "App", "App.csproj"); + string cacheFile = projectFile + ".lscache"; + string indexPath = Path.Combine(tempRoot, "index", "lscache-donor-index.json"); + Directory.CreateDirectory(Path.GetDirectoryName(cacheFile)!); + File.WriteAllText(cacheFile, "version=2\n"); + + Assert.False(ProjectDataDonorIndex.TryRecordWrite( + projectFile, + cacheFile, + new ProjectDataDonorWriteOptions + { + Enabled = false, + IndexPath = indexPath, + WorkspaceRoot = workspaceRoot, + }, + out string? message)); + Assert.Null(message); + Assert.False(File.Exists(indexPath)); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void WriteTask_InvalidDonorIndexOverride_DoesNotFailSuccessfulCacheWrite() + { + string tempRoot = CreateTempRoot(); + try + { + BuildEngineStub engine = new(); + WriteProjectDataSliceTask task = CreateFinalWriteTask( + Path.Combine(tempRoot, "worktree"), + "\0invalid-index-path", + engine); + + Assert.True(task.Execute()); + Assert.True(task.Succeeded); + Assert.True(File.Exists(task.OutputPath)); + Assert.Empty(engine.Warnings); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void DonorIndex_UnavailableGitMetadata_RecordsSparseEntry() + { + string tempRoot = CreateTempRoot(); + try + { + string workspaceRoot = Path.Combine(tempRoot, "worktree"); + string projectFile = Path.Combine(workspaceRoot, "src", "App", "App.csproj"); + string cacheFile = projectFile + ".lscache"; + Directory.CreateDirectory(Path.Combine(workspaceRoot, ".git")); + Directory.CreateDirectory(Path.GetDirectoryName(projectFile)!); + File.WriteAllText(projectFile, ""); + File.WriteAllText(cacheFile, "version=2\n"); + string indexPath = Assert.IsType(ProjectDataDonorIndex.TryResolveDefaultIndexPath(projectFile)); + + Assert.True(ProjectDataDonorIndex.TryRecordWrite(projectFile, cacheFile, options: null, out string? message)); + Assert.Null(message); + + using JsonDocument index = JsonDocument.Parse(File.ReadAllText(indexPath)); + JsonElement entry = Assert.Single(index.RootElement.GetProperty("entries").EnumerateArray()); + Assert.Equal(Path.GetFullPath(workspaceRoot), entry.GetProperty("path").GetString()); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void WriteTask_MissingIndexes_DoNotShareEntries() + { + string tempRoot = CreateTempRoot(); + try + { + string firstWorkspaceRoot = Path.Combine(tempRoot, "first-worktree"); + string secondWorkspaceRoot = Path.Combine(tempRoot, "second-worktree"); + string firstIndexPath = Path.Combine(tempRoot, "first-index", "lscache-donor-index.json"); + string secondIndexPath = Path.Combine(tempRoot, "second-index", "lscache-donor-index.json"); + + Assert.True(CreateFinalWriteTask(firstWorkspaceRoot, firstIndexPath).Execute()); + Assert.True(CreateFinalWriteTask(secondWorkspaceRoot, secondIndexPath).Execute()); + + string secondIndexContent = File.ReadAllText(secondIndexPath); + Assert.Contains(JsonString(Path.GetFullPath(secondWorkspaceRoot)), secondIndexContent); + Assert.DoesNotContain(JsonString(Path.GetFullPath(firstWorkspaceRoot)), secondIndexContent); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void DonorIndex_DoesNotOverwrite_UnsupportedFutureVersion() + { + string tempRoot = CreateTempRoot(); + try + { + string workspaceRoot = Path.Combine(tempRoot, "worktree"); + string projectFile = Path.Combine(workspaceRoot, "src", "App", "App.csproj"); + string cacheFile = projectFile + ".lscache"; + string indexPath = Path.Combine(tempRoot, "index", "lscache-donor-index.json"); + Directory.CreateDirectory(Path.GetDirectoryName(cacheFile)!); + Directory.CreateDirectory(Path.GetDirectoryName(indexPath)!); + File.WriteAllText(cacheFile, "version=2\n"); + const string FutureIndex = """{"version":3,"entries":{"futureData":"preserve"}}"""; + File.WriteAllText(indexPath, FutureIndex); + + bool result = ProjectDataDonorIndex.TryRecordWrite( + projectFile, + cacheFile, + new ProjectDataDonorWriteOptions { IndexPath = indexPath, WorkspaceRoot = workspaceRoot }, + out string? message); + + Assert.False(result); + Assert.Contains("unsupported donor index version 3", message); + Assert.Equal(FutureIndex, File.ReadAllText(indexPath)); + Assert.Empty(Directory.EnumerateFiles(Path.GetDirectoryName(indexPath)!, "lscache-donor-index.json.corrupt-*")); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Theory] + [InlineData("""{"version":2,"entries":{"futureData":"preserve"}}""")] + [InlineData("""{"version":2,"entries":[{"path":"\u0000"}]}""")] + [InlineData("""{"entries":[]}""")] + [InlineData("[]")] + public void DonorIndex_QuarantinesAndRecreates_CorruptIndex(string corruptIndex) + { + string tempRoot = CreateTempRoot(); + try + { + string workspaceRoot = Path.Combine(tempRoot, "worktree"); + string projectFile = Path.Combine(workspaceRoot, "src", "App", "App.csproj"); + string cacheFile = projectFile + ".lscache"; + string indexPath = Path.Combine(tempRoot, "index", "lscache-donor-index.json"); + Directory.CreateDirectory(Path.GetDirectoryName(cacheFile)!); + Directory.CreateDirectory(Path.GetDirectoryName(indexPath)!); + File.WriteAllText(cacheFile, "version=2\n"); + File.WriteAllText(indexPath, corruptIndex); + + bool result = ProjectDataDonorIndex.TryRecordWrite( + projectFile, + cacheFile, + new ProjectDataDonorWriteOptions { IndexPath = indexPath, WorkspaceRoot = workspaceRoot }, + out string? message); + + Assert.True(result); + Assert.Contains("Recovered corrupt donor index", message); + Assert.Contains("\"version\": 2", File.ReadAllText(indexPath)); + string quarantinePath = Assert.Single(Directory.EnumerateFiles(Path.GetDirectoryName(indexPath)!, "lscache-donor-index.json.corrupt-*")); + Assert.Equal(corruptIndex, File.ReadAllText(quarantinePath)); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void DonorIndex_CleansTemporaryFile_WhenReplacementFails() + { + string tempRoot = CreateTempRoot(); + try + { + string workspaceRoot = Path.Combine(tempRoot, "worktree"); + string projectFile = Path.Combine(workspaceRoot, "src", "App", "App.csproj"); + string cacheFile = projectFile + ".lscache"; + string indexPath = Path.Combine(tempRoot, "index"); + Directory.CreateDirectory(Path.GetDirectoryName(cacheFile)!); + Directory.CreateDirectory(indexPath); + File.WriteAllText(cacheFile, "version=2\n"); + + Assert.False(ProjectDataDonorIndex.TryRecordWrite( + projectFile, + cacheFile, + new ProjectDataDonorWriteOptions { IndexPath = indexPath, WorkspaceRoot = workspaceRoot }, + out string? message)); + Assert.False(string.IsNullOrEmpty(message)); + Assert.Empty(Directory.EnumerateFiles(tempRoot, "index.*.tmp", SearchOption.TopDirectoryOnly)); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void DonorIndex_ReplacesIndex_WhileDeleteSharedReaderIsOpen() + { + string tempRoot = CreateTempRoot(); + try + { + string workspaceRoot = Path.Combine(tempRoot, "worktree"); + string indexPath = Path.Combine(tempRoot, "index", "lscache-donor-index.json"); + Assert.True(CreateFinalWriteTask(workspaceRoot, indexPath).Execute()); + + using FileStream reader = new(indexPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); + + Assert.True(CreateFinalWriteTask(workspaceRoot, indexPath).Execute()); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public async Task WriteTask_WaitsForExclusiveIndexFileLock() + { + string tempRoot = CreateTempRoot(); + try + { + string workspaceRoot = Path.Combine(tempRoot, "worktree"); + string indexPath = Path.Combine(tempRoot, "index", "lscache-donor-index.json"); + Directory.CreateDirectory(Path.GetDirectoryName(indexPath)!); + using FileStream indexLock = new(indexPath + ".lock", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); + + Task write = Task.Run(() => CreateFinalWriteTask(workspaceRoot, indexPath).Execute(), TestContext.Current.CancellationToken); + await Task.Delay(200, TestContext.Current.CancellationToken); + Assert.False(write.IsCompleted); + + indexLock.Dispose(); + Assert.True(await write); + Assert.True(File.Exists(indexPath)); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void TaskAssembly_DoesNotContainReaderSelectionOrGitTypes() + { + Assembly taskAssembly = typeof(WriteProjectDataSliceTask).Assembly; + + Assert.Null(taskAssembly.GetType("Microsoft.NET.ProjectData.ProjectDataDonorCandidate")); + Assert.Null(taskAssembly.GetType("Microsoft.NET.ProjectData.ProjectDataDonorOptions")); + Assert.Null(taskAssembly.GetType("Microsoft.NET.ProjectData.ProjectDataDonorIndex+GitQueryContext")); + Assert.Null(typeof(ProjectDataDonorIndex).GetMethod("EnumerateDonorCandidates", BindingFlags.Public | BindingFlags.Static)); + } + + [Fact] + public void DonorIndex_PreservesNewestCacheMtime_ForWorkspace() + { + string tempRoot = CreateTempRoot(); + try + { + string workspaceRoot = Path.Combine(tempRoot, "worktree"); + string projectFile = Path.Combine(workspaceRoot, "src", "App", "App.csproj"); + string cacheFile = projectFile + ".lscache"; + string indexPath = Path.Combine(tempRoot, "index", "lscache-donor-index.json"); + Directory.CreateDirectory(Path.GetDirectoryName(cacheFile)!); + File.WriteAllText(cacheFile, "version=2\n"); + File.SetLastWriteTimeUtc(cacheFile, DateTime.UtcNow.AddMinutes(-1)); + ProjectDataDonorWriteOptions options = new() + { + IndexPath = indexPath, + WorkspaceRoot = workspaceRoot + Path.DirectorySeparatorChar, + }; + + Assert.True(ProjectDataDonorIndex.TryRecordWrite(projectFile, cacheFile, options, out _)); + using JsonDocument firstIndex = JsonDocument.Parse(File.ReadAllText(indexPath)); + long newestMtimeMs = firstIndex.RootElement + .GetProperty("entries")[0] + .GetProperty("newestMtimeMs") + .GetInt64(); + + File.SetLastWriteTimeUtc(cacheFile, DateTime.UtcNow.AddHours(-1)); + options.WorkspaceRoot = workspaceRoot; + Assert.True(ProjectDataDonorIndex.TryRecordWrite(projectFile, cacheFile, options, out _)); + + using JsonDocument secondIndex = JsonDocument.Parse(File.ReadAllText(indexPath)); + JsonElement entry = Assert.Single(secondIndex.RootElement.GetProperty("entries").EnumerateArray()); + Assert.Equal( + newestMtimeMs, + entry.GetProperty("newestMtimeMs").GetInt64()); + Assert.Equal(workspaceRoot, entry.GetProperty("path").GetString()); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void WriteTask_DoesNotRecordDonorIndex_ForInnerSliceWrites() + { + string tempRoot = CreateTempRoot(); + try + { + string workspaceRoot = Path.Combine(tempRoot, "worktree"); + string projectFile = Path.Combine(workspaceRoot, "src", "App", "App.csproj"); + string outputPath = Path.Combine(workspaceRoot, "obj", "App.csproj.slice"); + string indexPath = Path.Combine(tempRoot, "index", "lscache-donor-index.json"); + var engine = new BuildEngineStub(); + var task = new WriteProjectDataSliceTask + { + BuildEngine = engine, + ProjectFilePath = projectFile, + OutputPath = outputPath, + DonorCacheIndexPath = indexPath, + DonorCacheWorkspaceRoot = workspaceRoot, + WriteHeader = false, + IsPrimary = false, + LastDtbSucceeded = true, + CommandLineArguments = ["/noconfig"], + }; + + bool result = task.Execute(); + + Assert.True(result); + Assert.True(task.Succeeded); + Assert.False(File.Exists(indexPath), $"Inner slice writes must not update donor index {indexPath}."); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void WriteTask_DoesNotRecordDonorIndex_ForNonPrimaryHeaderWrites() + { + string tempRoot = CreateTempRoot(); + try + { + string workspaceRoot = Path.Combine(tempRoot, "worktree"); + string projectFile = Path.Combine(workspaceRoot, "src", "App", "App.csproj"); + string outputPath = Path.Combine(workspaceRoot, "obj", "App.csproj.slice"); + string indexPath = Path.Combine(tempRoot, "index", "lscache-donor-index.json"); + var engine = new BuildEngineStub(); + var task = new WriteProjectDataSliceTask + { + BuildEngine = engine, + ProjectFilePath = projectFile, + OutputPath = outputPath, + DonorCacheIndexPath = indexPath, + DonorCacheWorkspaceRoot = workspaceRoot, + WriteHeader = true, + IsPrimary = false, + LastDtbSucceeded = true, + CommandLineArguments = ["/noconfig"], + }; + + bool result = task.Execute(); + + Assert.True(result); + Assert.True(task.Succeeded); + Assert.False(File.Exists(indexPath), $"Non-primary writes must not update donor index {indexPath}."); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void MergeTask_RecordsDonorIndex_AfterSuccessfulMerge() + { + string tempRoot = CreateTempRoot(); + try + { + string workspaceRoot = Path.Combine(tempRoot, "worktree"); + string projectFile = Path.Combine(workspaceRoot, "src", "App", "App.csproj"); + string outputPath = projectFile + ".lscache"; + string slicePath = Path.Combine(workspaceRoot, "obj", "Debug", "App.csproj.slice"); + string indexPath = Path.Combine(tempRoot, "index", "lscache-donor-index.json"); + WriteSlice(slicePath, "MergedAssembly", "net10.0"); + var engine = new BuildEngineStub(); + var task = new MergeProjectDataSlicesTask + { + BuildEngine = engine, + ProjectFilePath = projectFile, + OutputPath = outputPath, + SliceFiles = [new Microsoft.Build.Utilities.TaskItem(slicePath)], + DonorCacheIndexPath = indexPath, + DonorCacheWorkspaceRoot = workspaceRoot, + TargetFrameworks = "net10.0", + }; + + bool result = task.Execute(); + + Assert.True(result); + Assert.True(task.Succeeded); + Assert.True(File.Exists(outputPath), $"Expected merged cache at {outputPath}."); + Assert.True(File.Exists(indexPath), $"Expected donor index at {indexPath}."); + using JsonDocument index = JsonDocument.Parse(File.ReadAllText(indexPath)); + JsonElement entry = Assert.Single(index.RootElement.GetProperty("entries").EnumerateArray()); + Assert.Equal(Path.GetFullPath(workspaceRoot), entry.GetProperty("path").GetString()); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void MergeTask_DoesNotRecordDonorIndex_WhenNoSlicesAreMerged() + { + string tempRoot = CreateTempRoot(); + try + { + string workspaceRoot = Path.Combine(tempRoot, "worktree"); + string projectFile = Path.Combine(workspaceRoot, "src", "App", "App.csproj"); + string outputPath = projectFile + ".lscache"; + string missingSlicePath = Path.Combine(workspaceRoot, "obj", "Debug", "App.csproj.slice"); + string indexPath = Path.Combine(tempRoot, "index", "lscache-donor-index.json"); + var engine = new BuildEngineStub(); + var task = new MergeProjectDataSlicesTask + { + BuildEngine = engine, + ProjectFilePath = projectFile, + OutputPath = outputPath, + SliceFiles = [new Microsoft.Build.Utilities.TaskItem(missingSlicePath)], + DonorCacheIndexPath = indexPath, + DonorCacheWorkspaceRoot = workspaceRoot, + TargetFrameworks = "net10.0", + }; + + bool result = task.Execute(); + + Assert.True(result); + Assert.False(task.Succeeded); + Assert.False(File.Exists(indexPath), $"No-slice merge must not update donor index {indexPath}."); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Theory] + // A bare NuGet version is a minimum, so the resolved version may legitimately be higher. + // Stale restore validation separately compares the current request with project.assets.json. + [InlineData("12.0.3", "13.0.1", true)] + [InlineData("13.0.4", "13.0.1", false)] + [InlineData("11.0.0-preview.6.*", "11.0.0-preview.6.26359.118", true)] + [InlineData("11.0.0-preview.6.*", "11.0.0-preview.7.1", false)] + [InlineData("11.0.0-preview.6.*", "11.1.0", false)] + [InlineData("11.0.0-preview.6.*", "12.0.0", false)] + [InlineData("[1.0.0,2.0.0)", "1.5.0", true)] + [InlineData("[1.0.0,2.0.0)", "2.0.0", false)] + public void ValidatePackagesTask_UsesNuGetVersionRangeSemantics(string requestedVersion, string resolvedVersion, bool expectedResult) + { + string packagePath = CreatePackageDirectory(); + try + { + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "App.csproj", + PackageReferences = [CreateItem("Test.Package", ("Version", requestedVersion))], + ResolvedPackages = [CreateItem($"test.package/{resolvedVersion}", ("Name", "test.package"), ("Version", resolvedVersion), ("Path", packagePath))], + }; + + Assert.Equal(expectedResult, task.Execute()); + Assert.Equal(expectedResult ? 0 : 1, engine.Errors.Count); + } + finally + { + Directory.Delete(packagePath, recursive: true); + } + } + + [Fact] + public void ValidatePackagesTask_UsesVersionOverrideBeforeDirectAndCentralVersions() + { + string packagePath = CreatePackageDirectory(); + try + { + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "App.csproj", + ManagePackageVersionsCentrally = true, + PackageReferences = [CreateItem("Test.Package", ("Version", "1.0.0"), ("VersionOverride", "3.0.0"))], + PackageVersions = [CreateItem("Test.Package", ("Version", "2.0.0"))], + ResolvedPackages = [CreateItem("Test.Package/2.0.0", ("Name", "Test.Package"), ("Version", "2.0.0"), ("Path", packagePath))], + }; + + Assert.False(task.Execute()); + Assert.Contains("requested '3.0.0', resolved '2.0.0'", Assert.Single(engine.Errors).Message); + } + finally + { + Directory.Delete(packagePath, recursive: true); + } + } + + [Fact] + public void ValidatePackagesTask_UsesCentralVersionWhenReferenceHasNoVersion() + { + string packagePath = CreatePackageDirectory(); + try + { + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "App.csproj", + ManagePackageVersionsCentrally = true, + PackageReferences = [CreateItem("Test.Package")], + PackageVersions = [CreateItem("Test.Package", ("Version", "2.0.0"))], + ResolvedPackages = [CreateItem("Test.Package/1.0.0", ("Name", "Test.Package"), ("Version", "1.0.0"), ("Path", packagePath))], + }; + + Assert.False(task.Execute()); + Assert.Contains("requested '2.0.0', resolved '1.0.0'", Assert.Single(engine.Errors).Message); + } + finally + { + Directory.Delete(packagePath, recursive: true); + } + } + + [Fact] + public void ValidatePackagesTask_RejectsStaleRequestedVersion() + { + string tempRoot = CreateTempRoot(); + try + { + string packagePath = Path.Combine(tempRoot, "test.package", "13.0.1"); + Directory.CreateDirectory(packagePath); + string assetsFile = Path.Combine(tempRoot, "project.assets.json"); + File.WriteAllText( + assetsFile, + """ + { + "project": { + "frameworks": { + "net8.0": { + "dependencies": { + "Test.Package": { + "target": "Package", + "version": "[13.0.1, )" + } + } + } + } + } + } + """); + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "App.csproj", + AssetsFile = assetsFile, + TargetFramework = "net8.0", + PackageReferences = [CreateItem("Test.Package", ("Version", "12.0.3"))], + ResolvedPackages = [CreateItem("Test.Package/13.0.1", ("Name", "Test.Package"), ("Version", "13.0.1"), ("Path", packagePath))], + }; + + Assert.False(task.Execute()); + Assert.Contains("current request '12.0.3', restored request '[13.0.1, )'", Assert.Single(engine.Errors).Message); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Theory] + [InlineData("", "", "", null, null, true)] + [InlineData(";", "", "", null, null, true)] + [InlineData("", ";", " ; ", null, null, true)] + [InlineData("compile;build", "", "all", "Compile, Build", "All", true)] + [InlineData("compile,build", "", "", "None", null, true)] + [InlineData("all", "build", "", "Runtime, Compile, ContentFiles, Native, Analyzers, BuildTransitive", null, true)] + [InlineData("", "runtime", "", null, null, false)] + [InlineData("", "", "all", null, null, false)] + public void ValidatePackagesTask_ValidatesPackageAssetSelection( + string includeAssets, + string excludeAssets, + string privateAssets, + string? restoredInclude, + string? restoredSuppressParent, + bool expectedResult) + { + string tempRoot = CreateTempRoot(); + try + { + string packagePath = Path.Combine(tempRoot, "test.package", "1.0.0"); + Directory.CreateDirectory(packagePath); + string includeProperty = restoredInclude is null ? string.Empty : $", \"include\": \"{restoredInclude}\""; + string suppressParentProperty = restoredSuppressParent is null ? string.Empty : $", \"suppressParent\": \"{restoredSuppressParent}\""; + string assetsFile = Path.Combine(tempRoot, "project.assets.json"); + File.WriteAllText( + assetsFile, + $$""" + { + "project": { + "frameworks": { + "net8.0": { + "dependencies": { + "Test.Package": { + "target": "Package", + "version": "[1.0.0, )"{{includeProperty}}{{suppressParentProperty}} + } + } + } + } + } + } + """); + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "App.csproj", + AssetsFile = assetsFile, + TargetFramework = "net8.0", + PackageReferences = + [ + CreateItem( + "Test.Package", + ("Version", "1.0.0"), + ("IncludeAssets", includeAssets), + ("ExcludeAssets", excludeAssets), + ("PrivateAssets", privateAssets)), + ], + ResolvedPackages = [CreateItem("Test.Package/1.0.0", ("Name", "Test.Package"), ("Version", "1.0.0"), ("Path", packagePath))], + }; + + Assert.Equal(expectedResult, task.Execute()); + if (!expectedResult) + { + Assert.Contains("current assets", Assert.Single(engine.Errors).Message); + } + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void ValidatePackagesTask_RejectsMissingDeclaredPackageAndResolvedFolder() + { + var engine = new BuildEngineStub(); + string missingPath = Path.Combine(Path.GetTempPath(), "projectdata-missing-package-" + Guid.NewGuid().ToString("N")); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "App.csproj", + PackageReferences = [CreateItem("Declared.Package", ("Version", "1.0.0"))], + ResolvedPackages = [CreateItem("Other.Package/2.0.0", ("Name", "Other.Package"), ("Version", "2.0.0"), ("Path", missingPath))], + }; + + Assert.False(task.Execute()); + Assert.Contains(engine.Errors, error => error.Message?.Contains("restore graph does not contain declared PackageReference items: Declared.Package", StringComparison.Ordinal) == true); + Assert.Contains(engine.Errors, error => error.Message?.Contains($"package files are missing: Other.Package/2.0.0 at {missingPath}", StringComparison.Ordinal) == true); + } + + [Fact] + public void ValidatePackagesTask_RejectsRestoredRequestRemovedFromCurrentProject() + { + string tempRoot = CreateTempRoot(); + try + { + string assetsFile = Path.Combine(tempRoot, "project.assets.json"); + File.WriteAllText( + assetsFile, + """ + { + "project": { + "frameworks": { + "net8.0": { + "dependencies": { + "Removed.Package": { + "target": "Package", + "version": "[1.0.0, )" + } + } + } + } + } + } + """); + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "App.csproj", + AssetsFile = assetsFile, + TargetFramework = "net8.0", + }; + + Assert.False(task.Execute()); + Assert.Contains("Removed.Package (restored request '[1.0.0, )', no current PackageReference)", Assert.Single(engine.Errors).Message); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void ValidatePackagesTask_RejectsExplicitReferenceWithoutEvaluatedVersion() + { + string packagePath = CreatePackageDirectory(); + try + { + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "App.csproj", + ManagePackageVersionsCentrally = true, + PackageReferences = [CreateItem("Central.Package")], + ResolvedPackages = [CreateItem("Central.Package/2.0.0", ("Name", "Central.Package"), ("Version", "2.0.0"), ("Path", packagePath))], + }; + + Assert.False(task.Execute()); + Assert.Contains("declared PackageReference items have no evaluated version request: Central.Package", Assert.Single(engine.Errors).Message); + } + finally + { + Directory.Delete(packagePath, recursive: true); + } + } + + [Fact] + public void ValidatePackagesTask_ReportsMalformedRestoreGraphWithContext() + { + string tempRoot = CreateTempRoot(); + try + { + string packagePath = Path.Combine(tempRoot, "test.package", "1.0.0"); + Directory.CreateDirectory(packagePath); + string assetsFile = Path.Combine(tempRoot, "project.assets.json"); + File.WriteAllText( + assetsFile, + """ + { + "project": { + "frameworks": { + "net8.0": [] + } + } + } + """); + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "App.csproj", + AssetsFile = assetsFile, + TargetFramework = "net8.0", + PackageReferences = [CreateItem("Test.Package", ("Version", "1.0.0"))], + ResolvedPackages = [CreateItem("Test.Package/1.0.0", ("Name", "Test.Package"), ("Version", "1.0.0"), ("Path", packagePath))], + }; + + Assert.False(task.Execute()); + BuildErrorEventArgs error = Assert.Single(engine.Errors); + Assert.Contains($"restore graph '{assetsFile}' could not be read", error.Message); + Assert.Contains("target framework 'net8.0' must be a JSON object", error.Message); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void ValidatePackagesTask_ReportsMalformedDependencyTargetWithContext() + { + string tempRoot = CreateTempRoot(); + try + { + string assetsFile = Path.Combine(tempRoot, "project.assets.json"); + File.WriteAllText( + assetsFile, + """ + { + "project": { + "frameworks": { + "net8.0": { + "dependencies": { + "Broken.Package": { + "target": [], + "version": "[1.0.0, )" + } + } + } + } + } + } + """); + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "App.csproj", + AssetsFile = assetsFile, + TargetFramework = "net8.0", + }; + + Assert.False(task.Execute()); + BuildErrorEventArgs error = Assert.Single(engine.Errors); + Assert.Contains($"restore graph '{assetsFile}' could not be read", error.Message); + Assert.Contains("dependency request 'Broken.Package' for target framework 'net8.0' has no string target", error.Message); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void ValidatePackagesTask_IgnoresRestoredSdkAutoReferencedPackagesMissingFromCurrentEvaluation() + { + string tempRoot = CreateTempRoot(); + try + { + string assetsFile = Path.Combine(tempRoot, "project.assets.json"); + File.WriteAllText( + assetsFile, + """ + { + "project": { + "frameworks": { + "net10.0": { + "dependencies": { + "Aspire.Hosting.AppHost": { + "autoReferenced": true, + "target": "Package", + "version": "[13.3.5, )" + } + } + } + } + } + } + """); + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "AppHost.csproj", + AssetsFile = assetsFile, + TargetFramework = "net10.0", + }; + + Assert.True(task.Execute()); + Assert.Empty(engine.Errors); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void ValidatePackagesTask_MatchesCurrentReferenceToRestoredAutoReferencedRequest() + { + string tempRoot = CreateTempRoot(); + try + { + string packagePath = Path.Combine(tempRoot, "aspire.hosting.apphost", "13.3.5"); + Directory.CreateDirectory(packagePath); + string assetsFile = WriteAutoReferencedAssetsFile(tempRoot, "true", "\"[13.3.5, )\""); + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "AppHost.csproj", + AssetsFile = assetsFile, + TargetFramework = "net10.0", + PackageReferences = [CreateItem("Aspire.Hosting.AppHost", ("Version", "13.3.5"))], + ResolvedPackages = + [ + CreateItem( + "Aspire.Hosting.AppHost/13.3.5", + ("Name", "Aspire.Hosting.AppHost"), + ("Version", "13.3.5"), + ("Path", packagePath)), + ], + }; + + Assert.True(task.Execute()); + Assert.Empty(engine.Errors); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Theory] + [InlineData("false")] + [InlineData("\"true\"")] + public void ValidatePackagesTask_DoesNotIgnoreInvalidAutoReferencedMarkers(string autoReferenced) + { + string tempRoot = CreateTempRoot(); + try + { + string assetsFile = WriteAutoReferencedAssetsFile(tempRoot, autoReferenced, "\"[13.3.5, )\""); + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "AppHost.csproj", + AssetsFile = assetsFile, + TargetFramework = "net10.0", + }; + + Assert.False(task.Execute()); + Assert.Contains("no current PackageReference", Assert.Single(engine.Errors).Message); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void ValidatePackagesTask_ReportsMalformedAutoReferencedVersionWithContext() + { + string tempRoot = CreateTempRoot(); + try + { + string assetsFile = WriteAutoReferencedAssetsFile(tempRoot, "true", "[]"); + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "AppHost.csproj", + AssetsFile = assetsFile, + TargetFramework = "net10.0", + }; + + Assert.False(task.Execute()); + BuildErrorEventArgs error = Assert.Single(engine.Errors); + Assert.Contains($"restore graph '{assetsFile}' could not be read", error.Message); + Assert.Contains("package dependency request 'Aspire.Hosting.AppHost'", error.Message); + Assert.Contains("has no string version", error.Message); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + private static string WriteAutoReferencedAssetsFile(string tempRoot, string autoReferenced, string version) + { + string assetsFile = Path.Combine(tempRoot, "project.assets.json"); + File.WriteAllText( + assetsFile, + $$""" + { + "project": { + "frameworks": { + "net10.0": { + "dependencies": { + "Aspire.Hosting.AppHost": { + "autoReferenced": {{autoReferenced}}, + "target": "Package", + "version": {{version}} + } + } + } + } + } + } + """); + return assetsFile; + } + + [Theory] + [InlineData("2.0.0", "2.0.0", true)] + [InlineData("2.0.0", "1.0.0", false)] + [InlineData(null, "2.0.0", false)] + [InlineData("2.0.0", null, false)] + public void ValidatePackagesTask_ValidatesActiveCentralTransitivePins( + string? restoredPinVersion, + string? currentPinVersion, + bool expectedResult) + { + string tempRoot = CreateTempRoot(); + try + { + string restoredPin = restoredPinVersion is null + ? string.Empty + : $$""" + "Pinned.Package": { + "include": "Runtime, Compile", + "version": "[{{restoredPinVersion}}, )" + } + """; + string assetsFile = WriteCentralTransitiveAssetsFile(tempRoot, restoredPin); + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "App.csproj", + AssetsFile = assetsFile, + TargetFramework = "netstandard2.0", + TargetFrameworkMoniker = ".NETStandard,Version=v2.0", + ManagePackageVersionsCentrally = true, + CentralPackageTransitivePinningEnabled = true, + PackageVersions = currentPinVersion is null + ? [] + : [CreateItem("Pinned.Package", ("Version", currentPinVersion))], + }; + + Assert.Equal(expectedResult, task.Execute()); + if (expectedResult) + { + Assert.Empty(engine.Errors); + } + else + { + Assert.Contains( + engine.Errors, + error => error.Message?.Contains("central transitive package version requests differ from the restore graph", StringComparison.Ordinal) == true); + } + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void ValidatePackagesTask_IgnoresInactiveCentralVersionAndUnpinnedTransitivePackage() + { + string tempRoot = CreateTempRoot(); + try + { + string assetsFile = WriteCentralTransitiveAssetsFile(tempRoot, centralTransitiveRequests: string.Empty); + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "App.csproj", + AssetsFile = assetsFile, + TargetFramework = "netstandard2.0", + TargetFrameworkMoniker = ".NETStandard,Version=v2.0", + ManagePackageVersionsCentrally = true, + CentralPackageTransitivePinningEnabled = true, + PackageVersions = + [ + CreateItem("Inactive.Package", ("Version", "5.0.0")), + CreateItem("Inactive.Resolved", ("Version", "5.0.0")), + ], + }; + + Assert.True(task.Execute()); + Assert.Empty(engine.Errors); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void ValidatePackagesTask_ReportsMalformedCentralTransitivePinWithContext() + { + string tempRoot = CreateTempRoot(); + try + { + string assetsFile = WriteCentralTransitiveAssetsFile( + tempRoot, + """ + "Pinned.Package": { + "version": [] + } + """); + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "App.csproj", + AssetsFile = assetsFile, + TargetFramework = "netstandard2.0", + TargetFrameworkMoniker = ".NETStandard,Version=v2.0", + CentralPackageTransitivePinningEnabled = true, + }; + + Assert.False(task.Execute()); + BuildErrorEventArgs error = Assert.Single(engine.Errors); + Assert.Contains($"restore graph '{assetsFile}' could not be read", error.Message); + Assert.Contains("central transitive dependency request 'Pinned.Package'", error.Message); + Assert.Contains("has no string version", error.Message); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void ValidatePackagesTask_ReportsMalformedResolvedTargetGraphWithContext() + { + string tempRoot = CreateTempRoot(); + try + { + string assetsFile = Path.Combine(tempRoot, "project.assets.json"); + File.WriteAllText( + assetsFile, + """ + { + "targets": { + "netstandard2.0": [] + }, + "centralTransitiveDependencyGroups": {}, + "project": { + "restore": { + "CentralPackageTransitivePinningEnabled": true + }, + "frameworks": { + "netstandard2.0": { + "dependencies": {} + } + } + } + } + """); + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "App.csproj", + AssetsFile = assetsFile, + TargetFramework = "netstandard2.0", + TargetFrameworkMoniker = ".NETStandard,Version=v2.0", + CentralPackageTransitivePinningEnabled = true, + }; + + Assert.False(task.Execute()); + BuildErrorEventArgs error = Assert.Single(engine.Errors); + Assert.Contains($"restore graph '{assetsFile}' could not be read", error.Message); + Assert.Contains("resolved target graph for target framework 'netstandard2.0' must be a JSON object", error.Message); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Theory] + [InlineData("false", false, true)] + [InlineData("true", true, true)] + [InlineData("false", true, false)] + [InlineData("true", false, false)] + public void ValidatePackagesTask_ValidatesCentralTransitivePinningMode( + string restoredPinningMode, + bool currentPinningEnabled, + bool expectedResult) + { + string tempRoot = CreateTempRoot(); + try + { + string assetsFile = WriteCentralTransitiveAssetsFile( + tempRoot, + centralTransitiveRequests: string.Empty, + restoredPinningMode: restoredPinningMode); + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "App.csproj", + AssetsFile = assetsFile, + TargetFramework = "netstandard2.0", + TargetFrameworkMoniker = ".NETStandard,Version=v2.0", + ManagePackageVersionsCentrally = true, + CentralPackageTransitivePinningEnabled = currentPinningEnabled, + }; + + Assert.Equal(expectedResult, task.Execute()); + Assert.Equal( + expectedResult ? 0 : 1, + engine.Errors.Count(error => error.Message?.Contains("central transitive package pinning mode differs from the restore graph", StringComparison.Ordinal) == true)); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + [Fact] + public void ValidatePackagesTask_ReportsMalformedCentralTransitivePinningModeWithContext() + { + string tempRoot = CreateTempRoot(); + try + { + string assetsFile = WriteCentralTransitiveAssetsFile( + tempRoot, + centralTransitiveRequests: string.Empty, + restoredPinningMode: "\"true\""); + var engine = new BuildEngineStub(); + var task = new ValidateProjectDataPackagesTask + { + BuildEngine = engine, + ProjectFilePath = "App.csproj", + AssetsFile = assetsFile, + TargetFramework = "netstandard2.0", + TargetFrameworkMoniker = ".NETStandard,Version=v2.0", + ManagePackageVersionsCentrally = true, + CentralPackageTransitivePinningEnabled = true, + }; + + Assert.False(task.Execute()); + BuildErrorEventArgs error = Assert.Single(engine.Errors); + Assert.Contains($"restore graph '{assetsFile}' could not be read", error.Message); + Assert.Contains("central transitive package pinning mode in restore settings must be a JSON boolean", error.Message); + } + finally + { + DeleteTempRoot(tempRoot); + } + } + + private static string WriteCentralTransitiveAssetsFile( + string tempRoot, + string centralTransitiveRequests, + string restoredPinningMode = "true") + { + string assetsFile = Path.Combine(tempRoot, "project.assets.json"); + File.WriteAllText( + assetsFile, + $$""" + { + "targets": { + "netstandard2.0": { + "Pinned.Package/2.0.0": { + "type": "package" + }, + "Normal.Transitive/1.0.0": { + "type": "package" + }, + "Inactive.Resolved/5.0.0": { + "type": "package" + } + } + }, + "centralTransitiveDependencyGroups": { + ".NETStandard,Version=v2.0": { + {{centralTransitiveRequests}} + } + }, + "project": { + "restore": { + "CentralPackageTransitivePinningEnabled": {{restoredPinningMode}} + }, + "frameworks": { + "netstandard2.0": { + "dependencies": {}, + "centralPackageVersions": { + "Inactive.Resolved": "5.0.0" + } + } + } + } + } + """); + return assetsFile; + } + + private static Microsoft.Build.Utilities.TaskItem CreateItem(string itemSpec, params (string Name, string Value)[] metadata) + { + var item = new Microsoft.Build.Utilities.TaskItem(itemSpec); + foreach ((string name, string value) in metadata) + { + item.SetMetadata(name, value); + } + + return item; + } + + private static string CreatePackageDirectory() + { + string path = Path.Combine(Path.GetTempPath(), "projectdata-package-validation-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(path); + return path; + } + + private sealed class BuildEngineStub : IBuildEngine + { + public List Errors { get; } = []; + public List Warnings { get; } = []; + public List Messages { get; } = []; + public List CustomEvents { get; } = []; + + public bool ContinueOnError => false; + public int LineNumberOfTaskNode => 0; + public int ColumnNumberOfTaskNode => 0; + public string ProjectFileOfTaskNode => string.Empty; + + public bool BuildProjectFile(string projectFileName, string[] targetNames, System.Collections.IDictionary globalProperties, System.Collections.IDictionary targetOutputs) + => throw new NotSupportedException(); + + public void LogCustomEvent(CustomBuildEventArgs e) => this.CustomEvents.Add(e); + public void LogErrorEvent(BuildErrorEventArgs e) => this.Errors.Add(e); + public void LogMessageEvent(BuildMessageEventArgs e) => this.Messages.Add(e); + public void LogWarningEvent(BuildWarningEventArgs e) => this.Warnings.Add(e); + } + + private static string CreateTempRoot() + { + string path = Path.Combine(Path.GetTempPath(), "projectdata-task-donor-tests-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(path); + return path; + } + + private static void DeleteTempRoot(string tempRoot) + { + try + { + Directory.Delete(tempRoot, recursive: true); + } + catch + { + } + } + + private static string JsonString(string value) + => "\"" + value.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""; + + private static WriteProjectDataSliceTask CreateFinalWriteTask( + string workspaceRoot, + string indexPath, + BuildEngineStub? buildEngine = null) + { + string projectFile = Path.Combine(workspaceRoot, "src", "App", "App.csproj"); + return new WriteProjectDataSliceTask + { + BuildEngine = buildEngine ?? new BuildEngineStub(), + ProjectFilePath = projectFile, + OutputPath = projectFile + ".lscache", + DonorCacheIndexPath = indexPath, + DonorCacheWorkspaceRoot = workspaceRoot, + WriteHeader = true, + IsPrimary = true, + LastDtbSucceeded = true, + CommandLineArguments = ["/noconfig"], + }; + } + + private static void WriteSlice(string slicePath, string assemblyName, string targetFramework) + { + Directory.CreateDirectory(Path.GetDirectoryName(slicePath)!); + File.WriteAllText( + slicePath, + $$""" + [project] + project=App.csproj + language=C# + primary + lastDtbSucceeded + + [sliceDimensions] + TargetFramework={{targetFramework}} + + [properties] + AssemblyName={{assemblyName}} + + [commandLineArguments] + /noconfig + """); + } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/XunitV2TestContext.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/XunitV2TestContext.cs new file mode 100644 index 0000000000000..f19a26bc51a4e --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks.Tests/XunitV2TestContext.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.NET.ProjectData.Tasks.Tests; + +internal static class TestContext +{ + public static TestContextState Current { get; } = new(); + + internal sealed class TestContextState + { + public CancellationToken CancellationToken => System.Threading.CancellationToken.None; + } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/AnalyzerConfigFileFilter.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/AnalyzerConfigFileFilter.cs new file mode 100644 index 0000000000000..7796789bc9cf0 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/AnalyzerConfigFileFilter.cs @@ -0,0 +1,228 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; +using Microsoft.NET.ProjectData; + +namespace Microsoft.NET.ProjectData.Tasks; + +internal static class AnalyzerConfigFileFilter +{ + public static List Prepare( + string[]? items, + CachePathResolver resolver, + ITaskItem[]? sourceFiles, + bool filterSdkAnalyzerConfigFiles) + { + var result = new List(); + if (items == null || items.Length == 0) return new List(); + foreach (string item in items) + { + if (string.IsNullOrEmpty(item)) continue; + string absolute = resolver.ToAbsolutePath(item); + string portable = resolver.ToPortable(item); + if (filterSdkAnalyzerConfigFiles && IsSdkAnalyzerConfigFilePath(portable)) + { + continue; + } + + result.Add(new AnalyzerConfigFilePath(absolute, portable)); + } + + List sourceDirectories = GetSourceDirectories(sourceFiles, resolver); + List rootEditorConfigPaths = FindRootEditorConfigPaths(result, sourceDirectories); + return result + .Where(item => !IsEditorConfigFilePath(item.AbsolutePath) + || IsEditorConfigApplicableToAnySourceFile(item.AbsolutePath, sourceDirectories, rootEditorConfigPaths)) + .Select(item => item.PortablePath) + .OrderBy(static item => item, StringComparer.OrdinalIgnoreCase) + .ToList(); + } + + internal static bool IsSdkAnalyzerConfigFilePath(string? portablePath) + { + if (portablePath == null) return false; + + const string netSdkPrefix = PathSentinels.NetSdk + "/Sdks/Microsoft.NET.Sdk/"; + if (!portablePath.StartsWith(netSdkPrefix, StringComparison.OrdinalIgnoreCase)) return false; + + string sdkRelative = portablePath.Substring(netSdkPrefix.Length); + const string analyzerConfigPrefix = "analyzers/build/config/"; + if (sdkRelative.StartsWith(analyzerConfigPrefix, StringComparison.OrdinalIgnoreCase)) + { + string fileName = sdkRelative.Substring(analyzerConfigPrefix.Length); + if (fileName.IndexOf('/') >= 0) return false; + + return fileName.StartsWith("analysislevel_", StringComparison.OrdinalIgnoreCase) + && fileName.EndsWith(".globalconfig", StringComparison.OrdinalIgnoreCase); + } + + const string codeStylePrefix = "codestyle/"; + if (sdkRelative.StartsWith(codeStylePrefix, StringComparison.OrdinalIgnoreCase)) + { + string rest = sdkRelative.Substring(codeStylePrefix.Length); + const string configMarker = "/build/config/"; + int markerIndex = rest.IndexOf(configMarker, StringComparison.OrdinalIgnoreCase); + if (markerIndex <= 0) return false; + + string language = rest.Substring(0, markerIndex); + string fileName = rest.Substring(markerIndex + configMarker.Length); + if (!IsSafeLogicalSegment(language) || fileName.IndexOf('/') >= 0) return false; + + const string stylePrefix = "analysislevelstyle_"; + const string globalConfigSuffix = ".globalconfig"; + return fileName.StartsWith(stylePrefix, StringComparison.OrdinalIgnoreCase) + && fileName.EndsWith(globalConfigSuffix, StringComparison.OrdinalIgnoreCase); + } + + return false; + } + + private static List GetSourceDirectories(ITaskItem[]? sourceFiles, CachePathResolver resolver) + { + var result = new HashSet(StringComparers.Paths); + if (sourceFiles != null) + { + foreach (ITaskItem sourceFile in sourceFiles) + { + if (sourceFile == null) continue; + + string itemSpec = sourceFile.ItemSpec; + if (string.IsNullOrEmpty(itemSpec)) continue; + + string? directory = Path.GetDirectoryName(resolver.ToAbsolutePath(itemSpec)); + if (!string.IsNullOrEmpty(directory)) + { + result.Add(NormalizePathForComparison(directory)); + } + } + } + + if (result.Count == 0) + { + result.Add(NormalizePathForComparison(resolver.ProjectDirectory)); + } + + return result.ToList(); + } + + private static List FindRootEditorConfigPaths( + IEnumerable items, + IReadOnlyList sourceDirectories) + { + return items + .Select(static item => item.AbsolutePath) + .Where(IsEditorConfigFilePath) + .Where(path => sourceDirectories.Any(sourceDirectory => + IsSameOrAncestorDirectory(Path.GetDirectoryName(path) ?? string.Empty, sourceDirectory))) + .Where(EditorConfigHasRootTrue) + .Select(NormalizePathForComparison) + .ToList(); + } + + private static bool IsEditorConfigApplicableToAnySourceFile( + string editorConfigPath, + IReadOnlyList sourceDirectories, + IReadOnlyList rootEditorConfigPaths) + { + string editorConfigDirectory = Path.GetDirectoryName(editorConfigPath) ?? string.Empty; + return sourceDirectories.Any(sourceDirectory => IsSameOrAncestorDirectory(editorConfigDirectory, sourceDirectory) + && !HasRootEditorConfigBetween(editorConfigPath, editorConfigDirectory, sourceDirectory, rootEditorConfigPaths)); + } + + private static bool HasRootEditorConfigBetween( + string editorConfigPath, + string editorConfigDirectory, + string sourceDirectory, + IReadOnlyList rootEditorConfigPaths) + { + foreach (string rootEditorConfigPath in rootEditorConfigPaths) + { + if (PathsEqual(editorConfigPath, rootEditorConfigPath)) continue; + + string rootDirectory = Path.GetDirectoryName(rootEditorConfigPath) ?? string.Empty; + if (IsSameOrAncestorDirectory(rootDirectory, sourceDirectory) && IsAncestorDirectory(editorConfigDirectory, rootDirectory)) + { + return true; + } + } + + return false; + } + + private static bool EditorConfigHasRootTrue(string path) + { + if (!File.Exists(path)) return false; + + foreach (string line in File.ReadLines(path)) + { + string trimmed = line.Trim(); + if (trimmed.Length == 0 || trimmed[0] == '#' || trimmed[0] == ';') continue; + if (trimmed[0] == '[') return false; + + int equals = trimmed.IndexOf('='); + if (equals < 0) continue; + + string key = trimmed.Substring(0, equals).Trim(); + string value = trimmed.Substring(equals + 1).Trim(); + if (string.Equals(key, "root", StringComparison.OrdinalIgnoreCase) + && string.Equals(value, "true", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + + private static bool IsEditorConfigFilePath(string path) + => string.Equals(Path.GetFileName(path), ".editorconfig", StringComparison.OrdinalIgnoreCase); + + private static bool IsSameOrAncestorDirectory(string candidateDirectory, string descendantDirectory) + => PathsEqual(candidateDirectory, descendantDirectory) || IsAncestorDirectory(candidateDirectory, descendantDirectory); + + private static bool IsAncestorDirectory(string candidateDirectory, string descendantDirectory) + { + if (string.IsNullOrEmpty(candidateDirectory) || string.IsNullOrEmpty(descendantDirectory)) return false; + + string normalizedCandidate = NormalizeDirectoryForComparison(candidateDirectory); + string normalizedDescendant = NormalizeDirectoryForComparison(descendantDirectory); + return normalizedDescendant.Length > normalizedCandidate.Length + && normalizedDescendant.StartsWith(normalizedCandidate, StringComparisons.Paths); + } + + private static bool PathsEqual(string left, string right) + { + string normalizedLeft = Path.GetFullPath(left).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + string normalizedRight = Path.GetFullPath(right).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + return string.Equals(normalizedLeft, normalizedRight, StringComparisons.Paths); + } + + private static string NormalizeDirectoryForComparison(string path) + { + string normalized = NormalizePathForComparison(path); + return normalized + Path.DirectorySeparatorChar; + } + + private static string NormalizePathForComparison(string path) + => Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + + private static bool IsSafeLogicalSegment(string value) + => !string.IsNullOrWhiteSpace(value) + && value.IndexOf("..", StringComparison.Ordinal) < 0 + && value.IndexOf('/') < 0 + && value.IndexOf('\\') < 0; + + private readonly struct AnalyzerConfigFilePath + { + public AnalyzerConfigFilePath(string absolutePath, string portablePath) + { + this.AbsolutePath = absolutePath; + this.PortablePath = portablePath; + } + + public string AbsolutePath { get; } + public string PortablePath { get; } + } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/CachePathResolver.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/CachePathResolver.cs new file mode 100644 index 0000000000000..1764c593d1b12 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/CachePathResolver.cs @@ -0,0 +1,365 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.NET.ProjectData; + +namespace Microsoft.NET.ProjectData.Tasks; + +/// +/// Substitutes well-known absolute paths with sentinel tokens +/// (<NUGET>, <DOTNET>, <NETFXREF>) so that +/// emitted project data is portable across machines. Used both for full path +/// sections () and for absolute paths embedded inside +/// property values or command-line arguments (). +/// +internal sealed class CachePathResolver +{ + private readonly string[] nugetFolders; + private readonly string[] dotnetRoots; + private readonly string? netFxRefRoot; + private readonly string projectDir; + private readonly StringComparison pathsComparison; + + public CachePathResolver(string projectFilePath) + { + this.projectDir = Path.GetDirectoryName(projectFilePath) ?? string.Empty; + bool caseInsensitive = Path.DirectorySeparatorChar == '\\'; + this.pathsComparison = caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + this.nugetFolders = ResolveNuGetFolders(); + this.dotnetRoots = ResolveDotNetRoots(); + this.netFxRefRoot = ResolveNetFxRefRoot(); + } + + // For testing — allows injecting roots directly. + internal CachePathResolver(string projectDir, string[] nugetFolders, string[] dotnetRoots, string? netFxRefRoot) + { + this.projectDir = projectDir; + bool caseInsensitive = Path.DirectorySeparatorChar == '\\'; + this.pathsComparison = caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + this.nugetFolders = nugetFolders; + this.dotnetRoots = dotnetRoots; + this.netFxRefRoot = netFxRefRoot; + } + + internal string ProjectDirectory => this.projectDir; + + // Converts a full or project-relative file path to a portable /... + // form when it falls under one of the well-known roots, or to a project-relative + // path otherwise. + public string ToPortable(string inputPath) + { + if (string.IsNullOrEmpty(inputPath)) return inputPath; + + string absolutePath = this.ToAbsolutePath(inputPath); + + for (int i = 0; i < this.nugetFolders.Length; i++) + { + if (absolutePath.StartsWith(this.nugetFolders[i], this.pathsComparison)) + return PathSentinels.Nuget + "/" + absolutePath.Substring(this.nugetFolders[i].Length).Replace('\\', '/'); + } + for (int i = 0; i < this.dotnetRoots.Length; i++) + { + if (absolutePath.StartsWith(this.dotnetRoots[i], this.pathsComparison)) + { + string portable = PathSentinels.Dotnet + "/" + absolutePath.Substring(this.dotnetRoots[i].Length).Replace('\\', '/'); + return RewriteSdkPath(portable); + } + } + if (this.netFxRefRoot != null && absolutePath.StartsWith(this.netFxRefRoot, this.pathsComparison)) + return PathSentinels.NetFxRef + "/" + absolutePath.Substring(this.netFxRefRoot.Length).Replace('\\', '/'); + + string relative = MakeRelative(this.projectDir, absolutePath).Replace('\\', '/'); + return TryRewriteAsNuGetPp(relative) ?? relative; + } + + internal string ToAbsolutePath(string inputPath) + { + if (string.IsNullOrEmpty(inputPath)) return inputPath; + return Path.IsPathRooted(inputPath) + ? Path.GetFullPath(inputPath) + : Path.GetFullPath(Path.Combine(this.projectDir, inputPath)); + } + + /// + /// Detects project-relative paths produced by the SDK's NuGet content-asset preprocessor + /// (format: obj/{Config}/{TFM}/NuGet/{XxHash3-16hex}/{PackageId}/{Version}/...) + /// and rewrites them to the <NUGETPP>/{PackageId}/{Version}/... sentinel form. + /// This makes the cache fully portable: the obj-relative prefix and the environment-dependent + /// hash are both removed. At read time, the reader resolves <NUGETPP> by scanning + /// for the actual hash directory under the project's intermediate output path. + /// + internal static string? TryRewriteAsNuGetPp(string relativePath) + { + // Pattern: .../NuGet/<16-hex-chars>///... + // We look for "/NuGet/" followed by exactly 16 hex characters and then "/" + const string NuGetSegment = "/NuGet/"; + int nugetIdx = relativePath.IndexOf(NuGetSegment, StringComparison.OrdinalIgnoreCase); + if (nugetIdx < 0) return null; + + int hashStart = nugetIdx + NuGetSegment.Length; + // Must have at least 16 chars + trailing '/' + if (hashStart + 17 > relativePath.Length) return null; + if (relativePath[hashStart + 16] != '/') return null; + + // Validate all 16 characters are hex digits + for (int i = 0; i < 16; i++) + { + char c = relativePath[hashStart + i]; + if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))) + return null; + } + + // Everything after the hash+separator is "//..." + string packageRelativePath = relativePath.Substring(hashStart + 17); + return PathSentinels.NugetPp + "/" + packageRelativePath; + } + + // /sdk//Sdks/Microsoft.NET.Sdk/analyzers/Foo.dll + // → /Sdks/Microsoft.NET.Sdk/analyzers/Foo.dll + // + // The version segment is dropped because: + // - the contents of /sdk// are otherwise identical across SDK + // patch releases (analyzer DLLs, global configs); + // - keeping the version in the cache would invalidate every project's lscache + // on every SDK patch with no behavioral benefit. + // + // The reader's CachePathResolver requires a caller-supplied SDK binding to + // resolve ; the cache file itself is intentionally environment-agnostic. + internal static string RewriteSdkPath(string portable) + { + const string SdkPrefix = PathSentinels.Dotnet + "/sdk/"; + if (!portable.StartsWith(SdkPrefix, StringComparison.Ordinal)) return portable; + + int versionEnd = portable.IndexOf('/', SdkPrefix.Length); + if (versionEnd < 0) return portable; + + return PathSentinels.NetSdk + portable.Substring(versionEnd); + } + + // Finds an absolute path embedded anywhere inside a property value or + // command-line argument and replaces the first match with its sentinel form, + // preserving any surrounding text. + public string MakePortable(string text) + { + if (string.IsNullOrEmpty(text)) return text; + + for (int i = 0; i < this.nugetFolders.Length; i++) + { + int idx = text.IndexOf(this.nugetFolders[i], this.pathsComparison); + if (idx >= 0) + return FormatEmbedded(text, idx, PathSentinels.Nuget, idx + this.nugetFolders[i].Length); + } + for (int i = 0; i < this.dotnetRoots.Length; i++) + { + int idx = text.IndexOf(this.dotnetRoots[i], this.pathsComparison); + if (idx >= 0) + { + string portable = FormatEmbedded(text, idx, PathSentinels.Dotnet, idx + this.dotnetRoots[i].Length); + return RewriteEmbeddedSdkPath(portable, idx); + } + } + if (this.netFxRefRoot != null) + { + int idx = text.IndexOf(this.netFxRefRoot, this.pathsComparison); + if (idx >= 0) + return FormatEmbedded(text, idx, PathSentinels.NetFxRef, idx + this.netFxRefRoot.Length); + } + + int pathStart = FindAbsolutePathStart(text); + if (pathStart >= 0) + { + string prefix = text.Substring(0, pathStart); + string absolutePath = text.Substring(pathStart); + string relativePath = MakeRelative(this.projectDir, absolutePath).Replace('\\', '/'); + return prefix + PathSentinels.Path + relativePath; + } + + return text.Replace('\\', '/'); + } + + private static string FormatEmbedded(string source, int prefixLength, string sentinel, int suffixStart) + { + string prefix = source.Substring(0, prefixLength); + string suffix = source.Substring(suffixStart).Replace('\\', '/'); + return prefix + sentinel + "/" + suffix; + } + + // Same rewrite rule as RewriteSdkPath but for embedded matches: scans the + // result for "/sdk//" and rewrites to "/". + // sentinelStart marks where the original prefix ended; we look just after + // that for the embedded "" marker so we don't accidentally rewrite + // unrelated text earlier in the string. + internal static string RewriteEmbeddedSdkPath(string portable, int sentinelStart) + { + const string SdkSegment = PathSentinels.Dotnet + "/sdk/"; + int idx = portable.IndexOf(SdkSegment, sentinelStart, StringComparison.Ordinal); + if (idx < 0) return portable; + + int versionEnd = portable.IndexOf('/', idx + SdkSegment.Length); + if (versionEnd < 0) return portable; + + return portable.Substring(0, idx) + PathSentinels.NetSdk + portable.Substring(versionEnd); + } + + // Finds the start index of an absolute path embedded in text. + // Windows: looks for `[A-Za-z]:\` or `[A-Za-z]:/` not preceded by a letter-or-digit. + // Unix: looks for `/...` at start or after `:`, `"`, or ` `. + internal static int FindAbsolutePathStart(string text) + { + for (int i = 0; i <= text.Length - 3; i++) + { + if (char.IsLetter(text[i]) && text[i + 1] == ':' && (text[i + 2] == '\\' || text[i + 2] == '/')) + { + if (i == 0 || !char.IsLetterOrDigit(text[i - 1])) + return i; + } + } + + if (Path.DirectorySeparatorChar != '\\') + { + for (int i = 0; i < text.Length - 1; i++) + { + if (text[i] == '/' && char.IsLetter(text[i + 1])) + { + if (i == 0 || text[i - 1] == ':' || text[i - 1] == '"' || text[i - 1] == ' ') + { + int nextSlash = text.IndexOf('/', i + 1); + if (nextSlash > i + 1) + { + int colon = text.IndexOf(':', i + 1); + if (colon >= 0 && colon < nextSlash) continue; + return i; + } + } + } + } + } + return -1; + } + + // Returns the longest common directory prefix (ending with '/') of two forward-slash paths, + // or null if there is no common directory prefix. + internal static string? FindSharedDirPrefix(string a, string b) + { + int minLen = Math.Min(a.Length, b.Length); + int lastSlash = -1; + for (int i = 0; i < minLen; i++) + { + if (char.ToUpperInvariant(a[i]) != char.ToUpperInvariant(b[i])) + break; + if (a[i] == '/') + lastSlash = i; + } + return lastSlash < 0 ? null : a.Substring(0, lastSlash + 1); + } + + // netstandard2.0-compatible MakeRelative. Both paths must be absolute. + // Output is '/'-normalized by callers. + internal static string MakeRelative(string basePath, string fullPath) + { + if (string.IsNullOrEmpty(basePath)) return fullPath; + string normalizedBase = basePath.Replace('\\', '/'); + if (!normalizedBase.EndsWith("/")) normalizedBase += "/"; + string normalizedFull = fullPath.Replace('\\', '/'); + bool ci = Path.DirectorySeparatorChar == '\\'; + StringComparison cmp = ci ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + + string[] baseSegs = normalizedBase.TrimEnd('/').Split('/'); + string[] fullSegs = normalizedFull.Split('/'); + int common = 0; + int limit = Math.Min(baseSegs.Length, fullSegs.Length); + while (common < limit && string.Equals(baseSegs[common], fullSegs[common], cmp)) + common++; + + var rel = new System.Text.StringBuilder(); + for (int i = common; i < baseSegs.Length; i++) + { + if (rel.Length > 0) rel.Append('/'); + rel.Append(".."); + } + for (int i = common; i < fullSegs.Length; i++) + { + if (rel.Length > 0) rel.Append('/'); + rel.Append(fullSegs[i]); + } + return rel.Length == 0 ? "." : rel.ToString(); + } + + private static string[] ResolveNuGetFolders() + { + string? envVal = Environment.GetEnvironmentVariable("NUGET_PACKAGES"); + if (!string.IsNullOrWhiteSpace(envVal)) + return [NormalizeFolderPath(envVal)]; + + string userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + if (!string.IsNullOrEmpty(userProfile)) + return [NormalizeFolderPath(Path.Combine(userProfile, ".nuget", "packages"))]; + + return []; + } + + private static string[] ResolveDotNetRoots() + { + var list = new List(); + void Add(string? candidate) + { + if (string.IsNullOrEmpty(candidate) || !Directory.Exists(candidate)) return; + string norm = NormalizeFolderPath(candidate!); + foreach (var existing in list) + if (string.Equals(existing, norm, StringComparison.OrdinalIgnoreCase)) return; + list.Add(norm); + } + + Add(Environment.GetEnvironmentVariable("DOTNET_ROOT")); + Add(TryGetDotNetRootFromHostPath(Environment.GetEnvironmentVariable("DOTNET_HOST_PATH"))); + Add(TryGetDotNetRootFromSdkPath(AppContext.BaseDirectory)); + if (Path.DirectorySeparatorChar == '\\') + { + string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + if (!string.IsNullOrEmpty(programFiles)) Add(Path.Combine(programFiles, "dotnet")); + } + else + { + Add("/usr/share/dotnet"); + Add("/usr/local/share/dotnet"); + } + string userHome = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + if (string.IsNullOrEmpty(userHome)) userHome = Environment.GetEnvironmentVariable("HOME") ?? string.Empty; + if (!string.IsNullOrEmpty(userHome)) Add(Path.Combine(userHome, ".dotnet")); + return list.ToArray(); + } + + internal static string? TryGetDotNetRootFromHostPath(string? hostPath) + { + if (string.IsNullOrWhiteSpace(hostPath) || !Path.IsPathRooted(hostPath)) return null; + return Path.GetDirectoryName(Path.GetFullPath(hostPath)); + } + + internal static string? TryGetDotNetRootFromSdkPath(string? sdkPath) + { + if (string.IsNullOrWhiteSpace(sdkPath)) return null; + + DirectoryInfo sdkVersionDirectory = new DirectoryInfo(Path.GetFullPath(sdkPath)); + DirectoryInfo? sdkDirectory = sdkVersionDirectory.Parent; + if (sdkDirectory is null || !string.Equals(sdkDirectory.Name, "sdk", StringComparison.OrdinalIgnoreCase)) return null; + return sdkDirectory.Parent?.FullName; + } + + private static string? ResolveNetFxRefRoot() + { + if (Path.DirectorySeparatorChar != '\\') return null; + string pfx86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); + if (string.IsNullOrEmpty(pfx86)) return null; + string candidate = Path.Combine(pfx86, "Reference Assemblies", "Microsoft", "Framework", ".NETFramework"); + return Directory.Exists(candidate) ? NormalizeFolderPath(candidate) : null; + } + + internal static string NormalizeFolderPath(string path) + { + string full = Path.GetFullPath(path); + if (full.Length == 0 || full[full.Length - 1] != Path.DirectorySeparatorChar) + full += Path.DirectorySeparatorChar; + return full; + } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/ForwardCompat.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/ForwardCompat.cs new file mode 100644 index 0000000000000..22245c1d4a793 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/ForwardCompat.cs @@ -0,0 +1,938 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Microsoft.NET.ProjectData; + +namespace Microsoft.NET.ProjectData.Tasks; + +/// +/// Forward-compatibility support for the .lscache format. +/// +/// The cache version is version=<major>[.<minor>]. A newer minor +/// only adds data an older build does not understand — new sections, new [properties] keys, +/// or new item @metadata. So a teammate on an older C# Dev Kit must, when it regenerates a +/// file authored by a newer minor, carry that unknown data through losslessly (so +/// the file does not churn back and forth between versions) while otherwise writing exactly the +/// content it knows about. +/// +/// The candidate content an older build produces never contains future data (it generates +/// from MSBuild inputs only). Preservation therefore means: extract the fragments of the +/// existing file this build does not recognize and splice them, verbatim, into the freshly +/// generated candidate. Known content is never reserialized, so it is byte-stable. +/// +/// "Unknown" is defined against what the current writer can emit — the schema's +/// emittable subset: a section not in , a [properties] +/// key not in (the schema's cached properties), or an item +/// @metadata key not emitted for that section's item type (see +/// ). Metadata is judged per section, not as a flattened +/// union, so a newer minor that reuses an existing metadata name on a different item type is still +/// preserved rather than mistaken for regenerable data. +/// +internal static class ForwardCompat +{ + // Sections the current writer emits. Schema-generated; matched case-sensitively because + // section names are emitted and matched verbatim. + private static readonly HashSet KnownSections = new HashSet(CacheFormat.Sections.All, StringComparer.Ordinal); + + // Item @metadata keys the writer emits, resolved PER cache section rather than as a flattened + // union across all item types. Schema-generated from each section's itemType link + // (CacheFormat.Sections.MetadataBySection). Case-insensitive because the wire form diverges from + // the schema casing (the writer emits "@link" for the "Link" metadata). A section absent from + // this map emits no metadata, so every @metadata found under it is unknown (and therefore + // preserved). Keying per section is what lets a newer minor reuse a known metadata NAME on a + // different item type: the older writer that does not emit that name there still treats it as + // unknown and carries it forward, instead of mistaking it for data it can regenerate and + // dropping it. + internal static readonly Dictionary> KnownMetadataBySection = BuildKnownMetadataBySection(); + + private static Dictionary> BuildKnownMetadataBySection() + { + var map = new Dictionary>(StringComparer.Ordinal); + foreach (KeyValuePair entry in CacheFormat.Sections.MetadataBySection) + map[entry.Key] = new HashSet(entry.Value, StringComparer.OrdinalIgnoreCase); + + return map; + } + + /// + /// The exact set of [properties] keys the writer can emit: every schema property + /// (), which the generated _ProjectDataProperties + /// MSBuild allow-list mirrors. Using the emittable set is what makes "a brand-new key appeared" + /// detectable as forward-compat data rather than as a known-but-currently-absent property. + /// + internal static readonly HashSet KnownPropertyKeys = new HashSet(ProjectProperties.All, StringComparer.OrdinalIgnoreCase); + + private const string VersionLinePrefix = "version="; + + // Identity assigned to the shared/primary segment (the block before the first "---", or the + // sole segment of a single-target file). It has no [sliceDimensions], so it cannot collide + // with a per-TFM slice identity. + private const string SharedSegmentIdentity = "\u0000shared"; + + /// + /// Returns augmented with any forward-compatible data found in + /// that the current writer does not produce, or the same + /// reference when there is nothing to preserve. + /// + /// The current on-disk cache content (with any legacy hash= + /// line already stripped). + /// The freshly generated cache content. + /// The major version this build understands. Preservation only runs + /// when the existing file's major matches; a different major is an incompatible format and the + /// caller overwrites it wholesale. + internal static string PreserveUnknownData(string existingText, string candidateText, int currentMajor) + { + // Forward-compat preservation is pay-for-play. The expensive parse + scan below only ever + // needs to run when the existing file was authored by a NEWER minor than this writer emits. + // By the "bump the minor whenever the emittable section / [properties] key / item @metadata + // set grows" invariant (guarded by ForwardCompatTests.AddingEmittableField_RequiresMinorBump), + // a same-or-older minor cannot contain anything this writer does not already produce: there + // is nothing to preserve and no higher stamp to carry forward. Probing just the two version + // headers is a zero-allocation span scan — splitting and parsing the whole file (hundreds of + // KB of transient allocation on a large cache) is reserved for the rare newer-minor case. + // Today, with no minor field shipped, every file takes this fast path. + if (!TryReadVersionHeader(existingText, out int existingMajor, out int existingMinor) + || existingMajor != currentMajor) + { + // A different major (or unrecognized header) is an incompatible format the caller + // overwrites wholesale; an empty/headerless existing file has nothing to preserve. + return candidateText; + } + + int candidateMinor = 0; + if (TryReadVersionHeader(candidateText, out int candidateMajor, out int parsedCandidateMinor) + && candidateMajor == currentMajor) + { + candidateMinor = parsedCandidateMinor; + } + + if (existingMinor <= candidateMinor) + return candidateText; + + // ---- Newer-minor path: the existing file may carry data this writer cannot regenerate. ---- + List existingSegments = ParseSegments(existingText); + if (existingSegments.Count == 0) + return candidateText; + + Segment existingPrimary = existingSegments[0]; + + // Index existing segments by slice identity so we can match them to candidate segments. + var existingByIdentity = new Dictionary(StringComparer.Ordinal); + foreach (Segment seg in existingSegments) + existingByIdentity[seg.Identity] = seg; + + // Split the candidate once into raw lines, shared between the splice target (candidateLines, + // which must preserve exact bytes for round-trip) and the structural parse below. Splitting + // on '\n' and re-joining on '\n' round-trips the exact bytes, so known content is never + // reformatted; the parser only reads the array, so one split serves both consumers. + string[] candidateRawLines = candidateText.Split('\n'); + var candidateLines = new List(candidateRawLines); + List candidateSegments = ParseSegments(candidateRawLines); + + var insertions = new Dictionary>(); + // Whole-section appends are tracked separately from item-local insertions (metadata, + // properties). At a shared anchor — e.g. when the last item in the file both carries unknown + // @metadata and is the line we append trailing unknown sections after — the item-local + // insertions must come FIRST so the @metadata stays attached to its item; the appended + // section then follows. Reassemble emits `insertions` before `appends` at each line. + var appends = new Dictionary>(); + bool changed = false; + + foreach (Segment candidateSeg in candidateSegments) + { + if (!existingByIdentity.TryGetValue(candidateSeg.Identity, out Segment? existingSeg)) + continue; + + changed |= PreserveSegment(existingSeg, candidateSeg, candidateLines, insertions, appends); + } + + // Carry the newer minor-version stamp forward so the file does not flip-flop between minors + // as different versions open it. We only reach here when existingMinor > candidateMinor, so + // existingMinor is always >= 1; the parsed primary stamp is authoritative and the reconstructed + // fallback (used only if the primary segment somehow lacks a version line) always carries it. + if (candidatePrimary(candidateSegments) is { VersionLineIndex: >= 0 } cp) + { + candidateLines[cp.VersionLineIndex] = existingPrimary.VersionLine + ?? $"{VersionLinePrefix}{existingMajor}.{existingMinor}"; + changed = true; + } + + if (!changed) + return candidateText; + + return Reassemble(candidateLines, insertions, appends); + + static Segment? candidatePrimary(List segs) => segs.Count > 0 ? segs[0] : null; + } + + /// + /// Parses the first version= header in directly off the underlying + /// span without allocating, so the preservation fast path never splits the whole file. Blank lines, + /// leading comments, and a leading legacy hash= header are skipped (mirroring the reader); + /// the first version= line wins. Returns when the first non-blank, + /// non-comment, non-hash line is not a version line. + /// + internal static bool TryReadVersionHeader(string text, out int major, out int minor) + { + major = -1; + minor = 0; + int pos = 0; + int len = text.Length; + while (pos < len) + { + int nl = text.IndexOf('\n', pos); + int lineEnd = nl < 0 ? len : nl; + int trimmedEnd = lineEnd > pos && text[lineEnd - 1] == '\r' ? lineEnd - 1 : lineEnd; + + if (trimmedEnd > pos) + { + ReadOnlySpan line = text.AsSpan(pos, trimmedEnd - pos); + if (line.StartsWith(VersionLinePrefix.AsSpan(), StringComparison.Ordinal)) + return TryParseVersion(line, out major, out minor); + if (line.StartsWith("hash=".AsSpan(), StringComparison.Ordinal)) + { + // Legacy header: skip it and keep looking for the version line (mirrors the reader). + if (nl < 0) + break; + pos = nl + 1; + continue; + } + if (line[0] != CacheFormat.CommentChar) + return false; // first real content is not a version line + } + + if (nl < 0) + break; + pos = nl + 1; + } + + return false; + } + + /// + /// Byte-level precondition for : returns + /// only when was authored by a strictly-NEWER minor of the + /// SAME major as this writer — the one case in which would do + /// any work (see its version gate at lines ~98-114). It mirrors that gate exactly, but reads the + /// two version= headers straight off the UTF-8 bytes, so the writer can decide whether to + /// preserve without decoding the whole existing/candidate file to . + /// On the overwhelmingly common same-version change this skips two ~file-sized string + /// allocations (and the LOH traffic they cause on large caches). + /// + /// + /// Conservative by construction: any header it cannot parse, or a different major, yields + /// — matching 's own "unrecognized + /// header / different major → nothing to preserve" branch. The cache header is ASCII and both the + /// byte and char parsers apply identical blank-line / comment-skip rules, so the two parses always + /// agree for the same content. re-checks the gate itself, so even + /// an over-permissive result here only wastes the rare-path allocation; it can never drop data. + /// + internal static bool ExistingHasNewerMinor(ReadOnlySpan existingContent, ReadOnlySpan candidateContent, int currentMajor) + { + if (!TryReadVersionHeader(existingContent, out int existingMajor, out int existingMinor) + || existingMajor != currentMajor) + { + return false; + } + + int candidateMinor = 0; + if (TryReadVersionHeader(candidateContent, out int candidateMajor, out int parsedCandidateMinor) + && candidateMajor == currentMajor) + { + candidateMinor = parsedCandidateMinor; + } + + return existingMinor > candidateMinor; + } + + /// + /// Returns when the existing content was authored by an older minor of + /// the current major and is otherwise byte-for-byte identical to the candidate. + /// + /// + /// A minor version is a compatibility marker for the file's payload, not a mandatory stamp of the + /// writer binary that last evaluated the project. An existing older stamp remains valid when no + /// new data was emitted, avoiding a rewrite of every cache after an additive schema change. + /// + internal static bool MatchesExceptForOlderMinorVersion( + ReadOnlySpan existingContent, + ReadOnlySpan candidateContent, + int currentMajor) + { + if (!TryReadVersionHeader( + existingContent, + out int existingMajor, + out int existingMinor, + out int existingVersionStart, + out int existingVersionLength) + || existingMajor != currentMajor + || !TryReadVersionHeader( + candidateContent, + out int candidateMajor, + out int candidateMinor, + out int candidateVersionStart, + out int candidateVersionLength) + || candidateMajor != currentMajor + || existingMinor >= candidateMinor) + { + return false; + } + + return existingContent.Slice(0, existingVersionStart) + .SequenceEqual(candidateContent.Slice(0, candidateVersionStart)) + && existingContent.Slice(existingVersionStart + existingVersionLength) + .SequenceEqual(candidateContent.Slice(candidateVersionStart + candidateVersionLength)); + } + + /// + /// UTF-8 byte counterpart of : finds + /// the first version= header, skipping blank lines, comment lines, and a leading legacy + /// hash= header, without allocating. Returns when the first real + /// content line is not a version line. + /// + private static bool TryReadVersionHeader(ReadOnlySpan text, out int major, out int minor) + => TryReadVersionHeader(text, out major, out minor, out _, out _); + + private static bool TryReadVersionHeader( + ReadOnlySpan text, + out int major, + out int minor, + out int versionStart, + out int versionLength) + { + major = -1; + minor = 0; + versionStart = -1; + versionLength = 0; + int pos = 0; + int len = text.Length; + while (pos < len) + { + int rel = text.Slice(pos).IndexOf((byte)'\n'); + int nl = rel < 0 ? -1 : pos + rel; + int lineEnd = nl < 0 ? len : nl; + int trimmedEnd = lineEnd > pos && text[lineEnd - 1] == (byte)'\r' ? lineEnd - 1 : lineEnd; + + if (trimmedEnd > pos) + { + ReadOnlySpan line = text.Slice(pos, trimmedEnd - pos); + if (line.StartsWith(VersionLinePrefixUtf8)) + { + if (!TryParseVersion(line, out major, out minor)) + return false; + + versionStart = pos; + versionLength = trimmedEnd - pos; + return true; + } + if (line.StartsWith("hash="u8)) + { + // Legacy header: skip it and keep looking for the version line (mirrors the reader). + if (nl < 0) + break; + pos = nl + 1; + continue; + } + if (line[0] != (byte)CacheFormat.CommentChar) + return false; // first real content is not a version line + } + + if (nl < 0) + break; + pos = nl + 1; + } + + return false; + } + + private static bool TryParseVersion(ReadOnlySpan versionLine, out int major, out int minor) + { + major = -1; + minor = 0; + if (!versionLine.StartsWith(VersionLinePrefixUtf8)) + return false; + + ReadOnlySpan value = versionLine.Slice(VersionLinePrefixUtf8.Length); + int dot = value.IndexOf((byte)'.'); + ReadOnlySpan majorPart = dot >= 0 ? value.Slice(0, dot) : value; + ReadOnlySpan minorPart = dot >= 0 ? value.Slice(dot + 1) : "0"u8; + + if (!TryParseNonNegativeInt(majorPart, out major) || major <= 0) + { + major = -1; + return false; + } + + // A present-but-malformed or multi-part minor (e.g. "0.5", "3-preview") cannot be ordered as a + // plain integer. Treat it as the newest-possible minor so the preservation gate stays + // CONSERVATIVE: it runs the full preserve rather than skipping, and so never drops unknown + // data it cannot prove is older. The writer only ever emits an integer minor; this guards + // against a future/rogue writer that doesn't. The major still governs read compatibility. + if (!TryParseNonNegativeInt(minorPart, out minor)) + minor = int.MaxValue; + + return true; + } + + // netstandard2.0 lacks int.TryParse(ReadOnlySpan, ...); this zero-allocation parser keeps the + // preservation pre-check off the heap. Accepts only non-negative decimal integers. + private static bool TryParseNonNegativeInt(ReadOnlySpan span, out int value) + { + value = 0; + if (span.Length == 0) + return false; + + long acc = 0; + foreach (byte b in span) + { + if (b < (byte)'0' || b > (byte)'9') + return false; + acc = (acc * 10) + (b - '0'); + if (acc > int.MaxValue) + return false; + } + + value = (int)acc; + return true; + } + + private static ReadOnlySpan VersionLinePrefixUtf8 => "version="u8; + + private static bool PreserveSegment( + Segment existing, + Segment candidate, + List candidateLines, + Dictionary> insertions, + Dictionary> appends) + { + bool changed = false; + changed |= PreserveUnknownSections(existing, candidate, appends); + changed |= PreserveUnknownProperties(existing, candidate, candidateLines, insertions); + changed |= PreserveUnknownMetadata(existing, candidate, insertions); + return changed; + } + + // --- Unknown whole sections ------------------------------------------------------------------ + + private static bool PreserveUnknownSections(Segment existing, Segment candidate, Dictionary> appends) + { + // Preserve unknown sections near their existing known-section neighbors rather than appending + // everything to the segment end or re-sorting. Re-sorting would rewrite the newer writer's + // canonical layout and produce mixed-version flip-flop churn: a newer minor emits an additive + // section in its own order, an older minor here would move it, the newer minor moves it back, + // and so on. Round-tripping the bytes we found keeps the file stable. + var candidateSectionsByName = new Dictionary(StringComparer.Ordinal); + foreach (Section section in candidate.Sections) + { + if (section.Name is not null && !candidateSectionsByName.ContainsKey(section.Name)) + candidateSectionsByName.Add(section.Name, section); + } + + List? pendingUnknownSections = null; + bool changed = false; + foreach (Section section in existing.Sections) + { + if (section.Name is null) + continue; + + if (!KnownSections.Contains(section.Name)) + { + // Defensive: never duplicate a section the candidate somehow already has. + if (candidateSectionsByName.ContainsKey(section.Name)) + continue; + + pendingUnknownSections ??= new List(); + pendingUnknownSections.Add(string.Empty); + pendingUnknownSections.Add(CacheFormat.SectionHeader(section.Name)); + pendingUnknownSections.AddRange(section.Lines); + changed = true; + continue; + } + + if (pendingUnknownSections is not null + && candidateSectionsByName.TryGetValue(section.Name, out Section? candidateSection)) + { + AddInsertion(appends, LineBeforeSectionWithOptionalLeadingBlank(candidate, candidateSection), pendingUnknownSections); + pendingUnknownSections = null; + } + } + + if (pendingUnknownSections is not null) + { + AddInsertion(appends, candidate.LastContentLineIndex, pendingUnknownSections); + } + + return changed; + } + + private static int LineBeforeSectionWithOptionalLeadingBlank(Segment candidate, Section section) + { + int anchor = section.HeaderLineIndex == candidate.Start && candidate.Start > 0 + ? candidate.Start - 1 + : candidate.Start; + + if (candidate.VersionLineIndex >= candidate.Start && candidate.VersionLineIndex < section.HeaderLineIndex) + anchor = candidate.VersionLineIndex; + + foreach (Section candidateSection in candidate.Sections) + { + if (candidateSection.HeaderLineIndex >= section.HeaderLineIndex) + break; + + if (candidateSection.HeaderLineIndex > anchor) + anchor = candidateSection.HeaderLineIndex; + if (candidateSection.LastLineIndex > anchor && candidateSection.LastLineIndex < section.HeaderLineIndex) + anchor = candidateSection.LastLineIndex; + } + + return anchor; + } + + // --- Unknown [properties] keys --------------------------------------------------------------- + + private static bool PreserveUnknownProperties( + Segment existing, + Segment candidate, + List candidateLines, + Dictionary> insertions) + { + Section? existingProps = existing.Sections.FirstOrDefault(s => s.Name == CacheFormat.Sections.Properties); + if (existingProps is null) + return false; + + var unknown = new List(); + foreach (string line in existingProps.Lines) + { + string key = PropertyKey(line); + if (key.Length > 0 && !KnownPropertyKeys.Contains(key)) + unknown.Add(line); + } + + if (unknown.Count == 0) + return false; + + Section? candidateProps = candidate.Sections.FirstOrDefault(s => s.Name == CacheFormat.Sections.Properties); + if (candidateProps is null) + { + // Candidate has no [properties] section (rare — required properties almost always force + // one). Create one right after the [project]/[sliceDimensions] header block. + Section? anchorSection = candidate.Sections.FirstOrDefault(s => s.Name == CacheFormat.Sections.SliceDimensions) + ?? candidate.Sections.FirstOrDefault(s => s.Name == CacheFormat.Sections.Project) + ?? (candidate.Sections.Count > 0 ? candidate.Sections[candidate.Sections.Count - 1] : null); + + var block = new List { string.Empty, CacheFormat.SectionHeader(CacheFormat.Sections.Properties) }; + block.AddRange(unknown.OrderBy(PropertyKey, StringComparer.OrdinalIgnoreCase)); + + // Insert after the anchor's last content line. For a header-only anchor LastLineIndex is + // -1; fall back to its header line. With no sections at all, anchor at the segment's last + // content line. Reassemble only applies insertions keyed to a real candidate line (>= 0), + // so an insertion keyed at -1 would be silently dropped — never key there. + int anchorIndex = anchorSection is null + ? candidate.LastContentLineIndex + : anchorSection.LastLineIndex >= 0 ? anchorSection.LastLineIndex : anchorSection.HeaderLineIndex; + + AddInsertion(insertions, anchorIndex, block); + return true; + } + + // Merge each unknown key into the candidate's already-sorted [properties] block, skipping + // any the candidate already contains. + var present = new HashSet(candidateProps.Lines.Select(PropertyKey), StringComparer.OrdinalIgnoreCase); + bool changed = false; + foreach (string line in unknown) + { + string key = PropertyKey(line); + if (present.Contains(key)) + continue; + + int anchor = candidateProps.HeaderLineIndex; + for (int i = 0; i < candidateProps.Lines.Count; i++) + { + if (string.Compare(PropertyKey(candidateProps.Lines[i]), key, StringComparison.OrdinalIgnoreCase) < 0) + anchor = candidateProps.ContentLineIndices[i]; + else + break; + } + + AddInsertion(insertions, anchor, new List { line }); + present.Add(key); + changed = true; + } + + return changed; + } + + // --- Unknown item @metadata ------------------------------------------------------------------ + + private static bool PreserveUnknownMetadata(Segment existing, Segment candidate, Dictionary> insertions) + { + bool changed = false; + foreach (Section existingSection in existing.Sections) + { + if (existingSection.Name is null || !KnownSections.Contains(existingSection.Name)) + continue; + if (existingSection.Name is CacheFormat.Sections.Project + or CacheFormat.Sections.SliceDimensions + or CacheFormat.Sections.Properties) + { + continue; + } + + // Resolve "known" @metadata for THIS section. A section with no entry emits no metadata, + // so its known set is empty and every @metadata under it is unknown (and preserved). + HashSet? knownForSection = KnownMetadataBySection.TryGetValue(existingSection.Name, out HashSet? ks) + ? ks + : null; + + List existingLeaves = ExpandLeaves(existingSection); + bool anyUnknown = existingLeaves.Any(l => l.Metadata.Any(m => !IsKnownMetadata(knownForSection, m.Content))); + if (!anyUnknown) + continue; + + Section? candidateSection = candidate.Sections.FirstOrDefault(s => s.Name == existingSection.Name); + if (candidateSection is null) + continue; + + List candidateLeaves = ExpandLeaves(candidateSection); + var candidateByPath = new Dictionary(StringComparer.Ordinal); + foreach (Leaf leaf in candidateLeaves) + candidateByPath[leaf.Path] = leaf; + + foreach (Leaf existingLeaf in existingLeaves) + { + List unknownMeta = existingLeaf.Metadata + .Where(m => !IsKnownMetadata(knownForSection, m.Content)) + .ToList(); + if (unknownMeta.Count == 0) + continue; + if (!candidateByPath.TryGetValue(existingLeaf.Path, out Leaf? candidateLeaf)) + continue; // item removed from the candidate — drop its forward-compat metadata. + + var present = new HashSet( + candidateLeaf.Metadata.Select(m => MetadataKey(m.Content)), + StringComparer.OrdinalIgnoreCase); + + string indent = new string(' ', candidateLeaf.Indent + 1); + var toInsert = new List(); + // Preserve unknown @metadata in the existing file's encounter order — a newer writer + // emits an item's metadata in its own schema order, and re-sorting here would rewrite + // that layout and churn the cache in mixed-version teams. Dedup by key is order-free. + foreach (MetaLine meta in unknownMeta) + { + if (present.Add(MetadataKey(meta.Content))) + toInsert.Add(indent + meta.Content); + } + + if (toInsert.Count == 0) + continue; + + int anchor = candidateLeaf.Metadata.Count > 0 + ? candidateLeaf.Metadata[candidateLeaf.Metadata.Count - 1].LineIndex + : candidateLeaf.LineIndex; + AddInsertion(insertions, anchor, toInsert); + changed = true; + } + } + + return changed; + } + + // --- Parsing --------------------------------------------------------------------------------- + + private static List ParseSegments(string text) => ParseSegments(text.Split('\n')); + + // Overload that reuses an already-split line array, so a caller that also needs the raw lines + // (e.g. the splice target) does not pay for a second Split of the same text. The parser trims + // lines internally for its own decisions but never mutates the array, so sharing it is safe. + private static List ParseSegments(string[] lines) + { + var segments = new List(); + int segStart = 0; + for (int i = 0; i <= lines.Length; i++) + { + bool atSeparator = i < lines.Length && Trim(lines[i]) == CacheFormat.SliceSeparator; + bool atEnd = i == lines.Length; + if (!atSeparator && !atEnd) + continue; + + segments.Add(ParseSegment(lines, segStart, i)); + segStart = i + 1; + } + + return segments; + } + + private static Segment ParseSegment(string[] lines, int start, int end) + { + var segment = new Segment { Start = start, End = end }; + Section? current = null; + var sliceDimensionLines = new List(); + + for (int i = start; i < end; i++) + { + string raw = Trim(lines[i]); + if (raw.Length == 0) + continue; + + if (raw.StartsWith(VersionLinePrefix, StringComparison.Ordinal)) + { + segment.VersionLine = raw; + segment.VersionLineIndex = i; + continue; + } + + if (raw[0] == CacheFormat.CommentChar) + continue; + + if (raw.Length >= 2 && raw[0] == '[' && raw[raw.Length - 1] == ']') + { + string name = raw.Substring(1, raw.Length - 2); + current = new Section { Name = name, HeaderLineIndex = i }; + segment.Sections.Add(current); + continue; + } + + if (segment.LastContentLineIndex < i) + segment.LastContentLineIndex = i; + + if (current is null) + continue; + + current.Lines.Add(raw); + current.ContentLineIndices.Add(i); + current.LastLineIndex = i; + + if (current.Name == CacheFormat.Sections.SliceDimensions) + sliceDimensionLines.Add(raw); + } + + segment.Identity = sliceDimensionLines.Count == 0 + ? SharedSegmentIdentity + : string.Join("\n", sliceDimensionLines.OrderBy(l => l, StringComparer.OrdinalIgnoreCase)); + + if (segment.LastContentLineIndex < start) + segment.LastContentLineIndex = Math.Max(start, end - 1); + + return segment; + } + + /// + /// Expands a path section's indentation-compressed lines into leaves, recording each leaf's full + /// path, its line index, indentation, and the @metadata lines attached to it. Mirrors the + /// reader's expansion so existing/candidate leaves match by full path regardless of how sibling + /// changes alter compression. + /// + private static List ExpandLeaves(Section section) + { + var leaves = new List(); + var prefixStack = new Stack<(int Indent, string Prefix)>(); + Leaf? lastLeaf = null; + + for (int i = 0; i < section.Lines.Count; i++) + { + string line = section.Lines[i]; + int globalIndex = section.ContentLineIndices[i]; + int indent = CountIndent(line); + string content = line.Substring(indent); + + if (content.Length > 0 && content[0] == '@') + { + lastLeaf?.Metadata.Add(new MetaLine(content, globalIndex)); + continue; + } + + while (prefixStack.Count > 0 && prefixStack.Peek().Indent >= indent) + prefixStack.Pop(); + + string prefix = prefixStack.Count > 0 ? prefixStack.Peek().Prefix : string.Empty; + + if (content.Length > 0 && content[content.Length - 1] == '/') + { + prefixStack.Push((indent, prefix + content)); + lastLeaf = null; + } + else + { + lastLeaf = new Leaf(prefix + content, globalIndex, indent); + leaves.Add(lastLeaf); + } + } + + return leaves; + } + + // --- Reassembly ------------------------------------------------------------------------------ + + private static string Reassemble( + List candidateLines, + Dictionary> insertions, + Dictionary> appends) + { + int extraCount = insertions.Sum(kvp => kvp.Value.Count) + appends.Sum(kvp => kvp.Value.Count); + var output = new List(candidateLines.Count + extraCount); + for (int i = 0; i < candidateLines.Count; i++) + { + output.Add(candidateLines[i]); + // Item-local insertions (metadata, properties) first, then whole-section appends, so a + // section appended at the same anchor as the last item's @metadata never gets between the + // item and its metadata. + if (insertions.TryGetValue(i, out List? extra)) + output.AddRange(extra); + if (appends.TryGetValue(i, out List? appended)) + output.AddRange(appended); + } + + return string.Join("\n", output); + } + + private static void AddInsertion(Dictionary> insertions, int afterLineIndex, List lines) + { + if (!insertions.TryGetValue(afterLineIndex, out List? existing)) + { + existing = new List(); + insertions[afterLineIndex] = existing; + } + + existing.AddRange(lines); + } + + // --- Small helpers --------------------------------------------------------------------------- + + internal static bool TryParseVersion(string versionLine, out int major, out int minor) + { + major = -1; + minor = 0; + return versionLine is not null && TryParseVersion(versionLine.AsSpan(), out major, out minor); + } + + private static bool TryParseVersion(ReadOnlySpan versionLine, out int major, out int minor) + { + major = -1; + minor = 0; + if (!versionLine.StartsWith(VersionLinePrefix.AsSpan(), StringComparison.Ordinal)) + return false; + + ReadOnlySpan value = versionLine.Slice(VersionLinePrefix.Length); + int dot = value.IndexOf('.'); + ReadOnlySpan majorPart = dot >= 0 ? value.Slice(0, dot) : value; + ReadOnlySpan minorPart = dot >= 0 ? value.Slice(dot + 1) : "0".AsSpan(); + + if (!TryParseNonNegativeInt(majorPart, out major) || major <= 0) + { + major = -1; + return false; + } + + // A present-but-malformed or multi-part minor (e.g. "0.5", "3-preview") cannot be ordered as a + // plain integer. Treat it as the newest-possible minor so the preservation gate stays + // CONSERVATIVE: it runs the full preserve rather than skipping, and so never drops unknown + // data it cannot prove is older. The writer only ever emits an integer minor; this guards + // against a future/rogue writer that doesn't. The major still governs read compatibility. + if (!TryParseNonNegativeInt(minorPart, out minor)) + minor = int.MaxValue; + + return true; + } + + // netstandard2.0 lacks int.TryParse(ReadOnlySpan<char>, out int); this zero-allocation parser + // keeps the preservation fast path off the heap. Accepts only non-negative decimal integers. + private static bool TryParseNonNegativeInt(ReadOnlySpan span, out int value) + { + value = 0; + if (span.Length == 0) + return false; + + long acc = 0; + foreach (char c in span) + { + if (c < '0' || c > '9') + return false; + acc = (acc * 10) + (c - '0'); + if (acc > int.MaxValue) + return false; + } + + value = (int)acc; + return true; + } + + private static string Trim(string line) => line.Length > 0 && line[line.Length - 1] == '\r' ? line.Substring(0, line.Length - 1) : line; + + private static string PropertyKey(string line) + { + int eq = line.IndexOf('='); + return eq < 0 ? line : line.Substring(0, eq); + } + + private static string MetadataKey(string content) + { + // content starts with '@'. + string body = content.Substring(1); + int eq = body.IndexOf('='); + return eq < 0 ? body : body.Substring(0, eq); + } + + // A @metadata line is "known" only when the section that contains it actually emits that key. + // A null set means the section emits no metadata at all, so nothing under it is known. + private static bool IsKnownMetadata(HashSet? knownForSection, string content) + => knownForSection is not null && knownForSection.Contains(MetadataKey(content)); + + private static int CountIndent(string line) + { + int i = 0; + while (i < line.Length && line[i] == ' ') + i++; + return i; + } + + private sealed class Segment + { + public int Start { get; set; } + public int End { get; set; } + public string Identity { get; set; } = SharedSegmentIdentity; + public string? VersionLine { get; set; } + public int VersionLineIndex { get; set; } = -1; + public List
Sections { get; } = new List
(); + + // Index of the last non-blank line in the segment — the anchor for appending whole sections. + public int LastContentLineIndex { get; set; } = -1; + } + + private sealed class Section + { + public string? Name { get; set; } + public int HeaderLineIndex { get; set; } = -1; + public List Lines { get; } = new List(); + public List ContentLineIndices { get; } = new List(); + public int LastLineIndex { get; set; } = -1; + } + + private sealed class Leaf + { + public Leaf(string path, int lineIndex, int indent) + { + this.Path = path; + this.LineIndex = lineIndex; + this.Indent = indent; + } + + public string Path { get; } + public int LineIndex { get; } + public int Indent { get; } + public List Metadata { get; } = new List(); + } + + private readonly struct MetaLine + { + public MetaLine(string content, int lineIndex) + { + this.Content = content; + this.LineIndex = lineIndex; + } + + public string Content { get; } + public int LineIndex { get; } + } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/MergeProjectDataSlicesTask.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/MergeProjectDataSlicesTask.cs new file mode 100644 index 0000000000000..98e229413545e --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/MergeProjectDataSlicesTask.cs @@ -0,0 +1,218 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; +using Microsoft.NET.ProjectData; + +namespace Microsoft.NET.ProjectData.Tasks; + +/// +/// Thin MSBuild wrapper around : runs once on the +/// outer multi-TFM build (after DispatchToInnerBuilds), collects all slice +/// files matching , merges them into +/// with structural deduplication, and swallows any exception so a merge fault +/// never fails the build. +/// +public sealed class MergeProjectDataSlicesTask : Microsoft.Build.Utilities.Task +{ + /// + /// Absolute path of the merged cache file. When empty, the user-folder cache + /// path is computed from via + /// . + /// + public string OutputPath { get; set; } = string.Empty; + + public string SliceGlob { get; set; } = string.Empty; + + public ITaskItem[]? SliceFiles { get; set; } + + public bool PreserveExistingSlices { get; set; } + + /// + /// Semicolon-separated TargetFrameworks list from the outer build, used + /// to select the primary slice for merged multi-TFM cache files. + /// + public string TargetFrameworks { get; set; } = string.Empty; + + /// + /// Required when is empty so the user-folder layout + /// can be derived for this project. + /// + public string ProjectFilePath { get; set; } = string.Empty; + + /// + /// Project intermediate output directory ($(IntermediateOutputPath)). Transient + /// .tmp side-files for the atomic write are placed here (when on the same volume as the + /// output) so they never appear next to committed source. Optional; falls back to the output + /// directory. + /// + public string IntermediateOutputPath { get; set; } = string.Empty; + + /// + /// Optional override for the repo-scoped donor index path. When empty, the task resolves + /// <git-common-dir>\dotnet-projectdata\lscache-donor-index.json from . + /// + public string DonorCacheIndexPath { get; set; } = string.Empty; + + /// + /// Optional override for the logical workspace root recorded in the donor index. + /// + public string DonorCacheWorkspaceRoot { get; set; } = string.Empty; + + [Output] + public bool Succeeded { get; set; } + + [Output] + public bool FoundSlices { get; set; } + + [Output] + public string ResolvedOutputPath { get; set; } = string.Empty; + + public override bool Execute() + { + if (string.IsNullOrEmpty(this.OutputPath) && string.IsNullOrEmpty(this.ProjectFilePath)) + { + // Misconfigured task invocation — neither input is supplied so we cannot + // determine where to write. Fail loud (Error) rather than swallow into the + // catch-all below, which is reserved for runtime IO/parse failures. + this.Log.LogError( + "MergeProjectDataSlicesTask requires either OutputPath or ProjectFilePath to be specified."); + return false; + } + + try + { + this.Succeeded = false; + this.FoundSlices = false; + this.ResolvedOutputPath = this.ResolveOutputPath(); + + int count; + if (this.SliceFiles is { Length: > 0 }) + { + string[] sliceFiles = this.SliceFiles + .Select(item => item.ItemSpec) + .Where(path => !string.IsNullOrWhiteSpace(path)) + .ToArray(); + string[] existing = sliceFiles.Where(File.Exists).ToArray(); + string[] missing = sliceFiles.Except(existing, StringComparer.OrdinalIgnoreCase).ToArray(); + if (missing.Length > 0) + { + this.Log.LogMessage(MessageImportance.Low, + "ProjectData: skipping missing slices for {0}: {1}", + this.ProjectFilePath, + string.Join(";", missing)); + } + + if (existing.Length == 0) + { + this.DeleteOutputPathIfNotProjectFolder(); + return true; + } + + this.FoundSlices = true; + count = ProjectDataMerger.Merge(this.ResolvedOutputPath, existing, this.TargetFrameworks, this.PreserveExistingSlices, this.IntermediateOutputPath); + } + else + { + this.FoundSlices = true; + string[] sliceFiles = ProjectDataMerger.FindSlices(this.SliceGlob).ToArray(); + if (sliceFiles.Length == 0) + { + this.FoundSlices = false; + count = 0; + } + else + { + count = ProjectDataMerger.Merge(this.ResolvedOutputPath, sliceFiles, this.TargetFrameworks, this.PreserveExistingSlices, this.IntermediateOutputPath); + } + } + + if (count == 0) + { + this.DeleteOutputPathIfNotProjectFolder(); + this.Log.LogMessage(MessageImportance.Low, "ProjectData: no slice files found matching {0}; skipping merge.", this.SliceGlob); + } + else + { + this.Succeeded = true; + UnsupportedProjectDataMarker.Delete(this.ProjectFilePath); + this.RecordDonorIndexEntry(); + this.Log.LogMessage(MessageImportance.Low, "ProjectData: merged {0} slices into {1}.", count, this.ResolvedOutputPath); + } + } + catch (Exception ex) + { + // Cache-write failures should not break the user's build, but they must be + // visible at default verbosity so the user knows the cache might be stale. + // ``LogMessage(Low)`` was invisible under ``-v:minimal`` (the default for + // ``dotnet build``) and hid real diagnostics; ``LogWarning`` matches the + // .NET SDK convention for non-fatal task failures. Catch all exception + // types — narrowing previously caused legitimate DTB scenarios (e.g. a + // ``KeyNotFoundException`` from a missing slice property) to fail the + // build instead of degrading to a stale-cache warning. + this.Log.LogWarning( + "ProjectData: failed to merge slices for {0}: {1}", + string.IsNullOrEmpty(this.OutputPath) ? this.ProjectFilePath : this.OutputPath, + ex.Message); + } + return true; + } + + private string ResolveOutputPath() + { + if (!string.IsNullOrEmpty(this.OutputPath)) + { + return this.OutputPath; + } + if (string.IsNullOrEmpty(this.ProjectFilePath)) + { + throw new InvalidOperationException( + "MergeProjectDataSlicesTask: either OutputPath or ProjectFilePath must be supplied."); + } + return UserFolderCachePath.Compute(this.ProjectFilePath); + } + + private void RecordDonorIndexEntry() + { + ProjectDataDonorWriteOptions options = new() + { + IndexPath = string.IsNullOrEmpty(this.DonorCacheIndexPath) ? null : this.DonorCacheIndexPath, + WorkspaceRoot = string.IsNullOrEmpty(this.DonorCacheWorkspaceRoot) ? null : this.DonorCacheWorkspaceRoot, + }; + + bool recorded = ProjectDataDonorIndex.TryRecordWrite(this.ProjectFilePath, this.ResolvedOutputPath, options, out string? message); + if (recorded && !string.IsNullOrEmpty(message)) + { + this.Log.LogMessage(MessageImportance.Low, "ProjectData: {0}", message); + } + else if (!recorded && !string.IsNullOrEmpty(message)) + { + this.Log.LogMessage(MessageImportance.Low, "ProjectData: failed to update donor index for {0}: {1}", this.ProjectFilePath, message); + } + } + + private void DeleteOutputPathIfNotProjectFolder() + { + if (string.IsNullOrEmpty(this.ResolvedOutputPath) || + PathsEqual(this.ResolvedOutputPath, Path.GetFullPath(this.ProjectFilePath) + ".lscache")) + { + return; + } + + try + { + File.Delete(this.ResolvedOutputPath); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + this.Log.LogWarning("ProjectData: failed to delete stale output {0}: {1}", this.ResolvedOutputPath, ex.Message); + } + } + + private static bool PathsEqual(string left, string right) + => string.Equals( + Path.GetFullPath(left), + Path.GetFullPath(right), + StringComparisons.Paths); +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/Microsoft.NET.ProjectData.Tasks.csproj b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/Microsoft.NET.ProjectData.Tasks.csproj new file mode 100644 index 0000000000000..7b877aabffc15 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/Microsoft.NET.ProjectData.Tasks.csproj @@ -0,0 +1,59 @@ + + + + netstandard2.0 + + false + false + Microsoft.NET.ProjectData.Tasks + + true + false + + $(NoWarn);IDE0051;IDE0060 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/PooledCacheRender.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/PooledCacheRender.cs new file mode 100644 index 0000000000000..7455cdc7dd194 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/PooledCacheRender.cs @@ -0,0 +1,145 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Buffers; +using System.Text; + +namespace Microsoft.NET.ProjectData.Tasks; + +/// +/// A that renders cache content into pooled and +/// buffers, normalizing line endings in place, so the writer never allocates +/// the candidate as a or a heap array on the common +/// path. The candidate string is produced lazily via only when a newer +/// minor version's data has to be spliced in. Always use with using so both pooled +/// buffers are returned. +/// +internal sealed class PooledCacheRender : TextWriter +{ + private static readonly char[] NewLineChars = { '\n' }; + + private char[] _chars; + private int _charLength; + private byte[]? _bytes; + private int _byteLength; + + private PooledCacheRender(int initialCapacity) + { + this._chars = ArrayPool.Shared.Rent(initialCapacity); + this.CoreNewLine = NewLineChars; + } + + public override Encoding Encoding => ProjectDataWriter.Utf8NoBom; + + public byte[] Bytes => this._bytes!; + + public int ByteLength => this._byteLength; + + public static PooledCacheRender Create(Action writeContent) + { + var render = new PooledCacheRender(initialCapacity: 4096); + try + { + writeContent(render); + render.Finish(); + return render; + } + catch + { + render.Dispose(); + throw; + } + } + + /// Materializes the rendered content as a string (rare splice path only). + public string GetText() => new string(this._chars, 0, this._charLength); + + public override void Write(char value) + { + this.EnsureCapacity(this._charLength + 1); + this._chars[this._charLength++] = value; + } + + public override void Write(string? value) + { + if (string.IsNullOrEmpty(value)) + return; + + this.EnsureCapacity(this._charLength + value!.Length); + value.CopyTo(0, this._chars, this._charLength, value.Length); + this._charLength += value.Length; + } + + public override void Write(char[] buffer, int index, int count) + { + if (count <= 0) + return; + + this.EnsureCapacity(this._charLength + count); + Array.Copy(buffer, index, this._chars, this._charLength, count); + this._charLength += count; + } + + /// + /// Normalizes line endings in in place (\r\n and lone + /// \r both collapse to \n), returning the new logical length. Mirrors + /// ProjectDataWriter.NormalizeLineEndings(string) exactly but without allocating. + /// + private static int NormalizeNewLinesInPlace(char[] buffer, int length) + { + int firstCr = Array.IndexOf(buffer, '\r', 0, length); + if (firstCr < 0) + return length; + + int write = firstCr; + for (int read = firstCr; read < length; read++) + { + char c = buffer[read]; + if (c == '\r') + { + buffer[write++] = '\n'; + if (read + 1 < length && buffer[read + 1] == '\n') + read++; // collapse the "\n" half of a "\r\n" pair + } + else + { + buffer[write++] = c; + } + } + + return write; + } + + private void Finish() + { + this._charLength = NormalizeNewLinesInPlace(this._chars, this._charLength); + int byteCount = ProjectDataWriter.Utf8NoBom.GetByteCount(this._chars, 0, this._charLength); + this._bytes = ArrayPool.Shared.Rent(byteCount); + this._byteLength = ProjectDataWriter.Utf8NoBom.GetBytes(this._chars, 0, this._charLength, this._bytes, 0); + } + + private void EnsureCapacity(int required) + { + if (required <= this._chars.Length) + return; + + int newSize = Math.Max(required, this._chars.Length * 2); + char[] larger = ArrayPool.Shared.Rent(newSize); + Array.Copy(this._chars, 0, larger, 0, this._charLength); + ArrayPool.Shared.Return(this._chars); + this._chars = larger; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + ArrayPool.Shared.Return(this._chars); + if (this._bytes != null) + ArrayPool.Shared.Return(this._bytes); + } + + base.Dispose(disposing); + } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/ProjectDataBuildCompletionLogger.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/ProjectDataBuildCompletionLogger.cs new file mode 100644 index 0000000000000..adba10be30d2c --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/ProjectDataBuildCompletionLogger.cs @@ -0,0 +1,535 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text; +using System.Text.Json; +using Microsoft.Build.Framework; +using Microsoft.NET.ProjectData; + +namespace Microsoft.NET.ProjectData.Tasks; + +/// +/// Central MSBuild logger that records structured aggregate ProjectDataBuild evidence. +/// +public sealed class ProjectDataBuildCompletionLogger : ILogger +{ + private const int MaxDiagnosticsPerProject = 5; + private const int MaxDiagnostics = 200; + private const int MaxSubmissions = 1024; + private const int MaxContexts = 20_000; + + private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + private static readonly JsonSerializerOptions SerializerOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = false, + }; + + private readonly object gate = new(); + private readonly List submissions = []; + private readonly List contexts = []; + private readonly List diagnostics = []; + private readonly Dictionary projectByContext = new(StringComparer.Ordinal); + private readonly Dictionary phaseBySubmission = []; + private readonly Dictionary diagnosticCountByProject = new(StringComparer.OrdinalIgnoreCase); + + private string receiptDirectory = string.Empty; + private string attemptId = string.Empty; + private string latestPhase = "Unknown"; + private bool initialized; + private bool buildFinished; + private bool buildSucceeded; + private bool buildCancelled; + private bool projectDataBuildSubmissionObserved; + private int truncatedDiagnosticCount; + private int truncatedSubmissionCount; + private int truncatedContextCount; + private string completedUtc = string.Empty; + private IEventSource? eventSource; + + public LoggerVerbosity Verbosity { get; set; } = LoggerVerbosity.Quiet; + + public string? Parameters { get; set; } + + public void Initialize(IEventSource eventSource) + { + try + { + if (eventSource is null) + { + throw new ArgumentNullException(nameof(eventSource)); + } + if (!TryParseParameters(this.Parameters, out this.receiptDirectory, out this.attemptId)) + { + TryWriteLoggerError("ProjectDataBuild completion logger parameters were invalid."); + return; + } + + this.initialized = true; + this.eventSource = eventSource; + eventSource.AnyEventRaised += this.OnAnyEventRaised; + } + catch (Exception ex) + { + TryWriteLoggerError($"ProjectDataBuild completion logger failed to initialize: {ex.Message}"); + } + } + + public void Shutdown() + { + try + { + if (this.initialized && this.buildCancelled && !this.buildFinished) + { + this.WriteEvidence(); + } + if (this.eventSource is not null) + { + this.eventSource.AnyEventRaised -= this.OnAnyEventRaised; + this.eventSource = null; + } + } + catch (Exception ex) + { + TryWriteLoggerError($"ProjectDataBuild completion logger failed during shutdown: {ex.Message}"); + } + } + + private void OnAnyEventRaised(object sender, BuildEventArgs e) + { + _ = sender; + try + { + lock (this.gate) + { + switch (e) + { + case BuildSubmissionStartedEventArgs submission: + this.RecordSubmission(submission); + break; + case ProjectStartedEventArgs projectStarted: + this.RecordProjectContext(projectStarted); + break; + case ProjectEvaluationStartedEventArgs evaluationStarted: + this.RecordEvaluationContext(evaluationStarted); + break; + case BuildErrorEventArgs error: + this.RecordDiagnostic( + severity: "Error", + error.ProjectFile, + error.File, + error.Code, + error.Message, + error.LineNumber, + error.ColumnNumber, + error.BuildEventContext, + emitFrame: true); + break; + case BuildWarningEventArgs warning: + this.RecordDiagnostic( + severity: "Warning", + warning.ProjectFile, + warning.File, + warning.Code, + warning.Message, + warning.LineNumber, + warning.ColumnNumber, + warning.BuildEventContext, + emitFrame: false); + break; + case BuildCanceledEventArgs: + this.buildCancelled = true; + this.completedUtc = DateTimeOffset.UtcNow.ToString("O"); + this.WriteEvidence(); + break; + case BuildFinishedEventArgs finished: + this.buildFinished = true; + this.buildSucceeded = finished.Succeeded; + this.completedUtc = finished.Timestamp.ToUniversalTime().ToString("O"); + this.WriteEvidence(); + break; + } + } + } + catch (Exception ex) + { + TryWriteLoggerError($"ProjectDataBuild completion logger ignored an event failure: {ex.Message}"); + } + } + + private void RecordSubmission(BuildSubmissionStartedEventArgs submission) + { + string[] targetNames = submission.TargetNames?.Where(static target => !string.IsNullOrWhiteSpace(target)).ToArray() ?? []; + bool isRestoring = TryGetBooleanGlobalProperty(submission.GlobalProperties, "MSBuildIsRestoring"); + string phase = ClassifyPhase(isRestoring, targetNames); + this.latestPhase = phase; + this.projectDataBuildSubmissionObserved |= string.Equals(phase, "ProjectDataBuild", StringComparison.Ordinal); + if (this.submissions.Count >= MaxSubmissions) + { + this.truncatedSubmissionCount++; + return; + } + + this.phaseBySubmission[submission.SubmissionId] = phase; + this.submissions.Add(new ProjectDataBuildSubmissionRecord + { + SubmissionId = submission.SubmissionId, + Phase = phase, + MSBuildIsRestoring = isRestoring, + EntryProjects = submission.EntryProjectsFullPath?.Where(static path => !string.IsNullOrWhiteSpace(path)).ToArray() ?? [], + TargetNames = targetNames, + Context = ConvertContext(submission.BuildEventContext), + }); + } + + private void RecordProjectContext(ProjectStartedEventArgs projectStarted) + { + if (this.contexts.Count >= MaxContexts) + { + this.truncatedContextCount++; + return; + } + + string projectFile = projectStarted.ProjectFile ?? string.Empty; + string contextKey = GetContextKey(projectStarted.BuildEventContext); + if (contextKey.Length > 0 && projectFile.Length > 0) + { + this.projectByContext[contextKey] = projectFile; + } + + this.contexts.Add(new ProjectDataBuildContextRecord + { + Kind = "Project", + ProjectFilePath = projectFile, + Context = ConvertContext(projectStarted.BuildEventContext), + ParentContext = ConvertContext(projectStarted.ParentProjectBuildEventContext), + }); + } + + private void RecordEvaluationContext(ProjectEvaluationStartedEventArgs evaluationStarted) + { + if (this.contexts.Count >= MaxContexts) + { + this.truncatedContextCount++; + return; + } + + string projectFile = evaluationStarted.ProjectFile ?? string.Empty; + string contextKey = GetContextKey(evaluationStarted.BuildEventContext); + if (contextKey.Length > 0 && projectFile.Length > 0) + { + this.projectByContext[contextKey] = projectFile; + } + + this.contexts.Add(new ProjectDataBuildContextRecord + { + Kind = "Evaluation", + ProjectFilePath = projectFile, + Context = ConvertContext(evaluationStarted.BuildEventContext), + }); + } + + private void RecordDiagnostic( + string severity, + string? projectFile, + string? file, + string? code, + string? message, + int line, + int column, + BuildEventContext? context, + bool emitFrame) + { + string resolvedProjectFile; + string projectFilePathSource; + if (IsProjectFilePath(file)) + { + resolvedProjectFile = file!; + projectFilePathSource = ProjectDataBuildDiagnosticRecord.FileProjectPathSource; + } + else if (IsProjectFilePath(projectFile)) + { + resolvedProjectFile = projectFile!; + projectFilePathSource = ProjectDataBuildDiagnosticRecord.ProjectFileProjectPathSource; + } + else if (this.projectByContext.TryGetValue(GetContextKey(context), out string? contextProjectFile)) + { + resolvedProjectFile = contextProjectFile; + projectFilePathSource = ProjectDataBuildDiagnosticRecord.ContextProjectPathSource; + } + else + { + resolvedProjectFile = projectFile ?? string.Empty; + projectFilePathSource = ProjectDataBuildDiagnosticRecord.UnknownProjectPathSource; + } + + string diagnosticKey = resolvedProjectFile.Length == 0 ? "" : resolvedProjectFile; + this.diagnosticCountByProject.TryGetValue(diagnosticKey, out int projectDiagnosticCount); + bool globalCapReached = this.diagnostics.Count >= MaxDiagnostics; + bool projectCapReached = projectDiagnosticCount >= MaxDiagnosticsPerProject; + int replacementIndex = -1; + if (globalCapReached || projectCapReached) + { + if (string.Equals(severity, "Error", StringComparison.OrdinalIgnoreCase)) + { + if (projectCapReached) + { + replacementIndex = this.diagnostics.FindLastIndex(existing => + string.Equals(existing.Severity, "Warning", StringComparison.OrdinalIgnoreCase) && + string.Equals( + string.IsNullOrEmpty(existing.ProjectFilePath) ? "" : existing.ProjectFilePath, + diagnosticKey, + StringComparison.OrdinalIgnoreCase)); + } + + if (replacementIndex < 0 && globalCapReached && !projectCapReached) + { + replacementIndex = this.diagnostics.FindLastIndex(static existing => + string.Equals(existing.Severity, "Warning", StringComparison.OrdinalIgnoreCase)); + } + } + + this.truncatedDiagnosticCount++; + if (replacementIndex < 0) + { + return; + } + } + + ProjectDataBuildDiagnosticRecord diagnostic = new() + { + Severity = severity, + Phase = this.GetPhase(context), + ProjectFilePath = resolvedProjectFile, + ProjectFilePathSource = projectFilePathSource, + FilePath = file ?? string.Empty, + Code = code ?? string.Empty, + Message = message ?? string.Empty, + Line = line, + Column = column, + Context = ConvertContext(context), + }; + + if (replacementIndex >= 0) + { + ProjectDataBuildDiagnosticRecord replaced = this.diagnostics[replacementIndex]; + string replacedKey = string.IsNullOrEmpty(replaced.ProjectFilePath) ? "" : replaced.ProjectFilePath; + this.DecrementDiagnosticCount(replacedKey); + this.diagnostics[replacementIndex] = diagnostic; + this.diagnosticCountByProject.TryGetValue(diagnosticKey, out int replacementProjectDiagnosticCount); + this.diagnosticCountByProject[diagnosticKey] = replacementProjectDiagnosticCount + 1; + } + else + { + this.diagnosticCountByProject[diagnosticKey] = projectDiagnosticCount + 1; + this.diagnostics.Add(diagnostic); + } + + if (emitFrame) + { + this.EmitProvisionalDiagnostic(diagnostic); + } + } + + private void EmitProvisionalDiagnostic(ProjectDataBuildDiagnosticRecord diagnostic) + { + try + { + Console.Error.WriteLine(ProjectDataBuildDiagnosticProtocol.Encode(this.attemptId, diagnostic)); + } + catch (Exception ex) + { + TryWriteLoggerError($"ProjectDataBuild completion logger failed to emit a provisional diagnostic: {ex.Message}"); + } + } + + private void DecrementDiagnosticCount(string diagnosticKey) + { + if (!this.diagnosticCountByProject.TryGetValue(diagnosticKey, out int count) || count <= 1) + { + this.diagnosticCountByProject.Remove(diagnosticKey); + return; + } + + this.diagnosticCountByProject[diagnosticKey] = count - 1; + } + + private string GetPhase(BuildEventContext? context) + { + if (context is not null && this.phaseBySubmission.TryGetValue(context.SubmissionId, out string? phase)) + { + return phase; + } + + return this.latestPhase; + } + + private void WriteEvidence() + { + if (!this.initialized) + { + return; + } + + try + { + Directory.CreateDirectory(this.receiptDirectory); + ProjectDataBuildAttemptManifest manifest = new() + { + AttemptId = this.attemptId, + BuildFinished = this.buildFinished, + BuildSucceeded = this.buildSucceeded, + BuildCancelled = this.buildCancelled, + ProjectDataBuildSubmissionObserved = this.projectDataBuildSubmissionObserved, + CompletedUtc = this.completedUtc, + TruncatedDiagnosticCount = this.truncatedDiagnosticCount, + TruncatedSubmissionCount = this.truncatedSubmissionCount, + TruncatedContextCount = this.truncatedContextCount, + Submissions = [.. this.submissions], + Contexts = [.. this.contexts], + Diagnostics = [.. this.diagnostics], + }; + string manifestPath = ProjectDataBuildAttemptManifest.GetManifestFilePath(this.receiptDirectory); + WriteJsonAtomically(manifestPath, JsonSerializer.Serialize(manifest, SerializerOptions)); + ProjectDataBuildReceipt.WriteAggregateCompletion(this.receiptDirectory, this.attemptId); + } + catch (Exception ex) + { + TryWriteLoggerError($"ProjectDataBuild completion logger failed to write evidence: {ex.Message}"); + } + } + + private static bool TryParseParameters(string? parameters, out string receiptDirectory, out string attemptId) + { + receiptDirectory = string.Empty; + attemptId = string.Empty; + if (string.IsNullOrWhiteSpace(parameters)) + { + return false; + } + + string[] parts = parameters!.Split(';'); + if (parts.Length != 2) + { + return false; + } + + try + { + receiptDirectory = Utf8NoBom.GetString(Convert.FromBase64String(parts[0])); + attemptId = parts[1]; + return receiptDirectory.Length > 0 && attemptId.Length > 0; + } + catch (FormatException) + { + return false; + } + } + + private static string ClassifyPhase(bool isRestoring, IReadOnlyList targetNames) + { + if (isRestoring || targetNames.Any(static target => string.Equals(target, "Restore", StringComparison.OrdinalIgnoreCase))) + { + return "Restore"; + } + + if (targetNames.Any(static target => string.Equals(target, "ProjectDataBuild", StringComparison.OrdinalIgnoreCase))) + { + return "ProjectDataBuild"; + } + + return "Unknown"; + } + + private static bool TryGetBooleanGlobalProperty(IReadOnlyDictionary? properties, string name) + { + if (properties is null) + { + return false; + } + + if (!properties.TryGetValue(name, out string? value)) + { + value = properties + .FirstOrDefault(pair => string.Equals(pair.Key, name, StringComparison.OrdinalIgnoreCase)) + .Value; + } + + return bool.TryParse(value, out bool result) && result; + } + + private static ProjectDataBuildEventContextRecord? ConvertContext(BuildEventContext? context) + => context is null + ? null + : new ProjectDataBuildEventContextRecord + { + NodeId = context.NodeId, + ProjectContextId = context.ProjectContextId, + ProjectInstanceId = context.ProjectInstanceId, + TargetId = context.TargetId, + TaskId = context.TaskId, + SubmissionId = context.SubmissionId, + EvaluationId = context.EvaluationId, + BuildRequestId = context.BuildRequestId, + }; + + private static string GetContextKey(BuildEventContext? context) + => context is null + ? string.Empty + : $"{context.NodeId}:{context.ProjectContextId}:{context.ProjectInstanceId}:{context.SubmissionId}:{context.EvaluationId}:{context.BuildRequestId}"; + + private static bool IsProjectFilePath(string? path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return false; + } + + string extension = Path.GetExtension(path); + return extension.Equals(".csproj", StringComparison.OrdinalIgnoreCase) || + extension.Equals(".vbproj", StringComparison.OrdinalIgnoreCase) || + extension.Equals(".fsproj", StringComparison.OrdinalIgnoreCase) || + extension.Equals(".vcxproj", StringComparison.OrdinalIgnoreCase) || + extension.Equals(".esproj", StringComparison.OrdinalIgnoreCase) || + extension.Equals(".proj", StringComparison.OrdinalIgnoreCase); + } + + private static void WriteJsonAtomically(string path, string content) + { + string tempPath = path + "." + Guid.NewGuid().ToString("N") + ".tmp"; + try + { + File.WriteAllText(tempPath, content, Utf8NoBom); + if (File.Exists(path)) + { + File.Replace(tempPath, path, destinationBackupFileName: null); + } + else + { + File.Move(tempPath, path); + } + } + finally + { + try + { + File.Delete(tempPath); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + } + } + } + + private static void TryWriteLoggerError(string message) + { + try + { + Console.Error.WriteLine(message); + } + catch + { + } + } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/ProjectDataMerger.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/ProjectDataMerger.cs new file mode 100644 index 0000000000000..a2050e66de285 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/ProjectDataMerger.cs @@ -0,0 +1,644 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text; + +namespace Microsoft.NET.ProjectData.Tasks; + +/// +/// Reads all per-TFM slice files for a multi-targeting project, parses them into +/// structured sections, deduplicates content shared across TFMs, and writes a +/// single merged file with shared sections at the top and per-TFM diffs after +/// --- separators. Invoked from the outer multi-TFM build after all +/// inner builds have completed. +/// +internal static class ProjectDataMerger +{ + // The major version this merger emits, parsed once from the canonical version header. Used to + // gate slice preservation: an existing merged cache of a different major is incompatible and its + // slices must not be re-emitted under this header. + private static readonly int CurrentMajorVersion = + ForwardCompat.TryReadVersionHeader(CacheFormat.VersionHeader, out int major, out _) ? major : 2; + + // Section names in output order. + private static readonly string[] ListSectionNames = + [ + "commandLineArguments", + "sourceFiles", + "frameworkPacks", + "metadataReferences", + "sdkAnalyzerPacks", + "analyzerReferences", + "sdkAnalyzerConfigPolicy", + "analyzerConfigFiles", + "additionalFiles", + "embeddedResources", + "projectReferences", + "capabilities", + ]; + + // Sections whose lines use indentation-based path compression: an unindented + // line establishes a directory/path prefix, and following lines whose indent + // is greater than the header's belong to that group. The merger must intersect + // such sections at the *group* level — never line-by-line — or a child line + // (e.g. " ConsoleApp2.AssemblyInfo.cs") could be hoisted into the shared block + // without its parent prefix line, producing a corrupt cache file. + private static readonly HashSet IndentedSections = new(StringComparer.Ordinal) + { + "sourceFiles", + "metadataReferences", + "analyzerReferences", + "analyzerConfigFiles", + "additionalFiles", + "embeddedResources", + "projectReferences", + }; + + /// + /// Reads all slice files matching , merges them into + /// with a banner and --- separators. + /// Returns the number of slices merged, or 0 if none were found. + /// + /// + /// The glob is expected to follow MSBuild conventions, e.g. obj/**/<project>.csproj.slice. + /// We split on "**" to get the base directory and the filename to search for recursively. + /// + public static int Merge(string outputPath, string sliceGlob, string? targetFrameworks = null) + { + return Merge(outputPath, FindSlices(sliceGlob), targetFrameworks); + } + + public static int Merge(string outputPath, IEnumerable sliceFiles, string? targetFrameworks = null, bool preserveExistingSlices = false, string? tempDirectory = null) + { + List sortedSliceFiles = sliceFiles + .Where(static file => !string.IsNullOrWhiteSpace(file)) + .OrderBy(static file => file, StringComparer.OrdinalIgnoreCase) + .ToList(); + if (sortedSliceFiles.Count == 0) return 0; + + var slices = new List(sortedSliceFiles.Count); + foreach (string file in sortedSliceFiles) + slices.Add(ParseSlice(File.ReadAllText(file))); + + if (preserveExistingSlices) + AddPreservedExistingSlices(outputPath, slices); + + ProjectDataWriter.AtomicWriteStreamed(outputPath, writer => WriteMergedContent(writer, slices, targetFrameworks), tempDirectory); + + return sortedSliceFiles.Count; + } + + private static void AddPreservedExistingSlices(string outputPath, List slices) + { + if (!File.Exists(outputPath)) + return; + + string existingContent = File.ReadAllText(outputPath); + + // Only preserve slices from a SAME-major cache. A different (or unrecognized) major is an + // incompatible format: parsing it with this version's grammar and re-emitting the slices + // under our version= header would corrupt data or smuggle future-major content into a + // current-major file that the reader would then accept. Preserve nothing — the merge writes + // a clean current-major file from the freshly generated slices instead. + if (!ForwardCompat.TryReadVersionHeader(existingContent, out int existingMajor, out _) + || existingMajor != CurrentMajorVersion) + { + return; + } + + List existingSlices = ParseMergedContent(existingContent); + if (existingSlices.Count == 0) + return; + + // Preserve every existing slice for a TFM that wasn't regenerated in THIS build. This path is + // non-Windows-only (see _ProjectDataPreserveExistingProjectFolderCache) and exists so a slice + // for a TFM that is OS-conditionally excluded on this machine — e.g. + // net8.0;net9.0 — is + // carried forward instead of churning out of the committed cache. We deliberately do NOT filter + // by the current $(TargetFrameworks): at merge time the task only sees this OS's evaluated TFM + // list, so an OS-excluded TFM is indistinguishable from a removed one. Permanent removals are + // cleaned up by the platform that builds the full TFM set (the Windows preserve=false path drops + // non-regenerated slices); over-filtering here would silently churn the legitimate OS-excluded + // case the feature exists to protect. + var currentSliceIdentities = new HashSet( + slices.Select(GetSliceIdentity).OfType(), + StringComparer.OrdinalIgnoreCase); + + foreach (SliceData existingSlice in existingSlices) + { + string? identity = GetSliceIdentity(existingSlice); + if (identity is null || currentSliceIdentities.Contains(identity)) + continue; + + slices.Add(existingSlice); + currentSliceIdentities.Add(identity); + } + } + + internal static List FindSlices(string sliceGlob) + { + string baseDir; + string pattern; + int starStar = sliceGlob.IndexOf("**", StringComparison.Ordinal); + if (starStar >= 0) + { + baseDir = sliceGlob.Substring(0, starStar).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + if (string.IsNullOrEmpty(baseDir)) baseDir = "."; + string rest = sliceGlob.Substring(starStar + 2).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + pattern = string.IsNullOrEmpty(rest) ? "*" : rest; + } + else + { + baseDir = Path.GetDirectoryName(sliceGlob) ?? "."; + if (string.IsNullOrEmpty(baseDir)) baseDir = "."; + pattern = Path.GetFileName(sliceGlob); + } + + if (!Directory.Exists(baseDir)) return []; + var files = new List(Directory.EnumerateFiles(baseDir, pattern, SearchOption.AllDirectories)); + files.Sort(StringComparer.OrdinalIgnoreCase); + return files; + } + + /// + /// Writes the merged .lscache content directly to . + /// + internal static void WriteMergedContent(TextWriter writer, List slices, string? targetFrameworks = null) + { + WriteBanner(writer); + StampPrimarySlice(slices, targetFrameworks); + + if (slices.Count == 1) + { + WriteSingleSliceContent(writer, slices[0]); + return; + } + + // Compute shared content across all slices. + List sharedProjectLines = IntersectAllOrdered(slices.Select(s => s.ProjectLines).ToList()); + List sharedProperties = IntersectAllOrdered(slices.Select(s => s.Properties).ToList()); + var sharedSections = new Dictionary>(); + foreach (string section in ListSectionNames) + { + var allLists = slices.Select(s => s.ListSections.TryGetValue(section, out List? v) ? v : []).ToList(); + sharedSections[section] = IndentedSections.Contains(section) + ? FlattenGroups(IntersectAllOrderedGroups(allLists.Select(GroupByIndentation).ToList())) + : IntersectAllOrdered(allLists); + } + + // Write shared [project] (no [sliceDimensions]). + writer.WriteLine(); + WriteProjectSection(writer, sharedProjectLines); + + // Write shared [properties]. + if (sharedProperties.Count > 0) + { + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.Properties)); + foreach (string line in sharedProperties) + writer.WriteLine(line); + } + + // Write shared list sections. + foreach (string section in ListSectionNames) + { + if (sharedSections[section].Count > 0) + { + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(section)); + foreach (string line in sharedSections[section]) + writer.WriteLine(line); + } + } + + // Write per-TFM slices (diff only). + for (int i = 0; i < slices.Count; i++) + { + writer.WriteLine(); + writer.WriteLine(CacheFormat.SliceSeparator); + writer.WriteLine(); + + // Per-TFM [project] lines (excluding shared ones). + var sharedProjectSet = new HashSet(sharedProjectLines, StringComparer.Ordinal); + WriteProjectSection(writer, slices[i].ProjectLines.Where(line => !sharedProjectSet.Contains(line)), slices[i].IsPrimary); + + // [sliceDimensions] is always per-TFM. + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.SliceDimensions)); + foreach (string line in slices[i].SliceDimensions) + writer.WriteLine(line); + + // Per-TFM [properties] (excluding shared ones). + var sharedPropsSet = new HashSet(sharedProperties, StringComparer.Ordinal); + var diffProps = slices[i].Properties.Where(p => !sharedPropsSet.Contains(p)).ToList(); + if (diffProps.Count > 0) + { + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.Properties)); + foreach (string line in diffProps) + writer.WriteLine(line); + } + + // Per-TFM list sections (excluding shared lines). + foreach (string section in ListSectionNames) + { + List sliceLines = slices[i].ListSections.TryGetValue(section, out List? v) ? v : []; + List diffLines; + if (IndentedSections.Contains(section)) + { + // Group-aware diff: drop entire groups whose stringified content matches a shared group. + var sharedGroupKeys = new HashSet( + GroupByIndentation(sharedSections[section]).Select(StringifyGroup), + StringComparer.Ordinal); + diffLines = FlattenGroups(GroupByIndentation(sliceLines) + .Where(g => !sharedGroupKeys.Contains(StringifyGroup(g))) + .ToList()); + } + else + { + var sharedSet = new HashSet(sharedSections[section], StringComparer.Ordinal); + diffLines = sliceLines.Where(line => !sharedSet.Contains(line)).ToList(); + } + if (diffLines.Count > 0) + { + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(section)); + foreach (string line in diffLines) + writer.WriteLine(line); + } + } + } + } + + private static void WriteSingleSliceContent(TextWriter writer, SliceData slice) + { + writer.WriteLine(); + // Intentionally omit the ``primary`` marker for single-slice output: the reader's + // ``ToProjectDto`` falls back to ``slices[0]`` when no slice has ``IsPrimary``, so + // the marker is redundant when only one slice exists. Skipping it keeps cache + // files smaller and avoids spurious diffs when a project's TargetFrameworks set + // shrinks from multi- to single-targeting. The multi-slice path below still + // writes ``primary`` on the canonical (non-.NETFramework) slice, which is where + // the marker actually disambiguates between sibling slices. + WriteProjectSection(writer, slice.ProjectLines, isPrimary: false); + + if (slice.Properties.Count > 0) + { + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.Properties)); + foreach (string line in slice.Properties) + writer.WriteLine(line); + } + + foreach (string section in ListSectionNames) + { + if (slice.ListSections.TryGetValue(section, out List? lines) && lines.Count > 0) + { + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(section)); + foreach (string line in lines) + writer.WriteLine(line); + } + } + } + + private static void WriteProjectSection(TextWriter writer, IEnumerable projectLines, bool isPrimary = false) + { + List lines = projectLines.ToList(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.Project)); + foreach (string line in lines.Where(static l => l.StartsWith(CacheFormat.ProjectHeaderPrefix, StringComparison.Ordinal))) + writer.WriteLine(line); + writer.Write(CacheFormat.LanguagePrefix); writer.WriteLine("C#"); + if (isPrimary) + writer.WriteLine(CacheFormat.PrimaryMarker); + foreach (string line in lines.Where(static l => !l.StartsWith(CacheFormat.ProjectHeaderPrefix, StringComparison.Ordinal))) + writer.WriteLine(line); + } + + private static void StampPrimarySlice(List slices, string? targetFrameworks) + { + if (slices.Count == 0) return; + + foreach (SliceData slice in slices) + slice.IsPrimary = false; + + SliceData? primarySlice = GetPrimarySlice(slices, targetFrameworks); + + // If the outer build does not provide a usable TargetFrameworks list, fall + // back to the deterministic slice order supplied by FindSlices/callers. + (primarySlice ?? slices[0]).IsPrimary = true; + } + + private static SliceData? GetPrimarySlice(List slices, string? targetFrameworks) + { + List orderedTargetFrameworks = GetTargetFrameworks(targetFrameworks); + SliceData? primarySlice = orderedTargetFrameworks + .Where(static targetFramework => !IsNetFrameworkTargetFramework(targetFramework)) + .Select(targetFramework => FindSlice(slices, targetFramework)) + .FirstOrDefault(static slice => slice is not null); + if (primarySlice is not null) + return primarySlice; + + primarySlice = orderedTargetFrameworks + .Select(targetFramework => FindSlice(slices, targetFramework)) + .FirstOrDefault(static slice => slice is not null); + if (primarySlice is not null) + return primarySlice; + + return slices.FirstOrDefault(slice => !IsNetFrameworkTargetFramework(slice.GetSliceDimension(ProjectProperties.TargetFramework))); + } + + private static SliceData? FindSlice(List slices, string targetFramework) + { + return slices.FirstOrDefault(slice => string.Equals( + slice.GetSliceDimension(ProjectProperties.TargetFramework), + targetFramework, + StringComparison.OrdinalIgnoreCase)); + } + + private static List GetTargetFrameworks(string? targetFrameworks) + { + if (string.IsNullOrWhiteSpace(targetFrameworks)) + return []; + + var result = new List(); + foreach (string targetFramework in targetFrameworks!.Split(';')) + { + string trimmed = targetFramework.Trim(); + if (trimmed.Length > 0) + result.Add(trimmed); + } + + return result; + } + + private static bool IsNetFrameworkTargetFramework(string? targetFramework) + { + if (string.IsNullOrWhiteSpace(targetFramework)) + return false; + + string normalized = targetFramework!.Trim(); + if (!normalized.StartsWith("net", StringComparison.OrdinalIgnoreCase)) + return false; + + string version = normalized.Substring(3); + return version.Length is 2 or 3 + && version.All(static c => c >= '0' && c <= '9') + && version[0] is >= '1' and <= '4'; + } + + /// Parses a slice file into structured sections. + internal static SliceData ParseSlice(string content) + { + var data = new SliceData(); + string? currentSection = null; + List? currentList = null; + + foreach (string rawLine in content.Split('\n')) + { + string line = rawLine.TrimEnd('\r'); + if (line.Length == 0) continue; + if (line.StartsWith("version=", StringComparison.Ordinal)) continue; + if (line[0] == CacheFormat.CommentChar) continue; + + if (line[0] == '[' && line[line.Length - 1] == ']') + { + currentSection = line.Substring(1, line.Length - 2); + currentList = null; + if (currentSection != CacheFormat.Sections.Project && currentSection != CacheFormat.Sections.SliceDimensions && currentSection != CacheFormat.Sections.Properties) + { + currentList = []; + data.ListSections[currentSection] = currentList; + } + continue; + } + + switch (currentSection) + { + case CacheFormat.Sections.Project: + if (string.Equals(line, CacheFormat.PrimaryMarker, StringComparison.Ordinal)) + data.IsPrimary = true; + else if (!line.StartsWith(CacheFormat.LanguagePrefix, StringComparison.Ordinal)) + data.ProjectLines.Add(line); + break; + case CacheFormat.Sections.SliceDimensions: + data.SliceDimensions.Add(line); + break; + case CacheFormat.Sections.Properties: + data.Properties.Add(line); + break; + default: + currentList?.Add(string.Equals(currentSection, CacheFormat.Sections.SdkAnalyzerConfigPolicy, StringComparison.Ordinal) + ? ProjectDataWriter.CanonicalizeSdkAnalyzerConfigPolicyLine(line, data.GetTargetFrameworkIdentifier(), data.GetTargetFrameworkVersion()) + : line); + break; + } + } + + return data; + } + + internal static List ParseMergedContent(string content) + { + List segments = SplitMergedContent(content); + if (segments.Count == 0) + return []; + + if (segments.Count == 1) + { + SliceData singleSlice = ParseSlice(segments[0]); + return singleSlice.SliceDimensions.Count == 0 ? [] : [singleSlice]; + } + + SliceData sharedData = ParseSlice(segments[0]); + var slices = new List(segments.Count - 1); + foreach (string segment in segments.Skip(1)) + { + SliceData sliceDiff = ParseSlice(segment); + if (sliceDiff.SliceDimensions.Count == 0) + continue; + + slices.Add(CombineSlices(sharedData, sliceDiff)); + } + + return slices; + } + + private static List SplitMergedContent(string content) + { + var segments = new List(); + var builder = new StringBuilder(); + using var reader = new StringReader(content); + string? line; + while ((line = reader.ReadLine()) is not null) + { + if (string.Equals(line.TrimEnd('\r'), CacheFormat.SliceSeparator, StringComparison.Ordinal)) + { + segments.Add(builder.ToString()); + builder.Clear(); + continue; + } + + builder.AppendLine(line); + } + + segments.Add(builder.ToString()); + return segments; + } + + private static SliceData CombineSlices(SliceData sharedData, SliceData sliceDiff) + { + var combined = new SliceData { IsPrimary = sliceDiff.IsPrimary }; + combined.ProjectLines.AddRange(sharedData.ProjectLines); + combined.ProjectLines.AddRange(sliceDiff.ProjectLines); + combined.SliceDimensions.AddRange(sliceDiff.SliceDimensions); + combined.Properties.AddRange(sharedData.Properties); + combined.Properties.AddRange(sliceDiff.Properties); + + foreach (string section in ListSectionNames) + { + var lines = new List(); + if (sharedData.ListSections.TryGetValue(section, out List? sharedLines)) + lines.AddRange(sharedLines); + if (sliceDiff.ListSections.TryGetValue(section, out List? diffLines)) + lines.AddRange(diffLines); + if (lines.Count > 0) + combined.ListSections[section] = lines; + } + + return combined; + } + + private static string? GetSliceIdentity(SliceData slice) + => slice.SliceDimensions.Count == 0 + ? null + : string.Join("\n", slice.SliceDimensions.OrderBy(static line => line, StringComparer.OrdinalIgnoreCase)); + + /// Computes the ordered intersection of multiple lists (preserves order from first). + private static List IntersectAllOrdered(List> lists) + { + if (lists.Count == 0) return []; + if (lists.Count == 1) return new List(lists[0]); + var sets = lists.Skip(1).Select(l => new HashSet(l, StringComparer.Ordinal)).ToList(); + return lists[0].Where(item => sets.All(s => s.Contains(item))).ToList(); + } + + /// + /// Splits a path-section's lines into "groups": an unindented (indent-0) line + /// plus every following line whose indent is greater than zero. Each group is + /// the unit of compression — the unindented line is a path or directory prefix, + /// and the indented lines below it are continuations (children whose paths + /// share that prefix, or @-metadata attached to a leaf). + /// + /// + /// Path sections cannot be intersected line-by-line: an indented continuation + /// has no meaning without its preceding indent-0 header. Multi-TFM merging + /// must compare these groups whole. + /// + internal static List> GroupByIndentation(List lines) + { + var groups = new List>(); + List? current = null; + foreach (string line in lines) + { + bool isIndented = line.Length > 0 && line[0] == ' '; + if (!isIndented) + { + current = [line]; + groups.Add(current); + } + else + { + // Indented continuation: attach to the most recent group, or start + // a new group if none exists (defensive; shouldn't normally happen). + if (current == null) + { + current = [line]; + groups.Add(current); + } + else + { + current.Add(line); + } + } + } + return groups; + } + + /// Joins a group's lines with \n for use as a hash/equality key. + private static string StringifyGroup(List group) => string.Join("\n", group); + + /// Flattens a list of groups back into a flat line list. + private static List FlattenGroups(List> groups) + { + var result = new List(); + foreach (List g in groups) result.AddRange(g); + return result; + } + + /// Computes the ordered intersection of grouped lists (preserves group order from first). + private static List> IntersectAllOrderedGroups(List>> groupedLists) + { + if (groupedLists.Count == 0) return []; + if (groupedLists.Count == 1) return new List>(groupedLists[0]); + var sets = groupedLists.Skip(1) + .Select(gl => new HashSet(gl.Select(StringifyGroup), StringComparer.Ordinal)) + .ToList(); + return groupedLists[0].Where(g => sets.All(s => s.Contains(StringifyGroup(g)))).ToList(); + } + + private static void WriteBanner(TextWriter writer) + { + writer.WriteLine(CacheFormat.VersionHeader); + writer.WriteLine(); + writer.WriteLine("# This file caches language service data to improve the performance of C# Dev Kit."); + writer.WriteLine("# It is not intended for manual editing. It can safely be deleted and will be"); + writer.WriteLine("# regenerated automatically. For more information, see https://aka.ms/lscache"); + writer.WriteLine("#"); + writer.WriteLine("# To control where cache files are stored, use the following VS Code setting:"); + writer.WriteLine("# \"dotnet.projectsystem.cacheInProjectFolder\": true"); + } + + internal sealed class SliceData + { + public List ProjectLines { get; } = []; + public List SliceDimensions { get; } = []; + public List Properties { get; } = []; + public Dictionary> ListSections { get; } = new(StringComparer.Ordinal); + public bool IsPrimary { get; set; } + + public string? GetSliceDimension(string name) + { + foreach (string line in this.SliceDimensions) + { + int equalsIndex = line.IndexOf('='); + if (equalsIndex > 0 && string.Equals(line.Substring(0, equalsIndex), name, StringComparison.Ordinal)) + return line.Substring(equalsIndex + 1); + } + + return null; + } + + public string? GetTargetFramework() + => this.GetSliceDimension(ProjectProperties.TargetFramework) ?? GetValue(this.Properties, ProjectProperties.TargetFramework); + + public string? GetTargetFrameworkIdentifier() + => GetValue(this.Properties, ProjectProperties.TargetFrameworkIdentifier); + + public string? GetTargetFrameworkVersion() + => GetValue(this.Properties, ProjectProperties.TargetFrameworkVersion); + + private static string? GetValue(List lines, string name) + { + foreach (string line in lines) + { + int equalsIndex = line.IndexOf('='); + if (equalsIndex > 0 && string.Equals(line.Substring(0, equalsIndex), name, StringComparison.Ordinal)) + return line.Substring(equalsIndex + 1); + } + + return null; + } + } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/ProjectDataWriter.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/ProjectDataWriter.cs new file mode 100644 index 0000000000000..32ca2c53c7d42 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/ProjectDataWriter.cs @@ -0,0 +1,2124 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Buffers; +using System.Collections.Frozen; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.NET.ProjectData; + +namespace Microsoft.NET.ProjectData.Tasks; + +/// +/// Builds the textual project-data content for a single slice and writes it atomically. +/// All methods are pure (no MSBuild dependency) so they can be unit-tested directly +/// without mocking MSBuild infrastructure; is the only +/// member that does file I/O. +/// +internal static class ProjectDataWriter +{ + private const string NewLine = "\n"; + + // Portable-path prefix for entries rooted in an SDK targeting/runtime ref pack. + // Reader expands these via FrameworkList.xml at read time. + internal const string DotNetPacksPrefix = PathSentinels.Dotnet + "/packs/"; + internal const string NetFxRefPrefix = PathSentinels.NetFxRef + "/"; + internal const string NuGetPrefix = PathSentinels.Nuget + "/"; + internal const string MissingNetFrameworkReferenceAssembliesReason = "MissingNetFrameworkReferenceAssemblies"; + + // Roslyn repo/toolset CSharp targets suppress CS8002 for .NETCoreApp because strong naming is ignored there. + // Normalize it so caches stay stable when DTB imports SDK inbox targets instead. + private const string NetCoreAppIgnoredStrongNameWarning = "8002"; + private static readonly StringComparer PathComparer = StringComparers.Paths; + + private static int ComparePortablePaths(string? left, string? right) + { + int comparison = StringComparer.OrdinalIgnoreCase.Compare(left, right); + return comparison != 0 ? comparison : StringComparer.Ordinal.Compare(left, right); + } + + /// + /// Capabilities that are universal to managed projects and add no + /// filtering value. Excluding them keeps cache files smaller and avoids noise + /// in capability-based queries. + /// + private static readonly FrozenSet ExcludedCapabilities = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "AllTargetOutputGroups", + "AppServicePublish", + "AspNetCoreInProcessHosting", + "AssemblyReferences", + "BuildWindowsDesktopTarget", + "COMReferences", + "CSharp", + "DeclaredSourceItems", + "DotNetCoreRazorConfiguration", + "DynamicDependentFile", + "DynamicFileNesting", + "GenerateDocumentationFile", + "LanguageService", + "Managed", + "NetSdkOCIImageBuild", + "OutputGroups", + "ProjectReferences", + "ReferencesFolder", + "RelativePathDerivedDefaultNamespace", + "SharedProjectReferences", + "SingleFileGenerators", + "SupportHierarchyContextSvc", + "SupportsComputeRunCommand", + "SupportsTypeScriptNuGet", + "UserSourceItems", + "VisualStudioWellKnownOutputGroups", + "WebNestingDefaults", + }.ToFrozenSet(StringComparer.OrdinalIgnoreCase); + + /// + /// Properties that must never appear in the cache file. These are + /// environment-specific values injected at read time by the snapshot + /// factory; writing them would produce stale/machine-local data. + /// + private static readonly FrozenSet ExcludedProperties = new HashSet(StringComparer.OrdinalIgnoreCase) + { + ProjectProperties.SolutionPath, + }.ToFrozenSet(StringComparer.OrdinalIgnoreCase); + + /// + /// Properties that must always be written, even when empty/whitespace. + /// Generated from server/src/Microsoft.NET.ProjectData.Generators/project-data-schema.json by DataModelSchemaGenerator. + /// + private static readonly FrozenSet RequiredProperties = ProjectProperties.Required.ToFrozenSet(StringComparer.OrdinalIgnoreCase); + + // Builds the complete content for one slice. When writeHeader=true the + // version/banner/[project] header is included (single-target, or any single + // file consumed directly); when false only the [project] section onward + // is emitted (multi-TFM inner-build slices, merged later by ProjectDataMerger). + public static string BuildContent( + string projectFilePath, + bool writeHeader, + bool isPrimary, + bool lastDtbSucceeded, + ITaskItem[]? sliceDimensions, + ITaskItem[]? properties, + string[]? commandLineArguments, + ITaskItem[]? sourceFiles, + ITaskItem[]? metadataReferences, + ITaskItem[]? analyzerReferences, + string[]? analyzerConfigFiles, + string[]? additionalFiles, + ITaskItem[]? embeddedResources = null, + ITaskItem[]? projectReferences = null, + string[]? capabilities = null, + ITaskItem[]? sdkKnownAnalyzerPacks = null, + ITaskItem[]? sdkAnalyzerConfigPolicy = null, + Action? duplicateItemReporter = null) + { + using var writer = new StringWriter(); + writer.NewLine = NewLine; + WriteContent( + writer, + projectFilePath, + writeHeader, + isPrimary, + lastDtbSucceeded, + sliceDimensions, + properties, + commandLineArguments, + sourceFiles, + metadataReferences, + analyzerReferences, + analyzerConfigFiles, + additionalFiles, + embeddedResources, + projectReferences, + capabilities, + sdkKnownAnalyzerPacks, + sdkAnalyzerConfigPolicy, + duplicateItemReporter); + + return writer.ToString(); + } + + public static void WriteContent( + TextWriter writer, + string projectFilePath, + bool writeHeader, + bool isPrimary, + bool lastDtbSucceeded, + ITaskItem[]? sliceDimensions, + ITaskItem[]? properties, + string[]? commandLineArguments, + ITaskItem[]? sourceFiles, + ITaskItem[]? metadataReferences, + ITaskItem[]? analyzerReferences, + string[]? analyzerConfigFiles, + string[]? additionalFiles, + ITaskItem[]? embeddedResources = null, + ITaskItem[]? projectReferences = null, + string[]? capabilities = null, + ITaskItem[]? sdkKnownAnalyzerPacks = null, + ITaskItem[]? sdkAnalyzerConfigPolicy = null, + Action? duplicateItemReporter = null) + { + CachePathResolver resolver = new CachePathResolver(projectFilePath); + + if (writeHeader) + { + writer.WriteLine(CacheFormat.VersionHeader); + writer.WriteLine(); + writer.WriteLine("# This file caches language service data to improve the performance of C# Dev Kit."); + writer.WriteLine("# It is not intended for manual editing. It can safely be deleted and will be"); + writer.WriteLine("# regenerated automatically. For more information, see https://aka.ms/lscache"); + writer.WriteLine("#"); + writer.WriteLine("# To control where cache files are stored, use the following VS Code setting:"); + writer.WriteLine("# \"dotnet.projectsystem.cacheInProjectFolder\": true"); + } + + // [project] + if (writeHeader) writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.Project)); + writer.Write(CacheFormat.ProjectHeaderPrefix); writer.WriteLine(resolver.ToPortable(projectFilePath)); + writer.Write(CacheFormat.LanguagePrefix); writer.WriteLine("C#"); + if (isPrimary) writer.WriteLine(CacheFormat.PrimaryMarker); + if (lastDtbSucceeded) writer.WriteLine(CacheFormat.LastDtbSucceededMarker); + + // [sliceDimensions] + List> sliceKvps = ToSortedKvps(sliceDimensions); + if (sliceKvps.Count > 0) + { + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.SliceDimensions)); + foreach (KeyValuePair kvp in sliceKvps) + { + writer.Write(kvp.Key); + writer.Write('='); + writer.WriteLine(kvp.Value); + } + } + + // [properties] — sorted OrdinalIgnoreCase; values MakePortable'd. + // Empty / unset values are skipped for optional properties, but required + // properties (from project-data-schema.json) are always written — even when + // empty — so the reader can distinguish "not set" from "set to empty". + // Properties in the ExcludedProperties set are never written (defense-in-depth). + List> propKvps = ToSortedKvps(properties); + if (propKvps.Count > 0) + { + bool wroteHeader = false; + foreach (KeyValuePair kvp in propKvps) + { + string value = kvp.Value ?? string.Empty; + if (value == "*Undefined*") continue; + if (ExcludedProperties.Contains(kvp.Key)) continue; + if (string.IsNullOrWhiteSpace(value) && !RequiredProperties.Contains(kvp.Key)) continue; + if (!wroteHeader) + { + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.Properties)); + wroteHeader = true; + } + writer.Write(kvp.Key); + writer.Write('='); + writer.WriteLine(resolver.MakePortable(value)); + } + } + + var targetFramework = new TargetFramework( + GetItemValue(sliceDimensions, ProjectProperties.TargetFramework) ?? GetItemValue(properties, ProjectProperties.TargetFramework), + GetItemValue(properties, ProjectProperties.TargetFrameworkIdentifier), + GetItemValue(properties, ProjectProperties.TargetFrameworkVersion)); + + // [commandLineArguments] — order preserved; file-based args filtered out + // Required item type — always write header even if empty. + { + var filtered = new List(); + if (commandLineArguments != null) + { + foreach (var arg in commandLineArguments) + { + if (arg == null) continue; + if (IsFileArgument(arg)) continue; + if (IsMachineSpecificArgument(arg)) continue; + if (IsPlatformArgument(arg)) continue; + if (ShouldSkipCommandLineArgument(arg, targetFramework.Identifier)) continue; + string normalized = NormalizeCommandLineArgument(arg, targetFramework.Identifier); + if (!string.IsNullOrEmpty(normalized)) + { + filtered.Add(normalized); + } + } + } + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.CommandLineArguments)); + foreach (var arg in filtered) + writer.WriteLine(resolver.MakePortable(arg)); + } + + // Path sections. + // Metadata and analyzer refs are pre-processed: shared-framework entries + // rooted in an SDK .App.Ref pack or a recognized NuGet ref pack are removed, + // and the pack name is added to [frameworkPacks] for the reader to expand via + // FrameworkList.xml. Workload-pack entries remain explicit because their + // versions do not follow the target framework's version scheme. + SortedSet frameworkPacks = new SortedSet(StringComparer.OrdinalIgnoreCase); + SortedSet sdkAnalyzerPacks = new SortedSet(StringComparer.OrdinalIgnoreCase); + SortedSet sdkAnalyzerConfigPolicyLines = BuildSdkAnalyzerConfigPolicy(sdkAnalyzerConfigPolicy, targetFramework); + List> preparedMetadataRefs = PrepareMetadataRefs( + metadataReferences, + resolver, + frameworkPacks, + targetFramework); + List preparedAnalyzerRefs = PrepareAnalyzerRefs( + analyzerReferences, + resolver, + frameworkPacks, + sdkAnalyzerPacks, + sdkKnownAnalyzerPacks, + targetFramework, + projectFilePath, + duplicateItemReporter); + List preparedAnalyzerConfigFiles = AnalyzerConfigFileFilter.Prepare( + analyzerConfigFiles, + resolver, + sourceFiles, + filterSdkAnalyzerConfigFiles: sdkAnalyzerConfigPolicyLines.Count > 0); + preparedAnalyzerConfigFiles = ToSortedDistinctPortablePaths( + preparedAnalyzerConfigFiles, + CacheFormat.Sections.AnalyzerConfigFiles, + projectFilePath, + duplicateItemReporter); + + EmitSourceFileSection(writer, sourceFiles, resolver, projectFilePath, duplicateItemReporter); + WriteFrameworkPacksSection(writer, frameworkPacks); + EmitMetadataRefSection(writer, preparedMetadataRefs); + WriteSdkAnalyzerPacksSection(writer, sdkAnalyzerPacks); + EmitSimplePathSectionRequired(writer, CacheFormat.SectionHeader(CacheFormat.Sections.AnalyzerReferences), preparedAnalyzerRefs); + WriteSdkAnalyzerConfigPolicySection(writer, sdkAnalyzerConfigPolicyLines); + EmitSimplePathSection(writer, CacheFormat.SectionHeader(CacheFormat.Sections.AnalyzerConfigFiles), preparedAnalyzerConfigFiles); + WritePreparedPathSection( + writer, + CacheFormat.SectionHeader(CacheFormat.Sections.AdditionalFiles), + PrepareDistinctPortablePaths(additionalFiles, resolver, CacheFormat.Sections.AdditionalFiles, projectFilePath, duplicateItemReporter), + required: false); + EmitEmbeddedResourceSection(writer, embeddedResources, resolver); + EmitProjectReferenceSection(writer, projectReferences, resolver, projectFilePath, duplicateItemReporter); + + // Capabilities — simple string list, one per line. + // Exclude well-known capabilities that are universal to managed projects + // and add no filtering value — they would just bloat every cache file. + if (capabilities is { Length: > 0 }) + { + // Deduplicate (MSBuild can emit duplicates), exclude banned, and sort for stable output. + var uniqueCaps = new SortedSet(StringComparer.OrdinalIgnoreCase); + foreach (string cap in capabilities) + { + if (!ExcludedCapabilities.Contains(cap)) + uniqueCaps.Add(cap); + } + + if (uniqueCaps.Count > 0) + { + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.Capabilities)); + foreach (string cap in uniqueCaps) + writer.WriteLine(cap); + } + } + } + + // The major version this build understands, parsed from the writer's own version header. + // Forward-compatibility preservation only runs against an existing file of the same major. + private static readonly int CurrentMajorVersion = ForwardCompat.TryParseVersion(CacheFormat.VersionHeader, out int major, out _) + ? major + : throw new InvalidOperationException($"CacheFormat.VersionHeader '{CacheFormat.VersionHeader}' is not a valid 'version=[.]' header."); + + internal static readonly UTF8Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + + /// + /// Atomically writes arbitrary to via a + /// .tmp side-file, skipping the write when the existing file already has identical content + /// (so a no-op never touches the file and never churns watchers / git). No forward-compat + /// preservation is applied — this overload is for non-cache content. + /// + public static void AtomicWrite(string outputPath, string content, string? tempDirectory = null) + { + string normalized = NormalizeLineEndings(content); + byte[] bytes = Utf8NoBom.GetBytes(normalized); + WriteAtomicallyCore(outputPath, bytes, bytes.Length, preserveUnknownData: false, candidateTextFactory: null, tempDirectory); + } + + /// + /// Atomically writes cache content produced by to + /// . The existing file is read once (into a pooled buffer) and is + /// used both to preserve forward-compatible data a newer minor version may have written and to + /// decide whether the write can be skipped — a content match means the file is left untouched. + /// No SHA-256 / hash header is written; a no-op build never touches the file. + /// + /// + /// The candidate is rendered into pooled / buffers rather + /// than a plus a array, so the common no-op / + /// same-version path allocates nothing on the large-object heap. The candidate string is + /// materialized lazily only on the rare path where a newer minor version's data actually has to + /// be spliced in. + /// + public static void AtomicWriteStreamed(string outputPath, Action writeContent, string? tempDirectory = null) + { + using PooledCacheRender render = PooledCacheRender.Create(writeContent); + WriteAtomicallyCore(outputPath, render.Bytes, render.ByteLength, preserveUnknownData: true, candidateTextFactory: render.GetText, tempDirectory); + } + + /// + /// Shared write core: compares the candidate ( / + /// ) against the existing file, optionally splices in + /// forward-compatible data, and atomically replaces the file only when the final bytes differ + /// (or a legacy hash= header still needs stripping). The existing file is read into a + /// pooled buffer to stay off the large-object heap. + /// lazily produces the candidate as a string and is invoked only on the rare preservation path. + /// + private static void WriteAtomicallyCore( + string outputPath, + byte[] candidateBuffer, + int candidateLength, + bool preserveUnknownData, + Func? candidateTextFactory, + string? tempDirectory) + { + byte[]? rented = null; + try + { + int existingLength = 0; + int contentStart = 0; + bool existingHadHashLine = false; + bool existingPresent = false; + + if (TryGetFileLength(outputPath, out int fileLength) && fileLength > 0) + { + rented = ArrayPool.Shared.Rent(fileLength); + if (TryReadAll(outputPath, rented, fileLength, out existingLength)) + { + existingPresent = true; + contentStart = SkipLegacyHashLine(rented, existingLength, out existingHadHashLine); + } + } + + byte[] finalBuffer = candidateBuffer; + int finalLength = candidateLength; + + if (existingPresent) + { + int existingContentLength = existingLength - contentStart; + var existingContent = new ReadOnlySpan(rented!, contentStart, existingContentLength); + var candidateContent = new ReadOnlySpan(candidateBuffer, 0, candidateLength); + + // A cache's minor stamp marks payload compatibility, not the writer binary. If a newer + // writer emits no new data, retain the still-valid older stamp and leave the file + // untouched rather than churning every cache after each minor schema bump. + bool candidateMatchesExisting = existingContent.SequenceEqual(candidateContent) + || (preserveUnknownData + && ForwardCompat.MatchesExceptForOlderMinorVersion( + existingContent, + candidateContent, + CurrentMajorVersion)); + bool bufferWasMerged = false; + + // Only decode the existing/candidate bytes to strings (and run the full splice) when a + // byte-level probe says the existing file was authored by a NEWER minor — the one case + // where ForwardCompat.PreserveUnknownData can carry anything forward. On the common + // same-version change this skips two ~file-sized string allocations. + if (preserveUnknownData + && !candidateMatchesExisting + && ForwardCompat.ExistingHasNewerMinor(existingContent, candidateContent, CurrentMajorVersion)) + { + string existingText = Utf8NoBom.GetString(rented!, contentStart, existingContentLength); + string candidateText = candidateTextFactory!(); + string merged = ForwardCompat.PreserveUnknownData(existingText, candidateText, CurrentMajorVersion); + if (!ReferenceEquals(merged, candidateText)) + { + byte[] mergedBytes = Utf8NoBom.GetBytes(merged); + finalBuffer = mergedBytes; + finalLength = mergedBytes.Length; + bufferWasMerged = true; + } + } + + // Skip the write only when the final content already matches AND there is no stale legacy + // hash line to strip. The legacy-hash case forces exactly one rewrite per file. + bool finalMatchesExisting = bufferWasMerged + ? existingContent.SequenceEqual(new ReadOnlySpan(finalBuffer, 0, finalLength)) + : candidateMatchesExisting; + if (!existingHadHashLine && finalMatchesExisting) + { + return; + } + } + + AtomicReplace(outputPath, finalBuffer, finalLength, tempDirectory); + } + finally + { + if (rented != null) + ArrayPool.Shared.Return(rented); + } + } + + private static bool TryGetFileLength(string path, out int length) + { + length = 0; + try + { + long len = new FileInfo(path).Length; + if (len <= 0 || len > int.MaxValue) + return false; + length = (int)len; + return true; + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + return false; + } + } + + private static bool TryReadAll(string path, byte[] buffer, int length, out int bytesRead) + { + bytesRead = 0; + try + { + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + int total = 0; + int read; + while (total < length && (read = stream.Read(buffer, total, length - total)) > 0) + total += read; + bytesRead = total; + return true; + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + return false; + } + } + + /// + /// Returns the offset of the first byte after a leading legacy hash=... line, or 0 when + /// the file has no such line. Pre-regeneration cache files still start with this header; the + /// reader skips it too. The flag lets the writer force a one-time rewrite that strips it. + /// + private static int SkipLegacyHashLine(byte[] buffer, int length, out bool hadHashLine) + { + hadHashLine = false; + ReadOnlySpan prefix = "hash="u8; + if (length < prefix.Length) + return 0; + if (!new ReadOnlySpan(buffer, 0, prefix.Length).SequenceEqual(prefix)) + return 0; + + for (int i = prefix.Length; i < length; i++) + { + if (buffer[i] == (byte)'\n') + { + hadHashLine = true; + return i + 1; + } + } + + // A file that is nothing but a hash line (no newline) — treat the whole thing as the header. + hadHashLine = true; + return length; + } + + private static void AtomicReplace(string outputPath, byte[] content, int length, string? tempDirectory) + { + string? outputDir = Path.GetDirectoryName(outputPath); + if (outputDir != null) Directory.CreateDirectory(outputDir); + + // Keep the transient side-file out of the (committed, watched) project folder by preferring + // the intermediate output directory, falling back to the output directory when none is given + // or it is on a different volume. + string tempDir = ResolveTempDirectory(tempDirectory, outputPath, outputDir); + string tempPath = Path.Combine(tempDir, Path.GetFileName(outputPath) + "." + Guid.NewGuid().ToString("N") + ".tmp"); + try + { + using (var stream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096)) + stream.Write(content, 0, length); + + ReplaceOrMove(tempPath, outputPath); + } + finally + { + // Best-effort cleanup of the temp file (may already be gone after a successful move). + try { File.Delete(tempPath); } catch { } + } + } + + /// + /// Chooses the directory for the atomic-write temp side-file. Prefers + /// (the project's intermediate output directory) so transient .tmp files never appear next + /// to committed source. Falls back to the output directory when no temp directory is requested, + /// when it is on a different volume ( requires + /// the temp file and the destination to share a volume), or when it cannot be created. + /// + private static string ResolveTempDirectory(string? tempDirectory, string outputPath, string? outputDir) + { + string fallback = string.IsNullOrEmpty(outputDir) ? "." : outputDir!; + if (string.IsNullOrEmpty(tempDirectory)) + return fallback; + + try + { + string full = Path.GetFullPath(tempDirectory!); + StringComparison comparison = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? StringComparison.OrdinalIgnoreCase + : StringComparison.Ordinal; + if (!string.Equals(Path.GetPathRoot(full), Path.GetPathRoot(Path.GetFullPath(outputPath)), comparison)) + return fallback; + + Directory.CreateDirectory(full); + return full; + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or ArgumentException or NotSupportedException) + { + return fallback; + } + } + + private static string NormalizeLineEndings(string content) + => content.IndexOf('\r') < 0 + ? content + : content.Replace("\r\n", "\n").Replace('\r', '\n'); + + private static void ReplaceOrMove(string tempPath, string outputPath) + { + try + { + File.Replace(tempPath, outputPath, null); + } + catch (FileNotFoundException) + { + try + { + File.Move(tempPath, outputPath); + } + catch (IOException moveException) + { + try + { + File.Replace(tempPath, outputPath, null); + } + catch (FileNotFoundException) + { + ExceptionDispatchInfo.Capture(moveException).Throw(); + throw; + } + } + } + } + + // Returns true for command-line args that represent file inputs. These are + // excluded from [commandLineArguments] because CPS puts them in dedicated sections. + internal static bool IsFileArgument(string arg) + { + if (arg.StartsWith("/reference:", StringComparison.OrdinalIgnoreCase)) return true; + if (arg.StartsWith("/analyzer:", StringComparison.OrdinalIgnoreCase)) return true; + if (arg.StartsWith("/analyzerconfig:", StringComparison.OrdinalIgnoreCase)) return true; + if (arg.StartsWith("/additionalfile:", StringComparison.OrdinalIgnoreCase)) return true; + if (arg.StartsWith("/sourcelink:", StringComparison.OrdinalIgnoreCase)) return true; + if (arg.StartsWith("/embed:", StringComparison.OrdinalIgnoreCase)) return true; + if (arg.StartsWith("/resource:", StringComparison.OrdinalIgnoreCase)) return true; + // Bare source-file paths go into [sourceFiles]. On Unix, absolute source + // paths start with "/" and would otherwise be mistaken for compiler switches. + if (IsRootedSourceFileArgument(arg)) return true; + if (IsPortableSourceFileArgument(arg)) return true; + if (arg.Length > 0 && arg[0] != '/' && arg[0] != '-') return true; + return false; + } + + // Returns true for compiler switches that vary per machine but have no effect + // on compilation output. Including them would cause cache churn across locales. + internal static bool IsMachineSpecificArgument(string arg) + { + // /preferreduilang: controls the language of diagnostic messages, + // not the compilation result. It varies by OS locale. + if (arg.StartsWith("/preferreduilang:", StringComparison.OrdinalIgnoreCase)) return true; + if (arg.StartsWith("-preferreduilang:", StringComparison.OrdinalIgnoreCase)) return true; + return false; + } + + internal static bool IsPlatformArgument(string arg) + => arg.StartsWith("/platform:", StringComparison.OrdinalIgnoreCase) + || arg.StartsWith("-platform:", StringComparison.OrdinalIgnoreCase); + + internal static string NormalizeCommandLineArgument(string arg, string? targetFrameworkIdentifier) + { + if (!string.Equals(targetFrameworkIdentifier, ".NETCoreApp", StringComparison.OrdinalIgnoreCase)) + { + return arg; + } + + const string slashNoWarn = "/nowarn:"; + const string dashNoWarn = "-nowarn:"; + string prefix; + if (arg.StartsWith(slashNoWarn, StringComparison.OrdinalIgnoreCase)) + { + prefix = arg.Substring(0, slashNoWarn.Length); + } + else if (arg.StartsWith(dashNoWarn, StringComparison.OrdinalIgnoreCase)) + { + prefix = arg.Substring(0, dashNoWarn.Length); + } + else + { + return arg; + } + + string warnings = arg.Substring(prefix.Length); + foreach (string warning in warnings.Split(',', ';')) + { + if (string.Equals(warning.Trim(), NetCoreAppIgnoredStrongNameWarning, StringComparison.OrdinalIgnoreCase)) + { + return arg; + } + } + + return string.IsNullOrEmpty(warnings) + ? prefix + NetCoreAppIgnoredStrongNameWarning + : arg + "," + NetCoreAppIgnoredStrongNameWarning; + } + + internal static bool ShouldSkipCommandLineArgument(string arg, string? targetFrameworkIdentifier) + { + return string.Equals(targetFrameworkIdentifier, ".NETFramework", StringComparison.OrdinalIgnoreCase) + && (arg.StartsWith("/platform:", StringComparison.OrdinalIgnoreCase) + || arg.StartsWith("-platform:", StringComparison.OrdinalIgnoreCase)); + } + + internal static bool TryValidateNetFrameworkReferences( + string projectFilePath, + ITaskItem[]? sliceDimensions, + ITaskItem[]? properties, + ITaskItem[]? metadataReferences, + out string unsupportedReason) + { + unsupportedReason = string.Empty; + var targetFramework = new TargetFramework( + GetItemValue(sliceDimensions, ProjectProperties.TargetFramework) ?? GetItemValue(properties, ProjectProperties.TargetFramework), + GetItemValue(properties, ProjectProperties.TargetFrameworkIdentifier), + GetItemValue(properties, ProjectProperties.TargetFrameworkVersion)); + if (!targetFramework.IsNetFramework) + { + return true; + } + + if (metadataReferences == null || metadataReferences.Length == 0) + { + unsupportedReason = MissingNetFrameworkReferenceAssembliesReason; + return false; + } + + string projectDirectory = Path.GetDirectoryName(projectFilePath) ?? string.Empty; + CachePathResolver resolver = new CachePathResolver(projectFilePath); + bool foundCanonicalReferenceAssembly = false; + + foreach (ITaskItem item in metadataReferences) + { + if (item == null || string.IsNullOrWhiteSpace(item.ItemSpec)) + { + continue; + } + + string absolutePath = Path.IsPathRooted(item.ItemSpec) + ? Path.GetFullPath(item.ItemSpec) + : Path.GetFullPath(Path.Combine(projectDirectory, item.ItemSpec)); + string portable = resolver.ToPortable(absolutePath); + + if (TryExtractNetFrameworkReferenceAssembly(portable, targetFramework) is not null) + { + foundCanonicalReferenceAssembly = true; + if (!File.Exists(absolutePath)) + { + unsupportedReason = MissingNetFrameworkReferenceAssembliesReason; + return false; + } + + continue; + } + + if (IsNetFrameworkCoreAssemblyName(Path.GetFileName(item.ItemSpec)) && !File.Exists(absolutePath)) + { + unsupportedReason = MissingNetFrameworkReferenceAssembliesReason; + return false; + } + } + + if (!foundCanonicalReferenceAssembly) + { + unsupportedReason = MissingNetFrameworkReferenceAssembliesReason; + return false; + } + + return true; + } + + private static bool IsRootedSourceFileArgument(string arg) + { + if (!Path.IsPathRooted(arg)) return false; + string extension = Path.GetExtension(arg); + return string.Equals(extension, ".cs", StringComparison.OrdinalIgnoreCase); + } + + private static bool IsPortableSourceFileArgument(string arg) + { + if (!arg.StartsWith(PathSentinels.Path, StringComparison.OrdinalIgnoreCase) + && !arg.StartsWith(PathSentinels.Nuget, StringComparison.OrdinalIgnoreCase) + && !arg.StartsWith(PathSentinels.Dotnet, StringComparison.OrdinalIgnoreCase) + && !arg.StartsWith(PathSentinels.NetSdk, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + string extension = Path.GetExtension(arg); + return string.Equals(extension, ".cs", StringComparison.OrdinalIgnoreCase); + } + + private static void WritePreparedPathSection(TextWriter writer, string header, List portables, bool required) + { + if (portables.Count == 0 && !required) return; + writer.WriteLine(); + writer.WriteLine(header); + if (portables.Count > 0) + { + portables.Sort(ComparePortablePaths); + EmitCompressed(writer, portables, 0); + } + } + + private static List PrepareDistinctPortablePaths( + ITaskItem[]? items, + CachePathResolver resolver, + string section, + string projectFilePath, + Action? duplicateItemReporter) + { + if (items == null || items.Length == 0) return []; + + var portables = new List(items.Length); + var seenPortables = new HashSet(PathComparer); + foreach (ITaskItem item in items) + { + if (item == null) continue; + string path = item.ItemSpec; + if (!string.IsNullOrEmpty(path)) + { + AddDistinctPortablePath(portables, seenPortables, resolver.ToPortable(path), section, projectFilePath, duplicateItemReporter); + } + } + + return portables; + } + + private static List PrepareDistinctPortablePaths( + string[]? items, + CachePathResolver resolver, + string section, + string projectFilePath, + Action? duplicateItemReporter) + { + if (items == null || items.Length == 0) return []; + + var portables = new List(items.Length); + var seenPortables = new HashSet(PathComparer); + foreach (string item in items) + { + if (!string.IsNullOrEmpty(item)) + { + AddDistinctPortablePath(portables, seenPortables, resolver.ToPortable(item), section, projectFilePath, duplicateItemReporter); + } + } + + return portables; + } + + private static void EmitSourceFileSection( + TextWriter writer, + ITaskItem[]? items, + CachePathResolver resolver, + string projectFilePath, + Action? duplicateItemReporter) + { + if (items == null || items.Length == 0) + { + // Required item type — write empty section header + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.SourceFiles)); + return; + } + + var sortedPaths = new List(items.Length); + var lookup = new Dictionary(PathComparer); + foreach (ITaskItem item in items) + { + if (item == null) continue; + string path = item.ItemSpec; + if (string.IsNullOrEmpty(path)) continue; + + string portable = resolver.ToPortable(path); + // First occurrence wins. Two ``ITaskItem``s can collapse to the same portable + // form (e.g. case variations on case-insensitive file systems, or wildcards + // overlapping explicit ``Include``s with metadata). Without this dedup the + // trie writer emits the path twice with its metadata block duplicated. + // ``Dictionary.TryAdd`` is netstandard2.1+ so use the ``ContainsKey`` shape. + if (!lookup.ContainsKey(portable)) + { + lookup.Add(portable, item); + sortedPaths.Add(portable); + } + else if (duplicateItemReporter is not null) + { + duplicateItemReporter(new ProjectDataDuplicateItemDiagnostic(projectFilePath, CacheFormat.Sections.SourceFiles, portable)); + } + } + + sortedPaths.Sort(ComparePortablePaths); + if (sortedPaths.Count == 0) return; + + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.SourceFiles)); + EmitCompressedWithMetadata(writer, sortedPaths, 0, "", lookup, EmitSourceFileMetadata); + } + + private static void EmitProjectReferenceSection( + TextWriter writer, + ITaskItem[]? items, + CachePathResolver resolver, + string projectFilePath, + Action? duplicateItemReporter) + { + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.ProjectReferences)); + if (items is null || items.Length == 0) + { + return; + } + + var sortedPaths = new List(items.Length); + var lookup = new Dictionary(PathComparer); + foreach (ITaskItem item in items) + { + if (item is null || string.IsNullOrEmpty(item.ItemSpec)) + { + continue; + } + + string portable = resolver.ToPortable(item.ItemSpec); + if (lookup.ContainsKey(portable)) + { + duplicateItemReporter?.Invoke(new ProjectDataDuplicateItemDiagnostic(projectFilePath, CacheFormat.Sections.ProjectReferences, portable)); + continue; + } + + lookup.Add(portable, item); + sortedPaths.Add(portable); + } + + sortedPaths.Sort(ComparePortablePaths); + EmitCompressedWithMetadata(writer, sortedPaths, 0, "", lookup, EmitProjectReferenceMetadata); + } + + // Emits a pre-built (already portable + sorted) string-path section. + private static void EmitSimplePathSection(TextWriter writer, string header, List portables) + { + if (portables.Count == 0) return; + writer.WriteLine(); + writer.WriteLine(header); + EmitCompressed(writer, portables, 0); + } + + // Required variant: always writes the header, even when empty. + private static void EmitSimplePathSectionRequired(TextWriter writer, string header, List portables) + { + writer.WriteLine(); + writer.WriteLine(header); + if (portables.Count > 0) + EmitCompressed(writer, portables, 0); + } + + // Converts metadataReferences ITaskItem[] to a sorted portable form. Entries + // rooted in an SDK .App.Ref pack or a recognized NuGet framework ref pack are diverted into + // and dropped from the result; .NET Framework + // reference assemblies remain in metadataReferences as canonical + // /vX.Y.Z/*.dll entries. + internal static List> PrepareMetadataRefs( + ITaskItem[]? items, CachePathResolver resolver, SortedSet frameworkPacks) + => PrepareMetadataRefs(items, resolver, frameworkPacks, default); + + internal static List> PrepareMetadataRefs( + ITaskItem[]? items, + CachePathResolver resolver, + SortedSet frameworkPacks, + TargetFramework targetFramework) + { + var result = new List>(); + if (items == null || items.Length == 0) return result; + foreach (ITaskItem item in items) + { + if (item == null) continue; + string path = item.ItemSpec; + if (string.IsNullOrEmpty(path)) continue; + string portable = resolver.ToPortable(path); + string? netFrameworkReferenceAssembly = TryExtractNetFrameworkReferenceAssembly(portable, targetFramework); + if (netFrameworkReferenceAssembly != null) + { + portable = NetFxRefPrefix + netFrameworkReferenceAssembly; + } + + string? packName = TryExtractRefPackName(portable); + if (packName != null) + { + frameworkPacks.Add(packName); + continue; + } + packName = TryExtractNuGetRefPackName(item, portable, targetFramework); + if (packName != null) + { + // Always emit to [frameworkPacks] regardless of resolution location. + // The same canonical pack (e.g. Microsoft.NETCore.App.Ref) may resolve from + // /packs/ on one machine and / on another depending on which + // SDKs/targeting packs are installed. Classifying by location produces + // environment-dependent lscache churn. The reader probes both locations. + frameworkPacks.Add(packName); + continue; + } + result.Add(new KeyValuePair(portable, item)); + } + result.Sort(static (a, b) => ComparePortablePaths(a.Key, b.Key)); + return result; + } + + // Converts analyzerReferences string[] to a sorted portable form, + // diverting entries rooted in an SDK .App.Ref pack or a recognized NuGet framework ref pack into the + // set (and dropping them from the result). + internal static List PrepareAnalyzerRefs( + ITaskItem[]? items, CachePathResolver resolver, SortedSet frameworkPacks) + => PrepareAnalyzerRefs(items, resolver, frameworkPacks, default); + + internal static List PrepareAnalyzerRefs( + ITaskItem[]? items, + CachePathResolver resolver, + SortedSet frameworkPacks, + TargetFramework targetFramework) + => PrepareAnalyzerRefs(items, resolver, frameworkPacks, new SortedSet(StringComparer.OrdinalIgnoreCase), sdkKnownAnalyzerPacks: null, targetFramework); + + internal static List PrepareAnalyzerRefs( + ITaskItem[]? items, + CachePathResolver resolver, + SortedSet frameworkPacks, + SortedSet sdkAnalyzerPacks, + ITaskItem[]? sdkKnownAnalyzerPacks, + TargetFramework targetFramework, + string projectFilePath = "", + Action? duplicateItemReporter = null) + { + if (items == null || items.Length == 0) return []; + + var result = new List(items.Length); + var seenResult = new HashSet(PathComparer); + foreach (ITaskItem item in items) + { + if (item == null) continue; + string path = item.ItemSpec; + if (string.IsNullOrEmpty(path)) continue; + string portable = resolver.ToPortable(path); + string? packName = TryExtractRefPackName(portable); + if (packName != null) + { + frameworkPacks.Add(packName); + continue; + } + packName = TryExtractNuGetRefPackName(item, portable, targetFramework); + if (packName != null) + { + // See PrepareMetadataRefs for rationale: classify by canonical pack name, + // not resolution location. + frameworkPacks.Add(packName); + continue; + } + string? sdkAnalyzerPackName = TryExtractSdkAnalyzerPackName(item, portable, sdkKnownAnalyzerPacks, targetFramework); + if (sdkAnalyzerPackName != null) + { + sdkAnalyzerPacks.Add(sdkAnalyzerPackName); + continue; + } + AddDistinctPortablePath(result, seenResult, portable, CacheFormat.Sections.AnalyzerReferences, projectFilePath, duplicateItemReporter); + } + result.Sort(ComparePortablePaths); + return result; + } + + private static List ToSortedDistinctPortablePaths( + IEnumerable paths, + string section, + string projectFilePath, + Action? duplicateItemReporter) + { + List result = []; + HashSet seenResult = new(PathComparer); + foreach (string path in paths) + { + AddDistinctPortablePath(result, seenResult, path, section, projectFilePath, duplicateItemReporter); + } + + result.Sort(ComparePortablePaths); + return result; + } + + private static void AddDistinctPortablePath( + List paths, + HashSet seenPaths, + string portablePath, + string section, + string projectFilePath, + Action? duplicateItemReporter) + { + if (seenPaths.Add(portablePath)) + { + paths.Add(portablePath); + } + else + { + if (duplicateItemReporter is not null) + { + duplicateItemReporter(new ProjectDataDuplicateItemDiagnostic(projectFilePath, section, portablePath)); + } + } + } + + internal static SortedSet BuildSdkAnalyzerConfigPolicy(ITaskItem[]? items, TargetFramework targetFramework) + { + var policies = new SortedSet(StringComparer.OrdinalIgnoreCase); + if (items == null || items.Length == 0) return policies; + + foreach (ITaskItem item in items) + { + if (item == null) continue; + if (!string.Equals(item.ItemSpec, "Microsoft.NET.Sdk", StringComparison.OrdinalIgnoreCase)) continue; + if (IsTrue(GetMetadataValue(item, "SkipGlobalAnalyzerConfigForPackage"))) continue; + + // Only emit each policy line when the SDK actually applies the corresponding + // analyzer pack. The SDK gates NetAnalyzer DLLs on `$(EnableNETAnalyzers)` and + // CodeStyle DLLs on `$(EnforceCodeStyleInBuild) And '$(Language)' == 'C#'` + // (Microsoft.NET.Sdk.Analyzers.targets). Emitting the policy unconditionally + // produces orphan entries that reference packs with zero DLLs in + // `[analyzerReferences]`. + if (IsTrue(GetMetadataValue(item, "EnableNETAnalyzers"))) + { + policies.Add(BuildNetAnalyzersPolicyLine(item, targetFramework)); + } + + string? languageSegment = TryGetSdkCodeStyleLanguageSegment(GetMetadataValue(item, "Language")); + if (languageSegment != null && IsTrue(GetMetadataValue(item, "EnforceCodeStyleInBuild"))) + { + policies.Add(BuildCodeStylePolicyLine(item, languageSegment, targetFramework)); + } + } + + return policies; + } + + // Emits the [frameworkPacks] section, listing pack names only (no version). + // The reader expands each pack via /packs///data/FrameworkList.xml + // into managed metadata references and CS analyzer references. + internal static void WriteFrameworkPacksSection(StringBuilder sb, SortedSet packs) + { + using var writer = new StringWriter(sb); + WriteFrameworkPacksSection(writer, packs); + } + + internal static void WriteFrameworkPacksSection(TextWriter writer, SortedSet packs) + { + if (packs.Count == 0) return; + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.FrameworkPacks)); + foreach (string name in packs) writer.WriteLine(name); + } + + internal static void WriteSdkAnalyzerPacksSection(TextWriter writer, SortedSet packs) + { + if (packs.Count == 0) return; + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.SdkAnalyzerPacks)); + foreach (string name in packs) writer.WriteLine(name); + } + + internal static void WriteSdkAnalyzerConfigPolicySection(TextWriter writer, SortedSet policies) + { + if (policies.Count == 0) return; + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.SdkAnalyzerConfigPolicy)); + foreach (string policy in policies) writer.WriteLine(policy); + } + + // Returns the ref-pack name if is rooted under + // /packs///... and the pack follows the shared-framework + // .App.Ref naming convention; otherwise null. Workload packs use independent + // version schemes and must remain explicit metadata/analyzer references. + internal static string? TryExtractRefPackName(string? portablePath) + { + if (portablePath == null) return null; + if (!portablePath.StartsWith(DotNetPacksPrefix, StringComparison.OrdinalIgnoreCase)) return null; + int nameStart = DotNetPacksPrefix.Length; + int nameEnd = portablePath.IndexOf('/', nameStart); + if (nameEnd <= nameStart) return null; + string packName = portablePath.Substring(nameStart, nameEnd - nameStart); + if (!packName.EndsWith(".App.Ref", StringComparison.OrdinalIgnoreCase)) return null; + // Require at least one more '/' after the version segment so we don't + // misclassify /packs/Foo/Bar (no file under it). + int verEnd = portablePath.IndexOf('/', nameEnd + 1); + if (verEnd < 0) return null; + return packName; + } + + // Returns the canonical targeting-pack package name when the item is rooted under + // ///... and MSBuild marked it as a framework-reference asset. + internal static string? TryExtractNuGetRefPackName(ITaskItem item, string? portablePath, TargetFramework targetFramework) + { + if (portablePath == null) return null; + const string NuGetPrefix = PathSentinels.Nuget + "/"; + if (!portablePath.StartsWith(NuGetPrefix, StringComparison.OrdinalIgnoreCase)) return null; + + int packageStart = NuGetPrefix.Length; + int packageEnd = portablePath.IndexOf('/', packageStart); + if (packageEnd <= packageStart) return null; + + int versionStart = packageEnd + 1; + int versionEnd = portablePath.IndexOf('/', versionStart); + if (versionEnd <= versionStart) return null; + + string packageId = portablePath.Substring(packageStart, packageEnd - packageStart); + string packageVersion = portablePath.Substring(versionStart, versionEnd - versionStart); + string pathUnderPackage = portablePath.Substring(versionEnd + 1); + + if (!IsNuGetRefPackAssetPath(pathUnderPackage, targetFramework)) return null; + + string? canonicalPackageId = TryGetKnownNuGetFrameworkPackPackageId(packageId); + if (canonicalPackageId == null) + { + return null; + } + + string metadataPackageId = item.GetMetadata("NuGetPackageId"); + string metadataPackageVersion = item.GetMetadata("NuGetPackageVersion"); + + if ((!string.IsNullOrWhiteSpace(metadataPackageId) + && !string.Equals(metadataPackageId, packageId, StringComparison.OrdinalIgnoreCase)) + || (!string.IsNullOrWhiteSpace(metadataPackageVersion) + && !string.Equals(metadataPackageVersion, packageVersion, StringComparison.OrdinalIgnoreCase))) + { + return null; + } + + // Always return the canonical package id from `TryGetKnownNuGetFrameworkPackPackageId`, + // never the case-as-NuGet-emitted `metadataPackageId`. NuGet preserves the casing + // from the package's own `.nuspec`, which has historically varied across .NET + // SDK versions and feeds (e.g. `microsoft.netcore.app.ref` vs `Microsoft.NETCore.App.Ref`). + // Echoing that casing through to the cache reintroduces the very environment + // dependence this PR is eliminating. The canonical id is the single source of + // truth used elsewhere in the writer and reader. + return canonicalPackageId; + } + + internal static string? TryExtractSdkAnalyzerPackName(ITaskItem item, string? portablePath, ITaskItem[]? sdkKnownAnalyzerPacks, TargetFramework targetFramework) + { + if (portablePath == null) return null; + const string NuGetPrefix = PathSentinels.Nuget + "/"; + if (!portablePath.StartsWith(NuGetPrefix, StringComparison.OrdinalIgnoreCase)) return null; + + int packageStart = NuGetPrefix.Length; + int packageEnd = portablePath.IndexOf('/', packageStart); + if (packageEnd <= packageStart) return null; + + int versionStart = packageEnd + 1; + int versionEnd = portablePath.IndexOf('/', versionStart); + if (versionEnd <= versionStart) return null; + + string packageId = portablePath.Substring(packageStart, packageEnd - packageStart); + string packageVersion = portablePath.Substring(versionStart, versionEnd - versionStart); + string pathUnderPackage = portablePath.Substring(versionEnd + 1); + if (!IsNuGetAnalyzerAssetPath(pathUnderPackage)) return null; + + string metadataPackageId = item.GetMetadata("NuGetPackageId"); + string metadataPackageVersion = item.GetMetadata("NuGetPackageVersion"); + if (!string.Equals(metadataPackageId, packageId, StringComparison.OrdinalIgnoreCase) + || !string.Equals(metadataPackageVersion, packageVersion, StringComparison.OrdinalIgnoreCase) + || !IsSdkKnownAnalyzerPack(sdkKnownAnalyzerPacks, metadataPackageId, targetFramework)) + { + return null; + } + + return metadataPackageId; + } + + internal static string? TryExtractNetFrameworkReferenceAssembly(string? portablePath, TargetFramework targetFramework) + { + if (portablePath == null || string.IsNullOrWhiteSpace(targetFramework.VersionString)) + { + return null; + } + + if (portablePath.StartsWith(NetFxRefPrefix, StringComparison.OrdinalIgnoreCase)) + { + string netFxRelative = portablePath.Substring(NetFxRefPrefix.Length); + int netFxVersionEnd = netFxRelative.IndexOf('/'); + string netFxVersion = netFxVersionEnd >= 0 ? netFxRelative.Substring(0, netFxVersionEnd) : netFxRelative; + return IsSameNetFrameworkVersion(netFxVersion, targetFramework.VersionString!) && IsNetFrameworkReferenceAssemblyPath(netFxRelative) + ? "v" + targetFramework.VersionString + netFxRelative.Substring(netFxVersion.Length) + : null; + } + + if (!portablePath.StartsWith(NuGetPrefix, StringComparison.OrdinalIgnoreCase)) + { + return TryExtractNetFrameworkReferenceAssemblyFromFrameworkRootPath(portablePath, targetFramework.VersionString!); + } + + int packageStart = NuGetPrefix.Length; + int packageEnd = portablePath.IndexOf('/', packageStart); + if (packageEnd <= packageStart) + { + return null; + } + + string packageId = portablePath.Substring(packageStart, packageEnd - packageStart); + if (!IsNetFrameworkReferenceAssembliesPackage(packageId)) + { + return null; + } + + int versionStart = packageEnd + 1; + int versionEnd = portablePath.IndexOf('/', versionStart); + if (versionEnd <= versionStart) + { + return null; + } + + string pathUnderPackage = portablePath.Substring(versionEnd + 1); + const string buildPrefix = "build/.NETFramework/"; + if (!pathUnderPackage.StartsWith(buildPrefix, StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + int frameworkVersionStart = buildPrefix.Length; + int frameworkVersionEnd = pathUnderPackage.IndexOf('/', frameworkVersionStart); + if (frameworkVersionEnd <= frameworkVersionStart) + { + return null; + } + + string packageFrameworkVersion = pathUnderPackage.Substring(frameworkVersionStart, frameworkVersionEnd - frameworkVersionStart); + string packageRelativePath = pathUnderPackage.Substring(frameworkVersionStart); + return IsSameNetFrameworkVersion(packageFrameworkVersion, targetFramework.VersionString!) && IsNetFrameworkReferenceAssemblyPath(packageRelativePath) + ? "v" + targetFramework.VersionString + packageRelativePath.Substring(packageFrameworkVersion.Length) + : null; + } + + private static bool IsNuGetRefPackAssetPath(string pathUnderPackage, TargetFramework targetFramework) + { + if (string.IsNullOrWhiteSpace(targetFramework.Alias)) return false; + + string refPrefix = "ref/" + targetFramework.Alias + "/"; + if (pathUnderPackage.StartsWith(refPrefix, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (!string.IsNullOrWhiteSpace(targetFramework.VersionString)) + { + refPrefix = "ref/net" + targetFramework.VersionString + "/"; + if (pathUnderPackage.StartsWith(refPrefix, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return pathUnderPackage.StartsWith("analyzers/", StringComparison.OrdinalIgnoreCase); + } + + private static bool IsNuGetAnalyzerAssetPath(string pathUnderPackage) + => pathUnderPackage.StartsWith("analyzers/", StringComparison.OrdinalIgnoreCase); + + private static bool IsSdkKnownAnalyzerPack(ITaskItem[]? sdkKnownAnalyzerPacks, string packageId, TargetFramework targetFramework) + { + if (sdkKnownAnalyzerPacks == null || string.IsNullOrWhiteSpace(packageId)) return false; + + foreach (ITaskItem item in sdkKnownAnalyzerPacks) + { + if (item == null) continue; + string knownPackageId = item.GetMetadata("PackageId"); + if (string.IsNullOrWhiteSpace(knownPackageId)) + { + knownPackageId = item.ItemSpec; + } + + if (!string.Equals(knownPackageId, packageId, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + string knownTargetFramework = item.GetMetadata(ProjectProperties.TargetFramework); + if (!string.IsNullOrWhiteSpace(knownTargetFramework) + && !string.IsNullOrWhiteSpace(targetFramework.Alias) + && !string.Equals(knownTargetFramework, targetFramework.Alias, StringComparison.OrdinalIgnoreCase) + && !IsSameMajorVersion(knownTargetFramework, targetFramework.Version)) + { + continue; + } + + return true; + } + + return false; + } + + /// + /// Checks whether a TFM alias from SDK metadata (e.g. "net8.0", "netcoreapp3.1") has the + /// same major version as the given parsed version. Handles both net and netcoreapp prefixes. + /// + private static bool IsSameMajorVersion(string sdkTargetFramework, Version? targetVersion) + { + if (targetVersion == null) return false; + + ReadOnlySpan span = sdkTargetFramework.AsSpan(); + if (span.StartsWith("netcoreapp", StringComparison.OrdinalIgnoreCase)) + span = span.Slice("netcoreapp".Length); + else if (span.StartsWith("net", StringComparison.OrdinalIgnoreCase)) + span = span.Slice("net".Length); + else + return false; + + // Extract digits up to the first non-digit (e.g. '.' or '-') + int end = 0; + while (end < span.Length && char.IsDigit(span[end])) end++; + return end > 0 && int.TryParse(span.Slice(0, end).ToString(), out int major) && major == targetVersion.Major; + } + + private static bool IsNetFrameworkCoreAssemblyName(string? fileName) + => string.Equals(fileName, "mscorlib.dll", StringComparison.OrdinalIgnoreCase) + || string.Equals(fileName, "System.dll", StringComparison.OrdinalIgnoreCase) + || string.Equals(fileName, "System.Core.dll", StringComparison.OrdinalIgnoreCase); + + private static bool IsNetFrameworkReferenceAssembliesPackage(string packageId) + => string.Equals(packageId, "microsoft.netframework.referenceassemblies", StringComparison.OrdinalIgnoreCase) + || packageId.StartsWith("microsoft.netframework.referenceassemblies.net", StringComparison.OrdinalIgnoreCase); + + private static bool IsNetFrameworkReferenceAssemblyPath(string relativePath) + => relativePath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) + && relativePath.IndexOf('\\') < 0 + && !relativePath.Contains("../", StringComparison.Ordinal) + && !Path.GetFileName(relativePath).Contains("..", StringComparison.Ordinal); + + private static string? TryExtractNetFrameworkReferenceAssemblyFromFrameworkRootPath(string portablePath, string targetVersion) + { + const string frameworkMarker = ".NETFramework/"; + int frameworkMarkerIndex = portablePath.IndexOf(frameworkMarker, StringComparison.OrdinalIgnoreCase); + if (frameworkMarkerIndex < 0) + { + return null; + } + + string relative = portablePath.Substring(frameworkMarkerIndex + frameworkMarker.Length); + int versionEnd = relative.IndexOf('/'); + if (versionEnd <= 0) + { + return null; + } + + string version = relative.Substring(0, versionEnd); + return IsSameNetFrameworkVersion(version, targetVersion) && IsNetFrameworkReferenceAssemblyPath(relative) + ? "v" + targetVersion + relative.Substring(version.Length) + : null; + } + + /// + /// Compares a version extracted from a file path (e.g. "v4.7.2") with the target framework + /// version string (e.g. "4.7.2"). Tolerates a leading "v" on either side. + /// + private static bool IsSameNetFrameworkVersion(string pathVersion, string targetFrameworkVersion) + { + string left = pathVersion.StartsWith("v", StringComparison.OrdinalIgnoreCase) ? pathVersion.Substring(1) : pathVersion; + string right = targetFrameworkVersion.StartsWith("v", StringComparison.OrdinalIgnoreCase) ? targetFrameworkVersion.Substring(1) : targetFrameworkVersion; + return Version.TryParse(left, out Version? leftVersion) + && Version.TryParse(right, out Version? rightVersion) + && leftVersion.Major == rightVersion.Major + && (leftVersion.Minor < 0 ? 0 : leftVersion.Minor) == (rightVersion.Minor < 0 ? 0 : rightVersion.Minor) + && (leftVersion.Build < 0 ? 0 : leftVersion.Build) == (rightVersion.Build < 0 ? 0 : rightVersion.Build); + } + + private static string BuildNetAnalyzersPolicyLine(ITaskItem item, TargetFramework targetFramework) + { + var builder = new StringBuilder("Microsoft.NET.Sdk/analyzers"); + string analysisLevel = GetMetadataValue(item, "AnalysisLevel"); + string effectiveAnalysisLevel = GetMetadataValue(item, "EffectiveAnalysisLevel"); + string canonicalAnalysisLevel = CanonicalizeAnalysisLevel(analysisLevel, effectiveAnalysisLevel, targetFramework, out string parsedAnalysisLevelSuffix); + string analysisLevelSuffix = GetMetadataValue(item, "AnalysisLevelSuffix"); + string codeAnalysisTreatWarningsAsErrors = GetMetadataValue(item, "CodeAnalysisTreatWarningsAsErrors"); + string effectiveCodeAnalysisTreatWarningsAsErrors = GetMetadataValue(item, "EffectiveCodeAnalysisTreatWarningsAsErrors"); + + AppendPolicyValue(builder, "AnalysisLevel", canonicalAnalysisLevel); + AppendPolicyValue(builder, "AnalysisMode", GetMetadataValue(item, "AnalysisMode")); + + SplitAnalysisLevel(canonicalAnalysisLevel, out _, out string parsedCanonicalAnalysisLevelSuffix); + string effectiveAnalysisLevelSuffix = !string.IsNullOrWhiteSpace(analysisLevelSuffix) ? analysisLevelSuffix : parsedAnalysisLevelSuffix; + if (!StringEquals(effectiveAnalysisLevelSuffix, parsedCanonicalAnalysisLevelSuffix)) + { + AppendPolicyValue(builder, "AnalysisLevelSuffix", effectiveAnalysisLevelSuffix); + } + + string rulesVersion = GetMetadataValue(item, "MicrosoftCodeAnalysisNetAnalyzersRulesVersion"); + if (!StringEquals(rulesVersion, TrimTrailingDotZero(effectiveAnalysisLevel))) + { + AppendPolicyValue(builder, "MicrosoftCodeAnalysisNetAnalyzersRulesVersion", rulesVersion); + } + + AppendPolicyValue(builder, "CodeAnalysisTreatWarningsAsErrors", codeAnalysisTreatWarningsAsErrors); + if (!StringEquals(effectiveCodeAnalysisTreatWarningsAsErrors, codeAnalysisTreatWarningsAsErrors)) + { + AppendPolicyValue(builder, "EffectiveCodeAnalysisTreatWarningsAsErrors", effectiveCodeAnalysisTreatWarningsAsErrors); + } + + return builder.ToString(); + } + + private static string BuildCodeStylePolicyLine(ITaskItem item, string languageSegment, TargetFramework targetFramework) + { + var builder = new StringBuilder("Microsoft.NET.Sdk/codestyle/"); + builder.Append(languageSegment); + + string analysisLevel = GetMetadataValue(item, "AnalysisLevel"); + string analysisMode = GetMetadataValue(item, "AnalysisMode"); + string analysisLevelSuffix = GetMetadataValue(item, "AnalysisLevelSuffix"); + string effectiveAnalysisLevel = GetMetadataValue(item, "EffectiveAnalysisLevel"); + string canonicalAnalysisLevel = CanonicalizeAnalysisLevel(analysisLevel, effectiveAnalysisLevel, targetFramework, out string parsedAnalysisLevelSuffix); + string analysisLevelStyle = GetMetadataValue(item, "AnalysisLevelStyle"); + string analysisModeStyle = GetMetadataValue(item, "AnalysisModeStyle"); + string analysisLevelSuffixStyle = GetMetadataValue(item, "AnalysisLevelSuffixStyle"); + + AppendPolicyValue(builder, "AnalysisLevel", canonicalAnalysisLevel); + AppendPolicyValue(builder, "AnalysisMode", analysisMode); + + if (!StringEquals(analysisLevelStyle, analysisLevel)) + { + AppendPolicyValue(builder, "AnalysisLevelStyle", analysisLevelStyle); + } + + if (!StringEquals(analysisModeStyle, analysisMode)) + { + AppendPolicyValue(builder, "AnalysisModeStyle", analysisModeStyle); + } + + SplitAnalysisLevel(canonicalAnalysisLevel, out _, out string parsedCanonicalAnalysisLevelSuffix); + string effectiveAnalysisLevelSuffix = !string.IsNullOrWhiteSpace(analysisLevelSuffix) ? analysisLevelSuffix : parsedAnalysisLevelSuffix; + if (!StringEquals(effectiveAnalysisLevelSuffix, parsedCanonicalAnalysisLevelSuffix)) + { + AppendPolicyValue(builder, "AnalysisLevelSuffix", effectiveAnalysisLevelSuffix); + } + + string styleFallbackSuffix = string.IsNullOrWhiteSpace(analysisLevelStyle) ? effectiveAnalysisLevelSuffix : parsedAnalysisLevelSuffix; + SplitAnalysisLevel(string.IsNullOrWhiteSpace(analysisLevelStyle) ? analysisLevel : analysisLevelStyle, out _, out string parsedAnalysisLevelSuffixStyle); + if (!StringEquals(analysisLevelSuffixStyle, parsedAnalysisLevelSuffixStyle) && !StringEquals(analysisLevelSuffixStyle, styleFallbackSuffix)) + { + AppendPolicyValue(builder, "AnalysisLevelSuffixStyle", analysisLevelSuffixStyle); + } + + return builder.ToString(); + } + + private static string CanonicalizeAnalysisLevel(string analysisLevel, string effectiveAnalysisLevel, TargetFramework targetFramework, out string parsedAnalysisLevelSuffix) + { + SplitAnalysisLevel(analysisLevel, out string analysisLevelPrefix, out parsedAnalysisLevelSuffix); + string analysisLevelCore = string.IsNullOrWhiteSpace(analysisLevelPrefix) ? analysisLevel : analysisLevelPrefix; + return IsDefaultAnalysisLevel(analysisLevelCore, effectiveAnalysisLevel, targetFramework) + ? string.Empty + : analysisLevel; + } + + private static bool IsDefaultAnalysisLevel(string analysisLevelCore, string effectiveAnalysisLevel, TargetFramework targetFramework) + { + if (string.IsNullOrWhiteSpace(analysisLevelCore)) + { + return true; + } + + if (targetFramework.Version == null) + { + return false; + } + + if (targetFramework.IsNetFramework) + { + return string.Equals(analysisLevelCore, "latest", StringComparison.OrdinalIgnoreCase) + && (string.IsNullOrWhiteSpace(effectiveAnalysisLevel) + || string.Equals(effectiveAnalysisLevel, "latest", StringComparison.OrdinalIgnoreCase)); + } + + if (string.Equals(analysisLevelCore, "latest", StringComparison.OrdinalIgnoreCase)) + { + return string.IsNullOrWhiteSpace(effectiveAnalysisLevel) + || string.Equals(effectiveAnalysisLevel, "latest", StringComparison.OrdinalIgnoreCase) + || VersionGreaterThanOrEquals(effectiveAnalysisLevel, targetFramework.Version); + } + + return VersionEquals(analysisLevelCore, targetFramework.Version); + } + + private static string? TryGetKnownNuGetFrameworkPackPackageId(string packageId) + { + if (string.Equals(packageId, "microsoft.netcore.app.ref", StringComparison.OrdinalIgnoreCase)) + { + return "Microsoft.NETCore.App.Ref"; + } + + if (string.Equals(packageId, "microsoft.aspnetcore.app.ref", StringComparison.OrdinalIgnoreCase)) + { + return "Microsoft.AspNetCore.App.Ref"; + } + + if (string.Equals(packageId, "microsoft.windowsdesktop.app.ref", StringComparison.OrdinalIgnoreCase)) + { + return "Microsoft.WindowsDesktop.App.Ref"; + } + + return null; + } + + internal static string CanonicalizeSdkAnalyzerConfigPolicyLine(string line, string? targetFrameworkIdentifier, string? targetFrameworkVersion) + { + if (!line.StartsWith("Microsoft.NET.Sdk/analyzers", StringComparison.OrdinalIgnoreCase) + && !line.StartsWith("Microsoft.NET.Sdk/codestyle/", StringComparison.OrdinalIgnoreCase)) + { + return line; + } + + string[] parts = line.Split('|'); + int analysisLevelIndex = FindPolicyValueIndex(parts, "AnalysisLevel"); + if (analysisLevelIndex < 0) + { + return line; + } + + var targetFramework = new TargetFramework(alias: null, targetFrameworkIdentifier, targetFrameworkVersion); + string analysisLevel = parts[analysisLevelIndex].Substring("AnalysisLevel=".Length); + string canonicalAnalysisLevel = CanonicalizeAnalysisLevel(analysisLevel, effectiveAnalysisLevel: string.Empty, targetFramework, out string parsedAnalysisLevelSuffix); + if (StringEquals(canonicalAnalysisLevel, analysisLevel)) + { + return line; + } + + var canonicalParts = new List(parts.Length + 1); + for (int i = 0; i < parts.Length; i++) + { + if (i == analysisLevelIndex) + { + if (!string.IsNullOrWhiteSpace(canonicalAnalysisLevel)) + { + canonicalParts.Add("AnalysisLevel=" + EscapePolicyValue(canonicalAnalysisLevel)); + } + + continue; + } + + canonicalParts.Add(parts[i]); + } + + if (!string.IsNullOrWhiteSpace(parsedAnalysisLevelSuffix) + && FindPolicyValueIndex(parts, "AnalysisLevelSuffix") < 0) + { + canonicalParts.Add("AnalysisLevelSuffix=" + EscapePolicyValue(parsedAnalysisLevelSuffix)); + } + + return string.Join("|", canonicalParts); + } + + private static int FindPolicyValueIndex(string[] parts, string name) + { + string prefix = name + "="; + for (int i = 1; i < parts.Length; i++) + { + if (parts[i].StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + return i; + } + } + + return -1; + } + + private static void AppendPolicyValue(StringBuilder builder, string name, string value) + { + if (string.IsNullOrWhiteSpace(value) || string.Equals(value, "*Undefined*", StringComparison.Ordinal)) + { + return; + } + + builder.Append('|'); + builder.Append(name); + builder.Append('='); + builder.Append(EscapePolicyValue(value.Trim())); + } + + private static string EscapePolicyValue(string value) + { + return value + .Replace("%", "%25") + .Replace("|", "%7C") + .Replace("=", "%3D") + .Replace("\r", string.Empty) + .Replace("\n", string.Empty); + } + + private static string GetMetadataValue(ITaskItem item, string name) + => item.GetMetadata(name) ?? string.Empty; + + private static bool IsTrue(string value) + => string.Equals(value, "true", StringComparison.OrdinalIgnoreCase); + + private static bool StringEquals(string? left, string? right) + => string.Equals(left ?? string.Empty, right ?? string.Empty, StringComparison.OrdinalIgnoreCase); + + private static string TrimTrailingDotZero(string value) + { + while (value.EndsWith(".0", StringComparison.Ordinal)) + { + value = value.Substring(0, value.Length - 2); + } + + return value; + } + + private static void SplitAnalysisLevel(string analysisLevel, out string prefix, out string suffix) + { + int separator = analysisLevel.IndexOf('-'); + if (separator <= 0 || separator == analysisLevel.Length - 1) + { + prefix = string.Empty; + suffix = string.Empty; + return; + } + + prefix = analysisLevel.Substring(0, separator); + suffix = analysisLevel.Substring(separator + 1); + } + + private static string? TryGetSdkCodeStyleLanguageSegment(string language) + { + if (string.Equals(language, "C#", StringComparison.OrdinalIgnoreCase) + || string.Equals(language, "CSharp", StringComparison.OrdinalIgnoreCase) + || string.Equals(language, "cs", StringComparison.OrdinalIgnoreCase)) + { + return "cs"; + } + + return null; + } + + private static bool VersionEquals(string value, Version version) + { + if (!Version.TryParse(value, out Version? parsedVersion)) + { + return int.TryParse(value, out int major) + && major == version.Major + && version.Minor == 0; + } + + int minor = parsedVersion.Minor < 0 ? 0 : parsedVersion.Minor; + return parsedVersion.Major == version.Major && minor == version.Minor; + } + + private static bool VersionGreaterThanOrEquals(string value, Version version) + { + if (!Version.TryParse(value, out Version? parsedVersion)) + { + return int.TryParse(value, out int major) + && major >= version.Major + && version.Minor == 0; + } + + int minor = parsedVersion.Minor < 0 ? 0 : parsedVersion.Minor; + return parsedVersion.Major > version.Major + || (parsedVersion.Major == version.Major && minor >= version.Minor); + } + + /// + /// Bundles the MSBuild-evaluated target framework properties for a project slice, + /// pre-computing a parsed from the version string. + /// + internal readonly struct TargetFramework + { + /// The TFM alias, e.g. net8.0, net472. From $(TargetFramework). + public string? Alias { get; } + + /// E.g. .NETCoreApp, .NETFramework. From $(TargetFrameworkIdentifier). + public string? Identifier { get; } + + /// E.g. 8.0, 4.8. From $(TargetFrameworkVersion), with the leading "v" stripped. + public string? VersionString { get; } + + /// Parsed form of . Null when the raw value is missing or malformed. + public Version? Version { get; } + + /// Whether the target framework identifier is .NETFramework. + public bool IsNetFramework => string.Equals(this.Identifier, ".NETFramework", StringComparison.OrdinalIgnoreCase); + + public TargetFramework(string? alias, string? identifier, string? version) + { + this.Alias = alias; + this.Identifier = identifier; + + if (!string.IsNullOrWhiteSpace(version)) + { + this.VersionString = version!.StartsWith("v", StringComparison.OrdinalIgnoreCase) ? version.Substring(1) : version; + this.Version = Version.TryParse(this.VersionString, out Version v) ? v : null; + } + } + } + + private static string? GetItemValue(ITaskItem[]? items, string itemSpec) + { + if (items == null) return null; + foreach (ITaskItem item in items) + { + if (item == null) continue; + if (!string.Equals(item.ItemSpec, itemSpec, StringComparison.OrdinalIgnoreCase)) continue; + + string value = item.GetMetadata("Value"); + return string.IsNullOrWhiteSpace(value) ? null : value; + } + + return null; + } + + private static void EmitMetadataRefSection(TextWriter writer, List> portableItems) + { + // Required item type — always write header + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.MetadataReferences)); + if (portableItems.Count == 0) return; + + var sortedPaths = new List(portableItems.Count); + var lookup = new Dictionary(PathComparer); + foreach (KeyValuePair kvp in portableItems) + { + // First occurrence wins. See ``EmitSourceFileSection`` for the rationale — + // two upstream items can collapse to the same portable form. + if (!lookup.ContainsKey(kvp.Key)) + { + lookup.Add(kvp.Key, kvp.Value); + sortedPaths.Add(kvp.Key); + } + } + + EmitCompressedWithMetadata(writer, sortedPaths, 0, "", lookup, EmitMetadataReferenceMetadata); + } + + // Trie-based path compression: groups paths by directory segment so each + // directory is emitted exactly once with its files and subdirectories nested + // under it. Output is wire-compatible with CacheFileReader.ExpandCompressedPaths + // (lines ending in '/' are directory headers pushed onto an indent stack). + internal static void EmitCompressed(StringBuilder sb, List paths, int indent) + { + using var writer = new StringWriter(sb); + EmitCompressed(writer, paths, indent); + } + + private static void EmitCompressed(TextWriter writer, IEnumerable paths, int indent) + { + PathTrieNode root = BuildTrie(paths); + EmitTrie(writer, root, indent, lookup: null, emitMetadata: null); + } + + private static void EmitCompressedWithMetadata( + TextWriter writer, List paths, int indent, + string accPrefix, Dictionary lookup, + Action emitMetadata) + { + // accPrefix is unused here: the trie carries each leaf's full portable + // path directly so the metadata lookup does not need a running prefix. + _ = accPrefix; + PathTrieNode root = BuildTrie(paths); + EmitTrie(writer, root, indent, lookup, emitMetadata); + } + + // Builds a directory-segment trie from already-sorted portable paths. + // Sentinel tokens (e.g. "", "") naturally land as a single + // first segment because they contain no '/' before the next separator. + private static PathTrieNode BuildTrie(IEnumerable paths) + { + var root = new PathTrieNode(); + foreach (string path in paths) + { + if (string.IsNullOrEmpty(path)) continue; + string[] segments = path.Split('/'); + PathTrieNode node = root; + for (int i = 0; i < segments.Length; i++) + { + string seg = segments[i]; + if (seg.Length == 0) + { + // Skip empty segments from a leading '/' or doubled separator. + continue; + } + bool isLeaf = i == segments.Length - 1; + if (isLeaf) + { + node.Files.Add(new PathTrieLeaf(seg, path)); + } + else + { + if (!node.Directories.TryGetValue(seg, out PathTrieNode? child)) + { + child = new PathTrieNode(); + node.Directories.Add(seg, child); + } + node = child; + } + } + } + return root; + } + + private static void EmitTrie( + TextWriter writer, PathTrieNode node, int indent, + Dictionary? lookup, + Action? emitMetadata) + { + var directories = new List<(string DisplayName, PathTrieNode? Dir, PathTrieLeaf? File)>(node.Directories.Count); + var files = new List<(string DisplayName, PathTrieLeaf File)>(node.Files.Count); + + foreach (KeyValuePair kvp in node.Directories) + { + if (TryCollapseSingleFileSubtree(kvp.Value, kvp.Key, out PathTrieLeaf leaf, out string leafDisplayName)) + { + directories.Add((leafDisplayName, null, leaf)); + continue; + } + + // Collapse chains of single-child directories so a/b/c/ emits on one line + // when each intermediate has exactly one directory child and no files. + string name = kvp.Key; + PathTrieNode target = kvp.Value; + while (target.Files.Count == 0 && target.Directories.Count == 1) + { + KeyValuePair only = target.Directories.First(); + name = name + "/" + only.Key; + target = only.Value; + } + directories.Add((name, target, null)); + } + foreach (PathTrieLeaf leaf in node.Files) + { + files.Add((leaf.Name, leaf)); + } + + directories.Sort(static (a, b) => ComparePortablePaths(a.DisplayName, b.DisplayName)); + files.Sort(static (a, b) => ComparePortablePaths(a.DisplayName, b.DisplayName)); + + // Emit directory-origin entries first and direct files second, with + // deterministic ordinal-ignore-case ordering within each group. A + // collapsed single-file subtree is still a directory-origin entry, so + // package paths under / keep package-name alphabetical order. + foreach ((string displayName, PathTrieNode? dir, PathTrieLeaf? file) in directories) + { + WriteIndent(writer, indent); + if (dir is not null) + { + writer.Write(displayName); + writer.WriteLine('/'); + EmitTrie(writer, dir, indent + 1, lookup, emitMetadata); + } + else + { + PathTrieLeaf leaf = file!.Value; + writer.WriteLine(displayName); + if (lookup != null && emitMetadata != null + && lookup.TryGetValue(leaf.FullPath, out ITaskItem? refItem)) + { + emitMetadata(writer, indent + 1, refItem); + } + } + } + + foreach ((string displayName, PathTrieLeaf leaf) in files) + { + WriteIndent(writer, indent); + writer.WriteLine(displayName); + if (lookup != null && emitMetadata != null + && lookup.TryGetValue(leaf.FullPath, out ITaskItem? refItem)) + { + emitMetadata(writer, indent + 1, refItem); + } + } + } + + private static bool TryCollapseSingleFileSubtree( + PathTrieNode node, string prefix, + out PathTrieLeaf leaf, out string displayName) + { + while (node.Files.Count == 0 && node.Directories.Count == 1) + { + KeyValuePair only = node.Directories.First(); + prefix = prefix + "/" + only.Key; + node = only.Value; + } + + if (node.Files.Count == 1 && node.Directories.Count == 0) + { + leaf = node.Files[0]; + displayName = prefix + "/" + leaf.Name; + return true; + } + + leaf = default; + displayName = ""; + return false; + } + + private sealed class PathTrieNode + { + public SortedDictionary Directories { get; } = + new(PathComparer); + public List Files { get; } = new(); + } + + private readonly struct PathTrieLeaf(string name, string fullPath) + { + public string Name { get; } = name; + public string FullPath { get; } = fullPath; + } + + private static void EmitSourceFileMetadata(TextWriter writer, int indent, ITaskItem item) + { + string link = item.GetMetadata(ProjectItems.Compile.Link); + if (!string.IsNullOrWhiteSpace(link)) + { + WriteIndent(writer, indent); + writer.Write("@link="); + writer.WriteLine(link.Replace('\\', '/')); + } + } + + private static void EmitProjectReferenceMetadata(TextWriter writer, int indent, ITaskItem item) + { + string referenceOutputAssembly = item.GetMetadata(ProjectItems.ProjectReference.ReferenceOutputAssembly); + if (string.Equals(referenceOutputAssembly, "false", StringComparison.OrdinalIgnoreCase)) + { + WriteIndent(writer, indent); + writer.WriteLine("@ReferenceOutputAssembly=false"); + } + } + + private static void EmitEmbeddedResourceSection(TextWriter writer, ITaskItem[]? items, CachePathResolver resolver) + { + if (items == null || items.Length == 0) return; + + var sortedPaths = new List(items.Length); + var lookup = new Dictionary(PathComparer); + foreach (ITaskItem item in items) + { + if (item == null) continue; + string path = item.ItemSpec; + if (string.IsNullOrEmpty(path)) continue; + + string portable = resolver.ToPortable(path); + if (!lookup.ContainsKey(portable)) + { + sortedPaths.Add(portable); + lookup[portable] = item; + } + } + + sortedPaths.Sort(ComparePortablePaths); + if (sortedPaths.Count == 0) return; + + writer.WriteLine(); + writer.WriteLine(CacheFormat.SectionHeader(CacheFormat.Sections.EmbeddedResources)); + EmitCompressedWithMetadata(writer, sortedPaths, 0, "", lookup, EmitEmbeddedResourceMetadata); + } + + private static void EmitEmbeddedResourceMetadata(TextWriter writer, int indent, ITaskItem item) + { + string generator = item.GetMetadata(ProjectItems.EmbeddedResource.Generator); + if (!string.IsNullOrEmpty(generator)) + { + WriteIndent(writer, indent); + writer.Write("@Generator="); + writer.WriteLine(generator); + } + + string lastGenOutput = item.GetMetadata(ProjectItems.EmbeddedResource.LastGenOutput); + if (!string.IsNullOrEmpty(lastGenOutput)) + { + WriteIndent(writer, indent); + writer.Write("@LastGenOutput="); + writer.WriteLine(lastGenOutput); + } + + string customToolNamespace = item.GetMetadata(ProjectItems.EmbeddedResource.CustomToolNamespace); + if (!string.IsNullOrEmpty(customToolNamespace)) + { + WriteIndent(writer, indent); + writer.Write("@CustomToolNamespace="); + writer.WriteLine(customToolNamespace); + } + } + + private static void EmitMetadataReferenceMetadata(TextWriter writer, int indent, ITaskItem item) + { + string aliases = item.GetMetadata("Aliases"); + if (!string.IsNullOrEmpty(aliases) && aliases != "global") + { + WriteIndent(writer, indent); + writer.Write("@aliases="); + writer.WriteLine(aliases); + } + + string embedStr = item.GetMetadata("EmbedInteropTypes"); + if (string.Equals(embedStr, "true", StringComparison.OrdinalIgnoreCase)) + { + WriteIndent(writer, indent); + writer.WriteLine("@embedInteropTypes"); + } + } + + private static void WriteIndent(TextWriter writer, int count) + { + for (int i = 0; i < count; i++) writer.Write(' '); + } + + private static List> ToSortedKvps(ITaskItem[]? items) + { + var list = new List>(); + if (items == null) return list; + foreach (ITaskItem item in items) + { + if (item == null) continue; + list.Add(new KeyValuePair( + item.ItemSpec ?? string.Empty, + item.GetMetadata("Value") ?? string.Empty)); + } + list.Sort(static (a, b) => StringComparer.OrdinalIgnoreCase.Compare(a.Key, b.Key)); + return list; + } +} + +internal readonly struct ProjectDataDuplicateItemDiagnostic +{ + public ProjectDataDuplicateItemDiagnostic(string projectFilePath, string section, string itemSpec) + { + this.ProjectFilePath = projectFilePath; + this.Section = section; + this.ItemSpec = itemSpec; + } + + public string ProjectFilePath { get; } + public string Section { get; } + public string ItemSpec { get; } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/StringComparers.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/StringComparers.cs new file mode 100644 index 0000000000000..d8c681fab9b07 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/StringComparers.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; + +namespace Microsoft.NET.ProjectData.Tasks; + +internal static class StringComparers +{ + public static StringComparer Paths { get; } = + RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; +} + +internal static class StringComparisons +{ + public static StringComparison Paths { get; } = + RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/ValidateProjectDataPackagesTask.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/ValidateProjectDataPackagesTask.cs new file mode 100644 index 0000000000000..f188b577851f4 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/ValidateProjectDataPackagesTask.cs @@ -0,0 +1,894 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json; +using Microsoft.Build.Framework; +using NuGet.Versioning; + +namespace Microsoft.NET.ProjectData.Tasks; + +/// +/// Validates that evaluated package references agree with the resolved restore graph and that +/// resolved package folders still exist. +/// +public sealed class ValidateProjectDataPackagesTask : Microsoft.Build.Utilities.Task +{ + [Required] + public string ProjectFilePath { get; set; } = string.Empty; + + public string AssetsFile { get; set; } = string.Empty; + + public string TargetFramework { get; set; } = string.Empty; + + public string TargetFrameworkMoniker { get; set; } = string.Empty; + + public bool ManagePackageVersionsCentrally { get; set; } + + public bool CentralPackageTransitivePinningEnabled { get; set; } + + public ITaskItem[] PackageReferences { get; set; } = []; + + public ITaskItem[] PackageVersions { get; set; } = []; + + public ITaskItem[] ResolvedPackages { get; set; } = []; + + public override bool Execute() + { + Dictionary resolvedPackagesById = this.GetResolvedPackagesById(); + Dictionary centralVersionsById = this.GetCentralVersionsById(); + RestoreGraphRequests? restoredRequests = this.GetRestoreGraphRequests(); + Dictionary? restoredRequestedVersionsById = restoredRequests?.DirectRequests; + var currentPackageIds = new HashSet( + this.PackageReferences.Select(static packageReference => packageReference.ItemSpec), + StringComparer.OrdinalIgnoreCase); + List missingPackages = []; + List missingRequestedVersions = []; + List incompatiblePackages = []; + List staleRequestedVersions = []; + List incompatibleCentralTransitiveVersions = []; + List staleCentralTransitiveVersions = []; + bool centralTransitivePinningModeChanged = restoredRequests?.CentralPackageTransitivePinningEnabled is bool restoredPinningEnabled && + restoredPinningEnabled != this.CentralPackageTransitivePinningEnabled; + + foreach (ITaskItem packageReference in this.PackageReferences) + { + if (string.Equals(packageReference.GetMetadata("IsImplicitlyDefined"), "true", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + string packageId = packageReference.ItemSpec; + if (!resolvedPackagesById.TryGetValue(packageId, out ITaskItem? resolvedPackage)) + { + missingPackages.Add(packageId); + continue; + } + + string requestedVersion = this.GetRequestedVersion(packageReference, centralVersionsById); + if (string.IsNullOrWhiteSpace(requestedVersion)) + { + missingRequestedVersions.Add(packageId); + continue; + } + + string resolvedVersion = GetResolvedVersion(resolvedPackage); + if (!VersionRange.TryParse(requestedVersion, out VersionRange? requestedRange)) + { + incompatiblePackages.Add($"{packageId} (invalid requested version '{requestedVersion}')"); + continue; + } + + if (restoredRequestedVersionsById is not null) + { + if (!restoredRequestedVersionsById.TryGetValue(packageId, out string? restoredRequestedVersion)) + { + staleRequestedVersions.Add($"{packageId} (current request '{requestedVersion}', missing from restored requests)"); + } + else if (!VersionRange.TryParse(restoredRequestedVersion, out VersionRange? restoredRequestedRange) || + !requestedRange.Equals(restoredRequestedRange)) + { + staleRequestedVersions.Add($"{packageId} (current request '{requestedVersion}', restored request '{restoredRequestedVersion}')"); + } + else if (restoredRequests!.DirectAssetSelections.TryGetValue(packageId, out PackageAssetSelection? restoredAssetSelection)) + { + PackageAssetSelection currentAssetSelection = GetCurrentAssetSelection(packageReference); + if (!currentAssetSelection.Equals(restoredAssetSelection)) + { + staleRequestedVersions.Add( + $"{packageId} (current assets '{currentAssetSelection}', restored assets '{restoredAssetSelection}')"); + } + } + } + + if (!NuGetVersion.TryParse(resolvedVersion, out NuGetVersion? resolvedNuGetVersion)) + { + incompatiblePackages.Add($"{packageId} (requested '{requestedVersion}', invalid resolved version '{resolvedVersion}')"); + } + else if (!requestedRange.Satisfies(resolvedNuGetVersion)) + { + incompatiblePackages.Add($"{packageId} (requested '{requestedVersion}', resolved '{resolvedVersion}')"); + } + else if (requestedRange.Float is not null && !requestedRange.Float.Satisfies(resolvedNuGetVersion)) + { + incompatiblePackages.Add($"{packageId} (requested '{requestedVersion}', resolved '{resolvedVersion}')"); + } + } + + if (this.CentralPackageTransitivePinningEnabled && restoredRequests is not null) + { + var activeCentralTransitiveVersionsById = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (KeyValuePair centralVersion in centralVersionsById) + { + if (!currentPackageIds.Contains(centralVersion.Key) && + restoredRequests.ResolvedVersions.TryGetValue(centralVersion.Key, out string? centralResolvedVersion) && + (restoredRequests.CentralTransitiveRequests.ContainsKey(centralVersion.Key) || + !restoredRequests.CentralVersions.TryGetValue(centralVersion.Key, out string? restoredCentralVersion) || + !AreEquivalentVersionRanges(centralVersion.Value, restoredCentralVersion))) + { + activeCentralTransitiveVersionsById[centralVersion.Key] = centralVersion.Value; + this.ValidateCentralTransitiveVersion( + centralVersion.Key, + centralVersion.Value, + centralResolvedVersion, + incompatibleCentralTransitiveVersions); + } + } + + foreach (KeyValuePair currentRequest in activeCentralTransitiveVersionsById) + { + if (!restoredRequests.CentralTransitiveRequests.TryGetValue(currentRequest.Key, out string? restoredRequest)) + { + staleCentralTransitiveVersions.Add($"{currentRequest.Key} (current request '{currentRequest.Value}', missing from restored central transitive requests)"); + } + else if (!AreEquivalentVersionRanges(currentRequest.Value, restoredRequest)) + { + staleCentralTransitiveVersions.Add($"{currentRequest.Key} (current request '{currentRequest.Value}', restored request '{restoredRequest}')"); + } + } + + foreach (KeyValuePair restoredRequest in restoredRequests.CentralTransitiveRequests) + { + if (!activeCentralTransitiveVersionsById.ContainsKey(restoredRequest.Key)) + { + staleCentralTransitiveVersions.Add($"{restoredRequest.Key} (restored request '{restoredRequest.Value}', no current active central transitive pin)"); + } + } + } + + if (restoredRequestedVersionsById is not null) + { + foreach (KeyValuePair restoredRequest in restoredRequestedVersionsById) + { + if (!currentPackageIds.Contains(restoredRequest.Key) && + !restoredRequests!.AutoReferencedRequestIds.Contains(restoredRequest.Key)) + { + staleRequestedVersions.Add($"{restoredRequest.Key} (restored request '{restoredRequest.Value}', no current PackageReference)"); + } + } + } + + if (missingPackages.Count > 0) + { + this.Log.LogError( + "ProjectData: cannot write project data for '{0}' because the restore graph does not contain declared PackageReference items: {1}. Run restore successfully before ProjectDataBuild.", + this.ProjectFilePath, + string.Join("; ", missingPackages.OrderBy(static packageId => packageId, StringComparer.OrdinalIgnoreCase))); + } + + if (missingRequestedVersions.Count > 0) + { + this.Log.LogError( + "ProjectData: cannot write project data for '{0}' because declared PackageReference items have no evaluated version request: {1}. Restore may be stale or central package version metadata may be missing. Run restore successfully before ProjectDataBuild.", + this.ProjectFilePath, + string.Join("; ", missingRequestedVersions.OrderBy(static packageId => packageId, StringComparer.OrdinalIgnoreCase))); + } + + if (incompatiblePackages.Count > 0) + { + this.Log.LogError( + "ProjectData: cannot write project data for '{0}' because declared PackageReference versions are not satisfied by the restore graph: {1}. Run restore successfully before ProjectDataBuild.", + this.ProjectFilePath, + string.Join("; ", incompatiblePackages.OrderBy(static package => package, StringComparer.OrdinalIgnoreCase))); + } + + if (staleRequestedVersions.Count > 0) + { + this.Log.LogError( + "ProjectData: cannot write project data for '{0}' because declared PackageReference requests differ from the restore graph: {1}. Run restore successfully before ProjectDataBuild.", + this.ProjectFilePath, + string.Join("; ", staleRequestedVersions.OrderBy(static package => package, StringComparer.OrdinalIgnoreCase))); + } + + if (incompatibleCentralTransitiveVersions.Count > 0) + { + this.Log.LogError( + "ProjectData: cannot write project data for '{0}' because central transitive package version requests are not satisfied by the restore graph: {1}. Run restore successfully before ProjectDataBuild.", + this.ProjectFilePath, + string.Join("; ", incompatibleCentralTransitiveVersions.OrderBy(static package => package, StringComparer.OrdinalIgnoreCase))); + } + + if (staleCentralTransitiveVersions.Count > 0) + { + this.Log.LogError( + "ProjectData: cannot write project data for '{0}' because central transitive package version requests differ from the restore graph: {1}. Run restore successfully before ProjectDataBuild.", + this.ProjectFilePath, + string.Join("; ", staleCentralTransitiveVersions.OrderBy(static package => package, StringComparer.OrdinalIgnoreCase))); + } + + if (centralTransitivePinningModeChanged) + { + this.Log.LogError( + "ProjectData: cannot write project data for '{0}' because central transitive package pinning mode differs from the restore graph: current '{1}', restored '{2}'. Run restore successfully before ProjectDataBuild.", + this.ProjectFilePath, + this.CentralPackageTransitivePinningEnabled, + restoredRequests!.CentralPackageTransitivePinningEnabled); + } + + return !this.Log.HasLoggedErrors; + } + + private void ValidateCentralTransitiveVersion( + string packageId, + string requestedVersion, + string resolvedVersion, + List incompatiblePackages) + { + if (!VersionRange.TryParse(requestedVersion, out VersionRange? requestedRange)) + { + incompatiblePackages.Add($"{packageId} (invalid requested version '{requestedVersion}')"); + return; + } + + if (!NuGetVersion.TryParse(resolvedVersion, out NuGetVersion? resolvedNuGetVersion)) + { + incompatiblePackages.Add($"{packageId} (requested '{requestedVersion}', invalid resolved version '{resolvedVersion}')"); + } + else if (!requestedRange.Satisfies(resolvedNuGetVersion) || + (requestedRange.Float is not null && !requestedRange.Float.Satisfies(resolvedNuGetVersion))) + { + incompatiblePackages.Add($"{packageId} (requested '{requestedVersion}', resolved '{resolvedVersion}')"); + } + } + + private Dictionary GetResolvedPackagesById() + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (ITaskItem resolvedPackage in this.ResolvedPackages) + { + string packageId = resolvedPackage.GetMetadata("Name"); + if (string.IsNullOrWhiteSpace(packageId)) + { + packageId = GetIdentityPart(resolvedPackage.ItemSpec); + } + + if (!string.IsNullOrWhiteSpace(packageId)) + { + result[packageId] = resolvedPackage; + } + + string packagePath = resolvedPackage.GetMetadata("Path"); + if (string.IsNullOrWhiteSpace(packagePath)) + { + this.Log.LogError( + "ProjectData: cannot write project data for '{0}' because resolved package '{1}' has no package path. Run restore successfully before ProjectDataBuild.", + this.ProjectFilePath, + resolvedPackage.ItemSpec); + } + else if (!Directory.Exists(packagePath)) + { + this.Log.LogError( + "ProjectData: cannot write project data for '{0}' because package files are missing: {1} at {2}. Run restore successfully before ProjectDataBuild.", + this.ProjectFilePath, + resolvedPackage.ItemSpec, + packagePath); + } + } + + return result; + } + + private Dictionary GetCentralVersionsById() + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (ITaskItem packageVersion in this.PackageVersions) + { + string version = packageVersion.GetMetadata("Version"); + if (!string.IsNullOrWhiteSpace(packageVersion.ItemSpec) && !string.IsNullOrWhiteSpace(version)) + { + result[packageVersion.ItemSpec] = version; + } + } + + return result; + } + + private RestoreGraphRequests? GetRestoreGraphRequests() + { + if (string.IsNullOrWhiteSpace(this.AssetsFile) || string.IsNullOrWhiteSpace(this.TargetFramework)) + { + return null; + } + + try + { + using FileStream stream = File.OpenRead(this.AssetsFile); + using JsonDocument assetsFile = JsonDocument.Parse(stream); + if (!TryGetProperty(assetsFile.RootElement, "project", out JsonElement project) || + !TryGetProperty(project, "frameworks", out JsonElement frameworks) || + !TryGetProperty(frameworks, this.TargetFramework, out JsonElement framework)) + { + this.Log.LogError( + "ProjectData: cannot write project data for '{0}' because restore graph '{1}' does not contain dependency requests for target framework '{2}'. Run restore successfully before ProjectDataBuild.", + this.ProjectFilePath, + this.AssetsFile, + this.TargetFramework); + return null; + } + + var directRequests = new Dictionary(StringComparer.OrdinalIgnoreCase); + var directAssetSelections = new Dictionary(StringComparer.OrdinalIgnoreCase); + var autoReferencedRequestIds = new HashSet(StringComparer.OrdinalIgnoreCase); + if (framework.ValueKind != JsonValueKind.Object) + { + this.LogAssetsFileError($"target framework '{this.TargetFramework}' must be a JSON object"); + return null; + } + + bool hasCentralPackageEvidence = this.ManagePackageVersionsCentrally || + this.CentralPackageTransitivePinningEnabled || + this.PackageVersions.Length > 0 || + TryGetProperty(framework, "centralPackageVersions", out _) || + TryGetProperty(assetsFile.RootElement, "centralTransitiveDependencyGroups", out _); + bool? restoredPinningEnabled = this.GetRestoredCentralPackageTransitivePinningMode(project, hasCentralPackageEvidence); + if (hasCentralPackageEvidence && restoredPinningEnabled is null) + { + return null; + } + + if (!TryGetProperty(framework, "dependencies", out JsonElement dependencies)) + { + dependencies = default; + } + else if (dependencies.ValueKind != JsonValueKind.Object) + { + this.LogAssetsFileError($"dependency requests for target framework '{this.TargetFramework}' must be a JSON object"); + return null; + } + + if (dependencies.ValueKind == JsonValueKind.Object) + { + foreach (JsonProperty dependency in dependencies.EnumerateObject()) + { + if (!TryGetStringProperty(dependency.Value, "target", out string target)) + { + this.LogAssetsFileError($"dependency request '{dependency.Name}' for target framework '{this.TargetFramework}' has no string target"); + return null; + } + + if (!string.Equals(target, "Package", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (!TryGetStringProperty(dependency.Value, "version", out string version) || + string.IsNullOrWhiteSpace(version)) + { + this.LogAssetsFileError($"package dependency request '{dependency.Name}' for target framework '{this.TargetFramework}' has no string version"); + return null; + } + + if (TryGetBooleanProperty(dependency.Value, "autoReferenced", out bool autoReferenced) && + autoReferenced) + { + autoReferencedRequestIds.Add(dependency.Name); + } + + directRequests[dependency.Name] = version; + if (!this.TryGetRestoredAssetSelection(dependency, this.TargetFramework, out PackageAssetSelection? assetSelection)) + { + return null; + } + + directAssetSelections[dependency.Name] = assetSelection; + } + } + + Dictionary? restoredCentralVersions = this.GetRestoredCentralVersions(framework); + Dictionary? centralTransitiveRequests = this.GetRestoredCentralTransitiveRequests( + assetsFile.RootElement, + directRequests); + Dictionary? resolvedVersions = this.GetRestoredResolvedVersions(assetsFile.RootElement); + return restoredCentralVersions is null || centralTransitiveRequests is null || resolvedVersions is null + ? null + : new RestoreGraphRequests( + directRequests, + directAssetSelections, + autoReferencedRequestIds, + restoredCentralVersions, + centralTransitiveRequests, + resolvedVersions, + restoredPinningEnabled); + } + catch (IOException ex) + { + this.LogAssetsFileError(ex.Message); + } + catch (UnauthorizedAccessException ex) + { + this.LogAssetsFileError(ex.Message); + } + catch (JsonException ex) + { + this.LogAssetsFileError(ex.Message); + } + + return null; + } + + private bool TryGetRestoredAssetSelection( + JsonProperty dependency, + string targetFramework, + out PackageAssetSelection assetSelection) + { + PackageAssetFlags include = PackageAssetFlags.All; + if (TryGetProperty(dependency.Value, "include", out JsonElement includeElement)) + { + if (includeElement.ValueKind != JsonValueKind.String) + { + this.LogAssetsFileError($"package dependency request '{dependency.Name}' for target framework '{targetFramework}' has no string include assets"); + assetSelection = null!; + return false; + } + + include = ParseRestoredAssetFlags(includeElement.GetString(), PackageAssetFlags.All); + } + + PackageAssetFlags suppressParent = PackageAssetSelection.DefaultSuppressParent; + if (TryGetProperty(dependency.Value, "suppressParent", out JsonElement suppressParentElement)) + { + if (suppressParentElement.ValueKind != JsonValueKind.String) + { + this.LogAssetsFileError($"package dependency request '{dependency.Name}' for target framework '{targetFramework}' has no string private assets"); + assetSelection = null!; + return false; + } + + suppressParent = ParseRestoredAssetFlags(suppressParentElement.GetString(), PackageAssetSelection.DefaultSuppressParent); + } + + assetSelection = new PackageAssetSelection(include, suppressParent); + return true; + } + + private bool? GetRestoredCentralPackageTransitivePinningMode(JsonElement project, bool required) + { + if (!TryGetProperty(project, "restore", out JsonElement restore)) + { + if (required) + { + this.LogAssetsFileError("restore settings do not contain central transitive package pinning mode"); + } + + return null; + } + + if (restore.ValueKind != JsonValueKind.Object) + { + this.LogAssetsFileError("restore settings must be a JSON object"); + return null; + } + + if (!TryGetProperty(restore, "CentralPackageTransitivePinningEnabled", out JsonElement pinningEnabled)) + { + // NuGet 6.13 writes the property only when enabled; omission records false. + return required ? false : null; + } + + if (pinningEnabled.ValueKind != JsonValueKind.True && + pinningEnabled.ValueKind != JsonValueKind.False) + { + this.LogAssetsFileError("central transitive package pinning mode in restore settings must be a JSON boolean"); + return null; + } + + return pinningEnabled.GetBoolean(); + } + + private Dictionary? GetRestoredCentralVersions(JsonElement framework) + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (!this.CentralPackageTransitivePinningEnabled || + !TryGetProperty(framework, "centralPackageVersions", out JsonElement centralVersions)) + { + return result; + } + + if (centralVersions.ValueKind != JsonValueKind.Object) + { + this.LogAssetsFileError($"central package versions for target framework '{this.TargetFramework}' must be a JSON object"); + return null; + } + + foreach (JsonProperty centralVersion in centralVersions.EnumerateObject()) + { + if (centralVersion.Value.ValueKind != JsonValueKind.String || + string.IsNullOrWhiteSpace(centralVersion.Value.GetString())) + { + this.LogAssetsFileError($"central package version '{centralVersion.Name}' for target framework '{this.TargetFramework}' has no string version"); + return null; + } + + result[centralVersion.Name] = centralVersion.Value.GetString()!; + } + + return result; + } + + private Dictionary? GetRestoredCentralTransitiveRequests( + JsonElement assetsFile, + IReadOnlyDictionary directRequests) + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (!this.CentralPackageTransitivePinningEnabled || + !TryGetProperty(assetsFile, "centralTransitiveDependencyGroups", out JsonElement groups)) + { + return result; + } + + if (groups.ValueKind != JsonValueKind.Object) + { + this.LogAssetsFileError("central transitive dependency groups must be a JSON object"); + return null; + } + + if (!this.TryGetTargetFrameworkGroup(groups, out JsonElement group)) + { + return result; + } + + if (group.ValueKind != JsonValueKind.Object) + { + this.LogAssetsFileError($"central transitive dependency group for target framework '{this.TargetFramework}' must be a JSON object"); + return null; + } + + foreach (JsonProperty dependency in group.EnumerateObject()) + { + if (directRequests.ContainsKey(dependency.Name)) + { + continue; + } + + if (!TryGetStringProperty(dependency.Value, "version", out string version) || + string.IsNullOrWhiteSpace(version)) + { + this.LogAssetsFileError($"central transitive dependency request '{dependency.Name}' for target framework '{this.TargetFramework}' has no string version"); + return null; + } + + result[dependency.Name] = version; + } + + return result; + } + + private Dictionary? GetRestoredResolvedVersions(JsonElement assetsFile) + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (!this.CentralPackageTransitivePinningEnabled) + { + return result; + } + + if (!TryGetProperty(assetsFile, "targets", out JsonElement targets) || + targets.ValueKind != JsonValueKind.Object) + { + this.LogAssetsFileError("resolved target graphs must be a JSON object"); + return null; + } + + if (!this.TryGetTargetFrameworkGroup(targets, out JsonElement target)) + { + this.LogAssetsFileError($"resolved target graphs do not contain target framework '{this.TargetFramework}'"); + return null; + } + + if (target.ValueKind != JsonValueKind.Object) + { + this.LogAssetsFileError($"resolved target graph for target framework '{this.TargetFramework}' must be a JSON object"); + return null; + } + + foreach (JsonProperty dependency in target.EnumerateObject()) + { + if (!TryGetStringProperty(dependency.Value, "type", out string dependencyType)) + { + this.LogAssetsFileError($"resolved dependency '{dependency.Name}' for target framework '{this.TargetFramework}' has no string type"); + return null; + } + + if (!string.Equals(dependencyType, "package", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + int separatorIndex = dependency.Name.LastIndexOf('/'); + if (separatorIndex <= 0 || separatorIndex == dependency.Name.Length - 1) + { + this.LogAssetsFileError($"resolved package identity '{dependency.Name}' for target framework '{this.TargetFramework}' has no concrete version"); + return null; + } + + result[dependency.Name.Substring(0, separatorIndex)] = dependency.Name.Substring(separatorIndex + 1); + } + + return result; + } + + private bool TryGetTargetFrameworkGroup(JsonElement groups, out JsonElement group) + { + if (!string.IsNullOrWhiteSpace(this.TargetFrameworkMoniker) && + TryGetProperty(groups, this.TargetFrameworkMoniker, out group)) + { + return true; + } + + return TryGetProperty(groups, this.TargetFramework, out group); + } + + private void LogAssetsFileError(string message) + { + this.Log.LogError( + "ProjectData: cannot write project data for '{0}' because restore graph '{1}' could not be read: {2}. Run restore successfully before ProjectDataBuild.", + this.ProjectFilePath, + this.AssetsFile, + message); + } + + private string GetRequestedVersion(ITaskItem packageReference, IReadOnlyDictionary centralVersionsById) + { + string versionOverride = packageReference.GetMetadata("VersionOverride"); + if (this.ManagePackageVersionsCentrally && !string.IsNullOrWhiteSpace(versionOverride)) + { + return versionOverride; + } + + string version = packageReference.GetMetadata("Version"); + if (!string.IsNullOrWhiteSpace(version)) + { + return version; + } + + return this.ManagePackageVersionsCentrally && + centralVersionsById.TryGetValue(packageReference.ItemSpec, out string? centralVersion) + ? centralVersion + : string.Empty; + } + + private static bool TryGetProperty(JsonElement element, string propertyName, out JsonElement value) + { + if (element.ValueKind != JsonValueKind.Object) + { + value = default; + return false; + } + + foreach (JsonProperty property in element.EnumerateObject()) + { + if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase)) + { + value = property.Value; + return true; + } + } + + value = default; + return false; + } + + private static bool TryGetBooleanProperty(JsonElement element, string propertyName, out bool value) + { + if (element.ValueKind == JsonValueKind.Object && + element.TryGetProperty(propertyName, out JsonElement property) && + (property.ValueKind == JsonValueKind.True || property.ValueKind == JsonValueKind.False)) + { + value = property.GetBoolean(); + return true; + } + + value = false; + return false; + } + + private static bool TryGetStringProperty(JsonElement element, string propertyName, out string value) + { + if (element.ValueKind == JsonValueKind.Object && + element.TryGetProperty(propertyName, out JsonElement property) && + property.ValueKind == JsonValueKind.String) + { + value = property.GetString() ?? string.Empty; + return true; + } + + value = string.Empty; + return false; + } + + private static bool AreEquivalentVersionRanges(string left, string right) + => VersionRange.TryParse(left, out VersionRange? leftRange) && + VersionRange.TryParse(right, out VersionRange? rightRange) && + leftRange.Equals(rightRange); + + private static PackageAssetSelection GetCurrentAssetSelection(ITaskItem packageReference) + { + PackageAssetFlags include = ParseEvaluatedAssetFlags( + packageReference.GetMetadata("IncludeAssets"), + PackageAssetFlags.All); + PackageAssetFlags exclude = ParseEvaluatedAssetFlags( + packageReference.GetMetadata("ExcludeAssets"), + PackageAssetFlags.None); + PackageAssetFlags suppressParent = ParseEvaluatedAssetFlags( + packageReference.GetMetadata("PrivateAssets"), + PackageAssetSelection.DefaultSuppressParent); + return new PackageAssetSelection(include & ~exclude, suppressParent); + } + + private static PackageAssetFlags ParseEvaluatedAssetFlags(string? value, PackageAssetFlags defaultValue) + => ParseAssetFlags(value, defaultValue, ';', expandBuildTransitive: true); + + private static PackageAssetFlags ParseRestoredAssetFlags(string? value, PackageAssetFlags defaultValue) + => ParseAssetFlags(value, defaultValue, ',', expandBuildTransitive: false); + + private static PackageAssetFlags ParseAssetFlags( + string? value, + PackageAssetFlags defaultValue, + char separator, + bool expandBuildTransitive) + { + if (string.IsNullOrWhiteSpace(value)) + { + return defaultValue; + } + + PackageAssetFlags result = PackageAssetFlags.None; + bool hasToken = false; + foreach (string part in value!.Split([separator], StringSplitOptions.RemoveEmptyEntries)) + { + string token = part.Trim(); + if (token.Length == 0) + { + continue; + } + + hasToken = true; + switch (token.ToLowerInvariant()) + { + case "all": + result |= PackageAssetFlags.All; + break; + case "runtime": + result |= PackageAssetFlags.Runtime; + break; + case "compile": + result |= PackageAssetFlags.Compile; + break; + case "build": + result |= PackageAssetFlags.Build; + break; + case "contentfiles": + result |= PackageAssetFlags.ContentFiles; + break; + case "native": + result |= PackageAssetFlags.Native; + break; + case "analyzers": + result |= PackageAssetFlags.Analyzers; + break; + case "buildtransitive": + result |= PackageAssetFlags.BuildTransitive; + if (expandBuildTransitive) + { + result |= PackageAssetFlags.Build; + } + break; + } + } + + return hasToken ? result : defaultValue; + } + + private static string GetResolvedVersion(ITaskItem resolvedPackage) + { + string version = resolvedPackage.GetMetadata("Version"); + if (!string.IsNullOrWhiteSpace(version)) + { + return version; + } + + int separatorIndex = resolvedPackage.ItemSpec.LastIndexOf('/'); + return separatorIndex >= 0 ? resolvedPackage.ItemSpec.Substring(separatorIndex + 1) : string.Empty; + } + + private static string GetIdentityPart(string resolvedPackageIdentity) + { + int separatorIndex = resolvedPackageIdentity.LastIndexOf('/'); + return separatorIndex >= 0 ? resolvedPackageIdentity.Substring(0, separatorIndex) : resolvedPackageIdentity; + } + + private sealed class RestoreGraphRequests + { + public RestoreGraphRequests( + Dictionary directRequests, + Dictionary directAssetSelections, + HashSet autoReferencedRequestIds, + Dictionary centralVersions, + Dictionary centralTransitiveRequests, + Dictionary resolvedVersions, + bool? centralPackageTransitivePinningEnabled) + { + this.DirectRequests = directRequests; + this.DirectAssetSelections = directAssetSelections; + this.AutoReferencedRequestIds = autoReferencedRequestIds; + this.CentralVersions = centralVersions; + this.CentralTransitiveRequests = centralTransitiveRequests; + this.ResolvedVersions = resolvedVersions; + this.CentralPackageTransitivePinningEnabled = centralPackageTransitivePinningEnabled; + } + + public Dictionary DirectRequests { get; } + + public Dictionary DirectAssetSelections { get; } + + public HashSet AutoReferencedRequestIds { get; } + + public Dictionary CentralVersions { get; } + + public Dictionary CentralTransitiveRequests { get; } + + public Dictionary ResolvedVersions { get; } + + public bool? CentralPackageTransitivePinningEnabled { get; } + } + + [Flags] + private enum PackageAssetFlags + { + None = 0, + Runtime = 1 << 0, + Compile = 1 << 1, + Build = 1 << 2, + ContentFiles = 1 << 3, + Native = 1 << 4, + Analyzers = 1 << 5, + BuildTransitive = 1 << 6, + All = Runtime | Compile | Build | ContentFiles | Native | Analyzers | BuildTransitive, + } + + private sealed class PackageAssetSelection : IEquatable + { + public const PackageAssetFlags DefaultSuppressParent = + PackageAssetFlags.Build | PackageAssetFlags.ContentFiles | PackageAssetFlags.Analyzers; + + public PackageAssetSelection(PackageAssetFlags include, PackageAssetFlags suppressParent) + { + this.Include = include; + this.SuppressParent = suppressParent; + } + + public PackageAssetFlags Include { get; } + + public PackageAssetFlags SuppressParent { get; } + + public bool Equals(PackageAssetSelection? other) + => other is not null && + this.Include == other.Include && + this.SuppressParent == other.SuppressParent; + + public override bool Equals(object? obj) => this.Equals(obj as PackageAssetSelection); + + public override int GetHashCode() => ((int)this.Include * 397) ^ (int)this.SuppressParent; + + public override string ToString() => $"include={this.Include}; private={this.SuppressParent}"; + } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/WriteProjectDataBuildReceiptTask.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/WriteProjectDataBuildReceiptTask.cs new file mode 100644 index 0000000000000..acec847aba9a0 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/WriteProjectDataBuildReceiptTask.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; +using Microsoft.NET.ProjectData; + +namespace Microsoft.NET.ProjectData.Tasks; + +/// +/// Writes a completion receipt after the outer project-level ProjectDataBuild target completes. +/// +public sealed class WriteProjectDataBuildReceiptTask : Microsoft.Build.Utilities.Task +{ + [Required] + public string ReceiptDirectory { get; set; } = string.Empty; + + [Required] + public string AttemptId { get; set; } = string.Empty; + + [Required] + public string ProjectFilePath { get; set; } = string.Empty; + + public override bool Execute() + { + try + { + string receiptPath = ProjectDataBuildReceipt.Write(this.ReceiptDirectory, this.AttemptId, this.ProjectFilePath); + this.Log.LogMessage(MessageImportance.Low, "ProjectData: wrote completed receipt for {0}: {1}", this.ProjectFilePath, receiptPath); + return true; + } + catch (Exception ex) when (ex is not OutOfMemoryException and not StackOverflowException) + { + this.Log.LogError( + "ProjectData: failed to write completed receipt for {0} in attempt {1}: {2}", + this.ProjectFilePath, + this.AttemptId, + ex.Message); + return false; + } + } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/WriteProjectDataSliceTask.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/WriteProjectDataSliceTask.cs new file mode 100644 index 0000000000000..1a2f7477ab676 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/WriteProjectDataSliceTask.cs @@ -0,0 +1,272 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; +using Microsoft.NET.ProjectData; + +namespace Microsoft.NET.ProjectData.Tasks; + +/// +/// Thin MSBuild wrapper around : declares the MSBuild +/// inputs as properties, calls the writer, and swallows any exception so a writer +/// fault never fails the build. +/// +public sealed class WriteProjectDataSliceTask : Microsoft.Build.Utilities.Task +{ + [Required] + public string ProjectFilePath { get; set; } = string.Empty; + + /// + /// Absolute path of the cache file. When empty, the user-folder cache path is + /// computed from via . + /// + public string OutputPath { get; set; } = string.Empty; + + /// + /// Project intermediate output directory ($(IntermediateOutputPath)). Transient + /// .tmp side-files for the atomic write are placed here (when on the same volume as the + /// output) so they never appear next to committed source. Optional; falls back to the output + /// directory. + /// + public string IntermediateOutputPath { get; set; } = string.Empty; + + /// + /// Optional override for the repo-scoped donor index path. When empty, the task resolves + /// <git-common-dir>\dotnet-projectdata\lscache-donor-index.json from . + /// + public string DonorCacheIndexPath { get; set; } = string.Empty; + + /// + /// Optional override for the logical workspace root recorded in the donor index. + /// + public string DonorCacheWorkspaceRoot { get; set; } = string.Empty; + + public bool WriteHeader { get; set; } + public bool IsPrimary { get; set; } + public bool LastDtbSucceeded { get; set; } + + /// + /// True when the invoking build forced CoreCompile to run so that the compiler command + /// line is always captured. The authoritative ProjectDataBuild graph sets + /// NonExistentFile in _PrepareProjectDataBuild for exactly this reason, so an + /// empty there genuinely means the project produces no C# + /// compilation and should be marked unsupported. + /// + /// When false — for example the opportunistic EnableProjectDataOnBuild hook running on an + /// ordinary incremental build — an empty instead means + /// CoreCompile was skipped as up-to-date (its AfterTargets hook still fires). That + /// is a perfectly good project, so the task must leave any existing project-data file and + /// unsupported marker untouched rather than poison the shared cache with a spurious + /// CompilerCommandLineArgumentsEmpty marker. + /// + public bool CoreCompileForced { get; set; } + + public ITaskItem[]? SliceDimensions { get; set; } + public ITaskItem[]? Properties { get; set; } + public string[]? CommandLineArguments { get; set; } + public ITaskItem[]? SourceFiles { get; set; } + public ITaskItem[]? MetadataReferences { get; set; } + public ITaskItem[]? AnalyzerReferences { get; set; } + public string[]? AnalyzerConfigFiles { get; set; } + public string[]? AdditionalFiles { get; set; } + public ITaskItem[]? EmbeddedResources { get; set; } + public ITaskItem[]? ProjectReferences { get; set; } + public string[]? Capabilities { get; set; } + public ITaskItem[]? SdkKnownAnalyzerPacks { get; set; } + public ITaskItem[]? SdkAnalyzerConfigPolicy { get; set; } + + [Output] + public bool Succeeded { get; set; } + + [Output] + public string ResolvedOutputPath { get; set; } = string.Empty; + + public override bool Execute() + { + try + { + this.Succeeded = false; + this.ResolvedOutputPath = this.ResolveOutputPath(); + + if (!ProjectDataWriter.TryValidateNetFrameworkReferences( + this.ProjectFilePath, + this.SliceDimensions, + this.Properties, + this.MetadataReferences, + out string unsupportedReason)) + { + if (this.ShouldDeleteOutputOnValidationFailure()) + { + this.DeleteFileIfExists(this.ResolvedOutputPath); + } + + if (this.IsPrimary) + { + UnsupportedProjectDataMarker.Write(this.ProjectFilePath, unsupportedReason); + } + + this.Log.LogMessage(MessageImportance.Low, + "ProjectData: skipped writing project-data file for {0}: {1}.", this.ProjectFilePath, unsupportedReason); + return true; + } + + if (this.CommandLineArguments == null || this.CommandLineArguments.Length == 0) + { + // An empty compiler command line is only authoritative when the build forced + // CoreCompile to run (the ProjectDataBuild graph). On an ordinary incremental build, + // the EnableProjectDataOnBuild hook fires AfterTargets="CoreCompile" even when + // CoreCompile was skipped as up-to-date, leaving @(CscCommandLineArgs) empty for a + // perfectly good project. Poisoning the shared cache with an unsupported marker (or + // deleting the good project-data file) in that case makes projects silently vanish on + // the next non-forced workspace refresh. Leave existing state untouched instead. + if (!this.CoreCompileForced) + { + this.Log.LogMessage(MessageImportance.Low, + "ProjectData: leaving project-data for {0} untouched; CscCommandLineArgs was empty but CoreCompile was not forced (likely skipped as up-to-date).", + this.ProjectFilePath); + return true; + } + + if (this.ShouldDeleteOutputOnValidationFailure()) + { + this.DeleteFileIfExists(this.ResolvedOutputPath); + } + + if (this.IsPrimary) + { + UnsupportedProjectDataMarker.Write(this.ProjectFilePath, "CompilerCommandLineArgumentsEmpty"); + } + + this.Log.LogMessage(MessageImportance.Low, + "ProjectData: skipped writing project-data file for {0} because CscCommandLineArgs was empty.", + this.ProjectFilePath); + return true; + } + + ProjectDataWriter.AtomicWriteStreamed( + this.ResolvedOutputPath, + writer => ProjectDataWriter.WriteContent( + writer, + this.ProjectFilePath, + this.WriteHeader, + this.IsPrimary, + this.LastDtbSucceeded, + this.SliceDimensions, + this.Properties, + this.CommandLineArguments, + this.SourceFiles, + this.MetadataReferences, + this.AnalyzerReferences, + this.AnalyzerConfigFiles, + this.AdditionalFiles, + this.EmbeddedResources, + this.ProjectReferences, + this.Capabilities, + this.SdkKnownAnalyzerPacks, + this.SdkAnalyzerConfigPolicy, + this.ReportDuplicateItem), + this.IntermediateOutputPath); + this.Succeeded = true; + UnsupportedProjectDataMarker.Delete(this.ProjectFilePath); + this.RecordDonorIndexEntryIfFinalCache(); + + this.Log.LogMessage(MessageImportance.Low, + "ProjectData: wrote {0}.", this.ResolvedOutputPath); + } + catch (Exception ex) + { + // Cache-write failures should not break the user's build, but they must be + // visible at default verbosity so the user knows the cache might be stale. + // ``LogMessage(Low)`` was invisible under ``-v:minimal`` (the default for + // ``dotnet build``) and hid real diagnostics; ``LogWarning`` matches the + // .NET SDK convention for non-fatal task failures. Catch all exception + // types — narrowing previously caused legitimate DTB scenarios (e.g. a + // ``KeyNotFoundException`` from a missing slice property) to fail the + // build instead of degrading to a stale-cache warning. + this.Log.LogWarning( + "ProjectData: failed to write project-data file for {0}: {1}", + this.ProjectFilePath, + ex.Message); + } + return true; + } + + private string ResolveOutputPath() + => string.IsNullOrEmpty(this.OutputPath) + ? UserFolderCachePath.Compute(this.ProjectFilePath) + : this.OutputPath; + + private void RecordDonorIndexEntryIfFinalCache() + { + if (!this.WriteHeader || !this.IsPrimary) + { + return; + } + + this.RecordDonorIndexEntry(); + } + + private void RecordDonorIndexEntry() + { + ProjectDataDonorWriteOptions options = new() + { + IndexPath = string.IsNullOrEmpty(this.DonorCacheIndexPath) ? null : this.DonorCacheIndexPath, + WorkspaceRoot = string.IsNullOrEmpty(this.DonorCacheWorkspaceRoot) ? null : this.DonorCacheWorkspaceRoot, + }; + + bool recorded = ProjectDataDonorIndex.TryRecordWrite(this.ProjectFilePath, this.ResolvedOutputPath, options, out string? message); + if (recorded && !string.IsNullOrEmpty(message)) + { + this.Log.LogMessage(MessageImportance.Low, "ProjectData: {0}", message); + } + else if (!recorded && !string.IsNullOrEmpty(message)) + { + this.Log.LogMessage(MessageImportance.Low, "ProjectData: failed to update donor index for {0}: {1}", this.ProjectFilePath, message); + } + } + + private void ReportDuplicateItem(ProjectDataDuplicateItemDiagnostic diagnostic) + { + this.Log.LogWarning( + "ProjectData: duplicate {0} item in {1}: {2}. The duplicate entry was omitted from {3}.", + diagnostic.Section, + diagnostic.ProjectFilePath, + diagnostic.ItemSpec, + this.ResolvedOutputPath); + } + + private bool ShouldDeleteOutputOnValidationFailure() + { + if (!this.IsPrimary) + { + return true; + } + + string projectFolderOutputPath = Path.GetFullPath(this.ProjectFilePath) + ".lscache"; + return !PathsEqual(this.ResolvedOutputPath, projectFolderOutputPath); + } + + private static bool PathsEqual(string left, string right) + => string.Equals( + Path.GetFullPath(left), + Path.GetFullPath(right), + StringComparisons.Paths); + + private void DeleteFileIfExists(string path) + { + if (string.IsNullOrEmpty(path)) + { + return; + } + + try + { + File.Delete(path); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + this.Log.LogWarning("ProjectData: failed to delete stale output {0}: {1}", path, ex.Message); + } + } +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/WriteUnsupportedProjectDataMarkerTask.cs b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/WriteUnsupportedProjectDataMarkerTask.cs new file mode 100644 index 0000000000000..7a801300b6851 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/WriteUnsupportedProjectDataMarkerTask.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Security.Cryptography; +using Microsoft.Build.Framework; +using Microsoft.NET.ProjectData; + +namespace Microsoft.NET.ProjectData.Tasks; + +/// +/// Writes the user-cache marker that records a project as intentionally unsupported by ProjectData. +/// +public sealed class WriteUnsupportedProjectDataMarkerTask : Microsoft.Build.Utilities.Task +{ + [Required] + public string ProjectFilePath { get; set; } = string.Empty; + + public string Reason { get; set; } = string.Empty; + + [Output] + public string MarkerPath { get; set; } = string.Empty; + + public override bool Execute() + { + try + { + this.MarkerPath = UnsupportedProjectDataMarker.Write(this.ProjectFilePath, this.Reason); + this.Log.LogMessage( + MessageImportance.Low, + "ProjectData: wrote unsupported marker for {0}: {1} ({2})", + this.ProjectFilePath, + this.MarkerPath, + this.Reason); + } + catch (Exception ex) when (IsRecoverableMarkerWriteException(ex)) + { + this.Log.LogWarning( + "ProjectData: failed to write unsupported marker for {0}: {1}", + this.ProjectFilePath, + ex.Message); + } + + return true; + } + + private static bool IsRecoverableMarkerWriteException(Exception ex) + => ex is IOException or UnauthorizedAccessException or ArgumentException or NotSupportedException or InvalidOperationException or CryptographicException; +} diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/build/Microsoft.NET.ProjectData.Schema.props b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/build/Microsoft.NET.ProjectData.Schema.props new file mode 100644 index 0000000000000..7b9c0ff41ee94 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/build/Microsoft.NET.ProjectData.Schema.props @@ -0,0 +1,24 @@ + + + + + <_ProjectDataProperties Include="AssemblyName" > $(AssemblyName) + <_ProjectDataProperties Include="BaseIntermediateOutputPath" > $(BaseIntermediateOutputPath) + <_ProjectDataProperties Include="BaseOutputPath" > $(BaseOutputPath) + <_ProjectDataProperties Include="CompilerGeneratedFilesOutputPath" > $(CompilerGeneratedFilesOutputPath) + <_ProjectDataProperties Include="IsTestProject" > $(IsTestProject) + <_ProjectDataProperties Include="MaxSupportedLangVersion" > $(MaxSupportedLangVersion) + <_ProjectDataProperties Include="OutputType" > $(OutputType) + <_ProjectDataProperties Include="ProjectAssetsFile" > $(ProjectAssetsFile) + <_ProjectDataProperties Include="RootNamespace" > $(RootNamespace) + <_ProjectDataProperties Include="RunAnalyzers" > $(RunAnalyzers) + <_ProjectDataProperties Include="RunAnalyzersDuringLiveAnalysis" > $(RunAnalyzersDuringLiveAnalysis) + <_ProjectDataProperties Include="SolutionPath" > $(SolutionPath) + <_ProjectDataProperties Include="TargetFramework" > $(TargetFramework) + <_ProjectDataProperties Include="TargetFrameworkIdentifier" > $(TargetFrameworkIdentifier) + <_ProjectDataProperties Include="TargetFrameworkVersion" > $(TargetFrameworkVersion) + <_ProjectDataProperties Include="TargetPath" > $(TargetPath) + <_ProjectDataProperties Include="TargetRefPath" > $(TargetRefPath) + <_ProjectDataProperties Include="TemporaryDependencyNodeTargetIdentifier" > $(TargetFramework) + + diff --git a/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/build/Microsoft.NET.ProjectData.targets b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/build/Microsoft.NET.ProjectData.targets new file mode 100644 index 0000000000000..6442af004b963 --- /dev/null +++ b/src/LanguageServer/ProjectData/Microsoft.NET.ProjectData.Tasks/build/Microsoft.NET.ProjectData.targets @@ -0,0 +1,635 @@ + + + + + + + + + + + + + + + + + <_ProjectDataTaskAssembly>$(MSBuildThisFileDirectory)Microsoft.NET.ProjectData.Tasks.dll + + + + + + false + + + false + + + true + + + <_ProjectDataTargetFrameworkSupported Condition="'$(TargetFrameworkIdentifier)' == '' or + '$(TargetFrameworkIdentifier)' != '.NETFramework' or + '$(UsingMicrosoftNETSdk)' == 'true'">true + + <_ProjectDataCanWriteOutput Condition="'$(UsingMicrosoftNETSdk)' == 'true' and + '$(UsingMicrosoftNoTargetsSdk)' != 'true' and + '$(ExcludeFromBuild)' != 'true' and + '$(_ProjectDataTargetFrameworkSupported)' == 'true' and + ('$(Language)' == 'C#' or + ('$(TargetFrameworks)' != '' and '$(TargetFramework)' == '' and '$(MSBuildProjectExtension)' == '.csproj'))">true + <_ProjectDataCanRunBuildTargets Condition="'$(UsingMicrosoftNETSdk)' == 'true' and + '$(UsingMicrosoftNoTargetsSdk)' != 'true' and + '$(ExcludeFromBuild)' != 'true'">true + <_ProjectDataShouldWriteUnsupportedMarker Condition="'$(_ProjectDataCanWriteOutput)' != 'true'">true + <_ProjectDataUnsupportedReason Condition="'$(UsingMicrosoftNETSdk)' != 'true'">UsingMicrosoftNETSdkFalse + <_ProjectDataUnsupportedReason Condition="'$(_ProjectDataUnsupportedReason)' == '' and '$(UsingMicrosoftNoTargetsSdk)' == 'true'">MicrosoftBuildNoTargetsSdk + <_ProjectDataUnsupportedReason Condition="'$(_ProjectDataUnsupportedReason)' == '' and '$(ExcludeFromBuild)' == 'true'">ExcludeFromBuildTrue + <_ProjectDataUnsupportedReason Condition="'$(_ProjectDataUnsupportedReason)' == '' and '$(_ProjectDataTargetFrameworkSupported)' != 'true'">UnsupportedTargetFramework + <_ProjectDataUnsupportedReason Condition="'$(_ProjectDataUnsupportedReason)' == '' and '$(Language)' != '' and '$(Language)' != 'C#'">LanguageNotCSharp + <_ProjectDataUnsupportedReason Condition="'$(_ProjectDataUnsupportedReason)' == '' and '$(_ProjectDataShouldWriteUnsupportedMarker)' == 'true'">CannotWriteProjectData + + + <_ProjectDataPath Condition="'$(EnableProjectDataInProjectFolder)' == 'true'">$(MSBuildProjectFullPath).lscache + <_ProjectDataAppendRuntimeIdentifierToIntermediateOutputPath Condition="'$(RuntimeIdentifier)' != '' and + '$(_UsingDefaultRuntimeIdentifier)' != 'true' and + ('$(AppendRuntimeIdentifierToOutputPath)' == 'true' or + ('$(AppendRuntimeIdentifierToOutputPath)' == '' and '$(UsingNETSdkDefaults)' == 'true'))">true + <_ProjectDataSlicePath>$(IntermediateOutputPath)$(MSBuildProjectFile).slice + <_ProjectDataSlicePath Condition="'$(TargetFrameworks)' != '' and '$(TargetFramework)' != '' and '$(_ProjectDataAppendRuntimeIdentifierToIntermediateOutputPath)' != 'true'">$(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/$(MSBuildProjectFile).slice + <_ProjectDataSlicePath Condition="'$(TargetFrameworks)' != '' and '$(TargetFramework)' != '' and '$(_ProjectDataAppendRuntimeIdentifierToIntermediateOutputPath)' == 'true'">$(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/$(RuntimeIdentifier)/$(MSBuildProjectFile).slice + <_ProjectDataUserFolderStampPath>$(IntermediateOutputPath)$(MSBuildProjectFile).lscache.stamp + <_ProjectDataPreserveExistingProjectFolderOutput Condition="'$(EnableProjectDataInProjectFolder)' == 'true' and '$(OS)' != 'Windows_NT'">true + <_ProjectDataPreserveExistingProjectFolderOutput Condition="'$(_ProjectDataPreserveExistingProjectFolderOutput)' == ''">false + <_ProjectDataMergeOutputs>$(_ProjectDataPath) + <_ProjectDataMergeOutputs Condition="'$(_ProjectDataBuildForce)' == 'true'">$(_ProjectDataMergeOutputs);__NonExistentSubDir__\__NonExistentFile__ + + + <_ProjectDataMergeAfterTargets Condition="'$(IsCrossTargetingBuild)' == 'true'">DispatchToInnerBuilds + + + true + + + + + <_ProjectDataBuildInnerTargets>_DeleteUnsupportedProjectData;_WriteUnsupportedProjectDataMarker;_ValidateProjectDataAssetsFile;ResolveAssemblyReferencesDesignTime;ResolveProjectReferencesDesignTime;ResolvePackageDependenciesDesignTime;_ValidateProjectDataResolvedPackages;_PrepareProjectDataBuild;Compile;_ValidateProjectDataMetadataReferences;_ProjectDataCollectFrameworkReferences;_WriteProjectData;_WriteProjectDataUserFolder;_WriteProjectDataSlice + <_ProjectDataBuildUnsupportedTargets>_DeleteUnsupportedProjectData;_WriteUnsupportedProjectDataMarker + + + <_ProjectDataBuildAssetsFile>$(MSBuildProjectExtensionsPath)project.assets.json + <_ProjectDataNuGetGeneratedPropsFile>$(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).nuget.g.props + <_ProjectDataNuGetGeneratedTargetsFile>$(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).nuget.g.targets + + <_ProjectDataNuGetGeneratedPropsImported Condition="'$(RestoreSuccess)' == 'True'">true + + <_ProjectDataBuildDependsOn Condition="'$(_ProjectDataCanRunBuildTargets)' == 'true' and '$(_ProjectDataCanWriteOutput)' == 'true' and '$(IsCrossTargetingBuild)' == 'true'">_PrepareProjectDataOuterBuild;DispatchToInnerBuilds + <_ProjectDataBuildDependsOn Condition="'$(_ProjectDataCanRunBuildTargets)' == 'true' and '$(_ProjectDataCanWriteOutput)' == 'true' and '$(_ProjectDataBuildDependsOn)' == ''">$(_ProjectDataBuildInnerTargets) + <_ProjectDataBuildDependsOn Condition="'$(_ProjectDataShouldWriteUnsupportedMarker)' == 'true' and '$(_ProjectDataBuildDependsOn)' == ''">$(_ProjectDataBuildUnsupportedTargets) + + + + + + + + + + <_ProjectDataOuterBuildActive>true + + $(_ProjectDataBuildInnerTargets) + + + + + + + + + + + + + + + <_ProjectDataResolvedAssetsFile Condition="'$(_ProjectDataResolvedAssetsFile)' == '' and '$(ProjectAssetsFile)' != ''">$(ProjectAssetsFile) + <_ProjectDataResolvedAssetsFile Condition="'$(_ProjectDataResolvedAssetsFile)' == ''">$(_ProjectDataBuildAssetsFile) + + + + + + + + + + + + + + + + <_ProjectDataFrameworkMetadataReference Include="@(ReferencePathWithRefAssemblies)" + Condition="'%(ReferencePathWithRefAssemblies.FrameworkReferenceName)' != '' or + ('$(TargetFrameworkIdentifier)' == '.NETStandard' and '%(ReferencePathWithRefAssemblies.Filename)%(ReferencePathWithRefAssemblies.Extension)' == 'netstandard.dll')" /> + + + + + + + + + + + + + <_ProjectDataBuildActive>true + true + $(IntermediateOutputPath)projectdata.force-corecompile.never + + true + + + + + + + + + <_ProjectDataSdkKnownAnalyzerPack Include="@(KnownILLinkPack)"> + %(KnownILLinkPack.Identity) + %(KnownILLinkPack.ILLinkPackVersion) + %(KnownILLinkPack.TargetFramework) + + + + + + + <_ProjectDataSourceFiles Include="@(Compile)" + Condition="'%(Compile.DefiningProjectName)%(Compile.DefiningProjectExtension)' != 'Microsoft.NET.GenerateAssemblyInfo.targets'" /> + + <_ProjectDataSdkAnalyzerConfigPolicy Remove="@(_ProjectDataSdkAnalyzerConfigPolicy)" /> + <_ProjectDataSdkAnalyzerConfigPolicy Include="Microsoft.NET.Sdk"> + $(Language) + $(SkipGlobalAnalyzerConfigForPackage) + + $(EnableNETAnalyzers) + $(EnforceCodeStyleInBuild) + $(AnalysisLevel) + $(AnalysisLevelSuffix) + $(AnalysisMode) + $(EffectiveAnalysisLevel) + $(MicrosoftCodeAnalysisNetAnalyzersRulesVersion) + $(CodeAnalysisTreatWarningsAsErrors) + $(EffectiveCodeAnalysisTreatWarningsAsErrors) + $(AnalysisLevelStyle) + $(AnalysisLevelSuffixStyle) + $(AnalysisModeStyle) + $(EffectiveAnalysisLevelStyle) + + + + + + <_ProjectDataTargetFramework Include="$(TargetFrameworks)" /> + + <_ProjectDataExpectedSlice Include="@(_ProjectDataTargetFramework->'$(BaseIntermediateOutputPath)$(Configuration)/%(Identity)/$(MSBuildProjectFile).slice')" + Condition="'$(_ProjectDataAppendRuntimeIdentifierToIntermediateOutputPath)' != 'true'" /> + <_ProjectDataExpectedSlice Include="@(_ProjectDataTargetFramework->'$(BaseIntermediateOutputPath)$(Configuration)/%(Identity)/$(RuntimeIdentifier)/$(MSBuildProjectFile).slice')" + Condition="'$(_ProjectDataAppendRuntimeIdentifierToIntermediateOutputPath)' == 'true'" /> + + + + + + + <_ProjectDataSliceDimensions Include="TargetFramework"> + $(TargetFramework) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LanguageServer/Protocol/Handler/Definitions/AbstractGoToDefinitionHandler.cs b/src/LanguageServer/Protocol/Handler/Definitions/AbstractGoToDefinitionHandler.cs index d8e11563d9aa7..6623401370153 100644 --- a/src/LanguageServer/Protocol/Handler/Definitions/AbstractGoToDefinitionHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Definitions/AbstractGoToDefinitionHandler.cs @@ -35,7 +35,12 @@ public AbstractGoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSour public abstract Task HandleRequestAsync(TextDocumentPositionParams request, RequestContext context, CancellationToken cancellationToken); - protected async Task GetDefinitionAsync(LSP.TextDocumentPositionParams request, bool forSymbolType, RequestContext context, CancellationToken cancellationToken) + protected async Task GetDefinitionAsync( + LSP.TextDocumentPositionParams request, + string method, + bool forSymbolType, + RequestContext context, + CancellationToken cancellationToken) { var workspace = await context.GetWorkspaceAsync(cancellationToken).ConfigureAwait(false); var document = await context.GetDocumentAsync(cancellationToken).ConfigureAwait(false); @@ -44,7 +49,11 @@ public AbstractGoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSour var linePosition = ProtocolConversions.PositionToLinePosition(request.Position); - return await GetDefinitionsAsync(_globalOptions, _metadataAsSourceFileService, workspace, document, forSymbolType, linePosition, cancellationToken).ConfigureAwait(false); + var locations = await GetDefinitionsAsync(_globalOptions, _metadataAsSourceFileService, workspace, document, forSymbolType, linePosition, cancellationToken).ConfigureAwait(false); + if (locations is null or []) + await context.GetRequiredLspService().ReportEmptySymbolResultAsync(method, document, request.Position, cancellationToken).ConfigureAwait(false); + + return locations; } internal static async Task GetDefinitionsAsync(IGlobalOptionService globalOptions, IMetadataAsSourceFileService? metadataAsSourceFileService, Workspace workspace, Document document, bool forSymbolType, LinePosition linePosition, CancellationToken cancellationToken) diff --git a/src/LanguageServer/Protocol/Handler/Definitions/GoToDefinitionHandler.cs b/src/LanguageServer/Protocol/Handler/Definitions/GoToDefinitionHandler.cs index 678ab5918e4cb..f7a8849d6d13e 100644 --- a/src/LanguageServer/Protocol/Handler/Definitions/GoToDefinitionHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Definitions/GoToDefinitionHandler.cs @@ -25,5 +25,5 @@ public GoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSourceFileSe } public override Task HandleRequestAsync(LSP.TextDocumentPositionParams request, RequestContext context, CancellationToken cancellationToken) - => GetDefinitionAsync(request, forSymbolType: false, context, cancellationToken); + => GetDefinitionAsync(request, LSP.Methods.TextDocumentDefinitionName, forSymbolType: false, context, cancellationToken); } diff --git a/src/LanguageServer/Protocol/Handler/Definitions/GoToTypeDefinitionHandler.cs b/src/LanguageServer/Protocol/Handler/Definitions/GoToTypeDefinitionHandler.cs index f190725073b50..fd3f9e69096ba 100644 --- a/src/LanguageServer/Protocol/Handler/Definitions/GoToTypeDefinitionHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Definitions/GoToTypeDefinitionHandler.cs @@ -25,5 +25,5 @@ public GoToTypeDefinitionHandler(IMetadataAsSourceFileService metadataAsSourceFi } public override Task HandleRequestAsync(LSP.TextDocumentPositionParams request, RequestContext context, CancellationToken cancellationToken) - => GetDefinitionAsync(request, forSymbolType: true, context, cancellationToken); + => GetDefinitionAsync(request, LSP.Methods.TextDocumentTypeDefinitionName, forSymbolType: true, context, cancellationToken); } diff --git a/src/LanguageServer/Protocol/Handler/IInitializeManager.cs b/src/LanguageServer/Protocol/Handler/IInitializeManager.cs index 8e724a20f98bf..195fa6c63d334 100644 --- a/src/LanguageServer/Protocol/Handler/IInitializeManager.cs +++ b/src/LanguageServer/Protocol/Handler/IInitializeManager.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler; @@ -15,8 +14,5 @@ internal interface IInitializeManager : ILspService InitializeParams? TryGetInitializeParams(); - /// Expected to be non-default after the Initialize event. - ImmutableArray GetRequiredWorkspaceFolderPaths(); - void SetInitializeParams(InitializeParams initializeParams); } diff --git a/src/LanguageServer/Protocol/Handler/IWorkspaceFolderTracker.cs b/src/LanguageServer/Protocol/Handler/IWorkspaceFolderTracker.cs new file mode 100644 index 0000000000000..718f28ba11e19 --- /dev/null +++ b/src/LanguageServer/Protocol/Handler/IWorkspaceFolderTracker.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler; + +internal interface IWorkspaceFolderTracker : ILspService +{ + event EventHandler? WorkspaceFoldersChanged; + + ImmutableHashSet GetRequiredWorkspaceFolderPaths(); + + void Update(WorkspaceFolder[]? addedFolders, WorkspaceFolder[]? removedFolders); +} diff --git a/src/LanguageServer/Protocol/Handler/InitializeManager.cs b/src/LanguageServer/Protocol/Handler/InitializeManager.cs index d3d783c4ac541..907578daceccd 100644 --- a/src/LanguageServer/Protocol/Handler/InitializeManager.cs +++ b/src/LanguageServer/Protocol/Handler/InitializeManager.cs @@ -3,20 +3,13 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler; internal sealed class InitializeManager : IInitializeManager { - public InitializeManager() - { - } - private InitializeParams? _initializeParams; - private ImmutableArray _workspaceFolderPathsOpt; public ClientCapabilities GetClientCapabilities() { @@ -32,22 +25,6 @@ public void SetInitializeParams(InitializeParams initializeParams) { Contract.ThrowIfFalse(_initializeParams == null); _initializeParams = initializeParams; - _workspaceFolderPathsOpt = initializeParams.WorkspaceFolders is [_, ..] workspaceFolders ? GetFolderPaths(workspaceFolders) : []; - - static ImmutableArray GetFolderPaths(WorkspaceFolder[] workspaceFolders) - { - var builder = ArrayBuilder.GetInstance(workspaceFolders.Length); - foreach (var workspaceFolder in workspaceFolders) - { - if (workspaceFolder.DocumentUri.ParsedDocumentUri is null) - continue; - - var workspaceFolderPath = workspaceFolder.DocumentUri.GetDocumentFilePathFromUri(); - builder.Add(workspaceFolderPath); - } - - return builder.ToImmutableAndFree(); - } } public InitializeParams? TryGetInitializeParams() @@ -55,12 +32,6 @@ static ImmutableArray GetFolderPaths(WorkspaceFolder[] workspaceFolders) return _initializeParams; } - public ImmutableArray GetRequiredWorkspaceFolderPaths() - { - Contract.ThrowIfTrue(_workspaceFolderPathsOpt.IsDefault, $"{nameof(_workspaceFolderPathsOpt)} was not initialized. Was this accessed before the OnInitialized event ran?"); - return _workspaceFolderPathsOpt; - } - public ClientCapabilities? TryGetClientCapabilities() { return _initializeParams?.Capabilities; diff --git a/src/LanguageServer/Protocol/Handler/References/FindAllReferencesHandler.cs b/src/LanguageServer/Protocol/Handler/References/FindAllReferencesHandler.cs index 9d5af0e2529a2..27ca9e8a65d43 100644 --- a/src/LanguageServer/Protocol/Handler/References/FindAllReferencesHandler.cs +++ b/src/LanguageServer/Protocol/Handler/References/FindAllReferencesHandler.cs @@ -59,12 +59,16 @@ public FindAllReferencesHandler( using var progress = BufferedProgress.Create(referenceParams.PartialResultToken); var includeDeclaration = referenceParams.Context.IncludeDeclaration; - await FindReferencesAsync(progress, workspace, document, linePosition, clientCapabilities.HasVisualStudioLspCapability(), includeDeclaration, _globalOptions, _metadataAsSourceFileService, _asyncListener, cancellationToken).ConfigureAwait(false); + var hasReferences = await FindReferencesAsync(progress, workspace, document, linePosition, clientCapabilities.HasVisualStudioLspCapability(), includeDeclaration, _globalOptions, _metadataAsSourceFileService, _asyncListener, cancellationToken).ConfigureAwait(false); - return progress.GetFlattenedValues(); + var references = progress.GetFlattenedValues(); + if (!hasReferences) + await context.GetRequiredLspService().ReportEmptySymbolResultAsync(LSP.Methods.TextDocumentReferencesName, document, referenceParams.Position, cancellationToken).ConfigureAwait(false); + + return references; } - internal static async Task FindReferencesAsync( + internal static async Task FindReferencesAsync( IProgress[]> progress, Workspace workspace, Document document, @@ -86,5 +90,6 @@ internal static async Task FindReferencesAsync( var classificationOptions = globalOptions.GetClassificationOptionsProvider(); await findUsagesService.FindReferencesAsync(findUsagesContext, document, position, classificationOptions, cancellationToken).ConfigureAwait(false); await findUsagesContext.OnCompletedAsync(cancellationToken).ConfigureAwait(false); + return findUsagesContext.HasReportedReferences; } } diff --git a/src/LanguageServer/Protocol/Handler/References/FindImplementationsHandler.cs b/src/LanguageServer/Protocol/Handler/References/FindImplementationsHandler.cs index fb3854f08919c..be32f00bbba97 100644 --- a/src/LanguageServer/Protocol/Handler/References/FindImplementationsHandler.cs +++ b/src/LanguageServer/Protocol/Handler/References/FindImplementationsHandler.cs @@ -41,7 +41,11 @@ public FindImplementationsHandler(IGlobalOptionService globalOptions) var linePosition = ProtocolConversions.PositionToLinePosition(request.Position); var classificationOptions = _globalOptions.GetClassificationOptionsProvider(); - return await FindImplementationsAsync(document, linePosition, classificationOptions, supportsVisualStudioExtensions, cancellationToken).ConfigureAwait(false); + var locations = await FindImplementationsAsync(document, linePosition, classificationOptions, supportsVisualStudioExtensions, cancellationToken).ConfigureAwait(false); + if (locations.Length == 0) + await context.GetRequiredLspService().ReportEmptySymbolResultAsync(LSP.Methods.TextDocumentImplementationName, document, request.Position, cancellationToken).ConfigureAwait(false); + + return locations; } internal static async Task FindImplementationsAsync(Document document, LinePosition linePosition, OptionsProvider classificationOptions, bool supportsVisualStudioExtensions, CancellationToken cancellationToken) diff --git a/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs b/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs index 13e95082da3b7..26fc077ccf416 100644 --- a/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs +++ b/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs @@ -74,6 +74,8 @@ internal sealed class FindUsagesLSPContext : FindUsagesContext // Unique identifier given to each definition and reference. private int _id = 0; + internal bool HasReportedReferences { get; private set; } + public FindUsagesLSPContext( IProgress[]> progress, Workspace workspace, @@ -374,6 +376,9 @@ private static ClassifiedTextRun[] GetClassifiedTextRuns( private async ValueTask ReportReferencesAsync(ImmutableSegmentedList> referencesToReport, CancellationToken cancellationToken) { + if (!referencesToReport.IsEmpty) + HasReportedReferences = true; + // We can report outside of the lock here since _progress is thread-safe. _progress.Report([.. referencesToReport]); } diff --git a/src/LanguageServer/Protocol/Handler/ServerLifetime/DidChangeWorkspaceFoldersNotificationHandler.cs b/src/LanguageServer/Protocol/Handler/ServerLifetime/DidChangeWorkspaceFoldersNotificationHandler.cs new file mode 100644 index 0000000000000..1129f5c2574e9 --- /dev/null +++ b/src/LanguageServer/Protocol/Handler/ServerLifetime/DidChangeWorkspaceFoldersNotificationHandler.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CommonLanguageServerProtocol.Framework; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.ServerLifetime; + +[ExportCSharpVisualBasicLspServiceFactory(typeof(DidChangeWorkspaceFoldersNotificationHandler)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DidChangeWorkspaceFoldersNotificationHandlerFactory() : ILspServiceFactory +{ + public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) + => new DidChangeWorkspaceFoldersNotificationHandler( + lspServices.GetRequiredService()); +} + +[Method(Methods.WorkspaceDidChangeWorkspaceFoldersName)] +internal sealed class DidChangeWorkspaceFoldersNotificationHandler(IWorkspaceFolderTracker workspaceFolderTracker) + : ILspServiceNotificationHandler +{ + public bool MutatesSolutionState => true; + public bool RequiresLSPSolution => false; + + Task INotificationHandler.HandleNotificationAsync( + DidChangeWorkspaceFoldersParams request, RequestContext requestContext, CancellationToken cancellationToken) + { + workspaceFolderTracker.Update( + addedFolders: request.Event?.Added, + removedFolders: request.Event?.Removed); + + return Task.CompletedTask; + } +} diff --git a/src/LanguageServer/Protocol/Handler/ServerLifetime/InitializeHandler.cs b/src/LanguageServer/Protocol/Handler/ServerLifetime/InitializeHandler.cs index 330079e97baee..f3acc3dd1704d 100644 --- a/src/LanguageServer/Protocol/Handler/ServerLifetime/InitializeHandler.cs +++ b/src/LanguageServer/Protocol/Handler/ServerLifetime/InitializeHandler.cs @@ -22,6 +22,7 @@ public async Task HandleRequestAsync(InitializeParams request, var clientCapabilitiesManager = context.GetRequiredLspService(); var clientCapabilities = request.Capabilities; clientCapabilitiesManager.SetInitializeParams(request); + context.GetRequiredLspService().Update(request.WorkspaceFolders, removedFolders: null); var lspServices = context.GetRequiredService(); var capabilitiesProvider = context.GetRequiredLspService(); diff --git a/src/LanguageServer/Protocol/Handler/Telemetry/RequestTelemetryLogger.cs b/src/LanguageServer/Protocol/Handler/Telemetry/RequestTelemetryLogger.cs index 3b93f9f3bc960..de6acbb28a728 100644 --- a/src/LanguageServer/Protocol/Handler/Telemetry/RequestTelemetryLogger.cs +++ b/src/LanguageServer/Protocol/Handler/Telemetry/RequestTelemetryLogger.cs @@ -3,8 +3,13 @@ // See the LICENSE file in the project root for more information. using System; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Telemetry; +using Microsoft.CodeAnalysis.Text; +using LSP = Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler; @@ -32,7 +37,7 @@ public void UpdateFindDocumentTelemetryData(bool success, string? workspaceKind) protected virtual void IncreaseFindDocumentCount(string workspaceCounterMetricName) { - RoslynTelemetry.Count(FunctionId.LSP_FindDocumentInWorkspace, workspaceCounterMetricName, 1, + RoslynTelemetry.Current.Count(FunctionId.LSP_FindDocumentInWorkspace, workspaceCounterMetricName, 1, new("server", ServerTypeName), new("workspace", workspaceCounterMetricName)); } @@ -40,11 +45,60 @@ protected virtual void IncreaseFindDocumentCount(string workspaceCounterMetricNa public void UpdateUsedForkedSolutionCounter(bool usedForkedSolution) { var metricName = usedForkedSolution ? "ForkedCount" : "NonForkedCount"; - RoslynTelemetry.Count(FunctionId.LSP_UsedForkedSolution, metricName, 1, + RoslynTelemetry.Current.Count(FunctionId.LSP_UsedForkedSolution, metricName, 1, new("server", ServerTypeName), new("usedForkedSolution", usedForkedSolution)); } + public async Task ReportEmptySymbolResultAsync( + string method, + Document document, + LSP.Position position, + CancellationToken cancellationToken) + { + var telemetry = RoslynTelemetry.Current; + if (!telemetry.IsEnabled(FunctionId.LSP_SymbolRequest_EmptyResult)) + return; + + var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + var token = default(SyntaxToken); + var positionKind = GetInvalidPositionKind(text, position, out var optionalLine); + var isValidLine = optionalLine.HasValue; + var isValidPosition = positionKind is null; + var line = optionalLine.GetValueOrDefault(); + + if (isValidPosition) + { + var absolutePosition = line.Start + position.Character; + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + token = root.FindToken(absolutePosition, findInsideTrivia: true); + positionKind = GetPositionKind(text, line, absolutePosition, token); + } + + var logMessage = KeyValueLogMessage.Create(static (properties, args) => + { + properties["server"] = args.serverTypeName; + properties["method"] = args.method; + properties["line"] = args.position.Line; + properties["character"] = args.position.Character; + properties["lineCount"] = args.text.Lines.Count; + properties["language"] = args.document.Project.Language; + properties["workspaceKind"] = args.document.Project.Solution.WorkspaceKind; + properties["positionKind"] = args.positionKind; + + if (args.isValidLine) + properties["lineLength"] = args.line.Span.Length; + + if (args.isValidPosition) + { + properties["tokenRawKind"] = args.token.RawKind; + properties["parentNodeRawKind"] = args.token.Parent?.RawKind ?? 0; + } + }, (serverTypeName: ServerTypeName, method, document, position, text, line, positionKind, isValidLine, isValidPosition, token), logLevel: LogLevel.Information); + + telemetry.Log(FunctionId.LSP_SymbolRequest_EmptyResult, logMessage); + } + public void UpdateTelemetryData( string methodName, string? language, @@ -53,10 +107,10 @@ public void UpdateTelemetryData( Result result) { // Store the request time metrics per LSP method. - RoslynTelemetry.Record(FunctionId.LSP_TimeInQueue, "TimeInQueue", (long)queuedDuration.TotalMilliseconds, + RoslynTelemetry.Current.Record(FunctionId.LSP_TimeInQueue, "TimeInQueue", (long)queuedDuration.TotalMilliseconds, new("server", ServerTypeName)); - RoslynTelemetry.Record(FunctionId.LSP_RequestDuration, "RequestDuration", (long)requestDuration.TotalMilliseconds, + RoslynTelemetry.Current.Record(FunctionId.LSP_RequestDuration, "RequestDuration", (long)requestDuration.TotalMilliseconds, new("server", ServerTypeName), new("method", methodName), new("language", language)); @@ -69,7 +123,7 @@ public void UpdateTelemetryData( _ => throw ExceptionUtilities.UnexpectedValue(result) }; - RoslynTelemetry.Count(FunctionId.LSP_RequestCounter, metricName, 1, + RoslynTelemetry.Current.Count(FunctionId.LSP_RequestCounter, metricName, 1, new("server", ServerTypeName), new("method", methodName), new("language", language)); @@ -79,7 +133,36 @@ public void Dispose() { // Ensure that telemetry logged for this server instance is flushed before potentially creating a new instance. // This is also called on disposal of the telemetry session, but will no-op if already flushed. - RoslynTelemetry.Flush(); + RoslynTelemetry.Current.Flush(); + } + + private static string? GetInvalidPositionKind(SourceText text, LSP.Position position, out TextLine? line) + { + if (position.Line < 0 || position.Line >= text.Lines.Count) + { + line = null; + return "LineOutOfRange"; + } + + line = text.Lines[position.Line]; + if (position.Character < 0 || position.Character > line.Value.Span.Length) + return "CharacterOutOfRange"; + + return null; + } + + private static string GetPositionKind(SourceText text, TextLine line, int absolutePosition, SyntaxToken token) + { + if (absolutePosition == text.Length) + return "EndOfFile"; + + if (absolutePosition == line.End) + return "EndOfLine"; + + if (char.IsWhiteSpace(text[absolutePosition])) + return "Whitespace"; + + return token.Span.Contains(absolutePosition) ? "Token" : "Trivia"; } internal enum Result diff --git a/src/LanguageServer/Protocol/Handler/WorkspaceFolderTracker.cs b/src/LanguageServer/Protocol/Handler/WorkspaceFolderTracker.cs new file mode 100644 index 0000000000000..188f46d33f39e --- /dev/null +++ b/src/LanguageServer/Protocol/Handler/WorkspaceFolderTracker.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.IO; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler; + +internal sealed class WorkspaceFolderTracker : IWorkspaceFolderTracker +{ + // The gate makes updates atomic; volatile allows lock-free reads of the latest immutable snapshot. + private readonly object _gate = new(); + private volatile ImmutableHashSet _workspaceFolderPaths = ImmutableHashSet.Create(PathUtilities.Comparer); + + public event EventHandler? WorkspaceFoldersChanged; + + public void Update(WorkspaceFolder[]? addedFolders, WorkspaceFolder[]? removedFolders) + { + ImmutableHashSet updatedWorkspaceFolderPaths; + lock (_gate) + { + updatedWorkspaceFolderPaths = _workspaceFolderPaths; + if (removedFolders is not null) + { + foreach (var workspaceFolder in removedFolders) + { + if (GetNormalizedFilePath(workspaceFolder) is not { } normalizedPath) + continue; + + updatedWorkspaceFolderPaths = updatedWorkspaceFolderPaths.Remove(normalizedPath); + } + } + + if (addedFolders is not null) + { + foreach (var workspaceFolder in addedFolders) + { + if (GetNormalizedFilePath(workspaceFolder) is not { } normalizedPath) + continue; + + updatedWorkspaceFolderPaths = updatedWorkspaceFolderPaths.Add(normalizedPath); + } + } + + if (updatedWorkspaceFolderPaths.SetEquals(_workspaceFolderPaths)) + return; + + _workspaceFolderPaths = updatedWorkspaceFolderPaths; + } + + WorkspaceFoldersChanged?.Invoke(this, EventArgs.Empty); + } + + public ImmutableHashSet GetRequiredWorkspaceFolderPaths() + => _workspaceFolderPaths; + + private static string? GetNormalizedFilePath(WorkspaceFolder workspaceFolder) + => workspaceFolder.DocumentUri.ParsedDocumentUri?.IsFile == true + ? NormalizePath(workspaceFolder.DocumentUri.GetDocumentFilePathFromUri()) + : null; + + private static string NormalizePath(string path) + { + var fullPath = Path.GetFullPath(path); + var rootLength = Path.GetPathRoot(fullPath)?.Length ?? 0; + return fullPath.Length > rootLength && PathUtilities.IsDirectorySeparator(fullPath[^1]) + ? fullPath[..^1] + : fullPath; + } +} diff --git a/src/LanguageServer/Protocol/Protocol/CompletionParams.cs b/src/LanguageServer/Protocol/Protocol/CompletionParams.cs index 33a94014e2cef..cd0d6b507d778 100644 --- a/src/LanguageServer/Protocol/Protocol/CompletionParams.cs +++ b/src/LanguageServer/Protocol/Protocol/CompletionParams.cs @@ -13,7 +13,7 @@ namespace Roslyn.LanguageServer.Protocol; /// See the Language Server Protocol specification for additional information. /// /// -internal sealed class CompletionParams : TextDocumentPositionParams, IPartialResultParams?>, IWorkDoneProgressOptions +internal sealed class CompletionParams : TextDocumentPositionParams, IPartialResultParams?>, IWorkDoneProgressParams { /// /// The completion context. This is only available if the client specifies the @@ -37,7 +37,7 @@ public IProgress?>? PartialResultToken } /// - [JsonPropertyName("workDoneProgress")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public bool WorkDoneProgress { get; init; } + [JsonPropertyName(Methods.WorkDoneTokenName)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IProgress? WorkDoneToken { get; set; } } diff --git a/src/LanguageServer/Protocol/Protocol/DocumentRangeFormattingOptions.cs b/src/LanguageServer/Protocol/Protocol/DocumentRangeFormattingOptions.cs index 956d3269997eb..e8bccf1860f70 100644 --- a/src/LanguageServer/Protocol/Protocol/DocumentRangeFormattingOptions.cs +++ b/src/LanguageServer/Protocol/Protocol/DocumentRangeFormattingOptions.cs @@ -18,4 +18,12 @@ internal class DocumentRangeFormattingOptions : IWorkDoneProgressOptions [JsonPropertyName("workDoneProgress")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool WorkDoneProgress { get; init; } + + /// + /// Whether the server supports formatting multiple ranges at once. + /// + /// Since LSP 3.18 + [JsonPropertyName("rangesSupport")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public bool RangesSupport { get; init; } } diff --git a/src/LanguageServer/Protocol/Protocol/DocumentRangeFormattingParams.cs b/src/LanguageServer/Protocol/Protocol/DocumentRangeFormattingParams.cs index 0621faaca11a9..57dcd8d4daa2b 100644 --- a/src/LanguageServer/Protocol/Protocol/DocumentRangeFormattingParams.cs +++ b/src/LanguageServer/Protocol/Protocol/DocumentRangeFormattingParams.cs @@ -4,6 +4,7 @@ namespace Roslyn.LanguageServer.Protocol; +using System; using System.Text.Json.Serialization; /// @@ -12,7 +13,7 @@ namespace Roslyn.LanguageServer.Protocol; /// See the Language Server Protocol specification for additional information. /// /// -internal sealed class DocumentRangeFormattingParams : ITextDocumentParams +internal sealed class DocumentRangeFormattingParams : ITextDocumentParams, IWorkDoneProgressParams { /// /// Gets or sets the identifier for the text document to be formatted. @@ -46,4 +47,9 @@ public FormattingOptions Options get; set; } + + /// + [JsonPropertyName(Methods.WorkDoneTokenName)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IProgress? WorkDoneToken { get; set; } } diff --git a/src/LanguageServer/Protocol/Protocol/DocumentRangesFormattingParams.cs b/src/LanguageServer/Protocol/Protocol/DocumentRangesFormattingParams.cs new file mode 100644 index 0000000000000..fce2dad38f75a --- /dev/null +++ b/src/LanguageServer/Protocol/Protocol/DocumentRangesFormattingParams.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Roslyn.LanguageServer.Protocol; + +using System; +using System.Text.Json.Serialization; + +/// +/// Class which represents the parameter that is sent with textDocument/rangesFormatting message. +/// +/// See the Language Server Protocol specification for additional information. +/// +/// +/// Since LSP 3.18 +internal sealed class DocumentRangesFormattingParams : ITextDocumentParams, IWorkDoneProgressParams +{ + /// + /// Gets or sets the identifier for the text document to be formatted. + /// + [JsonPropertyName("textDocument")] + [JsonRequired] + public TextDocumentIdentifier TextDocument { get; set; } + + /// + /// Gets or sets the ranges to format. + /// + [JsonPropertyName("ranges")] + [JsonRequired] + public Range[] Ranges { get; set; } + + /// + /// Gets or sets the formatting options. + /// + [JsonPropertyName("options")] + [JsonRequired] + public FormattingOptions Options { get; set; } + + /// + [JsonPropertyName(Methods.WorkDoneTokenName)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IProgress? WorkDoneToken { get; set; } +} diff --git a/src/LanguageServer/Protocol/Protocol/InitializeParams.cs b/src/LanguageServer/Protocol/Protocol/InitializeParams.cs index 4dc52d5a4aaaf..7a4b5de995592 100644 --- a/src/LanguageServer/Protocol/Protocol/InitializeParams.cs +++ b/src/LanguageServer/Protocol/Protocol/InitializeParams.cs @@ -99,14 +99,14 @@ public ClientCapabilities Capabilities /// Gets or sets the initial trace setting. /// [JsonPropertyName("trace")] - [DefaultValue(typeof(TraceSetting), "off")] + [DefaultValue(typeof(TraceValue), "off")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public TraceSetting Trace + public TraceValue Trace { get; set; #pragma warning disable SA1500, SA1513 // Braces for multi-line statements should not share line, Closing brace should be followed by blank line - } = TraceSetting.Off; + } = TraceValue.Off; #pragma warning restore SA1500, SA1513 // Braces for multi-line statements should not share line, Closing brace should be followed by blank line /// diff --git a/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionClientCapabilities.cs b/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionClientCapabilities.cs new file mode 100644 index 0000000000000..a6947ca77a332 --- /dev/null +++ b/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionClientCapabilities.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Roslyn.LanguageServer.Protocol; + +/// +/// Client capabilities specific to inline completions. +/// +/// See the Language Server Protocol specification for additional information. +/// +/// +/// Since LSP 3.18 +internal sealed class InlineCompletionClientCapabilities : DynamicRegistrationSetting +{ +} diff --git a/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionContext.cs b/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionContext.cs new file mode 100644 index 0000000000000..f72a741f27dc0 --- /dev/null +++ b/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionContext.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Roslyn.LanguageServer.Protocol; + +using System.Text.Json.Serialization; + +/// +/// Provides information about the context in which an inline completion was requested. +/// +/// See the Language Server Protocol specification for additional information. +/// +/// +/// Since LSP 3.18 +internal sealed class InlineCompletionContext +{ + /// + /// Describes how the inline completion was triggered. + /// + [JsonPropertyName("triggerKind")] + [JsonRequired] + public InlineCompletionTriggerKind TriggerKind { get; set; } + + /// + /// Provides information about the currently selected item in the autocomplete widget + /// if it is visible. + /// + /// If set, provided inline completions must extend the text of the selected item + /// and use the same range, otherwise they are not shown as preview. + /// As an example, if the document text is console. and the selected item is + /// .log replacing the . in the document, the inline completion must + /// also replace . and start with .log, for example .telemet. + /// + /// + /// Inline completion providers are requested again whenever the selected item changes. + /// + /// + [JsonPropertyName("selectedCompletionInfo")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public SelectedCompletionInfo? SelectedCompletionInfo { get; set; } +} diff --git a/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionItem.cs b/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionItem.cs new file mode 100644 index 0000000000000..19dd7fb9304df --- /dev/null +++ b/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionItem.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Roslyn.LanguageServer.Protocol; + +using System.Text.Json.Serialization; + +/// +/// An inline completion item represents a text snippet that is proposed inline +/// to complete text that is being typed. +/// +/// See the Language Server Protocol specification for additional information. +/// +/// +/// Since LSP 3.18 +internal sealed class InlineCompletionItem +{ + /// + /// The text to replace the range with. Must be set. + /// + /// Is used both for the preview and the accept operation. + /// + /// + [JsonPropertyName("insertText")] + [JsonRequired] + public SumType InsertText { get; set; } + + /// + /// A text that is used to decide if this inline completion should be shown. + /// When the is used. + /// + /// An inline completion is shown if the text to replace is a prefix of the + /// filter text. + /// + /// + [JsonPropertyName("filterText")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? FilterText { get; set; } + + /// + /// The range to replace. + /// + /// Must begin and end on the same line. + /// + /// + /// Prefer replacements over insertions to provide a better experience when + /// the user deletes typed text. + /// + /// + [JsonPropertyName("range")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Range? Range { get; set; } + + /// + /// An optional that is executed after + /// inserting this completion. + /// + [JsonPropertyName("command")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Command? Command { get; set; } + + /// + /// The format of the insert text. The format applies to the . + /// If omitted defaults to . + /// + [JsonPropertyName("insertTextFormat")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public InsertTextFormat InsertTextFormat { get; set; } = InsertTextFormat.Plaintext; +} diff --git a/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionList.cs b/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionList.cs new file mode 100644 index 0000000000000..3e693723bfbd0 --- /dev/null +++ b/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionList.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Roslyn.LanguageServer.Protocol; + +using System.Text.Json.Serialization; + +/// +/// Represents a collection of inline completion items to be presented in the editor. +/// +/// See the Language Server Protocol specification for additional information. +/// +/// +/// Since LSP 3.18 +internal sealed class InlineCompletionList +{ + /// + /// The inline completion items. + /// + [JsonPropertyName("items")] + [JsonRequired] + public InlineCompletionItem[] Items { get; set; } +} diff --git a/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionOptions.cs b/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionOptions.cs new file mode 100644 index 0000000000000..ff026de945bb9 --- /dev/null +++ b/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionOptions.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Roslyn.LanguageServer.Protocol; + +using System.Text.Json.Serialization; + +/// +/// Inline completion options used during static registration. +/// +/// See the Language Server Protocol specification for additional information. +/// +/// +/// Since LSP 3.18 +internal class InlineCompletionOptions : IWorkDoneProgressOptions +{ + /// + [JsonPropertyName("workDoneProgress")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public bool WorkDoneProgress { get; init; } +} diff --git a/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionParams.cs b/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionParams.cs new file mode 100644 index 0000000000000..b5886dcd4372c --- /dev/null +++ b/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionParams.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Roslyn.LanguageServer.Protocol; + +using System; +using System.Text.Json.Serialization; + +/// +/// A parameter literal used in inline completion requests. +/// +/// See the Language Server Protocol specification for additional information. +/// +/// +/// Since LSP 3.18 +internal sealed class InlineCompletionParams : ITextDocumentPositionParams, IWorkDoneProgressParams +{ + /// + /// The text document. + /// + [JsonPropertyName("textDocument")] + [JsonRequired] + public TextDocumentIdentifier TextDocument { get; set; } + + /// + /// The position inside the text document. + /// + [JsonPropertyName("position")] + [JsonRequired] + public Position Position { get; set; } + + /// + /// Additional information about the context in which inline completions + /// were requested. + /// + [JsonPropertyName("context")] + [JsonRequired] + public InlineCompletionContext Context { get; set; } + + /// + [JsonPropertyName(Methods.WorkDoneTokenName)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IProgress? WorkDoneToken { get; set; } +} diff --git a/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionRegistrationOptions.cs b/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionRegistrationOptions.cs new file mode 100644 index 0000000000000..69023b0cd52b5 --- /dev/null +++ b/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionRegistrationOptions.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Roslyn.LanguageServer.Protocol; + +using System.Text.Json.Serialization; + +/// +/// Inline completion options used during static or dynamic capability registration. +/// +/// See the Language Server Protocol specification for additional information. +/// +/// +/// Since LSP 3.18 +internal sealed class InlineCompletionRegistrationOptions : InlineCompletionOptions, ITextDocumentRegistrationOptions, IStaticRegistrationOptions +{ + /// + [JsonPropertyName("documentSelector")] + public DocumentFilter[]? DocumentSelector { get; set; } + + /// + [JsonPropertyName("id")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Id { get; set; } +} diff --git a/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionTriggerKind.cs b/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionTriggerKind.cs new file mode 100644 index 0000000000000..e60e7a2f4ef3e --- /dev/null +++ b/src/LanguageServer/Protocol/Protocol/InlineCompletions/InlineCompletionTriggerKind.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Roslyn.LanguageServer.Protocol; + +/// +/// Describes how an inline completion request was triggered. +/// +/// See the Language Server Protocol specification for additional information. +/// +/// +/// Since LSP 3.18 +internal enum InlineCompletionTriggerKind +{ + /// + /// Completion was triggered explicitly by a user gesture. + /// + Invoked = 1, + + /// + /// Completion was triggered automatically while editing. + /// + Automatic = 2, +} diff --git a/src/LanguageServer/Protocol/Protocol/InlineCompletions/SelectedCompletionInfo.cs b/src/LanguageServer/Protocol/Protocol/InlineCompletions/SelectedCompletionInfo.cs new file mode 100644 index 0000000000000..828af8aa38efa --- /dev/null +++ b/src/LanguageServer/Protocol/Protocol/InlineCompletions/SelectedCompletionInfo.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Roslyn.LanguageServer.Protocol; + +using System.Text.Json.Serialization; + +/// +/// Describes the currently selected completion item. +/// +/// See the Language Server Protocol specification for additional information. +/// +/// +/// Since LSP 3.18 +internal sealed class SelectedCompletionInfo +{ + /// + /// The range that will be replaced if this completion item is accepted. + /// + [JsonPropertyName("range")] + [JsonRequired] + public Range Range { get; set; } + + /// + /// The text the range will be replaced with if this completion is accepted. + /// + [JsonPropertyName("text")] + [JsonRequired] + public string Text { get; set; } +} diff --git a/src/LanguageServer/Protocol/Protocol/InlineCompletions/StringValue.cs b/src/LanguageServer/Protocol/Protocol/InlineCompletions/StringValue.cs new file mode 100644 index 0000000000000..acd8ca671fef4 --- /dev/null +++ b/src/LanguageServer/Protocol/Protocol/InlineCompletions/StringValue.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Roslyn.LanguageServer.Protocol; + +using System.Text.Json.Serialization; + +/// +/// A string value used as a snippet, a computed value, or with both. +/// +/// See the Language Server Protocol specification for additional information. +/// +/// +/// Since LSP 3.18 +internal sealed class StringValue +{ + /// + /// The kind of string value. + /// + [JsonPropertyName("kind")] + [JsonRequired] + public string Kind { get; init; } = "snippet"; + + /// + /// The snippet string. + /// + [JsonPropertyName("value")] + [JsonRequired] + public string Value { get; set; } +} diff --git a/src/LanguageServer/Protocol/Protocol/Methods.Document.cs b/src/LanguageServer/Protocol/Protocol/Methods.Document.cs index cf9ee709397e9..97e770de5154f 100644 --- a/src/LanguageServer/Protocol/Protocol/Methods.Document.cs +++ b/src/LanguageServer/Protocol/Protocol/Methods.Document.cs @@ -372,7 +372,7 @@ partial class Methods /// /// /// If computing full completion items is expensive, servers can additionally provide a handler for the completion - /// item resolve request (‘completionItem/resolve’), which is sent when a completion item is selected in the user interface. + /// item resolve request (‘completionItem/resolve’), which is sent when a completion item is selected in the user interface. /// /// /// See the Language Server Protocol specification for additional information. @@ -401,6 +401,26 @@ partial class Methods /// public static readonly LspRequest TextDocumentCompletionResolve = new(TextDocumentCompletionResolveName); + /// + /// Method name for 'textDocument/inlineCompletion'. + /// + /// The inline completion request is sent from the client to the server to compute inline + /// completions at a given cursor position. Inline completions are similar to completions + /// but are displayed inline with the text and can only be accepted or rejected. + /// + /// + /// See the Language Server Protocol specification for additional information. + /// + /// + /// Since LSP 3.18 + public const string TextDocumentInlineCompletionName = "textDocument/inlineCompletion"; + + /// + /// Strongly typed message object for 'textDocument/inlineCompletion'. + /// + /// Since LSP 3.18 + public static readonly LspRequest?> TextDocumentInlineCompletion = new(TextDocumentInlineCompletionName); + /// /// Method name for 'textDocument/signatureHelp'. /// @@ -547,6 +567,23 @@ partial class Methods /// public static readonly LspRequest TextDocumentRangeFormatting = new(TextDocumentRangeFormattingName); + /// + /// Method name for 'textDocument/rangesFormatting'. + /// + /// The document ranges formatting request is sent from the client to the server to format multiple ranges in a document at once. + /// + /// + /// See the Language Server Protocol specification for additional information. + /// + /// + /// Since LSP 3.18 + public const string TextDocumentRangesFormattingName = "textDocument/rangesFormatting"; + + /// + /// Strongly typed message object for 'textDocument/rangesFormatting'. + /// + public static readonly LspRequest TextDocumentRangesFormatting = new(TextDocumentRangesFormattingName); + /// /// Method name for 'textDocument/onTypeFormatting'. /// diff --git a/src/LanguageServer/Protocol/Protocol/Navigation/CallHierarchyIncomingCallsParams.cs b/src/LanguageServer/Protocol/Protocol/Navigation/CallHierarchyIncomingCallsParams.cs index ba00e3b5233b8..c10508481ac3a 100644 --- a/src/LanguageServer/Protocol/Protocol/Navigation/CallHierarchyIncomingCallsParams.cs +++ b/src/LanguageServer/Protocol/Protocol/Navigation/CallHierarchyIncomingCallsParams.cs @@ -29,9 +29,6 @@ internal sealed class CallHierarchyIncomingCallsParams : IWorkDoneProgressParams public IProgress? WorkDoneToken { get; set; } /// - /// - /// may only be used if the client opts in via - /// [JsonPropertyName(Methods.PartialResultTokenName)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IProgress? PartialResultToken { get; set; } diff --git a/src/LanguageServer/Protocol/Protocol/Navigation/CallHierarchyOutgoingCallsParams.cs b/src/LanguageServer/Protocol/Protocol/Navigation/CallHierarchyOutgoingCallsParams.cs index 19ed1b6773cc9..3f4e94d47a5f9 100644 --- a/src/LanguageServer/Protocol/Protocol/Navigation/CallHierarchyOutgoingCallsParams.cs +++ b/src/LanguageServer/Protocol/Protocol/Navigation/CallHierarchyOutgoingCallsParams.cs @@ -8,9 +8,9 @@ namespace Roslyn.LanguageServer.Protocol; /// -/// The params sent in a 'callHierarchy/incomingCalls' request. +/// The params sent in a 'callHierarchy/outgoingCalls' request. /// -/// See the Language Server Protocol specification for additional information. +/// See the Language Server Protocol specification for additional information. /// /// /// Since LSP 3.16 @@ -29,9 +29,6 @@ internal sealed class CallHierarchyOutgoingCallsParams : IWorkDoneProgressParams public IProgress? WorkDoneToken { get; set; } /// - /// - /// may only be used if the client opts in via - /// [JsonPropertyName(Methods.PartialResultTokenName)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IProgress? PartialResultToken { get; set; } diff --git a/src/LanguageServer/Protocol/Protocol/Navigation/TypeHierarchySubtypesParams.cs b/src/LanguageServer/Protocol/Protocol/Navigation/TypeHierarchySubtypesParams.cs index 1f74a2466d16b..4fb75c3b70c33 100644 --- a/src/LanguageServer/Protocol/Protocol/Navigation/TypeHierarchySubtypesParams.cs +++ b/src/LanguageServer/Protocol/Protocol/Navigation/TypeHierarchySubtypesParams.cs @@ -14,7 +14,7 @@ namespace Roslyn.LanguageServer.Protocol; /// /// /// Since LSP 3.17 -internal sealed class TypeHierarchySubtypesParams : TextDocumentPositionParams, IWorkDoneProgressParams, IPartialResultParams +internal sealed class TypeHierarchySubtypesParams : IWorkDoneProgressParams, IPartialResultParams { /// /// The for which to return subtypes @@ -29,9 +29,6 @@ internal sealed class TypeHierarchySubtypesParams : TextDocumentPositionParams, public IProgress? WorkDoneToken { get; set; } /// - /// - /// may only be used if the client opts in via - /// [JsonPropertyName(Methods.PartialResultTokenName)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IProgress? PartialResultToken { get; set; } diff --git a/src/LanguageServer/Protocol/Protocol/Navigation/TypeHierarchySupertypesParams.cs b/src/LanguageServer/Protocol/Protocol/Navigation/TypeHierarchySupertypesParams.cs index d2b91e533eaab..0f0dcbb48b54a 100644 --- a/src/LanguageServer/Protocol/Protocol/Navigation/TypeHierarchySupertypesParams.cs +++ b/src/LanguageServer/Protocol/Protocol/Navigation/TypeHierarchySupertypesParams.cs @@ -14,7 +14,7 @@ namespace Roslyn.LanguageServer.Protocol; /// /// /// Since LSP 3.17 -internal sealed class TypeHierarchySupertypesParams : TextDocumentPositionParams, IWorkDoneProgressParams, IPartialResultParams +internal sealed class TypeHierarchySupertypesParams : IWorkDoneProgressParams, IPartialResultParams { /// /// The for which to return supertypes @@ -29,9 +29,6 @@ internal sealed class TypeHierarchySupertypesParams : TextDocumentPositionParams public IProgress? WorkDoneToken { get; set; } /// - /// - /// may only be used if the client opts in via - /// [JsonPropertyName(Methods.PartialResultTokenName)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IProgress? PartialResultToken { get; set; } diff --git a/src/LanguageServer/Protocol/Protocol/RangeFormattingClientCapabilities.cs b/src/LanguageServer/Protocol/Protocol/RangeFormattingClientCapabilities.cs index 7bd6d1e8b18ae..51f18f69a34e0 100644 --- a/src/LanguageServer/Protocol/Protocol/RangeFormattingClientCapabilities.cs +++ b/src/LanguageServer/Protocol/Protocol/RangeFormattingClientCapabilities.cs @@ -4,6 +4,8 @@ namespace Roslyn.LanguageServer.Protocol; +using System.Text.Json.Serialization; + /// /// Capabilities specific to the `textDocument/rangeFormatting` request. /// @@ -12,4 +14,11 @@ namespace Roslyn.LanguageServer.Protocol; /// internal sealed class RangeFormattingClientCapabilities : DynamicRegistrationSetting { + /// + /// Whether the client supports formatting multiple ranges at once. + /// + /// Since LSP 3.18 + [JsonPropertyName("rangesSupport")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public bool RangesSupport { get; init; } } diff --git a/src/LanguageServer/Protocol/Protocol/ServerCapabilities.cs b/src/LanguageServer/Protocol/Protocol/ServerCapabilities.cs index 0da048b0063ee..3b55b81ea7dc9 100644 --- a/src/LanguageServer/Protocol/Protocol/ServerCapabilities.cs +++ b/src/LanguageServer/Protocol/Protocol/ServerCapabilities.cs @@ -286,6 +286,14 @@ public TextDocumentSyncOptions? TextDocumentSync [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public SumType? WorkspaceSymbolProvider { get; set; } + /// + /// The server provides inline completions. + /// + /// Since LSP 3.18 + [JsonPropertyName("inlineCompletionProvider")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public SumType? InlineCompletionProvider { get; set; } + /// /// Workspace specific server capabilities. /// diff --git a/src/LanguageServer/Protocol/Protocol/TextDocumentClientCapabilities.cs b/src/LanguageServer/Protocol/Protocol/TextDocumentClientCapabilities.cs index 0bb1af5fca422..f686c7b974ae7 100644 --- a/src/LanguageServer/Protocol/Protocol/TextDocumentClientCapabilities.cs +++ b/src/LanguageServer/Protocol/Protocol/TextDocumentClientCapabilities.cs @@ -239,4 +239,12 @@ internal class TextDocumentClientCapabilities [JsonPropertyName("diagnostic")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public DiagnosticSetting? Diagnostic { get; set; } + + /// + /// Capabilities specific to the `textDocument/inlineCompletion` request. + /// + /// Since LSP 3.18 + [JsonPropertyName("inlineCompletion")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public InlineCompletionClientCapabilities? InlineCompletion { get; set; } } diff --git a/src/LanguageServer/Protocol/Protocol/TraceSetting.cs b/src/LanguageServer/Protocol/Protocol/TraceSetting.cs deleted file mode 100644 index da43e3361c8b6..0000000000000 --- a/src/LanguageServer/Protocol/Protocol/TraceSetting.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Roslyn.LanguageServer.Protocol; - -using System.ComponentModel; -using System.Text.Json.Serialization; - -/// -/// Value representing the language server trace setting. -/// -/// See the Language Server Protocol specification for additional information. -/// -[JsonConverter(typeof(StringEnumConverter))] -[TypeConverter(typeof(StringEnumConverter.TypeConverter))] -internal readonly record struct TraceSetting(string Value) : IStringEnum -{ - /// - /// Setting for 'off'. - /// - public static readonly TraceSetting Off = new("off"); - - /// - /// Setting for 'messages'. - /// - public static readonly TraceSetting Messages = new("messages"); - - /// - /// Setting for 'verbose'. - /// - public static readonly TraceSetting Verbose = new("verbose"); -} diff --git a/src/LanguageServer/Protocol/RoslynLanguageServer.cs b/src/LanguageServer/Protocol/RoslynLanguageServer.cs index 6b67a758f8b4f..7d7e10b7395ec 100644 --- a/src/LanguageServer/Protocol/RoslynLanguageServer.cs +++ b/src/LanguageServer/Protocol/RoslynLanguageServer.cs @@ -132,6 +132,7 @@ private FrozenDictionary> GetBaseServices( AddLazyService>(lspServices => new RequestContextFactory(lspServices)); AddLazyService(lspServices => new TelemetryService(lspServices)); AddLazyService(_ => HandlerProvider); + AddService(new WorkspaceFolderTracker()); AddService(new InitializeManager()); AddService(new InitializeHandler()); AddService(new InitializedHandler()); diff --git a/src/LanguageServer/ProtocolUnitTests/HandlerTests.cs b/src/LanguageServer/ProtocolUnitTests/HandlerTests.cs index fd3b00a7b4f3a..8a1fb4ec95f21 100644 --- a/src/LanguageServer/ProtocolUnitTests/HandlerTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/HandlerTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.IO; using System.Composition; using System.Text.Json; using System.Text.Json.Serialization; @@ -41,6 +42,24 @@ public HandlerTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) typeof(TestFSharpOnlyNotificationHandler), typeof(TestConfigurableDocumentHandler)); + [Fact] + public void WorkspaceFolderTrackerPreservesSetForEquivalentUpdate() + { + var tracker = new WorkspaceFolderTracker(); + var workspaceFolder = new WorkspaceFolder { DocumentUri = new("file:///Workspace"), Name = "Workspace" }; + var equivalentWorkspaceFolder = new WorkspaceFolder { DocumentUri = new("file:///Workspace/"), Name = "Workspace" }; + var eventCount = 0; + tracker.WorkspaceFoldersChanged += (_, _) => eventCount++; + + tracker.Update([workspaceFolder], removedFolders: null); + var workspaceFolders = tracker.GetRequiredWorkspaceFolderPaths(); + tracker.Update([equivalentWorkspaceFolder], [workspaceFolder]); + + Assert.Equal(1, eventCount); + Assert.Same(workspaceFolders, tracker.GetRequiredWorkspaceFolderPaths()); + Assert.Equal(Path.GetFullPath(workspaceFolder.DocumentUri.GetDocumentFilePathFromUri()), Assert.Single(workspaceFolders)); + } + [Theory, CombinatorialData] public async Task CanExecuteRequestHandler(bool mutatingLspWorkspace) { diff --git a/src/LanguageServer/ProtocolUnitTests/Telemetry/RequestTelemetryLoggerTests.cs b/src/LanguageServer/ProtocolUnitTests/Telemetry/RequestTelemetryLoggerTests.cs new file mode 100644 index 0000000000000..2d0839d5c083e --- /dev/null +++ b/src/LanguageServer/ProtocolUnitTests/Telemetry/RequestTelemetryLoggerTests.cs @@ -0,0 +1,166 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.UnitTests.Logging; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Test.Utilities; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Telemetry; + +[UseExportProvider] +public sealed class RequestTelemetryLoggerTests +{ + [Fact] + public async Task ReportsEmptyResultPosition() + { + using var telemetry = RoslynTelemetry.SetCurrent(new RoslynTelemetry()); + var sink = new TestTelemetryEventSink(); + using var registration = RoslynTelemetry.Current.AddEventSink(sink); + using var workspace = TestWorkspace.CreateCSharp( + """ + class C + { + + } + """); + var document = workspace.CurrentSolution.Projects.Single().Documents.Single(); + var position = new Position(line: 2, character: 0); + var logger = new RequestTelemetryLogger(WellKnownLspServerKinds.CSharpVisualBasicLspServer.ToTelemetryString()); + + await logger.ReportEmptySymbolResultAsync( + Methods.TextDocumentDefinitionName, + document, + position, + CancellationToken.None); + + var telemetryEvent = Assert.Single(sink.PostedEvents); + Assert.Equal("vs/ide/vbcs/lsp/symbolrequest/emptyresult", telemetryEvent.Name); + var properties = telemetryEvent.Properties; + var text = await document.GetTextAsync(); + var root = await document.GetSyntaxRootAsync(); + var absolutePosition = text.Lines[2].Start; + + Assert.Equal(0, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.character"]); + Assert.Equal(LanguageNames.CSharp, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.language"]); + Assert.Equal(2, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.line"]); + Assert.Equal(text.Lines.Count, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.linecount"]); + Assert.Equal(0, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.linelength"]); + Assert.Equal(Methods.TextDocumentDefinitionName, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.method"]); + Assert.Equal(root!.FindToken(absolutePosition, findInsideTrivia: true).Parent!.RawKind, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.parentnoderawkind"]); + Assert.Equal("EndOfLine", properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.positionkind"]); + Assert.Equal(WellKnownLspServerKinds.CSharpVisualBasicLspServer.ToTelemetryString(), properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.server"]); + Assert.Equal(root.FindToken(absolutePosition, findInsideTrivia: true).RawKind, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.tokenrawkind"]); + Assert.Equal(document.Project.Solution.WorkspaceKind, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.workspacekind"]); + } + + [Fact] + public async Task ReportsEmptyResultTokenContext() + { + using var telemetry = RoslynTelemetry.SetCurrent(new RoslynTelemetry()); + var sink = new TestTelemetryEventSink(); + using var registration = RoslynTelemetry.Current.AddEventSink(sink); + using var workspace = TestWorkspace.CreateCSharp( + """ + class C + { + void M() + { + M(); + } + } + """); + var document = workspace.CurrentSolution.Projects.Single().Documents.Single(); + var position = new Position(line: 4, character: 8); + var logger = new RequestTelemetryLogger(WellKnownLspServerKinds.CSharpVisualBasicLspServer.ToTelemetryString()); + await logger.ReportEmptySymbolResultAsync( + Methods.TextDocumentDefinitionName, + document, + position, + CancellationToken.None); + + var properties = Assert.Single(sink.PostedEvents).Properties; + var text = await document.GetTextAsync(); + var root = await document.GetSyntaxRootAsync(); + var token = root!.FindToken(text.Lines.GetPosition(new LinePosition(position.Line, position.Character)), findInsideTrivia: true); + + Assert.Equal(text.Lines.Count, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.linecount"]); + Assert.Equal(text.Lines[position.Line].Span.Length, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.linelength"]); + Assert.Equal("Token", properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.positionkind"]); + Assert.Equal(token.RawKind, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.tokenrawkind"]); + Assert.Equal(token.Parent!.RawKind, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.parentnoderawkind"]); + } + + [Theory] + [InlineData(-1, 0, "LineOutOfRange")] + [InlineData(2, 0, "LineOutOfRange")] + [InlineData(0, -1, "CharacterOutOfRange")] + [InlineData(0, 8, "CharacterOutOfRange")] + public async Task ReportsInvalidPosition(int line, int character, string expectedPositionKind) + { + using var telemetry = RoslynTelemetry.SetCurrent(new RoslynTelemetry()); + var sink = new TestTelemetryEventSink(); + using var registration = RoslynTelemetry.Current.AddEventSink(sink); + using var workspace = TestWorkspace.CreateCSharp("class C"); + var document = workspace.CurrentSolution.Projects.Single().Documents.Single(); + var logger = new RequestTelemetryLogger(WellKnownLspServerKinds.CSharpVisualBasicLspServer.ToTelemetryString()); + await logger.ReportEmptySymbolResultAsync( + Methods.TextDocumentDefinitionName, + document, + new Position(line, character), + CancellationToken.None); + + var properties = Assert.Single(sink.PostedEvents).Properties; + Assert.Equal(line, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.line"]); + Assert.Equal(character, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.character"]); + Assert.Equal(1, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.linecount"]); + Assert.Equal(expectedPositionKind, properties["vs.ide.vbcs.lsp.symbolrequest.emptyresult.positionkind"]); + } +} + +[CollectionDefinition(nameof(RequestTelemetryHandlerTests), DisableParallelization = true)] +public sealed class RequestTelemetryHandlerTestsCollection; + +[Collection(nameof(RequestTelemetryHandlerTests))] +[UseExportProvider] +public sealed class RequestTelemetryHandlerTests(ITestOutputHelper testOutputHelper) : AbstractLanguageServerProtocolTests(testOutputHelper) +{ + [Theory, CombinatorialData] + public async Task StreamedReferencesWithResultsAreNotReportedAsEmpty(bool mutatingLspWorkspace) + { + var markup = + """ + class C + { + void M() + { + {|caret:|}M(); + } + } + """; + await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, CapabilitiesWithVSExtensions); + using var progress = BufferedProgress.Create(null); + using var telemetry = RoslynTelemetry.SetCurrent(new RoslynTelemetry()); + var sink = new TestTelemetryEventSink(); + using var registration = RoslynTelemetry.Current.AddEventSink(sink); + + var results = await References.FindAllReferencesHandlerTests.RunFindAllReferencesAsync( + testLspServer, + testLspServer.GetLocations("caret").Single(), + progress); + + Assert.NotEmpty(results); + Assert.DoesNotContain( + sink.PostedEvents, + telemetryEvent => telemetryEvent.Name == "vs/ide/vbcs/lsp/symbolrequest/emptyresult"); + } +} diff --git a/src/LanguageServer/ProtocolUnitTests/TestConfigurableDocumentHandler.cs b/src/LanguageServer/ProtocolUnitTests/TestConfigurableDocumentHandler.cs index fc40c751b7276..17bf7f52cfc13 100644 --- a/src/LanguageServer/ProtocolUnitTests/TestConfigurableDocumentHandler.cs +++ b/src/LanguageServer/ProtocolUnitTests/TestConfigurableDocumentHandler.cs @@ -29,21 +29,24 @@ internal sealed class TestConfigurableDocumentHandler() : ILspServiceDocumentReq private bool? _mutatesSolutionState; private bool? _requiresLSPSolution; - private Task? _response; + private Func>? _responseFactory; public bool MutatesSolutionState => _mutatesSolutionState ?? throw new InvalidOperationException($"{nameof(ConfigureHandler)} has not been called"); public bool RequiresLSPSolution => _requiresLSPSolution ?? throw new InvalidOperationException($"{nameof(ConfigureHandler)} has not been called"); public void ConfigureHandler(bool mutatesSolutionState, bool requiresLspSolution, Task response) + => ConfigureHandler(mutatesSolutionState, requiresLspSolution, (_, _) => response); + + public void ConfigureHandler(bool mutatesSolutionState, bool requiresLspSolution, Func> responseFactory) { - if (_mutatesSolutionState is not null || _requiresLSPSolution is not null || _response is not null) + if (_mutatesSolutionState is not null || _requiresLSPSolution is not null || _responseFactory is not null) { throw new InvalidOperationException($"{nameof(ConfigureHandler)} has already been called"); } _mutatesSolutionState = mutatesSolutionState; _requiresLSPSolution = requiresLspSolution; - _response = response; + _responseFactory = responseFactory; } public TextDocumentIdentifier GetTextDocumentIdentifier(TestRequestWithDocument request) @@ -53,8 +56,8 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(TestRequestWithDocument public Task HandleRequestAsync(TestRequestWithDocument request, RequestContext context, CancellationToken cancellationToken) { - Contract.ThrowIfNull(_response, $"{nameof(ConfigureHandler)} has not been called"); - return _response; + Contract.ThrowIfNull(_responseFactory, $"{nameof(ConfigureHandler)} has not been called"); + return _responseFactory(context, cancellationToken); } public static void ConfigureHandler(TestLspServer server, bool mutatesSolutionState, bool requiresLspSolution, Task response) @@ -63,4 +66,15 @@ public static void ConfigureHandler(TestLspServer server, bool mutatesSolutionSt TypeRef.From(typeof(TestRequestWithDocument)), TypeRef.From(typeof(TestConfigurableResponse)), LanguageServerConstants.DefaultLanguageName); handler.ConfigureHandler(mutatesSolutionState, requiresLspSolution, response); } + + public static void ConfigureHandler( + TestLspServer server, + bool mutatesSolutionState, + bool requiresLspSolution, + Func> responseFactory) + { + var handler = (TestConfigurableDocumentHandler)server.GetQueueAccessor()!.Value.GetHandlerProvider().GetMethodHandler(TestConfigurableDocumentHandler.MethodName, + TypeRef.From(typeof(TestRequestWithDocument)), TypeRef.From(typeof(TestConfigurableResponse)), LanguageServerConstants.DefaultLanguageName); + handler.ConfigureHandler(mutatesSolutionState, requiresLspSolution, responseFactory); + } } diff --git a/src/LanguageServer/ProtocolUnitTests/TypeHierarchy/TypeHierarchyTests.cs b/src/LanguageServer/ProtocolUnitTests/TypeHierarchy/TypeHierarchyTests.cs index 29b5681602a83..800cf54861893 100644 --- a/src/LanguageServer/ProtocolUnitTests/TypeHierarchy/TypeHierarchyTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/TypeHierarchy/TypeHierarchyTests.cs @@ -418,8 +418,6 @@ private static void AssertEqualsItem(LSP.TypeHierarchyItem item, string expected LSP.Methods.TypeHierarchySupertypesName, new LSP.TypeHierarchySupertypesParams { - TextDocument = CreateTextDocumentIdentifier(item.Uri), - Position = item.SelectionRange.Start, Item = item, }, CancellationToken.None) ?? []; @@ -429,8 +427,6 @@ private static void AssertEqualsItem(LSP.TypeHierarchyItem item, string expected LSP.Methods.TypeHierarchySubtypesName, new LSP.TypeHierarchySubtypesParams { - TextDocument = CreateTextDocumentIdentifier(item.Uri), - Position = item.SelectionRange.Start, Item = item, }, CancellationToken.None) ?? []; diff --git a/src/Razor/Directory.Packages.props b/src/Razor/Directory.Packages.props index 99c56a1f061ff..6abe22930385e 100644 --- a/src/Razor/Directory.Packages.props +++ b/src/Razor/Directory.Packages.props @@ -14,7 +14,6 @@ - @@ -40,7 +39,7 @@ at the appropriate version, in combination with using CentralPackageTransitivePinningEnabled, will ensure the correct version is used in all places --> - + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.codegen.cs index b0f88cc1dc784..1931ab72ba948 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.codegen.cs @@ -48,15 +48,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. __builder.CloseElement(); } #pragma warning restore 1998 -#nullable restore -#line (7,13)-(9,1) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml" - - void IDisposable.Dispose(){ } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.ir.txt index b1196e1363c06..286de8d3dd6a2 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.ir.txt @@ -16,5 +16,3 @@ LazyIntermediateToken - (74:3,0 [4] BasicComponent.cshtml) - Html - CSharpExpression - (79:3,5 [29] BasicComponent.cshtml) LazyIntermediateToken - (79:3,5 [29] BasicComponent.cshtml) - CSharp - string.Format("{0}", "Hello") - CSharpCode - (132:6,12 [37] BasicComponent.cshtml) - LazyIntermediateToken - (132:6,12 [37] BasicComponent.cshtml) - CSharp - \n void IDisposable.Dispose(){ }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/DefaultTagHelperResolutionPhaseTest.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/DefaultTagHelperResolutionPhaseTest.cs index 2e80286a53770..6783ec3da1272 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/DefaultTagHelperResolutionPhaseTest.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/DefaultTagHelperResolutionPhaseTest.cs @@ -1,19 +1,59 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.Threading.Tasks; using Xunit; namespace Microsoft.AspNetCore.Razor.Language; public class DefaultTagHelperResolutionPhaseTest { + [Fact] + public void Process_ConcurrentMixedFileKinds_UsesCorrectResolver() + { + // Arrange + var configuration = new RazorConfiguration( + RazorLanguageVersion.Version_5_0, + "MVC-3.0", + Extensions: []); + + var projectEngine = RazorProjectEngine.Create( + configuration, + RazorProjectFileSystem.Empty, + builder => builder.RegisterExtensions()); + + var inputs = new[] + { + (Source: RazorSourceDocument.Create("

View

", "/Views/Index.cshtml"), FileKind: RazorFileKind.Legacy), + (Source: RazorSourceDocument.Create("

Component

", "/Components/App.razor"), FileKind: RazorFileKind.Component), + }; + + // Act and assert: match classic rzc's concurrency while repeating enough to exercise the timing-sensitive overlap. + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 4 }; + Parallel.For(0, 2_000, parallelOptions, iteration => + { + var (source, fileKind) = inputs[iteration % inputs.Length]; + var codeDocument = projectEngine.Process( + source, + fileKind, + ImmutableArray.Empty, + tagHelpers: null); + + var generatedCode = codeDocument.GetCSharpDocument().GeneratedCode.ToString(); + Assert.Contains( + fileKind == RazorFileKind.Component ? "BuildRenderTree" : "ExecuteAsync", + generatedCode); + }); + } + [Fact] public void MergeSourceSpans_SameLine_ReturnsCorrectSpan() { // Arrange var filePath = "test.razor"; var first = new SourceSpan(filePath, absoluteIndex: 10, lineIndex: 2, characterIndex: 5, length: 3, lineCount: 0, endCharacterIndex: 8); - var last = new SourceSpan(filePath, absoluteIndex: 15, lineIndex: 2, characterIndex: 10, length: 4, lineCount: 0, endCharacterIndex: 14); + var last = new SourceSpan(filePath, absoluteIndex: 15, lineIndex: 2, characterIndex: 10, length: 4, lineCount: 0, endCharacterIndex: 14); // Act var result = DefaultTagHelperResolutionPhase.MergeSourceSpans(first, last); @@ -36,7 +76,7 @@ public void MergeSourceSpans_MultiLine_ReturnsCorrectSpan() // first spans lines 1-2 (lineCount = 1 means it crosses into the next line) var first = new SourceSpan(filePath, absoluteIndex: 0, lineIndex: 1, characterIndex: 0, length: 10, lineCount: 1, endCharacterIndex: 5); // last is on line 3 (lineIndex = 3) - var last = new SourceSpan(filePath, absoluteIndex: 20, lineIndex: 3, characterIndex: 2, length: 5, lineCount: 0, endCharacterIndex: 7); + var last = new SourceSpan(filePath, absoluteIndex: 20, lineIndex: 3, characterIndex: 2, length: 5, lineCount: 0, endCharacterIndex: 7); // Act var result = DefaultTagHelperResolutionPhase.MergeSourceSpans(first, last); @@ -58,7 +98,7 @@ public void MergeSourceSpans_AdjacentSpans_ReturnsCorrectSpan() var filePath = "test.razor"; var first = new SourceSpan(filePath, absoluteIndex: 5, lineIndex: 0, characterIndex: 5, length: 3, lineCount: 0, endCharacterIndex: 8); // last starts right where first ends - var last = new SourceSpan(filePath, absoluteIndex: 8, lineIndex: 0, characterIndex: 8, length: 4, lineCount: 0, endCharacterIndex: 12); + var last = new SourceSpan(filePath, absoluteIndex: 8, lineIndex: 0, characterIndex: 8, length: 4, lineCount: 0, endCharacterIndex: 12); // Act var result = DefaultTagHelperResolutionPhase.MergeSourceSpans(first, last); @@ -94,7 +134,7 @@ public void MergeSourceSpans_NullFilePath_PreservesNullFilePath() { // Arrange — file path is null (e.g. for in-memory content) var first = new SourceSpan(filePath: null, absoluteIndex: 0, lineIndex: 0, characterIndex: 0, length: 3, lineCount: 0, endCharacterIndex: 3); - var last = new SourceSpan(filePath: null, absoluteIndex: 5, lineIndex: 0, characterIndex: 5, length: 2, lineCount: 0, endCharacterIndex: 7); + var last = new SourceSpan(filePath: null, absoluteIndex: 5, lineIndex: 0, characterIndex: 5, length: 2, lineCount: 0, endCharacterIndex: 7); // Act var result = DefaultTagHelperResolutionPhase.MergeSourceSpans(first, last); diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs index 1c1095b1ac2ce..2dccc6de7b7ee 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs @@ -6166,6 +6166,9 @@ @inherits BaseComponent CompileToAssembly(generated, [ // (1,31): warning CS8669: The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source. // public partial class TestComponent : BaseComponent + // The component splits into decl and impl partials, each of which repeats the annotated base + // type, so the warning is reported once per partial (both mapped to the same Razor location). + Diagnostic(ErrorCode.WRN_MissingNonNullTypesContextForAnnotationInGeneratedCode, "?").WithLocation(1, 31), Diagnostic(ErrorCode.WRN_MissingNonNullTypesContextForAnnotationInGeneratedCode, "?").WithLocation(1, 31) ]); } @@ -13329,6 +13332,41 @@ public class Image : ComponentBase CompileToAssembly(generated); } + [Fact] + public void TildePath_ComponentParam_InheritedAssetPath() + { + // Arrange + AdditionalSyntaxTrees.Add(Parse(AssetPathStubs)); + AdditionalSyntaxTrees.Add(Parse(""" + using Microsoft.AspNetCore.Components; + + namespace Test + { + public abstract class ImageBase : ComponentBase + { + [Parameter, AssetPath] + public virtual string Source { get; set; } + } + + public class Image : ImageBase + { + [Parameter] + public override string Source { get; set; } + } + } + """)); + + // Act + var generated = CompileToCSharp(""" + + """); + + // Assert + AssertDocumentNodeMatchesBaseline(generated.CodeDocument); + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated); + } + [Fact] public void TildePath_ComponentParam_NotOptedIn() { diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/MarkupSplitterComponentTest.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/MarkupSplitterComponentTest.cs index 74a04f36aaeeb..ca3b3239acf5d 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/MarkupSplitterComponentTest.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/MarkupSplitterComponentTest.cs @@ -226,10 +226,10 @@ public void PureCSharpCode_HasNoClassBodyMarkup_AndDoesNotSplit() } [Fact] - public void MarkupMethod_WithTypeParam_FallsBackAndCompiles() + public void MarkupMethod_WithTypeParam_SplitsAndCompiles() { - // A generic component (@typeparam) with a markup method takes the single-document path: the early - // move-based split would give the two partial halves inconsistent arity. Verify it still compiles. + // A generic component (@typeparam) with a markup method splits: the markup method lifts to the impl + // half and both partials repeat the `` arity, which is legal. Verify it compiles. var generated = CompileToCSharp(""" @typeparam T @code { @@ -237,6 +237,11 @@ @typeparam T } """); + // The markup method lifts to the impl half; the decl half keeps none of it. + Assert.NotNull(generated.DeclCode); + Assert.DoesNotContain("Make", generated.DeclCode); + Assert.Contains("Make", generated.Code); + CompileToAssembly(generated); } @@ -256,6 +261,27 @@ @inject System.IServiceProvider Services CompileToAssembly(generated); } + [Fact] + public void InjectOnly_SplitsAndCompiles() + { + // @inject is markup-free descriptor surface (an injected property), so a component whose only + // non-C# member is an @inject has no class-body markup and splits -- the injected property and any + // other markup-free members stay in the decl half; only the render body lifts to the impl. + var generated = CompileToCSharp(""" + @inject System.IServiceProvider Services + @code { + [Microsoft.AspNetCore.Components.Parameter] public int Count { get; set; } + } +

@Count

+ """); + + Assert.NotNull(generated.DeclCode); + Assert.Contains("Services", generated.DeclCode); + Assert.Contains("Count", generated.DeclCode); + + CompileToAssembly(generated); + } + [Fact] public void MarkupMethod_InNestedClass_FallsBackAndCompiles() { @@ -314,12 +340,10 @@ class Nested } [Fact] - public void Inject_AlongsideMarkup_FallsBack() + public void Inject_AlongsideMarkup_SplitsAndCompiles() { - // @inject lowers to a ComponentInjectIntermediateNode (surface, an ExtensionIntermediateNode - // like a template). The splitter can't route it, so a component mixing it with markup falls back. - // A markup *method* is used so the inject is the unambiguous cause (a markup property would fall - // back on its own). + // @inject lowers to a ComponentInjectIntermediateNode (a markup-free surface declaration). It has + // no markup to lift, so it routes to the decl half while the markup method lifts to the impl half. var generated = CompileToCSharp(""" @inject System.IServiceProvider Services @code { @@ -334,9 +358,17 @@ @inject System.IServiceProvider Services Assert.NotNull(primaryClass); Assert.NotNull(renderMethod); + // The split routes the markup method to impl and keeps the injected property in decl. var decision = MarkupSplitter.Split(primaryClass, renderMethod, generated.CodeDocument.ParserOptions); - var fallback = Assert.IsType(decision); - Assert.Equal(FallbackReason.UnsupportedClassBodyNode, fallback.Reason); + Assert.IsType(decision); + + Assert.NotNull(generated.DeclCode); + Assert.Contains("Services", generated.DeclCode); + Assert.DoesNotContain("Make", generated.DeclCode); + Assert.Contains("Make", generated.Code); + Assert.DoesNotContain("Services", generated.Code); + + CompileToAssembly(generated); } [Fact] @@ -446,5 +478,83 @@ public void DeclDocument_IsProducedBeforeTagHelperResolution() Assert.DoesNotContain("Make", decl.Text.ToString()); } + [Fact] + public void MarkupFreeImplements_SplitsIntoDeclAndImpl() + { + // A markup-free @implements component splits: the interface list stays on both partials (partials + // may repeat it), and the render-only impl keeps it so the ImplementInterface code action is still + // offered against the impl partial. The decl half is the full class surface, not a fallback shell. + var generated = CompileToCSharp(""" + @implements System.IDisposable + @code { + public void Dispose() { } + } + """); + + Assert.NotNull(generated.DeclCode); + Assert.Contains("IDisposable", generated.DeclCode); + Assert.Contains("Dispose", generated.DeclCode); + + CompileToAssembly(generated); + } + + [Fact] + public void MarkupFreeInherits_SplitsIntoDeclAndImpl() + { + // A markup-free @inherits component splits: the base type stays on both partials, so the impl + // stays a component the compiler flags for unimplemented base members (keeping ImplementAbstractClass + // offered against it). The decl half carries the full class surface, not a fallback shell. + var generated = CompileToCSharp(""" + @inherits Microsoft.AspNetCore.Components.LayoutComponentBase +

Hello

+ """); + + Assert.NotNull(generated.DeclCode); + Assert.Contains("LayoutComponentBase", generated.DeclCode); + + CompileToAssembly(generated); + } + + [Fact] + public void MarkupFreeTypeParam_SplitsIntoDeclAndImpl() + { + // A generic component with no class-body markup splits; the type parameter lives on the decl half. + // A markup-free @typeparam previously routed to the single-document fallback. + var generated = CompileToCSharp(""" + @typeparam T + @code { + [Microsoft.AspNetCore.Components.Parameter] public T Value { get; set; } + } + """); + + Assert.NotNull(generated.DeclCode); + Assert.Contains("Value", generated.DeclCode); + + CompileToAssembly(generated); + } + + [Fact] + public void MarkupMethod_WithImplements_SplitsAndCompiles() + { + // A header directive combined with class-body markup splits: the markup-free members (here the + // explicit Dispose) stay in the decl half, the markup method lifts to the impl half, and both + // partials repeat the interface list. Verify each half routes correctly and it compiles. + var generated = CompileToCSharp(""" + @implements System.IDisposable + @code { + public void Dispose() { } + private Microsoft.AspNetCore.Components.RenderFragment Make() => @

Hi

; + } + """); + + Assert.NotNull(generated.DeclCode); + Assert.Contains("IDisposable", generated.DeclCode); + Assert.Contains("Dispose", generated.DeclCode); + Assert.DoesNotContain("Make", generated.DeclCode); + Assert.Contains("Make", generated.Code); + + CompileToAssembly(generated); + } + private static string Slice(string text, SourceSpan span) => text.Substring(span.AbsoluteIndex, span.Length); } diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/MarkupSplitterTest.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/MarkupSplitterTest.cs index 716008d95f586..94c0bf7abe346 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/MarkupSplitterTest.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/MarkupSplitterTest.cs @@ -475,13 +475,14 @@ public void IsMarkupNode_ExcludesCSharpAndSurfaceNodes() } [Fact] - public void Split_ClassBodyWithInject_FallsBack() + public void Split_ClassBodyWithInject_RoutesInjectToDecl() { - // A component whose @code mixes markup with an @inject cannot be split: the inject is surface the - // splitter cannot route, so classification falls back rather than moving it to impl. + // A component whose @code mixes markup with an @inject splits: the markup method lifts to impl, + // and the @inject (a markup-free surface declaration) routes to the decl half. var renderMethod = CreateRenderMethod(); + var inject = new Components.ComponentInjectIntermediateNode("Foo", "Bar", typeSpan: null, memberSpan: null); var primaryClass = CreatePrimaryClass( - new Components.ComponentInjectIntermediateNode("Foo", "Bar", typeSpan: null, memberSpan: null), + inject, CreateCSharpCode("void M(RenderTreeBuilder __builder) { "), new MarkupElementIntermediateNode { TagName = "ul" }, CreateCSharpCode(" }"), @@ -489,8 +490,16 @@ public void Split_ClassBodyWithInject_FallsBack() var decision = MarkupSplitter.Split(primaryClass, renderMethod, ParserOptions(LanguageVersion.CSharp13)); - var fallback = Assert.IsType(decision); - Assert.Equal(FallbackReason.UnsupportedClassBodyNode, fallback.Reason); + var plan = Assert.IsType(decision); + Assert.Equal(2, plan.Members.Length); + + // The markup method lifts wholly to impl. + Assert.Empty(plan.Members[0].DeclPieces); + Assert.NotEmpty(plan.Members[0].ImplPieces); + + // The @inject routes to the decl half, carried by reference. + Assert.Empty(plan.Members[1].ImplPieces); + Assert.Same(inject, Assert.Single(plan.Members[1].DeclPieces)); } [Fact] diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.codegen.cs index 81b879b8139e3..f6d5cb2ff83f7 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.codegen.cs @@ -98,18 +98,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. __builder.AddMarkupContent(4, "

Item3

"); } #pragma warning restore 1998 -#nullable restore -#line (16,8)-(21,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] public TItem1 Item1 { get; set; } - [Parameter] public List Items2 { get; set; } - [Parameter] public TItem3 Item3 { get; set; } - [Parameter] public RenderFragment ChildContent { get; set; } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.decl.codegen.cs index e45d95be4f654..42b501380cbaf 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.decl.codegen.cs @@ -2,11 +2,79 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; +#nullable restore +#line (1,2)-(1,40) "x:\dir\subdir\Test\TestComponent.cshtml" +using Microsoft.AspNetCore.Components; + +#nullable disable + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (2,12)-(2,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem1 + +#line default +#line hidden +#nullable disable + , +#nullable restore +#line (3,12)-(3,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem2 + +#line default +#line hidden +#nullable disable + , +#nullable restore +#line (4,12)-(4,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem3 + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase +#nullable restore +#line (2,19)-(2,39) "x:\dir\subdir\Test\TestComponent.cshtml" +where TItem1 : Image + +#line default +#line hidden +#nullable disable +#nullable restore +#line (3,19)-(3,38) "x:\dir\subdir\Test\TestComponent.cshtml" +where TItem2 : ITag + +#line default +#line hidden +#nullable disable +#nullable restore +#line (4,19)-(4,46) "x:\dir\subdir\Test\TestComponent.cshtml" +where TItem3 : Image, new() + +#line default +#line hidden +#nullable disable #nullable disable { +#nullable restore +#line (16,8)-(21,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] public TItem1 Item1 { get; set; } + [Parameter] public List Items2 { get; set; } + [Parameter] public TItem3 Item3 { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.decl.ir.txt index 2298bd8171093..66930a99cc788 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TItem1, TItem2, TItem3 + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (1:0,1 [38] x:\dir\subdir\Test\TestComponent.cshtml) - Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TItem1, TItem2, TItem3 + CSharpCode - (294:15,7 [236] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (294:15,7 [236] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public TItem1 Item1 { get; set; }\n [Parameter] public List Items2 { get; set; }\n [Parameter] public TItem3 Item3 { get; set; }\n [Parameter] public RenderFragment ChildContent { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..0c8cea2f8e4bc --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.decl.mappings.txt @@ -0,0 +1,50 @@ +Source Location: (1:0,1 [38] x:\dir\subdir\Test\TestComponent.cshtml) +|using Microsoft.AspNetCore.Components;| +Generated Location: (320:11,0 [38] ) +|using Microsoft.AspNetCore.Components;| + +Source Location: (52:1,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem1| +Generated Location: (564:20,0 [6] ) +|TItem1| + +Source Location: (92:2,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem2| +Generated Location: (711:28,0 [6] ) +|TItem2| + +Source Location: (131:3,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem3| +Generated Location: (858:36,0 [6] ) +|TItem3| + +Source Location: (59:1,18 [20] x:\dir\subdir\Test\TestComponent.cshtml) +|where TItem1 : Image| +Generated Location: (1061:44,0 [20] ) +|where TItem1 : Image| + +Source Location: (99:2,18 [19] x:\dir\subdir\Test\TestComponent.cshtml) +|where TItem2 : ITag| +Generated Location: (1215:51,0 [19] ) +|where TItem2 : ITag| + +Source Location: (138:3,18 [27] x:\dir\subdir\Test\TestComponent.cshtml) +|where TItem3 : Image, new()| +Generated Location: (1368:58,0 [27] ) +|where TItem3 : Image, new()| + +Source Location: (294:15,7 [236] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] public TItem1 Item1 { get; set; } + [Parameter] public List Items2 { get; set; } + [Parameter] public TItem3 Item3 { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } +| +Generated Location: (1559:67,0 [236] ) +| + [Parameter] public TItem1 Item1 { get; set; } + [Parameter] public List Items2 { get; set; } + [Parameter] public TItem3 Item3 { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.ir.txt index 5d63deeebed69..c1fa727220407 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.ir.txt @@ -18,5 +18,3 @@ CSharpCode - (266:11,0 [3] x:\dir\subdir\Test\TestComponent.cshtml) LazyIntermediateToken - (266:11,0 [3] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - }\n MarkupBlock - -

Item3

- CSharpCode - (294:15,7 [236] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (294:15,7 [236] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public TItem1 Item1 { get; set; }\n [Parameter] public List Items2 { get; set; }\n [Parameter] public TItem3 Item3 { get; set; }\n [Parameter] public RenderFragment ChildContent { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.mappings.txt index e2628d9d7957c..621ae341bc1ad 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters/TestComponent.mappings.txt @@ -54,18 +54,3 @@ Generated Location: (2301:91,0 [3] ) |} | -Source Location: (294:15,7 [236] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] public TItem1 Item1 { get; set; } - [Parameter] public List Items2 { get; set; } - [Parameter] public TItem3 Item3 { get; set; } - [Parameter] public RenderFragment ChildContent { get; set; } -| -Generated Location: (2547:102,0 [236] ) -| - [Parameter] public TItem1 Item1 { get; set; } - [Parameter] public List Items2 { get; set; } - [Parameter] public TItem3 Item3 { get; set; } - [Parameter] public RenderFragment ChildContent { get; set; } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.codegen.cs index 81b879b8139e3..f6d5cb2ff83f7 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.codegen.cs @@ -98,18 +98,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. __builder.AddMarkupContent(4, "

Item3

"); } #pragma warning restore 1998 -#nullable restore -#line (16,8)-(21,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] public TItem1 Item1 { get; set; } - [Parameter] public List Items2 { get; set; } - [Parameter] public TItem3 Item3 { get; set; } - [Parameter] public RenderFragment ChildContent { get; set; } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.decl.codegen.cs index e45d95be4f654..42b501380cbaf 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.decl.codegen.cs @@ -2,11 +2,79 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; +#nullable restore +#line (1,2)-(1,40) "x:\dir\subdir\Test\TestComponent.cshtml" +using Microsoft.AspNetCore.Components; + +#nullable disable + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (2,12)-(2,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem1 + +#line default +#line hidden +#nullable disable + , +#nullable restore +#line (3,12)-(3,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem2 + +#line default +#line hidden +#nullable disable + , +#nullable restore +#line (4,12)-(4,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem3 + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase +#nullable restore +#line (2,19)-(2,39) "x:\dir\subdir\Test\TestComponent.cshtml" +where TItem1 : Image + +#line default +#line hidden +#nullable disable +#nullable restore +#line (3,19)-(3,38) "x:\dir\subdir\Test\TestComponent.cshtml" +where TItem2 : ITag + +#line default +#line hidden +#nullable disable +#nullable restore +#line (4,19)-(4,46) "x:\dir\subdir\Test\TestComponent.cshtml" +where TItem3 : Image, new() + +#line default +#line hidden +#nullable disable #nullable disable { +#nullable restore +#line (16,8)-(21,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] public TItem1 Item1 { get; set; } + [Parameter] public List Items2 { get; set; } + [Parameter] public TItem3 Item3 { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.decl.ir.txt index 2298bd8171093..be83b75e4e5b2 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TItem1, TItem2, TItem3 + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (1:0,1 [38] x:\dir\subdir\Test\TestComponent.cshtml) - Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TItem1, TItem2, TItem3 + CSharpCode - (297:15,7 [236] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (297:15,7 [236] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public TItem1 Item1 { get; set; }\n [Parameter] public List Items2 { get; set; }\n [Parameter] public TItem3 Item3 { get; set; }\n [Parameter] public RenderFragment ChildContent { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..7e9af9beb73d0 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.decl.mappings.txt @@ -0,0 +1,50 @@ +Source Location: (1:0,1 [38] x:\dir\subdir\Test\TestComponent.cshtml) +|using Microsoft.AspNetCore.Components;| +Generated Location: (320:11,0 [38] ) +|using Microsoft.AspNetCore.Components;| + +Source Location: (52:1,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem1| +Generated Location: (564:20,0 [6] ) +|TItem1| + +Source Location: (93:2,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem2| +Generated Location: (711:28,0 [6] ) +|TItem2| + +Source Location: (133:3,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem3| +Generated Location: (858:36,0 [6] ) +|TItem3| + +Source Location: (59:1,18 [20] x:\dir\subdir\Test\TestComponent.cshtml) +|where TItem1 : Image| +Generated Location: (1061:44,0 [20] ) +|where TItem1 : Image| + +Source Location: (100:2,18 [19] x:\dir\subdir\Test\TestComponent.cshtml) +|where TItem2 : ITag| +Generated Location: (1215:51,0 [19] ) +|where TItem2 : ITag| + +Source Location: (140:3,18 [27] x:\dir\subdir\Test\TestComponent.cshtml) +|where TItem3 : Image, new()| +Generated Location: (1368:58,0 [27] ) +|where TItem3 : Image, new()| + +Source Location: (297:15,7 [236] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] public TItem1 Item1 { get; set; } + [Parameter] public List Items2 { get; set; } + [Parameter] public TItem3 Item3 { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } +| +Generated Location: (1559:67,0 [236] ) +| + [Parameter] public TItem1 Item1 { get; set; } + [Parameter] public List Items2 { get; set; } + [Parameter] public TItem3 Item3 { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.ir.txt index 2b5e638100650..82587b21eb2b9 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.ir.txt @@ -18,5 +18,3 @@ CSharpCode - (269:11,0 [3] x:\dir\subdir\Test\TestComponent.cshtml) LazyIntermediateToken - (269:11,0 [3] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - }\n MarkupBlock - -

Item3

- CSharpCode - (297:15,7 [236] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (297:15,7 [236] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public TItem1 Item1 { get; set; }\n [Parameter] public List Items2 { get; set; }\n [Parameter] public TItem3 Item3 { get; set; }\n [Parameter] public RenderFragment ChildContent { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.mappings.txt index a3e25a4d8e866..409fcb5864415 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithConstrainedTypeParameters_WithSemicolon/TestComponent.mappings.txt @@ -54,18 +54,3 @@ Generated Location: (2301:91,0 [3] ) |} | -Source Location: (297:15,7 [236] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] public TItem1 Item1 { get; set; } - [Parameter] public List Items2 { get; set; } - [Parameter] public TItem3 Item3 { get; set; } - [Parameter] public RenderFragment ChildContent { get; set; } -| -Generated Location: (2547:102,0 [236] ) -| - [Parameter] public TItem1 Item1 { get; set; } - [Parameter] public List Items2 { get; set; } - [Parameter] public TItem3 Item3 { get; set; } - [Parameter] public RenderFragment ChildContent { get; set; } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.codegen.cs index 280557313f16f..6c91919457d5e 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.codegen.cs @@ -79,18 +79,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. __builder.CloseElement(); } #pragma warning restore 1998 -#nullable restore -#line (15,8)-(20,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] public TItem[] Items1 { get; set; } - [Parameter] public List Items2 { get; set; } - [Parameter] public Func Items3 { get; set; } - [Parameter] public RenderFragment ChildContent { get; set; } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.decl.codegen.cs index a95703435fd3d..ae735d8641e8e 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.decl.codegen.cs @@ -2,11 +2,42 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; +#nullable restore +#line (1,2)-(1,40) "x:\dir\subdir\Test\TestComponent.cshtml" +using Microsoft.AspNetCore.Components; + +#nullable disable + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (2,12)-(2,17) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (15,8)-(20,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] public TItem[] Items1 { get; set; } + [Parameter] public List Items2 { get; set; } + [Parameter] public Func Items3 { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.decl.ir.txt index cbfa0567b3baf..d130ca0db13ff 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TItem + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (1:0,1 [38] x:\dir\subdir\Test\TestComponent.cshtml) - Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TItem + CSharpCode - (222:14,7 [248] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (222:14,7 [248] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public TItem[] Items1 { get; set; }\n [Parameter] public List Items2 { get; set; }\n [Parameter] public Func Items3 { get; set; }\n [Parameter] public RenderFragment ChildContent { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..fef97937482fe --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.decl.mappings.txt @@ -0,0 +1,25 @@ +Source Location: (1:0,1 [38] x:\dir\subdir\Test\TestComponent.cshtml) +|using Microsoft.AspNetCore.Components;| +Generated Location: (320:11,0 [38] ) +|using Microsoft.AspNetCore.Components;| + +Source Location: (52:1,11 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem| +Generated Location: (564:20,0 [5] ) +|TItem| + +Source Location: (222:14,7 [248] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] public TItem[] Items1 { get; set; } + [Parameter] public List Items2 { get; set; } + [Parameter] public Func Items3 { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } +| +Generated Location: (796:30,0 [248] ) +| + [Parameter] public TItem[] Items1 { get; set; } + [Parameter] public List Items2 { get; set; } + [Parameter] public Func Items3 { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.ir.txt index 0ed3fca122433..1debb8d597332 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.ir.txt @@ -21,5 +21,3 @@ MarkupElement - (181:12,0 [30] x:\dir\subdir\Test\TestComponent.cshtml) - p CSharpExpression - (185:12,4 [22] x:\dir\subdir\Test\TestComponent.cshtml) LazyIntermediateToken - (185:12,4 [22] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ChildContent(Items3()) - CSharpCode - (222:14,7 [248] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (222:14,7 [248] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public TItem[] Items1 { get; set; }\n [Parameter] public List Items2 { get; set; }\n [Parameter] public Func Items3 { get; set; }\n [Parameter] public RenderFragment ChildContent { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.mappings.txt index b942dae1a1b3b..d31fa1cc408ee 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterArray/TestComponent.mappings.txt @@ -39,18 +39,3 @@ Source Location: (185:12,4 [22] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (1975:72,24 [22] ) |ChildContent(Items3())| -Source Location: (222:14,7 [248] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] public TItem[] Items1 { get; set; } - [Parameter] public List Items2 { get; set; } - [Parameter] public Func Items3 { get; set; } - [Parameter] public RenderFragment ChildContent { get; set; } -| -Generated Location: (2235:83,0 [248] ) -| - [Parameter] public TItem[] Items1 { get; set; } - [Parameter] public List Items2 { get; set; } - [Parameter] public Func Items3 { get; set; } - [Parameter] public RenderFragment ChildContent { get; set; } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.codegen.cs index 3a6c7e2a30a4a..8382ac6b76f13 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.codegen.cs @@ -77,17 +77,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. } #pragma warning restore 1998 -#nullable restore -#line (14,8)-(18,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] public (TItem1, TItem2) Item1 { get; set; } - [Parameter] public List<(TItem1, TItem2)> Items2 { get; set; } - [Parameter] public RenderFragment<(TItem1, TItem2)> ChildContent { get; set; } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.decl.codegen.cs index ec5c35cb1ae0c..f68eb7e1e7ae8 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.decl.codegen.cs @@ -2,11 +2,49 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; +#nullable restore +#line (1,2)-(1,40) "x:\dir\subdir\Test\TestComponent.cshtml" +using Microsoft.AspNetCore.Components; + +#nullable disable + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (2,12)-(2,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem1 + +#line default +#line hidden +#nullable disable + , +#nullable restore +#line (3,12)-(3,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem2 + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (14,8)-(18,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] public (TItem1, TItem2) Item1 { get; set; } + [Parameter] public List<(TItem1, TItem2)> Items2 { get; set; } + [Parameter] public RenderFragment<(TItem1, TItem2)> ChildContent { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.decl.ir.txt index 52d4ecf6b5ad1..20806ecb4fd62 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TItem1, TItem2 + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (1:0,1 [38] x:\dir\subdir\Test\TestComponent.cshtml) - Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TItem1, TItem2 + CSharpCode - (207:13,7 [215] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (207:13,7 [215] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public (TItem1, TItem2) Item1 { get; set; }\n [Parameter] public List<(TItem1, TItem2)> Items2 { get; set; }\n [Parameter] public RenderFragment<(TItem1, TItem2)> ChildContent { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..17aa821a3cf39 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.decl.mappings.txt @@ -0,0 +1,28 @@ +Source Location: (1:0,1 [38] x:\dir\subdir\Test\TestComponent.cshtml) +|using Microsoft.AspNetCore.Components;| +Generated Location: (320:11,0 [38] ) +|using Microsoft.AspNetCore.Components;| + +Source Location: (52:1,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem1| +Generated Location: (564:20,0 [6] ) +|TItem1| + +Source Location: (71:2,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem2| +Generated Location: (711:28,0 [6] ) +|TItem2| + +Source Location: (207:13,7 [215] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] public (TItem1, TItem2) Item1 { get; set; } + [Parameter] public List<(TItem1, TItem2)> Items2 { get; set; } + [Parameter] public RenderFragment<(TItem1, TItem2)> ChildContent { get; set; } +| +Generated Location: (944:38,0 [215] ) +| + [Parameter] public (TItem1, TItem2) Item1 { get; set; } + [Parameter] public List<(TItem1, TItem2)> Items2 { get; set; } + [Parameter] public RenderFragment<(TItem1, TItem2)> ChildContent { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.ir.txt index a9e065ef61680..59ed06a32dd66 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.ir.txt @@ -18,5 +18,3 @@ LazyIntermediateToken - (171:10,8 [18] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ChildContent(item) CSharpCode - (195:11,0 [3] x:\dir\subdir\Test\TestComponent.cshtml) LazyIntermediateToken - (195:11,0 [3] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - }\n - CSharpCode - (207:13,7 [215] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (207:13,7 [215] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public (TItem1, TItem2) Item1 { get; set; }\n [Parameter] public List<(TItem1, TItem2)> Items2 { get; set; }\n [Parameter] public RenderFragment<(TItem1, TItem2)> ChildContent { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.mappings.txt index c4ff5ae508ade..a78cc54bf7880 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple/TestComponent.mappings.txt @@ -39,16 +39,3 @@ Generated Location: (1913:71,0 [3] ) |} | -Source Location: (207:13,7 [215] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] public (TItem1, TItem2) Item1 { get; set; } - [Parameter] public List<(TItem1, TItem2)> Items2 { get; set; } - [Parameter] public RenderFragment<(TItem1, TItem2)> ChildContent { get; set; } -| -Generated Location: (2099:81,0 [215] ) -| - [Parameter] public (TItem1, TItem2) Item1 { get; set; } - [Parameter] public List<(TItem1, TItem2)> Items2 { get; set; } - [Parameter] public RenderFragment<(TItem1, TItem2)> ChildContent { get; set; } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.codegen.cs index 954230879bc3d..a17ec2d6c0236 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.codegen.cs @@ -61,19 +61,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. ); } #pragma warning restore 1998 -#nullable restore -#line (4,8)-(10,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] - public TParam InferParam { get; set; } - - [Parameter] - public RenderFragment<(MyClass I1, MyStruct I2, TParam P)> Template { get; set; } - -#line default -#line hidden -#nullable disable - } } namespace __Blazor.Test.TestComponent diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.decl.codegen.cs index 0278bf0607f5f..aca34bc4ff624 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.decl.codegen.cs @@ -2,11 +2,45 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; +#nullable restore +#line (1,2)-(1,9) "x:\dir\subdir\Test\TestComponent.cshtml" +using N + +#nullable disable + ; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (2,12)-(2,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TParam + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (4,8)-(10,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] + public TParam InferParam { get; set; } + + [Parameter] + public RenderFragment<(MyClass I1, MyStruct I2, TParam P)> Template { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.decl.ir.txt index 8a361060e5ec7..2c5c1baf8e092 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.decl.ir.txt @@ -1,3 +1,11 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TParam + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + UsingDirective - (1:0,1 [7] x:\dir\subdir\Test\TestComponent.cshtml) - N + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TParam + CSharpCode - (38:3,7 [169] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (38:3,7 [169] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public TParam InferParam { get; set; }\n\n [Parameter]\n public RenderFragment<(MyClass I1, MyStruct I2, TParam P)> Template { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..df2765dcf0f3e --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.decl.mappings.txt @@ -0,0 +1,27 @@ +Source Location: (1:0,1 [7] x:\dir\subdir\Test\TestComponent.cshtml) +|using N| +Generated Location: (371:12,0 [7] ) +|using N| + +Source Location: (21:1,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TParam| +Generated Location: (591:22,0 [6] ) +|TParam| + +Source Location: (38:3,7 [169] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] + public TParam InferParam { get; set; } + + [Parameter] + public RenderFragment<(MyClass I1, MyStruct I2, TParam P)> Template { get; set; } +| +Generated Location: (823:32,0 [169] ) +| + [Parameter] + public TParam InferParam { get; set; } + + [Parameter] + public RenderFragment<(MyClass I1, MyStruct I2, TParam P)> Template { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.ir.txt index 834147bc6aab9..92105f10cf7a5 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.ir.txt @@ -18,8 +18,6 @@ LazyIntermediateToken - (293:13,33 [21] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - context.I2.MyStructId ComponentAttribute - (239:11,27 [1] x:\dir\subdir\Test\TestComponent.cshtml) - InferParam - InferParam - AttributeStructure.DoubleQuotes IntermediateToken - (239:11,27 [1] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - 1 - CSharpCode - (38:3,7 [169] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (38:3,7 [169] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public TParam InferParam { get; set; }\n\n [Parameter]\n public RenderFragment<(MyClass I1, MyStruct I2, TParam P)> Template { get; set; }\n NamespaceDeclaration - - __Blazor.Test.TestComponent ClassDeclaration - - internal static - TypeInference - - ComponentTypeInferenceMethod - - __Blazor.Test.TestComponent.TypeInference - CreateTestComponent_0 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.mappings.txt index bc826356e1b15..5e4b7952efddd 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTupleGloballyQualifiedTypes/TestComponent.mappings.txt @@ -23,30 +23,13 @@ Source Location: (293:13,33 [21] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (1574:53,25 [21] ) |context.I2.MyStructId| -Source Location: (38:3,7 [169] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] - public TParam InferParam { get; set; } - - [Parameter] - public RenderFragment<(MyClass I1, MyStruct I2, TParam P)> Template { get; set; } -| -Generated Location: (1828:65,0 [169] ) -| - [Parameter] - public TParam InferParam { get; set; } - - [Parameter] - public RenderFragment<(MyClass I1, MyStruct I2, TParam P)> Template { get; set; } -| - Source Location: (213:11,1 [13] x:\dir\subdir\Test\TestComponent.cshtml) |TestComponent| -Generated Location: (2528:85,45 [13] ) +Generated Location: (2226:72,45 [13] ) |TestComponent| Source Location: (227:11,15 [10] x:\dir\subdir\Test\TestComponent.cshtml) |InferParam| -Generated Location: (2734:89,0 [10] ) +Generated Location: (2432:76,0 [10] ) |InferParam| diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.codegen.cs index f6ee1de682490..5009c7684437e 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.codegen.cs @@ -84,16 +84,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. __builder.CloseComponent(); } #pragma warning restore 1998 -#nullable restore -#line (6,8)-(9,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] - public List<(TDomain Domain, TValue Value)> Data { get; set; } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.decl.codegen.cs index 85133a6391f02..efe13136bdac5 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.decl.codegen.cs @@ -2,11 +2,58 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,19) "x:\dir\subdir\Test\TestComponent.cshtml" +TDomain + +#line default +#line hidden +#nullable disable + , +#nullable restore +#line (2,12)-(2,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TValue + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase +#nullable restore +#line (1,20)-(1,42) "x:\dir\subdir\Test\TestComponent.cshtml" +where TDomain : struct + +#line default +#line hidden +#nullable disable +#nullable restore +#line (2,19)-(2,40) "x:\dir\subdir\Test\TestComponent.cshtml" +where TValue : struct + +#line default +#line hidden +#nullable disable #nullable disable { +#nullable restore +#line (6,8)-(9,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] + public List<(TDomain Domain, TValue Value)> Data { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.decl.ir.txt index 75e736bf9fd7b..45f3d875fde7f 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TDomain, TValue + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TDomain, TValue + CSharpCode - (161:5,7 [87] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (161:5,7 [87] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public List<(TDomain Domain, TValue Value)> Data { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..11751a93038ea --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.decl.mappings.txt @@ -0,0 +1,31 @@ +Source Location: (11:0,11 [7] x:\dir\subdir\Test\TestComponent.cshtml) +|TDomain| +Generated Location: (474:16,0 [7] ) +|TDomain| + +Source Location: (54:1,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TValue| +Generated Location: (622:24,0 [6] ) +|TValue| + +Source Location: (19:0,19 [22] x:\dir\subdir\Test\TestComponent.cshtml) +|where TDomain : struct| +Generated Location: (825:32,0 [22] ) +|where TDomain : struct| + +Source Location: (61:1,18 [21] x:\dir\subdir\Test\TestComponent.cshtml) +|where TValue : struct| +Generated Location: (981:39,0 [21] ) +|where TValue : struct| + +Source Location: (161:5,7 [87] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] + public List<(TDomain Domain, TValue Value)> Data { get; set; } +| +Generated Location: (1164:48,0 [87] ) +| + [Parameter] + public List<(TDomain Domain, TValue Value)> Data { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.ir.txt index 3312f4739d971..4c0019209ab53 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.ir.txt @@ -14,5 +14,3 @@ IntermediateToken - (122:3,36 [7] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - decimal ComponentTypeArgument - (139:3,53 [7] x:\dir\subdir\Test\TestComponent.cshtml) - TValue IntermediateToken - (139:3,53 [7] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - decimal - CSharpCode - (161:5,7 [87] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (161:5,7 [87] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public List<(TDomain Domain, TValue Value)> Data { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.mappings.txt index ac7088fde4af6..f5fa337a88019 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameterValueTuple_ExplicitGenericArguments/TestComponent.mappings.txt @@ -43,14 +43,3 @@ Source Location: (107:3,21 [4] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (2138:77,0 [4] ) |null| -Source Location: (161:5,7 [87] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] - public List<(TDomain Domain, TValue Value)> Data { get; set; } -| -Generated Location: (2381:88,0 [87] ) -| - [Parameter] - public List<(TDomain Domain, TValue Value)> Data { get; set; } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.codegen.cs index 5085e463188b3..74282ab016df3 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.codegen.cs @@ -68,17 +68,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. } #pragma warning restore 1998 -#nullable restore -#line (12,8)-(16,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] public TItem1 Item1 { get; set; } - [Parameter] public List Items2 { get; set; } - [Parameter] public RenderFragment ChildContent { get; set; } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.decl.codegen.cs index ec5c35cb1ae0c..dd8f8b6063f68 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.decl.codegen.cs @@ -2,11 +2,49 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; +#nullable restore +#line (1,2)-(1,40) "x:\dir\subdir\Test\TestComponent.cshtml" +using Microsoft.AspNetCore.Components; + +#nullable disable + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (2,12)-(2,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem1 + +#line default +#line hidden +#nullable disable + , +#nullable restore +#line (3,12)-(3,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem2 + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (12,8)-(16,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] public TItem1 Item1 { get; set; } + [Parameter] public List Items2 { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.decl.ir.txt index 52d4ecf6b5ad1..61a2526130450 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TItem1, TItem2 + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (1:0,1 [38] x:\dir\subdir\Test\TestComponent.cshtml) - Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TItem1, TItem2 + CSharpCode - (188:11,7 [185] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (188:11,7 [185] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public TItem1 Item1 { get; set; }\n [Parameter] public List Items2 { get; set; }\n [Parameter] public RenderFragment ChildContent { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..fdcd833406ca5 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.decl.mappings.txt @@ -0,0 +1,28 @@ +Source Location: (1:0,1 [38] x:\dir\subdir\Test\TestComponent.cshtml) +|using Microsoft.AspNetCore.Components;| +Generated Location: (320:11,0 [38] ) +|using Microsoft.AspNetCore.Components;| + +Source Location: (52:1,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem1| +Generated Location: (564:20,0 [6] ) +|TItem1| + +Source Location: (71:2,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem2| +Generated Location: (711:28,0 [6] ) +|TItem2| + +Source Location: (188:11,7 [185] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] public TItem1 Item1 { get; set; } + [Parameter] public List Items2 { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } +| +Generated Location: (944:38,0 [185] ) +| + [Parameter] public TItem1 Item1 { get; set; } + [Parameter] public List Items2 { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.ir.txt index 2e3415c7f20cb..7ff9caabb1a77 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.ir.txt @@ -17,5 +17,3 @@ LazyIntermediateToken - (165:8,24 [7] x:\dir\subdir\Test\TestComponent.cshtml) - Html - ;\n CSharpCode - (178:10,0 [3] x:\dir\subdir\Test\TestComponent.cshtml) LazyIntermediateToken - (178:10,0 [3] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - }\n - CSharpCode - (188:11,7 [185] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (188:11,7 [185] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public TItem1 Item1 { get; set; }\n [Parameter] public List Items2 { get; set; }\n [Parameter] public RenderFragment ChildContent { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.mappings.txt index 3e39fb327c7b9..5222b56020652 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters/TestComponent.mappings.txt @@ -34,16 +34,3 @@ Generated Location: (1684:62,0 [3] ) |} | -Source Location: (188:11,7 [185] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] public TItem1 Item1 { get; set; } - [Parameter] public List Items2 { get; set; } - [Parameter] public RenderFragment ChildContent { get; set; } -| -Generated Location: (1870:72,0 [185] ) -| - [Parameter] public TItem1 Item1 { get; set; } - [Parameter] public List Items2 { get; set; } - [Parameter] public RenderFragment ChildContent { get; set; } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_Interconnected/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_Interconnected/TestComponent.decl.codegen.cs index ba7f82401a49a..0725814a3d42b 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_Interconnected/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_Interconnected/TestComponent.decl.codegen.cs @@ -2,9 +2,46 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,14) "x:\dir\subdir\Test\TestComponent.cshtml" +T1 + +#line default +#line hidden +#nullable disable + , +#nullable restore +#line (2,12)-(2,14) "x:\dir\subdir\Test\TestComponent.cshtml" +T2 + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase +#nullable restore +#line (1,15)-(1,31) "x:\dir\subdir\Test\TestComponent.cshtml" +where T1 : C + +#line default +#line hidden +#nullable disable +#nullable restore +#line (2,15)-(2,35) "x:\dir\subdir\Test\TestComponent.cshtml" +where T2 : D + +#line default +#line hidden +#nullable disable #nullable disable { } diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_Interconnected/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_Interconnected/TestComponent.decl.ir.txt index 6b1dc2a4d2c8a..0f82e4e92a0e3 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_Interconnected/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_Interconnected/TestComponent.decl.ir.txt @@ -1,3 +1,8 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - T1, T2 + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - T1, T2 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_Interconnected/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_Interconnected/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..055499a911b2c --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_Interconnected/TestComponent.decl.mappings.txt @@ -0,0 +1,20 @@ +Source Location: (11:0,11 [2] x:\dir\subdir\Test\TestComponent.cshtml) +|T1| +Generated Location: (474:16,0 [2] ) +|T1| + +Source Location: (43:1,11 [2] x:\dir\subdir\Test\TestComponent.cshtml) +|T2| +Generated Location: (617:24,0 [2] ) +|T2| + +Source Location: (14:0,14 [16] x:\dir\subdir\Test\TestComponent.cshtml) +|where T1 : C| +Generated Location: (816:32,0 [16] ) +|where T1 : C| + +Source Location: (46:1,14 [20] x:\dir\subdir\Test\TestComponent.cshtml) +|where T2 : D| +Generated Location: (966:39,0 [20] ) +|where T2 : D| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.codegen.cs index 5085e463188b3..74282ab016df3 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.codegen.cs @@ -68,17 +68,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. } #pragma warning restore 1998 -#nullable restore -#line (12,8)-(16,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] public TItem1 Item1 { get; set; } - [Parameter] public List Items2 { get; set; } - [Parameter] public RenderFragment ChildContent { get; set; } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.decl.codegen.cs index ec5c35cb1ae0c..dd8f8b6063f68 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.decl.codegen.cs @@ -2,11 +2,49 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; +#nullable restore +#line (1,2)-(1,40) "x:\dir\subdir\Test\TestComponent.cshtml" +using Microsoft.AspNetCore.Components; + +#nullable disable + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (2,12)-(2,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem1 + +#line default +#line hidden +#nullable disable + , +#nullable restore +#line (3,12)-(3,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem2 + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (12,8)-(16,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] public TItem1 Item1 { get; set; } + [Parameter] public List Items2 { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.decl.ir.txt index 52d4ecf6b5ad1..fc0aef7d7f61f 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TItem1, TItem2 + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (1:0,1 [38] x:\dir\subdir\Test\TestComponent.cshtml) - Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TItem1, TItem2 + CSharpCode - (190:11,7 [185] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (190:11,7 [185] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public TItem1 Item1 { get; set; }\n [Parameter] public List Items2 { get; set; }\n [Parameter] public RenderFragment ChildContent { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..ec92bec89d4bf --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.decl.mappings.txt @@ -0,0 +1,28 @@ +Source Location: (1:0,1 [38] x:\dir\subdir\Test\TestComponent.cshtml) +|using Microsoft.AspNetCore.Components;| +Generated Location: (320:11,0 [38] ) +|using Microsoft.AspNetCore.Components;| + +Source Location: (52:1,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem1| +Generated Location: (564:20,0 [6] ) +|TItem1| + +Source Location: (72:2,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem2| +Generated Location: (711:28,0 [6] ) +|TItem2| + +Source Location: (190:11,7 [185] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] public TItem1 Item1 { get; set; } + [Parameter] public List Items2 { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } +| +Generated Location: (944:38,0 [185] ) +| + [Parameter] public TItem1 Item1 { get; set; } + [Parameter] public List Items2 { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.ir.txt index e7cae26d9a027..aea038b5f9f94 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.ir.txt @@ -17,5 +17,3 @@ LazyIntermediateToken - (167:8,24 [7] x:\dir\subdir\Test\TestComponent.cshtml) - Html - ;\n CSharpCode - (180:10,0 [3] x:\dir\subdir\Test\TestComponent.cshtml) LazyIntermediateToken - (180:10,0 [3] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - }\n - CSharpCode - (190:11,7 [185] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (190:11,7 [185] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public TItem1 Item1 { get; set; }\n [Parameter] public List Items2 { get; set; }\n [Parameter] public RenderFragment ChildContent { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.mappings.txt index b3724ec047e3d..ba03f9fa90e25 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/ComponentWithTypeParameters_WithSemicolon/TestComponent.mappings.txt @@ -34,16 +34,3 @@ Generated Location: (1684:62,0 [3] ) |} | -Source Location: (190:11,7 [185] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] public TItem1 Item1 { get; set; } - [Parameter] public List Items2 { get; set; } - [Parameter] public RenderFragment ChildContent { get; set; } -| -Generated Location: (1870:72,0 [185] ) -| - [Parameter] public TItem1 Item1 { get; set; } - [Parameter] public List Items2 { get; set; } - [Parameter] public RenderFragment ChildContent { get; set; } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.codegen.cs index bdaec4406f05d..2031bd021ee31 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.codegen.cs @@ -76,15 +76,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. ); } #pragma warning restore 1998 -#nullable restore -#line (5,8)-(7,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] public T Parameter { get; set; } - -#line default -#line hidden -#nullable disable - } } namespace __Blazor.Test.TestComponent diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.decl.codegen.cs index c4e3c7388638a..aab445ad51c49 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.decl.codegen.cs @@ -2,11 +2,41 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; +#nullable restore +#line (1,2)-(1,43) "x:\dir\subdir\Test\TestComponent.cshtml" +using Microsoft.AspNetCore.Components.Web + +#nullable disable + ; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (2,12)-(2,13) "x:\dir\subdir\Test\TestComponent.cshtml" +T + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (5,8)-(7,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] public T Parameter { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.decl.ir.txt index b6e48cbf4180e..c59bbc9e07c77 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.decl.ir.txt @@ -1,3 +1,11 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - T + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + UsingDirective - (1:0,1 [41] x:\dir\subdir\Test\TestComponent.cshtml) - Microsoft.AspNetCore.Components.Web + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - T + CSharpCode - (270:4,7 [52] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (270:4,7 [52] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public T Parameter { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..12838b35a4c12 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.decl.mappings.txt @@ -0,0 +1,19 @@ +Source Location: (1:0,1 [41] x:\dir\subdir\Test\TestComponent.cshtml) +|using Microsoft.AspNetCore.Components.Web| +Generated Location: (372:12,0 [41] ) +|using Microsoft.AspNetCore.Components.Web| + +Source Location: (55:1,11 [1] x:\dir\subdir\Test\TestComponent.cshtml) +|T| +Generated Location: (626:22,0 [1] ) +|T| + +Source Location: (270:4,7 [52] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] public T Parameter { get; set; } +| +Generated Location: (852:32,0 [52] ) +| + [Parameter] public T Parameter { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.ir.txt index 1b1c497105f22..a94ae2e856e01 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.ir.txt @@ -38,8 +38,6 @@ LazyIntermediateToken - (222:3,64 [20] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - "named-form-handler" ComponentAttribute - (256:3,98 [1] x:\dir\subdir\Test\TestComponent.cshtml) - Parameter - Parameter - AttributeStructure.DoubleQuotes IntermediateToken - (256:3,98 [1] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - 2 - CSharpCode - (270:4,7 [52] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (270:4,7 [52] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public T Parameter { get; set; }\n NamespaceDeclaration - - __Blazor.Test.TestComponent ClassDeclaration - - internal static - TypeInference - - ComponentTypeInferenceMethod - - __Blazor.Test.TestComponent.TypeInference - CreateTestComponent_0 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.mappings.txt index 488e3caa229f8..e8e114c5cbc21 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic/TestComponent.mappings.txt @@ -33,32 +33,23 @@ Source Location: (256:3,98 [1] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (2190:70,0 [1] ) |2| -Source Location: (270:4,7 [52] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] public T Parameter { get; set; } -| -Generated Location: (2388:80,0 [52] ) -| - [Parameter] public T Parameter { get; set; } -| - Source Location: (59:2,1 [13] x:\dir\subdir\Test\TestComponent.cshtml) |TestComponent| -Generated Location: (2955:96,45 [13] ) +Generated Location: (2771:87,45 [13] ) |TestComponent| Source Location: (140:2,82 [9] x:\dir\subdir\Test\TestComponent.cshtml) |Parameter| -Generated Location: (3358:103,0 [9] ) +Generated Location: (3174:94,0 [9] ) |Parameter| Source Location: (159:3,1 [13] x:\dir\subdir\Test\TestComponent.cshtml) |TestComponent| -Generated Location: (3833:113,45 [13] ) +Generated Location: (3649:104,45 [13] ) |TestComponent| Source Location: (245:3,87 [9] x:\dir\subdir\Test\TestComponent.cshtml) |Parameter| -Generated Location: (4236:120,0 [9] ) +Generated Location: (4052:111,0 [9] ) |Parameter| diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.codegen.cs index 7227a02ca88bf..ced9d04e69ca6 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.codegen.cs @@ -76,15 +76,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. ); } #pragma warning restore 1998 -#nullable restore -#line (5,8)-(7,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] public T Parameter { get; set; } - -#line default -#line hidden -#nullable disable - } } namespace __Blazor.Test.TestComponent diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.decl.codegen.cs index c4e3c7388638a..aab445ad51c49 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.decl.codegen.cs @@ -2,11 +2,41 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; +#nullable restore +#line (1,2)-(1,43) "x:\dir\subdir\Test\TestComponent.cshtml" +using Microsoft.AspNetCore.Components.Web + +#nullable disable + ; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (2,12)-(2,13) "x:\dir\subdir\Test\TestComponent.cshtml" +T + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (5,8)-(7,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] public T Parameter { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.decl.ir.txt index b6e48cbf4180e..c59bbc9e07c77 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.decl.ir.txt @@ -1,3 +1,11 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - T + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + UsingDirective - (1:0,1 [41] x:\dir\subdir\Test\TestComponent.cshtml) - Microsoft.AspNetCore.Components.Web + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - T + CSharpCode - (270:4,7 [52] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (270:4,7 [52] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public T Parameter { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..12838b35a4c12 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.decl.mappings.txt @@ -0,0 +1,19 @@ +Source Location: (1:0,1 [41] x:\dir\subdir\Test\TestComponent.cshtml) +|using Microsoft.AspNetCore.Components.Web| +Generated Location: (372:12,0 [41] ) +|using Microsoft.AspNetCore.Components.Web| + +Source Location: (55:1,11 [1] x:\dir\subdir\Test\TestComponent.cshtml) +|T| +Generated Location: (626:22,0 [1] ) +|T| + +Source Location: (270:4,7 [52] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] public T Parameter { get; set; } +| +Generated Location: (852:32,0 [52] ) +| + [Parameter] public T Parameter { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.ir.txt index 64c526473a851..e2a88a86305e3 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.ir.txt @@ -38,8 +38,6 @@ LazyIntermediateToken - (222:3,64 [20] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - "named-form-handler" ComponentAttribute - (256:3,98 [1] x:\dir\subdir\Test\TestComponent.cshtml) - Parameter - Parameter - AttributeStructure.DoubleQuotes IntermediateToken - (256:3,98 [1] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - 2 - CSharpCode - (270:4,7 [52] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (270:4,7 [52] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public T Parameter { get; set; }\n NamespaceDeclaration - - __Blazor.Test.TestComponent ClassDeclaration - - internal static - TypeInference - - ComponentTypeInferenceMethod - - __Blazor.Test.TestComponent.TypeInference - CreateTestComponent_0 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.mappings.txt index 2830d00cdedad..25672adc764ac 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/FormName_Component_Generic_RazorLangVersion7/TestComponent.mappings.txt @@ -33,32 +33,23 @@ Source Location: (256:3,98 [1] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (2190:70,0 [1] ) |2| -Source Location: (270:4,7 [52] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] public T Parameter { get; set; } -| -Generated Location: (2388:80,0 [52] ) -| - [Parameter] public T Parameter { get; set; } -| - Source Location: (59:2,1 [13] x:\dir\subdir\Test\TestComponent.cshtml) |TestComponent| -Generated Location: (2955:96,45 [13] ) +Generated Location: (2771:87,45 [13] ) |TestComponent| Source Location: (140:2,82 [9] x:\dir\subdir\Test\TestComponent.cshtml) |Parameter| -Generated Location: (3346:103,0 [9] ) +Generated Location: (3162:94,0 [9] ) |Parameter| Source Location: (159:3,1 [13] x:\dir\subdir\Test\TestComponent.cshtml) |TestComponent| -Generated Location: (3829:113,45 [13] ) +Generated Location: (3645:104,45 [13] ) |TestComponent| Source Location: (245:3,87 [9] x:\dir\subdir\Test\TestComponent.cshtml) |Parameter| -Generated Location: (4220:120,0 [9] ) +Generated Location: (4036:111,0 [9] ) |Parameter| diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.codegen.cs index 97ad21ae9b55d..740a0a0bad8d7 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.codegen.cs @@ -72,17 +72,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. __builder.CloseComponent(); } #pragma warning restore 1998 -#nullable restore -#line (3,8)-(7,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - public TParam ParentValue { get; set; } = default; - - public void UpdateValue(TParam value) { ParentValue = value; } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.decl.codegen.cs index 0278bf0607f5f..a142468f7d2d2 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.decl.codegen.cs @@ -2,11 +2,37 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TParam + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (3,8)-(7,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + public TParam ParentValue { get; set; } = default; + + public void UpdateValue(TParam value) { ParentValue = value; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.decl.ir.txt index 8a361060e5ec7..25a01ed8fbe58 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TParam + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TParam + CSharpCode - (119:2,7 [128] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (119:2,7 [128] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public TParam ParentValue { get; set; } = default;\n\n public void UpdateValue(TParam value) { ParentValue = value; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..9375f0fe11525 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.decl.mappings.txt @@ -0,0 +1,18 @@ +Source Location: (11:0,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TParam| +Generated Location: (474:16,0 [6] ) +|TParam| + +Source Location: (119:2,7 [128] x:\dir\subdir\Test\TestComponent.cshtml) +| + public TParam ParentValue { get; set; } = default; + + public void UpdateValue(TParam value) { ParentValue = value; } +| +Generated Location: (705:26,0 [128] ) +| + public TParam ParentValue { get; set; } = default; + + public void UpdateValue(TParam value) { ParentValue = value; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.ir.txt index 4cd9d16881f0d..d01ba1a0b6781 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.ir.txt @@ -21,5 +21,3 @@ ComponentAttribute - (95:1,76 [11] x:\dir\subdir\Test\TestComponent.cshtml) - bind-Value - Value - AttributeStructure.DoubleQuotes CSharpExpression - (95:1,76 [11] x:\dir\subdir\Test\TestComponent.cshtml) IntermediateToken - (95:1,76 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - UpdateValue - CSharpCode - (119:2,7 [128] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (119:2,7 [128] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public TParam ParentValue { get; set; } = default;\n\n public void UpdateValue(TParam value) { ParentValue = value; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.mappings.txt index 5537c5e611cff..4b05dc33a4568 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Action/TestComponent.mappings.txt @@ -33,16 +33,3 @@ Source Location: (84:1,65 [5] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (2288:65,0 [5] ) |Value| -Source Location: (119:2,7 [128] x:\dir\subdir\Test\TestComponent.cshtml) -| - public TParam ParentValue { get; set; } = default; - - public void UpdateValue(TParam value) { ParentValue = value; } -| -Generated Location: (2535:76,0 [128] ) -| - public TParam ParentValue { get; set; } = default; - - public void UpdateValue(TParam value) { ParentValue = value; } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.codegen.cs index b56dc9a0bd55f..740a0a0bad8d7 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.codegen.cs @@ -72,16 +72,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. __builder.CloseComponent(); } #pragma warning restore 1998 -#nullable restore -#line (3,8)-(6,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - public TParam ParentValue { get; set; } = default; - public EventCallback UpdateValue { get; set; } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.decl.codegen.cs index 0278bf0607f5f..678f9dbb83532 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.decl.codegen.cs @@ -2,11 +2,36 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TParam + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (3,8)-(6,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + public TParam ParentValue { get; set; } = default; + public EventCallback UpdateValue { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.decl.ir.txt index 8a361060e5ec7..0ba60608d8d10 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TParam + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TParam + CSharpCode - (119:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (119:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public TParam ParentValue { get; set; } = default;\n public EventCallback UpdateValue { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..8e4d6c737c94e --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.decl.mappings.txt @@ -0,0 +1,16 @@ +Source Location: (11:0,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TParam| +Generated Location: (474:16,0 [6] ) +|TParam| + +Source Location: (119:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) +| + public TParam ParentValue { get; set; } = default; + public EventCallback UpdateValue { get; set; } +| +Generated Location: (705:26,0 [118] ) +| + public TParam ParentValue { get; set; } = default; + public EventCallback UpdateValue { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.ir.txt index cdc372d6a8f8c..d01ba1a0b6781 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.ir.txt @@ -21,5 +21,3 @@ ComponentAttribute - (95:1,76 [11] x:\dir\subdir\Test\TestComponent.cshtml) - bind-Value - Value - AttributeStructure.DoubleQuotes CSharpExpression - (95:1,76 [11] x:\dir\subdir\Test\TestComponent.cshtml) IntermediateToken - (95:1,76 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - UpdateValue - CSharpCode - (119:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (119:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public TParam ParentValue { get; set; } = default;\n public EventCallback UpdateValue { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.mappings.txt index aa1f50d879870..4b05dc33a4568 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_EventCallback/TestComponent.mappings.txt @@ -33,14 +33,3 @@ Source Location: (84:1,65 [5] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (2288:65,0 [5] ) |Value| -Source Location: (119:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) -| - public TParam ParentValue { get; set; } = default; - public EventCallback UpdateValue { get; set; } -| -Generated Location: (2535:76,0 [118] ) -| - public TParam ParentValue { get; set; } = default; - public EventCallback UpdateValue { get; set; } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.codegen.cs index 4ca111cbe0579..740a0a0bad8d7 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.codegen.cs @@ -72,17 +72,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. __builder.CloseComponent(); } #pragma warning restore 1998 -#nullable restore -#line (3,8)-(7,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - public TParam ParentValue { get; set; } = default; - - public Task UpdateValue(TParam value) { ParentValue = value; return Task.CompletedTask; } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.decl.codegen.cs index 0278bf0607f5f..886d6a3c6bd3d 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.decl.codegen.cs @@ -2,11 +2,37 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TParam + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (3,8)-(7,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + public TParam ParentValue { get; set; } = default; + + public Task UpdateValue(TParam value) { ParentValue = value; return Task.CompletedTask; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.decl.ir.txt index 8a361060e5ec7..638fcf80107c6 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TParam + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TParam + CSharpCode - (119:2,7 [155] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (119:2,7 [155] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public TParam ParentValue { get; set; } = default;\n\n public Task UpdateValue(TParam value) { ParentValue = value; return Task.CompletedTask; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..2c31033baf933 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.decl.mappings.txt @@ -0,0 +1,18 @@ +Source Location: (11:0,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TParam| +Generated Location: (474:16,0 [6] ) +|TParam| + +Source Location: (119:2,7 [155] x:\dir\subdir\Test\TestComponent.cshtml) +| + public TParam ParentValue { get; set; } = default; + + public Task UpdateValue(TParam value) { ParentValue = value; return Task.CompletedTask; } +| +Generated Location: (705:26,0 [155] ) +| + public TParam ParentValue { get; set; } = default; + + public Task UpdateValue(TParam value) { ParentValue = value; return Task.CompletedTask; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.ir.txt index 4e0036732cbb7..d01ba1a0b6781 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.ir.txt @@ -21,5 +21,3 @@ ComponentAttribute - (95:1,76 [11] x:\dir\subdir\Test\TestComponent.cshtml) - bind-Value - Value - AttributeStructure.DoubleQuotes CSharpExpression - (95:1,76 [11] x:\dir\subdir\Test\TestComponent.cshtml) IntermediateToken - (95:1,76 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - UpdateValue - CSharpCode - (119:2,7 [155] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (119:2,7 [155] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public TParam ParentValue { get; set; } = default;\n\n public Task UpdateValue(TParam value) { ParentValue = value; return Task.CompletedTask; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.mappings.txt index 88ded0f8658d5..4b05dc33a4568 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericBindToGenericComponent_ExplicitType_WithGetSet_Function/TestComponent.mappings.txt @@ -33,16 +33,3 @@ Source Location: (84:1,65 [5] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (2288:65,0 [5] ) |Value| -Source Location: (119:2,7 [155] x:\dir\subdir\Test\TestComponent.cshtml) -| - public TParam ParentValue { get; set; } = default; - - public Task UpdateValue(TParam value) { ParentValue = value; return Task.CompletedTask; } -| -Generated Location: (2535:76,0 [155] ) -| - public TParam ParentValue { get; set; } = default; - - public Task UpdateValue(TParam value) { ParentValue = value; return Task.CompletedTask; } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.codegen.cs index c888672e4ff7e..97c9f9d6486ae 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.codegen.cs @@ -72,17 +72,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. __builder.CloseComponent(); } #pragma warning restore 1998 -#nullable restore -#line (3,8)-(7,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - public TParam ParentValue { get; set; } - - public void Update() { } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.decl.codegen.cs index 0278bf0607f5f..2072ab111c628 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.decl.codegen.cs @@ -2,11 +2,37 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TParam + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (3,8)-(7,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + public TParam ParentValue { get; set; } + + public void Update() { } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.decl.ir.txt index 8a361060e5ec7..cb4b24561ced4 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TParam + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TParam + CSharpCode - (116:2,7 [79] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (116:2,7 [79] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public TParam ParentValue { get; set; }\n\n public void Update() { }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..dfa5acbad0815 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.decl.mappings.txt @@ -0,0 +1,18 @@ +Source Location: (11:0,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TParam| +Generated Location: (474:16,0 [6] ) +|TParam| + +Source Location: (116:2,7 [79] x:\dir\subdir\Test\TestComponent.cshtml) +| + public TParam ParentValue { get; set; } + + public void Update() { } +| +Generated Location: (705:26,0 [79] ) +| + public TParam ParentValue { get; set; } + + public void Update() { } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.ir.txt index 5742dcef83456..8a853d1ca8695 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.ir.txt @@ -20,5 +20,3 @@ IntermediateToken - - CSharp - ); } ComponentAttribute - (97:1,78 [6] x:\dir\subdir\Test\TestComponent.cshtml) - bind-Value - Value - AttributeStructure.DoubleQuotes IntermediateToken - (97:1,78 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - Update - CSharpCode - (116:2,7 [79] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (116:2,7 [79] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public TParam ParentValue { get; set; }\n\n public void Update() { }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.mappings.txt index 3adfc73842157..4b92e22231e65 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_ExplicitType_WithAfter_Action/TestComponent.mappings.txt @@ -33,16 +33,3 @@ Source Location: (84:1,65 [5] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (2105:65,0 [5] ) |Value| -Source Location: (116:2,7 [79] x:\dir\subdir\Test\TestComponent.cshtml) -| - public TParam ParentValue { get; set; } - - public void Update() { } -| -Generated Location: (2352:76,0 [79] ) -| - public TParam ParentValue { get; set; } - - public void Update() { } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.codegen.cs index 91d7b1d6a9e45..7be205824dc26 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.codegen.cs @@ -44,17 +44,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. ); }); } #pragma warning restore 1998 -#nullable restore -#line (3,8)-(7,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - public TParam ParentValue { get; set; } - - public void Update() { } - -#line default -#line hidden -#nullable disable - } } namespace __Blazor.Test.TestComponent diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.decl.codegen.cs index 0278bf0607f5f..2072ab111c628 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.decl.codegen.cs @@ -2,11 +2,37 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TParam + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (3,8)-(7,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + public TParam ParentValue { get; set; } + + public void Update() { } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.decl.ir.txt index 8a361060e5ec7..f4f6a545d929a 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TParam + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TParam + CSharpCode - (100:2,7 [79] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (100:2,7 [79] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public TParam ParentValue { get; set; }\n\n public void Update() { }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..a764754d6d9a6 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.decl.mappings.txt @@ -0,0 +1,18 @@ +Source Location: (11:0,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TParam| +Generated Location: (474:16,0 [6] ) +|TParam| + +Source Location: (100:2,7 [79] x:\dir\subdir\Test\TestComponent.cshtml) +| + public TParam ParentValue { get; set; } + + public void Update() { } +| +Generated Location: (705:26,0 [79] ) +| + public TParam ParentValue { get; set; } + + public void Update() { } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.ir.txt index 32afe6f85d76b..315b2fc7f5e07 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.ir.txt @@ -18,8 +18,6 @@ IntermediateToken - - CSharp - ); } ComponentAttribute - (81:1,62 [6] x:\dir\subdir\Test\TestComponent.cshtml) - bind-Value - Value - AttributeStructure.DoubleQuotes IntermediateToken - (81:1,62 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - Update - CSharpCode - (100:2,7 [79] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (100:2,7 [79] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public TParam ParentValue { get; set; }\n\n public void Update() { }\n NamespaceDeclaration - - __Blazor.Test.TestComponent ClassDeclaration - - internal static - TypeInference - - ComponentTypeInferenceMethod - - __Blazor.Test.TestComponent.TypeInference - CreateMyComponent_0 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.mappings.txt index 9c35a816e3b7f..b2a7764f5ff6b 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithAfter_Action/TestComponent.mappings.txt @@ -13,26 +13,13 @@ Source Location: (81:1,62 [6] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (1284:38,0 [6] ) |Update| -Source Location: (100:2,7 [79] x:\dir\subdir\Test\TestComponent.cshtml) -| - public TParam ParentValue { get; set; } - - public void Update() { } -| -Generated Location: (1491:48,0 [79] ) -| - public TParam ParentValue { get; set; } - - public void Update() { } -| - Source Location: (20:1,1 [11] x:\dir\subdir\Test\TestComponent.cshtml) |MyComponent| -Generated Location: (2017:66,45 [11] ) +Generated Location: (1806:55,45 [11] ) |MyComponent| Source Location: (38:1,19 [5] x:\dir\subdir\Test\TestComponent.cshtml) |Value| -Generated Location: (2217:70,0 [5] ) +Generated Location: (2006:59,0 [5] ) |Value| diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.codegen.cs index 85519c2896be5..fd8eeb3e1a1e5 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.codegen.cs @@ -44,17 +44,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. , ParentValue))); } #pragma warning restore 1998 -#nullable restore -#line (3,8)-(7,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - public TParam ParentValue { get; set; } = default; - - public void UpdateValue(TParam value) { ParentValue = value; } - -#line default -#line hidden -#nullable disable - } } namespace __Blazor.Test.TestComponent diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.decl.codegen.cs index 0278bf0607f5f..a142468f7d2d2 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.decl.codegen.cs @@ -2,11 +2,37 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TParam + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (3,8)-(7,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + public TParam ParentValue { get; set; } = default; + + public void UpdateValue(TParam value) { ParentValue = value; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.decl.ir.txt index 8a361060e5ec7..14c6001c0b9aa 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TParam + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TParam + CSharpCode - (103:2,7 [128] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (103:2,7 [128] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public TParam ParentValue { get; set; } = default;\n\n public void UpdateValue(TParam value) { ParentValue = value; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..612cd1f4d4d3f --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.decl.mappings.txt @@ -0,0 +1,18 @@ +Source Location: (11:0,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TParam| +Generated Location: (474:16,0 [6] ) +|TParam| + +Source Location: (103:2,7 [128] x:\dir\subdir\Test\TestComponent.cshtml) +| + public TParam ParentValue { get; set; } = default; + + public void UpdateValue(TParam value) { ParentValue = value; } +| +Generated Location: (705:26,0 [128] ) +| + public TParam ParentValue { get; set; } = default; + + public void UpdateValue(TParam value) { ParentValue = value; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.ir.txt index 914f01aa9a938..54b71b3326071 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.ir.txt @@ -19,8 +19,6 @@ ComponentAttribute - (79:1,60 [11] x:\dir\subdir\Test\TestComponent.cshtml) - bind-Value - Value - AttributeStructure.DoubleQuotes CSharpExpression - (79:1,60 [11] x:\dir\subdir\Test\TestComponent.cshtml) IntermediateToken - (79:1,60 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - UpdateValue - CSharpCode - (103:2,7 [128] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (103:2,7 [128] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public TParam ParentValue { get; set; } = default;\n\n public void UpdateValue(TParam value) { ParentValue = value; }\n NamespaceDeclaration - - __Blazor.Test.TestComponent ClassDeclaration - - internal static - TypeInference - - ComponentTypeInferenceMethod - - __Blazor.Test.TestComponent.TypeInference - CreateMyComponent_0 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.mappings.txt index 55dd47414da26..ff5a46a8443b6 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Action/TestComponent.mappings.txt @@ -13,26 +13,13 @@ Source Location: (79:1,60 [11] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (1330:38,0 [11] ) |UpdateValue| -Source Location: (103:2,7 [128] x:\dir\subdir\Test\TestComponent.cshtml) -| - public TParam ParentValue { get; set; } = default; - - public void UpdateValue(TParam value) { ParentValue = value; } -| -Generated Location: (1553:48,0 [128] ) -| - public TParam ParentValue { get; set; } = default; - - public void UpdateValue(TParam value) { ParentValue = value; } -| - Source Location: (20:1,1 [11] x:\dir\subdir\Test\TestComponent.cshtml) |MyComponent| -Generated Location: (2160:66,45 [11] ) +Generated Location: (1900:55,45 [11] ) |MyComponent| Source Location: (38:1,19 [5] x:\dir\subdir\Test\TestComponent.cshtml) |Value| -Generated Location: (2360:70,0 [5] ) +Generated Location: (2100:59,0 [5] ) |Value| diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.codegen.cs index 5d06df85eb8e0..fd8eeb3e1a1e5 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.codegen.cs @@ -44,16 +44,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. , ParentValue))); } #pragma warning restore 1998 -#nullable restore -#line (3,8)-(6,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - public TParam ParentValue { get; set; } = default; - public EventCallback UpdateValue { get; set; } - -#line default -#line hidden -#nullable disable - } } namespace __Blazor.Test.TestComponent diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.decl.codegen.cs index 0278bf0607f5f..678f9dbb83532 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.decl.codegen.cs @@ -2,11 +2,36 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TParam + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (3,8)-(6,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + public TParam ParentValue { get; set; } = default; + public EventCallback UpdateValue { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.decl.ir.txt index 8a361060e5ec7..3f82c8efdff62 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TParam + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TParam + CSharpCode - (103:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (103:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public TParam ParentValue { get; set; } = default;\n public EventCallback UpdateValue { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..c8150dc5304e7 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.decl.mappings.txt @@ -0,0 +1,16 @@ +Source Location: (11:0,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TParam| +Generated Location: (474:16,0 [6] ) +|TParam| + +Source Location: (103:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) +| + public TParam ParentValue { get; set; } = default; + public EventCallback UpdateValue { get; set; } +| +Generated Location: (705:26,0 [118] ) +| + public TParam ParentValue { get; set; } = default; + public EventCallback UpdateValue { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.ir.txt index b45061f1830d1..54b71b3326071 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.ir.txt @@ -19,8 +19,6 @@ ComponentAttribute - (79:1,60 [11] x:\dir\subdir\Test\TestComponent.cshtml) - bind-Value - Value - AttributeStructure.DoubleQuotes CSharpExpression - (79:1,60 [11] x:\dir\subdir\Test\TestComponent.cshtml) IntermediateToken - (79:1,60 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - UpdateValue - CSharpCode - (103:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (103:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public TParam ParentValue { get; set; } = default;\n public EventCallback UpdateValue { get; set; }\n NamespaceDeclaration - - __Blazor.Test.TestComponent ClassDeclaration - - internal static - TypeInference - - ComponentTypeInferenceMethod - - __Blazor.Test.TestComponent.TypeInference - CreateMyComponent_0 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.mappings.txt index eb2ee6aa5b003..ff5a46a8443b6 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_EventCallback/TestComponent.mappings.txt @@ -13,24 +13,13 @@ Source Location: (79:1,60 [11] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (1330:38,0 [11] ) |UpdateValue| -Source Location: (103:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) -| - public TParam ParentValue { get; set; } = default; - public EventCallback UpdateValue { get; set; } -| -Generated Location: (1553:48,0 [118] ) -| - public TParam ParentValue { get; set; } = default; - public EventCallback UpdateValue { get; set; } -| - Source Location: (20:1,1 [11] x:\dir\subdir\Test\TestComponent.cshtml) |MyComponent| -Generated Location: (2150:65,45 [11] ) +Generated Location: (1900:55,45 [11] ) |MyComponent| Source Location: (38:1,19 [5] x:\dir\subdir\Test\TestComponent.cshtml) |Value| -Generated Location: (2350:69,0 [5] ) +Generated Location: (2100:59,0 [5] ) |Value| diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.codegen.cs index c8d9527c66320..fd8eeb3e1a1e5 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.codegen.cs @@ -44,17 +44,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. , ParentValue))); } #pragma warning restore 1998 -#nullable restore -#line (3,8)-(7,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - public TParam ParentValue { get; set; } = default; - - public Task UpdateValue(TParam value) { ParentValue = value; return Task.CompletedTask; } - -#line default -#line hidden -#nullable disable - } } namespace __Blazor.Test.TestComponent diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.decl.codegen.cs index 0278bf0607f5f..886d6a3c6bd3d 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.decl.codegen.cs @@ -2,11 +2,37 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TParam + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (3,8)-(7,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + public TParam ParentValue { get; set; } = default; + + public Task UpdateValue(TParam value) { ParentValue = value; return Task.CompletedTask; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.decl.ir.txt index 8a361060e5ec7..7aea7698b19a0 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TParam + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TParam + CSharpCode - (103:2,7 [155] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (103:2,7 [155] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public TParam ParentValue { get; set; } = default;\n\n public Task UpdateValue(TParam value) { ParentValue = value; return Task.CompletedTask; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..44d1887ebddd1 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.decl.mappings.txt @@ -0,0 +1,18 @@ +Source Location: (11:0,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TParam| +Generated Location: (474:16,0 [6] ) +|TParam| + +Source Location: (103:2,7 [155] x:\dir\subdir\Test\TestComponent.cshtml) +| + public TParam ParentValue { get; set; } = default; + + public Task UpdateValue(TParam value) { ParentValue = value; return Task.CompletedTask; } +| +Generated Location: (705:26,0 [155] ) +| + public TParam ParentValue { get; set; } = default; + + public Task UpdateValue(TParam value) { ParentValue = value; return Task.CompletedTask; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.ir.txt index 9d90415ef63bf..54b71b3326071 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.ir.txt @@ -19,8 +19,6 @@ ComponentAttribute - (79:1,60 [11] x:\dir\subdir\Test\TestComponent.cshtml) - bind-Value - Value - AttributeStructure.DoubleQuotes CSharpExpression - (79:1,60 [11] x:\dir\subdir\Test\TestComponent.cshtml) IntermediateToken - (79:1,60 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - UpdateValue - CSharpCode - (103:2,7 [155] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (103:2,7 [155] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public TParam ParentValue { get; set; } = default;\n\n public Task UpdateValue(TParam value) { ParentValue = value; return Task.CompletedTask; }\n NamespaceDeclaration - - __Blazor.Test.TestComponent ClassDeclaration - - internal static - TypeInference - - ComponentTypeInferenceMethod - - __Blazor.Test.TestComponent.TypeInference - CreateMyComponent_0 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.mappings.txt index d6b33596834a8..ff5a46a8443b6 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentBindToGenericComponent_InferredType_WithGetSet_Function/TestComponent.mappings.txt @@ -13,26 +13,13 @@ Source Location: (79:1,60 [11] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (1330:38,0 [11] ) |UpdateValue| -Source Location: (103:2,7 [155] x:\dir\subdir\Test\TestComponent.cshtml) -| - public TParam ParentValue { get; set; } = default; - - public Task UpdateValue(TParam value) { ParentValue = value; return Task.CompletedTask; } -| -Generated Location: (1553:48,0 [155] ) -| - public TParam ParentValue { get; set; } = default; - - public Task UpdateValue(TParam value) { ParentValue = value; return Task.CompletedTask; } -| - Source Location: (20:1,1 [11] x:\dir\subdir\Test\TestComponent.cshtml) |MyComponent| -Generated Location: (2187:66,45 [11] ) +Generated Location: (1900:55,45 [11] ) |MyComponent| Source Location: (38:1,19 [5] x:\dir\subdir\Test\TestComponent.cshtml) |Value| -Generated Location: (2387:70,0 [5] ) +Generated Location: (2100:59,0 [5] ) |Value| diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.codegen.cs index 0b918f4d88e1b..42cd94c6dbd68 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.codegen.cs @@ -53,19 +53,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. __builder.CloseComponent(); } #pragma warning restore 1998 -#nullable restore -#line (3,8)-(9,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] - public TItem MyItem { get; set; } - - [Parameter] - public TItem2 MyItem2 { get; set; } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.decl.codegen.cs index 21a10e338f23c..40c1a7d76d7bd 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.decl.codegen.cs @@ -2,11 +2,47 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,17) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem + +#line default +#line hidden +#nullable disable + , +#nullable restore +#line (2,12)-(2,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem2 + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (3,8)-(9,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] + public TItem MyItem { get; set; } + + [Parameter] + public TItem2 MyItem2 { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.decl.ir.txt index 47857988533f5..291fb266c7fee 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TItem, TItem2 + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TItem, TItem2 + CSharpCode - (44:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (44:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public TItem MyItem { get; set; }\n\n [Parameter]\n public TItem2 MyItem2 { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..8487080b43d46 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.decl.mappings.txt @@ -0,0 +1,27 @@ +Source Location: (11:0,11 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem| +Generated Location: (474:16,0 [5] ) +|TItem| + +Source Location: (29:1,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem2| +Generated Location: (620:24,0 [6] ) +|TItem2| + +Source Location: (44:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] + public TItem MyItem { get; set; } + + [Parameter] + public TItem2 MyItem2 { get; set; } +| +Generated Location: (851:34,0 [118] ) +| + [Parameter] + public TItem MyItem { get; set; } + + [Parameter] + public TItem2 MyItem2 { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.ir.txt index cfb027ee934d9..edd4e58f37d9b 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.ir.txt @@ -12,5 +12,3 @@ IntermediateToken - (190:10,23 [3] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - int ComponentTypeArgument - (202:10,35 [6] x:\dir\subdir\Test\TestComponent.cshtml) - TItem IntermediateToken - (202:10,35 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - string - CSharpCode - (44:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (44:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public TItem MyItem { get; set; }\n\n [Parameter]\n public TItem2 MyItem2 { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.mappings.txt index d11b989a55a43..408639344d800 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentMultipleTypeParamUsage/TestComponent.mappings.txt @@ -23,20 +23,3 @@ Source Location: (190:10,23 [3] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (1255:46,0 [3] ) |int| -Source Location: (44:2,7 [118] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] - public TItem MyItem { get; set; } - - [Parameter] - public TItem2 MyItem2 { get; set; } -| -Generated Location: (1500:57,0 [118] ) -| - [Parameter] - public TItem MyItem { get; set; } - - [Parameter] - public TItem2 MyItem2 { get; set; } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.codegen.cs index a78ea9a449873..6fa54f2743c5f 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.codegen.cs @@ -37,16 +37,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. __builder.CloseComponent(); } #pragma warning restore 1998 -#nullable restore -#line (2,8)-(5,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] - public TItem MyItem { get; set; } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.decl.codegen.cs index a95703435fd3d..897877ac1a8c2 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.decl.codegen.cs @@ -2,11 +2,36 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,17) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (2,8)-(5,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] + public TItem MyItem { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.decl.ir.txt index cbfa0567b3baf..7d737f78dc4fd 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TItem + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TItem + CSharpCode - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public TItem MyItem { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..0e896623d3fbd --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.decl.mappings.txt @@ -0,0 +1,16 @@ +Source Location: (11:0,11 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem| +Generated Location: (474:16,0 [5] ) +|TItem| + +Source Location: (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] + public TItem MyItem { get; set; } +| +Generated Location: (704:26,0 [58] ) +| + [Parameter] + public TItem MyItem { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.ir.txt index a2aa74a0f3408..5de7ec8a54e24 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.ir.txt @@ -10,5 +10,3 @@ Component - (88:6,0 [33] x:\dir\subdir\Test\TestComponent.cshtml) - TestComponent ComponentTypeArgument - (110:6,22 [7] x:\dir\subdir\Test\TestComponent.cshtml) - TItem LazyIntermediateToken - (111:6,23 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - string - CSharpCode - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public TItem MyItem { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.mappings.txt index 1f79098c02d16..6e0dbd73a1e57 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression/TestComponent.mappings.txt @@ -13,14 +13,3 @@ Source Location: (111:6,23 [6] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (948:30,0 [6] ) |string| -Source Location: (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] - public TItem MyItem { get; set; } -| -Generated Location: (1196:41,0 [58] ) -| - [Parameter] - public TItem MyItem { get; set; } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.codegen.cs index 7c0b449e0122c..c2f346f99b2bf 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.codegen.cs @@ -37,16 +37,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. __builder.CloseComponent(); } #pragma warning restore 1998 -#nullable restore -#line (2,8)-(5,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] - public TItem MyItem { get; set; } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.decl.codegen.cs index a95703435fd3d..897877ac1a8c2 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.decl.codegen.cs @@ -2,11 +2,36 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,17) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (2,8)-(5,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] + public TItem MyItem { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.decl.ir.txt index cbfa0567b3baf..7d737f78dc4fd 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TItem + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TItem + CSharpCode - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public TItem MyItem { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..0e896623d3fbd --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.decl.mappings.txt @@ -0,0 +1,16 @@ +Source Location: (11:0,11 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem| +Generated Location: (474:16,0 [5] ) +|TItem| + +Source Location: (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] + public TItem MyItem { get; set; } +| +Generated Location: (704:26,0 [58] ) +| + [Parameter] + public TItem MyItem { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.ir.txt index 1e7851d08a683..1cc9439435511 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.ir.txt @@ -10,5 +10,3 @@ Component - (88:6,0 [35] x:\dir\subdir\Test\TestComponent.cshtml) - TestComponent ComponentTypeArgument - (110:6,22 [9] x:\dir\subdir\Test\TestComponent.cshtml) - TItem LazyIntermediateToken - (112:6,24 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - string - CSharpCode - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public TItem MyItem { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.mappings.txt index add78b4342bca..0b3a6a66d63f5 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeParamUsageWithImplicitExpression2/TestComponent.mappings.txt @@ -13,14 +13,3 @@ Source Location: (112:6,24 [6] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (948:30,0 [6] ) |string| -Source Location: (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] - public TItem MyItem { get; set; } -| -Generated Location: (1196:41,0 [58] ) -| - [Parameter] - public TItem MyItem { get; set; } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.codegen.cs index 41d0bc99d7a25..927059867f9fe 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.codegen.cs @@ -37,16 +37,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. __builder.CloseComponent(); } #pragma warning restore 1998 -#nullable restore -#line (2,8)-(5,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] - public TItem MyItem { get; set; } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.decl.codegen.cs index a95703435fd3d..897877ac1a8c2 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.decl.codegen.cs @@ -2,11 +2,36 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,17) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (2,8)-(5,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] + public TItem MyItem { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.decl.ir.txt index cbfa0567b3baf..7d737f78dc4fd 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TItem + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TItem + CSharpCode - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public TItem MyItem { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..0e896623d3fbd --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.decl.mappings.txt @@ -0,0 +1,16 @@ +Source Location: (11:0,11 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem| +Generated Location: (474:16,0 [5] ) +|TItem| + +Source Location: (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] + public TItem MyItem { get; set; } +| +Generated Location: (704:26,0 [58] ) +| + [Parameter] + public TItem MyItem { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.ir.txt index c18edc3ce354f..c5912471ace62 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.ir.txt @@ -10,5 +10,3 @@ Component - (88:6,0 [32] x:\dir\subdir\Test\TestComponent.cshtml) - TestComponent ComponentTypeArgument - (110:6,22 [6] x:\dir\subdir\Test\TestComponent.cshtml) - TItem IntermediateToken - (110:6,22 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - string - CSharpCode - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public TItem MyItem { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.mappings.txt index abb05a9191247..88de86e3d2b62 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsage/TestComponent.mappings.txt @@ -13,14 +13,3 @@ Source Location: (110:6,22 [6] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (948:30,0 [6] ) |string| -Source Location: (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] - public TItem MyItem { get; set; } -| -Generated Location: (1196:41,0 [58] ) -| - [Parameter] - public TItem MyItem { get; set; } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.codegen.cs index 1526328ed789a..a50c4639e7b4a 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.codegen.cs @@ -37,16 +37,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. __builder.CloseComponent(); } #pragma warning restore 1998 -#nullable restore -#line (2,8)-(5,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] - public TItem MyItem { get; set; } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.decl.codegen.cs index a95703435fd3d..897877ac1a8c2 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.decl.codegen.cs @@ -2,11 +2,36 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,17) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (2,8)-(5,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] + public TItem MyItem { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.decl.ir.txt index cbfa0567b3baf..7d737f78dc4fd 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TItem + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TItem + CSharpCode - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public TItem MyItem { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..0e896623d3fbd --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.decl.mappings.txt @@ -0,0 +1,16 @@ +Source Location: (11:0,11 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem| +Generated Location: (474:16,0 [5] ) +|TItem| + +Source Location: (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] + public TItem MyItem { get; set; } +| +Generated Location: (704:26,0 [58] ) +| + [Parameter] + public TItem MyItem { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.ir.txt index 7ff82db78328c..59b90791116b4 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.ir.txt @@ -10,5 +10,3 @@ Component - (88:6,0 [36] x:\dir\subdir\Test\TestComponent.cshtml) - TestComponent ComponentTypeArgument - (110:6,22 [10] x:\dir\subdir\Test\TestComponent.cshtml) - TItem IntermediateToken - (110:6,22 [10] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - string - CSharpCode - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public TItem MyItem { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.mappings.txt index 2df174079650f..f83cc9c65fb1d 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWhitespace/TestComponent.mappings.txt @@ -13,14 +13,3 @@ Source Location: (110:6,22 [10] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (948:30,0 [10] ) | string | -Source Location: (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] - public TItem MyItem { get; set; } -| -Generated Location: (1200:41,0 [58] ) -| - [Parameter] - public TItem MyItem { get; set; } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.codegen.cs index 0018c48ace995..c33e78ecebdfd 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.codegen.cs @@ -37,16 +37,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. __builder.CloseComponent(); } #pragma warning restore 1998 -#nullable restore -#line (2,8)-(5,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] - public TItem MyItem { get; set; } - -#line default -#line hidden -#nullable disable - } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.decl.codegen.cs index a95703435fd3d..897877ac1a8c2 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.decl.codegen.cs @@ -2,11 +2,36 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,17) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (2,8)-(5,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] + public TItem MyItem { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.decl.ir.txt index cbfa0567b3baf..7d737f78dc4fd 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TItem + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TItem + CSharpCode - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public TItem MyItem { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..0e896623d3fbd --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.decl.mappings.txt @@ -0,0 +1,16 @@ +Source Location: (11:0,11 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem| +Generated Location: (474:16,0 [5] ) +|TItem| + +Source Location: (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] + public TItem MyItem { get; set; } +| +Generated Location: (704:26,0 [58] ) +| + [Parameter] + public TItem MyItem { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.ir.txt index 4469dfce76588..8c6db715c0bec 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.ir.txt @@ -10,5 +10,3 @@ Component - (88:6,0 [47] x:\dir\subdir\Test\TestComponent.cshtml) - TestComponent ComponentTypeArgument - (110:6,22 [21] x:\dir\subdir\Test\TestComponent.cshtml) - TItem IntermediateToken - (110:6,22 [21] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - TestComponent - CSharpCode - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public TItem MyItem { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.mappings.txt index 742d4fceb65df..b1220ece8da39 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithGenericType/TestComponent.mappings.txt @@ -13,14 +13,3 @@ Source Location: (110:6,22 [21] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (948:30,0 [21] ) |TestComponent| -Source Location: (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] - public TItem MyItem { get; set; } -| -Generated Location: (1211:41,0 [58] ) -| - [Parameter] - public TItem MyItem { get; set; } -| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.codegen.cs index 30bf3bf117af5..0622105f3d54f 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.codegen.cs @@ -36,16 +36,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. ); } #pragma warning restore 1998 -#nullable restore -#line (2,8)-(5,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] - public TItem MyItem { get; set; } - -#line default -#line hidden -#nullable disable - } } namespace __Blazor.Test.TestComponent diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.decl.codegen.cs index a95703435fd3d..897877ac1a8c2 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.decl.codegen.cs @@ -2,11 +2,36 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,17) "x:\dir\subdir\Test\TestComponent.cshtml" +TItem + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (2,8)-(5,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] + public TItem MyItem { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.decl.ir.txt index cbfa0567b3baf..7d737f78dc4fd 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TItem + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TItem + CSharpCode - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public TItem MyItem { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..0e896623d3fbd --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.decl.mappings.txt @@ -0,0 +1,16 @@ +Source Location: (11:0,11 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem| +Generated Location: (474:16,0 [5] ) +|TItem| + +Source Location: (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] + public TItem MyItem { get; set; } +| +Generated Location: (704:26,0 [58] ) +| + [Parameter] + public TItem MyItem { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.ir.txt index 4b2821f7a2e1b..899138ae55984 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.ir.txt @@ -10,8 +10,6 @@ Component - (88:6,0 [28] x:\dir\subdir\Test\TestComponent.cshtml) - TestComponent ComponentAttribute - (111:6,23 [1] x:\dir\subdir\Test\TestComponent.cshtml) - MyItem - MyItem - AttributeStructure.DoubleQuotes IntermediateToken - (111:6,23 [1] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - 1 - CSharpCode - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter]\n public TItem MyItem { get; set; }\n NamespaceDeclaration - - __Blazor.Test.TestComponent ClassDeclaration - - internal static - TypeInference - - ComponentTypeInferenceMethod - - __Blazor.Test.TestComponent.TypeInference - CreateTestComponent_0 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.mappings.txt index 130a3b53fcd56..7e419a9e5716f 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponentTypeUsageWithInference/TestComponent.mappings.txt @@ -8,24 +8,13 @@ Source Location: (111:6,23 [1] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (986:30,0 [1] ) |1| -Source Location: (25:1,7 [58] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] - public TItem MyItem { get; set; } -| -Generated Location: (1184:40,0 [58] ) -| - [Parameter] - public TItem MyItem { get; set; } -| - Source Location: (89:6,1 [13] x:\dir\subdir\Test\TestComponent.cshtml) |TestComponent| -Generated Location: (1639:57,45 [13] ) +Generated Location: (1449:47,45 [13] ) |TestComponent| Source Location: (103:6,15 [6] x:\dir\subdir\Test\TestComponent.cshtml) |MyItem| -Generated Location: (1841:61,0 [6] ) +Generated Location: (1651:51,0 [6] ) |MyItem| diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeExplicit/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeExplicit/TestComponent.decl.codegen.cs index 1eab8d07594d9..3acc48276168f 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeExplicit/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeExplicit/TestComponent.decl.codegen.cs @@ -2,9 +2,30 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; +#nullable restore +#line (2,2)-(2,12) "x:\dir\subdir\Test\TestComponent.cshtml" +using Test + +#nullable disable + ; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TChild + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { } diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeExplicit/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeExplicit/TestComponent.decl.ir.txt index a506bef700101..e048ccb817a0c 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeExplicit/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeExplicit/TestComponent.decl.ir.txt @@ -1,3 +1,9 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TChild + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + UsingDirective - (20:1,1 [10] x:\dir\subdir\Test\TestComponent.cshtml) - Test + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TChild diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeExplicit/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeExplicit/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..f269727572b31 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeExplicit/TestComponent.decl.mappings.txt @@ -0,0 +1,10 @@ +Source Location: (20:1,1 [10] x:\dir\subdir\Test\TestComponent.cshtml) +|using Test| +Generated Location: (372:12,0 [10] ) +|using Test| + +Source Location: (11:0,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TChild| +Generated Location: (595:22,0 [6] ) +|TChild| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.codegen.cs index 64b7d67bf5431..326a91e33db0a 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.codegen.cs @@ -50,16 +50,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. )); } #pragma warning restore 1998 -#nullable restore -#line (5,2)-(8,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] public TChild ChildItem { get; set; } - [Parameter] public EventCallback MyChildEvent { get; set; } - -#line default -#line hidden -#nullable disable - } } namespace __Blazor.Test.TestComponent diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.decl.codegen.cs index 1eab8d07594d9..4ea33fb4771db 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.decl.codegen.cs @@ -2,11 +2,42 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; +#nullable restore +#line (2,2)-(2,12) "x:\dir\subdir\Test\TestComponent.cshtml" +using Test + +#nullable disable + ; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,18) "x:\dir\subdir\Test\TestComponent.cshtml" +TChild + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { +#nullable restore +#line (5,2)-(8,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] public TChild ChildItem { get; set; } + [Parameter] public EventCallback MyChildEvent { get; set; } + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.decl.ir.txt index a506bef700101..e96671b404b82 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.decl.ir.txt @@ -1,3 +1,11 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TChild + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + UsingDirective - (20:1,1 [10] x:\dir\subdir\Test\TestComponent.cshtml) - Test + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TChild + CSharpCode - (97:4,1 [138] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (97:4,1 [138] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public TChild ChildItem { get; set; }\n [Parameter] public EventCallback MyChildEvent { get; set; }\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..d154ee94b679d --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.decl.mappings.txt @@ -0,0 +1,21 @@ +Source Location: (20:1,1 [10] x:\dir\subdir\Test\TestComponent.cshtml) +|using Test| +Generated Location: (372:12,0 [10] ) +|using Test| + +Source Location: (11:0,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TChild| +Generated Location: (595:22,0 [6] ) +|TChild| + +Source Location: (97:4,1 [138] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] public TChild ChildItem { get; set; } + [Parameter] public EventCallback MyChildEvent { get; set; } +| +Generated Location: (826:32,0 [138] ) +| + [Parameter] public TChild ChildItem { get; set; } + [Parameter] public EventCallback MyChildEvent { get; set; } +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.ir.txt index c61a5e11aaba6..e6dab41c9581d 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.ir.txt @@ -13,8 +13,6 @@ IntermediateToken - (51:2,19 [9] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ChildItem ComponentAttribute - (71:2,39 [12] x:\dir\subdir\Test\TestComponent.cshtml) - MyEvent - MyEvent - AttributeStructure.DoubleQuotes IntermediateToken - (71:2,39 [12] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - MyChildEvent - CSharpCode - (97:4,1 [138] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (97:4,1 [138] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public TChild ChildItem { get; set; }\n [Parameter] public EventCallback MyChildEvent { get; set; }\n NamespaceDeclaration - - __Blazor.Test.TestComponent ClassDeclaration - - internal static - TypeInference - - ComponentTypeInferenceMethod - - __Blazor.Test.TestComponent.TypeInference - CreateMyComponent_0 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.mappings.txt index f6c688ce3d6f2..ea2e8cf11a552 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/GenericComponent_GenericEventCallbackWithGenericTypeParameter_NestedTypeInference/TestComponent.mappings.txt @@ -18,29 +18,18 @@ Source Location: (71:2,39 [12] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (1343:44,0 [12] ) |MyChildEvent| -Source Location: (97:4,1 [138] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] public TChild ChildItem { get; set; } - [Parameter] public EventCallback MyChildEvent { get; set; } -| -Generated Location: (1553:54,0 [138] ) -| - [Parameter] public TChild ChildItem { get; set; } - [Parameter] public EventCallback MyChildEvent { get; set; } -| - Source Location: (33:2,1 [11] x:\dir\subdir\Test\TestComponent.cshtml) |MyComponent| -Generated Location: (2167:71,45 [11] ) +Generated Location: (1897:61,45 [11] ) |MyComponent| Source Location: (45:2,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) |Item| -Generated Location: (2365:75,0 [4] ) +Generated Location: (2095:65,0 [4] ) |Item| Source Location: (62:2,30 [7] x:\dir\subdir\Test\TestComponent.cshtml) |MyEvent| -Generated Location: (2613:84,0 [7] ) +Generated Location: (2343:74,0 [7] ) |MyEvent| diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective/TestComponent.decl.codegen.cs index 144c68968b7b6..5055d6b550573 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective/TestComponent.decl.codegen.cs @@ -2,9 +2,24 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent : +#nullable restore +#line (1,11)-(1,24) "x:\dir\subdir\Test\TestComponent.cshtml" +BaseComponent + +#line default +#line hidden +#nullable disable + #nullable disable { } diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective/TestComponent.decl.ir.txt index be84a3d3e1a71..c46bb3960d51d 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective/TestComponent.decl.ir.txt @@ -1,3 +1,8 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - BaseComponent - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..876e829a99a3b --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective/TestComponent.decl.mappings.txt @@ -0,0 +1,5 @@ +Source Location: (10:0,10 [13] x:\dir\subdir\Test\TestComponent.cshtml) +|BaseComponent| +Generated Location: (476:16,0 [13] ) +|BaseComponent| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType/TestComponent.decl.codegen.cs index 144c68968b7b6..8cd0663ada428 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType/TestComponent.decl.codegen.cs @@ -2,9 +2,24 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent : +#nullable restore +#line (1,11)-(1,33) "x:\dir\subdir\Test\TestComponent.cshtml" +BaseComponent + +#line default +#line hidden +#nullable disable + #nullable disable { } diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType/TestComponent.decl.ir.txt index be84a3d3e1a71..1497629047e71 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType/TestComponent.decl.ir.txt @@ -1,3 +1,8 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - BaseComponent - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..004cd42c09d0b --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType/TestComponent.decl.mappings.txt @@ -0,0 +1,5 @@ +Source Location: (10:0,10 [22] x:\dir\subdir\Test\TestComponent.cshtml) +|BaseComponent| +Generated Location: (476:16,0 [22] ) +|BaseComponent| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType_NullableDisabled/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType_NullableDisabled/TestComponent.decl.codegen.cs index 144c68968b7b6..8cd0663ada428 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType_NullableDisabled/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType_NullableDisabled/TestComponent.decl.codegen.cs @@ -2,9 +2,24 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent : +#nullable restore +#line (1,11)-(1,33) "x:\dir\subdir\Test\TestComponent.cshtml" +BaseComponent + +#line default +#line hidden +#nullable disable + #nullable disable { } diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType_NullableDisabled/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType_NullableDisabled/TestComponent.decl.ir.txt index be84a3d3e1a71..1497629047e71 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType_NullableDisabled/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType_NullableDisabled/TestComponent.decl.ir.txt @@ -1,3 +1,8 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - BaseComponent - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType_NullableDisabled/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType_NullableDisabled/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..004cd42c09d0b --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InheritsDirective_NullableReferenceType_NullableDisabled/TestComponent.decl.mappings.txt @@ -0,0 +1,5 @@ +Source Location: (10:0,10 [22] x:\dir\subdir\Test\TestComponent.cshtml) +|BaseComponent| +Generated Location: (476:16,0 [22] ) +|BaseComponent| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.codegen.cs index 8e493f86c2c04..4c42913c7441a 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.codegen.cs @@ -26,96 +26,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. __builder.AddMarkupContent(0, "
\r\n Content\r\n
"); } #pragma warning restore 1998 - [global::Microsoft.AspNetCore.Components.InjectAttribute] private -#nullable restore -#line (11,9)-(11,14) "x:\dir\subdir\Test\TestComponent.cshtml" -float - -#line default -#line hidden -#nullable disable - -#nullable restore -#line (11,15)-(11,21) "x:\dir\subdir\Test\TestComponent.cshtml" -Value5 - -#line default -#line hidden -#nullable disable - { get; set; } - = default!; - [global::Microsoft.AspNetCore.Components.InjectAttribute] private -#nullable restore -#line (5,9)-(5,15) "x:\dir\subdir\Test\TestComponent.cshtml" -double - -#line default -#line hidden -#nullable disable - -#nullable restore -#line (5,16)-(5,22) "x:\dir\subdir\Test\TestComponent.cshtml" -Value4 - -#line default -#line hidden -#nullable disable - { get; set; } - = default!; - [global::Microsoft.AspNetCore.Components.InjectAttribute] private -#nullable restore -#line (4,9)-(4,12) "x:\dir\subdir\Test\TestComponent.cshtml" -int - -#line default -#line hidden -#nullable disable - -#nullable restore -#line (4,13)-(4,19) "x:\dir\subdir\Test\TestComponent.cshtml" -Value3 - -#line default -#line hidden -#nullable disable - { get; set; } - = default!; - [global::Microsoft.AspNetCore.Components.InjectAttribute] private -#nullable restore -#line (3,15)-(3,28) "x:\dir\subdir\Test\TestComponent.cshtml" -StringBuilder - -#line default -#line hidden -#nullable disable - -#nullable restore -#line (3,38)-(3,44) "x:\dir\subdir\Test\TestComponent.cshtml" -Value2 - -#line default -#line hidden -#nullable disable - { get; set; } - = default!; - [global::Microsoft.AspNetCore.Components.InjectAttribute] private -#nullable restore -#line (2,9)-(2,15) "x:\dir\subdir\Test\TestComponent.cshtml" -string - -#line default -#line hidden -#nullable disable - -#nullable restore -#line (2,16)-(2,22) "x:\dir\subdir\Test\TestComponent.cshtml" -Value1 - -#line default -#line hidden -#nullable disable - { get; set; } - = default!; } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.decl.codegen.cs index 144c68968b7b6..0224f977478d4 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.decl.codegen.cs @@ -2,11 +2,114 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; +#nullable restore +#line (1,2)-(1,19) "x:\dir\subdir\Test\TestComponent.cshtml" +using System.Text + +#nullable disable + ; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { + [global::Microsoft.AspNetCore.Components.InjectAttribute] private +#nullable restore +#line (11,9)-(11,14) "x:\dir\subdir\Test\TestComponent.cshtml" +float + +#line default +#line hidden +#nullable disable + +#nullable restore +#line (11,15)-(11,21) "x:\dir\subdir\Test\TestComponent.cshtml" +Value5 + +#line default +#line hidden +#nullable disable + { get; set; } + = default!; + [global::Microsoft.AspNetCore.Components.InjectAttribute] private +#nullable restore +#line (5,9)-(5,15) "x:\dir\subdir\Test\TestComponent.cshtml" +double + +#line default +#line hidden +#nullable disable + +#nullable restore +#line (5,16)-(5,22) "x:\dir\subdir\Test\TestComponent.cshtml" +Value4 + +#line default +#line hidden +#nullable disable + { get; set; } + = default!; + [global::Microsoft.AspNetCore.Components.InjectAttribute] private +#nullable restore +#line (4,9)-(4,12) "x:\dir\subdir\Test\TestComponent.cshtml" +int + +#line default +#line hidden +#nullable disable + +#nullable restore +#line (4,13)-(4,19) "x:\dir\subdir\Test\TestComponent.cshtml" +Value3 + +#line default +#line hidden +#nullable disable + { get; set; } + = default!; + [global::Microsoft.AspNetCore.Components.InjectAttribute] private +#nullable restore +#line (3,15)-(3,28) "x:\dir\subdir\Test\TestComponent.cshtml" +StringBuilder + +#line default +#line hidden +#nullable disable + +#nullable restore +#line (3,38)-(3,44) "x:\dir\subdir\Test\TestComponent.cshtml" +Value2 + +#line default +#line hidden +#nullable disable + { get; set; } + = default!; + [global::Microsoft.AspNetCore.Components.InjectAttribute] private +#nullable restore +#line (2,9)-(2,15) "x:\dir\subdir\Test\TestComponent.cshtml" +string + +#line default +#line hidden +#nullable disable + +#nullable restore +#line (2,16)-(2,22) "x:\dir\subdir\Test\TestComponent.cshtml" +Value1 + +#line default +#line hidden +#nullable disable + { get; set; } + = default!; } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.decl.ir.txt index be84a3d3e1a71..7543002302842 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.decl.ir.txt @@ -1,3 +1,14 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + UsingDirective - (1:0,1 [17] x:\dir\subdir\Test\TestComponent.cshtml) - System.Text + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - + ComponentInject - + ComponentInject - + ComponentInject - + ComponentInject - + ComponentInject - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..48ff23f52725e --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.decl.mappings.txt @@ -0,0 +1,55 @@ +Source Location: (1:0,1 [17] x:\dir\subdir\Test\TestComponent.cshtml) +|using System.Text| +Generated Location: (372:12,0 [17] ) +|using System.Text| + +Source Location: (172:10,8 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|float| +Generated Location: (764:25,0 [5] ) +|float| + +Source Location: (178:10,14 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|Value5| +Generated Location: (916:33,0 [6] ) +|Value5| + +Source Location: (117:4,8 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|double| +Generated Location: (1177:43,0 [6] ) +|double| + +Source Location: (124:4,15 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|Value4| +Generated Location: (1328:51,0 [6] ) +|Value4| + +Source Location: (96:3,8 [3] x:\dir\subdir\Test\TestComponent.cshtml) +|int| +Generated Location: (1589:61,0 [3] ) +|int| + +Source Location: (100:3,12 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|Value3| +Generated Location: (1737:69,0 [6] ) +|Value3| + +Source Location: (57:2,14 [13] x:\dir\subdir\Test\TestComponent.cshtml) +|StringBuilder| +Generated Location: (1999:79,0 [13] ) +|StringBuilder| + +Source Location: (80:2,37 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|Value2| +Generated Location: (2157:87,0 [6] ) +|Value2| + +Source Location: (28:1,8 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|string| +Generated Location: (2418:97,0 [6] ) +|string| + +Source Location: (35:1,15 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|Value1| +Generated Location: (2569:105,0 [6] ) +|Value1| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.ir.txt index e2855d490a7a7..c40410a1c1781 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.ir.txt @@ -9,8 +9,3 @@ ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - MethodDeclaration - - protected override - void - BuildRenderTree MarkupBlock - -
\n Content\n
- ComponentInject - - ComponentInject - - ComponentInject - - ComponentInject - - ComponentInject - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.mappings.txt index 8d4e192255748..5af9649b92445 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/InjectDirective/TestComponent.mappings.txt @@ -3,53 +3,3 @@ Generated Location: (372:12,0 [17] ) |using System.Text| -Source Location: (172:10,8 [5] x:\dir\subdir\Test\TestComponent.cshtml) -|float| -Generated Location: (1068:31,0 [5] ) -|float| - -Source Location: (178:10,14 [6] x:\dir\subdir\Test\TestComponent.cshtml) -|Value5| -Generated Location: (1220:39,0 [6] ) -|Value5| - -Source Location: (117:4,8 [6] x:\dir\subdir\Test\TestComponent.cshtml) -|double| -Generated Location: (1481:49,0 [6] ) -|double| - -Source Location: (124:4,15 [6] x:\dir\subdir\Test\TestComponent.cshtml) -|Value4| -Generated Location: (1632:57,0 [6] ) -|Value4| - -Source Location: (96:3,8 [3] x:\dir\subdir\Test\TestComponent.cshtml) -|int| -Generated Location: (1893:67,0 [3] ) -|int| - -Source Location: (100:3,12 [6] x:\dir\subdir\Test\TestComponent.cshtml) -|Value3| -Generated Location: (2041:75,0 [6] ) -|Value3| - -Source Location: (57:2,14 [13] x:\dir\subdir\Test\TestComponent.cshtml) -|StringBuilder| -Generated Location: (2303:85,0 [13] ) -|StringBuilder| - -Source Location: (80:2,37 [6] x:\dir\subdir\Test\TestComponent.cshtml) -|Value2| -Generated Location: (2461:93,0 [6] ) -|Value2| - -Source Location: (28:1,8 [6] x:\dir\subdir\Test\TestComponent.cshtml) -|string| -Generated Location: (2722:103,0 [6] ) -|string| - -Source Location: (35:1,15 [6] x:\dir\subdir\Test\TestComponent.cshtml) -|Value1| -Generated Location: (2873:111,0 [6] ) -|Value1| - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam/TestComponent.decl.codegen.cs index c4e3c7388638a..009774174f791 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam/TestComponent.decl.codegen.cs @@ -2,9 +2,24 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,13) "x:\dir\subdir\Test\TestComponent.cshtml" +T + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { } diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam/TestComponent.decl.ir.txt index b6e48cbf4180e..6859d88569273 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam/TestComponent.decl.ir.txt @@ -1,3 +1,8 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - T + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - T diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..d644a80f59001 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam/TestComponent.decl.mappings.txt @@ -0,0 +1,5 @@ +Source Location: (11:0,11 [1] x:\dir\subdir\Test\TestComponent.cshtml) +|T| +Generated Location: (474:16,0 [1] ) +|T| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_First/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_First/TestComponent.decl.codegen.cs index c4e3c7388638a..39122beff5892 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_First/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_First/TestComponent.decl.codegen.cs @@ -2,9 +2,24 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (2,12)-(2,13) "x:\dir\subdir\Test\TestComponent.cshtml" +T + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { } diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_First/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_First/TestComponent.decl.ir.txt index b6e48cbf4180e..6859d88569273 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_First/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_First/TestComponent.decl.ir.txt @@ -1,3 +1,8 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - T + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - T diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_First/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_First/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..3d2a803be1840 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_First/TestComponent.decl.mappings.txt @@ -0,0 +1,5 @@ +Source Location: (89:1,11 [1] x:\dir\subdir\Test\TestComponent.cshtml) +|T| +Generated Location: (474:16,0 [1] ) +|T| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_Razor9/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_Razor9/TestComponent.decl.codegen.cs index c4e3c7388638a..009774174f791 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_Razor9/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_Razor9/TestComponent.decl.codegen.cs @@ -2,9 +2,24 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,13) "x:\dir\subdir\Test\TestComponent.cshtml" +T + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { } diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_Razor9/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_Razor9/TestComponent.decl.ir.txt index b6e48cbf4180e..6859d88569273 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_Razor9/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_Razor9/TestComponent.decl.ir.txt @@ -1,3 +1,8 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - T + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - T diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_Razor9/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_Razor9/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..d644a80f59001 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_Directive_WithTypeParam_Razor9/TestComponent.decl.mappings.txt @@ -0,0 +1,5 @@ +Source Location: (11:0,11 [1] x:\dir\subdir\Test\TestComponent.cshtml) +|T| +Generated Location: (474:16,0 [1] ) +|T| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.codegen.cs index 058dc02da3e2b..ec8798e785ab8 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.codegen.cs @@ -52,15 +52,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. ); } #pragma warning restore 1998 -#nullable restore -#line (6,2)-(8,1) "x:\dir\subdir\Test\TestComponent.cshtml" - - [Parameter] public TRenderMode RenderModeParam { get; set;} - -#line default -#line hidden -#nullable disable - } } namespace __Blazor.Test.TestComponent diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.decl.codegen.cs index 9335bd4948fb4..588aa41f88a99 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.decl.codegen.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.decl.codegen.cs @@ -2,11 +2,42 @@ #pragma warning disable 1591 namespace Test { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class TestComponent + public partial class TestComponent< +#nullable restore +#line (1,12)-(1,23) "x:\dir\subdir\Test\TestComponent.cshtml" +TRenderMode + +#line default +#line hidden +#nullable disable + > : global::Microsoft.AspNetCore.Components.ComponentBase +#nullable restore +#line (1,24)-(1,96) "x:\dir\subdir\Test\TestComponent.cshtml" +where TRenderMode : Microsoft.AspNetCore.Components.IComponentRenderMode + +#line default +#line hidden +#nullable disable #nullable disable { +#nullable restore +#line (6,2)-(8,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] public TRenderMode RenderModeParam { get; set;} + +#line default +#line hidden +#nullable disable + } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.decl.ir.txt index c210f8e24afb2..e28cae0395c8d 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.decl.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.decl.ir.txt @@ -1,3 +1,10 @@ Document - NamespaceDeclaration - - Test - ClassDeclaration - - public partial - TestComponent - - - TRenderMode + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - - TRenderMode + CSharpCode - (241:5,1 [67] x:\dir\subdir\Test\TestComponent.cshtml) + LazyIntermediateToken - (241:5,1 [67] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public TRenderMode RenderModeParam { get; set;}\n diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.decl.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.decl.mappings.txt new file mode 100644 index 0000000000000..5f77ef886539f --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.decl.mappings.txt @@ -0,0 +1,19 @@ +Source Location: (11:0,11 [11] x:\dir\subdir\Test\TestComponent.cshtml) +|TRenderMode| +Generated Location: (474:16,0 [11] ) +|TRenderMode| + +Source Location: (23:0,23 [72] x:\dir\subdir\Test\TestComponent.cshtml) +|where TRenderMode : Microsoft.AspNetCore.Components.IComponentRenderMode| +Generated Location: (682:24,0 [72] ) +|where TRenderMode : Microsoft.AspNetCore.Components.IComponentRenderMode| + +Source Location: (241:5,1 [67] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] public TRenderMode RenderModeParam { get; set;} +| +Generated Location: (916:33,0 [67] ) +| + [Parameter] public TRenderMode RenderModeParam { get; set;} +| + diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.ir.txt index 941e9802c060c..38668154302b9 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.ir.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.ir.txt @@ -12,8 +12,6 @@ IntermediateToken - (127:2,28 [15] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - RenderModeParam ComponentAttribute - (161:2,62 [64] x:\dir\subdir\Test\TestComponent.cshtml) - RenderModeParam - RenderModeParam - AttributeStructure.DoubleQuotes IntermediateToken - (161:2,62 [64] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer - CSharpCode - (241:5,1 [67] x:\dir\subdir\Test\TestComponent.cshtml) - LazyIntermediateToken - (241:5,1 [67] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] public TRenderMode RenderModeParam { get; set;}\n NamespaceDeclaration - - __Blazor.Test.TestComponent ClassDeclaration - - internal static - TypeInference - - ComponentTypeInferenceMethod - - __Blazor.Test.TestComponent.TypeInference - CreateTestComponent_0 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.mappings.txt index c01a444eb6175..2ff6362335c40 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.mappings.txt +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/RenderMode_With_TypeInference/TestComponent.mappings.txt @@ -18,22 +18,13 @@ Source Location: (161:2,62 [64] x:\dir\subdir\Test\TestComponent.cshtml) Generated Location: (1369:46,0 [64] ) |Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer| -Source Location: (241:5,1 [67] x:\dir\subdir\Test\TestComponent.cshtml) -| - [Parameter] public TRenderMode RenderModeParam { get; set;} -| -Generated Location: (1630:56,0 [67] ) -| - [Parameter] public TRenderMode RenderModeParam { get; set;} -| - Source Location: (100:2,1 [13] x:\dir\subdir\Test\TestComponent.cshtml) |TestComponent| -Generated Location: (2273:73,45 [13] ) +Generated Location: (2074:64,45 [13] ) |TestComponent| Source Location: (144:2,45 [15] x:\dir\subdir\Test\TestComponent.cshtml) |RenderModeParam| -Generated Location: (2487:77,0 [15] ) +Generated Location: (2288:68,0 [15] ) |RenderModeParam| diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.builder.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.builder.txt new file mode 100644 index 0000000000000..07e83d451ccf2 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.builder.txt @@ -0,0 +1,2 @@ +[0000] OpenComponent() +[0001] AddComponentParameter(nameof(global::Test.Image. Source ), global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck(Assets[@"images/logo.png"])) \ No newline at end of file diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.codegen.cs new file mode 100644 index 0000000000000..3e3fe0518c3a5 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.codegen.cs @@ -0,0 +1,35 @@ +// +#pragma warning disable 1591 +namespace Test +{ + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default + #line hidden + #nullable restore + public partial class TestComponent : global::Microsoft.AspNetCore.Components.ComponentBase + #nullable disable + { + #pragma warning disable 1998 + protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder) + { + __builder.OpenComponent(0); + __builder.AddComponentParameter(1, nameof(global::Test.Image. +#nullable restore +#line (1,8)-(1,14) "x:\dir\subdir\Test\TestComponent.cshtml" +Source + +#line default +#line hidden +#nullable disable + ), global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck(Assets[@"images/logo.png"])); + __builder.CloseComponent(); + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.decl.codegen.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.decl.codegen.cs new file mode 100644 index 0000000000000..ca1dfaa54283e --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.decl.codegen.cs @@ -0,0 +1,19 @@ +// +#pragma warning disable 1591 +namespace Test +{ + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default + #line hidden + #nullable restore + public partial class TestComponent : global::Microsoft.AspNetCore.Components.ComponentBase + #nullable disable + { + } +} +#pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.decl.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.decl.ir.txt new file mode 100644 index 0000000000000..1c85b5f8fd44f --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.decl.ir.txt @@ -0,0 +1,8 @@ +Document - + NamespaceDeclaration - - Test + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.ir.txt new file mode 100644 index 0000000000000..618d7916d13e6 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.ir.txt @@ -0,0 +1,13 @@ +Document - + NamespaceDeclaration - - Test + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - + MethodDeclaration - - protected override - void - BuildRenderTree + Component - (0:0,0 [36] x:\dir\subdir\Test\TestComponent.cshtml) - Image + ComponentAttribute - (15:0,15 [17] x:\dir\subdir\Test\TestComponent.cshtml) - Source - Source - AttributeStructure.DoubleQuotes + CSharpExpression - + IntermediateToken - - CSharp - Assets[@"images/logo.png"] diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.mappings.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.mappings.txt new file mode 100644 index 0000000000000..bf1ff4713e19a --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_InheritedAssetPath/TestComponent.mappings.txt @@ -0,0 +1,10 @@ +Source Location: (1:0,1 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|Image| +Generated Location: (703:19,49 [5] ) +|Image| + +Source Location: (7:0,7 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|Source| +Generated Location: (871:23,0 [6] ) +|Source| + diff --git a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/Utf8SupportMap.cs b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/Utf8SupportMap.cs index 01ce83facfb55..23c3ec9ac9765 100644 --- a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/Utf8SupportMap.cs +++ b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/Utf8SupportMap.cs @@ -27,9 +27,11 @@ namespace Microsoft.CodeAnalysis.Razor.Compiler.CSharp; internal sealed class Utf8SupportMap : IEquatable { /// - /// Information about an @inherits directive extracted from a parsed document. + /// Information about an @inherits directive extracted from a parsed document. A reference type so + /// the incremental-generator driver's instantiations over it share canonical code rather than JITting a + /// dedicated value-type instantiation. /// - internal readonly record struct InheritsInfo(string FilePath, string BaseTypeName, ImmutableArray Usings); + internal sealed record InheritsInfo(string FilePath, string BaseTypeName, ImmutableArray Usings); public static readonly Utf8SupportMap Empty = new( ImmutableSortedDictionary.Empty, diff --git a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorMarkupSplitPhase.cs b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorMarkupSplitPhase.cs index dd30825da61da..a3ddedfde4a9d 100644 --- a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorMarkupSplitPhase.cs +++ b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorMarkupSplitPhase.cs @@ -24,12 +24,9 @@ namespace Microsoft.AspNetCore.Razor.Language; /// /// Running before tag-helper resolution is the point: the decl half is markup-free and depends only on /// user source, so tag-helper discovery can consume it early and stay incremental. A component with no -/// class-body markup still splits -- its whole body is the decl, its render method the impl. A component -/// whose body has markup the analysis can't route safely (a markup property, an unsupported member, an -/// @inject, a preprocessor directive, or unrecoverable syntax), or one carrying a header/arity -/// directive (@inherits/@implements/@typeparam) -- whose base type, interfaces, or -/// type parameters a move-based partition would leave duplicated on the impl header -- is left as a single -/// document for the fallback lowering. +/// class-body markup still splits -- its whole body is the decl, its render method the impl. Only a body +/// with markup the analysis can't route safely (a markup property, an unsupported member, a preprocessor +/// directive, or unrecoverable syntax) falls back to single-document lowering. /// internal sealed class DefaultRazorMarkupSplitPhase : RazorEnginePhaseBase { @@ -77,15 +74,6 @@ protected override RazorCodeDocument ExecuteCore(RazorCodeDocument codeDocument, return RouteToFallbackDiscovery(shellDecl: null); } - // A header/arity directive (@inherits/@implements/@typeparam) puts a base type, interfaces, or - // type parameters on the class header. A move-based partition leaves that header on the impl half - // as well, so combining one with class-body markup would emit the header on both partials; such a - // document lowers as a single file instead and its descriptor comes from fallback discovery. - if (HasUnsplittableDocumentDirective(documentNode)) - { - return RouteToFallbackDiscovery(BuildStubDeclDocument(documentNode, primaryNamespace, primaryClass)); - } - // Decide the split over the classified class body. Only an unroutable body (fallback) stays a // single document; NoSplit and SplitPlan both produce a decl. var decision = MarkupSplitter.Split(primaryClass, renderMethod, codeDocument.ParserOptions); @@ -96,6 +84,10 @@ protected override RazorCodeDocument ExecuteCore(RazorCodeDocument codeDocument, var plan = decision as SplitDecision.SplitPlan; + // @inherits/@implements/@typeparam land on the class header, and both partials repeat it (C# allows + // that). Keeping it on the impl is deliberate: the compiler flags unimplemented base members there, + // so the ImplementInterface / ImplementAbstractClass code actions keep targeting the impl partial. + // BuildDeclDocument captures the decl's view of the class body first because MakeImplInPlace then // rewrites that shared primary class in place. The decl subtree keeps its leaf nodes by reference, // so DefaultRazorDeclCSharpLoweringPhase lowers it while those nodes still hold their classified @@ -271,7 +263,7 @@ private static DocumentIntermediateNode BuildDeclDocument( // Rewrites the primary class into the impl half in place: keep the render method and compiler- // synthesized helpers, drop the decl-only surface, and (for a split plan) append the markup-bearing - // pieces lifted from the class body. + // pieces lifted from the class body. The class header stays, duplicated with the decl. private static void MakeImplInPlace( ClassDeclarationIntermediateNode primaryClass, MethodDeclarationIntermediateNode renderMethod, @@ -308,21 +300,6 @@ private static void MakeImplInPlace( } } - // True if the document carries a header/arity directive (@inherits/@implements/@typeparam). Walks - // descendants because classification can nest these directives under the namespace or class. - private static bool HasUnsplittableDocumentDirective(DocumentIntermediateNode documentNode) - { - foreach (var directive in documentNode.FindDescendantNodes()) - { - if (directive.DirectiveName is "inherits" or "implements" or "typeparam") - { - return true; - } - } - - return false; - } - // Class-decoration nodes (@layout -> [Layout], @attribute -> [...], @page -> [Route]) lower to // namespace- or document-level nodes that decorate the class. They are the decl half's public surface // and are shared into the decl subtree; the same node kept in the impl half decorates the impl partial diff --git a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultTagHelperResolutionPhase.cs b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultTagHelperResolutionPhase.cs index 5932ffe6a7f06..574c93e7ad6c5 100644 --- a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultTagHelperResolutionPhase.cs +++ b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultTagHelperResolutionPhase.cs @@ -24,8 +24,6 @@ namespace Microsoft.AspNetCore.Razor.Language; /// internal partial class DefaultTagHelperResolutionPhase : RazorEnginePhaseBase { - private TagHelperResolver _resolver; - /// /// Entry point: resolves all unresolved nodes /// in the IR tree. For each, matches against tag helper bindings and either converts to a @@ -50,7 +48,7 @@ protected override RazorCodeDocument ExecuteCore(RazorCodeDocument codeDocument, // Choose resolver based on file kind and language version. Component features // (MarkupElementIntermediateNode, RZ10012 diagnostics) require Version_3_0+ because // the ComponentDocumentClassifierPass is only registered at that version. - _resolver = (codeDocument.FileKind.IsComponent() || codeDocument.FileKind.IsComponentImport()) + TagHelperResolver resolver = (codeDocument.FileKind.IsComponent() || codeDocument.FileKind.IsComponentImport()) && parserOptions.LanguageVersion >= RazorLanguageVersion.Version_3_0 ? new ComponentTagHelperResolver() : new LegacyTagHelperResolver(); @@ -58,7 +56,7 @@ protected override RazorCodeDocument ExecuteCore(RazorCodeDocument codeDocument, if (tagHelperContext == null || tagHelperContext.TagHelpers is []) { // No tag helpers discovered - unwrap all UnresolvedElement nodes to their fallback. - UnwrapAllElements(documentNode, documentNode); + UnwrapAllElements(documentNode, resolver, documentNode); // Still need to set referenced tag helpers for downstream phases. return codeDocument.WithReferencedTagHelpers([]); @@ -69,7 +67,7 @@ protected override RazorCodeDocument ExecuteCore(RazorCodeDocument codeDocument, using var usedHelpers = new TagHelperCollection.Builder(); var sourceDocument = codeDocument.Source; - var context = new ResolutionContext(sourceDocument, documentNode); + var context = new ResolutionContext(sourceDocument, documentNode, resolver); ResolveElements(documentNode, binder, prefix, usedHelpers, in context); // Add tag helper descriptor validation diagnostics (e.g. RZ3003). @@ -95,11 +93,16 @@ private readonly struct ResolutionContext { public readonly RazorSourceDocument SourceDocument; public readonly DocumentIntermediateNode DocumentNode; + public readonly TagHelperResolver Resolver; - public ResolutionContext(RazorSourceDocument sourceDocument, DocumentIntermediateNode documentNode) + public ResolutionContext( + RazorSourceDocument sourceDocument, + DocumentIntermediateNode documentNode, + TagHelperResolver resolver) { SourceDocument = sourceDocument; DocumentNode = documentNode; + Resolver = resolver; } } @@ -160,7 +163,7 @@ private TagHelperIntermediateNode ResolveElement( { TryAddMalformedEndTagDiagnostic(elementNode, tagName, binder, attributes, parent, tagHelperParent, prefix); - _resolver.ConvertToPlainElement(parent, index, elementNode); + context.Resolver.ConvertToPlainElement(parent, index, elementNode); return null; } @@ -264,7 +267,7 @@ private TagHelperIntermediateNode ResolveElement( // Add resolver-specific diagnostics (e.g. RZ10012 for component-like elements, // case mismatch between start/end tags). - _resolver.AddMatchedElementDiagnostics(tagHelperNode, elementNode, binding, in context); + context.Resolver.AddMatchedElementDiagnostics(tagHelperNode, elementNode, binding, in context); // Check if resolved tag name is a void element (handles prefixed elements like th:input). var isResolvedVoidElement = elementNode.IsVoidElement || Legacy.ParserHelpers.VoidElements.Contains(resolvedTagName); @@ -281,7 +284,7 @@ private TagHelperIntermediateNode ResolveElement( // Build body and attributes. var bodyNode = new TagHelperBodyIntermediateNode(); - _resolver.BuildTagHelper(tagHelperNode, bodyNode, elementNode, binding, context.SourceDocument, in context); + context.Resolver.BuildTagHelper(tagHelperNode, bodyNode, elementNode, binding, context.SourceDocument, in context); return (tagHelperNode, bodyNode); } @@ -445,12 +448,12 @@ private void ConvertToPlainElementAndResolve( bool emitDiagnostics = true) { var childCountBefore = parent.Children.Count; - _resolver.ConvertToPlainElement(parent, index, elementNode); + context.Resolver.ConvertToPlainElement(parent, index, elementNode); var resultCount = parent.Children.Count - childCountBefore + 1; // +1 because the original was removed if (emitDiagnostics && resultCount > 0) { - _resolver.AddUnmatchedElementDiagnostic(parent.Children[index], elementNode, context.DocumentNode); + context.Resolver.AddUnmatchedElementDiagnostic(parent.Children[index], elementNode, context.DocumentNode); } for (var j = index + resultCount - 1; j >= index; j--) @@ -785,7 +788,10 @@ private static void FlattenToDirectCSharpTokens(IntermediateNode source, Interme /// resolved by tag helper matching. Converts each to a plain element using the resolver. /// Recursively processes the tree to handle nested elements. /// - private void UnwrapAllElements(IntermediateNode node, DocumentIntermediateNode documentNode = null) + private static void UnwrapAllElements( + IntermediateNode node, + TagHelperResolver resolver, + DocumentIntermediateNode documentNode = null) { if (node is DocumentIntermediateNode doc) { @@ -795,17 +801,17 @@ private void UnwrapAllElements(IntermediateNode node, DocumentIntermediateNode d for (var i = node.Children.Count - 1; i >= 0; i--) { var child = node.Children[i]; - UnwrapAllElements(child, documentNode); + UnwrapAllElements(child, resolver, documentNode); if (child is UnresolvedElementIntermediateNode elementNode) { var countBefore = node.Children.Count; - _resolver.ConvertToPlainElement(node, i, elementNode); + resolver.ConvertToPlainElement(node, i, elementNode); var resultCount = node.Children.Count - countBefore + 1; if (resultCount > 0) { - _resolver.AddUnmatchedElementDiagnostic(node.Children[i], elementNode, documentNode); + resolver.AddUnmatchedElementDiagnostic(node.Children[i], elementNode, documentNode); } } } diff --git a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/MarkupSplitter.Classify.cs b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/MarkupSplitter.Classify.cs index 8c3f9d3c4ecc8..712c771bd70d6 100644 --- a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/MarkupSplitter.Classify.cs +++ b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/MarkupSplitter.Classify.cs @@ -133,16 +133,24 @@ private static ImmutableArray RouteMembers( pieceBuilders[i] = []; } + // Decl-surface declarations (@inject) carry no source text -- only a zero-length span -- and always + // belong in the decl half, so collect them apart from the per-member routing. + var declSurface = new List(); + foreach (var child in analysis.Children) { if (child.Node is CSharpCodeIntermediateNode csharp) { RouteCSharpChild(csharp, child, memberSpans, pieceBuilders); } + else if (IsDeclSurfaceDeclaration(child.Node)) + { + declSurface.Add(child.Node); + } else { - // A markup marker or a zero-length synthesized declaration lives entirely inside one - // member; route the original node there by reference (keeping its source mappings). + // A markup marker lives entirely inside one member; route the original node there by + // reference (keeping its source mappings). AllMarkupCovered guarantees an owner. var owner = FindMemberIndex(memberSpans, child.Start); if (owner >= 0) { @@ -151,7 +159,7 @@ private static ImmutableArray RouteMembers( } } - var result = ImmutableArray.CreateBuilder(memberSpans.Length); + var result = ImmutableArray.CreateBuilder(memberSpans.Length + (declSurface.Count > 0 ? 1 : 0)); for (var i = 0; i < memberSpans.Length; i++) { var pieces = pieceBuilders[i].ToImmutableArray(); @@ -163,6 +171,13 @@ private static ImmutableArray RouteMembers( : new RoutedMember(declPieces: pieces, implPieces: [])); } + // Emit the decl-surface declarations as a final decl-only member so BuildDeclDocument places them + // in the decl half (and MakeImplInPlace keeps them out of the impl). + if (declSurface.Count > 0) + { + result.Add(new RoutedMember(declPieces: [.. declSurface], implPieces: [])); + } + return result.ToImmutable(); } diff --git a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/MarkupSplitter.cs b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/MarkupSplitter.cs index 36d47d37db6c5..224b98ee0ac39 100644 --- a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/MarkupSplitter.cs +++ b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/MarkupSplitter.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Immutable; +using Microsoft.AspNetCore.Razor.Language.Components; using Microsoft.AspNetCore.Razor.Language.Extensions; using Microsoft.AspNetCore.Razor.Language.Intermediate; using Microsoft.AspNetCore.Razor.PooledObjects; @@ -141,16 +142,18 @@ public static bool HasClassBodyMarkup( /// missing a markup node would let it leak into the resolution-free decl half. /// /// - /// This is the deliberately over-eager gate classifier. It can flag a non-markup extension - /// node (an @inject) as "markup"; that only causes to run, which then sees - /// the node isn't a kind it can route () and falls back. Routing - /// itself uses the precise allow-list , never this predicate. + /// The deliberately over-eager gate: an unrecognized node counts as markup, which only makes + /// run and then fall back if it can't route the node; routing itself uses the + /// precise allow-list . @inject + /// () is excluded -- it is a markup-free injected property, + /// so a component whose only non-C# member is an @inject splits. /// internal static bool IsClassBodyMarkup(IntermediateNode node) => node is not (CSharpCodeIntermediateNode or FieldDeclarationIntermediateNode or PropertyDeclarationIntermediateNode or - MethodDeclarationIntermediateNode); + MethodDeclarationIntermediateNode or + ComponentInjectIntermediateNode); /// /// The precise allow-list of markup intermediate node kinds the splitter knows how to route to the @@ -172,12 +175,20 @@ MarkupBlockIntermediateNode or HtmlContentIntermediateNode; /// - /// A class-body node the splitter can route: raw C# text (which stays in decl or lifts to impl with - /// its member) or a recognized markup node (which lifts to impl). Any other kind -- a structured or - /// extension member such as @inject -- means the file can't be split and must fall back. + /// A class-body node the splitter can route: raw C# text, a recognized markup node, or a decl-surface + /// declaration like @inject (see ). Anything else forces a + /// fallback. /// internal static bool IsSupportedClassBodyNode(IntermediateNode node) - => node is CSharpCodeIntermediateNode || IsMarkupNode(node); + => node is CSharpCodeIntermediateNode || IsMarkupNode(node) || IsDeclSurfaceDeclaration(node); + + /// + /// A markup-free declaration synthesized as its own class-body child -- an @inject, which lowers + /// to an [Inject] property. It has no source text (only a zero-length analysis span), so routing + /// always places it in the decl half. + /// + internal static bool IsDeclSurfaceDeclaration(IntermediateNode node) + => node is ComponentInjectIntermediateNode; /// /// The ordered user-authored class-body children -- everything that isn't the render method or a diff --git a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelpers/Producers/ComponentTagHelperProducer.cs b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelpers/Producers/ComponentTagHelperProducer.cs index cc487fb556a4a..a00d1dccafc79 100644 --- a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelpers/Producers/ComponentTagHelperProducer.cs +++ b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelpers/Producers/ComponentTagHelperProducer.cs @@ -313,7 +313,7 @@ private static void CreateProperty(Compilation compilation, TagHelperDescriptorB // [AssetPath] enables ~/ expansion only for string parameters. On any other type the // rewritten Assets[...] value wouldn't be assignable, so ignore the opt-in and warn. - if (property.GetAttributes().Any(static a => a.HasFullName(ComponentsApi.AssetPathAttribute.MetadataName))) + if (HasAssetPathAttribute(property)) { if (property.Type.SpecialType == SpecialType.System_String) { @@ -334,6 +334,19 @@ private static void CreateProperty(Compilation compilation, TagHelperDescriptorB } }); + static bool HasAssetPathAttribute(IPropertySymbol property) + { + for (IPropertySymbol? current = property; current is not null; current = current.OverriddenProperty) + { + if (current.GetAttributes().Any(static a => a.HasFullName(ComponentsApi.AssetPathAttribute.MetadataName))) + { + return true; + } + } + + return false; + } + static bool HasTypeParameter(ITypeSymbol type) { if (type is ITypeParameterSymbol) diff --git a/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests.csproj b/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests.csproj index cae0919ef6b72..bea6ddf5f2a35 100644 --- a/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests.csproj +++ b/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests.csproj @@ -33,7 +33,7 @@ - + diff --git a/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/RazorSourceGeneratorComponentTests.cs b/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/RazorSourceGeneratorComponentTests.cs index be0276d7b3638..5dc895570711e 100644 --- a/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/RazorSourceGeneratorComponentTests.cs +++ b/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/RazorSourceGeneratorComponentTests.cs @@ -215,7 +215,7 @@ @inject IServiceProvider ServiceProvider // Assert Assert.Empty(result.Diagnostics); - // Component1.razor (@inject -> fallback, shell decl + impl) = 2 + // Component1.razor (@inject -> split, decl + impl) = 2 Assert.Equal(2, result.GeneratedSources.Length); result.VerifyOutputsMatchBaseline(); } diff --git a/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/RazorSourceGeneratorTests.cs b/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/RazorSourceGeneratorTests.cs index 2f9f12fcf8f0d..15a44ec7f2201 100644 --- a/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/RazorSourceGeneratorTests.cs +++ b/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/RazorSourceGeneratorTests.cs @@ -721,19 +721,15 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. [ConditionalFact(typeof(IsEnglishLocal))] public async Task IncrementalCompilation_WhenFallbackComponentMarkupChanges_SlowDiscoveryStaysCached() { - // A fallback component (one the split can't partition -- here via @inherits) has its descriptor - // produced by slow discovery over the fallback decl trees. Its decl is markup-free and - // checksum-suppressed, so a markup-only edit leaves it byte-identical. Slow discovery must stay - // cached across that edit rather than re-parsing every fallback decl and re-discovering. + // A fallback component (one the split can't partition -- here a markup property, which is + // descriptor surface that can't move to the markup-free decl half) has its descriptor produced + // by slow discovery over the fallback decl trees. Its decl is checksum-suppressed and depends + // only on the class body, so an edit to the render markup leaves it byte-identical. Slow + // discovery must stay cached across that edit rather than re-parsing every fallback decl and + // re-discovering. var project = CreateTestProject(new() { - ["Shared/MyBase.razor"] = "@inherits MyApp.MyComponentBase\n

Base component

", - }, new() - { - ["MyComponentBase.cs"] = """ - namespace MyApp; - public class MyComponentBase : Microsoft.AspNetCore.Components.ComponentBase { } - """, + ["Shared/MyBase.razor"] = "@code {\n public Microsoft.AspNetCore.Components.RenderFragment Frag => @frag;\n}\n

Base component

", }); var compilation = await project.GetCompilationAsync(); var (driver, additionalTexts, _) = await GetDriverWithAdditionalTextAndProviderAsync(project, trackSteps: true); @@ -741,9 +737,9 @@ public class MyComponentBase : Microsoft.AspNetCore.Components.ComponentBase { } var result = RunGenerator(compilation!, ref driver); Assert.Empty(result.Diagnostics); - // Change only the markup of the fallback component. + // Change only the render markup of the fallback component. var updated = new TestAdditionalText("Shared/MyBase.razor", - SourceText.From("@inherits MyApp.MyComponentBase\n

Base component changed

", Encoding.UTF8)); + SourceText.From("@code {\n public Microsoft.AspNetCore.Components.RenderFragment Frag => @frag;\n}\n

Base component changed

", Encoding.UTF8)); driver = driver.ReplaceAdditionalText(additionalTexts.First(f => f.Path == updated.Path), updated); result = RunGenerator(compilation!, ref driver); diff --git a/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/TestFiles/RazorSourceGeneratorComponentTests/Inject/Shared/Component1_razor.decl.g.cs b/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/TestFiles/RazorSourceGeneratorComponentTests/Inject/Shared/Component1_razor.decl.g.cs index 62f33359d785a..52e5bd730688a 100644 --- a/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/TestFiles/RazorSourceGeneratorComponentTests/Inject/Shared/Component1_razor.decl.g.cs +++ b/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/TestFiles/RazorSourceGeneratorComponentTests/Inject/Shared/Component1_razor.decl.g.cs @@ -2,11 +2,36 @@ #pragma warning disable 1591 namespace MyApp.Shared { + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default #line hidden #nullable restore - public partial class Component1 + public partial class Component1 : global::Microsoft.AspNetCore.Components.ComponentBase #nullable disable { + [global::Microsoft.AspNetCore.Components.InjectAttribute] private +#nullable restore +#line (1,9)-(1,25) "Shared/Component1.razor" +IServiceProvider + +#line default +#line hidden +#nullable disable + +#nullable restore +#line (1,26)-(1,41) "Shared/Component1.razor" +ServiceProvider + +#line default +#line hidden +#nullable disable + { get; set; } + = default!; } } #pragma warning restore 1591 diff --git a/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/TestFiles/RazorSourceGeneratorComponentTests/Inject/Shared/Component1_razor.g.cs b/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/TestFiles/RazorSourceGeneratorComponentTests/Inject/Shared/Component1_razor.g.cs index 341701525c4c7..144d7c28e507f 100644 --- a/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/TestFiles/RazorSourceGeneratorComponentTests/Inject/Shared/Component1_razor.g.cs +++ b/src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/TestFiles/RazorSourceGeneratorComponentTests/Inject/Shared/Component1_razor.g.cs @@ -20,24 +20,6 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. { } #pragma warning restore 1998 - [global::Microsoft.AspNetCore.Components.InjectAttribute] private -#nullable restore -#line (1,9)-(1,25) "Shared/Component1.razor" -IServiceProvider - -#line default -#line hidden -#nullable disable - -#nullable restore -#line (1,26)-(1,41) "Shared/Component1.razor" -ServiceProvider - -#line default -#line hidden -#nullable disable - { get; set; } - = default!; } } #pragma warning restore 1591 diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.cs.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.cs.xlf index 2704d91a04e19..328d3af25f797 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.cs.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.cs.xlf @@ -9,39 +9,39 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Nově dostupné v hlavních prohlížečích{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (Standardní hodnoty od {0}) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ Široce dostupné v hlavních prohlížečích{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Atributy direktivy Blazor {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (a požadované atributy...) The term "req'd" is an abbreviation for "required" directive - directive + direktiva Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Vložení fragmentu kódu direktivy +[Tab] pro navigaci mezi elementy, [Enter] pro dokončení @@ -61,7 +61,7 @@ {0} Keyword - {0} Keyword + Klíčové slovo {0} @@ -71,7 +71,7 @@ MDN Reference - MDN Reference + Referenční informace k MDN Link text for MDN documentation URL in HTML completion tooltips @@ -91,7 +91,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + Aktivovat znovu dokončení… diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.de.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.de.xlf index 782294ed6037d..1c0ae27108fc8 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.de.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.de.xlf @@ -9,39 +9,39 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Neu verfügbar in gängigen Browsern{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (Basisplan seit {0}) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ In großen Browsern allgemein verfügbar{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Attribute für Blazor-Direktiven {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (und erforderliche Attribute) The term "req'd" is an abbreviation for "required" directive - directive + Direktive Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Fügen Sie einen Anweisungscodeschnipsel +[Registerkarte] ein, um zwischen Elementen zu navigieren, [EINGABETASTE], um den Vorgang abzuschließen @@ -61,7 +61,7 @@ {0} Keyword - {0} Keyword + {0}-Schlüsselwort @@ -71,7 +71,7 @@ MDN Reference - MDN Reference + MDN-Referenz Link text for MDN documentation URL in HTML completion tooltips @@ -91,7 +91,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "Abschlüsse erneut auslösen..." diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.es.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.es.xlf index d020d5a1c444d..238728c8b3f79 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.es.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.es.xlf @@ -9,39 +9,39 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Disponible recientemente en los principales exploradores{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (Línea base desde {0}) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ Ampliamente disponible en los exploradores principales{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Atributos de directiva de Blazor {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (y atributos req'd...) The term "req'd" is an abbreviation for "required" directive - directive + directiva Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Insertar un fragmento de código de directiva +[Tab] para navegar entre elementos, [Entrar] para completar @@ -61,7 +61,7 @@ {0} Keyword - {0} Keyword + {0} Palabra clave @@ -71,7 +71,7 @@ MDN Reference - MDN Reference + Referencia de MDN Link text for MDN documentation URL in HTML completion tooltips @@ -91,7 +91,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "Volver a desencadenar las finalizaciones..." diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.fr.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.fr.xlf index 936ce1b7c9014..8245361a3f4a8 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.fr.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.fr.xlf @@ -9,39 +9,39 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Nouveauté disponible sur les principaux navigateurs{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (Base de référence depuis {0}) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ Largement disponible sur les principaux navigateurs{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Attributs de directive Blazor {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (et attributs requis...) The term "req'd" is an abbreviation for "required" directive - directive + directif Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Insérer un extrait de code de directive +[Tab] pour naviguer entre les éléments, [Entrée] pour terminer @@ -61,7 +61,7 @@ {0} Keyword - {0} Keyword + Mot clé {0} @@ -71,7 +71,7 @@ MDN Reference - MDN Reference + Référence MDN Link text for MDN documentation URL in HTML completion tooltips @@ -91,7 +91,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + « Déclencher à nouveau les complétions... » diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.it.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.it.xlf index 55a2bc3353d76..73b0712e13c66 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.it.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.it.xlf @@ -9,39 +9,39 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Ora disponibile nei principali browser{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (Baseline dal {0}) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ Ampiamente disponibile nei principali browser{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Attributo per la direttiva Blazor {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (e gli attributi richiesti...) The term "req'd" is an abbreviation for "required" directive - directive + direttiva Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Consente di inserire un frammento di codice di direttiva +[TAB] per spostarsi tra gli elementi, [INVIO] per completare @@ -61,7 +61,7 @@ {0} Keyword - {0} Keyword + Parola chiave di {0} @@ -71,7 +71,7 @@ MDN Reference - MDN Reference + Riferimento notifica sulla ricezione del messaggio Link text for MDN documentation URL in HTML completion tooltips @@ -91,7 +91,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "Riattiva i completamenti..." diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.ja.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.ja.xlf index 0c20728da94fc..2c08d3caef3dc 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.ja.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.ja.xlf @@ -9,39 +9,39 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 主要なブラウザー間で新たに使用可能{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + ({0} 以降のベースライン) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ 主要なブラウザー間で広く利用できます{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Blazor ディレクティブ属性 {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (および必須属性...) The term "req'd" is an abbreviation for "required" directive - directive + ディレクティブ Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + ディレクティブ コード スニペットを挿入する +[Tab] を挿入して要素間を移動し、[Enter] を押して完了します @@ -61,7 +61,7 @@ {0} Keyword - {0} Keyword + {0} キーワード @@ -71,7 +71,7 @@ MDN Reference - MDN Reference + MDN リファレンス Link text for MDN documentation URL in HTML completion tooltips @@ -91,7 +91,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "再トリガーの完了..." diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.ko.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.ko.xlf index f881a532b60f0..a3bf39264b742 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.ko.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.ko.xlf @@ -9,39 +9,39 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 주요 브라우저에서 새로 사용 가능{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + ({0} 이후 기준) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ 주요 브라우저에서 널리 사용 가능{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Blazor 지시문 특성 {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0}(및 필수 특성...) The term "req'd" is an abbreviation for "required" directive - directive + 지시문 Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + 지시문 코드 조각 삽입 +요소 간에 탐색하려면 [Tab], 완료하려면 [Enter] @@ -61,7 +61,7 @@ {0} Keyword - {0} Keyword + {0} 키워드 @@ -71,7 +71,7 @@ MDN Reference - MDN Reference + MDN 참조 Link text for MDN documentation URL in HTML completion tooltips @@ -91,7 +91,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "완료된 항목 다시 트리거" diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.pl.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.pl.xlf index 2861324a34051..003813171f152 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.pl.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.pl.xlf @@ -9,39 +9,39 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Nowo dostępne w głównych przeglądarkach{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (Punkt odniesienia od {0}) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ Powszechnie dostępne w głównych przeglądarkach{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Atrybut dyrektywy Blazor {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (i atrybuty req'd...) The term "req'd" is an abbreviation for "required" directive - directive + dyrektywa Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Wstaw fragment kodu dyrektywy +klawisz [Tab], aby przechodzić pomiędzy elementami, klawisz [Enter], aby zakończyć @@ -61,7 +61,7 @@ {0} Keyword - {0} Keyword + Słowo kluczowe {0} @@ -71,7 +71,7 @@ MDN Reference - MDN Reference + Referencje MDN Link text for MDN documentation URL in HTML completion tooltips @@ -91,7 +91,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "Ponownie wyzwalaj uzupełniania..." diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.pt-BR.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.pt-BR.xlf index a992efd3c46f1..d1e3e71f7910b 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.pt-BR.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.pt-BR.xlf @@ -9,39 +9,39 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Recém-disponível nos principais navegadores{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (Linha de base desde {0}) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ Amplamente disponível nos principais navegadores{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Atributos da diretiva Blazor {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (e atributos exigidos...) The term "req'd" is an abbreviation for "required" directive - directive + diretiva Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Insira um snippet de código de diretiva +[Tab] para navegar entre elementos, [Enter] para concluir @@ -61,7 +61,7 @@ {0} Keyword - {0} Keyword + {0} Palavra-chave @@ -71,7 +71,7 @@ MDN Reference - MDN Reference + Referência de MDN Link text for MDN documentation URL in HTML completion tooltips @@ -91,7 +91,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "Disparar conclusões novamente..." diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.ru.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.ru.xlf index d891e496c844b..f96e153a54db0 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.ru.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.ru.xlf @@ -9,39 +9,39 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Теперь доступно во всех основных браузерах{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (Базовый план с {0}) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ Широко доступно в основных браузерах{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Атрибуты директивы Blazor {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (и обязательные атрибуты...) The term "req'd" is an abbreviation for "required" directive - directive + директива Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Вставьте фрагмент кода директивы +[TAB] для перемещения между элементами, нажмите [ВВОД] для завершения @@ -61,7 +61,7 @@ {0} Keyword - {0} Keyword + Ключевое слово: {0} @@ -71,7 +71,7 @@ MDN Reference - MDN Reference + Справочник по MDN Link text for MDN documentation URL in HTML completion tooltips @@ -91,7 +91,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "Повторный запуск завершений..." diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.tr.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.tr.xlf index 417bb1adc1d96..5133601727999 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.tr.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.tr.xlf @@ -9,39 +9,39 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Başlıca tarayıcılarda yeni kullanıma sunuldu{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + ( {0} itibarıyla temel alınan) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ~D83C;~DD95;Başlıca tarayıcılarda yaygın olarak kullanıma sunuldu{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Blazor yönergesi öznitelikleri {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (ve gerekli öznitelikler...) The term "req'd" is an abbreviation for "required" directive - directive + yönerge Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Yönerge kod parçacığı ekle +Öğeler arasında gezinmek için [Tab], tamamlamak için [Enter] @@ -61,7 +61,7 @@ {0} Keyword - {0} Keyword + {0} Anahtar Sözcüğü @@ -71,7 +71,7 @@ MDN Reference - MDN Reference + MDN Başvurusu Link text for MDN documentation URL in HTML completion tooltips @@ -91,7 +91,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "Tamamlamaları yeniden tetikleyin..." diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.zh-Hans.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.zh-Hans.xlf index 74a631c4de1af..b165b9e411330 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.zh-Hans.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.zh-Hans.xlf @@ -9,39 +9,39 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 在主流浏览器中全新上线{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (自 {0} 起为基线) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ 在主流浏览器中广泛提供{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Blazor 指令特性 {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (和必需属性...) The term "req'd" is an abbreviation for "required" directive - directive + 指令 Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + 插入指令代码片段 +按 [Tab] 在元素之间导航,按 [Enter] 以完成 @@ -61,7 +61,7 @@ {0} Keyword - {0} Keyword + {0} 关键字 @@ -71,7 +71,7 @@ MDN Reference - MDN Reference + MDN 参考 Link text for MDN documentation URL in HTML completion tooltips @@ -91,7 +91,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + “重新触发完成…” diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.zh-Hant.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.zh-Hant.xlf index 94b5962667f2c..dbd8267d85bf7 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.zh-Hant.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Resources/xlf/SR.zh-Hant.xlf @@ -9,39 +9,39 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 主要瀏覽器均已新增支援{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (自 {0} 起的基準) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ 廣泛適用於主要瀏覽器{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Blazor 指示詞屬性 {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (和必要屬性...) The term "req'd" is an abbreviation for "required" directive - directive + 指示詞 Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + 插入指示詞程式碼片段 +[Tab] 在元素之間瀏覽,[Enter] 完成 @@ -61,7 +61,7 @@ {0} Keyword - {0} Keyword + {0} 關鍵字 @@ -71,7 +71,7 @@ MDN Reference - MDN Reference + MDN 參考資料 Link text for MDN documentation URL in HTML completion tooltips @@ -91,7 +91,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "重新觸發完成..." diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.cs.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.cs.xlf index 06ac52fb0beb6..30eef489dfd84 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.cs.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.cs.xlf @@ -182,7 +182,7 @@ U většiny moderních prohlížečů (včetně Firefoxu 38+, Google Chromu 34+ * Deklarované kódování znaků musí odpovídat kódování, ve kterém byla stránka uložená, aby nedocházelo k poškození znaků a bezpečnostním mezerám. * Prvek [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "Prvek HTML <meta> představuje metadata, která nelze reprezentovat jinými prvky souvisejícími s metadaty HTML, například <base>, <link>, <script>, <style> nebo <title>.") deklarující kódování musí být uvnitř prvku [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head "Prvek HTML <head> poskytuje obecné informace (metadata) o dokumentu, včetně jeho názvu a odkazů na skripty a šablony stylů.") a **v prvních 1024 bajtech** HTML, protože některé prohlížeče se před výběrem kódování dívají jen na tyto bajty. * Tento prvek [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "Prvek HTML <meta> představuje metadata, která nelze reprezentovat jinými prvky souvisejícími s metadaty HTML, například <base>, <link>, <script>, <style> nebo <title>.") je jen jednou částí [algoritmu pro určení znakové sady stránky](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Algoritmus znakové sady stránky"). Hlavička [`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) a případné [značky pořadí bajtů](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark "Definice tohoto termínu (značky pořadí bajtů) zatím nebyla napsaná. Zvažte prosím, jestli byste nepřispěli!") tento prvek přepíší. -* Důrazně se doporučuje definovat kódování znaků. Pokud není kódování stránky definované, jsou možné techniky cross-scriptingu, například [záložní technika cross-scriptingu UTF-7](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7). +* Důrazně se doporučuje definovat kódování znaků. Pokud není kódování stránky definované, jsou možné techniky cross-scriptingu, například [záložní technika cross-scriptingu `UTF-7`](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7). * Prvek [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "Prvek HTML <meta> představuje metadata, která nelze reprezentovat jinými prvky souvisejícími s metadaty HTML, například <base>, <link>, <script>, <style> nebo <title>.") s atributem `charset` je synonymem pro před-HTML5 zápis `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">`, kde _`IANAcharset`_ obsahuje hodnotu ekvivalentního atributu [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset). Tato syntaxe je pořád povolená, ale už se nedoporučuje. @@ -647,7 +647,7 @@ Definuje [cookie](https://developer.mozilla.org/en-US/docs/cookie) pro stránku. * `chapters` * Názvy kapitol jsou určené k použití při navigaci uživatele v mediálním prostředku. * `metadata` -* Stopy používané skripty. Není viditelné pro uživatele. +* Stopy používané skripty. Uživateli se nezobrazuje. @@ -1648,7 +1648,7 @@ Viz také: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewp Tento atribut definuje velikosti ikon pro vizuální média obsažená v prostředku. Musí být přítomen jen v případě, že atribut [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-rel) obsahuje hodnotu `icon` nebo nestandardní typ, například `apple-touch-icon` od Apple. Může mít následující hodnoty: * `any`, což znamená, že ikonu lze škálovat na libovolnou velikost, protože je ve vektorovém formátu, například `image/svg+xml`. -* seznam velikostí oddělených prázdnými znaky, kde každá má formát `_<šířka v pixelech>_x_<výška v pixelech>_` nebo `_<šířka v pixelech>_X_<výška v pixelech>_`. Každá z těchto velikostí musí být v prostředku obsažená. +* seznam velikostí oddělených prázdnými znaky, kde každá má formát `_<width in pixels>_x_<height in pixels>_` nebo `_<width in pixels>_X_<height in pixels>_`. Každá z těchto velikostí musí být v prostředku obsažená. **Poznámka:** Většina formátů ikon dokáže uložit jen jednu ikonu, proto atribut [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-sizes) většinou obsahuje jen jednu položku. Formát ICO od Microsoftu to umí, stejně jako ICNS od Apple. ICO je rozšířenější, proto byste ho měli rozhodně použít. diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.de.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.de.xlf index 595d5955a24f0..8b60bdac21f90 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.de.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.de.xlf @@ -182,7 +182,7 @@ Bei den meisten modernen Browsern, einschließlich Firefox 38+, Google Chrome 34 * Die deklarierte Zeichencodierung muss mit der Codierung übereinstimmen, mit der die Seite gespeichert wurde, um verstümmelte Zeichen und Sicherheitslücken zu vermeiden. * Das [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "Das HTML-<meta>element stellt Metadaten dar, die nicht durch andere HTML-Metaelemente dargestellt werden können, wie <base>, <link>, <script>, <style> oder <title>.")-Element, das die Codierung deklariert, muss sich innerhalb des [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head "The HTML <head> element provides general information (metadata) about the document, including its title and links to its scripts and style sheets.")-Elements und **innerhalb der ersten 1024 Byte** des HTML befinden, da einige Browser vor der Auswahl einer Codierung nur diese Byte lesen. * Dieses [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "Das HTML-<meta>element stellt Metadaten dar, die nicht durch andere HTML-Metaelemente dargestellt werden können, wie <base>, <link>, <script>, <style> oder <title>.")-Element ist nur ein Teil des [Algorithmus zum Ermitteln des Zeichensatzes einer Seite](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Algorithm charset page"). Der [`Content-Type`-Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) und alle [Bytereihenfolgemarken](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark "Die Definition dieses Ausdrucks (Bytereihenfolgemarken) wurde noch nicht geschrieben; bitte erwägen Sie, sie beisteuern!") dieses Element überschreiben. * Es wird dringend empfohlen, die Zeichencodierung zu definieren. -* Es wird dringend empfohlen, die Zeichencodierung zu definieren. Wenn die Codierung einer Seite nicht definiert ist, sind Cross-Scripting-Techniken möglich, z. B. die [`UTF-7`-Fallback-Cross-Scripting-Technik](https://code.google.com/p/doctype-mirror/wiki/HTML5). +* Es wird dringend empfohlen, die Zeichencodierung zu definieren. Wenn die Codierung einer Seite nicht definiert ist, sind Cross-Scripting-Techniken möglich, z. B. die [`UTF-7`-Fallback-Cross-Scripting-Technik](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7). * Das [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "Das HTML-<meta>element stellt Metadaten dar, die nicht durch andere HTML-Metaelemente dargestellt werden können, wie <base>, <link>, <script>, <style> oder <title>.")-Element mit einem `charset`-Attribut ist ein Synonym für das prä-HTML5 `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">`, wobei _`IANAcharset`_ den Wert des entsprechenden [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset)-Attributs enthält. Diese Syntax ist weiterhin zulässig, wird aber nicht mehr empfohlen. @@ -647,7 +647,7 @@ Definiert ein [Cookie](https://developer.mozilla.org/en-US/docs/cookie) für die * `chapters` * Kapitelüberschriften sind für die Navigation in der Medienressource gedacht. * `metadata` -* Von Skripts verwendete Spuren. Für die zuschauende Person nicht sichtbar. +* Von Skripts verwendete Spuren. Für den Benutzer nicht sichtbar. @@ -1635,7 +1635,7 @@ Siehe auch: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@view **Note:** According to the HTML5 specification, the default value for size should be 1; however, in practice, this has been found to break some web sites, and no other browser currently does that, so Mozilla has opted to continue to return 0 for the time being with Firefox. Wenn das Steuerelement als scrollbares Listenfeld dargestellt wird, etwa wenn `multiple` angegeben ist, gibt dieses Attribut die Anzahl der Zeilen in der Liste an, die gleichzeitig sichtbar sein sollen. Browser müssen ein select-Element nicht als scrollbares Listenfeld darstellen. Der Standardwert ist 0. -**Hinweis:** Laut HTML5-Spezifikation sollte der Standardwert für size 0 sein; in der Praxis hat sich jedoch gezeigt, dass dies einige Websites beeinträchtigt, und kein anderer Browser macht dies derzeit, daher gibt Mozilla in Firefox vorerst weiterhin 0 zurück. +**Hinweis:** Laut HTML5-Spezifikation sollte der Standardwert für size 1 sein; in der Praxis hat sich jedoch gezeigt, dass dies einige Websites beeinträchtigt, und kein anderer Browser macht dies derzeit, daher gibt Mozilla in Firefox vorerst weiterhin 0 zurück. diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.es.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.es.xlf index 2490b6b15b9d9..056c87fc12933 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.es.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.es.xlf @@ -121,12 +121,12 @@ Consulte [Browser compatibility](https://developer.mozilla.org/docs/Web/HTML/Ref For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) setting the autocomplete attribute will not prevent a browser's password manager from asking the user if they want to store login fields (username and password), if the user permits the storage the browser will autofill the login the next time the user visits the page. See [The autocomplete attribute and login fields](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion#The_autocomplete_attribute_and_login_fields). **Note:** If you set `autocomplete` to `off` in a form because the document provides its own auto-completion, then you should also set `autocomplete` to `off` for each of the form's `input` elements that the document can auto-complete. For details, see the note regarding Google Chrome in the [Browser Compatibility chart](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form#compatChart). - Indica si los elementos de entrada pueden tener, de forma predeterminada, sus valores completados automáticamente por el explorador. Este valor se puede invalidar con un atributo `autocomplete` en un elemento que pertenezca al formulario. Los valores posibles son: + Indica si los elementos de entrada pueden tener, de forma predeterminada, sus valores completados automáticamente por el explorador. Este valor se puede invalidar con un atributo `autocomplete` en un elemento que pertenezca al formulario. Los valores posibles son: * `off`: el usuario debe introducir explícitamente un valor en cada campo para cada uso o el documento proporciona su propio método de finalización automática; el explorador no finaliza automáticamente las entradas. * `on`: el explorador puede completar automáticamente los valores en función de los valores que el usuario ha escrito previamente en el formulario. -En la mayoría de los exploradores modernos (incluidos Firefox 38+, Google Chrome 34+, IE 11+), establecer el atributo autocompletar no impedirá que el administrador de contraseñas del explorador pregunte al usuario si quiere guardar los campos de inicio de sesión (nombre de usuario y contraseña). Si el usuario permite el guardado, el explorador rellenará automáticamente el inicio de sesión la próxima vez que visite la página. Vea [The autocomplete attribute and login fields](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion#The_autocomplete_attribute_and_login_fields). +En la mayoría de los exploradores modernos (incluidos Firefox 38+, Google Chrome 34+, IE 11+), establecer el atributo autocompletar no impedirá que el administrador de contraseñas del explorador pregunte al usuario si quiere guardar los campos de inicio de sesión (nombre de usuario y contraseña). Si el usuario permite el guardado, el explorador rellenará automáticamente el inicio de sesión la próxima vez que visite la página. Consulte [The autocomplete attribute and login fields](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion#The_autocomplete_attribute_and_login_fields). **Nota:** Si establece`autocomplete` en `off` en un formulario porque el documento proporciona su propia finalización automática, también debe establecer `autocomplete` en `off` para cada uno de los elementos`input`del formulario que el documento puede finalizar automáticamente. Para obtener más información, consulte la nota sobre Google Chrome en el [Browser Compatibility chart](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form#compatChart). @@ -167,7 +167,7 @@ En la mayoría de los exploradores modernos (incluidos Firefox 38+, Google Chrom * This [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") element is only one part of the [algorithm to determine a page's character set](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Algorithm charset page"). The [`Content-Type` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) and any [Byte-Order Marks](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark "The definition of that term (Byte-Order Marks) has not been written yet; please consider contributing it!") override this element. * It is strongly recommended to define the character encoding. If a page's encoding is undefined, cross-scripting techniques are possible, such as the [`UTF-7` fallback cross-scripting technique](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7). * The [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") element with a `charset` attribute is a synonym for the pre-HTML5 `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">`, where _`IANAcharset`_ contains the value of the equivalent [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) attribute. This syntax is still allowed, although no longer recommended. - Este atributo declara la codificación de caracteres de la página. Debe contener un [standard IANA MIME name for character encodings](https://www.iana.org/assignments/character-sets). Aunque el estándar no solicita una codificación específica, sugiere: + Este atributo declara la codificación de caracteres de la página. Debe contener un [nombre MIME estándar de IANA para codificaciones de caracteres](https://www.iana.org/assignments/character-sets). Aunque el estándar no solicita una codificación específica, sugiere: * Se recomienda a los autores que usen [`UTF-8`](https://developer.mozilla.org/en-US/docs/Glossary/UTF-8). * Los autores no deben usar codificaciones incompatibles con ASCII para evitar riesgos de seguridad: los exploradores que no los admiten pueden interpretar el contenido dañino como HTML. Esto sucede con `JIS_C6226-1983`, `JIS_X0212-1990`, `HZ-GB-2312`, `JOHAB`, la familia ISO-2022 y la familia EBCDIC. @@ -385,7 +385,7 @@ Para conseguir un efecto similar en scripts insertados dinámicamente, use `asyn * `text/plain`: (HTML5) This value can be overridden by a [`formenctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formenctype) attribute on a [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "The HTML <button> element represents a clickable button, which can be used in forms or anywhere in a document that needs simple, standard button functionality.") or [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") element. - Cuando el valor del atributo `method` es `post`, enctype es el [MIME type](https://en.wikipedia.org/wiki/Mime_type) del contenido que se usa para enviar el formulario al servidor. Los valores posibles son: + Cuando el valor del atributo `method` es `post`, enctype es el [MIME type](https://en.wikipedia.org/wiki/Mime_type) del contenido que se usa para enviar el formulario al servidor. Los valores posibles son: * `application/x-www-form-urlencoded`: valor predeterminado si no se especifica el atributo. * `multipart/form-data`: el valor usado para [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "El elemento HTML <input> se usa para crear controles interactivos para formularios basados en web con el fin de aceptar datos del usuario; hay una amplia variedad de tipos de datos de entrada y widgets de control disponibles, según el dispositivo y el agente de usuario") con el atributo `type` establecido en "file". @@ -436,7 +436,7 @@ Este valor se puede invalidar mediante un atributo [`formenctype`](https://devel * `text/plain` If this attribute is specified, it overrides the [`enctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-enctype) attribute of the button's form owner. - Si el botón es un botón de envío, este atributo especifica el tipo de contenido que se usa para enviar el formulario al servidor. Los valores posibles son: + Si el botón es un botón de envío, este atributo especifica el tipo de contenido que se usa para enviar el formulario al servidor. Los valores posibles son: * `application/x-www-form-urlencoded`: valor predeterminado si no se especifica el atributo. * `multipart/form-data`: use este valor si usa el elemento [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "El elemento HTML <input> se usa para crear controles interactivos para formularios basados en web con el fin de aceptar datos del usuario; hay una amplia variedad de tipos de datos de entrada y widgets de control disponibles, según el dispositivo y el agente de usuario") con el atributo [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-type) establecido en `file`. @@ -452,7 +452,7 @@ Si se especifica este atributo, invalida el atributo [`enctype`](https://develop * `get`: The data from the form are appended to the **form** attribute URI, with a '?' as a separator, and the resulting URI is sent to the server. Use this method when the form has no side-effects and contains only ASCII characters. If specified, this attribute overrides the [`method`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-method) attribute of the button's form owner. - Si el botón es un botón de envío, este atributo especifica el método HTTP que el explorador usa para enviar el formulario. Los valores posibles son: + Si el botón es un botón de envío, este atributo especifica el método HTTP que el explorador usa para enviar el formulario. Los valores posibles son: * `post`: los datos del formulario se incluyen en el cuerpo del formulario y se envían al servidor. * `get`: los datos del formulario se anexan al atributo **form** del URI, con un '?' como separador, y el URI resultante se envía al servidor. Use este método cuando el formulario no tenga efectos secundarios y contenga solo caracteres ASCII. @@ -632,7 +632,7 @@ Define una [cookie](https://developer.mozilla.org/en-US/docs/cookie) para la pá * Chapter titles are intended to be used when the user is navigating the media resource. * `metadata` * Tracks used by scripts. Not visible to the user. - Cómo se va a usar la pista de texto. Si se omite, el tipo predeterminado es `subtitles`. Si el atributo no está presente, se usará `subtitles`. Si el atributo contiene un valor no válido, se usará `metadata`. (Las versiones de Chrome anteriores a la 52 trataban un valor no válido como `subtitles`). Se permiten las palabras clave siguientes: + Cómo se va a usar la pista de texto. Si se omite, el tipo predeterminado es `subtitles`. Si el atributo no está presente, se usará `subtitles`. Si el atributo contiene un valor no válido, se usará `metadata`. (Las versiones de Chrome anteriores a la 52 trataban un valor no válido como `subtitles`.) Se permiten las palabras clave siguientes: * `subtitles` * Los subtítulos proporcionan traducción del contenido que el espectador no puede entender. Por ejemplo, diálogo o texto que no está en inglés en una película en inglés. @@ -647,7 +647,7 @@ Define una [cookie](https://developer.mozilla.org/en-US/docs/cookie) para la pá * `chapters` * Los títulos de capítulo están diseñados para usarse cuando el usuario navega por el recurso multimedia. * `metadata` -* Las pistas usadas por los scripts. No son visibles para el usuario. +* Las pistas usadas por los scripts. No es visible para el usuario. @@ -708,7 +708,7 @@ Define una [cookie](https://developer.mozilla.org/en-US/docs/cookie) para la pá * `dialog`: Use when the form is inside a [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog "The HTML <dialog> element represents a dialog box or other interactive component, such as an inspector or window.") element to close the dialog when submitted. This value can be overridden by a [`formmethod`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formmethod) attribute on a [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "The HTML <button> element represents a clickable button, which can be used in forms or anywhere in a document that needs simple, standard button functionality.") or [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") element. - Método[HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) que el explorador usa para enviar el formulario. Los valores posibles son: + Método [HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) que el explorador usa para enviar el formulario. Los valores posibles son: * `post`: corresponde al HTTP [POST method](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5); los datos del formulario se incluyen en el cuerpo del formulario y se envían al servidor. * `get`: corresponde al HTTP [GET method](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3); los datos de formulario se anexan al atributo `action` del URI con un '?' como separador y el URI resultante se envía al servidor. Use este método cuando el formulario no tenga efectos secundarios y contenga solo caracteres ASCII. @@ -947,7 +947,7 @@ Este nombre de metadatos está asociado al valor contenido por el atributo [`con * `application-name`: define el nombre de la aplicación que se ejecuta en la página web. -**Note:** +**Nota:** * Los exploradores pueden usarlo para identificar la aplicación. Es diferente del elemento [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title "El elemento HTML Title (<title>) define el título del documento que se muestra en la barra de título de un explorador o en la pestaña de una página") que normalmente contiene el nombre de la aplicación, pero también puede contener información como el nombre del documento o un estado. * Las páginas web simples no deben definir application-name. @@ -992,7 +992,7 @@ Enviar una dirección URL completa al realizar una solicitud del mismo origen, s Enviar la dirección URL completa (sin parámetros) para las solicitudes del mismo origen o entre orígenes. -**Notes:** +**Notas:** * Algunos exploradores admiten los valores en desuso de `always`, `default` y `never` para el origen de referencia. * La inserción dinámica de `<meta name="referrer">` (con [`document.write`](https://developer.mozilla.org/en-US/docs/Web/API/Document/write) o [`appendChild`](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild)) hace que el comportamiento del origen de referencia sea impredecible. @@ -1074,10 +1074,10 @@ Sinónimo de `noarchive`. [Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) -**Notes:** +**Notas:** * Solo los robots cooperativos siguen estas reglas. No espere evitar recopiladores de correo electrónico con ellos. -* El robot aún necesita acceder a la página para poder leer estas reglas. Para evitar el consumo de ancho de banda, use un _[robots.txt](https://developer.mozilla.org/en-US/docs/Glossary/robots.txt "robots.txt: Robots.txt es un archivo que normalmente se coloca en la raíz de cualquier sitio web. Decide si se permite o prohíbe el acceso a los rastreadores (crawlers) al sitio web")_ file. +* El robot aún necesita acceder a la página para poder leer estas reglas. Para evitar el consumo de ancho de banda, use un _[robots.txt](https://developer.mozilla.org/en-US/docs/Glossary/robots.txt "robots.txt: Robots.txt es un archivo que normalmente se coloca en la raíz de cualquier sitio web. Decide si se permite o prohíbe el acceso a los rastreadores crawlers al sitio web")_ file. * Si desea quitar una página, `noindex`, este funcionará, pero solo después de que el robot vuelva a visitar la página. Asegúrese de que el archivo `robots.txt` no impide visitas repetidas. * Algunos valores son mutuamente excluyentes, como `index`, `noindex` o `follow` y `nofollow`. En estos casos, el comportamiento del robot es indefinido y puede variar entre ellos. * Algunos robots rastreadores (crawlers), como Google, Yahoo y Bing, admiten los mismos valores para el encabezado HTTP `X-Robots-Tag`; esto permite que los documentos que no son HTML, como las imágenes, usen estas reglas. @@ -1135,14 +1135,14 @@ Estado Comentario -[CSS Device Adaptation -The definition of '<meta name="viewport">' in that specification.](https://drafts.csswg.org/css-device-adapt/#viewport-meta) +[Adaptación de dispositivos CSS +Definición de "<meta name="viewport">" en esa especificación.](https://drafts.csswg.org/css-device-adapt/#viewport-meta) Borrador de trabajo De forma no normativa se describe el elemento ventanilla META -Vea también:[`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport "El @viewport regla de CSS le permite configurar la ventanilla a través de la que se ve el documento. Se usa principalmente para dispositivos móviles, pero también lo usan los exploradores de escritorio que admiten características como "acoplar al borde" (como Microsoft Edge).") +Consulte también:[`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport "El @viewport regla de CSS le permite configurar la ventanilla a través de la que se ve el documento. Se usa principalmente para dispositivos móviles, pero también lo usan los exploradores de escritorio que admiten características como "acoplar al borde" (como Microsoft Edge).") **Notas:** @@ -1517,7 +1517,7 @@ Vea también:[`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@vie **Note:** Placeholders should only be used to show an example of the type of data that should be entered into a form; they are _not_ a substitute for a proper [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label "The HTML <label> element represents a caption for an item in a user interface.") element tied to the input. See [Labels and placeholders](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Labels_and_placeholders "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") in [<input>: The Input (Form Input) element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") for a full explanation. Sugerencia al usuario de lo que se puede escribir en el control. Los retornos de carro o avances de línea dentro del texto del marcador de posición deben tratarse como saltos de línea al representar la sugerencia. -**Nota:** Los marcadores de posición solo deben usarse para mostrar un ejemplo del tipo de datos que se deben escribir en un formulario; son _not_ un sustituto de un elemento [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label "El elemento HTML <label> representa un título para un elemento de una interfaz de usuario") apropiado, asociado a la entrada. Vea [Labels and placeholders](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Labels_and_placeholders "El elemento HTML <input> se usa para crear controles interactivos para formularios basados en web con el fin de aceptar datos del usuario; hay una amplia variedad de tipos de datos de entrada y widgets de control disponibles, según el dispositivo y el agente de usuario) en [<input>: The Input (Form Input) element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "El elemento HTML <input> se usa para crear controles interactivos para formularios basados en web con el fin de aceptar datos del usuario; hay una amplia variedad de tipos de datos de entrada y widgets de control disponibles, según el dispositivo y el agente de usuario") para obtener una explicación completa. +**Nota:** Los marcadores de posición solo deben usarse para mostrar un ejemplo del tipo de datos que se deben escribir en un formulario; son _not_ un sustituto de un elemento [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label "El elemento HTML <label> representa un título para un elemento de una interfaz de usuario") apropiado, asociado a la entrada. Consulte [Labels and placeholders](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Labels_and_placeholders "El elemento HTML <input> se usa para crear controles interactivos para formularios basados en web con el fin de aceptar datos del usuario; hay una amplia variedad de tipos de datos de entrada y widgets de control disponibles, según el dispositivo y el agente de usuario) en [<input>: The Input (Form Input) element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "El elemento HTML <input> se usa para crear controles interactivos para formularios basados en web con el fin de aceptar datos del usuario; hay una amplia variedad de tipos de datos de entrada y widgets de control disponibles, según el dispositivo y el agente de usuario") para obtener una explicación completa. @@ -1545,7 +1545,7 @@ Vea también:[`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@vie * `unsafe-url` means that the referrer will include the origin and the path (but not the fragment, password, or username). This case is unsafe because it can leak origins and paths from TLS-protected resources to insecure origins. Cadena que indica qué referencia usar al capturar el recurso: -* `no-referrer`: significa que `Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer "El encabezado de solicitud del remitente contiene la dirección de la página web anterior desde la que se siguió un vínculo a la página solicitada actualmente. El encabezado Referer permite a los servidores identificar desde dónde los visitan los usuarios y puede usar esos datos para el análisis, el registro o el almacenamiento en caché optimizado, por ejemplo") el encabezado no se enviará. +* `no-referrer`: significa que [`Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer "El encabezado de solicitud del remitente contiene la dirección de la página web anterior desde la que se siguió un vínculo a la página solicitada actualmente. El encabezado Referer permite a los servidores identificar desde dónde los visitan los usuarios y puede usar esos datos para el análisis, el registro o el almacenamiento en caché optimizado, por ejemplo") el encabezado no se enviará. *`no-referrer-when-downgrade`: significa que no [`Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer "El encabezado de solicitud del remitente contiene la dirección de la página web anterior desde la que se siguió un vínculo a la página solicitada actualmente. El encabezado Referer permite a los servidores identificar desde dónde los visitan los usuarios y puede usar esos datos para el análisis, el registro o el almacenamiento en caché optimizado, por ejemplo") se enviará el encabezado al navegar a un origen sin TLS (HTTPS). Este es el comportamiento predeterminado de un agente de usuario, si no se especifica ninguna directiva. * `origin`: significa que el remitente de la página será el origen, que es aproximadamente el esquema, el host y el puerto. * `origin-when-cross-origin`: significa que la navegación a otros orígenes se limitará al esquema, el host y el puerto, mientras que navegar en el mismo origen incluirá la ruta de acceso del remitente. @@ -1619,7 +1619,7 @@ Vea también:[`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@vie This attribute defines the scheme in which metadata is described. A scheme is a context leading to the correct interpretations of the [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) value, like a format. **Warning:** Do not use this value, as it is obsolete. There is no replacement as there was no real usage for it. - Este atributo define el esquema en el que se describen los metadatos. Un esquema es un contexto que conduce a las interpretaciones correctas del valor [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) , como un formato. + Este atributo define el esquema en el que se describen los metadatos. Un esquema es un contexto que conduce a las interpretaciones correctas del valor [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content), como un formato. **Advertencia:** No use este valor, ya que está obsoleto. No hay ningún reemplazo porque no había ningún uso real para él. @@ -1704,7 +1704,7 @@ The user agent selects any one of the available sources at its discretion. This Una lista de una o varias cadenas separadas por comas que indica un conjunto de posibles orígenes de imagen que el agente de usuario puede usar. Cada cadena se compone de: 1. Una dirección URL a una imagen, -2. Opcionalmente, un espacio en blanco seguido de uno de: +2. opcionalmente, un espacio en blanco seguido de uno de: * Un descriptor de ancho o un entero positivo seguido directamente de `w`. El descriptor de ancho se divide por el tamaño de origen especificado en el atributo `sizes` para calcular la densidad de píxeles efectiva. * Un descriptor de densidad de píxeles, que es un número de punto flotante positivo seguido directamente de `x`. @@ -1797,7 +1797,7 @@ Si se especifica este atributo, este elemento debe ir delante de cualquier otro **Note**: This attribute was deprecated in HTML4, but reintroduced in HTML5. **Note:** Prior to Gecko 9.0, negative values were incorrectly converted to 0. Starting in Gecko 9.0 all integer values are correctly parsed. - Este atributo entero indica el valor ordinal actual del elemento de lista tal y como se define en el elemento [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol "El elemento HTML <ol> representa una lista ordenada de elementos, que normalmente se representa como una lista numerada"). El único valor permitido para este atributo es un número, incluso si la lista se muestra con números romanos o letras. Los elementos de lista que siguen a este se continúan numerando desde el conjunto de valores. El atributo **value** no tiene ningún significado para las listas no ordenadas ([`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul "El elemento HTML <ul> representa una lista no ordenada de elementos, representada normalmente como una lista con viñetas")) o para los menús ([`<menu>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu "El elemento HTML <menu> representa un grupo de comandos que un usuario puede realizar o activar. Esto incluye ambos menús de lista, que pueden aparecer en la parte superior de una pantalla, así como menús contextuales, como los que pueden aparecer debajo de un botón después de hacer clic en él")). + Este atributo entero indica el valor ordinal actual del elemento de lista tal y como se define en el elemento [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol "El elemento HTML <ol> representa una lista ordenada de elementos, que normalmente se representa como una lista numerada"). El único valor permitido para este atributo es un número, incluso si la lista se muestra con números romanos o letras. Los elementos de lista que siguen a este se continúan numerando desde el conjunto de valores. El atributo **value** no tiene ningún significado para las listas no ordenadas ([`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul "El elemento HTML <ul> representa una lista no ordenada de elementos, representada normalmente como una lista con viñetas")) o para los menús ([`<menu>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu "El elemento HTML <menu> representa un grupo de comandos que un usuario puede realizar o activar. Esto incluye ambos menús de lista, que pueden aparecer en la parte superior de una pantalla, así como menús contextuales, como los que pueden aparecer debajo de un botón después de hacer clic en él".)). **Nota**: Este atributo quedó en desuso en HTML4, pero se reintroduce en HTML5. diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.fr.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.fr.xlf index 1722ea561c347..705891c7fa563 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.fr.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.fr.xlf @@ -43,8 +43,8 @@ Dans les versions antérieures de HTML, les différents codages de caractères p **Note d’utilisation** -* **N’utilisez pas cet attribut**, car il est obsolète. L’élément [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table l’élément « Html <table> représente des données tabulaires, c’est-à-dire les informations présentées dans une table à deux dimensions composée de lignes et de colonnes de cellules contenant des données. ») doivent être mises en forme à l’aide de [CSS](https://developer.mozilla.org/en-US/docs/CSS). Définissez [`margin-left`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left « La propriété CSS margin-left définit la zone de marge sur le côté gauche d’un élément. Une valeur positive le place plus loin de ses voisins, tandis qu’une valeur négative le rapproche. ») et [`margin-right`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right « La propriété CSS margin-right définit la zone de marge sur le côté droit d’un élément. Une valeur positive le place plus loin de ses voisins, tandis qu’une valeur négative le rapproche. ») sur `auto` ou [`margin`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin « La propriété CSS margin définit la zone de marge sur les quatre côtés d’un élément. C’est un raccourci pour margin-top, margin-right, margin-bottom et margin-left.") sur `0 auto` pour obtenir un effet similaire à celui de l’attribut d’alignement. -* Avant Firefox 4, Firefox prenait également en charge les valeurs `middle`, `absmiddle` et `abscenter` comme synonymes de `center`, en mode quirks uniquement. +* **N’utilisez pas cet attribut**, car il est obsolète. L’élément [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table l’élément « Html <table> représente des données tabulaires, c’est-à-dire les informations présentées dans une table à deux dimensions composée de lignes et de colonnes de cellules contenant des données. ») doivent être mises en forme à l’aide de [CSS](https://developer.mozilla.org/en-US/docs/CSS). Définissez [`margin-left`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left "La propriété CSS margin-left définit la zone de marge sur le côté gauche d'un élément. Une valeur positive le place plus loin de ses voisins, tandis qu'une valeur négative le rapproche.") et [`margin-right`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right "La propriété CSS margin-right définit la zone de marge sur le côté droit d'un élément. Une valeur positive le place plus loin de ses voisins, tandis qu'une valeur négative le rapproche.") de `auto` ou [`margin`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin "La propriété CSS margin définit la zone de marge sur les quatre côtés d'un élément. Il s'agit d'un raccourci pour margin-top, margin-right, margin-bottom et margin-left.") à `0 auto` pour obtenir un effet similaire à l'attribut align. +* Avant Firefox 4, Firefox prenait également en charge les valeurs `middle`, `absmiddle`, et `abscenter` comme synonymes de `center`, en mode bizarreries uniquement. @@ -55,7 +55,7 @@ Dans les versions antérieures de HTML, les différents codages de caractères p Set to `true` if the `<iframe>` can activate fullscreen mode by calling the [`requestFullscreen()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen "The Element.requestFullscreen() method issues an asynchronous request to make the element be displayed in full-screen mode.") method. This attribute is considered a legacy attribute and redefined as `allow="fullscreen"`. - Défini sur `true` si le `<iframe>` peut activer le mode plein écran en appelant la méthode [`requestFullscreen()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen « La méthode Element.requestFullscreen() envoie une requête asynchrone pour que l’élément s’affiche en mode plein écran. »). + Défini sur `true` si `<iframe>` peut activer le mode plein écran en appelant la méthode [`requestFullscreen()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen "La méthode Element.requestFullscreen() émet une requête asynchrone pour que l'élément soit affiché en mode plein écran."). Cet attribut est considéré comme obsolète et remplacé par `allow="fullscreen"`. @@ -93,7 +93,7 @@ Vous ne devez pas utiliser cet attribut si l’attribut `src` est absent (c’es Les navigateurs supposent généralement le pire cas et chargent les scripts de manière synchrone (c’est-à-dire `async="false"`) pendant l’analyse HTML. -Les scripts insérés dynamiquement (à l’aide de [`document.createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement « Dans un document HTML, la méthode document.createElement() crée l'élément HTML spécifié par tagName, ou un HTMLUnknownElement si tagName n'est pas reconnu. »)) se chargent par défaut de manière asynchrone, donc pour activer le chargement synchrone (c’est-à-dire pour que les scripts se chargent dans l’ordre dans lequel ils ont été insérés), définissez `async="false"`. +Les scripts insérés dynamiquement (à l'aide de [`document.createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement "Dans un document HTML, la méthode document.createElement() crée l'élément HTML spécifié par tagName, ou un HTMLUnknownElement si tagName n'est pas reconnu.")) se chargent de manière asynchrone par défaut, afin d'activer le chargement synchrone (c'est-à-dire que les scripts se chargent dans l'ordre dans lequel ils ont été insérés) définissez `async="false"`. Consultez [Compatibilité des navigateurs](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/script#Browser_compatibility) pour obtenir des informations sur la prise en charge par les navigateurs. Consultez aussi [Scripts asynchrones pour asm.js](https://developer.mozilla.org/en-US/docs/Games/Techniques/Async_scripts). @@ -109,7 +109,7 @@ Consultez [Compatibilité des navigateurs](https://developer.mozilla.org/docs/We * `off` ou `none`, aucune mise en majuscule automatique n’est appliquée (toutes les lettres sont en minuscules par défaut) * `on` ou `sentences`, la première lettre de chaque phrase est une majuscule par défaut ; toutes les autres lettres sont en minuscules par défaut -* `words`, la première lettre de chaque mot est une majuscule par défaut ; toutes les autres lettres sont en minuscules par défaut +* `words`, la première lettre de chaque mot est par défaut une lettre majuscule ; toutes les autres lettres sont par défaut en minuscules * `characters`, toutes les lettres doivent être en majuscules par défaut @@ -126,8 +126,8 @@ For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) sett * `off` : l’utilisateur doit saisir explicitement une valeur dans chaque champ à chaque utilisation, ou le document fournit sa propre méthode de saisie semi-automatique. Le navigateur ne complète pas automatiquement les entrées. * `on` : le navigateur peut compléter automatiquement les valeurs en fonction de celles que l’utilisateur a déjà saisies dans le formulaire. -Pour la plupart des navigateurs modernes (y compris Firefox 38+, Google Chrome 34+ et IE 11+), la définition de l’attribut autocomplete n’empêche pas le gestionnaire de mots de passe du navigateur de demander à l’utilisateur s’il souhaite enregistrer les champs de connexion (nom d’utilisateur et mot de passe). Si l’utilisateur autorise l’enregistrement, le navigateur remplira automatiquement les champs de connexion lors de la prochaine visite de la page. Consultez [Attribut de saisie semi-automatique et champs de connexion](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion#The_autocomplete_attribute_and_login_fields). -**Remarque :** si vous définissez `autocomplete` sur `off` dans un formulaire parce que le document fournit sa propre saisie semi-automatique, vous devez également définir `autocomplete` sur `off` pour chacun des éléments `input` du formulaire que le document peut compléter automatiquement. Pour plus d’informations, consultez la note concernant Google Chrome dans le [graphique de compatibilité du navigateur](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form#compatChart). +Pour la plupart des navigateurs modernes (y compris Firefox 38+, Google Chrome 34+, IE 11+), la définition de l'attribut de saisie semi-automatique n'empêchera pas le gestionnaire de mots de passe d'un navigateur de demander à l'utilisateur s'il souhaite stocker les champs de connexion (nom d'utilisateur et mot de passe). Si l'utilisateur autorise le stockage, le navigateur remplira automatiquement la connexion la prochaine fois que l'utilisateur visitera la page. Consultez [Attribut de saisie semi-automatique et champs de connexion](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion#The_autocomplete_attribute_and_login_fields). +**Remarque :** Si vous définissez `autocomplete` sur `off` dans un formulaire parce que le document fournit sa propre saisie semi-automatique, vous devez également définir `autocomplete` sur `off` pour chacun des éléments `input` du formulaire que le document peut compléter automatiquement. Pour plus d’informations, consultez la note concernant Google Chrome dans le [graphique de compatibilité du navigateur](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form#compatChart). @@ -167,23 +167,23 @@ Pour la plupart des navigateurs modernes (y compris Firefox 38+, Google Chrome * This [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") element is only one part of the [algorithm to determine a page's character set](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Algorithm charset page"). The [`Content-Type` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) and any [Byte-Order Marks](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark "The definition of that term (Byte-Order Marks) has not been written yet; please consider contributing it!") override this element. * It is strongly recommended to define the character encoding. If a page's encoding is undefined, cross-scripting techniques are possible, such as the [`UTF-7` fallback cross-scripting technique](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7). * The [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") element with a `charset` attribute is a synonym for the pre-HTML5 `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">`, where _`IANAcharset`_ contains the value of the equivalent [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) attribute. This syntax is still allowed, although no longer recommended. - Cet attribut déclare l’encodage de caractères de la page. Il doit contenir un [nom MIME IANA standard pour les encodages de caractères](https://www.iana.org/assignments/character-sets). Bien que la norme n’exige pas d’encodage spécifique, elle recommande : + Cet attribut déclare l’encodage de caractères de la page. Il doit contenir un [nom IANA MIME standard pour les encodages de caractères](https://www.iana.org/assignments/character-sets). Bien que la norme n’exige pas d’encodage spécifique, elle recommande : * Les auteurs sont encouragés à utiliser [`UTF-8`](https://developer.mozilla.org/en-US/docs/Glossary/UTF-8). -* Les auteurs ne doivent pas utiliser d’encodages incompatibles avec ASCII afin d’éviter les risques de sécurité : les navigateurs qui ne les prennent pas en charge peuvent interpréter du contenu malveillant comme du HTML. C’est le cas des familles `JIS_C6226-1983`, `JIS_X0212-1990`, `HZ-GB-2312`, `JOHAB`, ISO-2022 et EBCDIC. +* Les auteurs ne doivent pas utiliser d'encodages incompatibles avec ASCII pour éviter les risques de sécurité : les navigateurs qui ne les prennent pas en charge peuvent interpréter le contenu nuisible comme du HTML. C’est le cas des familles `JIS_C6226-1983`, `JIS_X0212-1990`, `HZ-GB-2312`, `JOHAB`, ISO-2022 et EBCDIC. **Remarque :** les encodages incompatibles avec ASCII sont ceux qui ne mappent pas les points de code 8 bits `0x20` à `0x7E` vers les points de code Unicode `0x0020` à `0x007E`) -* Les auteurs **ne doivent pas** utiliser `CESU-8`, `UTF-7`, `BOCU-1` et/ou `SCSU`, car des attaques de [scripting inter-site](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting) exploitant ces encodages ont été démontrées. -* Les auteurs ne doivent pas utiliser `UTF-32`, car tous les algorithmes d’encodage HTML5 ne peuvent pas le distinguer de `UTF-16`. +* Les auteurs **ne doivent pas** utiliser `CESU-8`, `UTF-7`, `BOCU-1` et/ou `SCSU` car des attaques de [cross-site scripting](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting) avec ces encodages ont été démontrées. +* Les auteurs ne doivent pas utiliser `UTF-32` car tous les algorithmes d'encodage HTML5 ne peuvent pas le distinguer de `UTF-16`. **Remarques :** * L’encodage de caractères déclaré doit correspondre à celui avec lequel la page a été enregistrée pour éviter l’affichage incorrect des caractères et les failles de sécurité. -* L’élément [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta « L’élément HTML <meta> représente des métadonnées qui ne peuvent pas être représentées par d’autres éléments HTML liés aux métadonnées, comme <base>, <link>, <script>, <style> ou <title>. ») qui déclare l’encodage doit se trouver à l’intérieur de l’élément [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head « L’élément HTML <head> fournit des informations générales (métadonnées) sur le document, y compris son titre et des liens vers ses scripts et feuilles de style. ») et **dans les 1 024 premiers octets** du code HTML, car certains navigateurs ne consultent que ces octets avant de choisir un encodage. -* Cet élément [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta « L’élément HTML <meta> représente des métadonnées qui ne peuvent pas être représentées par d’autres éléments HTML liés aux métadonnées, comme <base>, <link>, <script>, <style> ou <title>. ») n’est qu’une partie de l'[algorithme permettant de déterminer le jeu de caractères d’une page](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm « Page de jeu de caractères d’algorithme »). L’en-tête [`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) et tout [Byte-Order Mark](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark « La définition de ce terme (Byte-Order Marks) n’a pas encore été écrite ; n’hésitez pas à y contribuer ! ») priment sur cet élément. -* Il est fortement recommandé de définir l’encodage de caractères. Si l’encodage d’une page n’est pas défini, des techniques de scripting inter-site sont possibles, telles que la [technique de repli `UTF-7`](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7). -* [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta « L’élément HTML <meta> représente des métadonnées qui ne peuvent pas être représentées par d’autres éléments html liés aux métadonnées, comme <base>, <link>, <script>, <style> ou <title>. ») élément avec un attribut `charset` est un synonyme de la pré-HTML5 `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">`, où _`IANAcharset`_ contient la valeur de l’attribut [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) équivalent. Cette syntaxe est toujours autorisée, bien qu’elle ne soit plus recommandée. +* L'élément [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "L'élément HTML <meta> représente des métadonnées qui ne peuvent pas être représentées par d'autres éléments HTML liés aux méta, comme <base>, <link>, <script>, <style> ou <title>.") déclarant que l'encodage doit être à l'intérieur du [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head "L'élément HTML <head> fournit des informations générales (métadonnées) sur le document, y compris son titre et les liens vers ses scripts et feuilles de style.") et **dans les 1 024 premiers octets** du code HTML, car certains navigateurs ne consultent que ces octets avant de choisir un encodage. +* Cet élément [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "L'élément HTML <meta> représente des métadonnées qui ne peuvent pas être représentées par d'autres éléments HTML liés aux méta, comme <base>, <link>, <script>, <style> ou <title>.") n'est qu'une partie de l'[algorithme pour déterminer le caractère d'une page set](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Page de jeu de caractères de l'algorithme"). L’en-tête [`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) et tout [Byte-Order Mark](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark « La définition de ce terme (Byte-Order Marks) n’a pas encore été écrite ; n’hésitez pas à y contribuer ! ») priment sur cet élément. +* Il est fortement recommandé de définir l’encodage de caractères. Si l'encodage d'une page n'est pas défini, des techniques de script croisé sont possibles, telles que la [technique de script croisé de secours `UTF-7`](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7). +* L'élément [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "L'élément HTML <meta> représente des métadonnées qui ne peuvent pas être représentées par d'autres éléments HTML liés aux méta, comme <base>, <link>, <script>, <style> ou <title>.") avec un attribut `charset` est un synonyme de l'élément pré-HTML5 `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">`, où _`IANAcharset`_ contient la valeur de l'attribut équivalent [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset). Cette syntaxe est toujours autorisée, bien qu’elle ne soit plus recommandée. @@ -239,17 +239,17 @@ A cross-origin request (i.e. with an [`Origin`](https://developer.mozilla.org/en A cross-origin request (i.e. with an `Origin` HTTP header) is performed along with a credential sent (i.e. a cookie, certificate, and/or HTTP Basic authentication is performed). If the server does not give credentials to the origin site (through [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials "The Access-Control-Allow-Credentials response header tells browsers whether to expose the response to frontend JavaScript code when the request's credentials mode (Request.credentials) is "include".") HTTP header), the resource will be _tainted_ and its usage restricted. If the attribute is not present, the resource is fetched without a [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS: CORS (Cross-Origin Resource Sharing) is a system, consisting of transmitting HTTP headers, that determines whether browsers block frontend JavaScript code from accessing responses for cross-origin requests.") request (i.e. without sending the `Origin` HTTP header), preventing its non-tainted usage. If invalid, it is handled as if the enumerated keyword **anonymous** was used. See [CORS settings attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for additional information. - Cet attribut énuméré indique si [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS « CORS : CORS (Cross-Origin Resource Sharing) est un système, constitué d’en-têtes HTTP, qui détermine si les navigateurs empêchent le code JavaScript frontal d’accéder aux réponses pour les demandes cross-origin. ») doivent être utilisés lors de l’extraction de la ressource. [Images compatibles CORS](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_Enabled_Image) peut être réutilisé dans l’élément [`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas l’élément HTML <canvas> avec l’API de script de canevas ou l’API WebGL pour dessiner des graphiques et des animations. ») sans être _tainted_. Les valeurs autorisées sont : + Cet attribut énuméré indique si [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS : CORS (Cross-Origin Resource Sharing) est un système, consistant à transmettre des en-têtes HTTP, qui détermine si les navigateurs empêchent le code JavaScript frontal d'accéder aux réponses pour les requêtes d'origine croisée.") doit être utilisé lors de la récupération de la ressource. Les [images compatibles CORS](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_Enabled_Image) peuvent être réutilisées dans l'élément [`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas "Utilisez l'élément HTML <canvas> avec l'API de script canevas ou l'API WebGL pour dessiner des graphiques et des animations.") sans être _tainted_. Les valeurs autorisées sont : `anonymous` -Une demande cross-origin (c’est-à-dire avec une [`Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin « L’en-tête de la demande d’origine indique d’où provient une extraction. Il n’inclut aucune information de chemin d’accès, mais uniquement le nom du serveur. Il est envoyé avec les demandes CORS, ainsi qu’avec les demandes POST. Il est similaire à l’en-tête du référent, mais, contrairement à cet en-tête, il ne divulgue pas l’intégralité du chemin d’accès. ») En-tête HTTP) est effectué, mais aucune information d’identification n’est envoyée (par exemple, aucun cookie, certificat X.509 ou authentification HTTP De base). Si le serveur n’accorde pas d’informations d’identification au site d’origine (en ne définissant pas l’en-tête HTTP [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin « L’en-tête de réponse Access-Control-Allow-Origin indique si la réponse peut être partagée avec le code demandeur provenant de l’origine donnée. »)), l’image sera altérée et son utilisation sera restreinte. +Une requête d'origine croisée (c'est-à-dire avec un [`Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin "L'en-tête de requête Origin indique d'où provient une récupération. Il n’inclut aucune information de chemin d’accès, mais uniquement le nom du serveur. Il est envoyé avec les requêtes CORS, ainsi qu'avec les requêtes POST. Il est similaire à l'en-tête Referer, mais contrairement à cet en-tête, il ne divulgue pas le chemin complet.") En-tête HTTP) est effectué, mais aucun identifiant n'est envoyé (c'est-à-dire aucun cookie, certificat X.509 ou authentification HTTP Basic). Si le serveur ne donne pas d'informations d'identification au site d'origine (en ne définissant pas l'en-tête HTTP [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin "L'en-tête de réponse Access-Control-Allow-Origin indique si la réponse peut être partagée avec la requête de code de l'origine donnée.")), l'image sera corrompue et son utilisation restreinte. `use-credentials` -Une requête cross-origin (c’est-à-dire avec un en-tête HTTP `Origin`) est effectuée avec l’envoi d’informations d’identification (c’est-à-dire avec un cookie, un certificat et/ou une authentification HTTP Basic). Si le serveur n’accorde pas d’informations d’identification au site d’origine (via l’en-tête HTTP [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials « L’en-tête de réponse Access-Control-Allow-Credentials indique aux navigateurs s’il faut exposer la réponse au code JavaScript frontal lorsque le mode d’informations d’identification de la requête (Request.credentials) est 'include'. »)), la ressource sera _altérée_ et son utilisation sera restreinte. +Une requête d'origine croisée (c'est-à-dire avec un en-tête HTTP `Origin`) est effectuée avec un identifiant envoyé (c'est-à-dire qu'un cookie, un certificat et/ou une authentification HTTP Basic est effectué). Si le serveur ne donne pas d'informations d'identification au site d'origine (via [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials "L'en-tête de réponse Access-Control-Allow-Credentials indique aux navigateurs s'ils doivent exposer la réponse au code JavaScript frontal lorsque le mode d'informations d'identification de la demande (Request.credentials) est "include".") En-tête HTTP), la ressource sera _tainted_ et son utilisation restreinte. -Si l’attribut n’est pas présent, la ressource est extraite sans [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS « CORS : CORS (Cross-Origin Resource Sharing) est un système, constitué d’en-têtes HTTP transmis, qui détermine si les navigateurs empêchent le code JavaScript frontend d’accéder aux réponses pour les demandes cross-origin. ») (c’est-à-dire sans envoyer l’en-tête HTTP `Origin`) qui empêche son utilisation non altérée. S’il est invalide, il est traité comme si le mot clé énuméré **anonymous** était utilisé. Consultez [CORS settings attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) pour plus d’informations. +Si l'attribut n'est pas présent, la ressource est récupérée sans requête [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS : CORS (Cross-Origin Resource Sharing) est un système consistant à transmettre des en-têtes HTTP, qui détermine si les navigateurs empêchent le code JavaScript frontal d'accéder aux réponses pour les requêtes d'origine croisée.") (c'est-à-dire sans envoyer l'en-tête HTTP `Origin`), empêchant sa utilisation non entachée. S’il est invalide, il est traité comme si le mot clé énuméré **anonymous** était utilisé. Consultez [CORS settings attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) pour plus d’informations. @@ -326,7 +326,7 @@ Pour obtenir un effet similaire pour les scripts insérés dynamiquement, utilis * `ltr`, qui signifie _left to right_ et doit être utilisé pour les langues écrites de gauche à droite (comme l’anglais) ; * `rtl`, qui signifie _right to left_ et doit être utilisé pour les langues écrites de droite à gauche (comme l’arabe) ; -* `auto`, qui laisse l’agent utilisateur décider. Il utilise un algorithme de base lorsqu’il analyse les caractères à l’intérieur de l’élément jusqu’à ce qu’il trouve un caractère avec une forte directionnalité, puis applique cette directionnalité à l’ensemble de l’élément. +* `auto`, qui laisse l’agent utilisateur décider. Il utilise un algorithme de base pour analyser les caractères à l'intérieur de l'élément jusqu'à ce qu'il trouve un caractère avec une forte directionnalité, puis il applique cette directionnalité à l'ensemble de l'élément. @@ -358,9 +358,9 @@ Pour obtenir un effet similaire pour les scripts insérés dynamiquement, utilis * `true`, which indicates that the element may be dragged * `false`, which indicates that the element may not be dragged. - Attribut énuméré indiquant si l’élément peut être déplacé à l’aide de l’[API Glisser-déplacer](https://developer.mozilla.org/docs/DragDrop/Drag_and_Drop). Il peut avoir les valeurs suivantes : + Un attribut énuméré indiquant si l'élément peut être glissé, à l'aide de l'[API Drag and Drop](https://developer.mozilla.org/docs/DragDrop/Drag_and_Drop). Il peut avoir les valeurs suivantes : -* `true`, qui indique que l’élément peut être déplacé +* `true`, ce qui indique que l'élément peut être glissé * `false`, qui indique que l’élément peut être déplacé. @@ -370,11 +370,11 @@ Pour obtenir un effet similaire pour les scripts insérés dynamiquement, utilis * `copy`, which indicates that dropping will create a copy of the element that was dragged * `move`, which indicates that the element that was dragged will be moved to this new location. * `link`, will create a link to the dragged data. - Attribut énuméré indiquant les types de contenu pouvant être supprimés sur un élément, à l’aide de l'[API Glisser-déplacer](https://developer.mozilla.org/docs/DragDrop/Drag_and_Drop). Il peut avoir les valeurs suivantes : + Un attribut énuméré indiquant quels types de contenu peuvent être déposés sur un élément, à l'aide de l'[API Drag and Drop](https://developer.mozilla.org/docs/DragDrop/Drag_and_Drop). Il peut avoir les valeurs suivantes : * `copy`, qui indique que le dépôt crée une copie de l’élément déplacé * `move`, qui indique que l’élément déplacé sera déplacé vers ce nouvel emplacement. -* `link`, qui crée un lien vers les données déplacées. +* `link` créera un lien vers les données glissées. @@ -385,13 +385,13 @@ Pour obtenir un effet similaire pour les scripts insérés dynamiquement, utilis * `text/plain`: (HTML5) This value can be overridden by a [`formenctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formenctype) attribute on a [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "The HTML <button> element represents a clickable button, which can be used in forms or anywhere in a document that needs simple, standard button functionality.") or [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") element. - Lorsque la valeur de l’attribut `method` est `post`, enctype correspond au [type MIME](https://en.wikipedia.org/wiki/Mime_type) du contenu utilisé pour envoyer le formulaire au serveur. Les valeurs admises sont : + Lorsque la valeur de l'attribut `method` est `post`, enctype est le [type MIME](https://en.wikipedia.org/wiki/Mime_type) du contenu utilisé pour soumettre le formulaire au serveur. Les valeurs admises sont : -* `application/x-www-form-urlencoded` : valeur par défaut si l’attribut n’est pas spécifié. -* `multipart/form-data` : valeur utilisée pour un élément [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input « L’élément HTML <input> est utilisé pour créer des contrôles interactifs pour les formulaires Web afin d’accepter des données de la part de l’utilisateur ; une grande variété de types de données d’entrée et de widgets de contrôle sont disponibles, selon l’appareil et l’agent utilisateur. ») avec l’attribut `type` défini sur "file". -* `text/plain` : (HTML5) +* `application/x-www-form-urlencoded` : La valeur par défaut si l'attribut n'est pas spécifié. +* `multipart/form-data` : la valeur utilisée pour un élément [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "L'élément HTML <input> est utilisé pour créer des contrôles interactifs pour les formulaires Web afin d'accepter les données de l'utilisateur ; une grande variété de types de données d'entrée et de widgets de contrôle sont disponibles, en fonction du périphérique et de l'agent utilisateur.") élément avec l'attribut `type` défini sur "fichier". +* `text/plain`: (HTML5) -Cette valeur peut être remplacée par un attribut [`formenctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formenctype) sur un [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button « L’élément HTML <button> représente un bouton interactif, qui peut être utilisé dans les formulaires ou n’importe où dans un document nécessitant une fonctionnalité de bouton simple et standard. ») ou un [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input « L’élément HTML <input> est utilisé pour créer des contrôles interactifs pour les formulaires Web afin d’accepter les données de l’utilisateur ; une grande variété de types de données d’entrée et de widgets de contrôle sont disponibles, en fonction de l’appareil et de l’agent utilisateur. »). +Cette valeur peut être remplacée par un attribut [`formenctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formenctype) sur un [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "L'élément HTML <button> représente un bouton cliquable, qui peut être utilisé dans des formulaires ou n'importe où dans un document qui nécessite une fonctionnalité de bouton simple et standard.") ou [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "L'élément HTML <input> est utilisé pour créer des contrôles interactifs pour les formulaires Web afin d'accepter les données de l'utilisateur ; une grande variété de types de données d'entrée et de widgets de contrôle sont disponibles, en fonction de l'appareil et de l'agent utilisateur.") élément. @@ -413,7 +413,7 @@ Cette valeur peut être remplacée par un attribut [`formenctype`](https://devel The [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-id) of a [labelable](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form_labelable) form-related element in the same document as the `<label>` element. The first element in the document with an `id` matching the value of the `for` attribute is the _labeled control_ for this label element, if it is a labelable element. If it is not labelable then the `for` attribute has no effect. If there are other elements which also match the `id` value, later in the document, they are not considered. **Note**: A `<label>` element can have both a `for` attribute and a contained control element, as long as the `for` attribute points to the contained control element. - L’[`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-id) d’un élément de formulaire [étiquetable](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form_labelable) situé dans le même document que l’élément `<label>`. Le premier élément du document dont l’`id` correspond à la valeur de l’attribut `for` est le _contrôle étiqueté_ de cet élément d’étiquette, s’il s’agit d’un élément étiquetable. S’il ne l’est pas, l’attribut `for` n’a aucun effet. Si d’autres éléments correspondent également à la valeur de l’`id`, ils ne sont pas pris en compte s’ils apparaissent plus loin dans le document. + L’[`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-id) d’un élément de formulaire [étiquetable](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form_labelable) situé dans le même document que l’élément `<label>`. Le premier élément du document avec un `id` correspondant à la valeur de l'attribut `for` est le _labeled control_ pour cet élément label, s'il s'agit d'un élément étiquetable. S’il ne l’est pas, l’attribut `for` n’a aucun effet. Si d’autres éléments correspondent également à la valeur de l’`id`, ils ne sont pas pris en compte s’ils apparaissent plus loin dans le document. **Remarque** : un élément `<label>` peut avoir à la fois un attribut `for` et un élément de contrôle qu’il contient, à condition que l’attribut `for` pointe vers l’élément de contrôle contenu. @@ -438,11 +438,11 @@ Cette valeur peut être remplacée par un attribut [`formenctype`](https://devel If this attribute is specified, it overrides the [`enctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-enctype) attribute of the button's form owner. Si le bouton est un bouton d’envoi, cet attribut spécifie le type de contenu utilisé pour envoyer le formulaire au serveur. Les valeurs admises sont : -* `application/x-www-form-urlencoded` : valeur par défaut si l’attribut n’est pas spécifié. -* `multipart/form-data` : utilisez cette valeur si vous utilisez un élément [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input « L’élément HTML <input> est utilisé pour créer des contrôles interactifs pour les formulaires Web afin d’accepter des données de la part de l’utilisateur ; une grande variété de types de données d’entrée et de widgets de contrôle sont disponibles, selon l’appareil et l’agent utilisateur. ») avec l’attribut [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-type) défini sur `file`. +* `application/x-www-form-urlencoded` : La valeur par défaut si l'attribut n'est pas spécifié. +* `multipart/form-data` : utilisez cette valeur si vous utilisez un élément [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "L'élément HTML <input> est utilisé pour créer des contrôles interactifs pour les formulaires Web afin d'accepter les données de l'utilisateur ; une grande variété de types de données d'entrée et de widgets de contrôle sont disponibles, en fonction du périphérique et de l'agent utilisateur.") élément avec l'élément Attribut [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-type) défini sur `file`. * `text/plain` -Si cet attribut est spécifié, il remplace l’attribut [`enctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-enctype) du propriétaire du formulaire du bouton. +Si cet attribut est spécifié, il remplace l'attribut [`enctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-enctype) du propriétaire du formulaire du bouton. @@ -455,9 +455,9 @@ If specified, this attribute overrides the [`method`](https://developer.mozilla. Si le bouton est un bouton d’envoi, cet attribut spécifie la méthode HTTP que le navigateur utilise pour envoyer le formulaire. Les valeurs admises sont : * `post` : les données du formulaire sont incluses dans le corps du formulaire et envoyées au serveur. -* `get` : les données du formulaire sont ajoutées à l’URI de l’attribut **form**, avec un '?' comme séparateur, puis l’URI obtenu est envoyé au serveur. Utilisez cette méthode lorsque le formulaire n’a pas d’effet secondaire et ne contient que des caractères ASCII. +* `get` : les données du formulaire sont ajoutées à l'URI de l'attribut **form**, avec un '?' comme séparateur, et l'URI résultant est envoyé au serveur. Utilisez cette méthode lorsque le formulaire n’a pas d’effet secondaire et ne contient que des caractères ASCII. -Si cet attribut est spécifié, il remplace l’attribut [`method`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-method) du formulaire associé au bouton. +S'il est spécifié, cet attribut remplace l'attribut [`method`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-method) du propriétaire du formulaire du bouton. @@ -472,7 +472,7 @@ Si cet attribut est spécifié, il remplace l’attribut [`method`](https://deve * `_blank`: Load the response into a new unnamed browsing context. * `_parent`: Load the response into the parent browsing context of the current one. If there is no parent, this option behaves the same way as `_self`. * `_top`: Load the response into the top-level browsing context (that is, the browsing context that is an ancestor of the current one, and has no parent). If there is no parent, this option behaves the same way as `_self`. - Si le bouton est un bouton d’envoi, cet attribut est un nom ou un mot clé indiquant où afficher la réponse reçue après l’envoi du formulaire. Il s’agit d’un nom ou d’un mot clé pour un _browsing context_ (par exemple, un onglet, une fenêtre ou un cadre en ligne). Si cet attribut est spécifié, il remplace l’attribut [`target`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-target) du propriétaire du formulaire du bouton. Les mots-clés suivants ont une signification particulière : + Si le bouton est un bouton d’envoi, cet attribut est un nom ou un mot clé indiquant où afficher la réponse reçue après l’envoi du formulaire. Il s'agit du nom ou du mot-clé d'un _contexte de navigation_ (par exemple, un onglet, une fenêtre ou un cadre en ligne). Si cet attribut est spécifié, il remplace l'attribut [`target`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-target) du propriétaire du formulaire du bouton. Les mots-clés suivants ont une signification particulière : * `_self` : charge la réponse dans le même contexte de navigation que le contexte actuel. Cette valeur est la valeur par défaut si l’attribut n’est pas spécifié. * `_blank` : charge la réponse dans un nouveau contexte de navigation sans nom. @@ -533,25 +533,25 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page Définit une directive pragma. L’attribut est nommé `**http-equiv**(alent)` parce que toutes les valeurs autorisées sont des noms d’en-têtes HTTP particuliers : * `"content-language"` -Définit la langue par défaut de la page. Cet attribut peut être remplacé par l’attribut [lang](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) sur n’importe quel élément. +Définit la langue par défaut de la page. Il peut être remplacé par l'attribut [lang](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) sur n'importe quel élément. -**Avertissement :** n’utilisez pas cette valeur, car elle est obsolète. Préférez l’attribut `lang` sur l’élément [`<html>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html « L’élément HTML <html> représente la racine, c’est-à-dire l’élément de plus haut niveau, d’un document HTML. On l’appelle aussi élément racine. Tous les autres éléments doivent être des descendants de cet élément. »). +**Avertissement :** n’utilisez pas cette valeur, car elle est obsolète. Préférez l'attribut `lang` sur le [`<html>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html "L'élément HTML <html> représente la racine (élément de niveau supérieur) d'un document HTML, il est donc également appelé élément racine. On l’appelle aussi élément racine. Tous les autres éléments doivent être des descendants de cet élément. »). * `"content-security-policy"` -Permet aux auteurs de pages de définir une [politique de contenu](https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives) pour la page active. Les politiques de contenu définissent surtout les origines de serveur autorisées et les points de terminaison de script, ce qui aide à se protéger contre les attaques de scripting inter-site. +Permet aux auteurs de pages de définir une [politique de contenu](https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives) pour la page actuelle. Les stratégies de contenu spécifient principalement les origines de serveur autorisées et les points de terminaison de script qui aident à se prémunir contre les attaques de scripts intersites. * `"content-type"` -Définit le [type MIME](https://developer.mozilla.org/en-US/docs/Glossary/MIME_type) du document, suivi de son encodage de caractères. Il suit la même syntaxe que le champ d’en-tête d’entité HTTP `content-type`, mais comme il se trouve dans une page HTML, la plupart des valeurs autres que `text/html` sont impossibles. Par conséquent, la syntaxe valide de son `content` est la chaîne `text/html` suivie d’un jeu de caractères avec la syntaxe suivante : `; charset=_IANAcharset_`, où `IANAcharset` est le _nom MIME préféré_ pour un jeu de caractères, tel que [défini par l’IANA.](https://www.iana.org/assignments/character-sets) +Définit le [type MIME](https://developer.mozilla.org/en-US/docs/Glossary/MIME_type) du document, suivi de son encodage de caractères. Il suit la même syntaxe que le champ d'en-tête d'entité HTTP `content-type`, mais comme il se trouve à l'intérieur d'une page HTML, la plupart des valeurs autres que `text/html` sont impossibles. Par conséquent, la syntaxe valide pour son `content` est la chaîne '`text/html`' suivie d'un jeu de caractères avec la syntaxe suivante : '`; charset=_IANAcharset_`', où `IANAcharset` est le _nom MIME préféré_ pour un jeu de caractères comme [défini par l'IANA.](https://www.iana.org/assignments/character-sets) -**Avertissement :** n’utilisez pas cette valeur, car elle est obsolète. Utilisez l’attribut [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) sur l’élément [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta « L’élément HTML <meta> représente des métadonnées qui ne peuvent pas être représentées par d’autres éléments html liés aux métadonnées, comme <base>, <link>, élément <script>, <style> ou <title> »). +**Avertissement :** n’utilisez pas cette valeur, car elle est obsolète. Utilisez l'attribut [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) sur le [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "L'élément HTML <meta> représente des métadonnées qui ne peuvent pas être représentées par d'autres éléments HTML liés aux méta, comme <base>, <link>, <script>, <style> ou <title>.") élément. -**Remarque :** En tant que [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta « L’élément HTML <meta> représente des métadonnées qui ne peuvent pas être représentées par d’autres éléments liés aux métadonnées HTML, comme <base>, <link>, <script>, <style> ou <title>. ») ne peut pas modifier les types de documents dans la sérialisation XHTML ou HTML5, et ne jamais définir le type MIME sur un type MIME XHTML avec `<meta>`. +**Remarque :** Comme [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "L'élément HTML <meta> représente des métadonnées qui ne peuvent pas être représentées par d'autres éléments HTML liés aux métadonnées, comme <base>, <link>, <script>, <style> ou <title>.") ne peut pas modifier les types de documents en XHTML ou la sérialisation XHTML de HTML5, ne définissez jamais le type MIME sur un Tapez XHTML MIME avec `<meta>`. * `"refresh"` Cette instruction spécifie : * Nombre de secondes avant le rechargement de la page, uniquement si l’attribut [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) contient un entier positif. * Nombre de secondes avant que la page ne soit redirigée vers une autre, uniquement si l’attribut [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) contient un entier positif suivi de la chaîne '`;url=`', et d’une URL valide. * `"set-cookie"` -Définit un [cookie](https://developer.mozilla.org/en-US/docs/cookie) pour la page. Son contenu doit suivre la syntaxe définie dans la [spécification du cookie HTTP IETF](https://tools.ietf.org/html/draft-ietf-httpstate-cookie-14). +Définit un [cookie](https://developer.mozilla.org/en-US/docs/cookie) pour la page. Son contenu doit suivre la syntaxe définie dans la [Spécification des cookies HTTP de l'IETF](https://tools.ietf.org/html/draft-ietf-httpstate-cookie-14). **Avertissement :** N’utilisez pas cette instruction, car elle est obsolète. Utilisez plutôt l’en-tête HTTP [`Set-Cookie`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie). @@ -587,7 +587,7 @@ Définit un [cookie](https://developer.mozilla.org/en-US/docs/cookie) pour la pa **Note:** This attribute is allowed only if the `<img>` element is a descendant of an [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "The HTML <a> element (or anchor element) creates a hyperlink to other web pages, files, locations within the same page, email addresses, or any other URL.") element with a valid [`href`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-href) attribute. Cet attribut booléen indique que l’image fait partie d’une image cliquable côté serveur. Si c’est le cas, les coordonnées précises d’un clic sont envoyées au serveur. -**Remarque :** cet attribut n’est autorisé que si l’élément `<img>` est un descendant d’un élément [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a « L’élément HTML <a> (ou élément d’ancrage) crée un lien hypertexte vers d’autres pages Web, fichiers, emplacements dans la même page, adresses e-mail ou toute autre URL. ») avec un attribut [`href`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-href) valide. +**Remarque :** Cet attribut n'est autorisé que si l'élément `<img>` est un descendant d'un élément [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "L'élément HTML <a> (ou l'élément d'ancrage) crée un lien hypertexte vers d'autres pages Web, fichiers, emplacements dans la même page, adresses e-mail ou toute autre URL.") avec un élément valide Attribut [`href`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-href). @@ -632,14 +632,14 @@ Définit un [cookie](https://developer.mozilla.org/en-US/docs/cookie) pour la pa * Chapter titles are intended to be used when the user is navigating the media resource. * `metadata` * Tracks used by scripts. Not visible to the user. - Comment la piste de texte est censée être utilisée. Si vous l’omettez, le type par défaut est `subtitles`. Si l’attribut n’est pas présent, il utilise `subtitles`. Si l’attribut contient une valeur non valide, il utilise des `metadata`. (Les versions de Chrome antérieures à 52 considéraient une valeur non valide comme `subtitles`.) Les mots-clés suivants sont autorisés : + Comment la piste de texte est censée être utilisée. S'il est omis, le type par défaut est `subtitles`. Si l'attribut n'est pas présent, il utilisera les `subtitles`. Si l'attribut contient une valeur non valide, il utilisera de `metadata`. (Les versions de Chrome antérieures à 52 considéraient une valeur non valide comme `subtitles`.) Les mots-clés suivants sont autorisés : * `subtitles` -* Les sous-titres fournissent une traduction du contenu que le spectateur ne peut pas comprendre. Par exemple, les dialogues ou le texte qui ne sont pas en anglais dans un film en anglais. +* Les sous-titres fournissent une traduction du contenu qui ne peut pas être compris par le spectateur. Par exemple, les dialogues ou le texte qui ne sont pas en anglais dans un film en anglais. * Les sous-titres peuvent contenir du contenu supplémentaire, généralement des informations de contexte supplémentaires. Par exemple, le texte au début des films Star Wars, ou la date, l’heure et le lieu d’une scène. * `captions` -* Les sous-titres fermés fournissent une transcription et éventuellement une traduction de l’audio. -* Ils peuvent inclure des informations non verbales importantes, comme des indications musicales ou des effets sonores. Ils peuvent indiquer la source de l’indication (par exemple, musique, texte, personnage). +* Les sous-titres codés fournissent une transcription et éventuellement une traduction de l'audio. +* Ils peuvent inclure des informations non verbales importantes, comme des indications musicales ou des effets sonores. Cela peut indiquer la source de l'indication (par exemple, musique, texte, personnage). * Convient aux personnes malentendantes ou lorsque le son est coupé. * `descriptions` * Description textuelle du contenu vidéo. @@ -647,7 +647,7 @@ Définit un [cookie](https://developer.mozilla.org/en-US/docs/cookie) pour la pa * `chapters` * Les titres de chapitres sont destinés à être utilisés lorsque l’utilisateur navigue dans la ressource multimédia. * `metadata` -* Pistes utilisées par les scripts. Non visibles pour l’utilisateur. +* Pistes utilisées par les scripts. Non visible de l’utilisateur. @@ -696,7 +696,7 @@ Définit un [cookie](https://developer.mozilla.org/en-US/docs/cookie) pour la pa **Remarques :** -* En HTML 4, il ne peut s’agir que d’une simple liste de littéraux de description de média séparés par des espaces, c’est-à-dire [media types and groups](https://developer.mozilla.org/en-US/docs/Web/CSS/@media), lorsqu’ils sont définis et autorisés comme valeurs de cet attribut, comme `print`, `screen`, `aural`, `braille`. HTML5 a étendu cela à tout type de [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries), qui constituent un sur-ensemble des valeurs autorisées en HTML 4. +* En HTML 4, il ne peut s'agir que d'une simple liste de littéraux de description de média, séparés par des espaces blancs, c'est-à-dire [types et groupes de médias](https://developer.mozilla.org/en-US/docs/Web/CSS/@media), lorsqu'ils sont définis et autorisés comme valeurs pour cet attribut, tels que `print`, `screen`, `aural`, `braille`. HTML5 a étendu cela à tout type de [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries), qui constituent un sur-ensemble des valeurs autorisées en HTML 4. * Les navigateurs qui ne prennent pas en charge les [CSS3 Media Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries) ne reconnaîtront pas nécessairement le bon lien. N’oubliez pas de définir des liens de secours, avec le jeu restreint de requêtes média défini dans HTML 4. @@ -711,10 +711,10 @@ This value can be overridden by a [`formmethod`](https://developer.mozilla.org/e Méthode [HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) utilisée par le navigateur pour envoyer le formulaire. Les valeurs admises sont : * `post` : correspond à la méthode HTTP [POST](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5). Les données du formulaire sont incluses dans le corps du formulaire et envoyées au serveur. -* `get` : correspond à la méthode HTTP [GET](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3) ; les données du formulaire sont ajoutées à l’URI de l’attribut `action`, séparées par un « ? », puis l’URI résultant est envoyée au serveur. Utilisez cette méthode lorsque le formulaire n’a pas d’effet secondaire et ne contient que des caractères ASCII. -* `dialog` : à utiliser lorsque le formulaire se trouve à l’intérieur d’un élément [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog « L’élément HTML <dialog> représente une boîte de dialogue ou un autre composant interactif, tel qu’un inspecteur ou une fenêtre. ») pour fermer la boîte de dialogue lors de l’envoi. +* `get` : Correspond au HTTP [méthode GET](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3) ; les données du formulaire sont ajoutées à l'URI de l'attribut `action` avec un ? » comme séparateur, et l'URI résultant est envoyé au serveur. Utilisez cette méthode lorsque le formulaire n’a pas d’effet secondaire et ne contient que des caractères ASCII. +* `dialog` : à utiliser lorsque le formulaire se trouve à l'intérieur d'un élément [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog "L'élément HTML <dialog> représente une boîte de dialogue ou un autre composant interactif, tel qu'un inspecteur ou une fenêtre.") pour fermer la boîte de dialogue une fois soumise. -Cette valeur peut être remplacée par un attribut [`formmethod`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formmethod) sur un élément [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button « L’élément HTML <button> représente un bouton cliquable, que vous pouvez utiliser dans les formulaires ou partout dans un document nécessitant une fonctionnalité de bouton simple et standard. ») ou [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input « L’élément HTML <input> est utilisé pour créer des contrôles interactifs pour les formulaires Web afin de recevoir des données de l’utilisateur ; un large éventail de types de saisie et de contrôles est disponible, selon l’appareil et l’agent utilisateur. »). +Cette valeur peut être remplacée par un attribut [`formmethod`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formmethod) sur un [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "L'élément HTML <button> représente un bouton cliquable, qui peut être utilisé dans des formulaires ou n'importe où dans un document qui nécessite une fonctionnalité de bouton simple et standard.") ou [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "L'élément HTML <input> est utilisé pour créer des contrôles interactifs pour les formulaires Web afin d'accepter les données de l'utilisateur ; une grande variété de types de données d'entrée et de widgets de contrôle sont disponibles, en fonction de l'appareil et de l'agent utilisateur.") élément. @@ -1022,7 +1022,7 @@ Tout `noindex` -Demande au robot de ne pas indexer la page. +Requête au robot de ne pas indexer la page. Tout @@ -1034,7 +1034,7 @@ Tout `nofollow` -Demande au robot de ne pas suivre les liens de la page. +Requête au robot de ne pas suivre les liens de la page. Tout @@ -1052,7 +1052,7 @@ Empêche l’utilisation de la description de l’[Open Directory Project](https `noarchive` -Demande au moteur de recherche de ne pas mettre en cache le contenu de la page. +Requête au moteur de recherche de ne pas mettre en cache le contenu de la page. [Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives), [Yahoo](https://help.yahoo.com/kb/search-for-desktop/SLN2213.html), [Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) @@ -1064,7 +1064,7 @@ Empêche l’affichage de toute description de la page dans les résultats du mo `noimageindex` -Demande que cette page n’apparaisse pas comme page référente pour une image indexée. +Requête que cette page n'apparaisse pas comme page de référence d'une image indexée. [Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives) @@ -1546,7 +1546,7 @@ Consultez aussi : [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS Chaîne indiquant le référent à utiliser lors de la récupération de la ressource : * `no-referrer` signifie que l’en-tête [`Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer « L’en-tête de requête Referer contient l’adresse de la page web précédente depuis laquelle un lien vers la page actuellement demandée a été suivi. L’en-tête Referer permet aux serveurs d’identifier l’emplacement à partir duquel les personnes les visitent et peut utiliser ces données pour l’analytique, la journalisation ou la mise en cache optimisée, par exemple. ») l’en-tête n’est pas envoyé. -* `no-referrer-when-downgrade` signifie qu’aucun en-tête [`Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer « L’en-tête de demande du référent contient l’adresse de la page web précédente à partir de laquelle un lien vers la page actuellement demandée a été suivi. L’en-tête Referer permet aux serveurs d’identifier l’emplacement à partir duquel les personnes les visitent et peut utiliser ces données pour l’analytique, la journalisation ou la mise en cache optimisée, par exemple. ») n’est envoyé lors de la navigation vers une origine sans TLS (HTTPS). Il s’agit du comportement par défaut d’un agent utilisateur si aucune stratégie n’est spécifiée. +* `no-referrer-when-downgrade` signifie qu'aucun [`Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer "L'en-tête de requête Referer contient l'adresse de la page Web précédente à partir de laquelle un lien vers la page actuellement demandée a été suivi. L’en-tête Referer permet aux serveurs d’identifier l’emplacement à partir duquel les personnes les visitent et peut utiliser ces données pour l’analytique, la journalisation ou la mise en cache optimisée, par exemple. ») n’est envoyé lors de la navigation vers une origine sans TLS (HTTPS). Il s’agit du comportement par défaut d’un agent utilisateur si aucune stratégie n’est spécifiée. * `origin` signifie que le référent sera l’origine de la page, c’est-à-dire approximativement le schéma, l’hôte et le port. * `origin-when-cross-origin` signifie que la navigation vers d’autres origines se limitera au schéma, à l’hôte et au port, tandis que la navigation sur la même origine inclura le chemin du référent. * `unsafe-url` signifie que le référent inclura l’origine et le chemin, mais pas le fragment, le mot de passe ni le nom d’utilisateur. Ce cas n’est pas sécurisé, car il peut divulguer des origines et des chemins de ressources protégées par TLS vers des origines non sécurisées. @@ -1604,7 +1604,7 @@ Consultez aussi : [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS * `allow-presentation` : permet à la ressource de démarrer une [session de présentation](https://developer.mozilla.org/en-US/docs/Web/API/PresentationRequest). * `allow-same-origin`: si ce jeton n’est pas utilisé, la ressource est traitée comme provenant d’une origine spéciale qui échoue toujours à la [stratégie de même origine](https://developer.mozilla.org/en-US/docs/Glossary/same-origin_policy « stratégie de même origine : la stratégie de même origine est un mécanisme de sécurité critique qui limite la façon dont un document ou un script chargé à partir d’une origine peut interagir avec une ressource d’une autre origine. »). * `allow-scripts` : permet à la ressource d’exécuter des scripts, mais pas de créer des fenêtres contextuelles. -* `allow-storage-access-by-user-activation` : permet à la ressource de demander l’accès aux capacités de stockage du parent avec l’[API Storage Access](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API). +* `allow-storage-access-by-user-activation` : permet à la ressource de demander l'accès aux capacités de stockage du parent avec l'[API d'accès au stockage](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API). * `allow-top-navigation` : permet à la ressource de naviguer dans le contexte de navigation de niveau supérieur, c’est-à-dire celui nommé `_top`. * `allow-top-navigation-by-user-activation` : permet à la ressource de naviguer dans le contexte de navigation de niveau supérieur, mais uniquement si l’action est déclenchée par une intervention de l’utilisateur. @@ -1673,7 +1673,7 @@ Consultez aussi : [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS This attribute specifies the URI of an external script; this can be used as an alternative to embedding a script directly within a document. If a `script` element has a `src` attribute specified, it should not have a script embedded inside its tags. - Cet attribut spécifie l’URI d’un script externe ; cela peut servir d’alternative à l’incorporation directe d’un script dans un document. + Cet attribut spécifie l'URI d'un script externe ; cela peut être utilisé comme alternative à l’intégration d’un script directement dans un document. Si un élément `script` a un attribut `src` spécifié, il ne doit contenir aucun script entre ses balises. @@ -1735,7 +1735,7 @@ L’agent utilisateur sélectionne l’une des sources disponibles à sa discré * a _negative value_ means that the element should be focusable, but should not be reachable via sequential keyboard navigation; * `0` means that the element should be focusable and reachable via sequential keyboard navigation, but its relative order is defined by the platform convention; * a _positive value_ means that the element should be focusable and reachable via sequential keyboard navigation; the order in which the elements are focused is the increasing value of the [**tabindex**](https://developer.mozilla.org/docs/Web/HTML/Reference/Global_attributes/tabindex#attr-tabindex). If several elements share the same tabindex, their relative order follows their relative positions in the document. - Attribut entier indiquant si l’élément peut recevoir le focus de saisie (est _focusable_), s’il doit participer à la navigation séquentielle au clavier et, si oui, à quelle position. Il peut prendre plusieurs valeurs : + Un attribut entier indiquant si l'élément peut recevoir le focus de saisie (s'il est _focusable_), s'il doit participer à la navigation séquentielle au clavier et, le cas échéant, à quelle position. Il peut prendre plusieurs valeurs : * un _negative value_ signifie que l’élément doit pouvoir recevoir le focus, mais ne doit pas être atteignable par navigation séquentielle au clavier ; * `0` signifie que l’élément doit pouvoir recevoir le focus et être atteignable par navigation séquentielle au clavier, mais que son ordre relatif est défini par la convention de la plateforme ; diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.it.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.it.xlf index 8995b8775e591..3a084de69459f 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.it.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.it.xlf @@ -647,7 +647,7 @@ Definisce un [cookie](https://developer.mozilla.org/en-US/docs/cookie) per la pa * `chapters` * I titoli dei capitoli sono pensati per essere usati quando l'utente naviga nella risorsa multimediale. * `metadata` -* Tracce usate dagli script. Non visibili all'utente. +* Tracce usate dagli script. Non visibile all'utente. diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.ja.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.ja.xlf index 6880059d0881a..99d2434045bb7 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.ja.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.ja.xlf @@ -95,7 +95,7 @@ See [Browser compatibility](https://developer.mozilla.org/docs/Web/HTML/Referenc 動的に挿入されたスクリプト ([`document.createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement "HTML ドキュメントでは、document.createElement() メソッドは tagName で指定された HTML 要素、または tagName が認識されない場合は HTMLUnknownElement を作成します。")) は既定で非同期的に読み込まれるため、同期読み込みを有効にするため (つまり、スクリプトが挿入された順序で読み込まれる) には、`async="false"` を設定します。 -ブラウザーのサポートに関する注意事項については、[Browser compatibility](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/script#Browser_compatibility) を参照してください。また、[asm.js の非同期スクリプト](https://developer.mozilla.org/en-US/docs/Games/Techniques/Async_scripts) も参照してください。 +ブラウザーのサポートに関する注意事項については、[ブラウザーの互換性](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/script#Browser_compatibility) を参照してください。また、[asm.js の非同期スクリプト](https://developer.mozilla.org/en-US/docs/Games/Techniques/Async_scripts) も参照してください。 @@ -243,7 +243,7 @@ If the attribute is not present, the resource is fetched without a [CORS](https: `anonymous` -クロスオリジン要求 (つまり[`Origin`)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin "Origin 要求ヘッダーは、フェッチの送信元を示します。パス情報は含まれませんが、サーバー名のみが含まれます。CORS 要求と POST 要求で送信されます。これは Referer ヘッダーに似ていますが、このヘッダーとは異なり、パス全体は開示されません。) HTTP ヘッダー) は実行されますが、(Cookie、X.509 証明書、HTTP 基本認証など) 資格情報は送信されません。サーバーがオリジン サイトへの認証情報を提供しない場合 ([`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin "Access-Control-Allow-Origin レスポンスヘッダーは、指定されたオリジンからの要求コードとレスポンスを共有できるかどうかを示す。") HTTPヘッダー) を設定しない場合、画像は汚染され、その使用が制限されます。 +クロスオリジン要求 (つまり[`Origin`)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin "Origin 要求ヘッダーは、フェッチの送信元を示します。パス情報は含まれませんが、サーバー名のみが含まれます。CORS 要求と POST 要求で送信されます。これは Referer ヘッダーに似ていますが、このヘッダーとは異なり、パス全体は開示されません。) HTTP ヘッダー) は実行されますが、(Cookie、X.509 証明書、HTTP 基本認証など) 資格情報は送信されません。サーバーがオリジン サイトへの認証情報を提供しない場合 ([`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin "Access-Control-Allow-Origin 応答ヘッダーは、指定されたオリジンからの要求コードと応答を共有できるかどうかを示す。") HTTPヘッダー) を設定しない場合、画像は汚染され、その使用が制限されます。 `use-credentials` @@ -325,8 +325,8 @@ To achieve a similar effect for dynamically inserted scripts use `async="false"` 要素のテキストの方向性を示す列挙型属性です。以下の値を指定できます: * `ltr`: _lef to right_ を意味し、左から右に書かれる言語 (英語など) に使用します。 -* `rtl`:_right to left_ を意味し、右から左に書く言語 (アラビア語など) に使用します。* `auto`: ユーザー エージェントに決定を委ねます。 -ユーザー エージェントは、要素内の文字を解析し、明確な方向性を持つ文字が見つかるまで処理を進め、その方向性を要素全体に適用します。 +* `rtl`:_right to left_ を意味し、右から左に書く言語 (アラビア語など) に使用します。 +* `auto`: ユーザー エージェントに決定を委ねます。ユーザー エージェントは、要素内の文字を解析し、明確な方向性を持つ文字が見つかるまで処理を進め、その方向性を要素全体に適用します。 @@ -540,7 +540,7 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page * `"content-security-policy"` ページ作成者が現在のページの [content policy](https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives) を定義できるようにします。コンテンツ ポリシーでは、ほとんどの場合、許可されているサーバー オリジンとスクリプト エンドポイントが指定されています。これは、クロスサイト スクリプティング攻撃から保護するのに役立ちます。 * `"content-type"` -ドキュメントの [MIME type](https://developer.mozilla.org/en-US/docs/Glossary/MIME_type) を定義し、その後に文字エンコーディングが続きます。これはHTTPの `content-type` エンティティヘッダーフィールドと同じ構文を採用していますが、HTML ページ内にあるため、`text/html` 以外の値はほとんど使用できません。したがって、その `content` の有効な構文は、文字列 '`text/html`' の後に、次の構文を持つ文字セットが続くものです: "`; charset=_IANAcharset_`'。ここで、`IANAcharset` は、[defined by the IANA (IANA によって定義された)](https://www.iana.org/assignments/character-sets) 文字セットの _preferred MIME name_ です。 +ドキュメントの [MIME type](https://developer.mozilla.org/ja-JP/docs/Glossary/MIME_type) を定義し、その後に文字エンコーディングが続きます。これは HTTP の `content-type` エンティティ ヘッダー フィールドと同じ構文を採用していますが、HTML ページ内にあるため、`text/html` 以外の値はほとんど使用できません。したがって、その `content` の有効な構文は、文字列 '`text/html`' の後に、次の構文を持つ文字セットが続くものです: "`; charset=_IANAcharset_`'。ここで、`IANAcharset` は、[IANA によって定義された](https://www.iana.org/assignments/character-sets) 文字セットの _preferred MIME name_ です。 **警告:** この値は廃止されているため、使用しないでください。[`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML の <meta> 要素は、<base>、<link>、<script>、<style>、または <title> といった他の HTML メタ関連要素では表現できないメタデータを表します。") 要素で [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) 属性を使用してください。 @@ -696,8 +696,8 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page **注:** -* HTML 4 では、これは単に空白で区切られたメディア記述リテラル (すなわち、[media types and groups](https://developer.mozilla.org/en-US/docs/Web/CSS/@media)) のリストに限られます。これらは、`print`、`screen`、`aural`、`braille` など、この属性の値として定義され、許可されているものです。HTML5 では、これがあらゆる種類の [メディアクエリ](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries) に拡張されました。これらはHTML 4で許可されていた値のスーパーセットです。 -* [CSS3 Media Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries) をサポートしていないブラウザーでは、適切なリンクが認識されない場合があります。HTML 4 で定義されている限定的なメディア クエリのセットを、フォールバック リンクとして設定することを忘れないでください。 +* HTML 4 では、これは単に空白で区切られたメディア記述リテラル (すなわち、[media types and groups](https://developer.mozilla.org/ja-JP/docs/Web/CSS/@media)) のリストに限られます。これらは、`print`、`screen`、`aural`、`braille` など、この属性の値として定義され、許可されているものです。HTML5 では、これがあらゆる種類の [メディア クエリ](https://developer.mozilla.org/ja-JP/docs/Web/CSS/Media_queries) に拡張されました。これらはHTML 4で許可されていた値のスーパーセットです。 +* [CSS3 メディア クエリ](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries) をサポートしていないブラウザーでは、適切なリンクが認識されない場合があります。HTML 4 で定義されている限定的なメディア クエリのセットを、フォールバック リンクとして設定することを忘れないでください。 @@ -1517,7 +1517,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo **Note:** Placeholders should only be used to show an example of the type of data that should be entered into a form; they are _not_ a substitute for a proper [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label "The HTML <label> element represents a caption for an item in a user interface.") element tied to the input. See [Labels and placeholders](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Labels_and_placeholders "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") in [<input>: The Input (Form Input) element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") for a full explanation. コントロールに入力できる内容のヒント。プレースホルダー テキスト内の復帰または改行は、ヒントをレンダリングするときに改行として扱う必要があります。 -**注:** プレースホルダーは、フォームに入力する必要があるデータの種類の例を示すためにのみ使用する必要があります。入力に関連付けられた適切な [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label "HTML <label> 要素はユーザー インターフェイス内の項目のキャプションを表します。") 要素の代わりに _not_ を指定します。[Labels and placeholders](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Labels_and_placeholders "HTML の <input> 要素は、ユーザーからのデータを受け付けるための Web フォーム用インタラクティブ コントロールの作成に使用されます。デバイスやユーザー エージェントに応じて、多種多様な入力データやコントロール ウィジェットが利用可能です。") の [<input>: Input (フォーム入力) 要素](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML の <input> 要素は、ユーザーからのデータを受け付けるための Web ベースのフォーム用インタラクティブ コントロールを作成するために使用されます。デバイスやユーザー エージェントに応じて、多種多様な入力データやコントロール ウィジェットが利用可能です。") を参照してください。 +**注:** プレースホルダーは、フォームに入力する必要があるデータの種類の例を示すためにのみ使用する必要があります。入力に関連付けられた適切な [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label "HTML <label> 要素はユーザー インターフェイス内の項目のキャプションを表します。") 要素の代わりに _not_ を指定します。[ラベルとプレースホルダー](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Labels_and_placeholders "HTML の <input> 要素は、ユーザーからのデータを受け付けるための Web フォーム用インタラクティブ コントロールの作成に使用されます。デバイスやユーザー エージェントに応じて、多種多様な入力データやコントロール ウィジェットが利用可能です。") の [<input>: Input (フォーム入力) 要素](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML の <input> 要素は、ユーザーからのデータを受け付けるための Web ベースのフォーム用インタラクティブ コントロールを作成するために使用されます。デバイスやユーザー エージェントに応じて、多種多様な入力データやコントロール ウィジェットが利用可能です。") を参照してください。 @@ -1596,8 +1596,8 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo フレーム内のコンテンツに追加の制限を適用します。属性の値は、すべての制限を適用するために空にすることも、スペースで区切られたトークンを使用して特定の制限を解除することもできます。 * `allow-forms`: リソースがフォームを送信できるようにします。このキーワードを使用しない場合、フォームの送信はブロックされます。 -* `allow-modals`: リソースが [open modal windows (モーダル ウィンドウで開ける)](https://html.spec.whatwg.org/multipage/origin.html#sandboxed-modals-flag) ようにします。 -* `allow-orientation-lock`: リソースの [lock the screen orientation (画面の向きをロック)](https://developer.mozilla.org/en-US/docs/Web/API/Screen/lockOrientation) します。 +* `allow-modals`: リソースが [モーダル ウィンドウで開ける](https://html.spec.whatwg.org/multipage/origin.html#sandboxed-modals-flag) ようにします。 +* `allow-orientation-lock`: リソースの [画面の向きをロック](https://developer.mozilla.org/en-US/docs/Web/API/Screen/lockOrientation) します。 * `allow-pointer-lock`: リソースで [Pointer Lock API](https://developer.mozilla.org/en-US/docs/WebAPI/Pointer_Lock) を使用できるようにします。 * `allow-popups`: ポップアップ (`window.open()`、`target="_blank"`、`showModalDialog()` など) を許可します。このキーワードを使用しない場合、ポップアップはサイレントで開かなくなります。 * `allow-popups-to-escape-sandbox`: サンドボックス化されたドキュメントで、サンドボックスを継承するウィンドウなしで新しいウィンドウを開くことができます。たとえば、広告がリンクされているページに同じ制限を適用することなく、広告を安全にサンドボックス化できます。 @@ -1712,7 +1712,7 @@ The user agent selects any one of the available sources at its discretion. This 幅記述子とピクセル密度記述子を同じ `srcset` 属性に混在させるのは正しくありません。重複する記述子 (たとえば、両方とも '`2x`' で記述されている同じ `srcset` 内の 2 つのソース) も無効です。 -ユーザー エージェントは、任意の判断で利用可能なソースのいずれかを選択します。これにより、ユーザー設定や帯域幅の条件などに基づいて選択を調整するための重要な余裕が提供されます。例については、[Responsive images](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images) チュートリアルを参照してください。 +ユーザー エージェントは、任意の判断で利用可能なソースのいずれかを選択します。これにより、ユーザー設定や帯域幅の条件などに基づいて選択を調整するための重要な余裕が提供されます。例については、[レスポンシブ画像](https://developer.mozilla.org/ja-JP/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images) チュートリアルを参照してください。 @@ -1735,7 +1735,7 @@ The user agent selects any one of the available sources at its discretion. This * a _negative value_ means that the element should be focusable, but should not be reachable via sequential keyboard navigation; * `0` means that the element should be focusable and reachable via sequential keyboard navigation, but its relative order is defined by the platform convention; * a _positive value_ means that the element should be focusable and reachable via sequential keyboard navigation; the order in which the elements are focused is the increasing value of the [**tabindex**](https://developer.mozilla.org/docs/Web/HTML/Reference/Global_attributes/tabindex#attr-tabindex). If several elements share the same tabindex, their relative order follows their relative positions in the document. - 要素が入力フォーカスを取得できるかどうかを示す整数属性 (_focusable_)、シーケンシャル キーボード ナビゲーションに参加する必要があるかどうか、その場合はどの位置にあるかを示します。いくつかの値を取る可能性があります: + 要素が入力フォーカスを取得できるかどうかを示す整数属性 (is _focusable_)、シーケンシャル キーボード ナビゲーションに参加する必要があるかどうか、その場合はどの位置にあるかを示します。いくつかの値を取る可能性があります: * _negative value_ は、要素がフォーカス可能である必要がありますが、シーケンシャル キーボード ナビゲーションを介して到達できないことを意味します。 * `0` は、要素がシーケンシャル キーボード ナビゲーションを介してフォーカス可能で到達可能である必要があることを意味しますが、その相対順序はプラットフォーム規則によって定義されます。 @@ -1786,7 +1786,7 @@ If this attribute is specified, this element must come before any other elements The partial URL (starting with '#') of an [image map](https://developer.mozilla.org/en-US/docs/HTML/Element/map) associated with the element. **Note:** You cannot use this attribute if the `<img>` element is a descendant of an [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "The HTML <a> element (or anchor element) creates a hyperlink to other web pages, files, locations within the same page, email addresses, or any other URL.") or [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "The HTML <button> element represents a clickable button, which can be used in forms or anywhere in a document that needs simple, standard button functionality.") element. - 要素に関連付けられている [image map](https://developer.mozilla.org/en-US/docs/HTML/Element/map) の部分的な URL ('#' で始まる)。 + 要素に関連付けられている [イメージ マップ](https://developer.mozilla.org/en-US/docs/HTML/Element/map) の部分的な URL ('#' で始まる)。 **注:** `<img>` 要素が [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "HTML の <a> 要素 (アンカー要素) は、他の Web ページ、ファイル、同じページ内の場所、メール アドレス、またはその他の URL へのハイパーリンクを作成します。") または [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "HTML の <button> 要素は、クリック可能なボタンを表します。これは、フォームや、シンプルで標準的なボタン機能が必要なドキュメント内の任意の場所で使用できます。") 要素の子孫である場合、この属性は使用できません。 diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.ko.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.ko.xlf index 8b87cc086aac2..76fb187105bd8 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.ko.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.ko.xlf @@ -10,8 +10,8 @@ A space- or comma-delimited list of character encodings that the server accepts. The browser uses them in the order in which they are listed. The default value, the reserved string `"UNKNOWN"`, indicates the same encoding as that of the document containing the form element. In previous versions of HTML, the different character encodings could be delimited by spaces or commas. In HTML5, only spaces are allowed as delimiters. - 서버에서 허용하는 문자 인코딩의 공백 또는 쉼표로 구분된 목록입니다. 브라우저는 나열된 순서대로 사용합니다. 기본값인 예약된 문자열 `"UNKNOWN"`은 양식 요소가 포함된 문서와 같은 인코딩을 나타냅니다. -HTML의 이전 버전에서는 서로 다른 문자 인코딩을 공백이나 쉼표로 구분할 수 있었습니다. HTML5에서는 구분 기호로 공백만 허용됩니다. + 서버에서 허용하는 문자 인코딩의 공백 또는 쉼표로 구분된 목록입니다. 브라우저는 나열된 순서대로 사용합니다. 기본값인 예약 문자열 `"UNKNOWN"`은 양식 요소를 포함한 문서의 인코딩과 같은 인코딩을 나타냅니다. +이전 버전의 HTML에서는 서로 다른 문자 인코딩을 공백이나 쉼표로 구분할 수 있었습니다. HTML5에서는 구분 기호로 공백만 허용됩니다. @@ -35,7 +35,7 @@ HTML의 이전 버전에서는 서로 다른 문자 인코딩을 공백이나 * **Do not use this attribute**, as it has been deprecated. The [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table "The HTML <table> element represents tabular data — that is, information presented in a two-dimensional table comprised of rows and columns of cells containing data.") element should be styled using [CSS](https://developer.mozilla.org/en-US/docs/CSS). Set [`margin-left`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left "The margin-left CSS property sets the margin area on the left side of an element. A positive value places it farther from its neighbors, while a negative value places it closer.") and [`margin-right`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right "The margin-right CSS property sets the margin area on the right side of an element. A positive value places it farther from its neighbors, while a negative value places it closer.") to `auto` or [`margin`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin "The margin CSS property sets the margin area on all four sides of an element. It is a shorthand for margin-top, margin-right, margin-bottom, and margin-left.") to `0 auto` to achieve an effect that is similar to the align attribute. * Prior to Firefox 4, Firefox also supported the `middle`, `absmiddle`, and `abscenter` values as synonyms of `center`, in quirks mode only. - 이 열거형 특성은 포함하는 문서 안에서 표를 어떻게 정렬할지 나타냅니다. 다음 값을 사용할 수 있습니다. + 이 열거형 특성은 포함하는 문서 내에서 표를 정렬하는 방법을 나타냅니다. 다음 값을 사용할 수 있습니다. * left: 표를 문서의 왼쪽에 표시합니다. * center: 표를 문서의 가운데에 표시합니다. @@ -43,7 +43,7 @@ HTML의 이전 버전에서는 서로 다른 문자 인코딩을 공백이나 **사용 참고** -* **이 특성은 사용하지 마세요**. 더 이상 사용되지 않습니다. [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table "The HTML <table> element represents tabular data — that is, information presented in a two-dimensional table comprised of rows and columns of cells containing data.") 요소는 [CSS](https://developer.mozilla.org/en-US/docs/CSS)를 사용해 스타일을 지정해야 합니다. [`margin-left`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left "The margin-left CSS property sets the margin area on the left side of an element. A positive value places it farther from its neighbors, while a negative value places it closer.")와 [`margin-right`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right "The margin-right CSS property sets the margin area on the right side of an element. A positive value places it farther from its neighbors, while a negative value places it closer.")를 `auto`로 설정하거나, [`margin`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin "The margin CSS property sets the margin area on all four sides of an element. It is a shorthand for margin-top, margin-right, margin-bottom, and margin-left.")를 `0 auto`로 설정하면 align 특성과 비슷한 효과를 얻을 수 있습니다. +* **이 특성은 사용하지 마세요**, 더 이상 사용되지 않습니다. [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table "HTML <table> 요소는 표 형식 데이터를 나타냅니다. 즉, 데이터가 들어 있는 셀의 행과 열로 이루어진 2차원 표에 표시되는 정보입니다.") 요소는 [CSS](https://developer.mozilla.org/en-US/docs/CSS)를 사용해 스타일을 지정해야 합니다. [`margin-left`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left "margin-left CSS 속성은 요소의 왼쪽에 여백 영역을 설정합니다. 양수 값은 이웃 요소와 더 멀리 떨어뜨리고, 음수 값은 더 가깝게 둡니다.")와 [`margin-right`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right "margin-right CSS 속성은 요소의 오른쪽에 여백 영역을 설정합니다. 양수 값은 이웃 요소와 더 멀리 떨어뜨리고, 음수 값은 더 가깝게 둡니다.")를 `auto`로 설정하거나, [`margin`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin "margin CSS 속성은 요소의 네 면 모두에 여백 영역을 설정합니다. It is a shorthand for margin-top, margin-right, margin-bottom, and margin-left.")를 `0 auto`로 설정하면 align 특성과 비슷한 효과를 얻을 수 있습니다. * Firefox 4 이전에는 Firefox도 쿼크 모드에서만 `middle`, `absmiddle`, `abscenter` 값을 `center`의 동의어로 지원했습니다. @@ -67,9 +67,9 @@ This attribute is considered a legacy attribute and redefined as `allow="fullscr **Note:** Omitting this attribute altogether indicates that the image is a key part of the content, and no textual equivalent is available. Setting this attribute to an empty string (`alt=""`) indicates that this image is _not_ a key part of the content (decorative), and that non-visual browsers may omit it from rendering. 이 특성은 이미지에 대한 대체 텍스트 설명을 정의합니다. -**참고:** 브라우저가 항상 요소가 참조하는 이미지를 표시하는 것은 아닙니다. 그래픽을 표시하지 않는 브라우저, 사용자가 이미지 표시를 끈 경우, 또는 이미지가 올바르지 않거나 [지원되지 않는 형식](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/img#Supported_image_formats)인 경우가 그렇습니다. 이런 경우 브라우저는 이 요소의 `alt` 특성에 정의된 텍스트로 이미지를 대체할 수 있습니다. 이런 이유와 다른 이유들 때문에 가능한 한 `alt`에 유용한 값을 제공해야 합니다. +**참고:** 브라우저가 항상 요소가 참조하는 이미지를 표시하는 것은 아닙니다. 그래픽을 표시하지 않는 브라우저, 사용자가 이미지 표시를 끈 경우, 또는 이미지가 올바르지 않거나 [지원되지 않는 형식](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/img#Supported_image_formats)인 경우가 그렇습니다. 이런 경우 브라우저는 이 요소의 `alt` 특성에 정의된 텍스트로 이미지를 대신할 수 있습니다. 이런 이유와 다른 이유들 때문에 가능한 한 `alt`에 유용한 값을 제공해야 합니다. -**참고:** 이 특성을 아예 생략하면 이미지가 콘텐츠의 핵심 부분이고, 이에 해당하는 텍스트가 없음을 나타냅니다. 이 특성을 빈 문자열(`alt=""`)로 설정하면 이 이미지가 콘텐츠의 핵심 부분이 _아니며_ 장식용이고, 비시각적 브라우저는 렌더링에서 이를 생략할 수 있음을 나타냅니다. +**참고:** 이 특성을 아예 생략하면 이미지가 콘텐츠의 핵심 부분이고, 이에 해당하는 텍스트가 없음을 나타냅니다. 이 특성에 빈 문자열(`alt=""`)을 설정하면 이 이미지가 콘텐츠의 핵심 부분이 _아니며_(장식용), 비시각적 브라우저가 렌더링에서 이를 생략할 수 있음을 뜻합니다. @@ -89,13 +89,13 @@ Dynamically inserted scripts (using [`document.createElement()`](https://develop See [Browser compatibility](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/script#Browser_compatibility) for notes on browser support. See also [Async scripts for asm.js](https://developer.mozilla.org/en-US/docs/Games/Techniques/Async_scripts). 이 부울 특성은 가능하면 브라우저가 스크립트를 비동기적으로 로드해야 함을 나타냅니다. -이 특성은 `src` 특성이 없는 경우, 즉 인라인 스크립트에는 사용하면 안 됩니다. 이 경우에는 포함해도 효과가 없습니다. +`src` 특성이 없는 경우(즉, 인라인 스크립트의 경우) 이 특성을 사용하면 안 됩니다. 이 경우에는 포함해도 효과가 없습니다. 브라우저는 일반적으로 최악의 경우를 가정하고 HTML 구문 분석 중에 스크립트를 동기적으로 로드합니다. 즉, `async="false"`입니다. 동적으로 삽입된 스크립트([`document.createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement "HTML 문서에서 document.createElement() 메서드는 tagName으로 지정한 HTML 요소를 만들거나, tagName을 인식하지 못하면 HTMLUnknownElement를 만듭니다.") 사용)는 기본적으로 비동기적으로 로드됩니다. 따라서 동기식 로드를 사용하려면, 즉 스크립트가 삽입된 순서대로 로드되게 하려면 `async="false"`로 설정하세요. -브라우저 지원에 대한 참고 사항은 [Browser compatibility](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/script#Browser_compatibility)를 참조하세요. 또한 [Async scripts for asm.js](https://developer.mozilla.org/en-US/docs/Games/Techniques/Async_scripts)도 참조하세요. +브라우저 지원에 대한 참고 사항은 [브라우저 호환성](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/script#Browser_compatibility)을 참조하세요. [asm.js에 대한 비동기 스크립트](https://developer.mozilla.org/en-US/docs/Games/Techniques/Async_scripts)도 참조하세요. @@ -105,10 +105,9 @@ See [Browser compatibility](https://developer.mozilla.org/docs/Web/HTML/Referenc * `on` or `sentences`, the first letter of each sentence defaults to a capital letter; all other letters default to lowercase * `words`, the first letter of each word defaults to a capital letter; all other letters default to lowercase * `characters`, all letters should default to uppercase - 사용자가 텍스트를 입력하거나 편집할 때 텍스트가 자동으로 대문자로 바뀔지, 그리고 어떻게 바뀔지 제어합니다. + 사용자가 텍스트를 입력하거나 편집할 때 텍스트가 자동으로 대문자로 바뀔지, 그리고 어떻게 바뀔지 제어합니다. 다음 값을 사용할 수 있습니다. -사용할 수 있는 값은 다음과 같습니다. -* `off` 또는 `none`: 자동 대문자 표시를 적용하지 않습니다. 모든 문자는 기본적으로 소문자입니다. +* `off` 또는 `none`, 자동 캡슐화가 적용되지 않음(모든 문자 기본값은 소문자로) * `on` 또는 `sentences`: 각 문장의 첫 글자는 기본적으로 대문자이며, 나머지 문자는 기본적으로 소문자입니다. * `words`: 각 단어의 첫 글자는 기본적으로 대문자이며, 나머지 문자는 기본적으로 소문자입니다. * `characters`: 모든 문자는 기본적으로 대문자입니다. @@ -122,13 +121,13 @@ See [Browser compatibility](https://developer.mozilla.org/docs/Web/HTML/Referenc For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) setting the autocomplete attribute will not prevent a browser's password manager from asking the user if they want to store login fields (username and password), if the user permits the storage the browser will autofill the login the next time the user visits the page. See [The autocomplete attribute and login fields](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion#The_autocomplete_attribute_and_login_fields). **Note:** If you set `autocomplete` to `off` in a form because the document provides its own auto-completion, then you should also set `autocomplete` to `off` for each of the form's `input` elements that the document can auto-complete. For details, see the note regarding Google Chrome in the [Browser Compatibility chart](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form#compatChart). - 입력 요소의 값이 브라우저에서 기본적으로 자동 완성될 수 있는지 나타냅니다. 이 설정은 양식에 속한 요소의 `autocomplete` 특성으로 재정의할 수 있습니다. 가능한 값은 다음과 같습니다. + 입력 요소의 값이 기본적으로 브라우저에서 자동 완성될 수 있는지 여부를 나타냅니다. 이 설정은 양식에 속한 요소의 `autocomplete` 특성으로 재정의할 수 있습니다. 사용할 수 있는 값은 다음과 같습니다. * `off`: 사용자는 매번 각 필드에 값을 직접 입력해야 합니다. 또는 문서가 자체적인 자동 완성 방법을 제공합니다. 브라우저는 항목을 자동으로 완성하지 않습니다. * `on`: 브라우저는 사용자가 양식에 이전에 입력한 값을 기준으로 값을 자동으로 완성할 수 있습니다. -대부분의 최신 브라우저(Firefox 38+, Google Chrome 34+, IE 11+ 포함)에서는 autocomplete 특성을 설정해도 브라우저의 암호 관리자가 사용자에게 로그인 필드(사용자 이름 및 암호)를 저장할지 묻는 것을 막지 않습니다. 사용자가 저장을 허용하면 다음에 해당 페이지를 방문할 때 브라우저가 로그인 정보를 자동으로 채웁니다. 자세한 내용은 [The autocomplete attribute and login fields](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion#The_autocomplete_attribute_and_login_fields)를 참조하세요. -**참고:** 문서가 자체 자동 완성 기능을 제공하므로 양식에서 `autocomplete`를 `off`로 설정한 경우, 문서가 자동 완성할 수 있는 양식의 각 `input` 요소에도 `autocomplete`를 `off`로 설정해야 합니다. 자세한 내용은 [Browser Compatibility chart](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form#compatChart)에서 Google Chrome 관련 참고 사항을 확인하세요. +대부분의 최신 브라우저(Firefox 38+, Google Chrome 34+, IE 11+ 포함)에서는 autocomplete 특성을 설정해도 브라우저의 암호 관리자가 로그인 필드(사용자 이름 및 암호)를 저장할지 묻는 동작을 막지 않습니다. 사용자가 저장을 허용하면, 다음에 사용자가 페이지를 방문할 때 브라우저가 로그인 정보를 자동으로 채웁니다. 자세한 내용은 [The autocomplete attribute and login fields](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion#The_autocomplete_attribute_and_login_fields)를 참조하세요. +**참고:** 문서가 자체 자동 완성 기능을 제공하므로 양식에서 `autocomplete`를 `off`로 설정한 경우, 문서가 자동 완성할 수 있는 양식의 각 `input` 요소에도 `autocomplete`를 `off`로 설정해야 합니다. 자세한 내용은 [브라우저 호환성 차트](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form#compatChart)에서 Google Chrome에 대한 메모를 참조하세요. @@ -145,7 +144,7 @@ For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) sett This attribute contains a list of space-separated strings. Each string is the `id` of a group of cells that this header applies to. **Note:** Do not use this attribute as it is obsolete in the latest standard: use the [`scope`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th#attr-scope) attribute instead. - 이 특성은 공백으로 구분된 문자열 목록을 포함합니다. 각 문자열은 이 header가 적용되는 셀 그룹의 `id`입니다. + 공백으로 구분된 문자열 목록을 포함합니다. 각 문자열은 이 header가 적용되는 셀 그룹의 `id`입니다. **참고:** 최신 표준에서는 더 이상 사용되지 않으므로 이 특성을 사용하지 마세요. 대신 [`scope`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th#attr-scope) 특성을 사용하세요. @@ -168,23 +167,23 @@ For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) sett * This [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") element is only one part of the [algorithm to determine a page's character set](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Algorithm charset page"). The [`Content-Type` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) and any [Byte-Order Marks](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark "The definition of that term (Byte-Order Marks) has not been written yet; please consider contributing it!") override this element. * It is strongly recommended to define the character encoding. If a page's encoding is undefined, cross-scripting techniques are possible, such as the [`UTF-7` fallback cross-scripting technique](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7). * The [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") element with a `charset` attribute is a synonym for the pre-HTML5 `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">`, where _`IANAcharset`_ contains the value of the equivalent [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) attribute. This syntax is still allowed, although no longer recommended. - 이 특성은 페이지의 문자 인코딩을 선언합니다. [문자 인코딩에 대한 표준 IANA MIME 이름](https://www.iana.org/assignments/character-sets)이 들어 있어야 합니다. 표준은 특정 인코딩을 요구하지는 않지만, 다음을 권장합니다. + 이 특성은 페이지의 문자 인코딩을 선언합니다. [문자 인코딩에 대한 표준 IANA MIME 이름](https://www.iana.org/assignments/character-sets)을 포함해야 합니다. 표준은 특정 인코딩을 요구하지는 않지만, 다음을 권장합니다. * 작성자는 [`UTF-8`](https://developer.mozilla.org/en-US/docs/Glossary/UTF-8)을 사용하는 것이 좋습니다. * 작성자는 보안 위험을 피하기 위해 ASCII와 호환되지 않는 인코딩은 사용하면 안 됩니다. 이를 지원하지 않는 브라우저는 악성 콘텐츠를 HTML로 해석할 수 있습니다. 이런 문제는 `JIS_C6226-1983`, `JIS_X0212-1990`, `HZ-GB-2312`, `JOHAB`, ISO-2022 계열, EBCDIC 계열에서 발생합니다. -**참고:** ASCII와 호환되지 않는 인코딩은 8비트 코드 포인트 `0x20`~`0x7E`를 Unicode 코드 포인트 `0x0020`~`0x007E`에 매핑하지 않는 인코딩입니다) +**참고:** ASCII와 호환되지 않는 인코딩은 8비트 코드 포인트 `0x20`부터 `0x7E`까지를 유니코드 코드 포인트 `0x0020`부터 `0x007E`까지에 매핑하지 않는 인코딩입니다) -* 작성자는 `CESU-8`, `UTF-7`, `BOCU-1` 및/또는 `SCSU`를 사용하면 안 됩니다. 이 인코딩들을 이용한 [교차 사이트 스크립팅](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting) 공격이 입증되었기 때문입니다. -* 작성자는 `UTF-32`를 사용하면 안 됩니다. 모든 HTML5 인코딩 알고리즘이 이를 `UTF-16`과 구별할 수 있는 것은 아니기 때문입니다. +* 작성자는 `CESU-8`, `UTF-7`, `BOCU-1` 및/또는 `SCSU` 인코딩들을 이용한 [교차 사이트 스크립팅](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting) 공격이 입증되었으므로 이 인코딩을 사용하면 안 됩니다. +* 모든 HTML5 인코딩 알고리즘이 이를 `UTF-16`과 구별할 수 있는 것은 아니므로 작성자는 `UTF-32`를 사용하면 안 됩니다. **참고:** -* 선언한 문자 인코딩은 페이지를 저장한 인코딩과 일치해야 합니다. 이렇게 하면 문자가 깨지거나 보안 취약점이 생기는 일을 막을 수 있습니다. -* 인코딩을 선언하는 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> 요소는 다른 HTML 메타 관련 요소로는 표현할 수 없는 메타데이터를 나타냅니다. 예를 들어 <base>, <link>, <script>, <style> 또는 <title>가 있습니다.") 요소는 [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head "The HTML <head> element provides general information (metadata) about the document, including its title and links to its scripts and style sheets.") 요소 안에 있어야 하며, **HTML의 처음 1024바이트 안에** 있어야 합니다. 일부 브라우저는 인코딩을 선택하기 전에 해당 바이트만 보기 때문입니다. -* 이 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") 요소는 [페이지의 문자 집합을 결정하는 알고리즘](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Algorithm charset page")의 한 부분일 뿐입니다. [`Content-Type` 헤더](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type)와 모든 [바이트 순서 표시](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark "The definition of that term (Byte-Order Marks) has not been written yet; please consider contributing it!")는 이 요소보다 우선합니다. -* 문자 인코딩은 반드시 정의하는 것이 좋습니다. 페이지의 인코딩이 정의되지 않으면 [`UTF-7` 대체 크로스 스크립팅 기법](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7) 같은 크로스 스크립팅 기법이 가능해집니다. -* `charset` 특성이 있는 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") 요소는 HTML5 이전의 `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">`와 같은 의미입니다. 여기서 _`IANAcharset`_은 해당 [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) 특성의 값입니다. 이 구문은 여전히 허용되지만 더 이상 권장되지는 않습니다. +* 문자가 깨지거나 보안 취약점이 생기는 경우를 방지하기 위해 선언한 문자 인코딩은 페이지를 저장한 인코딩과 일치해야 합니다. +* 인코딩을 선언하는 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") 요소는 [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head "The HTML <head> element provides general information (metadata) about the document, including its title and links to its scripts and style sheets.") 요소 안에 있어야 하며, **HTML의 처음 1024바이트 안에** 있어야 합니다. 일부 브라우저는 인코딩을 선택하기 전에 해당 바이트만 보기 때문입니다. +* 이 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") 요소는 [페이지의 문자 집합을 결정하는 알고리즘](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Algorithm charset page")의 한 부분일 뿐입니다. [`Content-Type` 헤더](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) 및 [바이트 순서 표시](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark "해당 용어(바이트 순서 표시)의 정의가 아직 작성되지 않았습니다. 기여해 보세요!")가 이 요소를 재정의합니다. +* 문자 인코딩은 반드시 정의하는 것이 좋습니다. 페이지의 인코딩이 정의되지 않으면 [`UTF-7` 대체 크로스 스크립팅 기법](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7) 같은 교차 스크립팅 기법이 가능해집니다. +* `charset` 특성이 있는 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> 요소는 <base>, <link>, <script>, <style> 또는 <title> 같은 다른 HTML 메타 관련 요소로 나타낼 수 없는 메타데이터를 나타냅니다.”) 요소는 HTML5 이전의 `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">`과 동의어입니다. 여기서 _`IANAcharset`_에는 해당 [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) 특성의 값이 포함되어 있습니다. 이 구문은 더 이상 권장되지는 않지만 여전히 사용할 수 있습니다. @@ -217,7 +216,7 @@ For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) sett * `true` or the _empty string_, which indicates that the element must be editable; * `false`, which indicates that the element must not be editable. - 요소를 사용자가 편집할 수 있는지 여부를 나타내는 열거형 특성입니다. 편집할 수 있다면 브라우저가 위젯을 수정해 편집을 허용합니다. 이 특성은 다음 값 중 하나를 사용해야 합니다. + 사용자가 요소를 편집할 수 있어야 하는지 여부를 나타내는 열거형 특성입니다. 이 경우 브라우저는 편집을 허용하도록 위젯을 수정합니다. 이 특성은 다음 값 중 하나를 사용해야 합니다. * `true` 또는 _빈 문자열_: 요소를 편집할 수 있어야 함을 나타냅니다. * `false`: 요소를 편집할 수 없음을 나타냅니다. @@ -240,17 +239,17 @@ A cross-origin request (i.e. with an [`Origin`](https://developer.mozilla.org/en A cross-origin request (i.e. with an `Origin` HTTP header) is performed along with a credential sent (i.e. a cookie, certificate, and/or HTTP Basic authentication is performed). If the server does not give credentials to the origin site (through [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials "The Access-Control-Allow-Credentials response header tells browsers whether to expose the response to frontend JavaScript code when the request's credentials mode (Request.credentials) is "include".") HTTP header), the resource will be _tainted_ and its usage restricted. If the attribute is not present, the resource is fetched without a [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS: CORS (Cross-Origin Resource Sharing) is a system, consisting of transmitting HTTP headers, that determines whether browsers block frontend JavaScript code from accessing responses for cross-origin requests.") request (i.e. without sending the `Origin` HTTP header), preventing its non-tainted usage. If invalid, it is handled as if the enumerated keyword **anonymous** was used. See [CORS settings attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for additional information. - 이 열거형 특성은 리소스를 가져올 때 [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS: CORS (Cross-Origin Resource Sharing) is a system, consisting of transmitting HTTP headers, that determines whether browsers block frontend JavaScript code from accessing responses for cross-origin requests.")를 사용해야 하는지 여부를 나타냅니다. [CORS 지원 이미지](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_Enabled_Image)는 [⁠`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas "Use the HTML <canvas> element with either the canvas scripting API or the WebGL API to draw graphics and animations.") 요소에서 _오염이 되지 않은 상태_로 다시 사용할 수 있습니다. 허용되는 값은 다음과 같습니다. + 이 열거형 특성은 리소스를 가져올 때 [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS: CORS(Cross-Origin Resource Sharing)는 HTTP 헤더를 전송하는 시스템으로, 브라우저가 교차 출처 요청에 대한 응답에 프런트엔드 JavaScript 코드가 액세스하는 것을 차단해야 하는지 여부를 결정합니다.")를 사용해야 하는지 나타냅니다. [CORS가 활성화된 이미지](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_Enabled_Image)는 _오염되지 않은 상태_로 [`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas "HTML <canvas> 요소는 캔버스 스크립팅 API 또는 WebGL API를 사용해 그래픽과 애니메이션을 그릴 때 사용합니다.") 요소에서 다시 사용할 수 있습니다. 허용되는 값은 다음과 같습니다. `anonymous` -교차 출처 요청(즉, [`Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin "The Origin request header indicates where a fetch originates from. It doesn't include any path information, but only the server name. It is sent with CORS requests, as well as with POST requests. It is similar to the Referer header, but, unlike this header, it doesn't disclose the whole path.") HTTP 헤더 포함)이 수행되지만 자격 증명은 전송되지 않습니다(즉, 쿠키, X.509 인증서, HTTP Basic 인증 없음). 서버가 [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin "The Access-Control-Allow-Origin response header indicates whether the response can be shared with requesting code from the given origin.") HTTP 헤더를 설정하지 않아 원본 사이트에 자격 증명을 제공하지 않으면 이미지가 오염되고 사용이 제한됩니다. +교차 출처 요청(즉, [`Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin "The Origin request header indicates where a fetch originates from. 여기에는 경로 정보가 포함되지 않고 서버 이름만 포함됩니다. CORS 요청 및 POST 요청과 함께 전송됩니다. Referer 헤더와 비슷하지만, 이 헤더와 달리 전체 경로를 공개하지 않습니다.") HTTP 헤더 사용)이 수행되지만 자격 증명은 전송되지 않습니다(즉, 쿠키, X.509 인증서 또는 HTTP Basic 인증 없음). 서버가 [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin "The Access-Control-Allow-Origin response header indicates whether the response can be shared with requesting code from the given origin.") HTTP 헤더를 설정하지 않아 원본 사이트에 자격 증명을 제공하지 않으면 이미지가 오염되고 사용이 제한됩니다. `use-credentials` 교차 출처 요청(즉, `Origin` HTTP 헤더 포함)과 함께 자격 증명이 전송됩니다(즉, 쿠키, 인증서 및/또는 HTTP Basic 인증이 사용됨). 서버가 [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials "The Access-Control-Allow-Credentials response header tells browsers whether to expose the response to frontend JavaScript code when the request's credentials mode (Request.credentials) is "include".") HTTP 헤더를 통해 원본 사이트에 자격 증명을 제공하지 않으면 리소스가 _오염_되고 사용이 제한됩니다. -특성이 없으면 리소스는 [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS: CORS (Cross-Origin Resource Sharing) is a system, consisting of transmitting HTTP headers, that determines whether browsers block frontend JavaScript code from accessing responses for cross-origin requests.") 요청 없이(즉, `Origin` HTTP 헤더를 보내지 않고) 가져오므로 오염되지 않은 상태로 사용할 수 없습니다. 값이 잘못되면 열거형 키워드 **anonymous**를 사용한 것처럼 처리됩니다. 자세한 내용은 [CORS settings attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes)를 참조하세요. +특성이 없으면 리소스는 [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS: CORS(Cross-Origin Resource Sharing)는 HTTP 헤더를 전송하는 시스템으로, 브라우저가 교차 출처 요청에 대한 응답에 프런트엔드 JavaScript 코드가 액세스하는 것을 차단해야 하는지 여부를 결정합니다.") 요청 없이 가져오며(즉, `Origin` HTTP 헤더를 보내지 않음), 오염되지 않은 상태로는 사용할 수 없습니다. 잘못된 값이면 열거형 키워드 **anonymous**를 사용한 것처럼 처리됩니다. 자세한 내용은 [CORS 설정 특성](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes)을 참조하세요. @@ -281,7 +280,7 @@ Decode the image asynchronously to reduce delay in presenting other content. `auto` Default mode, which indicates no preference for the decoding mode. The browser decides what is best for the user. - 허용되는 값은 다음과 같습니다. + 브라우저에 이미지 디코딩 힌트를 제공합니다. 허용되는 값은 다음과 같습니다. `sync` 다른 콘텐츠와 함께 원자적으로 표시되도록 이미지를 동기적으로 디코딩합니다. @@ -292,7 +291,7 @@ Default mode, which indicates no preference for the decoding mode. The browser d `auto` -디코딩 모드에 대한 기본 설정이 없음을 나타내는 기본 모드입니다. +기본 모드이며 디코딩 모드에 대한 기본 설정이 없습니다. 브라우저는 사용자에게 가장 적합한 항목을 결정합니다. @@ -312,9 +311,9 @@ To achieve a similar effect for dynamically inserted scripts use `async="false"` `defer` 특성이 있는 스크립트는 스크립트가 로드되고 평가를 마칠 때까지 `DOMContentLoaded` 이벤트가 발생하지 않도록 합니다. -이 특성은 `src` 특성이 없는 경우, 즉 인라인 스크립트에는 사용하면 안 됩니다. 이 경우에는 효과가 없습니다. +이 특성은 `src` 특성이 없는 경우, 즉 인라인 스크립트에는 사용하면 안 됩니다. -동적으로 삽입된 스크립트에서 비슷한 효과를 얻으려면 대신 `async="false"`를 사용하세요. `defer` 특성이 있는 스크립트는 문서에 나타나는 순서대로 실행됩니다. +동적으로 삽입된 스크립트에 비슷한 효과를 내려면 대신 `async="false"`를 사용하세요. `defer` 특성이 있는 스크립트는 문서에 표시되는 순서대로 실행됩니다. @@ -323,7 +322,7 @@ To achieve a similar effect for dynamically inserted scripts use `async="false"` * `ltr`, which means _left to right_ and is to be used for languages that are written from the left to the right (like English); * `rtl`, which means _right to left_ and is to be used for languages that are written from the right to the left (like Arabic); * `auto`, which lets the user agent decide. It uses a basic algorithm as it parses the characters inside the element until it finds a character with a strong directionality, then it applies that directionality to the whole element. - 요소 텍스트의 방향성을 나타내는 열거형 특성입니다. 다음 값을 가질 수 있습니다. + 요소 텍스트의 방향성을 나타내는 열거형 특성입니다. 다음 값을 사용할 수 있습니다. * `ltr`: _왼쪽에서 오른쪽_을 뜻합니다. 영어처럼 왼쪽에서 오른쪽으로 쓰는 언어에 사용합니다. * `rtl`: _오른쪽에서 왼쪽_을 뜻합니다. 아랍어처럼 오른쪽에서 왼쪽으로 쓰는 언어에 사용합니다. @@ -344,7 +343,7 @@ To achieve a similar effect for dynamically inserted scripts use `async="false"` * Although HTTP(s) URLs need to be in the same-origin, [`blob:` URLs](https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL) and [`data:` URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) are allowed so that content generated by JavaScript, such as pictures created in an image-editor Web app, can be downloaded. * If the HTTP header [`Content-Disposition:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) gives a different filename than this attribute, the HTTP header takes priority over this attribute. * If `Content-Disposition:` is set to `inline`, Firefox prioritizes `Content-Disposition`, like the filename case, while Chrome prioritizes the `download` attribute. - 이 특성은 브라우저에 URL로 이동하지 말고 다운로드하라고 지시합니다. 따라서 사용자는 이를 로컬 파일로 저장하라는 메시지를 받게 됩니다. 특성에 값이 있으면 저장 프롬프트에 미리 입력된 파일 이름으로 사용됩니다. 사용자는 원하면 파일 이름을 바꿀 수 있습니다. 허용되는 값에는 제한이 없지만 `/` 및 `\`는 밑줄로 바뀝니다. 대부분의 파일 시스템은 파일 이름에서 일부 문장 부호를 제한하므로, 브라우저는 제안된 이름을 그에 맞게 조정합니다. + 이 특성은 브라우저에 URL로 이동하는 대신 해당 URL을 다운로드하도록 지시하므로, 사용자는 이를 로컬 파일로 저장하라는 메시지를 받게 됩니다. 특성에 값이 있으면 저장 프롬프트에서 미리 채워진 파일 이름으로 사용됩니다. 사용자는 원하면 파일 이름을 변경할 수 있습니다. 허용되는 값에는 제한이 없지만, `/`와 `\`는 밑줄로 변환됩니다. 대부분의 파일 시스템은 파일 이름에서 일부 문장 부호를 제한하므로, 브라우저는 제안된 이름을 그에 맞게 조정합니다. **참고:** @@ -371,7 +370,7 @@ To achieve a similar effect for dynamically inserted scripts use `async="false"` * `copy`, which indicates that dropping will create a copy of the element that was dragged * `move`, which indicates that the element that was dragged will be moved to this new location. * `link`, will create a link to the dragged data. - [끌어서 놓기 API](https://developer.mozilla.org/docs/DragDrop/Drag_and_Drop)를 사용해 요소에 놓을 수 있는 콘텐츠 형식을 나타내는 열거형 특성입니다. 다음 값을 사용할 수 있습니다. + [끌어서 놓기 API](https://developer.mozilla.org/docs/DragDrop/Drag_and_Drop)를 사용하여 요소에서 삭제할 수 있는 콘텐츠 형식을 나타내는 열거형 특성입니다. 다음 값을 사용할 수 있습니다. * `copy`: 놓으면 끌어온 요소의 복사본이 만들어집니다. * `move`: 끌어온 요소가 이 새 위치로 이동합니다. @@ -386,13 +385,13 @@ To achieve a similar effect for dynamically inserted scripts use `async="false"` * `text/plain`: (HTML5) This value can be overridden by a [`formenctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formenctype) attribute on a [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "The HTML <button> element represents a clickable button, which can be used in forms or anywhere in a document that needs simple, standard button functionality.") or [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") element. - `method` 특성의 값이 `post`인 경우 enctype은 양식을 서버에 제출하는 데 사용되는 콘텐츠의 [MIME 형식](https://en.wikipedia.org/wiki/Mime_type)입니다. 가능한 값은 다음과 같습니다. + `method` 특성의 값이 `post`인 경우 enctype은 양식을 서버에 제출하는 데 사용되는 콘텐츠의 [MIME 형식](https://en.wikipedia.org/wiki/Mime_type)입니다. 사용할 수 있는 값은 다음과 같습니다. -* `application/x-www-form-urlencoded`: 특성을 지정하지 않은 경우의 기본값입니다. -* `multipart/form-data`: `type` 특성이 "file"로 설정된 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 요소는 사용자로부터 데이터를 받기 위해 웹 기반 양식에 대한 대화형 컨트롤을 만드는 데 사용됩니다. 디바이스 및 사용자 에이전트에 따라 다양한 입력 데이터 유형과 컨트롤 위젯을 사용할 수 있습니다.") 요소에 사용되는 값입니다. +* `application/x-www-form-urlencoded`: 특성을 지정하지 않으면 기본값입니다. +* `multipart/form-data`: `type` 특성이 'file'로 설정된 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 요소를 사용하여 사용자의 데이터를 허용하기 위해 웹 기반 양식에 대한 대화형 컨트롤을 만드는 경우 이 값을 사용합니다. 디바이스 및 사용자 에이전트에 따라 다양한 유형의 입력 데이터 및 컨트롤 위젯을 사용할 수 있습니다.") 요소를 사용하는 경우 이 값을 사용합니다. * `text/plain`: (HTML5) -이 값은 [`formenctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formenctype) 특성이 있는 [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "The HTML <button> element represents a clickable button, which can be used in forms or anywhere in a document that needs simple, standard button functionality.") 또는 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") 요소에서 재정의할 수 있습니다. +이 값은 [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "HTML <button> 요소는 클릭할 수 있는 단추를 나타내며, 양식에서 또는 간단한 표준 단추 기능이 필요한 문서의 어디에서나 사용할 수 있습니다.") 또는 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 요소는 사용자로부터 데이터를 받기 위해 웹 기반 양식에 대한 대화형 컨트롤을 만드는 데 사용됩니다. 디바이스 및 사용자 에이전트에 따라 다양한 입력 데이터 유형과 컨트롤 위젯을 사용할 수 있습니다.") 요소의 [`formenctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formenctype) 특성으로 재정의할 수 있습니다. @@ -414,7 +413,7 @@ This value can be overridden by a [`formenctype`](https://developer.mozilla.org/ The [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-id) of a [labelable](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form_labelable) form-related element in the same document as the `<label>` element. The first element in the document with an `id` matching the value of the `for` attribute is the _labeled control_ for this label element, if it is a labelable element. If it is not labelable then the `for` attribute has no effect. If there are other elements which also match the `id` value, later in the document, they are not considered. **Note**: A `<label>` element can have both a `for` attribute and a contained control element, as long as the `for` attribute points to the contained control element. - `<label>` 요소와 동일한 문서에 있는 [labelable](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form_labelable) 양식 관련 요소의 [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-id)입니다. `for` 특성의 값과 일치하는 `id`가 있는 문서의 첫 번째 요소는 labelable 요소인 경우 이 레이블 요소에 대한 _레이블이 지정된 컨트롤_입니다. labelable 요소가 아니면 `for` 특성은 아무 효과가 없습니다. 뒤에 같은 `id` 값을 가진 다른 요소가 있어도 고려되지 않습니다. + `<label>` 요소와 동일한 문서에 있는 [labelable](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form_labelable) 양식 관련 요소의 [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-id)입니다. `for` 특성의 값과 일치하는 `id`가 있는 문서의 첫 번째 요소는 labelable 요소인 경우 이 레이블 요소에 대한 _레이블이 지정된 컨트롤_입니다. labelable 요소가 아니면 `for` 특성은 아무런 효과가 없습니다. 뒤에 같은 `id` 값을 가진 다른 요소가 있어도 고려되지 않습니다. **참고**: `<label>` 요소에는 `for` 특성과 포함된 control 요소를 둘 다 둘 수 있습니다. 단, `for` 특성이 포함된 control 요소를 가리켜야 합니다. @@ -437,7 +436,7 @@ This value can be overridden by a [`formenctype`](https://developer.mozilla.org/ * `text/plain` If this attribute is specified, it overrides the [`enctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-enctype) attribute of the button's form owner. - 단추가 제출 단추인 경우, 이 특성은 양식을 서버로 제출할 때 사용할 콘텐츠 형식을 지정합니다. 가능한 값은 다음과 같습니다. + 단추가 제출 단추인 경우, 이 특성은 양식을 서버로 제출할 때 사용할 콘텐츠 형식을 지정합니다. 사용할 수 있는 값은 다음과 같습니다. * `application/x-www-form-urlencoded`: 특성을 지정하지 않으면 기본값입니다. * `multipart/form-data`: [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-type) 특성이 `file`로 설정된 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 요소를 사용하여 사용자의 데이터를 허용하기 위해 웹 기반 양식에 대한 대화형 컨트롤을 만드는 경우 이 값을 사용합니다. 디바이스 및 사용자 에이전트에 따라 다양한 유형의 입력 데이터 및 컨트롤 위젯을 사용할 수 있습니다.") 요소를 사용하는 경우 이 값을 사용합니다. @@ -453,12 +452,12 @@ If this attribute is specified, it overrides the [`enctype`](https://developer.m * `get`: The data from the form are appended to the **form** attribute URI, with a '?' as a separator, and the resulting URI is sent to the server. Use this method when the form has no side-effects and contains only ASCII characters. If specified, this attribute overrides the [`method`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-method) attribute of the button's form owner. - 단추가 제출 단추인 경우, 이 특성은 브라우저가 양식을 제출할 때 사용하는 HTTP 메서드를 지정합니다. 가능한 값은 다음과 같습니다. + 단추가 제출 단추인 경우, 이 특성은 브라우저가 양식을 제출할 때 사용하는 HTTP 메서드를 지정합니다. 사용할 수 있는 값은 다음과 같습니다. * `post`: 양식 데이터를 양식 본문에 넣어 서버로 보냅니다. * `get`: 양식 데이터를 구분 기호 '?'와 함께 **form** 특성 URI에 추가한 뒤, 결과 URI를 서버로 보냅니다. 양식에 부작용이 없고 ASCII 문자만 포함된 경우 이 메서드를 사용하세요. -이 특성을 지정하면 단추 양식 소유자의 [`method`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-method) 특성을 덮어씁니다. +지정된 경우 이 특성은 단추의 양식 소유자의 [`method`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-method) 특성을 재정의합니다. @@ -473,12 +472,12 @@ If specified, this attribute overrides the [`method`](https://developer.mozilla. * `_blank`: Load the response into a new unnamed browsing context. * `_parent`: Load the response into the parent browsing context of the current one. If there is no parent, this option behaves the same way as `_self`. * `_top`: Load the response into the top-level browsing context (that is, the browsing context that is an ancestor of the current one, and has no parent). If there is no parent, this option behaves the same way as `_self`. - 버튼이 제출 버튼인 경우, 이 특성은 양식을 제출한 후 받은 응답을 어디에 표시할지 나타내는 이름 또는 키워드입니다. _browsing context_의 이름이나 키워드입니다. 예를 들어 탭, 창, 인라인 프레임입니다. 이 특성을 지정하면 버튼의 form owner에 있는 [`target`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-target) 특성을 재정의합니다. 다음 키워드에는 특별한 의미가 있습니다. + 단추가 제출 단추인 경우, 이 특성은 양식을 제출한 후 받은 응답을 표시할 위치를 나타내는 이름 또는 키워드입니다. _브라우징 컨텍스트_의 이름 또는 키워드입니다(예: 탭, 창 또는 인라인 프레임). 이 특성을 지정하면 단추의 양식 소유자에 대한 [`target`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-target) 특성을 재정의합니다. 다음 키워드에는 특별한 의미가 있습니다. * `_self`: 현재 browsing context와 같은 browsing context에 응답을 로드합니다. 이 값은 특성을 지정하지 않은 경우의 기본값입니다. * `_blank`: 이름이 지정되지 않은 새 browsing context에 응답을 로드합니다. * `_parent`: 현재 browsing context의 부모 browsing context에 응답을 로드합니다. 부모가 없으면 이 옵션은 `_self`와 같은 방식으로 동작합니다. -* `_top`: 최상위 browsing context, 즉 현재 browsing context의 조상이며 부모가 없는 browsing context에 응답을 로드합니다. 부모가 없으면 이 옵션은 `_self`와 같은 방식으로 동작합니다. +* `_top`: 최상위 browsing context, 즉 현재 browsing context의 조상이며 부모가 없는 browsing context에 응답을 로드합니다. 부모가 없는 경우 이 옵션은 `_self`와 동일한 방식으로 동작합니다. @@ -531,19 +530,19 @@ This instruction specifies: Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page. Its content must follow the syntax defined in the [IETF HTTP Cookie Specification](https://tools.ietf.org/html/draft-ietf-httpstate-cookie-14). **Warning:** Do not use this instruction, as it is obsolete. Use the HTTP header [`Set-Cookie`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) instead. - 프라그마 지시문을 정의합니다. 허용되는 값이 모두 특정 HTTP 헤더의 이름이므로 특성 이름이 `**http-equiv**(alent)`입니다. + pragma 지시문을 정의합니다. 허용되는 값이 모두 특정 HTTP 헤더의 이름이므로 특성 이름이 `**http-equiv**(alent)`입니다. * `"content-language"` 페이지의 기본 언어를 정의합니다. 이 값은 어떤 요소에서든 [lang](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) 특성으로 재정의할 수 있습니다. -**경고:** 이 값은 더 이상 사용되지 않으므로 사용하지 마세요. [`<html>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html "The HTML <html> element represents the root (top-level element) of an HTML document, so it is also referred to as the root element. All other elements must be descendants of this element.") 요소의 `lang` 특성을 사용하세요. +**경고:** 이 값은 사용되지 않으므로 사용하지 마세요. [`<html>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html "HTML <html> 요소는 HTML 문서의 루트(최상위 요소)를 나타내므로 루트 요소라고도 합니다. All other elements must be descendants of this element.") 요소의 `lang` 특성을 사용하세요. * `"content-security-policy"` 페이지 작성자가 현재 페이지에 대한 [content policy](https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives)를 정의할 수 있게 합니다. 콘텐츠 정책은 주로 허용되는 서버 원본과 스크립트 엔드포인트를 지정하며, 이는 교차 사이트 스크립팅 공격을 막는 데 도움이 됩니다. * `"content-type"` -문서의 [MIME type](https://developer.mozilla.org/en-US/docs/Glossary/MIME_type)과 문자 인코딩을 정의합니다. HTTP `content-type` 엔티티 헤더 필드와 같은 구문을 따르지만, HTML 페이지 안에 있으므로 `text/html` 외의 대부분 값은 사용할 수 없습니다. 따라서 `content`의 올바른 구문은 문자열 `text/html` 뒤에 `; charset=_IANAcharset_` 형식의 문자 집합이 오는 형태입니다. 여기서 `IANAcharset`은 [IANA에서 정의한](https://www.iana.org/assignments/character-sets) 문자 집합의 _기본 MIME 이름_입니다. +문서의 [MIME type](https://developer.mozilla.org/en-US/docs/Glossary/MIME_type)과 문자 인코딩을 정의합니다. HTTP `content-type` 엔터티 헤더 필드와 동일한 구문을 따르지만 HTML 페이지 내에 있으므로 `text/html` 이외의 대부분의 값은 불가능합니다. 따라서 `content`의 올바른 구문은 문자열 `text/html` 뒤에 `; charset=_IANAcharset_` 형식의 문자 집합이 오는 형태입니다. 여기서 `IANAcharset`은 [IANA에서 정의한](https://www.iana.org/assignments/character-sets) 문자 집합의 _기본 MIME 이름_입니다. -**경고:** 이 값은 더 이상 사용되지 않으므로 사용하지 마세요. [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) 특성을 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") 요소에 사용하세요. +**경고:** 이 값은 사용되지 않으므로 사용하지 마세요. [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) 특성을 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") 요소에 사용하세요. **참고:** [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.")는 XHTML이나 HTML5의 XHTML 직렬화에서 문서 형식을 바꿀 수 없으므로, `<meta>`를 사용해 MIME type을 XHTML MIME type으로 설정하지 마세요. @@ -586,7 +585,7 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page This Boolean attribute indicates that the image is part of a server-side map. If so, the precise coordinates of a click are sent to the server. **Note:** This attribute is allowed only if the `<img>` element is a descendant of an [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "The HTML <a> element (or anchor element) creates a hyperlink to other web pages, files, locations within the same page, email addresses, or any other URL.") element with a valid [`href`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-href) attribute. - 이 부울 특성은 이미지가 서버 쪽 맵의 일부임을 나타냅니다.이 경우 클릭의 정확한 좌표가 서버로 전송됩니다. + 이 부울 특성은 이미지가 서버 쪽 맵의 일부임을 나타냅니다. 이 경우 클릭의 정확한 좌표가 서버로 전송됩니다. **참고:** 이 특성은 `<img>` 요소가 유효한 [`href`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-href) 특성이 있는 [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "HTML <a> 요소(또는 앵커 요소)는 다른 웹 페이지, 파일, 위치, 같은 페이지 내의 위치, 이메일 주소 또는 다른 URL에 대한 하이퍼링크를 만듭니다.")의 하위 요소인 경우에만 허용됩니다. @@ -633,7 +632,7 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page * Chapter titles are intended to be used when the user is navigating the media resource. * `metadata` * Tracks used by scripts. Not visible to the user. - 텍스트 트랙을 어떻게 사용할지 나타냅니다. 생략하면 기본 종류는 `subtitles`입니다. 특성이 없으면 `subtitles`를 사용합니다. 특성에 잘못된 값이 있으면 `metadata`를 사용합니다. (Chrome 52 이전 버전에서는 잘못된 값을 `subtitles`로 처리했습니다.) 다음 키워드를 사용할 수 있습니다. + 텍스트 트랙을 어떻게 사용할지 지정합니다. 생략하면 기본 종류는 `subtitles`입니다. 특성이 없으면 `subtitles`를 사용합니다. 특성에 잘못된 값이 포함되면 `metadata`를 사용합니다. (Chrome 52 이전 버전에서는 잘못된 값을 `subtitles`로 처리했습니다.) 다음 키워드를 사용할 수 있습니다. * `subtitles` * 자막은 시청자가 이해할 수 없는 콘텐츠를 번역합니다. 예를 들어 영어 영화에서 영어가 아닌 대사나 텍스트가 여기에 해당합니다. @@ -648,7 +647,7 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page * `chapters` * 챕터 제목은 사용자가 미디어 리소스를 탐색할 때 사용하도록 만든 것입니다. * `metadata` -* 스크립트가 사용하는 트랙입니다. 사용자에게는 표시되지 않습니다. +* 스크립트가 사용하는 트랙입니다. 사용자에게 표시되지 않습니다. @@ -693,7 +692,7 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page * In HTML 4, this can only be a simple white-space-separated list of media description literals, i.e., [media types and groups](https://developer.mozilla.org/en-US/docs/Web/CSS/@media), where defined and allowed as values for this attribute, such as `print`, `screen`, `aural`, `braille`. HTML5 extended this to any kind of [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries), which are a superset of the allowed values of HTML 4. * Browsers not supporting [CSS3 Media Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries) won't necessarily recognize the adequate link; do not forget to set fallback links, the restricted set of media queries defined in HTML 4. - 이 특성은 연결된 리소스가 적용되는 미디어를 지정합니다. 값은 미디어 유형 / [media query](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries)여야 합니다. 이 특성은 주로 외부 스타일시트에 연결할 때 유용하며, 사용자 에이전트가 실행되는 장치에 가장 잘 맞는 스타일시트를 선택할 수 있게 해 줍니다. + 이 특성은 연결된 리소스가 적용되는 미디어를 지정합니다. 해당 값은 미디어 형식/[미디어 쿼리](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries)이어야 합니다. 이 특성은 주로 외부 스타일시트에 연결할 때 유용하며, 사용자 에이전트가 실행되는 장치에 가장 잘 맞는 스타일시트를 선택할 수 있게 해 줍니다. **참고:** @@ -709,13 +708,13 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page * `dialog`: Use when the form is inside a [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog "The HTML <dialog> element represents a dialog box or other interactive component, such as an inspector or window.") element to close the dialog when submitted. This value can be overridden by a [`formmethod`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formmethod) attribute on a [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "The HTML <button> element represents a clickable button, which can be used in forms or anywhere in a document that needs simple, standard button functionality.") or [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") element. - 브라우저가 양식을 제출하는 데 사용하는 [HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) 메서드입니다. 가능한 값은 다음과 같습니다. + 브라우저가 양식을 제출할 때 사용하는 [HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) 메서드입니다. 사용할 수 있는 값은 다음과 같습니다. * `post`: HTTP [POST method](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5)에 해당합니다. 양식 데이터는 양식 본문에 포함되어 서버로 전송됩니다. * `get`: HTTP [GET method](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3)에 해당합니다. 양식 데이터는 `action` 특성 URI에 '?'를 구분 기호로 붙여 추가되며, 결과 URI가 서버로 전송됩니다. 양식에 부작용이 없고 ASCII 문자만 포함된 경우 이 메서드를 사용하세요. * `dialog`: 양식이 [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog "HTML <dialog> 요소는 대화 상자나 검사기, 창 같은 대화형 구성 요소를 나타냅니다.") 요소 안에 있을 때 사용합니다. 제출하면 대화 상자가 닫힙니다. -이 값은 [`formmethod`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formmethod) 특성이 있는 [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "HTML <button> 요소는 클릭 가능한 단추를 나타냅니다. 양식에서 사용하거나, 문서의 어느 위치에서든 간단하고 표준적인 단추 기능이 필요할 때 사용할 수 있습니다.") 또는 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 요소는 웹 기반 양식에서 사용자의 데이터를 입력받기 위한 대화형 컨트롤을 만드는 데 사용됩니다. 장치와 사용자 에이전트에 따라 다양한 유형의 입력 데이터와 컨트롤 위젯을 사용할 수 있습니다.") 요소의 formmethod 특성으로 재정의할 수 있습니다. +이 값은 [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "HTML <button> 요소는 클릭할 수 있는 단추를 나타내며, 양식에서 또는 간단한 표준 단추 기능이 필요한 문서의 어디에서나 사용할 수 있습니다.") 또는 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 요소는 사용자로부터 데이터를 받기 위해 웹 기반 양식에 대한 대화형 컨트롤을 만드는 데 사용됩니다. 디바이스 및 사용자 에이전트에 따라 다양한 입력 데이터 유형과 컨트롤 위젯을 사용할 수 있습니다.") 요소의 [`formmethod`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formmethod) 특성으로 재정의할 수 있습니다. @@ -942,24 +941,24 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo * Though unstandardized, this declaration is respected by most mobile browsers due to de-facto dominance. * The default values may vary between devices and browsers. * To learn about this declaration in Firefox for Mobile, see [this article](https://developer.mozilla.org/en-US/docs/Mobile/Viewport_meta_tag "Mobile/Viewport meta tag"). - This attribute defines the name of a piece of document-level metadata. It should not be set if one of the attributes [`itemprop`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-itemprop), [`http-equiv`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-http-equiv) or [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) is also set. + 이 특성은 문서 수준 메타데이터의 이름을 정의합니다. [`itemprop`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-itemprop), [`http-equiv`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-http-equiv) or [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) 특성 중 하나가 함께 설정되어 있다면 설정해서는 안 됩니다. -This metadata name is associated with the value contained by the [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) attribute. The possible values for the name attribute are: +이 메타데이터 이름은 [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) 특성에 포함된 값과 연결됩니다. 이름 특성에 사용할 수 있는 값은 다음과 같습니다. -* `application-name` which defines the name of the application running in the web page. +* `application-name`: 웹 페이지에서 실행되는 애플리케이션의 이름을 정의합니다. -**Note:** +**참고:** -* Browsers may use this to identify the application. It is different from the [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title "The HTML Title element (<title>) defines the document's title that is shown in a browser's title bar or a page's tab.") element, which usually contain the application name, but may also contain information like the document name or a status. -* Simple web pages shouldn't define an application-name. +* 브라우저는 이를 사용하여 애플리케이션을 식별할 수 있습니다. 일반적으로 애플리케이션 이름을 포함하지만 문서 이름 또는 상태와 같은 정보를 포함할 수도 있는 [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title "HTML Title 요소(<title>)는 브라우저의 제목 표시줄 또는 페이지 탭에 표시되는 문서 제목을 정의합니다.") 요소와 다릅니다. +* 단순 웹 페이지는 애플리케이션 이름을 정의하면 안 됩니다. -* `author` which defines the name of the document's author. -* `description` which contains a short and accurate summary of the content of the page. Several browsers, like Firefox and Opera, use this as the default description of bookmarked pages. -* `generator` which contains the identifier of the software that generated the page. -* `keywords` which contains words relevant to the page's content separated by commas. -* `referrer` which controls the [`Referer` HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer) attached to requests sent from the document: +* `author`: 문서 작성자의 이름을 정의합니다. +* `description`: 페이지의 내용에 대한 짧고 정확한 요약을 포함합니다. Firefox 및 Opera와 같은 여러 브라우저에서는 이를 책갈피가 지정된 페이지의 기본 설명으로 사용합니다. +* `generator`: 페이지를 생성한 소프트웨어의 식별자를 포함합니다. +* `keywords`: 페이지 콘텐츠와 관련된 단어를 쉼표로 구분하여 포함합니다. +* `referrer`: 문서에서 보낸 요청에 첨부된 [`Referer` HTTP 헤더](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer)를 제어합니다. -Values for the `content` attribute of `<meta name="referrer">` +`<meta name="referrer">`의 `content` 특성 값이 `no-referrer` @@ -967,45 +966,45 @@ Do not send a HTTP `Referrer` header. `origin` -Send the [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin) of the document. +문서의 [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin)을 보냅니다. `no-referrer-when-downgrade` -Send the [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin) as a referrer to URLs as secure as the current page, (https→https), but does not send a referrer to less secure URLs (https→http). This is the default behaviour. +[origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin)을 현재 페이지(https https→)와 같은 안전한 URL에 referrer로 보내지만 덜 안전한 URL(https→http)에는 referrer를 보내지 않습니다. 이는 기본 동작입니다. `origin-when-cross-origin` -Send the full URL (stripped of parameters) for same-origin requests, but only send the [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin) for other cases. +같은 origin 요청에는 전체 URL에서 매개 변수를 제거한 값을 보내고, 다른 경우에는 [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin)만 보냅니다. `same-origin` -A referrer will be sent for [same-site origins](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy), but cross-origin requests will contain no referrer information. +[same-site origins](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)에 대해서는 referrer를 보내지만, cross-origin 요청에는 referrer 정보가 포함되지 않습니다. `strict-origin` -Only send the origin of the document as the referrer to a-priori as-much-secure destination (HTTPS->HTTPS), but don't send it to a less secure destination (HTTPS->HTTP). +문서의 origin만 a-priori as-much-secure 대상(HTTPS->HTTPS)으로 referrer로 보내고, 덜 안전한 대상(HTTPS->HTTP)에는 보내지 않습니다. `strict-origin-when-cross-origin` -Send a full URL when performing a same-origin request, only send the origin of the document to a-priori as-much-secure destination (HTTPS->HTTPS), and send no header to a less secure destination (HTTPS->HTTP). +같은 origin 요청을 수행할 때는 전체 URL을 보내고, 문서의 origin은 a-priori as-much-secure 대상(HTTPS->HTTPS)으로만 보냅니다. `unsafe-URL` -Send the full URL (stripped of parameters) for same-origin or cross-origin requests. +동일 원본 또는 원본 간 요청에 대한 전체 URL(매개 변수 제거)을 보냅니다. -**Notes:** +**참고:** -* Some browsers support the deprecated values of `always`, `default`, and `never` for referrer. -* Dynamically inserting `<meta name="referrer">` (with [`document.write`](https://developer.mozilla.org/en-US/docs/Web/API/Document/write) or [`appendChild`](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild)) makes the referrer behaviour unpredictable. -* When several conflicting policies are defined, the no-referrer policy is applied. +* 일부 브라우저는 referrer에 대해 더 이상 사용되지 않는 `always`, `default`, `never` 값을 지원합니다. +* `<meta name="referrer">`을 문서에 동적으로 삽입하면([`document.write`](https://developer.mozilla.org/en-US/docs/Web/API/Document/write) 또는 [`appendChild`](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild) 사용) referrer 동작이 예측할 수 없게 됩니다. +* 충돌하는 여러 정책이 정의되면 no-referrer 정책이 적용됩니다. -This attribute may also have a value taken from the extended list defined on [WHATWG Wiki MetaExtensions page](https://wiki.whatwg.org/wiki/MetaExtensions). Although none have been formally accepted yet, a few commonly used names are: +이 특성에는 [WHATWG Wiki MetaExtensions 페이지](https://wiki.whatwg.org/wiki/MetaExtensions)에 정의된 확장 목록에서 가져온 값이 있을 수도 있습니다. 아직 공식적으로 수락된 이름은 없지만 일반적으로 사용되는 몇 가지 이름은 다음과 같습니다. -* `creator` which defines the name of the creator of the document, such as an organization or institution. If there are more than one, several [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") elements should be used. -* `googlebot`, a synonym of `robots`, is only followed by Googlebot (the indexing crawler for Google). -* `publisher` which defines the name of the document's publisher. -* `robots` which defines the behaviour that cooperative crawlers, or "robots", should use with the page. It is a comma-separated list of the values below: +* `creator`: 조직 또는 기관과 같은 문서 작성자의 이름을 정의합니다. 작성자가 둘 이상이면 여러 개의 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") 요소를 사용해야 합니다. +* `googlebot`: `robots`의 동의어이며, Googlebot(Google의 인덱싱 크롤러)만 따릅니다. +* `publisher`: 문서 게시자의 이름을 정의합니다. +* `robots`: 협력적인 크롤러, 즉 로봇이 페이지에서 사용할 동작을 정의합니다. 이는 아래 값의 쉼표로 구분된 목록입니다. Values for the content of `<meta name="robots">` @@ -1047,109 +1046,109 @@ Equivalent to `noindex, nofollow` `noodp` -Prevents using the [Open Directory Project](https://www.dmoz.org/) description, if any, as the page description in search engine results. +검색 엔진 결과의 페이지 설명으로 [Open Directory Project](https://www.dmoz.org/) 설명을 사용할 수 없습니다(있는 경우). [Google](https://support.google.com/webmasters/answer/35624#nodmoz), [Yahoo](https://help.yahoo.com/kb/search-for-desktop/meta-tags-robotstxt-yahoo-search-sln2213.html#cont5), [Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) `noarchive` -Requests the search engine not to cache the page content. +검색 엔진에 페이지 콘텐츠를 캐시하지 않도록 요청합니다. [Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives), [Yahoo](https://help.yahoo.com/kb/search-for-desktop/SLN2213.html), [Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) `nosnippet` -Prevents displaying any description of the page in search engine results. +검색 엔진 결과에 페이지에 대한 설명을 표시하지 않습니다. [Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives), [Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) `noimageindex` -Requests this page not to appear as the referring page of an indexed image. +이 페이지를 인덱싱된 이미지의 참조 페이지로 표시하지 않도록 요청합니다. [Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives) `nocache` -Synonym of `noarchive`. +`noarchive`의 동의어입니다. [Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) -**Notes:** +**참고:** -* Only cooperative robots follow these rules. Do not expect to prevent e-mail harvesters with them. -* The robot still needs to access the page in order to read these rules. To prevent bandwidth consumption, use a _[robots.txt](https://developer.mozilla.org/en-US/docs/Glossary/robots.txt "robots.txt: Robots.txt is a file which is usually placed in the root of any website. It decides whether crawlers are permitted or forbidden access to the web site.")_ file. -* If you want to remove a page, `noindex` will work, but only after the robot visits the page again. Ensure that the `robots.txt` file is not preventing revisits. -* Some values are mutually exclusive, like `index` and `noindex`, or `follow` and `nofollow`. In these cases the robot's behaviour is undefined and may vary between them. -* Some crawler robots, like Google, Yahoo and Bing, support the same values for the HTTP header `X-Robots-Tag`; this allows non-HTML documents like images to use these rules. +* 협력적인 로봇만 이 규칙을 따릅니다. 이 규칙으로 이메일 수집기를 막을 수는 없습니다. +* 로봇이 이 규칙을 읽으려면 페이지에 먼저 접근해야 합니다. 대역폭 사용을 방지하려면 _[robots.txt](https://developer.mozilla.org/en-US/docs/Glossary/robots.txt "robots.txt: Robots.txt is a file which is usually placed in the root of any website. It decides whether crawlers are permitted or forbidden access to the web site.")_ 파일을 사용하세요. +* 페이지를 제거하려면 `noindex`를 사용할 수 있지만, robot이 페이지를 다시 방문한 뒤에만 적용됩니다. `robots.txt` 파일이 재방문을 막고 있지 않은지 확인하세요. +* `index`와 `noindex`, `follow`와 `nofollow`처럼 서로 배타적인 값도 있습니다. 이런 경우 robot의 동작은 정의되지 않으며 서로 달라질 수 있습니다. +* Google, Yahoo 및 Bing과 같은 일부 크롤러 로봇은 HTTP 헤더 `X-Robots-Tag`에 대해 동일한 값을 지원합니다. 이를 통해 이미지 같은 비HTML 문서에도 이 규칙을 사용할 수 있습니다. -* `slurp`, is a synonym of `robots`, but only for Slurp - the crawler for Yahoo Search. -* `viewport`, which gives hints about the size of the initial size of the [viewport](https://developer.mozilla.org/en-US/docs/Glossary/viewport "viewport: A viewport represents a polygonal (normally rectangular) area in computer graphics that is currently being viewed. In web browser terms, it refers to the part of the document you're viewing which is currently visible in its window (or the screen, if the document is being viewed in full screen mode). Content outside the viewport is not visible onscreen until scrolled into view."). Used by mobile devices only. +* `slurp`는 `robots`의 동의어이지만, Yahoo Search의 크롤러인 Slurp에만 적용됩니다. +* `viewport`는 현재 보고 있는 컴퓨터 그래픽의 초기 [viewport](https://developer.mozilla.org/en-US/docs/Glossary/viewport "viewport: A viewport represents a polygonal (normally rectangular) 영역의 초기 크기에 대한 정보를 제공합니다. 웹 브라우저 용어로는 현재 창 안에서 보이는 문서의 부분(또는 문서를 전체 화면 모드로 보고 있다면 화면)을 가리킵니다. Content outside the viewport is not visible onscreen until scrolled into view."). 모바일 디바이스에서만 사용됩니다. -Values for the content of `<meta name="viewport">` +`<meta name="viewport">` 콘텐츠 값 -Value +값 -Possible subvalues +가능한 하위값 -Description +설명 `width` -A positive integer number, or the text `device-width` +양의 정수 또는 텍스트 `device-width` -Defines the pixel width of the viewport that you want the web site to be rendered at. +웹 사이트를 렌더링할 뷰포트의 픽셀 너비를 정의합니다. `height` -A positive integer, or the text `device-height` +양의 정수 또는 텍스트 `device-height` -Defines the height of the viewport. Not used by any browser. +뷰포트의 높이를 정의합니다. 브라우저에서 사용되지 않습니다. `initial-scale` -A positive number between `0.0` and `10.0` +`0.0`에서 `10.0` 사이의 양수 -Defines the ratio between the device width (`device-width` in portrait mode or `device-height` in landscape mode) and the viewport size. +디바이스 너비(세로 모드에서는 `device-width`, 가로 모드에서는 `device-height`)와 viewport 크기 사이의 비율을 정의합니다. `maximum-scale` -A positive number between `0.0` and `10.0` +`0.0`에서 `10.0` 사이의 양수 -Defines the maximum amount to zoom in. It must be greater or equal to the `minimum-scale` or the behaviour is undefined. Browser settings can ignore this rule and iOS10+ ignores it by default. +최대 확대/축소 수준을 정의합니다. `minimum-scale`이상이어야 합니다. 그렇지 않으면 동작이 정의되지 않습니다. 브라우저 설정은 이 규칙을 무시할 수 있으며, iOS10+는 기본적으로 이를 무시합니다. `minimum-scale` -A positive number between `0.0` and `10.0` +`0.0`에서 `10.0` 사이의 양수 -Defines the minimum zoom level. It must be smaller or equal to the `maximum-scale` or the behaviour is undefined. Browser settings can ignore this rule and iOS10+ ignores it by default. +최소 확대/축소 수준을 정의합니다. `maximum-scale` 이하여야 합니다. 그렇지 않으면 동작이 정의되지 않습니다. 브라우저 설정은 이 규칙을 무시할 수 있으며 iOS10 이상은 기본적으로 이를 무시합니다. `user-scalable` -`yes` or `no` +`yes` 또는 `no` -If set to `no`, the user is not able to zoom in the webpage. The default is `yes`. Browser settings can ignore this rule, and iOS10+ ignores it by default. +`no`로 설정하면 사용자가 웹페이지를 확대할 수 없습니다. 기본값은 `yes`입니다. 브라우저 설정은 이 규칙을 무시할 수 있으며 iOS10 이상은 기본적으로 이를 무시합니다. -Specification +사양 -Status +상태 -Comment +메모 [CSS Device Adaptation The definition of '<meta name="viewport">' in that specification.](https://drafts.csswg.org/css-device-adapt/#viewport-meta) -Working Draft +작성 중 초안 -Non-normatively describes the Viewport META element +Viewport META 요소를 비규범적으로 설명합니다. -See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport "The @viewport CSS at-rule lets you configure the viewport through which the document is viewed. It's primarily used for mobile devices, but is also used by desktop browsers that support features like "snap to edge" (such as Microsoft Edge).") +참고 항목: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport "The @viewport CSS at-rule lets you configure the viewport through which the document is viewed. It's primarily used for mobile devices, but is also used by desktop browsers that support features like "snap to edge" (such as Microsoft Edge).") -**Notes:** +**참고:** -* Though unstandardized, this declaration is respected by most mobile browsers due to de-facto dominance. -* The default values may vary between devices and browsers. -* To learn about this declaration in Firefox for Mobile, see [this article](https://developer.mozilla.org/en-US/docs/Mobile/Viewport_meta_tag "Mobile/Viewport meta tag"). +* 표준은 아니지만, 사실상 널리 사용되기 때문에 대부분의 모바일 브라우저에서 이 선언을 지원합니다. +* 기본값은 디바이스와 브라우저에 따라 다를 수 있습니다. +* Firefox for Mobile에서 이 선언에 대해 알아보려면 [이 문서](https://developer.mozilla.org/en-US/docs/Mobile/Viewport_meta_tag "Mobile/Viewport meta tag")를 참조하세요. @@ -1518,7 +1517,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo **Note:** Placeholders should only be used to show an example of the type of data that should be entered into a form; they are _not_ a substitute for a proper [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label "The HTML <label> element represents a caption for an item in a user interface.") element tied to the input. See [Labels and placeholders](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Labels_and_placeholders "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") in [<input>: The Input (Form Input) element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") for a full explanation. 컨트롤에 입력할 수 있는 내용에 대한 힌트입니다. 자리 표시자 텍스트 안에 있는 캐리지 리턴이나 줄 바꿈 문자는 힌트를 표시할 때 줄 바꿈으로 처리해야 합니다. -**참고:** 자리 표시자는 양식에 입력해야 하는 데이터 형식의 예를 보여 주는 용도로만 사용해야 합니다. 입력과 연결된 적절한 [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label "The HTML <label> element represents a caption for an item in a user interface.") 요소를 대신할 수는 없습니다. 자세한 내용은 [<input>: Input(Form Input) 요소](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 요소는 사용자로부터 데이터를 받기 위해 웹 기반 양식에 대한 대화형 컨트롤을 만드는 데 사용됩니다. 디바이스 및 사용자 에이전트에 따라 다양한 입력 데이터 유형과 컨트롤 위젯을 사용할 수 있습니다.") 요소의 [레이블 및 자리 표시자](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Labels_and_placeholders "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.")를 참조하세요. +**참고:** 자리 표시자는 양식에 입력해야 하는 데이터 형식의 예를 보여 주는 용도로만 사용해야 합니다. 입력과 연결된 적절한 [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label "The HTML <label> element represents a caption for an item in a user interface.") 요소를 대신할 수는 없습니다. 자세한 내용은 [<input>: Input(Form Input) 요소](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 요소는 사용자로부터 데이터를 받기 위해 웹 기반 양식에 대한 대화형 컨트롤을 만드는 데 사용됩니다. 디바이스 및 사용자 에이전트에 따라 다양한 입력 데이터 유형과 컨트롤 위젯을 사용할 수 있습니다.") 요소의 [레이블 및 자리 표시자](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Labels_and_placeholders "HTML <input> 요소는 사용자로부터 데이터를 받기 위해 웹 기반 양식에 대한 대화형 컨트롤을 만드는 데 사용됩니다. 디바이스 및 사용자 에이전트에 따라 다양한 입력 데이터 유형과 컨트롤 위젯을 사용할 수 있습니다.")를 참조하세요. @@ -1546,11 +1545,11 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo * `unsafe-url` means that the referrer will include the origin and the path (but not the fragment, password, or username). This case is unsafe because it can leak origins and paths from TLS-protected resources to insecure origins. 리소스를 가져올 때 사용할 리퍼러를 나타내는 문자열입니다. -* `no-referrer`는 TLS(HTTPS)가 없는 출처로 이동할 때 [`Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer "The Referer request header contains the address of the previous web page from which a link to the currently requested page was followed. The Referer header allows servers to identify where people are visiting them from and may use that data for analytics, logging, or optimized caching, for example.") 헤더가 전송되지 않음을 의미합니다. -* `no-referrer-when-downgrade`는 TLS(HTTPS)가 없는 출처로 이동할 때 [`Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer "The Referer request header contains the address of the previous web page from which a link to the currently requested page was followed. The Referer header allows servers to identify where people are visiting them from and may use that data for analytics, logging, or optimized caching, for example.") 헤더가 전송되지 않음을 의미합니다. 정책이 따로 지정되지 않은 경우 사용자 에이전트의 기본 동작입니다. +* `no-referrer`는 [`Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer "The Referer request header contains the address of the previous web page from which a link to the currently requested page was followed. The Referer header allows servers to identify where people are visiting them from and may use that data for analytics, logging, or optimized caching, for example.") 헤더가 전송되지 않습니다. +* `no-referrer-when-downgrade`는 [`Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer "The Referer request header contains the address of the previous web page from which a link to the currently requested page was followed. The Referer header allows servers to identify where people are visiting them from and may use that data for analytics, logging, or optimized caching, for example.") 헤더가 TLS(HTTPS)가 없는 출처로 이동할 때 전송됨을 의미합니다. 정책이 따로 지정되지 않은 경우 사용자 에이전트의 기본 동작입니다. * `origin`은 참조 페이지가 페이지의 출처가 됨을 의미합니다. 출처는 대체로 스키마, 호스트, 포트입니다. * `origin-when-cross-origin`은 다른 출처로 이동할 때는 스키마, 호스트, 포트만 포함하고, 같은 출처로 이동할 때는 참조 페이지의 경로도 포함함을 의미합니다. -* `unsafe-url`은 참조 페이지에 출처와 경로가 포함됨을 의미합니다. 단, fragment, password, username은 포함되지 않습니다. 이 경우는 TLS로 보호되는 리소스의 출처와 경로가 안전하지 않은 출처로 유출될 수 있으므로 안전하지 않습니다. +* `unsafe-url`은 참조 페이지에 출처와 경로가 포함됨을 의미합니다. 단, fragment, password, username은 포함되지 않습니다. 이 경우는 TLS로 보호된 리소스의 출처와 경로가 안전하지 않은 출처로 유출될 수 있으므로 안전하지 않습니다. @@ -1620,7 +1619,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo This attribute defines the scheme in which metadata is described. A scheme is a context leading to the correct interpretations of the [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) value, like a format. **Warning:** Do not use this value, as it is obsolete. There is no replacement as there was no real usage for it. - 이 특성은 메타데이터를 설명하는 체계를 정의합니다. 체계는 형식과 같이 [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) 값을 올바르게 해석하는 컨텍스트입니다. + 이 특성은 메타데이터가 설명되는 체계를 정의합니다. 체계는 형식과 같이 [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) 값을 올바르게 해석하는 컨텍스트입니다. **경고:** 이 값은 사용되지 않으므로 사용하지 마세요. 실제 사용량이 없으므로 대체 항목이 없습니다. @@ -1636,7 +1635,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo **Note:** According to the HTML5 specification, the default value for size should be 1; however, in practice, this has been found to break some web sites, and no other browser currently does that, so Mozilla has opted to continue to return 0 for the time being with Firefox. 컨트롤이 스크롤 목록 상자로 표시되는 경우, 예를 들어 `multiple`이 지정된 경우 이 특성은 한 번에 표시해야 하는 목록의 행 수를 나타냅니다. 브라우저가 select 요소를 스크롤 목록 상자로 표시할 필요는 없습니다. 기본값은 0입니다. -**참고:** HTML5 사양에 따르면 size의 기본값은 1이어야 합니다. 하지만 실제로는 일부 웹 사이트를 깨뜨리는 것으로 확인됐고, 현재 다른 브라우저도 그렇게 하지 않기 때문에 Mozilla는 Firefox에서 당분간 0을 계속 반환하기로 했습니다. +**참고:** HTML5 사양에 따르면 size의 기본값은 1이어야 하지만, 실제로는 일부 웹 사이트를 깨뜨리는 것으로 확인됐고 현재 다른 브라우저도 그렇게 하지 않기 때문에 Mozilla는 Firefox에서 당분간 0을 계속 반환하기로 했습니다. @@ -1646,12 +1645,12 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo * a white-space separated list of sizes, each in the format `_<width in pixels>_x_<height in pixels>_` or `_<width in pixels>_X_<height in pixels>_`. Each of these sizes must be contained in the resource. **Note:** Most icon formats are only able to store one single icon; therefore most of the time the [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-sizes) contains only one entry. MS's ICO format does, as well as Apple's ICNS. ICO is more ubiquitous; you should definitely use it. - 이 특성은 리소스에 포함된 시각적 미디어의 아이콘 크기를 정의합니다. [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-rel)에 `icon` 값이나 Apple의 `apple-touch-icon` 같은 비표준 형식이 포함된 경우에만 사용할 수 있습니다. 다음 값을 사용할 수 있습니다. + 이 특성은 리소스에 포함된 시각적 미디어 아이콘의 크기를 정의합니다. [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-rel)에 `icon` 값이나 Apple의 `apple-touch-icon` 같은 비표준 형식이 포함된 경우에만 존재해야 합니다. 다음 값을 사용할 수 있습니다. * `any`: 아이콘이 `image/svg+xml` 같은 벡터 형식일 때, 원하는 크기로 조정할 수 있음을 의미합니다. -* 공백으로 구분된 크기 목록입니다. 각 값은 `_<width in pixels>_x_<height in pixels>_` 또는 `_<width in pixels>_X_<height in pixels>_` 형식입니다. 각 크기는 리소스에 포함되어 있어야 합니다. +* 공백으로 구분된 크기 목록이며, 각 값은 `_<width in pixels>_x_<height in pixels>_` 또는 `_<width in pixels>_X_<height in pixels>_` 형식입니다. 각 크기는 리소스에 포함되어 있어야 합니다. -**참고:** 대부분의 아이콘 형식은 아이콘 하나만 저장할 수 있습니다. 따라서 대부분의 경우 [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-sizes)에는 항목이 하나만 있습니다. MS의 ICO 형식과 Apple의 ICNS도 마찬가지입니다. ICO가 더 널리 쓰입니다. 꼭 사용하세요. +**참고:** 대부분의 아이콘 형식은 아이콘 하나만 저장할 수 있습니다. 따라서 대부분의 경우 [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-sizes)에는 항목이 하나만 있습니다. MS의 ICO 형식은 물론 Apple의 ICNS도 마찬가지입니다. ICO는 더 널리 사용되므로 꼭 사용하세요. @@ -1664,7 +1663,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo * `true`, which indicates that the element should be, if possible, checked for spelling errors; * `false`, which indicates that the element should not be checked for spelling errors. - 요소에 맞춤법 오류 검사를 적용할 수 있는지 여부를 나타내는 열거형 특성입니다. 가능한 값은 다음과 같습니다. + 열거형 특성은 요소에서 맞춤법 검사를 할 수 있는지 여부를 정의합니다. 다음 값을 사용할 수 있습니다. * `true`: 가능한 경우 요소에서 맞춤법 검사를 수행해야 함을 나타냅니다. * `false`: 요소에서 맞춤법 검사를 수행하지 않아야 함을 나타냅니다. @@ -1676,7 +1675,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo If a `script` element has a `src` attribute specified, it should not have a script embedded inside its tags. 이 특성은 외부 스크립트의 URI를 지정합니다. 문서에 스크립트를 직접 포함하는 대신 사용할 수 있습니다. -`script` 요소에 `src` 특성이 지정된 경우, 태그 안에 스크립트를 포함해서는 안 됩니다. +`script` 요소에 `src` 특성이 지정된 경우 해당 태그 안에 스크립트를 포함해서는 안 됩니다. @@ -1702,25 +1701,25 @@ If no descriptor is specified, the source is assigned the default descriptor: `1 It is incorrect to mix width descriptors and pixel density descriptors in the same `srcset` attribute. Duplicate descriptors (for instance, two sources in the same `srcset` which are both described with '`2x`') are also invalid. The user agent selects any one of the available sources at its discretion. This provides them with significant leeway to tailor their selection based on things like user preferences or bandwidth conditions. See our [Responsive images](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images) tutorial for an example. - 쉼표로 구분된 하나 이상의 문자열 목록입니다. 사용자 에이전트가 사용할 수 있는 이미지 소스 집합을 나타냅니다. 각 문자열은 다음으로 구성됩니다. + 사용자 에이전트에서 사용할 수 있는 이미지 소스 집합을 나타내는 쉼표로 구분된 하나 이상의 문자열 목록입니다. 사용자 에이전트가 사용할 수 있는 이미지 소스 집합을 나타냅니다. 1. 이미지 URL, 2. 필요에 따라 공백 뒤에 다음 중 하나가 옵니다. -* 너비 설명자입니다. 또는 바로 뒤에 '`w`'가 오는 양의 정수입니다. 너비 설명자는 `sizes` 특성에 지정된 소스 크기로 나누어 실제 픽셀 밀도를 계산합니다. +* 너비 설명자입니다. 또는 바로 뒤에 '`w`'가 오는 양의 정수입니다. 너비 설명자는 `sizes` 특성에 지정된 소스 크기로 나누어 유효 픽셀 밀도를 계산합니다. * 픽셀 밀도 설명자입니다. 또는 바로 뒤에 '`x`'가 오는 양의 부동 소수점 숫자입니다. -설명자를 지정하지 않으면 소스에는 기본 설명자인 `1x`가 할당됩니다. +설명자를 지정하지 않으면 소스에 기본 설명자인 `1x`가 할당됩니다. -같은 `srcset` 특성에서 너비 설명자와 픽셀 밀도 설명자를 함께 사용하는 것은 올바르지 않습니다. 중복된 설명자도 유효하지 않습니다. 예를 들어 같은 `srcset`에서 두 소스가 모두 '`2x`'로 설명되는 경우가 있습니다. +같은 `srcset` 특성에서 너비 설명자와 픽셀 밀도 설명자를 함께 사용하는 것은 올바르지 않습니다. 중복 설명자(예: '`2x`'로 설명된 동일한 `srcset`의 두 소스)도 잘못되었습니다. -사용자 에이전트는 사용 가능한 소스 중 하나를 임의로 선택합니다. 따라서 사용자 환경 설정이나 대역폭 조건에 따라 선택을 조정할 수 있는 여지가 큽니다. 예제는 [반응형 이미지](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images) 자습서를 참조하세요. +사용자 에이전트는 사용 가능한 소스 중 하나를 임의로 선택합니다. 이렇게 하면 사용자 기본 설정 또는 대역폭 조건과 같은 항목에 따라 선택을 조정할 수 있는 상당한 유연함을 제공합니다. 예제는 [반응형 이미지](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images) 자습서를 참조하세요. This integer attribute specifies the start value for numbering the individual list items. Although the ordering type of list elements might be Roman numerals, such as XXXI, or letters, the value of start is always represented as a number. To start numbering elements from the letter "C", use `<ol start="3">`. **Note**: This attribute was deprecated in HTML4, but reintroduced in HTML5. - 이 정수 특성은 개별 목록 항목의 번호를 매기기 위한 시작 값을 지정합니다. 목록 요소의 순서 유형이 XXXI 같은 로마 숫자나 문자일 수 있지만, 시작 값은 항상 숫자로 표시됩니다. 문자 "C"에서 번호 매기를 시작하려면 `<ol start="3">`를 사용하세요. + 이 정수 특성은 개별 목록 항목의 번호 매기기 시작 값을 지정합니다. 목록 요소의 순서 유형이 XXXI 같은 로마 숫자나 문자일 수 있지만, 시작 값은 항상 숫자로 표시됩니다. 문자 "C"에서 번호 매기를 시작하려면 `<ol start="3">`를 사용하세요. **참고**: 이 특성은 HTML4에서 더 이상 사용되지 않았지만 HTML5에서 다시 도입되었습니다. @@ -1736,7 +1735,7 @@ The user agent selects any one of the available sources at its discretion. This * a _negative value_ means that the element should be focusable, but should not be reachable via sequential keyboard navigation; * `0` means that the element should be focusable and reachable via sequential keyboard navigation, but its relative order is defined by the platform convention; * a _positive value_ means that the element should be focusable and reachable via sequential keyboard navigation; the order in which the elements are focused is the increasing value of the [**tabindex**](https://developer.mozilla.org/docs/Web/HTML/Reference/Global_attributes/tabindex#attr-tabindex). If several elements share the same tabindex, their relative order follows their relative positions in the document. - 요소가 입력 포커스를 가질 수 있는지(_포커스 가능_), 순차적 키보드 탐색에 참여하는지, 참여한다면 어느 위치인지 나타내는 정수 특성입니다. 사용할 수 있는 값은 다음과 같습니다. + 요소가 입력 포커스를 받을 수 있는지(_focusable_), 순차 키보드 탐색에 참여해야 하는지, 아니면 어떤 위치에 있는지를 나타내는 정수 특성입니다. 사용할 수 있는 값은 다음과 같습니다. * _음수_: 요소는 포커스를 받을 수 있지만 순차적 키보드 탐색으로는 도달할 수 없음을 의미합니다. * `0`: 요소는 포커스를 받을 수 있고 순차적 키보드 탐색으로 도달할 수 있지만, 상대적 순서는 플랫폼 규칙에 따라 정해짐을 의미합니다. @@ -1752,7 +1751,7 @@ The user agent selects any one of the available sources at its discretion. This * `_top`: Load the result into the top-level browsing context (that is, the browsing context that is an ancestor of the current one, and has no parent). If there is no parent, this option behaves the same way as `_self`. If this attribute is specified, this element must come before any other elements with attributes whose values are URLs. - 하이퍼링크나 양식으로 인해 탐색이 발생할 때, 명시적인 대상 참조가 없는 요소에 대해 결과를 표시할 기본 위치를 나타내는 이름 또는 키워드입니다. _browsing context_의 이름이나 키워드입니다. 예를 들어 탭, 창, 인라인 프레임이 있습니다. 다음 키워드에는 특별한 의미가 있습니다. + 명시적 대상 참조가 없는 요소에 대해 하이퍼링크 또는 양식으로 인해 탐색이 발생할 때 결과를 표시할 기본 위치를 나타내는 이름 또는 키워드입니다. _브라우징 컨텍스트_의 이름 또는 키워드입니다(예: 탭, 창 또는 인라인 프레임). 다음 키워드에는 특별한 의미가 있습니다. * `_self`: 현재 browsing context와 같은 browsing context에 응답을 로드합니다. 이 값은 특성을 지정하지 않은 경우의 기본값입니다. * `_blank`: 이름이 지정되지 않은 새 browsing context에 응답을 로드합니다. @@ -1772,7 +1771,7 @@ If this attribute is specified, this element must come before any other elements * empty string and `yes`, which indicates that the element will be translated. * `no`, which indicates that the element will not be translated. - 페이지를 지역화할 때 요소의 특성 값과 해당 [`Text`](https://developer.mozilla.org/docs/Web/API/Text "The Text interface represents the textual content of Element or Attr. If an element has no markup within its content, it has a single child implementing Text that contains the element's text. However, if the element contains markup, it is parsed into information items and Text nodes that form its children.") 노드 자식 값을 번역할지, 아니면 그대로 둘지를 지정하는 열거형 특성입니다. 다음 값을 사용할 수 있습니다. + 요소의 특성 값과 해당 [`Text`](https://developer.mozilla.org/docs/Web/API/Text "텍스트 인터페이스는 요소 또는 Attr의 텍스트 콘텐츠를 나타냅니다. 요소의 콘텐츠 내에 마크업이 없는 경우 요소의 텍스트를 포함하는 Text를 구현하는 단일 자식이 있습니다. 그러나 요소에 마크업가 포함되어 있으면 해당 자식을 구성하는 정보 항목 및 텍스트 노드로 구문 분석됩니다.") 노드 자식이 페이지가 지역화될 때 변역될지 아니면 변경되지 않은 상태로 둘지를 지정하는 데 사용되는 열거형 속성입니다. 다음 값을 사용할 수 있습니다. * 빈 문자열과 `yes`: 요소를 번역함을 나타냅니다. * `no`: 요소를 번역하지 않음을 나타냅니다. @@ -1798,7 +1797,7 @@ If this attribute is specified, this element must come before any other elements **Note**: This attribute was deprecated in HTML4, but reintroduced in HTML5. **Note:** Prior to Gecko 9.0, negative values were incorrectly converted to 0. Starting in Gecko 9.0 all integer values are correctly parsed. - 이 정수 특성은 [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol "HTML <ol> 요소는 정렬된 항목 목록을 나타내며 일반적으로 번호 매기기 목록으로 렌더링됩니다.") 요소에 정의된 대로 목록 항목의 현재 서수 값을 나타냅니다. 이 특성에서 허용되는 값은 숫자뿐입니다. 목록이 로마 숫자나 글자로 표시되더라도 마찬가지입니다. 이 항목 뒤에 오는 목록 항목은 설정된 값부터 번호를 이어갑니다. **value** 특성은 순서 없는 목록([`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul "The HTML <ul> element represents an unordered list of items, typically rendered as a bulleted list."))이나 메뉴([`<menu>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu "The HTML <menu> element represents a group of commands that a user can perform or activate. This includes both list menus, which might appear across the top of a screen, as well as context menus, such as those that might appear underneath a button after it has been clicked."))에는 의미가 없습니다. + 이 정수 특성은 [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol "HTML <ol> 요소는 정렬된 항목 목록을 나타내며 일반적으로 번호 매기기 목록으로 렌더링됩니다.") 요소에 정의된 대로 목록 항목의 현재 서수 값을 나타냅니다. 이 특성에서 허용되는 값은 숫자뿐입니다. 목록이 로마 숫자나 글자로 표시되더라도 마찬가지입니다. 이 항목 뒤에 오는 목록 항목은 설정된 값부터 번호를 이어받습니다. **value** 특성은 순서가 지정되지 않은 목록([`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul "HTML <ul> 요소는 정렬되지 않은 항목 목록을 나타내며, 일반적으로 글머리 기호 목록으로 렌더링됩니다.")) 또는 메뉴([`<menu>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu "HTML <menu> 요소는 사용자가 수행하거나 활성화할 수 있는 명령 그룹을 나타냅니다. This includes both list menus, which might appear across the top of a screen, as well as context menus, such as those that might appear underneath a button after it has been clicked."))에는 의미가 없습니다. **참고**: 이 특성은 HTML4에서 더 이상 사용되지 않았지만 HTML5에서 다시 도입되었습니다. @@ -1836,13 +1835,13 @@ If this attribute is specified, this element must come before any other elements * `off` : Like `soft` but changes appearance to `white-space: pre` so line segments exceeding `cols` are not wrapped and the `<textarea>` becomes horizontally scrollable. If this attribute is not specified, `soft` is its default value. - 컨트롤에서 텍스트를 줄 바꿈하는 방식을 나타냅니다. 가능한 값은 다음과 같습니다. + 컨트롤이 텍스트를 래핑하는 방법을 나타냅니다. 사용할 수 있는 값은 다음과 같습니다. * `hard`: 각 줄이 컨트롤의 너비를 넘지 않도록 브라우저가 자동으로 줄 바꿈(CR+LF)을 삽입합니다. `cols` 특성도 지정해야 이 기능이 적용됩니다. * `soft`: 브라우저는 값의 모든 줄 바꿈이 CR+LF 쌍이 되도록 보장하지만, 추가 줄 바꿈은 삽입하지 않습니다. * `off`: `soft`와 비슷하지만 표시 방식이 `white-space: pre`로 바뀌므로 `cols`를 초과하는 줄은 줄 바꿈이 되지 않고 `<textarea>`가 가로 스크롤이 가능해집니다. -이 특성을 지정하지 않으면 기본값은 `soft`입니다. +이 특성을 지정하지 않으면 `soft`가 기본값입니다. diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.pl.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.pl.xlf index fbaf94b67f337..f134927e572bf 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.pl.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.pl.xlf @@ -43,8 +43,8 @@ W poprzednich wersjach HTML różne kodowania znaków można było rozdzielać s **Uwaga dotycząca użycia** -* **Nie używaj tego atrybutu**, ponieważ jest przestarzały. Element [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table "Element HTML <table> reprezentuje dane tabelaryczne, czyli informacje przedstawione w dwuwymiarowej tabeli złożonej z wierszy i kolumn komórek zawierających dane.") powinien mieć styl nadany za pomocą [CSS](https://developer.mozilla.org/en-US/docs/CSS). Ustaw [`margin-left`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left "Właściwość CSS margin-left ustawia obszar marginesu po lewej stronie elementu. Wartość dodatnia przesuwa go dalej od sąsiadów, a wartość ujemna przybliża.") i [`margin-right`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right "Właściwość CSS margin-right ustawia obszar marginesu po prawej stronie elementu. Wartość dodatnia przesuwa go dalej od sąsiadów, a wartość ujemna przybliża.") na `auto` albo [`margin`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin "Właściwość CSS margin ustawia obszar marginesu ze wszystkich czterech stron elementu. Jest to skrót od margin-top, margin-right, margin-bottom i margin-left.") na `0 auto`, aby uzyskać efekt podobny do atrybutu align. -* Przed przeglądarką Firefox 4, przeglądarka Firefox obsługiwała też wartości `middle`, `absmiddle` i `abscenter` jako synonimy `center`, ale tylko w trybie zgodności ze starymi witrynami. +* **Nie używaj tego atrybutu**, ponieważ jest przestarzały. Element [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table „Element HTML <table> reprezentuje dane tabelaryczne, czyli informacje przedstawione w dwuwymiarowej tabeli złożonej z wierszy i kolumn komórek zawierających dane.”) powinien mieć styl nadany za pomocą [CSS](https://developer.mozilla.org/en-US/docs/CSS). Ustaw [`margin-left`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left „Właściwość CSS margin-left ustawia obszar marginesu po lewej stronie elementu. Wartość dodatnia przesuwa go dalej od sąsiadów, a wartość ujemna przybliża.”) i [`margin-right`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right „Właściwość CSS margin-right ustawia obszar marginesu po prawej stronie elementu. Wartość dodatnia przesuwa go dalej od sąsiadów, a wartość ujemna przybliża.”) na `auto` albo [`margin`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin „Właściwość CSS margin ustawia obszar marginesu ze wszystkich czterech stron elementu. Jest to skrót od margin-top, margin-right, margin-bottom i margin-left.") na `0 auto`, aby uzyskać efekt podobny do atrybutu align. +* Przed przeglądarką Firefox 4, przeglądarka Firefox obsługiwała też wartości `middle`, `absmiddle`, i `abscenter` jako synonimy `center`, ale tylko w trybie zgodności ze starymi witrynami. @@ -93,7 +93,7 @@ Tego atrybutu nie wolno używać, jeśli atrybut `src` jest nieobecny (czyli w p Przeglądarki zwykle zakładają najgorszy scenariusz i podczas analizowania języka HTML ładują skrypty synchronicznie, czyli `async="false"`. -Skrypty wstawiane dynamicznie (za pomocą polecenia [`document.createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement "W dokumencie HTML metoda document.createElement() tworzy element języka HTML określony przez tagName lub HTMLUnknownElement, jeśli tagName nie zostanie rozpoznany.")) domyślnie ładują się asynchronicznie, więc aby włączyć ładowanie synchroniczne, czyli ładowanie skryptów w kolejności ich wstawienia, ustaw polecenie `async="false"`. +Skrypty wstawiane dynamicznie (za pomocą polecenia [`document.createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement „W dokumencie HTML metoda document.createElement() tworzy element języka HTML określony przez tagName lub HTMLUnknownElement, jeśli tagName nie zostanie rozpoznany.”)) domyślnie ładują się asynchronicznie, więc aby włączyć ładowanie synchroniczne, czyli ładowanie skryptów w kolejności ich wstawienia, ustaw polecenie `async="false"`. Zobacz sekcję [Browser compatibility](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/script#Browser_compatibility), aby sprawdzić uwagi dotyczące obsługi w przeglądarkach. Zobacz też [Async scripts for asm.js](https://developer.mozilla.org/en-US/docs/Games/Techniques/Async_scripts). @@ -127,7 +127,7 @@ For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) sett * `on`: przeglądarka może automatycznie uzupełniać wartości na podstawie danych, które użytkownik wcześniej wpisał w formularzu. W przypadku większości nowoczesnych przeglądarek (w tym Firefox 38+, Google Chrome 34+, IE 11+) ustawienie atrybutu autouzupełniania nie zapobiegnie pytaniu przez menedżera haseł przeglądarki o to, czy użytkownik chce zapisać pola logowania (nazwę użytkownika i hasło). Jeśli użytkownik wyrazi zgodę na zapisanie, przeglądarka automatycznie wypełni pola logowania przy następnej wizycie użytkownika na stronie. Zobacz [Atrybut autouzupełniania i pola logowania](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion#The_autocomplete_attribute_and_login_fields). -**Uwaga:** Jeśli w formularzu ustawisz `autocomplete` na `off`, ponieważ dokument zapewnia własne autouzupełnianie, ustaw też `autocomplete` na `off` dla każdego elementu `input` tego formularza, który dokument może automatycznie uzupełniać. Aby uzyskać szczegółowe informacje, zapoznaj się z notatką dotyczącą przeglądarki Google Chrome w [tabeli zgodności przeglądarek](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form#compatChart). +**Uwaga:** Jeśli w formularzu ustawisz `autocomplete` na wartość `off`, ponieważ dokument zapewnia własne autouzupełnianie, ustaw też `autocomplete` na wartość `off` dla każdego elementu `input` tego formularza, który dokument może automatycznie uzupełniać. Aby uzyskać szczegółowe informacje, zapoznaj się z notatką dotyczącą przeglądarki Google Chrome w [tabeli zgodności przeglądarek](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form#compatChart). @@ -388,7 +388,7 @@ This value can be overridden by a [`formenctype`](https://developer.mozilla.org/ Gdy atrybut `method` ma wartość `post`, enctype określa [typ MIME](https://en.wikipedia.org/wiki/Mime_type) zawartości używanej do wysłania formularza do serwera. Możliwe wartości to: * `application/x-www-form-urlencoded`: wartość domyślna, jeśli atrybut nie został określony. -* `multipart/form-data`: Wartość używana dla elementu [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input „Element HTML <input> służy do tworzenia interaktywnych kontrolek dla formularzy internetowych w celu akceptowania danych od użytkownika. Dostępnych jest wiele różnych typów danych wejściowych i widżetów kontrolek, w zależności od urządzenia i agenta użytkownika.”) z atrybutem `type` ustawionym na „file”. +* `multipart/form-data`: Wartość używana dla elementu [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input „Element HTML <input> służy do tworzenia interaktywnych kontrolek dla formularzy internetowych w celu akceptowania danych od użytkownika. Dostępnych jest wiele różnych typów danych wejściowych i widżetów kontrolek, w zależności od urządzenia i agenta użytkownika.”) z atrybutem `type` ustawionym na wartość „file”. * `text/plain`: (HTML5) Tę wartość można zastąpić atrybutem [`formenctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formenctype) w elemencie [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button „Element HTML <button> reprezentuje klikalny przycisk, który można używać w formularzach lub w dowolnym miejscu dokumentu, w którym wymagana jest prosta, standardowa funkcjonalność przycisku.”) lub [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input „Element HTML <input> służy do tworzenia interaktywnych kontrolek dla formularzy internetowych w celu akceptowania danych od użytkownika; dostępna jest szeroka gama typów danych wejściowych i widżetów sterujących, w zależności od urządzenia i agenta użytkownika.”). @@ -439,7 +439,7 @@ If this attribute is specified, it overrides the [`enctype`](https://developer.m Jeśli przycisk jest przyciskiem przesyłania, ten atrybut określa typ zawartości używany do wysłania formularza na serwer. Możliwe wartości to: * `application/x-www-form-urlencoded`: wartość domyślna, jeśli atrybut nie został określony. -* `multipart/form-data`: Użyj tej wartości, jeśli używasz elementu [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input „Element HTML <input> służy do tworzenia interaktywnych kontrolek dla formularzy internetowych w celu akceptowania danych od użytkownika. Dostępnych jest wiele różnych typów danych wejściowych i widżetów kontrolek, w zależności od urządzenia i przeglądarki użytkownika.”) z atrybutem [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-type) ustawionym na `file`. +* `multipart/form-data`: Użyj tej wartości, jeśli używasz elementu [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input „Element HTML <input> służy do tworzenia interaktywnych kontrolek dla formularzy internetowych w celu akceptowania danych od użytkownika. Dostępnych jest wiele różnych typów danych wejściowych i widżetów kontrolek, w zależności od urządzenia i przeglądarki użytkownika.”) z atrybutem [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-type) ustawionym na wartość `file`. * `text/plain` Jeżeli ten atrybut jest określony, zastępuje on atrybut [`enctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-enctype) właściciela formularza przycisku. @@ -647,7 +647,7 @@ Definiuje [plik cookie](https://developer.mozilla.org/en-US/docs/cookie) dla str * `chapters` * Tytuły rozdziałów są przeznaczone do użycia, gdy użytkownik nawiguję po zasobie multimedialnym. * `metadata` -* Ścieżki używane przez skrypty. Niewidoczne dla użytkownika. +* Ścieżki używane przez skrypty. Niewidoczny dla użytkownika. @@ -696,7 +696,7 @@ Definiuje [plik cookie](https://developer.mozilla.org/en-US/docs/cookie) dla str **Notatki:** -* W HTML 4 może to być tylko prosta, rozdzielona białymi znakami lista literałów opisujących nośniki, czyli [media types and groups](https://developer.mozilla.org/en-US/docs/Web/CSS/@media), tam, gdzie są zdefiniowane i dozwolone jako wartości tego atrybutu, na przykład `print`, `screen`, `aural`, `braille`. HTML5 rozszerzył to na dowolne [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries), które są nadzbiorem dozwolonych wartości HTML 4. +* W HTML 4 może to być tylko prosta, rozdzielona białymi znakami lista literałów opisujących nośniki, czyli [typy i grupy nośników](https://developer.mozilla.org/en-US/docs/Web/CSS/@media), tam, gdzie są zdefiniowane i dozwolone jako wartości tego atrybutu, na przykład `print`, `screen`, `aural`, `braille`. HTML5 rozszerzył to na dowolne [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries), które są nadzbiorem dozwolonych wartości HTML 4. * Przeglądarki, które nie obsługują [CSS3 Media Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries), nie muszą rozpoznawać odpowiedniego odwołania; nie zapomnij ustawić odwołań zapasowych, czyli ograniczonego zestawu zapytań o media zdefiniowanego w HTML 4. @@ -1004,7 +1004,7 @@ Ten atrybut może też przyjmować wartość z rozszerzonej listy zdefiniowanej * `creator`, który definiuje nazwę twórcy dokumentu, na przykład organizacji lub instytucji. Jeśli jest ich więcej niż jedna, należy użyć kilku elementów [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "Element HTML <meta> reprezentuje metadane, których nie można przedstawić za pomocą innych elementów HTML związanych z metadanymi, takich jak <base>, <link>, <script>, <style> lub <title>."). * `googlebot`, synonim `robots`, jest przestrzegany tylko przez Googlebot (robota indeksującego Google). * `publisher`, który definiuje nazwę wydawcy dokumentu. -* `robots`, który definiuje zachowanie, jakie współpracujące roboty indeksujące, czyli „roboty”, powinny stosować wobec strony. To lista wartości oddzielonych przecinkami: +* `robots`, który definiuje zachowanie, jakie współpracujące roboty indeksujące, czyli "robots", powinny stosować wobec strony. To lista wartości oddzielonych przecinkami: Wartości zawartości elementu `<meta name="robots">` @@ -1142,7 +1142,7 @@ Wersja robocza Opisuje element META elementu viewport w sposób nienormatywny -Zobacz też: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport "Reguła CSS @viewport pozwala skonfigurować element viewport, przez który dokument jest wyświetlany. Jest używana głównie na urządzeniach mobilnych, ale też przez przeglądarki na komputerach, które obsługują funkcje takie jak "snap to edge" (na przykład Microsoft Edge).") +Zobacz też: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport "Reguła CSS @viewport pozwala skonfigurować element viewport, przez który dokument jest wyświetlany. Jest używana głównie na urządzeniach mobilnych, ale też przez przeglądarki na komputerach, które obsługują funkcje takie jak „snap to edge” (na przykład Microsoft Edge).") **Notatki:** @@ -1635,7 +1635,7 @@ Zobacz też: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@vie **Note:** According to the HTML5 specification, the default value for size should be 1; however, in practice, this has been found to break some web sites, and no other browser currently does that, so Mozilla has opted to continue to return 0 for the time being with Firefox. Jeśli kontrolka jest prezentowana jako przewijane pole listy (na przykład gdy określono `multiple`), ten atrybut określa liczbę wierszy na liście, które powinny być widoczne jednocześnie. Przeglądarki nie muszą prezentować elementu select jako przewijanej listy. Wartość domyślna to 0. -**Uwaga:** zgodnie ze specyfikacją HTML5 wartość domyślna dla size powinna wynosić 1; w praktyce jednak powodowało to problemy w niektórych witrynach, a żaden inny browser obecnie tak nie robi, więc Mozilla zdecydowała się na razie nadal zwracać 0 w przeglądarce Firefox. +**Uwaga:** zgodnie ze specyfikacją HTML5 wartość domyślna dla „size” powinna wynosić 1; w praktyce jednak powodowało to problemy w niektórych witrynach, a żaden inny browser obecnie tak nie robi, więc Mozilla zdecydowała się na razie nadal zwracać 0 w przeglądarce Firefox. @@ -1710,7 +1710,7 @@ The user agent selects any one of the available sources at its discretion. This Jeśli nie określono deskryptora, źródłu przypisywany jest domyślny deskryptor: `1x`. -Nie można mieszać deskryptorów szerokości i deskryptorów gęstości pikseli w tym samym atrybucie `srcset`. Zduplikowane deskryptory, na przykład dwa źródła w tym samym elemencie `srcset`, które są opisane jako '`2x`', też są nieprawidłowe. +Nie można mieszać deskryptorów szerokości i deskryptorów gęstości pikseli w tym samym atrybucie `srcset`. Zduplikowane deskryptory, na przykład dwa źródła w tym samym elemencie `srcset`, które są opisane jako `2x`, też są nieprawidłowe. Agent użytkownika wybiera dowolne elementy z dostępnych źródeł według własnego uznania. Daje mu to dużą swobodę w dostosowaniu wyboru do takich czynników jak preferencje użytkownika czy warunki przepustowości. Zobacz nasz samouczek [Responsive images](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images), aby zobaczyć przykład. @@ -1773,7 +1773,7 @@ Jeśli ten atrybut jest określony, ten element musi wystąpić przed wszystkimi * `no`, which indicates that the element will not be translated. Atrybut wyliczeniowy służący do określenia, czy wartości atrybutów elementu i wartości jego [`Text`](https://developer.mozilla.org/docs/Web/API/Text „Interfejs Text reprezentuje zawartość tekstową elementu lub atrybutu. Jeśli element nie ma w swojej zawartości żadnego znacznika, to ma pojedynczy element podrzędny wdrażający element Text, który zawiera tekst elementu. Jeśli jednak element zawiera znaczniki, są one przetwarzane na elementy informacyjne i węzły tekstowe, które tworzą jego elementy podrzędne.") Węzły podrzędne mają zostać przetłumaczone podczas lokalizacji strony lub czy mają pozostać niezmienione. Może przyjmować następujące wartości: -* pusty ciąg i `yes`, co oznacza, że element zostanie przetłumaczony. +* pusty ciąg i wartość `yes`, co oznacza, że element zostanie przetłumaczony. * `no`, co oznacza, że element nie zostanie przetłumaczony. @@ -1839,7 +1839,7 @@ If this attribute is not specified, `soft` is its default value. * `hard`: przeglądarka automatycznie wstawia podziały wierszy (CR+LF), tak aby każdy wiersz nie był szerszy niż kontrolka; aby to działało, trzeba też określić atrybut `cols`. * `soft`: przeglądarka zapewnia, że wszystkie podziały wierszy w wartości składają się z pary CR+LF, ale nie wstawia dodatkowych podziałów wierszy. -* `off`: jak `soft`, ale zmienia wygląd na `white-space: pre`, więc segmenty linii przekraczające `cols` nie są zawijane, a `<textarea>` staje się przewijalna w poziomie. +* `off`: jak `soft`, ale zmienia wygląd na `white-space: pre`, więc segmenty linii przekraczające `cols` nie są zawijane, a wartość `<textarea>` staje się przewijalna w poziomie. Jeśli ten atrybut nie zostanie określony, jego domyślną wartością jest `soft`. diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.pt-BR.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.pt-BR.xlf index fb97a58096346..5836785b3f941 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.pt-BR.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.pt-BR.xlf @@ -647,7 +647,7 @@ Define um [cookie](https://developer.mozilla.org/en-US/docs/cookie) para a pági * `chapters` * Títulos de capítulo devem ser usados quando o usuário estiver navegando pelo recurso de mídia. * `metadata` -* Faixas usadas por scripts. Não visível para o usuário. +* Faixas usadas por scripts. Não é visível para o usuário. @@ -1635,7 +1635,7 @@ Consulte também: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS **Note:** According to the HTML5 specification, the default value for size should be 1; however, in practice, this has been found to break some web sites, and no other browser currently does that, so Mozilla has opted to continue to return 0 for the time being with Firefox. Se o controle for apresentado como uma caixa de lista com rolagem (por exemplo, quando `multiple` é especificado), este atributo representa o número de linhas da lista que devem estar visíveis ao mesmo tempo. Os navegadores não são obrigados a apresentar um elemento select como uma caixa de lista com rolagem. O valor padrão é 0. -**Observação:** de acordo com a especificação do HTML5, o valor padrão de tamanho deveria ser 0; no entanto, na prática, isso foi observado como algo que quebra alguns sites, e nenhum outro navegador atualmente adota esse comportamento, então a Mozilla optou por continuar retornando 0 por enquanto no Firefox. +**Observação:** de acordo com a especificação do HTML5, o valor padrão de tamanho deveria ser 1; no entanto, na prática, isso foi observado como algo que quebra alguns sites, e nenhum outro navegador atualmente adota esse comportamento, então a Mozilla optou por continuar retornando 0 por enquanto no Firefox. diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.ru.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.ru.xlf index 82c96f3702427..fa2f42ba5434f 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.ru.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.ru.xlf @@ -39,11 +39,11 @@ In previous versions of HTML, the different character encodings could be delimit * слева: таблица отображается в левой части документа; * центр: таблица отображается в центре документа; -* справа: таблица отображается в правой части документа. +* справа: таблица отображается в правой части документа. **Примечание по использованию** -* **Не используйте этот атрибут**, так как он устарел. Элемент [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table "HTML-элемент <table> представляет табличные данные — то есть информацию, организованную в виде двумерной таблицы, состоящей из строк и столбцов ячеек с данными.") следует оформлять с помощью [CSS](https://developer.mozilla.org/en-US/docs/CSS). Установите [`margin-left`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left "CSS-свойство margin-left задает размер внешнего отступа с левой стороны элемента. Положительное значение отодвигает элемент от соседних, а отрицательное — приближает его.") и [`margin-right`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right "CSS-свойство margin-right задает внешний отступ с правой стороны элемента. Положительное значение отодвигает элемент от соседних, а отрицательное — приближает его.") на `auto` или [`margin`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin "CSS-свойство margin задает область внешнего отступа со всех четырех сторон элемента. Это сокращенная запись для margin-top, margin-right, margin-bottom и margin-left." с `0 auto` для достижения эффекта, аналогичного атрибуту align. +* **Не используйте этот атрибут**, так как он устарел. Элемент [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table "HTML-элемент <table> представляет табличные данные — то есть информацию, организованную в виде двумерной таблицы, состоящей из строк и столбцов ячеек с данными.") следует оформлять с помощью [CSS](https://developer.mozilla.org/en-US/docs/CSS). Установите [`margin-left`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left "CSS-свойство margin-left задает размер внешнего отступа с левой стороны элемента. Положительное значение отодвигает элемент от соседних, а отрицательное — приближает его.") и [`margin-right`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right "CSS-свойство margin-right задает внешний отступ с правой стороны элемента. Положительное значение отодвигает элемент от соседних, а отрицательное — приближает его.") на `auto` или [`margin`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin "CSS-свойство margin задает область внешнего отступа со всех четырех сторон элемента. Это сокращенная запись для margin-top, margin-right, margin-bottom и margin-left." с `0 auto` для достижения эффекта, аналогичного атрибуту align. * До версии Firefox 4 Firefox также поддерживал значения `middle`, `absmiddle` и `abscenter` в качестве синонимов `center`, но только в режиме совместимости. @@ -55,7 +55,7 @@ In previous versions of HTML, the different character encodings could be delimit Set to `true` if the `<iframe>` can activate fullscreen mode by calling the [`requestFullscreen()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen "The Element.requestFullscreen() method issues an asynchronous request to make the element be displayed in full-screen mode.") method. This attribute is considered a legacy attribute and redefined as `allow="fullscreen"`. - Установите значение `true`, если `<iframe>` может активировать полноэкранный режим, вызвав метод [`requestFullscreen()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen "Метод Element.requestFullscreen() отправляет асинхронный запрос для отображения элемента в полноэкранном режиме."). + Установите значение `true`, если `<iframe>` может активировать полноэкранный режим, вызвав метод [`requestFullscreen()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen "Метод Element.requestFullscreen() отправляет асинхронный запрос для отображения элемента в полноэкранном режиме."). Этот атрибут считается устаревшим и переопределен как `allow="fullscreen"`. @@ -65,9 +65,9 @@ This attribute is considered a legacy attribute and redefined as `allow="fullscr **Note:** Browsers do not always display the image referenced by the element. This is the case for non-graphical browsers (including those used by people with visual impairments), if the user chooses not to display images, or if the browser cannot display the image because it is invalid or an [unsupported type](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/img#Supported_image_formats). In these cases, the browser may replace the image with the text defined in this element's `alt` attribute. You should, for these reasons and others, provide a useful value for `alt` whenever possible. **Note:** Omitting this attribute altogether indicates that the image is a key part of the content, and no textual equivalent is available. Setting this attribute to an empty string (`alt=""`) indicates that this image is _not_ a key part of the content (decorative), and that non-visual browsers may omit it from rendering. - Этот атрибут определяет альтернативное текстовое описание изображения. + Этот атрибут определяет альтернативное текстовое описание изображения. -**Примечание:** Браузеры не всегда отображают изображение, на которое ссылается элемент. Это касается текстовых браузеров (в том числе тех, которыми пользуются люди с нарушениями зрения), случаев, когда пользователь отключает отображение изображений, а также ситуаций, когда браузер не может отобразить изображение из-за его некорректности или использования [неподдерживаемого формата](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/img#Supported_image_formats). В таких случаях браузер может заменить изображение текстом, указанным в атрибуте `alt` этого элемента. По этим и другим причинам вам следует по возможности указывать полезное значение для `alt`. +**Примечание:** Браузеры не всегда отображают изображение, на которое ссылается элемент. Это касается текстовых браузеров (в том числе тех, которыми пользуются люди с нарушениями зрения), случаев, когда пользователь отключает отображение изображений, а также ситуаций, когда браузер не может отобразить изображение из-за его некорректности или использования [неподдерживаемого формата](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/img#Supported_image_formats). В таких случаях браузер может заменить изображение текстом, указанным в атрибуте `alt` этого элемента. По этим и другим причинам вам следует по возможности указывать полезное значение для `alt`. **Примечание:** Полное отсутствие этого атрибута означает, что изображение является ключевой частью контента, и текстовый эквивалент отсутствует. Установка этого атрибута в значение пустой строки (`alt=""`) указывает на то, что данное изображение _не_ является важной частью контента (оно носит декоративный характер) и что невизуальные браузеры могут не включать его в процесс отображения. @@ -87,13 +87,13 @@ Browsers usually assume the worst case scenario and load scripts synchronously, Dynamically inserted scripts (using [`document.createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement "In an HTML document, the document.createElement() method creates the HTML element specified by tagName, or an HTMLUnknownElement if tagName isn't recognized.")) load asynchronously by default, so to turn on synchronous loading (i.e. scripts load in the order they were inserted) set `async="false"`. See [Browser compatibility](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/script#Browser_compatibility) for notes on browser support. See also [Async scripts for asm.js](https://developer.mozilla.org/en-US/docs/Games/Techniques/Async_scripts). - Это логический атрибут, указывающий на то, что браузер должен, если это возможно, загружать скрипт асинхронно. + Это логический атрибут, указывающий на то, что браузер должен, если это возможно, загружать скрипт асинхронно. -Этот атрибут не следует использовать, если отсутствует атрибут `src` (т. е. для встроенных сценариев). Если его включить в этот случай, это не возымеет никакого эффекта. +Этот атрибут не следует использовать, если отсутствует атрибут `src` (т. е. для встроенных сценариев). Если его включить в этот случай, это не возымеет никакого эффекта. -Браузеры обычно исходят из наихудшего сценария и загружают скрипты синхронно (т.е. `async="false"`) во время анализа HTML. +Браузеры обычно исходят из наихудшего сценария и загружают скрипты синхронно (т.е. `async="false"`) во время анализа HTML. -Динамически вставляемые скрипты (с использованием [`document.createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement "В HTML-документе метод document.createElement() создает HTML-элемент, указанный tagName, или HTMLUnknownElement, если tagName не распознан.")) по умолчанию загружаются асинхронно, поэтому для включения синхронной загрузки (т.е. скрипты загружаются в порядке их вставки) установите `async="false"`. +Динамически вставляемые скрипты (с использованием [`document.createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement "В HTML-документе метод document.createElement() создает HTML-элемент, указанный tagName, или HTMLUnknownElement, если tagName не распознан.")) по умолчанию загружаются асинхронно, поэтому для включения синхронной загрузки (т.е. скрипты загружаются в порядке их вставки) установите `async="false"`. Сведения о поддержке браузерами см. в разделе [Совместимость с браузерами](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/script#Browser_compatibility). См. также [Асинхронные сценарии для asm.js](https://developer.mozilla.org/en-US/docs/Games/Techniques/Async_scripts). @@ -110,7 +110,7 @@ See [Browser compatibility](https://developer.mozilla.org/docs/Web/HTML/Referenc * `off` или `none`, автоматическое преобразование в верхний регистр не применяется (все буквы по умолчанию строчные) * `on` или `sentences` первая буква каждого предложения по умолчанию заглавная; все остальные буквы по умолчанию строчные * `words`, первая буква каждого слова по умолчанию заглавная; все остальные буквы по умолчанию строчные -* `characters`, все буквы по умолчанию должны быть в верхнем регистре. +* `characters`, все буквы по умолчанию должны быть в верхнем регистре @@ -123,10 +123,10 @@ For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) sett **Note:** If you set `autocomplete` to `off` in a form because the document provides its own auto-completion, then you should also set `autocomplete` to `off` for each of the form's `input` elements that the document can auto-complete. For details, see the note regarding Google Chrome in the [Browser Compatibility chart](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form#compatChart). Указывает, могут ли входные элементы по умолчанию автоматически выполняться браузером. Этот параметр может быть переопределен атрибутом `autocomplete` у элемента, входящего в состав формы. Возможные значения: -* `off`: Пользователь должен явно вводить значение в каждое поле при каждом использовании, или документ предоставляет собственный метод автозаполнения; браузер не дополняет записи автоматически. -* `on`: Браузер может автоматически дополнять значения на основе значений, которые пользователь ранее ввел в форму. +* `off`: Пользователь должен явно вводить значение в каждое поле при каждом использовании, или документ предоставляет собственный метод автозаполнения; браузер не дополняет записи автоматически. +* `on`: Браузер может автоматически дополнять значения на основе значений, которые пользователь ранее ввел в форму. -В большинстве современных браузеров (включая Firefox 38+, Google Chrome 34+ и IE 11+) установка атрибута autocomplete не помешает встроенному диспетчеру паролей предложить пользователю сохранить данные для входа (имя пользователя и пароль); если пользователь согласится на сохранение, браузер автоматически подставит эти данные при следующем посещении страницы. См. [Атрибут автозаполнения и поля авторизации](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion#The_autocomplete_attribute_and_login_fields). +В большинстве современных браузеров (включая Firefox 38+, Google Chrome 34+ и IE 11+) установка атрибута autocomplete не помешает встроенному диспетчеру паролей предложить пользователю сохранить данные для входа (имя пользователя и пароль); если пользователь согласится на сохранение, браузер автоматически подставит эти данные при следующем посещении страницы. См. [Атрибут автозаполнения и поля авторизации](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion#The_autocomplete_attribute_and_login_fields). **Примечание:** Если вы `off` функцию `autocomplete` в форме, поскольку документ предоставляет собственную функцию автозаполнения, то вам также следует `off` функцию `autocomplete` для каждого из элементов `input` формы, для которых документ может выполнять автозаполнение. Подробные сведения см. в заметке о Google Chrome на [диаграмме совместимости браузера](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form#compatChart). @@ -144,7 +144,7 @@ For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) sett This attribute contains a list of space-separated strings. Each string is the `id` of a group of cells that this header applies to. **Note:** Do not use this attribute as it is obsolete in the latest standard: use the [`scope`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th#attr-scope) attribute instead. - Этот атрибут содержит список строк, разделенных пробелами. Каждая строка представляет собой `id` группы ячеек, к которым относится этот заголовок. + Этот атрибут содержит список строк, разделенных пробелами. Каждая строка представляет собой `id` группы ячеек, к которым относится этот заголовок. **Примечание:** Не используйте этот атрибут, так как он устарел в последней версии стандарта: вместо него используйте атрибут [`scope`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th#attr-scope). @@ -169,21 +169,21 @@ For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) sett * The [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") element with a `charset` attribute is a synonym for the pre-HTML5 `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">`, where _`IANAcharset`_ contains the value of the equivalent [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) attribute. This syntax is still allowed, although no longer recommended. Этот атрибут объявляет кодировку символов страницы. Он должен содержать [стандартное имя MIME по классификации IANA для кодировок символов](https://www.iana.org/assignments/character-sets). Хотя стандарт не требует конкретной кодировки, он предлагает: -* Авторам рекомендуется использовать [`UTF-8`](https://developer.mozilla.org/en-US/docs/Glossary/UTF-8). -* Во избежание рисков для безопасности авторам не следует использовать кодировки, несовместимые с ASCII: браузеры, не поддерживающие их, могут интерпретировать вредоносный контент как HTML. Это происходит с `JIS_C6226-1983`, `JIS_X0212-1990`, `HZ-GB-2312`, `JOHAB`, семейством ISO-2022 и семейством EBCDIC. +* Авторам рекомендуется использовать [`UTF-8`](https://developer.mozilla.org/en-US/docs/Glossary/UTF-8). +* Во избежание рисков для безопасности авторам не следует использовать кодировки, несовместимые с ASCII: браузеры, не поддерживающие их, могут интерпретировать вредоносный контент как HTML. Это происходит с `JIS_C6226-1983`, `JIS_X0212-1990`, `HZ-GB-2312`, `JOHAB`, семейством ISO-2022 и семейством EBCDIC. **Примечание:** ASCII-несовместимые кодировки — это те, которые не сопоставляют 8-битные кодовые точки `0x20` — `0x7E` с кодовыми точками Unicode `0x0020` — `0x007E` -* Авторам **не следует** использовать `CESU-8`, `UTF-7`, `BOCU-1` и/или `SCSU`, поскольку были продемонстрированы атаки с использованием этих кодировок, связанные с [межсайтовым скриптингом](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting). -* Авторам не следует использовать `UTF-32`, поскольку не все алгоритмы кодирования HTML5 могут отличить его от `UTF-16`. +* Авторам **не следует** использовать `CESU-8`, `UTF-7`, `BOCU-1` и/или `SCSU`, поскольку были продемонстрированы атаки с использованием этих кодировок, связанные с [межсайтовым скриптингом](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting). +* Авторам не следует использовать `UTF-32`, поскольку не все алгоритмы кодирования HTML5 могут отличить его от `UTF-16`. **Примечания:** -* Указанная кодировка символов должна совпадать с кодировкой, с которой была сохранена страница, чтобы избежать искажения символов и уязвимостей безопасности. -* Элемент [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "Элемент HTML <meta> представляет метаданные, которые не могут быть представлены другими связанными с HTML метаданными элементами, такими как <base>, <link> , <script>, <style> или <title>."), объявляющий кодировку, должен находиться внутри элемента [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head "Элемент HTML <head> предоставляет общую информацию (метаданные) о документе, включая его заголовок и ссылки на его скрипты и таблицы стилей.") и **в пределах первых 1024 байт** HTML, поскольку некоторые браузеры анализируют только эти байты перед выбором кодировки. -* Этот элемент [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML-элемент <meta> представляет метаданные, которые не могут быть представлены другими связанными с HTML мета-элементами, такими как <base>, <link> , <script>, <style> или <title>.") является лишь частью [алгоритма определения кодировки страницы](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Алгоритм кодировки страницы"). Заголовок [`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) и любые [метки порядка байтов](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark "Определение этого термина (метки порядка байтов) еще не написано; пожалуйста, рассмотрите возможность внесения своего вклада!") переопределяют этот элемент. -* Настоятельно рекомендуется указать кодировку символов. Если кодировка страницы не определена, возможны методы кросс-скриптинга, такие как [метод кросс-скриптинга с резервным кодом `UTF-7`](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7). -* Элемент [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> элемент представляет метаданные, которые не могут быть представлены другими связанными с HTML мета-элементами, такими как <base>, <link> , <script>, <style> или <title>.") с атрибутом `charset` является синонимом элемента `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">`, существовавшего до HTML5, где _`IANAcharset`_ содержит значение эквивалентного атрибута [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset). Этот синтаксис по-прежнему допускается, хотя больше не рекомендуется. +* Указанная кодировка символов должна совпадать с кодировкой, с которой была сохранена страница, чтобы избежать искажения символов и уязвимостей безопасности. +* Элемент [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "Элемент HTML <meta> представляет метаданные, которые не могут быть представлены другими связанными с HTML метаданными элементами, такими как <base>, <link>, <script>, <style> или <title>."), объявляющий кодировку, должен находиться внутри элемента [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head "Элемент HTML <head> предоставляет общую информацию (метаданные) о документе, включая его заголовок и ссылки на его скрипты и таблицы стилей.") и **в пределах первых 1024 байт** HTML, поскольку некоторые браузеры анализируют только эти байты перед выбором кодировки. +* Этот элемент [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML-элемент <meta> представляет метаданные, которые не могут быть представлены другими связанными с HTML мета-элементами, такими как <base>, <link>, <script>, <style> или <title>.") является лишь частью [алгоритма определения кодировки страницы](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Алгоритм кодировки страницы"). Заголовок [`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) и любые [метки порядка байтов](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark "Определение этого термина (метки порядка байтов) еще не написано; пожалуйста, рассмотрите возможность внесения своего вклада!") переопределяют этот элемент. +* Настоятельно рекомендуется указать кодировку символов. Если кодировка страницы не определена, возможны методы кросс-скриптинга, такие как [метод кросс-скриптинга с резервным кодом `UTF-7`](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7). +* Элемент [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> элемент представляет метаданные, которые не могут быть представлены другими связанными с HTML мета-элементами, такими как <base>, <link>, <script>, <style> или <title>.") с атрибутом `charset` является синонимом элемента `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">`, существовавшего до HTML5, где _`IANAcharset`_ содержит значение эквивалентного атрибута [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset). Этот синтаксис по-прежнему допускается, хотя больше не рекомендуется. @@ -243,11 +243,11 @@ If the attribute is not present, the resource is fetched without a [CORS](https: `anonymous` -Запрос из другого источника (т.е. с заголовком [`Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin "Заголовок запроса Origin указывает, откуда исходит запрос. Это не содержит сведений о пути — только имя сервера. Это отправляется с запросами CORS, а также с запросами POST. Это похоже на заголовок Referer, но, в отличие от него, не раскрывает полный путь. При этом передача учетных данных (например, файлов cookie, сертификатов X.509 или данных для базовой проверки подлинности HTTP) не осуществляется. Если сервер не предоставляет учетные данные исходному сайту (не устанавливая заголовок HTTP [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin "Заголовок ответа Access-Control-Allow-Origin указывает, можно ли передать ответ запрашивающему коду с данного источника")), изображение будет испорчено, и его использование будет ограничено. +Запрос из другого источника (т.е. с заголовком [`Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin "Заголовок запроса Origin указывает, откуда исходит запрос. Это не содержит сведений о пути — только имя сервера. Это отправляется с запросами CORS, а также с запросами POST. Это похоже на заголовок Referer, но, в отличие от него, не раскрывает полный путь. При этом передача учетных данных (например, файлов cookie, сертификатов X.509 или данных для базовой проверки подлинности HTTP) не осуществляется. Если сервер не предоставляет учетные данные исходному сайту (не устанавливая заголовок HTTP [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin "Заголовок ответа Access-Control-Allow-Origin указывает, можно ли передать ответ запрашивающему коду с данного источника")), изображение будет испорчено, и его использование будет ограничено. `use-credentials` -Выполняется междоменный запрос (т.е. с HTTP-заголовком `Origin`), а также отправляются учетные данные (т.е. cookie, сертификат и/или выполняется базовая HTTP-аутентификация). Если сервер не предоставляет учетные данные исходному сайту (через заголовок ответа [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials "Заголовок ответа Access-Control-Allow-Credentials сообщает браузерам, следует ли предоставлять ответ коду JavaScript на стороне клиента, если режим учетных данных запроса (Request.credentials) установлен в "include")), ресурс будет _загрязнен_, и его использование будет ограничено. +Выполняется междоменный запрос (т.е. с HTTP-заголовком `Origin`), а также отправляются учетные данные (т.е. cookie, сертификат и/или выполняется базовая HTTP-аутентификация). Если сервер не предоставляет учетные данные исходному сайту (через заголовок ответа [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials "Заголовок ответа Access-Control-Allow-Credentials сообщает браузерам, следует ли предоставлять ответ коду JavaScript на стороне клиента, если режим учетных данных запроса (Request.credentials) установлен в "include")), ресурс будет _загрязнен_, и его использование будет ограничено. Если атрибут отсутствует, ресурс загружается без запроса [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS: CORS (Cross-Origin Resource Sharing) is a system, consisting of transmitting HTTP headers, that determines whether browsers block frontend JavaScript code from accessing responses for cross-origin requests.") (то есть без отправки HTTP-заголовка `Origin`), что исключает его использование без статуса испорченного. Если значение недопустимо, оно обрабатывается так, как если бы было использовано перечисляемое ключевое слово **anonymous**. Дополнительные сведения см. в [атрибутах параметров CORS](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes). @@ -283,11 +283,11 @@ Default mode, which indicates no preference for the decoding mode. The browser d Предоставляет браузеру подсказку по декодированию изображения. Допустимые значения: `sync` -Декодируйте изображение синхронно для атомарного представления с другим содержимым. +Декодируйте изображение синхронно для атомарного представления с другим содержимым. `async` -Декодируйте изображение асинхронно, чтобы уменьшить задержку при отображении другого контента. +Декодируйте изображение асинхронно, чтобы уменьшить задержку при отображении другого контента. `auto` @@ -307,11 +307,11 @@ Scripts with the `defer` attribute will prevent the `DOMContentLoaded` event fro This attribute must not be used if the `src` attribute is absent (i.e. for inline scripts), in this case it would have no effect. To achieve a similar effect for dynamically inserted scripts use `async="false"` instead. Scripts with the `defer` attribute will execute in the order in which they appear in the document. - Этот логический атрибут устанавливается для того, чтобы указать браузеру, что скрипт должен быть выполнен после того, как документ будет проанализирован, но до срабатывания [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded "/en-US/docs/Web/Events/DOMContentLoaded"). + Этот логический атрибут устанавливается для того, чтобы указать браузеру, что скрипт должен быть выполнен после того, как документ будет проанализирован, но до срабатывания [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded "/en-US/docs/Web/Events/DOMContentLoaded"). -Скрипты с атрибутом `defer` предотвратят срабатывание события `DOMContentLoaded` до тех пор, пока скрипт не загрузится и не завершит свою работу. +Скрипты с атрибутом `defer` предотвратят срабатывание события `DOMContentLoaded` до тех пор, пока скрипт не загрузится и не завершит свою работу. -Этот атрибут не следует использовать, если атрибут `src` отсутствует (например, для встроенных скриптов), в этом случае он не будет иметь никакого эффекта. +Этот атрибут не следует использовать, если атрибут `src` отсутствует (например, для встроенных скриптов), в этом случае он не будет иметь никакого эффекта. Для достижения аналогичного эффекта при динамической вставке скриптов используйте вместо этого `async="false"`. Сценарии с атрибутом `defer` будут выполняться в том порядке, в котором они встречаются в документе. @@ -343,13 +343,13 @@ To achieve a similar effect for dynamically inserted scripts use `async="false"` * Although HTTP(s) URLs need to be in the same-origin, [`blob:` URLs](https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL) and [`data:` URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) are allowed so that content generated by JavaScript, such as pictures created in an image-editor Web app, can be downloaded. * If the HTTP header [`Content-Disposition:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) gives a different filename than this attribute, the HTTP header takes priority over this attribute. * If `Content-Disposition:` is set to `inline`, Firefox prioritizes `Content-Disposition`, like the filename case, while Chrome prioritizes the `download` attribute. - Этот атрибут предписывает браузерам загружать ресурс по указанному URL вместо перехода к нему, благодаря чему пользователю будет предложено сохранить его как локальный файл. Если атрибут имеет значение, он используется в качестве предварительно заполненного имени файла в запросе на сохранение (пользователь по-прежнему может изменить имя файла при его значении). Ограничений на допустимые значения нет, однако символы `/` и `\` заменяются на символы подчеркивания. В большинстве файловых систем существуют ограничения на использование знаков препинания в именах файлов, и браузеры соответствующим образом корректируют предлагаемое имя. + Этот атрибут предписывает браузерам загружать ресурс по указанному URL вместо перехода к нему, благодаря чему пользователю будет предложено сохранить его как локальный файл. Если атрибут имеет значение, он используется в качестве предварительно заполненного имени файла в запросе на сохранение (пользователь по-прежнему может изменить имя файла при его значении). Ограничений на допустимые значения нет, однако символы `/` и `\` заменяются на символы подчеркивания. В большинстве файловых систем существуют ограничения на использование знаков препинания в именах файлов, и браузеры соответствующим образом корректируют предлагаемое имя. **Примечания:** -* Этот атрибут работает только для [URL-адресов одного источника](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy). -* Хотя URL-адреса HTTP(s) должны находиться в одном источнике, допускаются URL-адреса [`blob:`](https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL) и [`data:` URL-адреса](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs), чтобы можно было загружать контент, сгенерированный JavaScript, например, изображения, созданные в веб-приложении для редактирования изображений. -* Если заголовок HTTP [`Content-Disposition:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) содержит другое имя файла, отличное от этого атрибута, заголовок HTTP имеет приоритет над этим атрибутом. +* Этот атрибут работает только для [URL-адресов одного источника](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy). +* Хотя URL-адреса HTTP(s) должны находиться в одном источнике, допускаются URL-адреса [`blob:`](https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL) и [`data:` URL-адреса](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs), чтобы можно было загружать контент, сгенерированный JavaScript, например, изображения, созданные в веб-приложении для редактирования изображений. +* Если заголовок HTTP [`Content-Disposition:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) содержит другое имя файла, отличное от этого атрибута, заголовок HTTP имеет приоритет над этим атрибутом. * Если для параметра `Content-Disposition:` установлено значение `inline`, Firefox отдает приоритет `Content-Disposition`, как и регистру имени файла, в то время как Chrome отдает приоритет атрибуту `download`. @@ -373,7 +373,7 @@ To achieve a similar effect for dynamically inserted scripts use `async="false"` Перечисляемый атрибут, указывающий, какие типы контента можно перетащить на элемент с использованием [Drag and Drop API](https://developer.mozilla.org/docs/DragDrop/Drag_and_Drop). Оно может принимать следующие значения: * `copy`, что означает, что перетаскивание создаст копию перетаскиваемого элемента -* `move`, что означает, что перетащенный элемент будет перемещен в новое место. +* `move`, что означает, что перетащенный элемент будет перемещен в новое место. * `link` создаст ссылку на перетаскиваемые данные. @@ -387,8 +387,8 @@ To achieve a similar effect for dynamically inserted scripts use `async="false"` This value can be overridden by a [`formenctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formenctype) attribute on a [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "The HTML <button> element represents a clickable button, which can be used in forms or anywhere in a document that needs simple, standard button functionality.") or [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") element. Когда значение атрибута `method` равно `post`, "enctype" представляет собой [MIME-тип](https://en.wikipedia.org/wiki/Mime_type) содержимого, используемого для отправки формы на сервер. Возможные значения: -* `application/x-www-form-urlencoded`: значение по умолчанию, если атрибут не указан. -* `multipart/form-data`: Значение, используемое для элемента [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "Элемент HTML <input> используется для создания интерактивных элементов управления для веб-форм с целью приема данных от пользователя; в зависимости от устройства и пользовательского агента доступно множество типов входных данных и элементов управления") с атрибутом `type`, установленным на "file". +* `application/x-www-form-urlencoded`: значение по умолчанию, если атрибут не указан. +* `multipart/form-data`: Значение, используемое для элемента [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "Элемент HTML <input> используется для создания интерактивных элементов управления для веб-форм с целью приема данных от пользователя; в зависимости от устройства и пользовательского агента доступно множество типов входных данных и элементов управления") с атрибутом `type`, установленным на "file". * `text/plain`: (HTML5) Это значение может быть переопределено атрибутом [`formenctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formenctype) элемента [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "Элемент HTML <button> представляет собой кликабельную кнопку, которую можно использовать в формах или в любом месте документа, где требуется простая, стандартная функциональность кнопки.") или [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "Элемент HTML <input> используется для создания интерактивных элементов управления для веб-форм с целью приема данных от пользователя; в зависимости от устройства и пользовательского агента доступно множество типов входных данных и элементов управления."). @@ -413,7 +413,7 @@ This value can be overridden by a [`formenctype`](https://developer.mozilla.org/ The [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-id) of a [labelable](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form_labelable) form-related element in the same document as the `<label>` element. The first element in the document with an `id` matching the value of the `for` attribute is the _labeled control_ for this label element, if it is a labelable element. If it is not labelable then the `for` attribute has no effect. If there are other elements which also match the `id` value, later in the document, they are not considered. **Note**: A `<label>` element can have both a `for` attribute and a contained control element, as long as the `for` attribute points to the contained control element. - Значение атрибута [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-id) элемента формы, для которого можно создать метку [labelable](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form_labelable), находящегося в том же документе, что и элемент `<label>`. Первый элемент документа, у которого атрибут `id` совпадает со значением атрибута `for`, является *элементом управления, связанным с меткой* (labeled control) для данного элемента "label" — при условии, что этот элемент допускает связывание с меткой. Если элемент невозможно связать с меткой, то атрибут `for` не оказывает никакого действия. Если в дальнейшем в документе присутствуют другие элементы, которые также соответствуют значению `id`, они не учитываются. + Значение атрибута [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-id) элемента формы, для которого можно создать метку [labelable](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form_labelable), находящегося в том же документе, что и элемент `<label>`. Первый элемент документа, у которого атрибут `id` совпадает со значением атрибута `for`, является *элементом управления, связанным с меткой* (labeled control) для данного элемента "label" — при условии, что этот элемент допускает связывание с меткой. Если элемент невозможно связать с меткой, то атрибут `for` не оказывает никакого действия. Если в дальнейшем в документе присутствуют другие элементы, которые также соответствуют значению `id`, они не учитываются. **Примечание**: Элемент `<label>` может иметь как атрибут `for`, так и содержащийся в нем элемент управления, при условии, что атрибут `for` указывает на содержащийся в нем элемент управления. @@ -438,8 +438,8 @@ This value can be overridden by a [`formenctype`](https://developer.mozilla.org/ If this attribute is specified, it overrides the [`enctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-enctype) attribute of the button's form owner. Если кнопка является кнопкой отправки, этот атрибут указывает тип содержимого, используемого для отправки формы на сервер. Возможные значения: -* `application/x-www-form-urlencoded`: значение по умолчанию, если атрибут не указан. -* `multipart/form-data`: Используйте это значение, если вы используете элемент [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "Элемент HTML <input> используется для создания интерактивных элементов управления для веб-форм с целью приема данных от пользователя; в зависимости от устройства и пользовательского агента доступен широкий спектр типов входных данных и виджетов управления") с атрибутом [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-type), установленным в значение `file`. +* `application/x-www-form-urlencoded`: значение по умолчанию, если атрибут не указан. +* `multipart/form-data`: Используйте это значение, если вы используете элемент [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "Элемент HTML <input> используется для создания интерактивных элементов управления для веб-форм с целью приема данных от пользователя; в зависимости от устройства и пользовательского агента доступен широкий спектр типов входных данных и виджетов управления") с атрибутом [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-type), установленным в значение `file`. * `text/plain` Если этот атрибут указан, он переопределяет атрибут [`enctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-enctype) владельца формы кнопки. @@ -454,8 +454,8 @@ If this attribute is specified, it overrides the [`enctype`](https://developer.m If specified, this attribute overrides the [`method`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-method) attribute of the button's form owner. Если кнопка является кнопкой отправки, этот атрибут указывает метод HTTP, который браузер использует для отправки формы. Возможные значения: -* `post`: Данные из формы включаются в тело формы и отправляются на сервер. -* `get`: Данные из формы добавляются к URI атрибута **form** с разделителем '?', и полученный URI отправляется на сервер. Используйте этот метод, если форма не имеет побочных эффектов и содержит только символы ASCII. +* `post`: Данные из формы включаются в тело формы и отправляются на сервер. +* `get`: Данные из формы добавляются к URI атрибута **form** с разделителем '?', и полученный URI отправляется на сервер. Используйте этот метод, если форма не имеет побочных эффектов и содержит только символы ASCII. Если этот атрибут указан, он переопределяет атрибут [`method`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-method) формы, к которой относится кнопка. @@ -474,9 +474,9 @@ If specified, this attribute overrides the [`method`](https://developer.mozilla. * `_top`: Load the response into the top-level browsing context (that is, the browsing context that is an ancestor of the current one, and has no parent). If there is no parent, this option behaves the same way as `_self`. Если кнопка является кнопкой отправки, этот атрибут представляет собой имя или ключевое слово, указывающее, где следует отобразить ответ, полученный после отправки формы. Это имя или ключевое слово для *контекста просмотра* (например, вкладки, окна или встроенного фрейма). Если этот атрибут указан, он переопределяет атрибут [`target`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-target) формы, к которой относится кнопка. Следующие ключевые слова имеют особое значение: -* `_self`: Загрузить ответ в тот же контекст просмотра, что и текущий. Это значение является значением по умолчанию, если атрибут не указан. -* `_blank`: Загрузить ответ в новый безымянный контекст просмотра. -* `_parent`: Загрузить ответ в родительский контекст просмотра текущего контекста. Если родительский элемент отсутствует, этот параметр ведет себя так же, как `_self`. +* `_self`: Загрузить ответ в тот же контекст просмотра, что и текущий. Это значение является значением по умолчанию, если атрибут не указан. +* `_blank`: Загрузить ответ в новый безымянный контекст просмотра. +* `_parent`: Загрузить ответ в родительский контекст просмотра текущего контекста. Если родительский элемент отсутствует, этот параметр ведет себя так же, как `_self`. * `_top`: Загрузить ответ в контекст просмотра верхнего уровня (то есть в контекст просмотра, являющийся предком текущего и не имеющий родителя). Если родительского не существует, этот параметр работает так же, как `_self`. @@ -530,28 +530,28 @@ This instruction specifies: Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page. Its content must follow the syntax defined in the [IETF HTTP Cookie Specification](https://tools.ietf.org/html/draft-ietf-httpstate-cookie-14). **Warning:** Do not use this instruction, as it is obsolete. Use the HTTP header [`Set-Cookie`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) instead. - Определяет директиву pragma. Атрибут называется `**http-equiv**(alent)`, потому что все допустимые значения являются именами конкретных HTTP-заголовков: + Определяет директиву pragma. Атрибут называется `**http-equiv**(alent)`, потому что все допустимые значения являются именами конкретных HTTP-заголовков: -* `"content-language"` -Определяет язык страницы по умолчанию. Его можно переопределить атрибутом [lang](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) любого элемента. +* `"content-language"` +Определяет язык страницы по умолчанию. Его можно переопределить атрибутом [lang](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) любого элемента. -**Внимание:** Не используйте это значение, так как оно устарело. Предпочтительнее использовать атрибут `lang` элемента [`<html>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html "Элемент HTML "<html>" представляет корень (элемент верхнего уровня) HTML-документа, поэтому его также называют корневым элементом. Все остальные элементы должны быть потомками этого элемента." элемента. +**Внимание:** Не используйте это значение, так как оно устарело. Предпочтительнее использовать атрибут `lang` элемента [`<html>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html "Элемент HTML "<html>" представляет корень (элемент верхнего уровня) HTML-документа, поэтому его также называют корневым элементом. Все остальные элементы должны быть потомками этого элемента." элемента. * `"content-security-policy"` -Позволяет авторам страниц определять [политику контента](https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives) для текущей страницы. Политики контента в основном определяют разрешенные источники серверов и конечные точки скриптов, что помогает защититься от атак межсайтового скриптинга. +Позволяет авторам страниц определять [политику контента](https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives) для текущей страницы. Политики контента в основном определяют разрешенные источники серверов и конечные точки скриптов, что помогает защититься от атак межсайтового скриптинга. * `"content-type"` Определяет [MIME-тип](https://developer.mozilla.org/en-US/docs/Glossary/MIME_type) документа, за которым следует его кодировка символов. Он использует тот же синтаксис, что и поле заголовка сущности `content-type` в протоколе HTTP, однако, поскольку он находится внутри HTML-страницы, использование большинства значений, отличных от `text/html`, невозможно. Таким образом, допустимый синтаксис для его `content` — это строка '`text/html`', за которой следует набор символов со следующим синтаксисом: '`; charset=_IANAcharset_`', где `IANAcharset` — это _предпочтительное MIME-имя_ для набора символов, [определенного IANA.](https://www.iana.org/assignments/character-sets) -**Внимание:** Не используйте это значение, так как оно устарело. Используйте атрибут [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) для элемента [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "Элемент HTML <meta> представляет метаданные, которые не могут быть представлены другими элементами HTML, связанными с метаданными, такими как <base>, <link> , <script>, <style> или <title>."). +**Внимание:** Не используйте это значение, так как оно устарело. Используйте атрибут [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) для элемента [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "Элемент HTML <meta> представляет метаданные, которые не могут быть представлены другими элементами HTML, связанными с метаданными, такими как <base>, <link>, <script>, <style> или <title>."). -**Примечание:** Поскольку [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML-элемент <meta> представляет метаданные, которые не могут быть представлены другими связанными с HTML мета-элементами, такими как <base>, <link> , <script>, <style> или <title>.") не может изменять типы документов в XHTML или сериализации XHTML HTML5, никогда не устанавливайте MIME-тип на MIME-тип XHTML с помощью `<meta>`. +**Примечание:** Поскольку [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML-элемент <meta> представляет метаданные, которые не могут быть представлены другими связанными с HTML мета-элементами, такими как <base>, <link>, <script>, <style> или <title>.") не может изменять типы документов в XHTML или сериализации XHTML HTML5, никогда не устанавливайте MIME-тип на MIME-тип XHTML с помощью `<meta>`. * `"refresh"` Данная инструкция указывает: -* Количество секунд до перезагрузки страницы — только если атрибут [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) содержит положительное целое число. -* Количество секунд до перенаправления страницы на другую — только если атрибут [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) содержит положительное целое число, за которым следует строка '`;url=`' и действительный URL. +* Количество секунд до перезагрузки страницы — только если атрибут [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) содержит положительное целое число. +* Количество секунд до перенаправления страницы на другую — только если атрибут [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) содержит положительное целое число, за которым следует строка '`;url=`' и действительный URL. * `"set-cookie"` -Определяет [cookie](https://developer.mozilla.org/en-US/docs/cookie) для страницы. Его содержимое должно соответствовать синтаксису, определенному в [спецификации IETF HTTP Cookie](https://tools.ietf.org/html/draft-ietf-httpstate-cookie-14). +Определяет [cookie](https://developer.mozilla.org/en-US/docs/cookie) для страницы. Его содержимое должно соответствовать синтаксису, определенному в [спецификации IETF HTTP Cookie](https://tools.ietf.org/html/draft-ietf-httpstate-cookie-14). **Внимание:** Не используйте эту инструкцию, так как она устарела. Вместо этого используйте HTTP-заголовок [`Set-Cookie`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie). @@ -585,7 +585,7 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page This Boolean attribute indicates that the image is part of a server-side map. If so, the precise coordinates of a click are sent to the server. **Note:** This attribute is allowed only if the `<img>` element is a descendant of an [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "The HTML <a> element (or anchor element) creates a hyperlink to other web pages, files, locations within the same page, email addresses, or any other URL.") element with a valid [`href`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-href) attribute. - Этот логический атрибут указывает, что изображение является частью карты-изображения на стороне сервера. В этом случае точные координаты клика отправляются на сервер. + Этот логический атрибут указывает, что изображение является частью карты-изображения на стороне сервера. В этом случае точные координаты клика отправляются на сервер. **Примечание:** Этот атрибут разрешен только в том случае, если элемент `<img>` является потомком элемента [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "Элемент HTML <a> (или элемент привязки) создает гиперссылку на другие веб-страницы, файлы, места на той же странице, адреса электронной почты или любой другой URL.") с допустимым атрибутом [`href`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-href). @@ -635,19 +635,19 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page Как предполагается использовать текстовую дорожку. Если опущено, по умолчанию используется тип `subtitles`. Если атрибут отсутствует, будет использоваться `subtitles`. Если атрибут содержит недопустимое значение, будет использовано `metadata`. (В версиях Chrome до 52 недопустимое значение рассматривалось как `subtitles`.) Разрешены следующие ключевые слова: * `subtitles` -* Субтитры обеспечивают перевод контента, который не понятен зрителю. Например, диалог или текст, не на английском языке, в англоязычном фильме. -* Субтитры могут содержать дополнительный контент, обычно это дополнительная справочная информация. Например, текст в начале фильмов "Звёздные войны" или дата, время и место действия сцены. +* Субтитры обеспечивают перевод контента, который не понятен зрителю. Например, диалог или текст, не на английском языке, в англоязычном фильме. +* Субтитры могут содержать дополнительный контент, обычно это дополнительная справочная информация. Например, текст в начале фильмов "Звёздные войны" или дата, время и место действия сцены. * `captions` -* Субтитры содержат транскрипцию и, возможно, перевод аудиозаписи. -* Она может включать важную невербальную информацию, такую как музыкальные сигналы или звуковые эффекты. Это может указывать на источник сигнала (например, музыка, текст, персонаж). -* Подходит для пользователей с нарушениями слуха или при отключенном звуке. +* Субтитры содержат транскрипцию и, возможно, перевод аудиозаписи. +* Она может включать важную невербальную информацию, такую как музыкальные сигналы или звуковые эффекты. Это может указывать на источник сигнала (например, музыка, текст, персонаж). +* Подходит для пользователей с нарушениями слуха или при отключенном звуке. * `descriptions` -* Текстовое описание содержания видео. -* Подходит для пользователей с нарушениями зрения или в случаях, когда видео не видно. +* Текстовое описание содержания видео. +* Подходит для пользователей с нарушениями зрения или в случаях, когда видео не видно. * `chapters` -* Заголовки глав предназначены для использования пользователем при навигации по медиаресурсу. +* Заголовки глав предназначены для использования пользователем при навигации по медиаресурсу. * `metadata` -* Дорожки, используемые скриптами. Невидимо для пользователя. +* Дорожки, используемые скриптами. Не отображается пользователю. @@ -692,11 +692,11 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page * In HTML 4, this can only be a simple white-space-separated list of media description literals, i.e., [media types and groups](https://developer.mozilla.org/en-US/docs/Web/CSS/@media), where defined and allowed as values for this attribute, such as `print`, `screen`, `aural`, `braille`. HTML5 extended this to any kind of [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries), which are a superset of the allowed values of HTML 4. * Browsers not supporting [CSS3 Media Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries) won't necessarily recognize the adequate link; do not forget to set fallback links, the restricted set of media queries defined in HTML 4. - Этот атрибут определяет тип носителя, к которому относится связанный ресурс. Его значением должен быть тип мультимедиа или [запрос мультимедиа](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries). Этот атрибут в основном полезен при подключении к внешним таблицам стилей — он позволяет пользовательскому агенту выбрать наиболее подходящую для устройства, на котором он работает. + Этот атрибут определяет тип носителя, к которому относится связанный ресурс. Его значением должен быть тип мультимедиа или [запрос мультимедиа](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries). Этот атрибут в основном полезен при подключении к внешним таблицам стилей — он позволяет пользовательскому агенту выбрать наиболее подходящую для устройства, на котором он работает. **Примечания:** -* В HTML 4 это может быть только простой список литералов описания медиафайлов, разделенных пробелами, т.е. [типы и группы медиафайлов](https://developer.mozilla.org/en-US/docs/Web/CSS/@media), где определены и разрешены значения для этого атрибута, такие как `print`, `screen`, `aural`, `braille`. HTML5 расширил это до любых типов [медиа-запросов](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries), которые являются надмножеством допустимых значений HTML 4. +* В HTML 4 это может быть только простой список литералов описания медиафайлов, разделенных пробелами, т.е. [типы и группы медиафайлов](https://developer.mozilla.org/en-US/docs/Web/CSS/@media), где определены и разрешены значения для этого атрибута, такие как `print`, `screen`, `aural`, `braille`. HTML5 расширил это до любых типов [медиа-запросов](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries), которые являются надмножеством допустимых значений HTML 4. * Браузеры, не поддерживающие [медиазапросы CSS3](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries), не обязательно распознают соответствующую ссылку; не забудьте установить резервные ссылки, используя ограниченный набор медиазапросов, определенных в HTML 4. @@ -710,9 +710,9 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page This value can be overridden by a [`formmethod`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formmethod) attribute on a [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "The HTML <button> element represents a clickable button, which can be used in forms or anywhere in a document that needs simple, standard button functionality.") or [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") element. Метод [HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP), который браузер использует для отправки формы. Возможные значения: -* `post`: Соответствует методу HTTP [POST](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5); данные формы включаются в тело формы и отправляются на сервер. -* `get`: Соответствует методу HTTP [GET](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3); данные формы добавляются к URI атрибута `action` с разделителем '?', и полученный URI отправляется на сервер. Используйте этот метод, если форма не имеет побочных эффектов и содержит только символы ASCII. -* `dialog`: Используйте, когда форма находится внутри элемента [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog "Элемент HTML <dialog> представляет собой диалоговое окно или другой интерактивный компонент, такой как инспектор или окно."), чтобы закрыть диалоговое окно после отправки. +* `post`: Соответствует методу HTTP [POST](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5); данные формы включаются в тело формы и отправляются на сервер. +* `get`: Соответствует методу HTTP [GET](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3); данные формы добавляются к URI атрибута `action` с разделителем '?', и полученный URI отправляется на сервер. Используйте этот метод, если форма не имеет побочных эффектов и содержит только символы ASCII. +* `dialog`: Используйте, когда форма находится внутри элемента [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog "Элемент HTML <dialog> представляет собой диалоговое окно или другой интерактивный компонент, такой как инспектор или окно."), чтобы закрыть диалоговое окно после отправки. Это значение может быть переопределено атрибутом [`formmethod`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formmethod) элемента [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "HTML-элемент <button> представляет собой кликабельную кнопку, которую можно использовать в формах или в любом другом месте документа, где требуется простая стандартная функциональность кнопки.") или [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML-элемент <input> используется для создания интерактивных элементов управления веб-форм для получения данных от пользователя; доступно множество типов входных данных и элементов управления в зависимости от устройства и пользовательского агента."). @@ -941,69 +941,69 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo * Though unstandardized, this declaration is respected by most mobile browsers due to de-facto dominance. * The default values may vary between devices and browsers. * To learn about this declaration in Firefox for Mobile, see [this article](https://developer.mozilla.org/en-US/docs/Mobile/Viewport_meta_tag "Mobile/Viewport meta tag"). - Этот атрибут определяет имя фрагмента метаданных на уровне документа. Его не следует устанавливать, если также установлен один из атрибутов [`itemprop`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-itemprop), [`http-equiv`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-http-equiv) или [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset). + Этот атрибут определяет имя фрагмента метаданных на уровне документа. Его не следует устанавливать, если также установлен один из атрибутов [`itemprop`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-itemprop), [`http-equiv`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-http-equiv) или [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset). Это имя метаданных связано со значением, содержащимся в атрибуте [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content). Возможные значения для атрибута name: -* `application-name`, определяющий имя приложения, работающего на веб-странице. +* `application-name`, определяющий имя приложения, работающего на веб-странице. **Примечание:** -* Браузеры могут использовать это для идентификации приложения. Он отличается от элемента [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title "Элемент HTML Title (<title>) определяет заголовок документа, который отображается в строке заголовка браузера или на вкладке страницы."), который обычно содержит название приложения, но может также содержать информацию, такую как название документа или статус. -* Простые веб-страницы не должны определять имя приложения. +* Браузеры могут использовать это для идентификации приложения. Он отличается от элемента [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title "Элемент HTML Title (<title>) определяет заголовок документа, который отображается в строке заголовка браузера или на вкладке страницы."), который обычно содержит название приложения, но может также содержать информацию, такую как название документа или статус. +* Простые веб-страницы не должны определять имя приложения. -* `author`, который определяет имя автора документа. -* `description`, содержащий краткое и точное описание содержимого страницы. Некоторые браузеры, такие как Firefox и Opera, используют это в качестве описания страниц, добавленных в закладки по умолчанию. -* `generator`, содержащий идентификатор программного обеспечения, сгенерировавшего страницу. -* `keywords`, содержащие слова, относящиеся к содержимому страницы, разделенные запятыми. +* `author`, который определяет имя автора документа. +* `description`, содержащий краткое и точное описание содержимого страницы. Некоторые браузеры, такие как Firefox и Opera, используют это в качестве описания страниц, добавленных в закладки по умолчанию. +* `generator`, содержащий идентификатор программного обеспечения, сгенерировавшего страницу. +* `keywords`, содержащие слова, относящиеся к содержимому страницы, разделенные запятыми. * `referrer`, который управляет [заголовком HTTP `Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer), прикрепленным к запросам, отправляемым из документа: Значения атрибута `content` объекта `<meta name="referrer">` `no-referrer` -Не отправляйте HTTP-заголовок `Referrer`. +Не отправляйте HTTP-заголовок `Referrer`. `origin` -Отправьте [источник](https://developer.mozilla.org/en-US/docs/Glossary/Origin) документа. +Отправьте [источник](https://developer.mozilla.org/en-US/docs/Glossary/Origin) документа. `no-referrer-when-downgrade` -Отправлять [источник](https://developer.mozilla.org/en-US/docs/Glossary/Origin) в качестве реферера для URL-адресов, столь же безопасных, как и текущая страница (https→https), но не отправлять реферер для менее безопасных URL-адресов (https→http). Это поведение по умолчанию. +Отправлять [источник](https://developer.mozilla.org/en-US/docs/Glossary/Origin) в качестве реферера для URL-адресов, столь же безопасных, как и текущая страница (https→https), но не отправлять реферер для менее безопасных URL-адресов (https→http). Это поведение по умолчанию. `origin-when-cross-origin` -Для запросов из одного источника отправляйте полный URL (без параметров), а для остальных случаев — только [источник](https://developer.mozilla.org/en-US/docs/Glossary/Origin). +Для запросов из одного источника отправляйте полный URL (без параметров), а для остальных случаев — только [источник](https://developer.mozilla.org/en-US/docs/Glossary/Origin). `same-origin` -Для запросов с одним и тем [же источником](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) будет отправлен реферер, но запросы с другими источниками не будут содержать никакой информации о реферере. +Для запросов с одним и тем [же источником](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) будет отправлен реферер, но запросы с другими источниками не будут содержать никакой информации о реферере. `strict-origin` -Отправляйте исходный документ только в качестве реферера в заранее максимально безопасное место назначения (HTTPS->HTTPS), но не отправляйте его в менее безопасное место назначения (HTTPS->HTTP). +Отправляйте исходный документ только в качестве реферера в заранее максимально безопасное место назначения (HTTPS->HTTPS), но не отправляйте его в менее безопасное место назначения (HTTPS->HTTP). `strict-origin-when-cross-origin` -При выполнении запроса из одного источника необходимо отправить полный URL-адрес, при отправке источника документа — только в заранее максимально защищенное место назначения (HTTPS->HTTPS), а при отправке менее защищенного места назначения — заголовок не отправлять. +При выполнении запроса из одного источника необходимо отправить полный URL-адрес, при отправке источника документа — только в заранее максимально защищенное место назначения (HTTPS->HTTPS), а при отправке менее защищенного места назначения — заголовок не отправлять. -`unsafe-URL` +`unsafe-URL` -Для запросов из одного или нескольких источников необходимо отправить полный URL-адрес (без параметров). +Для запросов из одного или нескольких источников необходимо отправить полный URL-адрес (без параметров). -**Примечания:** +**Примечания:** -* Некоторые браузеры поддерживают устаревшие значения `always`, `default` и `never` для параметра referrer. -* Динамическая вставка `<meta name="referrer">` (с помощью [`document.write`](https://developer.mozilla.org/en-US/docs/Web/API/Document/write) или [`appendChild`](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild)) делает поведение реферера непредсказуемым. -* При определении нескольких противоречащих друг другу политик применяется политика отсутствия рефереров. +* Некоторые браузеры поддерживают устаревшие значения `always`, `default` и `never` для параметра referrer. +* Динамическая вставка `<meta name="referrer">` (с помощью [`document.write`](https://developer.mozilla.org/en-US/docs/Web/API/Document/write) или [`appendChild`](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild)) делает поведение реферера непредсказуемым. +* При определении нескольких противоречащих друг другу политик применяется политика отсутствия рефереров. Этот атрибут также может иметь значение из расширенного списка, определенного на [странице whatWG Wiki MetaExtensions](https://wiki.whatwg.org/wiki/MetaExtensions). Хотя ни одно из них еще официально не утверждено, вот несколько часто используемых названий: -* `creator` — это параметр, определяющий имя создателя документа, например, организации или учреждения. Если их больше одного, следует использовать несколько элементов [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "Элемент HTML <meta> представляет метаданные, которые не могут быть представлены другими связанными с HTML мета-элементами, такими как <base>, <link> , <script>, <style> или <title>."). -* `googlebot`, синоним `robots`, сопровождается только Googlebot (индексирующий веб-краулер Google). -* `publisher`, который определяет имя издателя документа. +* `creator` — это параметр, определяющий имя создателя документа, например, организации или учреждения. Если их больше одного, следует использовать несколько элементов [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "Элемент HTML <meta> представляет метаданные, которые не могут быть представлены другими связанными с HTML мета-элементами, такими как <base>, <link>, <script>, <style> или <title>."). +* `googlebot`, синоним `robots`, сопровождается только Googlebot (индексирующий веб-краулер Google). +* `publisher`, который определяет имя издателя документа. * `robots` — параметр, определяющий поведение, которое должны использовать кооперативные веб-краулеры, или "роботы", при работе со страницей. Ниже приведён список значений, разделённых запятыми: Значения для содержимого `<meta name="robots">` @@ -1016,25 +1016,25 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo `index` -Позволяет роботу индексировать страницу (по умолчанию). +Позволяет роботу индексировать страницу (по умолчанию). Все `noindex` -Запрашивает у робота не индексировать страницу. +Запрашивает у робота не индексировать страницу. Все `follow` -Позволяет роботу переходить по ссылкам на странице (по умолчанию). +Позволяет роботу переходить по ссылкам на странице (по умолчанию). Все `nofollow` -Запрашивает у робота не переходить по ссылкам на странице. +Запрашивает у робота не переходить по ссылкам на странице. Все @@ -1044,110 +1044,110 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo [Google](https://support.google.com/webmasters/answer/79812) -`noodp` +`noodp` -Предотвращает использование описания [Open Directory Project](https://www.dmoz.org/), если таковое имеется, в качестве описания страницы в результатах поиска. +Предотвращает использование описания [Open Directory Project](https://www.dmoz.org/), если таковое имеется, в качестве описания страницы в результатах поиска. [Google](https://support.google.com/webmasters/answer/35624#nodmoz), [Yahoo](https://help.yahoo.com/kb/search-for-desktop/meta-tags-robotstxt-yahoo-search-sln2213.html#cont5), [Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) -`noarchive` +`noarchive` -Запрашивает у поисковой системы не кэшировать содержимое страницы. +Запрашивает у поисковой системы не кэшировать содержимое страницы. -[Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives), [Yahoo](https://help.yahoo.com/kb/search-for-desktop/SLN2213.html), [Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) +[Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives), [Yahoo](https://help.yahoo.com/kb/search-for-desktop/SLN2213.html), [Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) -`nosnippet` +`nosnippet` -Предотвращает отображение любого описания страницы в результатах поиска. +Предотвращает отображение любого описания страницы в результатах поиска. -[Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives), [Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) +[Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives), [Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) -`noimageindex` +`noimageindex` -Запрашивает, чтобы эта страница не отображалась как страница-источник индексированного изображения. +Запрашивает, чтобы эта страница не отображалась как страница-источник индексированного изображения. -[Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives) +[Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives) -`nocache` +`nocache` Синоним слова `noarchive`. -[Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) +[Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) -**Примечания:** +**Примечания:** -* Только роботы, работающие в режиме сотрудничества, следуют этим правилам. Не рассчитывайте предотвратить сбор адресов электронной почты с их помощью. -* Роботу по-прежнему необходимо получить доступ к странице, чтобы прочитать эти правила. Чтобы предотвратить избыточное потребление пропускной способности, используйте _[robots.txt](https://developer.mozilla.org/en-US/docs/Glossary/robots.txt "robots.txt: Robots.txt — это файл, который обычно размещается в корневой директории веб-сайта. Он определяет, разрешен или запрещен доступ поисковых роботов к веб-сайту."_ файл. -* Если вы хотите удалить страницу, сработает `noindex`, но только после того, как робот снова посетит эту страницу. Убедитесь, что файл `robots.txt` не препятствует повторным посещениям. -* Некоторые значения являются взаимоисключающими, например, `index` и `noindex`, или `follow` и `nofollow`. В этих случаях поведение робота не определено и может различаться между ними. -* Некоторые поисковые роботы, такие как Google, Yahoo и Bing, поддерживают одинаковые значения для HTTP-заголовка `X-Robots-Tag`; это позволяет использовать эти правила для не-HTML документов, таких как изображения. +* Только роботы, работающие в режиме сотрудничества, следуют этим правилам. Не рассчитывайте предотвратить сбор адресов электронной почты с их помощью. +* Роботу по-прежнему необходимо получить доступ к странице, чтобы прочитать эти правила. Чтобы предотвратить избыточное потребление пропускной способности, используйте _[robots.txt](https://developer.mozilla.org/en-US/docs/Glossary/robots.txt "robots.txt: Robots.txt — это файл, который обычно размещается в корневой директории веб-сайта. Он определяет, разрешен или запрещен доступ поисковых роботов к веб-сайту."_ файл. +* Если вы хотите удалить страницу, сработает `noindex`, но только после того, как робот снова посетит эту страницу. Убедитесь, что файл `robots.txt` не препятствует повторным посещениям. +* Некоторые значения являются взаимоисключающими, например, `index` и `noindex`, или `follow` и `nofollow`. В этих случаях поведение робота не определено и может различаться между ними. +* Некоторые поисковые роботы, такие как Google, Yahoo и Bing, поддерживают одинаковые значения для HTTP-заголовка `X-Robots-Tag`; это позволяет использовать эти правила для не-HTML документов, таких как изображения. -* `slurp` — синоним слова `robots`, но только для Slurp — поискового робота Yahoo. -* `viewport`, который дает подсказки о размере начального размера [области просмотра](https://developer.mozilla.org/en-US/docs/Glossary/viewport "Область просмотра: Область просмотра представляет собой многоугольную (обычно прямоугольную) область в компьютерной графике, которая в данный момент просматривается. В контексте веб-браузеров этот термин обозначает ту часть просматриваемого документа, которая в данный момент видна в окне (или на экране, если документ открыт в полноэкранном режиме). Содержимое, находящееся за пределами видимой области экрана, не отображается на экране до тех пор, пока не будет прокручено и не появится в поле зрения. Используется только на мобильных устройствах. +* `slurp` — синоним слова `robots`, но только для Slurp — поискового робота Yahoo. +* `viewport`, который дает подсказки о размере начального размера [области просмотра](https://developer.mozilla.org/en-US/docs/Glossary/viewport "Область просмотра: Область просмотра представляет собой многоугольную (обычно прямоугольную) область в компьютерной графике, которая в данный момент просматривается. В контексте веб-браузеров этот термин обозначает ту часть просматриваемого документа, которая в данный момент видна в окне (или на экране, если документ открыт в полноэкранном режиме). Содержимое, находящееся за пределами видимой области экрана, не отображается на экране до тех пор, пока не будет прокручено и не появится в поле зрения. Используется только на мобильных устройствах. -Значения для содержимого `<meta name="viewport">` +Значения для содержимого `<meta name="viewport">` Значение -Возможные подзначения +Возможные подзначения Описание `width` -Положительное целое число или текст `device-width` +Положительное целое число или текст `device-width` -Определяет ширину в пикселях области просмотра, в которой вы хотите отобразить веб-сайт. +Определяет ширину в пикселях области просмотра, в которой вы хотите отобразить веб-сайт. `height` -Положительное целое число или текст `device-height` +Положительное целое число или текст `device-height` -Определяет высоту области просмотра. Не используется ни одним браузером. +Определяет высоту области просмотра. Не используется ни одним браузером. `initial-scale` -Положительное число от `0.0` до `10.0` +Положительное число от `0.0` до `10.0` -Определяет соотношение между шириной устройства (`device-width` в портретном режиме или `device-height` в альбомном режиме) и размером области просмотра. +Определяет соотношение между шириной устройства (`device-width` в портретном режиме или `device-height` в альбомном режиме) и размером области просмотра. `maximum-scale` -Положительное число от `0.0` до `10.0` +Положительное число от `0.0` до `10.0` -Определяет максимальное значение масштабирования. Это значение должно быть больше или равно `minimum-scale`, иначе поведение будет неопределенным. В настройках браузера это правило можно игнорировать, а в iOS 10 и более поздних версиях оно игнорируется по умолчанию. +Определяет максимальное значение масштабирования. Это значение должно быть больше или равно `minimum-scale`, иначе поведение будет неопределенным. В настройках браузера это правило можно игнорировать, а в iOS 10 и более поздних версиях оно игнорируется по умолчанию. `minimum-scale` -Положительное число от `0.0` до `10.0` +Положительное число от `0.0` до `10.0` -Определяет минимальный уровень масштабирования. Это значение должно быть меньше или равно `maximum-scale`, иначе поведение будет неопределенным. В настройках браузера это правило можно игнорировать, а в iOS 10 и более поздних версиях оно игнорируется по умолчанию. +Определяет минимальный уровень масштабирования. Это значение должно быть меньше или равно `maximum-scale`, иначе поведение будет неопределенным. В настройках браузера это правило можно игнорировать, а в iOS 10 и более поздних версиях оно игнорируется по умолчанию. `user-scalable` `yes` или `no` -Если установить значение `no`, пользователь не сможет увеличивать масштаб веб-страницы. Значение по умолчанию — `yes`. В настройках браузера это правило можно игнорировать, а в iOS 10 и более поздних версиях оно игнорируется по умолчанию. +Если установить значение `no`, пользователь не сможет увеличивать масштаб веб-страницы. Значение по умолчанию — `yes`. В настройках браузера это правило можно игнорировать, а в iOS 10 и более поздних версиях оно игнорируется по умолчанию. -Спецификация +Спецификация Состояние -Комментарий +Комментарий -[Адаптация CSS-устройств -Определение '<meta name="viewport">' в этой спецификации.](https://drafts.csswg.org/css-device-adapt/#viewport-meta) +[Адаптация CSS-устройств +Определение '<meta name="viewport">' в этой спецификации.](https://drafts.csswg.org/css-device-adapt/#viewport-meta) -Рабочий проект +Рабочий проект Нестандартно описывает элемент VIEWPORT META -См. также: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport "Правило CSS @viewport позволяет настроить область просмотра, через которую отображается документ." Он в основном используется на мобильных устройствах, но также используется настольными браузерами, поддерживающими такие функции, как "привязка к краю" (например, Microsoft Edge).) +См. также: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport "Правило CSS @viewport позволяет настроить область просмотра, через которую отображается документ". Он в основном используется на мобильных устройствах, но также используется настольными браузерами, поддерживающими такие функции, как "привязка к краю" (например, Microsoft Edge).) -**Примечания:** +**Примечания:** -* Хотя это утверждение не стандартизировано, большинство мобильных браузеров его соблюдают из-за фактического доминирования. -* Значения по умолчанию могут различаться в зависимости от устройства и браузера. +* Хотя это утверждение не стандартизировано, большинство мобильных браузеров его соблюдают из-за фактического доминирования. +* Значения по умолчанию могут различаться в зависимости от устройства и браузера. * Чтобы узнать об этом объявлении в Firefox для мобильных устройств, см. [эту статью](https://developer.mozilla.org/en-US/docs/Mobile/Viewport_meta_tag "Метатег Mobile/Viewport"). @@ -1515,7 +1515,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo A hint to the user of what can be entered in the control. Carriage returns or line-feeds within the placeholder text must be treated as line breaks when rendering the hint. **Note:** Placeholders should only be used to show an example of the type of data that should be entered into a form; they are _not_ a substitute for a proper [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label "The HTML <label> element represents a caption for an item in a user interface.") element tied to the input. See [Labels and placeholders](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Labels_and_placeholders "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") in [<input>: The Input (Form Input) element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") for a full explanation. - Подсказка для пользователя о том, что можно ввести в элемент управления. Переводы строк или символы возврата каретки внутри текста-заполнителя должны рассматриваться как переносы строк при отображении подсказки. + Подсказка для пользователя о том, что можно ввести в элемент управления. Переводы строк или символы возврата каретки внутри текста-заполнителя должны рассматриваться как переносы строк при отображении подсказки. **Примечание:** Заполнители следует использовать только для демонстрации примера типа данных, которые должны быть введены в форму; они _не_ заменяют собой соответствующий элемент [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label "HTML-элемент <label> представляет собой подпись к элементу в пользовательском интерфейсе."), связанный с полем ввода. См. [Labels and placeholders](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Labels_and_placeholders"HtmL <input> используется для создания интерактивных элементов управления для веб-форм, чтобы принимать данные от пользователя; доступны различные типы входных данных и мини-приложения элементов управления в зависимости от устройства и агента пользователя.) в [<input>: элемент Input (Form Input)](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "Элемент HTML <input> используется для создания интерактивных элементов управления для веб-форм, чтобы принимать данные от пользователя; доступны различные типы входных данных и мини-приложения элементов управления в зависимости от устройства и агента пользователя.) для полного объяснения. @@ -1543,12 +1543,12 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo * `origin` means that the referrer will be the origin of the page, which is roughly the scheme, the host, and the port. * `origin-when-cross-origin` means that navigating to other origins will be limited to the scheme, the host, and the port, while navigating on the same origin will include the referrer's path. * `unsafe-url` means that the referrer will include the origin and the path (but not the fragment, password, or username). This case is unsafe because it can leak origins and paths from TLS-protected resources to insecure origins. - Строка, указывающая, какой реферер использовать при получении ресурса: + Строка, указывающая, какой реферер использовать при получении ресурса: -* `no-referrer` означает, что [`Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer "Заголовок запроса Referer содержит адрес предыдущей веб-страницы, с которой была совершена ссылка на текущую запрошенную страницу. Заголовок Referer позволяет серверам определять, откуда пользователи их посещают, и может использовать эти данные для аналитики, ведения журналов или оптимизированного кэширования, например. Заголовок Referer не будет отправлен. -* `no-referrer-when-downgrade` означает, что нет [`Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer "Заголовок запроса Referer содержит адрес предыдущей веб-страницы, с которой была совершена ссылка на текущую запрашиваемую страницу. Заголовок Referer позволяет серверам определять, откуда пользователи их посещают, и может использовать эти данные, например, для аналитики, ведения журналов или оптимизированного кэширования. Этот заголовок будет отправлен при переходе на сайт без TLS (HTTPS). Это поведение пользовательского агента по умолчанию, если не указана иная политика. -* `origin` означает, что Referrer будет источником страницы, который примерно равен схеме, хосту и порту. -* `origin-when-cross-origin` означает, что переход к другим источникам будет ограничен схемой, хостом и портом, в то время как переход к тому же источнику будет включать путь реферера. +* `no-referrer` означает, что [`Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer "Заголовок запроса Referer содержит адрес предыдущей веб-страницы, с которой была совершена ссылка на текущую запрошенную страницу. Заголовок Referer позволяет серверам определять, откуда пользователи их посещают, и может использовать эти данные для аналитики, ведения журналов или оптимизированного кэширования, например. Заголовок Referer не будет отправлен. +* `no-referrer-when-downgrade` означает, что нет [`Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer "Заголовок запроса Referer содержит адрес предыдущей веб-страницы, с которой была совершена ссылка на текущую запрашиваемую страницу. Заголовок Referer позволяет серверам определять, откуда пользователи их посещают, и может использовать эти данные, например, для аналитики, ведения журналов или оптимизированного кэширования. Этот заголовок будет отправлен при переходе на сайт без TLS (HTTPS). Это поведение пользовательского агента по умолчанию, если не указана иная политика. +* `origin` означает, что Referrer будет источником страницы, который примерно равен схеме, хосту и порту. +* `origin-when-cross-origin` означает, что переход к другим источникам будет ограничен схемой, хостом и портом, в то время как переход к тому же источнику будет включать путь реферера. * `unsafe-url` означает, что в поле Referrer будут указаны источник и путь (но не фрагмент URL, пароль или имя пользователя). Это задание небезопасно, так как может привести к утечке источников и путей от ресурсов, защищенных TLS, к небезопасным источникам. @@ -1593,25 +1593,25 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo * When the embedded document has the same origin as the embedding page, it is **strongly discouraged** to use both `allow-scripts` and `allow-same-origin`, as that lets the embedded document remove the `sandbox` attribute — making it no more secure than not using the `sandbox` attribute at all. * Sandboxing is useless if the attacker can display content outside a sandboxed `iframe` — such as if the viewer opens the frame in a new tab. Such content should be also served from a _separate origin_ to limit potential damage. * The `sandbox` attribute is unsupported in Internet Explorer 9 and earlier. - Применяет дополнительные ограничения к содержимому фрейма. Значение атрибута может быть либо пустым, чтобы применить все ограничения, либо состоять из разделенных пробелами токенов, чтобы снять определенные ограничения: - -* `allow-forms`: Разрешает ресурсу отправлять формы. Если это ключевое слово не используется, отправка формы блокируется. -* `allow-modals`: Разрешает ресурсу [открывать модальные окна](https://html.spec.whatwg.org/multipage/origin.html#sandboxed-modals-flag). -* `allow-orientation-lock`: Позволяет ресурсу [блокировать ориентацию экрана](https://developer.mozilla.org/en-US/docs/Web/API/Screen/lockOrientation). -* `allow-pointer-lock`: Позволяет ресурсу использовать [API блокировки указателя](https://developer.mozilla.org/en-US/docs/WebAPI/Pointer_Lock). -* `allow-popups`: Разрешает всплывающие окна (например, `window.open()`, `target="_blank"` или `showModalDialog()`). Если это ключевое слово не используется, всплывающее окно молча не откроется. -* `allow-popups-to-escape-sandbox`: Позволяет изолированному документу открывать новые окна без того, чтобы эти окна наследовали режим песочницы. Например, это позволяет безопасно изолировать рекламу, не накладывая тех же ограничений на страницу, на которую ведет реклама. -* `allow-presentation`: Позволяет ресурсу начать [сессию презентации](https://developer.mozilla.org/en-US/docs/Web/API/PresentationRequest). -* `allow-same-origin`: Если этот токен не используется, ресурс рассматривается как находящийся в особом месте, которое всегда не соответствует [политике одного источника](https://developer.mozilla.org/en-US/docs/Glossary/same-origin_policy "политика одного источника: политика одного источника — это критически важный механизм безопасности, который ограничивает взаимодействие документа или скрипта, загруженного из одного источника, с ресурсом из другого источника."). -* `allow-scripts`: Позволяет ресурсу запускать скрипты (но не создавать всплывающие окна). -* `allow-storage-access-by-user-activation`: Позволяет ресурсу запрашивать доступ к возможностям хранения родительского ресурса с помощью [API доступа к хранилищу](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API). -* `allow-top-navigation`: Позволяет ресурсу перемещаться по контексту просмотра верхнего уровня (тому, который называется `_top`). -* `allow-top-navigation-by-user-activation`: Позволяет ресурсу перемещаться по контексту просмотра верхнего уровня, но только если это инициируется жестом пользователя. - -**Примечания о песочнице:** - -* Когда встроенный документ имеет тот же источник, что и страница встраивания, **настоятельно не рекомендуется** использовать одновременно `allow-scripts` и `allow-same-origin`, поскольку это позволяет встроенному документу удалить атрибут `sandbox`, что делает его не более безопасным, чем полное отсутствие атрибута `sandbox`. -* Использование песочницы бесполезно, если злоумышленник может отображать контент за пределами изолированного `iframe` — например, если пользователь открывает фрейм в новой вкладке. Такой контент также следует предоставлять из _отдельного источника_, чтобы ограничить потенциальный ущерб. + Применяет дополнительные ограничения к содержимому фрейма. Значение атрибута может быть либо пустым, чтобы применить все ограничения, либо состоять из разделенных пробелами токенов, чтобы снять определенные ограничения: + +* `allow-forms`: Разрешает ресурсу отправлять формы. Если это ключевое слово не используется, отправка формы блокируется. +* `allow-modals`: Разрешает ресурсу [открывать модальные окна](https://html.spec.whatwg.org/multipage/origin.html#sandboxed-modals-flag). +* `allow-orientation-lock`: Позволяет ресурсу [блокировать ориентацию экрана](https://developer.mozilla.org/en-US/docs/Web/API/Screen/lockOrientation). +* `allow-pointer-lock`: Позволяет ресурсу использовать [API блокировки указателя](https://developer.mozilla.org/en-US/docs/WebAPI/Pointer_Lock). +* `allow-popups`: Разрешает всплывающие окна (например, `window.open()`, `target="_blank"` или `showModalDialog()`). Если это ключевое слово не используется, всплывающее окно молча не откроется. +* `allow-popups-to-escape-sandbox`: Позволяет изолированному документу открывать новые окна без того, чтобы эти окна наследовали режим песочницы. Например, это позволяет безопасно изолировать рекламу, не накладывая тех же ограничений на страницу, на которую ведет реклама. +* `allow-presentation`: Позволяет ресурсу начать [сессию презентации](https://developer.mozilla.org/en-US/docs/Web/API/PresentationRequest). +* `allow-same-origin`: Если этот токен не используется, ресурс рассматривается как находящийся в особом месте, которое всегда не соответствует [политике одного источника](https://developer.mozilla.org/en-US/docs/Glossary/same-origin_policy "политика одного источника: политика одного источника — это критически важный механизм безопасности, который ограничивает взаимодействие документа или скрипта, загруженного из одного источника, с ресурсом из другого источника."). +* `allow-scripts`: Позволяет ресурсу запускать скрипты (но не создавать всплывающие окна). +* `allow-storage-access-by-user-activation`: Позволяет ресурсу запрашивать доступ к возможностям хранения родительского ресурса с помощью [API доступа к хранилищу](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API). +* `allow-top-navigation`: Позволяет ресурсу перемещаться по контексту просмотра верхнего уровня (тому, который называется `_top`). +* `allow-top-navigation-by-user-activation`: Позволяет ресурсу перемещаться по контексту просмотра верхнего уровня, но только если это инициируется жестом пользователя. + +**Примечания о песочнице:** + +* Когда встроенный документ имеет тот же источник, что и страница встраивания, **настоятельно не рекомендуется** использовать одновременно `allow-scripts` и `allow-same-origin`, поскольку это позволяет встроенному документу удалить атрибут `sandbox`, что делает его не более безопасным, чем полное отсутствие атрибута `sandbox`. +* Использование песочницы бесполезно, если злоумышленник может отображать контент за пределами изолированного `iframe` — например, если пользователь открывает фрейм в новой вкладке. Такой контент также следует предоставлять из _отдельного источника_, чтобы ограничить потенциальный ущерб. * Атрибут `sandbox` не поддерживается в Internet Explorer 9 и более ранних версиях. @@ -1619,7 +1619,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo This attribute defines the scheme in which metadata is described. A scheme is a context leading to the correct interpretations of the [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) value, like a format. **Warning:** Do not use this value, as it is obsolete. There is no replacement as there was no real usage for it. - Этот атрибут определяет схему, в которой описываются метаданные. Схема — это контекст, ведущий к правильной интерпретации значения [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content), подобно формату. + Этот атрибут определяет схему, в которой описываются метаданные. Схема — это контекст, ведущий к правильной интерпретации значения [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content), подобно формату. **Внимание:** Не используйте это значение, так как оно устарело. Замены нет, так как реальной потребности в этом не было. @@ -1645,10 +1645,10 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo * a white-space separated list of sizes, each in the format `_<width in pixels>_x_<height in pixels>_` or `_<width in pixels>_X_<height in pixels>_`. Each of these sizes must be contained in the resource. **Note:** Most icon formats are only able to store one single icon; therefore most of the time the [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-sizes) contains only one entry. MS's ICO format does, as well as Apple's ICNS. ICO is more ubiquitous; you should definitely use it. - Этот атрибут определяет размеры значков для визуальных мультимедиа, содержащихся в данном ресурсе. Он должен присутствовать, только если [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-rel) содержит значение `icon` или нестандартный тип, например `apple-touch-icon` от Apple. Оно может принимать следующие значения: + Этот атрибут определяет размеры значков для визуальных мультимедиа, содержащихся в данном ресурсе. Он должен присутствовать, только если [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-rel) содержит значение `icon` или нестандартный тип, например `apple-touch-icon` от Apple. Оно может принимать следующие значения: -* `any` означает, что иконку можно масштабировать до любого размера, поскольку она находится в векторном формате, например, `image/svg+xml`. -* Список размеров, разделённых пробелами, каждый в формате `_<width in pixels>_x_<height in pixels>_` или `_<width in pixels>_X_<height in pixels>_`. Каждый из этих размеров должен содержаться в ресурсе. +* `any` означает, что иконку можно масштабировать до любого размера, поскольку она находится в векторном формате, например, `image/svg+xml`. +* Список размеров, разделённых пробелами, каждый в формате `_<width in pixels>_x_<height in pixels>_` или `_<width in pixels>_X_<height in pixels>_`. Каждый из этих размеров должен содержаться в ресурсе. **Примечание:** Большинство форматов значков могут хранить только один значок; поэтому в большинстве случаев [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-sizes) содержит только одну запись. Формат ICO от Майкрософт — тоже, как и ICNS от Apple. ICO встречается повсеместно; вам определенно стоит его использовать. @@ -1663,9 +1663,9 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo * `true`, which indicates that the element should be, if possible, checked for spelling errors; * `false`, which indicates that the element should not be checked for spelling errors. - Перечисляемый атрибут определяет, может ли элемент проверяться на наличие орфографических ошибок. Оно может принимать следующие значения: + Перечисляемый атрибут определяет, может ли элемент проверяться на наличие орфографических ошибок. Оно может принимать следующие значения: -* `true`, что означает, что элемент следует, по возможности, проверить на наличие орфографических ошибок; +* `true`, что означает, что элемент следует, по возможности, проверить на наличие орфографических ошибок; * `false`, что указывает на то, что элемент не следует проверять на наличие орфографических ошибок. @@ -1673,7 +1673,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo This attribute specifies the URI of an external script; this can be used as an alternative to embedding a script directly within a document. If a `script` element has a `src` attribute specified, it should not have a script embedded inside its tags. - Этот атрибут указывает URI внешнего скрипта; его можно использовать в качестве альтернативы непосредственному встраиванию скрипта в документ. + Этот атрибут указывает URI внешнего скрипта; его можно использовать в качестве альтернативы непосредственному встраиванию скрипта в документ. Если у элемента `script` указан атрибут `src`, то внутри его тегов не должно быть встроенного сценария. @@ -1701,16 +1701,16 @@ If no descriptor is specified, the source is assigned the default descriptor: `1 It is incorrect to mix width descriptors and pixel density descriptors in the same `srcset` attribute. Duplicate descriptors (for instance, two sources in the same `srcset` which are both described with '`2x`') are also invalid. The user agent selects any one of the available sources at its discretion. This provides them with significant leeway to tailor their selection based on things like user preferences or bandwidth conditions. See our [Responsive images](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images) tutorial for an example. - Список из одной или нескольких строк, разделенных запятыми, который задает набор возможных источников изображений для использования пользовательским агентом. Каждая строка состоит из: + Список из одной или нескольких строк, разделенных запятыми, который задает набор возможных источников изображений для использования пользовательским агентом. Каждая строка состоит из: -1. URL изображения, -2. При желании, пробел, за которым следует один из следующих символов: -* Описание ширины или положительное целое число, за которым непосредственно следует '`w`'. Дескриптор ширины делится на размер источника, указанный в атрибуте `sizes`, для вычисления эффективной плотности пикселей. -* Дескриптор плотности пикселей, представляющий собой положительное число с плавающей запятой, за которым непосредственно следует '`x`'. +1. URL изображения, +2. При желании, пробел, за которым следует один из следующих символов: +* Описание ширины или положительное целое число, за которым непосредственно следует '`w`'. Дескриптор ширины делится на размер источника, указанный в атрибуте `sizes`, для вычисления эффективной плотности пикселей. +* Дескриптор плотности пикселей, представляющий собой положительное число с плавающей запятой, за которым непосредственно следует '`x`'. -Если дескриптор не указан, источнику присваивается дескриптор по умолчанию: `1x`. +Если дескриптор не указан, источнику присваивается дескриптор по умолчанию: `1x`. -Неправильно смешивать дескрипторы ширины и дескрипторы плотности пикселей в одном атрибуте `srcset`. Повторяющиеся дескрипторы (например, два источника в одном и том же `srcset`, оба описанные с помощью '`2x`') также недопустимы. +Неправильно смешивать дескрипторы ширины и дескрипторы плотности пикселей в одном атрибуте `srcset`. Повторяющиеся дескрипторы (например, два источника в одном и том же `srcset`, оба описанные с помощью '`2x`') также недопустимы. Агент пользователя выбирает любой из доступных источников по своему усмотрению. Это предоставляет им значительную свободу в выборе, основанном на таких факторах, как пользовательские предпочтения или условия пропускной способности сети. Пример см. в нашем руководстве по [адаптивным изображениям](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images). @@ -1719,7 +1719,7 @@ The user agent selects any one of the available sources at its discretion. This This integer attribute specifies the start value for numbering the individual list items. Although the ordering type of list elements might be Roman numerals, such as XXXI, or letters, the value of start is always represented as a number. To start numbering elements from the letter "C", use `<ol start="3">`. **Note**: This attribute was deprecated in HTML4, but reintroduced in HTML5. - Этот целочисленный атрибут задает начальное значение для нумерации отдельных элементов списка. Хотя элементы списка могут нумероваться римскими цифрами (например, XXXI) или буквами, значение параметра start всегда задается числом. Чтобы начать нумерацию элементов с буквы "С", используйте `<ol start="3">`. + Этот целочисленный атрибут задает начальное значение для нумерации отдельных элементов списка. Хотя элементы списка могут нумероваться римскими цифрами (например, XXXI) или буквами, значение параметра start всегда задается числом. Чтобы начать нумерацию элементов с буквы "С", используйте `<ol start="3">`. **Примечание**: Этот атрибут был устарел в HTML4, но вновь введен в HTML5. @@ -1735,10 +1735,10 @@ The user agent selects any one of the available sources at its discretion. This * a _negative value_ means that the element should be focusable, but should not be reachable via sequential keyboard navigation; * `0` means that the element should be focusable and reachable via sequential keyboard navigation, but its relative order is defined by the platform convention; * a _positive value_ means that the element should be focusable and reachable via sequential keyboard navigation; the order in which the elements are focused is the increasing value of the [**tabindex**](https://developer.mozilla.org/docs/Web/HTML/Reference/Global_attributes/tabindex#attr-tabindex). If several elements share the same tabindex, their relative order follows their relative positions in the document. - Целочисленный атрибут, указывающий, может ли элемент получать фокус ввода (является ли он "фокусируемым"), должен ли он участвовать в последовательной навигации с клавиатуры и, если да, то на какой позиции. Оно может принимать несколько значений: + Целочисленный атрибут, указывающий, может ли элемент получать фокус ввода (является ли он "фокусируемым"), должен ли он участвовать в последовательной навигации с клавиатуры и, если да, то на какой позиции. Оно может принимать несколько значений: -* Отрицательное значение означает, что элемент должен быть фокусируемым, но не должен быть доступен для последовательной навигации с помощью клавиатуры; -* `0` означает, что элемент должен быть фокусируемым и доступным посредством последовательной навигации с помощью клавиатуры, но его относительный порядок определяется соглашением платформы; +* Отрицательное значение означает, что элемент должен быть фокусируемым, но не должен быть доступен для последовательной навигации с помощью клавиатуры; +* `0` означает, что элемент должен быть фокусируемым и доступным посредством последовательной навигации с помощью клавиатуры, но его относительный порядок определяется соглашением платформы; * Положительное значение означает, что элемент должен быть доступен для фокусировки и управления с помощью последовательной навигации с помощью клавиатуры; порядок фокусировки элементов определяется возрастающим значением [**tabindex**](https://developer.mozilla.org/docs/Web/HTML/Reference/Global_attributes/tabindex#attr-tabindex). Если несколько элементов имеют одинаковое значение tabindex, их относительный порядок определяется их взаимным расположением в документе. @@ -1751,12 +1751,12 @@ The user agent selects any one of the available sources at its discretion. This * `_top`: Load the result into the top-level browsing context (that is, the browsing context that is an ancestor of the current one, and has no parent). If there is no parent, this option behaves the same way as `_self`. If this attribute is specified, this element must come before any other elements with attributes whose values are URLs. - Имя или ключевое слово, указывающее место по умолчанию для отображения результата при переходе по гиперссылке или отправке формы — для элементов, у которых не задан явный целевой адрес. Это имя или ключевое слово, обозначающее *контекст просмотра* (например, вкладку, окно или встроенный фрейм). Следующие ключевые слова имеют особое значение: + Имя или ключевое слово, указывающее место по умолчанию для отображения результата при переходе по гиперссылке или отправке формы — для элементов, у которых не задан явный целевой адрес. Это имя или ключевое слово, обозначающее *контекст просмотра* (например, вкладку, окно или встроенный фрейм). Следующие ключевые слова имеют особое значение: -* `_self`: Загрузить результат в тот же контекст просмотра, что и текущий. Это значение является значением по умолчанию, если атрибут не указан. -* `_blank`: Загрузить результат в новый безымянный контекст просмотра. -* `_parent`: Загрузить результат в контекст просмотра родительского окна текущего окна. Если родительский элемент отсутствует, этот параметр ведет себя так же, как `_self`. -* `_top`: Загрузить результат в контекст просмотра верхнего уровня (то есть в контекст просмотра, являющийся предком текущего и не имеющий родителя). Если родительский элемент отсутствует, этот параметр ведет себя так же, как `_self`. +* `_self`: Загрузить результат в тот же контекст просмотра, что и текущий. Это значение является значением по умолчанию, если атрибут не указан. +* `_blank`: Загрузить результат в новый безымянный контекст просмотра. +* `_parent`: Загрузить результат в контекст просмотра родительского окна текущего окна. Если родительский элемент отсутствует, этот параметр ведет себя так же, как `_self`. +* `_top`: Загрузить результат в контекст просмотра верхнего уровня (то есть в контекст просмотра, являющийся предком текущего и не имеющий родителя). Если родительский элемент отсутствует, этот параметр ведет себя так же, как `_self`. Если этот атрибут указан, данный элемент должен располагаться перед любыми другими элементами, имеющими атрибуты, значениями которых являются URL-адреса. @@ -1771,9 +1771,9 @@ If this attribute is specified, this element must come before any other elements * empty string and `yes`, which indicates that the element will be translated. * `no`, which indicates that the element will not be translated. - Перечисляемый атрибут, используемый для указания того, являются ли значения атрибутов элемента и значения его [`Text`](https://developer.mozilla.org/docs/Web/API/Text "Интерфейс Text представляет текстовое содержимое элемента или атрибута. Если элемент не содержит разметки внутри своего содержимого, то у него имеется единственный дочерний узел, реализующий интерфейс Text и содержащий текст этого элемента. Однако если элемент содержит разметку, она разбирается на информационные элементы и текстовые узлы, образующие его дочерние элементы.") должны ли дочерние текстовые узлы переводиться при локализации страницы или оставаться без изменений. Оно может принимать следующие значения: + Перечисляемый атрибут, используемый для указания того, являются ли значения атрибутов элемента и значения его [`Text`](https://developer.mozilla.org/docs/Web/API/Text "Интерфейс Text представляет текстовое содержимое элемента или атрибута. Если элемент не содержит разметки внутри своего содержимого, то у него имеется единственный дочерний узел, реализующий интерфейс Text и содержащий текст этого элемента. Однако если элемент содержит разметку, она разбирается на информационные элементы и текстовые узлы, образующие его дочерние элементы.") должны ли дочерние текстовые узлы переводиться при локализации страницы или оставаться без изменений. Оно может принимать следующие значения: -* пустая строка и `yes`, указывающая на то, что элемент будет переведен. +* пустая строка и `yes`, указывающая на то, что элемент будет переведен. * `no`, что означает, что элемент не будет переведен. @@ -1786,7 +1786,7 @@ If this attribute is specified, this element must come before any other elements The partial URL (starting with '#') of an [image map](https://developer.mozilla.org/en-US/docs/HTML/Element/map) associated with the element. **Note:** You cannot use this attribute if the `<img>` element is a descendant of an [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "The HTML <a> element (or anchor element) creates a hyperlink to other web pages, files, locations within the same page, email addresses, or any other URL.") or [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "The HTML <button> element represents a clickable button, which can be used in forms or anywhere in a document that needs simple, standard button functionality.") element. - Частичный URL (начинающийся с '#') [карты изображений](https://developer.mozilla.org/en-US/docs/HTML/Element/map), связанной с элементом. + Частичный URL (начинающийся с '#') [карты изображений](https://developer.mozilla.org/en-US/docs/HTML/Element/map), связанной с элементом. **Примечание:** Вы не можете использовать этот атрибут, если элемент `<img>` является потомком элемента [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a Элемент HTML <a> (или элемент привязки) создает гиперссылку на другие веб-страницы, файлы, места на той же странице, адреса электронной почты или любой другой URL-адрес) или [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button`. Элемент HTML <button> представляет собой кликабельную кнопку, которую можно использовать в формах или в любом месте документа, где требуется простая стандартная функциональность кнопки.). @@ -1797,9 +1797,9 @@ If this attribute is specified, this element must come before any other elements **Note**: This attribute was deprecated in HTML4, but reintroduced in HTML5. **Note:** Prior to Gecko 9.0, negative values were incorrectly converted to 0. Starting in Gecko 9.0 all integer values are correctly parsed. - Этот целочисленный атрибут указывает текущее порядковое значение элемента списка, определяемое элементом [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol "Элемент HTML <ol> представляет собой упорядоченный список элементов, который обычно отображается как нумерованный список."). Единственным допустимым значением для этого атрибута является число, даже если список отображается с использованием римских цифр или букв. Элементы списка, следующие за данным, продолжают нумерацию, исходя из заданного значения. Атрибут **value** не имеет значения для неупорядоченных списков ([`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul "Элемент HTML <ul> представляет собой неупорядоченный список элементов, обычно отображаемый в виде маркированного списка.")) или для меню ([`<menu>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu "Элемент HTML <menu> представляет собой группу команд, которые пользователь может выполнить или активировать. Это включает в себя как списковые меню, которые могут появляться в верхней части экрана, так и контекстные меню, например, те, которые могут появляться под кнопкой после того, как на нее нажали.")). + Этот целочисленный атрибут указывает текущее порядковое значение элемента списка, определяемое элементом [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol "Элемент HTML <ol> представляет собой упорядоченный список элементов, который обычно отображается как нумерованный список."). Единственным допустимым значением для этого атрибута является число, даже если список отображается с использованием римских цифр или букв. Элементы списка, следующие за данным, продолжают нумерацию, исходя из заданного значения. Атрибут **value** не имеет значения для неупорядоченных списков ([`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul "Элемент HTML <ul> представляет собой неупорядоченный список элементов, обычно отображаемый в виде маркированного списка.")) или для меню ([`<menu>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu "Элемент HTML <menu> представляет собой группу команд, которые пользователь может выполнить или активировать. Это включает в себя как списковые меню, которые могут появляться в верхней части экрана, так и контекстные меню, например, те, которые могут появляться под кнопкой после того, как на нее нажали.")). -**Примечание**: Этот атрибут был устарел в HTML4, но вновь введен в HTML5. +**Примечание**: Этот атрибут был устарел в HTML4, но вновь введен в HTML5. **Примечание:** До версии Gecko 9.0 отрицательные значения некорректно преобразовывались в 0. Начиная с Gecko 9.0, все целочисленные значения обрабатываются правильно. @@ -1810,10 +1810,10 @@ If this attribute is specified, this element must come before any other elements * data: Default value. The value is passed to the object's implementation as a string. * ref: The value is a URI to a resource where run-time values are stored. * object: An ID of another [`<object>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object "The HTML <object> element represents an external resource, which can be treated as an image, a nested browsing context, or a resource to be handled by a plugin.") in the same document. - Задает тип атрибута `value`. Возможные значения: + Задает тип атрибута `value`. Возможные значения: -* данные: Значение по умолчанию. Значение передается в реализацию объекта в виде строки. -* ref: Значение представляет собой URI ресурса, где хранятся значения, полученные во время выполнения. +* данные: Значение по умолчанию. Значение передается в реализацию объекта в виде строки. +* ref: Значение представляет собой URI ресурса, где хранятся значения, полученные во время выполнения. * object: Идентификатор другого элемента [`<object>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object "HTML-элемент <object> представляет собой внешний ресурс, который может рассматриваться как изображение, вложенный контекст просмотра или ресурс, обрабатываемый плагином.") в том же документе. @@ -1835,11 +1835,11 @@ If this attribute is specified, this element must come before any other elements * `off` : Like `soft` but changes appearance to `white-space: pre` so line segments exceeding `cols` are not wrapped and the `<textarea>` becomes horizontally scrollable. If this attribute is not specified, `soft` is its default value. - Указывает, как элемент управления переносит текст. Возможные значения: + Указывает, как элемент управления переносит текст. Возможные значения: -* `hard`: Браузер автоматически вставляет переносы строк (CR+LF), так что каждая строка имеет ширину не более ширины элемента управления; для того, чтобы это вступило в силу, необходимо также указать атрибут `cols`. -* `soft`: Браузер гарантирует, что все переносы строк в значении состоят из пары CR+LF, но не вставляет никаких дополнительных переносов строк. -* `off`: Аналогично `soft`, но изменяет внешний вид на `white-space: pre`, так что отрезки строк, превышающие `cols`, не переносятся, а `<textarea>` становится прокручиваемым по горизонтали. +* `hard`: Браузер автоматически вставляет переносы строк (CR+LF), так что каждая строка имеет ширину не более ширины элемента управления; для того, чтобы это вступило в силу, необходимо также указать атрибут `cols`. +* `soft`: Браузер гарантирует, что все переносы строк в значении состоят из пары CR+LF, но не вставляет никаких дополнительных переносов строк. +* `off`: Аналогично `soft`, но изменяет внешний вид на `white-space: pre`, так что отрезки строк, превышающие `cols`, не переносятся, а `<textarea>` становится прокручиваемым по горизонтали. Если этот атрибут не указан, его значением по умолчанию является `soft`. diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.tr.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.tr.xlf index 1ba567d3dabf5..4f45809dadc02 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.tr.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.tr.xlf @@ -93,7 +93,7 @@ See [Browser compatibility](https://developer.mozilla.org/docs/Web/HTML/Referenc Tarayıcılar genellikle en kötü senaryoyu varsayar ve HTML ayrıştırması sırasında betikleri zaman uyumlu olarak (ör. `async="false"`) yükler. -Dinamik olarak eklenen betikler ([`document.createElement()`] kullanılarak](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement "Bir HTML belgede, document.createElement() yöntemi tagName tarafından belirtilen HTML öğesini veya tagName tanınmıyorsa htmlUnknownElement oluşturur.")) varsayılan olarak zaman uyumsuz yüklenir; bu nedenle zaman uyumlu yüklemeyi açmak için (betikler eklendiği sırayla yüklenir) `async="false"` olarak ayarlanır. +Dinamik olarak eklenen betikler ([`document.createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement "Bir HTML belgede, document.createElement() yöntemi tagName tarafından belirtilen HTML öğesini veya tagName tanınmıyorsa htmlUnknownElement oluşturur.")) kullanılarak varsayılan olarak zaman uyumsuz yüklenir; bu nedenle zaman uyumlu yüklemeyi açmak için (betikler eklendiği sırayla yüklenir) `async="false"` olarak ayarlanır. Tarayıcı desteğiyle ilgili notlar için [Tarayıcı uyumluluğu](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/script#Browser_compatibility) bölümüne bakın. Ayrıca [Asm.js için zaman uyumsuz betikler](https://developer.mozilla.org/en-US/docs/Games/Techniques/Async_scripts) bölümüne de bakın. @@ -183,7 +183,7 @@ For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) sett * Kodlamayı bildiren [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> öğesi, <base>, <link>, <script>, <style> veya <title> gibi diğer HTML meta verileriyle ilgili öğelerde temsil edilemeyen meta verileri temsil eder.") öğesi [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head "HTML <head> öğesi belge hakkında, başlığı ve betikleriyle stil sayfalarına bağlantıları gibi genel bilgileri (meta veriler) sağlar.") öğesinin içinde ve HTML'nin **ilk 1024 baytı içinde** yer almalıdır çünkü bazı tarayıcılar kodlamayı seçmeden önce yalnızca bu baytlara bakar. * Bu [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> öğesi <base>, <link>, <script>, <style> veya <title> gibi diğer HTML meta verileriyle ilgili öğelerde temsil edilemeyen meta verileri temsil eder.") öğesi, [sayfanın karakter kümesini saptayan algoritmanın](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Algorithm charset page") yalnızca bir parçasıdır. [`Content-Type` üst bilgisi](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) ve herhangi bir [Byte-Order Marks](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark "Bu terimin tanımı (Byte-Order Marks) henüz yazılmadı; lütfen katkıda bulunmayı düşünün!") bu öğeyi geçersiz kılar. * Karakter kodlamasını tanımlamanız kesinlikle önerilir. Bir sayfanın kodlaması tanımsızsa, [`UTF-7` yedek siteler arası komut dosyası çalıştırma tekniği](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7) gibi siteler arası komut dosyası çalıştırma teknikleri kullanılabilir. -* `charset` özniteliğini içeren [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> öğesi <base>, <link>, <script>, <style> veya <title> gibi diğer HTML meta verileriyle ilgili öğelerde temsil edilemeyen meta verileri temsil eder.") öğesi, HTML5 öncesi `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">` ile eş anlamlıdır; burada _`IANAcharset`_ , eşdeğer [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) özniteliğinin değerini içerir. Bu söz dizimine hala izin verilir, ancak artık önerilmez. +* `charset` özniteliğini içeren [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> öğesi <base>, <link>, <script>, <style> veya <title> gibi diğer HTML meta verileriyle ilgili öğelerde temsil edilemeyen meta verileri temsil eder.") öğesi, HTML5 öncesi `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">` ile eş anlamlıdır; burada _`IANAcharset`_, eşdeğer [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) özniteliğinin değerini içerir. Bu söz dizimine hala izin verilir, ancak artık önerilmez. @@ -535,7 +535,7 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page * `"content-language"` Sayfanın varsayılan dilini tanımlar. Herhangi bir öğedeki [lang](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) özniteliğiyle geçersiz kılınabilir. -**Uyarı:** Kullanımdan kaldırıldığı için bu değeri kullanmayın. [`<html>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html "HTML <html> öğesi bir HTML belgesinin kök (üst düzey öğesi) öğesini temsil eder, bu nedenle kök öğe olarak da adlandırılır. Diğer tüm öğeler bu öğenin alt öğeleri olmalıdır.") öğesinde `lang` özniteliğini tercih edin . +**Uyarı:** Kullanımdan kaldırıldığı için bu değeri kullanmayın. [`<html>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html "HTML <html> öğesi bir HTML belgesinin kök (üst düzey öğesi) öğesini temsil eder, bu nedenle kök öğe olarak da adlandırılır. Diğer tüm öğeler bu öğenin alt öğeleri olmalıdır.") öğesinde `lang` özniteliğini tercih edin. * `"content-security-policy"` Sayfa yazarlarının geçerli sayfa için bir [içerik ilkesi](https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives) tanımlamasına olanak sağlar. İçerik ilkeleri çoğunlukla izin verilen sunucu çıkış noktalarını ve betik uç noktalarını belirtir. Bu da siteler arası betik çalıştırma saldırılarına karşı koruma sağlamaya yardımcı olur. @@ -587,7 +587,7 @@ Sayfa için bir [çerez](https://developer.mozilla.org/en-US/docs/cookie) tanım **Note:** This attribute is allowed only if the `<img>` element is a descendant of an [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "The HTML <a> element (or anchor element) creates a hyperlink to other web pages, files, locations within the same page, email addresses, or any other URL.") element with a valid [`href`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-href) attribute. Bu Boole özniteliği, görüntünün bir sunucu tarafı eşlemesinin parçası olduğunu gösterir. Bu durumda, bir tıklamanın kesin koordinatları sunucuya gönderilir. -**Not:** Bu öznitelik yalnızca `<img>` öğesi geçerli bir [`href`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-href(<a>) özniteliğine sahip [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "HTML öğesi (veya bağlayıcı öğesi) aynı sayfa, e-posta adresleri veya başka bir URL içindeki diğer web sayfalarına, dosyalara, konumlara köprü oluşturur.") öğesinin alt öğesi ise izin verilir. +**Not:** Bu öznitelik yalnızca `<img>` öğesi geçerli bir [`href`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-href) özniteliğine sahip [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "HTML <a> öğesi (veya bağlayıcı öğesi) aynı sayfa, e-posta adresleri veya başka bir URL içindeki diğer web sayfalarına, dosyalara, konumlara köprü oluşturur.") öğesinin alt öğesi ise izin verilir. @@ -647,7 +647,7 @@ Sayfa için bir [çerez](https://developer.mozilla.org/en-US/docs/cookie) tanım * `chapters` * Bölüm başlıkları, kullanıcı medya kaynağında gezinirken kullanılmak üzere tasarlanmıştır. * `metadata` -* Betikler tarafından kullanılan parçalar. Kullanıcıya görünmez. +* Betikler tarafından kullanılan parçalar. Kullanıcı tarafından görülmez. @@ -1142,7 +1142,7 @@ Açıklama Viewport META öğesini normatif olmayan bir şekilde açıklar -Ayrıca bkz: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport "@viewport CSS at-rule, belgenin görüntüleneceği görünüm penceresini yapılandırmanıza olanaktanır. Asıl olarak mobil cihazlarda kullanılır ancak "kenara yasla" gibi özellikleri destekleyen masaüstü tarayıcıları tarafından da kullanılır (Microsoft Edge gibi).") +Ayrıca bkz: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport "@viewport CSS at-rule, belgenin görüntüleneceği görünüm penceresini yapılandırmanıza olanak tanır. Asıl olarak mobil cihazlarda kullanılır ancak "kenara yasla" gibi özellikleri destekleyen masaüstü tarayıcıları tarafından da kullanılır (Microsoft Edge gibi).") **Notlar:** @@ -1797,7 +1797,7 @@ Bu öznitelik belirtilirse bu öğe, değeri URL olan özniteliklere sahip diğe **Note**: This attribute was deprecated in HTML4, but reintroduced in HTML5. **Note:** Prior to Gecko 9.0, negative values were incorrectly converted to 0. Starting in Gecko 9.0 all integer values are correctly parsed. - Bu tamsayı özniteliği, [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol "HTML <ol> öğesi, genellikle numaralanmış liste olarak işlenen sıralı bir öğe listesini temsil eder.") öğesi tarafından tanımlandığında liste öğesinin geçerli sıra değerini gösterir. Bu öznitelik için izin verilen tek değer sayıdır. Liste Romen rakamlarıyla veya harflerle gösterilse bile bu değişmez. Bu öğeyi izleyen liste öğeleri, ayarlanan değerden itibaren numaralandırmaya devam eder. **value** özniteliğinin sıralanmamış listeler ([`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul "HTML <ul> öğesi, normalde madde işaretli liste olarak işlenen öğelerin sıralanmamış bir listesini temsil eder.") veya menüler ([`<menu>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu "HTML <menu> öğesi kullanıcının gerçekleştirip etkinleştirene bir komut grubunu temsil eder. Bu, ekranın üst kısmında görüntülenebilir liste menülerinin yanı sıra tıklandıktan sonra düğmenin altında görünenler gibi bağlam menülerini de içerir.")). + Bu tamsayı özniteliği, [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol "HTML <ol> öğesi, genellikle numaralanmış liste olarak işlenen sıralı bir öğe listesini temsil eder.") öğesi tarafından tanımlandığında liste öğesinin geçerli sıra değerini gösterir. Bu öznitelik için izin verilen tek değer sayıdır. Liste Romen rakamlarıyla veya harflerle gösterilse bile bu değişmez. Bu öğeyi izleyen liste öğeleri, ayarlanan değerden itibaren numaralandırmaya devam eder. **value** özniteliğinin sıralanmamış listeler ([`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul "HTML <ul> öğesi, normalde madde işaretli liste olarak işlenen öğelerin sıralanmamış bir listesini temsil eder.")) veya menüler ([`<menu>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu "HTML <menu> öğesi kullanıcının gerçekleştirip etkinleştirene bir komut grubunu temsil eder. Bu, ekranın üst kısmında görüntülenebilir liste menülerinin yanı sıra tıklandıktan sonra düğmenin altında görünenler gibi bağlam menülerini de içerir.")). **Not**: Bu öznitelik HTML4'te kullanım dışı bırakılmıştı, ancak HTML5'te yeniden kullanıma sunuldu. diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.zh-Hans.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.zh-Hans.xlf index 0bf48a749f68c..b30850341dda3 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.zh-Hans.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.zh-Hans.xlf @@ -43,7 +43,7 @@ In previous versions of HTML, the different character encodings could be delimit **使用说明** -* 不要使用此属性,因为它已被弃用。应使用 [CSS](https://developer.mozilla.org/en-US/docs/CSS)为 [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table "HTML <table> 元素表示表格数据,也就是以行和列组成的二维表格形式呈现、由单元格中的数据构成的信息。")元素设置样式。将 [`margin-left`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left "margin-left CSS 属性设置元素左侧的外边距区域。正值会使它离相邻元素更远,负值会使它更近。")和 [`margin-right`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right "margin-right CSS 属性设置元素右侧的外边距区域。正值会使它离相邻元素更远,负值会使它更近。")设置为 `auto`,或将 [`margin`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin "margin CSS 属性设置元素四个方向的外边距区域。它是 margin-top、margin-right、margin-bottom 和 margin-left 的简写。")设置为 `0 auto`,即可实现与 align 属性类似的效果。 +* 不要使用此属性,因为它已被弃用。应使用 [CSS](https://developer.mozilla.org/zh-CN/docs/CSS)为 [`<table>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/table "HTML <table> 元素表示表格数据,也就是以行和列组成的二维表格形式呈现、由单元格中的数据构成的信息。")元素设置样式。将 [`margin-left`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/margin-left "margin-left CSS 属性设置元素左侧的外边距区域。正值会使它离相邻元素更远,负值会使它更近。")和 [`margin-right`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/margin-right "margin-right CSS 属性设置元素右侧的外边距区域。正值会使它离相邻元素更远,负值会使它更近。")设置为 `auto`,或将 [`margin`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/margin "margin CSS 属性设置元素四个方向的外边距区域。它是 margin-top、margin-right、margin-bottom 和 margin-left 的简写。")设置为 `0 auto`,即可实现与 align 属性类似的效果。 * 在 Firefox 4 之前,Firefox 还在 quirks 模式下支持将 `middle`、`absmiddle` 和 `abscenter` 作为 `center` 的同义词。 @@ -55,7 +55,7 @@ In previous versions of HTML, the different character encodings could be delimit Set to `true` if the `<iframe>` can activate fullscreen mode by calling the [`requestFullscreen()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen "The Element.requestFullscreen() method issues an asynchronous request to make the element be displayed in full-screen mode.") method. This attribute is considered a legacy attribute and redefined as `allow="fullscreen"`. - 如果 `<iframe>` 可以通过调用 [`requestFullscreen()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen "Element.requestFullscreen()方法会发出异步请求,使元素以全屏模式显示。")方法来激活全屏模式,则将此值设为 `true`。 + 如果 `<iframe>` 可以通过调用 [`requestFullscreen()`](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/requestFullscreen "Element.requestFullscreen()方法发出异步请求,使元素以全屏模式显示。")方法来激活全屏模式,则将此值设为 `true`。 此属性被视为旧属性,已重新定义为 `allow="fullscreen"`。 @@ -67,7 +67,7 @@ This attribute is considered a legacy attribute and redefined as `allow="fullscr **Note:** Omitting this attribute altogether indicates that the image is a key part of the content, and no textual equivalent is available. Setting this attribute to an empty string (`alt=""`) indicates that this image is _not_ a key part of the content (decorative), and that non-visual browsers may omit it from rendering. 此属性定义图像的替换文字说明。 -**注意:** 浏览器并不总会显示元素引用的图像。在非图形浏览器中(包括视障人士使用的浏览器),如果用户选择不显示图像,或者如果浏览器因为图像无效或属于[不受支持的类型](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/img#Supported_image_formats)而无法显示图像,则会出现这种情况。在这些情况下,浏览器可能会用此元素的 `alt` 属性中定义的文本替换图像。基于这些原因以及其他原因,只要可能,你都应为 `alt` 提供有用的值。 +**注意:** 浏览器并不总会显示元素引用的图像。在非图形浏览器中(包括视障人士使用的浏览器),如果用户选择不显示图像,或者如果浏览器因为图像无效或属于 [不受支持的类型](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/img#Supported_image_formats)而无法显示图像,则会出现这种情况。在这些情况下,浏览器可能会用此元素的 `alt` 属性中定义的文本替换图像。基于这些原因以及其他原因,只要可能,你都应为 `alt` 提供有用的值。 **注意:** 完全省略此属性表示图像是内容的关键部分,并且没有可用的文本等效内容。将此属性设置为空字符串(`alt=""`) 表示该图像不是内容的关键部分(属于装饰性内容),非可视化浏览器可以在呈现时将其省略。 @@ -93,9 +93,9 @@ See [Browser compatibility](https://developer.mozilla.org/docs/Web/HTML/Referenc 浏览器通常会假定最坏情况,并在 HTML 解析期间同步加载脚本(即 `async="false"`)。 -动态插入的脚本(使用 [`document.createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement "在 HTML 文档中,document.createElement()方法会创建 tagName 指定的 HTML 元素;如果 tagName 无法识别,则创建 HTMLUnknownElement。"))默认异步加载,因此要启用同步加载(即,脚本按插入顺序加载),请设置 `async="false"`。 +动态插入的脚本(使用 [`document.createElement()`](https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createElement "在 HTML 文档中,document.createElement()方法会创建 tagName 指定的 HTML 元素;如果 tagName 无法识别,则创建 HTMLUnknownElement。"))默认异步加载,因此要启用同步加载(即,脚本按插入顺序加载),请设置 `async="false"`。 -有关浏览器支持的说明,请参阅[浏览器兼容性](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/script#Browser_compatibility)。另请参阅 [asm.js 的异步脚本](https://developer.mozilla.org/en-US/docs/Games/Techniques/Async_scripts)。 +有关浏览器支持的说明,请参阅 [浏览器兼容性](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/script#Browser_compatibility)。另请参阅 [asm.js 的异步脚本](https://developer.mozilla.org/zh-CN/docs/Games/Techniques/Async_scripts)。 @@ -126,7 +126,7 @@ For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) sett * `off`: 用户每次使用时都必须在每个字段中显式输入值,或者文档提供自己的自动完成方法;浏览器不会自动完成输入。 * `on`: 浏览器可以根据用户之前在表单中输入的值自动完成值。 -对于大多数现代浏览器(包括 Firefox 38+、Google Chrome 34+、IE 11+),设置 autocomplete 属性不会阻止浏览器的密码管理器询问用户是否要保存登录字段(用户名和密码)。如果用户允许保存,浏览器会在用户下次访问页面时自动填充登录信息。请参阅 [autocomplete 属性和登录字段](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion#The_autocomplete_attribute_and_login_fields)。 +对于大多数现代浏览器(包括 Firefox 38+、Google Chrome 34+、IE 11+),设置 autocomplete 属性不会阻止浏览器的密码管理器询问用户是否要保存登录字段(用户名和密码)。如果用户允许保存,浏览器会在用户下次访问页面时自动填充登录信息。请参阅 [autocomplete 属性和登录字段](https://developer.mozilla.org/zh-CN/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion#The_autocomplete_attribute_and_login_fields)。 **注意:** 如果由于文档提供了自己的自动完成功能而在表单中将 `autocomplete` 设置为 `off`,则还应为文档可自动完成的表单中的每个 `input` 元素将 `autocomplete` 设置为 `off`。有关详细信息,请参阅[浏览兼容性图表](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form#compatChart)中关于 Google Chrome 的说明。 @@ -146,7 +146,7 @@ For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) sett **Note:** Do not use this attribute as it is obsolete in the latest standard: use the [`scope`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th#attr-scope) attribute instead. 此属性包含一个以空格分隔的字符串列表。每个字符串都是此标头所应用的一组单元格的 `id`。 -**注意:** 请勿使用此属性,因为它在最新标准中已过时: 请改用 [`scope`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th#attr-scope)属性。 +**注意:** 请勿使用此属性,因为它在最新标准中已过时: 请改用 [`scope`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/th#attr-scope)属性。 @@ -167,23 +167,23 @@ For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) sett * This [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") element is only one part of the [algorithm to determine a page's character set](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Algorithm charset page"). The [`Content-Type` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) and any [Byte-Order Marks](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark "The definition of that term (Byte-Order Marks) has not been written yet; please consider contributing it!") override this element. * It is strongly recommended to define the character encoding. If a page's encoding is undefined, cross-scripting techniques are possible, such as the [`UTF-7` fallback cross-scripting technique](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7). * The [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") element with a `charset` attribute is a synonym for the pre-HTML5 `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">`, where _`IANAcharset`_ contains the value of the equivalent [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) attribute. This syntax is still allowed, although no longer recommended. - 此属性声明页面的字符编码。它必须包含[字符编码的标准 IANA MIME 名称](https://www.iana.org/assignments/character-sets)。虽然标准没有要求使用特定编码,但建议: + 此属性声明页面的字符编码。它必须包含 [字符编码的标准 IANA MIME 名称](https://www.iana.org/assignments/character-sets)。虽然标准没有要求使用特定编码,但建议: -* 建议作者使用 [`UTF-8`](https://developer.mozilla.org/en-US/docs/Glossary/UTF-8)。 +* 建议作者使用 [`UTF-8`](https://developer.mozilla.org/zh-CN/docs/Glossary/UTF-8)。 * 作者不应使用与 ASCII 不兼容的编码,以避免安全风险: 不支持这些编码的浏览器可能会将有害内容解释为 HTML。这种情况会出现在 `JIS_C6226-1983`、`JIS_X0212-1990`、`HZ-GB-2312`、`JOHAB`、ISO-2022 系列和 EBCDIC 系列中。 **注意:** 与 ASCII 不兼容的编码,是指没有将 8 位码位 `0x20` 到 `0x7E` 映射到 Unicode 码位 `0x0020` 到 `0x007E` 的编码 -* 作者不得使用 `CESU-8`、`UTF-7`、`BOCU-1` 和/或 `SCSU`,因为已经证明可以利用这些编码发起[跨站脚本](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting)攻击。 +* 作者不得使用 `CESU-8`、`UTF-7`、`BOCU-1` 和/或 `SCSU`,因为已经证明可以利用这些编码发起[跨站脚本](https://developer.mozilla.org/zh-CN/docs/Glossary/Cross-site_scripting)攻击。 * 作者不应使用 `UTF-32`,因为并非所有 HTML5 编码算法都能将它与 `UTF-16` 区分开来。 **注意:** * 声明的字符编码必须与页面保存时使用的编码一致,以避免乱码和安全漏洞。 -* 声明编码的 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> 元素表示不能由其他 HTML 元相关元素(如 <base>、<link>、<script>、<style> 或 <title>)表示的元素。")元素必须包含在 [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head "HTML <head> 元素提供文档的一般信息(元数据),包括其标题以及其脚本和样式表的链接。")元素以及 HTML 的前 1024 字节内,因为一些浏览器在选择编码之前只查看这些字节。 -* 此 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> 元素表示不能由其他 HTML 元相关元素(如 <base>、<link>、<script>、<style> 或 <title>)表示的元数据。")元素只是[用于确定页面字符集的算法](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Algorithm charset page")的一部分。[`Content-Type` 标头](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type)和任何[字节顺序标记](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark "(字节顺序标记)的定义尚未编写;请考虑贡献该术语!")会替代此元素。 +* 声明编码的 [`<meta>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/meta "HTML <meta> 元素表示不能由其他 HTML 元相关元素(如 <base>、<link>、<script>、<style> 或 <title>)表示的元素。")元素必须包含在 [`<head>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/head "HTML <head> 元素提供文档的一般信息(元数据),包括其标题以及其脚本和样式表的链接。")元素以及 HTML 的前 1024 字节内,因为一些浏览器在选择编码之前只查看这些字节。 +* 此 [`<meta>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/meta "HTML <meta> 元素表示不能由其他 HTML 元相关元素(如 <base>、<link>、<script>、<style> 或 <title>)表示的元数据。")元素只是[用于确定页面字符集的算法](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Algorithm charset page")的一部分。[`Content-Type` 标头](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Type)和任何[字节顺序标记](https://developer.mozilla.org/zh-CN/docs/Glossary/Byte-Order_Mark "(字节顺序标记)的定义尚未编写;请考虑贡献该术语!")会替代此元素。 * 强烈建议定义字符编码。如果页面编码未定义,可以使用跨脚本技术,例如 [`UTF-7` 回退跨脚本技术](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7)。 -* 具有 `charset` 属性的 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> 元素表示不能由其他 HTML 元相关元素(如 <base>、<link>、<script>、<style> 或 <title>)表示的元数据。")元素是预 HTML5 `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">` 的同义词,其中 `IANAcharset` 包含等效 [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset)属性的值。仍允许使用此语法,但不再是推荐做法。 +* 具有 `charset` 属性的 [`<meta>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/meta "HTML <meta> 元素表示不能由其他 HTML 元相关元素(如 <base>、<link>、<script>、<style> 或 <title>)表示的元数据。")元素是预 HTML5 `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">` 的同义词,其中 `IANAcharset` 包含等效 [`charset`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/meta#attr-charset)属性的值。仍允许使用此语法,但不再是推荐做法。 @@ -239,17 +239,17 @@ A cross-origin request (i.e. with an [`Origin`](https://developer.mozilla.org/en A cross-origin request (i.e. with an `Origin` HTTP header) is performed along with a credential sent (i.e. a cookie, certificate, and/or HTTP Basic authentication is performed). If the server does not give credentials to the origin site (through [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials "The Access-Control-Allow-Credentials response header tells browsers whether to expose the response to frontend JavaScript code when the request's credentials mode (Request.credentials) is "include".") HTTP header), the resource will be _tainted_ and its usage restricted. If the attribute is not present, the resource is fetched without a [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS: CORS (Cross-Origin Resource Sharing) is a system, consisting of transmitting HTTP headers, that determines whether browsers block frontend JavaScript code from accessing responses for cross-origin requests.") request (i.e. without sending the `Origin` HTTP header), preventing its non-tainted usage. If invalid, it is handled as if the enumerated keyword **anonymous** was used. See [CORS settings attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for additional information. - 此枚举属性指示在提取资源时,是否必须使用 [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS: CORS (跨域资源共享)是一个系统,由传输 HTTP 标头构成,确定浏览器是否阻止前端 JavaScript 代码访问跨域请求的响应。")。[支持 CORS 的图像](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_Enabled_Image)可以在 [`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas "将 HTML <canvas> 元素与画布脚本 API 或 WebGL API 配合使用以绘制图形和动画。")元素中重复使用,而不会被“污染”。允许的值包括: + 此枚举属性指示在提取资源时,是否必须使用 [CORS](https://developer.mozilla.org/zh-CN/docs/Glossary/CORS "CORS: CORS (跨域资源共享)是一个系统,由传输 HTTP 标头构成,确定浏览器是否阻止前端 JavaScript 代码访问跨域请求的响应。")。[支持 CORS 的图像](https://developer.mozilla.org/zh-CN/docs/Web/HTML/CORS_Enabled_Image)可以在 [`<canvas>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/canvas "将 HTML <canvas> 元素与画布脚本 API 或 WebGL API 配合使用以绘制图形和动画。")元素中重复使用,而不会被“污染”。允许的值包括: `anonymous` -会执行跨域请求(即,使用 [`Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin "源请求标头指示提取源自何处。它不包含任何路径信息,只包含服务器名称。它与 CORS 请求和 POST 请求一同发送。它与 Referer 标头类似,但与此标头不同的是,它不会披露完整路径。") HTTP 标头),但不会发送凭据(即,不发送 Cookie、X.509 证书或 HTTP 基本身份验证)。如果服务器没有通过设置 [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin "Access-Control-Allow-Origin 响应标头指示响应是否可与来自给定源的请求代码一起共享。") HTTP 标头向源站点提供凭据,则图像将被污染,其使用会受到限制。 +会执行跨域请求(即,使用 [`Origin`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Origin "源请求标头指示提取源自何处。它不包含任何路径信息,只包含服务器名称。它与 CORS 请求和 POST 请求一同发送。它与 Referer 标头类似,但与此标头不同的是,它不会披露完整路径。") HTTP 标头),但不会发送凭据(即,不发送 Cookie、X.509 证书或 HTTP 基本身份验证)。如果服务器没有通过设置 [`Access-Control-Allow-Origin`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Origin "Access-Control-Allow-Origin 响应标头指示响应是否可与来自给定源的请求代码一起共享。") HTTP 标头向源站点提供凭据,则图像将被污染,其使用会受到限制。 `use-credentials` -会执行跨域请求(即,带有 `Origin` HTTP 标头),同时发送凭据(即,会发送 Cookie、证书和/或 HTTP 基本身份验证)。如果服务器没有通过 [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials "Access-Control-Allow-Credentials 响应标头指示当请求的证书模式(Request.credentials)为 "include" 时,浏览器是否将响应公开给前端 JavaScript 代码。") HTTP 标头向源站点提供凭据,则资源将被污染,其使用会受到限制。 +会执行跨域请求(即,带有 `Origin` HTTP 标头),同时发送凭据(即,会发送 Cookie、证书和/或 HTTP 基本身份验证)。如果服务器没有通过 [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials "Access-Control-Allow-Credentials 响应标头指示当请求的证书模式(Request.credentials)为 "include" 时,浏览器是否将响应公开给前端 JavaScript 代码。") HTTP 标头向源站点提供凭据,则资源将被污染,其使用会受到限制。 -如果未提供此属性,则提取资源时不使用 [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS: CORS (Cross-Origin Resource Sharing)是一个系统,由传输 HTTP 标头构成,确定浏览器是否阻止前端 JavaScript 代码访问跨域请求的响应。")请求(即,不发送 `Origin` HTTP 标头),从而防止将其用于未污染的场景。如果值无效,则按使用了枚举关键字 anonymous 的情况处理。有关详细信息,请参阅 [CORS 设置属性](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes)。 +如果未提供此属性,则提取资源时不使用 [CORS](https://developer.mozilla.org/zh-CN/docs/Glossary/CORS "CORS: CORS (Cross-Origin Resource Sharing)是一个系统,由传输 HTTP 标头构成,确定浏览器是否阻止前端 JavaScript 代码访问跨域请求的响应。")请求(即,不发送 `Origin` HTTP 标头),从而防止将其用于未污染的场景。如果值无效,则按使用了枚举关键字 anonymous 的情况处理。有关详细信息,请参阅 [CORS 设置属性](https://developer.mozilla.org/zh-CN/docs/Web/HTML/CORS_settings_attributes)。 @@ -307,13 +307,13 @@ Scripts with the `defer` attribute will prevent the `DOMContentLoaded` event fro This attribute must not be used if the `src` attribute is absent (i.e. for inline scripts), in this case it would have no effect. To achieve a similar effect for dynamically inserted scripts use `async="false"` instead. Scripts with the `defer` attribute will execute in the order in which they appear in the document. - 设置此布尔属性是为了向浏览器指示,脚本应在文档解析完成后、但在触发 [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded "/en-US/docs/Web/Events/DOMContentLoaded")之前执行。 + 设置此布尔属性是为了向浏览器指示,脚本应在文档解析完成后、但在触发 [`DOMContentLoaded`](https://developer.mozilla.org/zh-CN/docs/Web/Events/DOMContentLoaded "/zh-CN/docs/Web/Events/DOMContentLoaded")之前执行。 带有 `defer` 属性的脚本会阻止 `DOMContentLoaded` 事件触发,直到该脚本加载并求值完毕。 如果缺少 `src` 属性(即,对于内联脚本),不得使用此属性,因为在这种情况下它不会起作用。 -若要让动态插入的脚本实现类似效果,请改用 `async="false"`。带有 `defer` 属性的脚本会按它们在文档中出现的顺序执行。 +要让动态插入的脚本实现类似效果,请改用 `async="false"`。带有 `defer` 属性的脚本会按它们在文档中出现的顺序执行。 @@ -347,9 +347,9 @@ To achieve a similar effect for dynamically inserted scripts use `async="false"` **注意:** -* 此属性仅适用于[同源 URL](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)。 -* 虽然 HTTP(s) URL 必须是同源,但允许使用 [`blob:` URL](https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL)和 [`data:` URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs),这样 JavaScript 生成的内容,例如图像编辑器 Web 应用中创建的图片,也可以下载。 -* 如果 HTTP 标头 [`Content-Disposition:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition)提供的文件名与此属性不同,则 HTTP 标头优先于此属性。 +* 此属性仅适用于 [同源 URL](https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy)。 +* 虽然 HTTP(s) URL 必须是同源,但允许使用 [`blob:` URL](https://developer.mozilla.org/zh-CN/docs/Web/API/URL.createObjectURL)和 [`data:` URL](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/Data_URIs),这样 JavaScript 生成的内容,例如图像编辑器 Web 应用中创建的图片,也可以下载。 +* 如果 HTTP 标头 [`Content-Disposition:`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition)提供的文件名与此属性不同,则 HTTP 标头优先于此属性。 * 如果 `Content-Disposition:` 设置为 `inline`,Firefox 会像处理文件名那样优先使用 `Content-Disposition`,而 Chrome 会优先使用 `download` 属性。 @@ -358,7 +358,7 @@ To achieve a similar effect for dynamically inserted scripts use `async="false"` * `true`, which indicates that the element may be dragged * `false`, which indicates that the element may not be dragged. - 一个枚举属性,指示是否可使用[拖放 API](https://developer.mozilla.org/docs/DragDrop/Drag_and_Drop)拖动元素。它可以具有以下值: + 一个枚举属性,指示是否可使用 [拖放 API](https://developer.mozilla.org/docs/DragDrop/Drag_and_Drop)拖动元素。它可以具有以下值: * `true`,表示可拖动该元素 * `false`,表示不能拖动该元素。 @@ -370,7 +370,7 @@ To achieve a similar effect for dynamically inserted scripts use `async="false"` * `copy`, which indicates that dropping will create a copy of the element that was dragged * `move`, which indicates that the element that was dragged will be moved to this new location. * `link`, will create a link to the dragged data. - 一个枚举属性,指示可使用[拖放 API](https://developer.mozilla.org/docs/DragDrop/Drag_and_Drop)将哪些类型的内容放在元素上。它可以具有以下值: + 一个枚举属性,指示可使用 [拖放 API](https://developer.mozilla.org/docs/DragDrop/Drag_and_Drop)将哪些类型的内容放在元素上。它可以具有以下值: * `copy`,表示放下时会创建所拖动元素的副本 * `move`,表示所拖动的元素会移动到该新位置 @@ -388,10 +388,10 @@ This value can be overridden by a [`formenctype`](https://developer.mozilla.org/ 当 `method` 属性的值为 `post` 时,enctype 是用于将表单提交到服务器的内容 [MIME 类型](https://en.wikipedia.org/wiki/Mime_type)。可能的值包括: * `application/x-www-form-urlencoded`: 如果未指定该属性,则为默认值。 -* `multipart/form-data`: 该值用于 `type` 属性设置为 "file" 的 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 元素用于为基于 Web 的表单创建交互式控件,,以便接受来自用户的数据;可使用各种类型的输入数据和控件小组件,具体取决于设备和用户代理。")元素。 +* `multipart/form-data`: 该值用于 `type` 属性设置为 "file" 的 [`<input>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input "HTML <input> 元素用于为基于 Web 的表单创建交互式控件,,以便接受来自用户的数据;可使用各种类型的输入数据和控件小组件,具体取决于设备和用户代理。")元素。 * `text/plain`: (HTML5) -此值可被 [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "HTML <button> 元素表示一个可点击的按钮,可以在窗体或文档中任何需要简单、标准按钮功能的地方使用。")或 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 元素用于为基于 Web 的窗体创建交互式控件,以便接受来自用户的数据;可使用各种类型的输入数据和控件小组件,具体取决于设备和用户代理。")元素上的 [`formenctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formenctype)属性覆盖。 +此值可被 [`<button>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/button "HTML <button> 元素表示一个可点击的按钮,可以在窗体或文档中任何需要简单、标准按钮功能的地方使用。")或 [`<input>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input "HTML <input> 元素用于为基于 Web 的窗体创建交互式控件,以便接受来自用户的数据;可使用各种类型的输入数据和控件小组件,具体取决于设备和用户代理。")元素上的 [`formenctype`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/button#attr-formenctype)属性覆盖。 @@ -413,7 +413,7 @@ This value can be overridden by a [`formenctype`](https://developer.mozilla.org/ The [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-id) of a [labelable](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form_labelable) form-related element in the same document as the `<label>` element. The first element in the document with an `id` matching the value of the `for` attribute is the _labeled control_ for this label element, if it is a labelable element. If it is not labelable then the `for` attribute has no effect. If there are other elements which also match the `id` value, later in the document, they are not considered. **Note**: A `<label>` element can have both a `for` attribute and a contained control element, as long as the `for` attribute points to the contained control element. - `<label>` 元素所在文档中[可标记](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form_labelable)表单相关元素的 [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-id)。如果文档中 `id` 与 `for` 属性的值匹配的第一个元素是可标记元素,则该元素是该 label 元素的已标记控件。如果它不是可标记元素,则 `for` 属性不起作用。如果还有其他元素也与 `id` 值匹配,则在文档的后面部分,不会考虑这些元素。 + `<label>` 元素所在文档中 [可标记](https://developer.mozilla.org/zh-CN/docs/Web/Guide/HTML/Content_categories#Form_labelable)表单相关元素的 [`id`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes#attr-id)。如果文档中 `id` 与 `for` 属性的值匹配的第一个元素是可标记元素,则该元素是该 label 元素的已标记控件。如果它不是可标记元素,则 `for` 属性不起作用。如果还有其他元素也与 `id` 值匹配,则在文档的后面部分,不会考虑这些元素。 **注意**: 只要 `for` 属性指向所包含的控件元素。,`<label>` 元素就可同时具有 `for` 元素和所包含的控件元素。 @@ -439,10 +439,10 @@ If this attribute is specified, it overrides the [`enctype`](https://developer.m 如果按钮是提交按钮,则此属性指定用于将表单提交到服务器的内容类型。可能的值包括: * `application/x-www-form-urlencoded`: 未指定该属性时的默认值。 -* `multipart/form-data`: 如果使用的是 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 元素用于为基于 Web 的表单创建交互式控件,以便接收来自用户的数据;可用的输入数据和控件小组件类型很多,具体取决于设备和用户代理。")元素,并将 [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-type)属性设置为 `file` 时,请使用此值。 +* `multipart/form-data`: 如果使用的是 [`<input>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input "HTML <input> 元素用于为基于 Web 的表单创建交互式控件,以便接收来自用户的数据;可用的输入数据和控件小组件类型很多,具体取决于设备和用户代理。")元素,并将 [`type`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input#attr-type)属性设置为 `file` 时,请使用此值。 * `text/plain` -如果指定此属性,它会覆盖按钮所属表单的 [`enctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-enctype)属性。 +如果指定此属性,它会覆盖按钮所属表单的 [`enctype`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/form#attr-enctype)属性。 @@ -457,7 +457,7 @@ If specified, this attribute overrides the [`method`](https://developer.mozilla. * `post`: 表单中的数据会包含在表单正文中并发送到服务器。 * `get`: 表单中的数据会追加到 form 属性 URI 中,并以 '?' 作为分隔符,然后将生成的 URI 发送到服务器。当表单没有副作用且仅包含 ASCII 字符时,请使用此方法。 -如果指定此属性,它会覆盖按钮所属表单的 [`method`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-method)属性。 +如果指定此属性,它会覆盖按钮所属表单的 [`method`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/form#attr-method)属性。 @@ -472,7 +472,7 @@ If specified, this attribute overrides the [`method`](https://developer.mozilla. * `_blank`: Load the response into a new unnamed browsing context. * `_parent`: Load the response into the parent browsing context of the current one. If there is no parent, this option behaves the same way as `_self`. * `_top`: Load the response into the top-level browsing context (that is, the browsing context that is an ancestor of the current one, and has no parent). If there is no parent, this option behaves the same way as `_self`. - 如果按钮是提交按钮,则此属性是一个名称或关键字,用于指示提交表单后接收到的响应显示在何处。这是一个浏览上下文(例如,选项卡、窗口或内联框架)的名称或关键字。如果指定此属性,它会覆盖按钮所属表单的 [`target`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-target)属性。以下关键字具有特殊含义: + 如果按钮是提交按钮,则此属性是一个名称或关键字,用于指示提交表单后接收到的响应显示在何处。这是一个浏览上下文(例如,选项卡、窗口或内联框架)的名称或关键字。如果指定此属性,它会覆盖按钮所属表单的 [`target`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/form#attr-target)属性。以下关键字具有特殊含义: * `_self`: 将响应加载到与当前相同的浏览上下文中。如果未指定该属性,这是默认值。 * `_blank`: 将响应加载到新的未命名浏览上下文中。 @@ -533,27 +533,27 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page 定义 pragma 指令。该属性之所以命名为 `**http-equiv**(alent)`,是因为所有允许的值都是特定 HTTP 标头的名称: * `"content-language"` -定义页面的默认语言。它可以被任何元素上的 [lang](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang)属性覆盖。 +定义页面的默认语言。它可以被任何元素上的 [lang](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/lang)属性覆盖。 -**警告:** 请勿使用此值,因为它已过时。请改用 [`<html>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html "HTML <html> 元素表示 HTML 文档的根(顶级元素),因为它也作为根元素引用。所有其他元素必须是该元素的后代。")元素上的 `lang` 属性。 +**警告:** 请勿使用此值,因为它已过时。请改用 [`<html>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/html "HTML <html> 元素表示 HTML 文档的根(顶级元素),因为它也作为根元素引用。所有其他元素必须是该元素的后代。")元素上的 `lang` 属性。 * `"content-security-policy"` -允许页面作者为当前页面定义[内容策略](https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives)。内容策略主要指定允许的服务器源和脚本终结点,以帮助防范跨站点脚本攻击。 +允许页面作者为当前页面定义 [内容策略](https://developer.mozilla.org/zh-CN/docs/Web/Security/CSP/CSP_policy_directives)。内容策略主要指定允许的服务器源和脚本终结点,以帮助防范跨站点脚本攻击。 * `"content-type"` -定义文档的 [MIME 类型](https://developer.mozilla.org/en-US/docs/Glossary/MIME_type),并附带其字符编码。它遵循与 HTTP `content-type` 实体标头字段相同的语法,但由于它位于 HTML 页面内,因此除 `text/html` 之外的大多数值都不可能有效。因此,其 `content` 的有效语法是字符串 `text/html`,后跟字符集,语法为 `; charset=_IANAcharset_`,其中 `IANAcharset` 是 [IANA 定义的](https://www.iana.org/assignments/character-sets)字符集的首选 MIME 名称。 +定义文档的 [MIME 类型](https://developer.mozilla.org/zh-CN/docs/Glossary/MIME_type),并附带其字符编码。它遵循与 HTTP `content-type` 实体标头字段相同的语法,但由于它位于 HTML 页面内,因此除 `text/html` 之外的大多数值都不可能有效。因此,其 `content` 的有效语法是字符串 `text/html`,后跟字符集,语法为 `; charset=_IANAcharset_`,其中 `IANAcharset` 是 [IANA 定义的](https://www.iana.org/assignments/character-sets)字符集的首选 MIME 名称。 -**警告:** 请勿使用此值,因为它已过时。请改用 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> 元素表示不能由其他 HTML 元相关元素(如 <base>、<link><script>、<style> 或 <title>)表示的元数据。")元素上的 [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset)属性。 +**警告:** 请勿使用此值,因为它已过时。请改用 [`<meta>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/meta "HTML <meta> 元素表示不能由其他 HTML 元相关元素(如 <base>、<link><script>、<style> 或 <title>)表示的元数据。")元素上的 [`charset`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/meta#attr-charset)属性。 -**注意:** 由于 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> 元素表示不能由其他 HTML 元相关元素(如 <base>, <link><script>、<style> 或 <title>)表示的元数据。")无法在 XHTML 或 HTML5 的 XHTML 序列化中更改文档类型,因此切勿通过 `<meta>` 将 MIME 类型设置为 XHTML MIME 类型。 +**注意:** 由于 [`<meta>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/meta "HTML <meta> 元素表示不能由其他 HTML 元相关元素(如 <base>, <link><script>、<style> 或 <title>)表示的元数据。")无法在 XHTML 或 HTML5 的 XHTML 序列化中更改文档类型,因此切勿通过 `<meta>` 将 MIME 类型设置为 XHTML MIME 类型。 * `"refresh"` 此指令指定: -* 页面应重新加载之前等待的秒数 - 仅当 [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content)属性包含正整数时。 -* 页面应重定向到其他页面之前等待的秒数 - 仅当 [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content)属性包含一个正整数,后跟字符串 `;url=` 和有效 URL 时。 +* 页面应重新加载之前等待的秒数 - 仅当 [`content`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/meta#attr-content)属性包含正整数时。 +* 页面应重定向到其他页面之前等待的秒数 - 仅当 [`content`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/meta#attr-content)属性包含一个正整数,后跟字符串 `;url=` 和有效 URL 时。 * `"set-cookie"` -为页面定义一个 [Cookie](https://developer.mozilla.org/en-US/docs/cookie)。其内容必须遵循 [IETF HTTP Cookie 规范](https://tools.ietf.org/html/draft-ietf-httpstate-cookie-14)中定义的语法。 +为页面定义一个 [Cookie](https://developer.mozilla.org/zh-CN/docs/cookie)。其内容必须遵循 [IETF HTTP Cookie 规范](https://tools.ietf.org/html/draft-ietf-httpstate-cookie-14)中定义的语法。 -**警告:** 请勿使用此指令,因为它已过时。请改用 HTTP 标头 [`Set-Cookie`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie)。 +**警告:** 请勿使用此指令,因为它已过时。请改用 HTTP 标头 [`Set-Cookie`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie)。 @@ -587,7 +587,7 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page **Note:** This attribute is allowed only if the `<img>` element is a descendant of an [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "The HTML <a> element (or anchor element) creates a hyperlink to other web pages, files, locations within the same page, email addresses, or any other URL.") element with a valid [`href`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-href) attribute. 此布尔属性指示图像是服务器端映射的一部分。如果是,点击的精确坐标会发送到服务器。 -**注意:** 只有当 `<img>` 元素是带有有效 [`href`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-href)属性的 [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "HTML <a> 元素(或 anchor 元素)会创建指向其他网页、文件、同一页面内的位置、电子邮件地址或任何其他 URL 的超链接")元素的后代时,才允许使用此属性。 +**注意:** 只有当 `<img>` 元素是带有有效 [`href`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/a#attr-href)属性的 [`<a>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/a "HTML <a> 元素(或 anchor 元素)会创建指向其他网页、文件、同一页面内的位置、电子邮件地址或任何其他 URL 的超链接")元素的后代时,才允许使用此属性。 @@ -692,12 +692,12 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page * In HTML 4, this can only be a simple white-space-separated list of media description literals, i.e., [media types and groups](https://developer.mozilla.org/en-US/docs/Web/CSS/@media), where defined and allowed as values for this attribute, such as `print`, `screen`, `aural`, `braille`. HTML5 extended this to any kind of [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries), which are a superset of the allowed values of HTML 4. * Browsers not supporting [CSS3 Media Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries) won't necessarily recognize the adequate link; do not forget to set fallback links, the restricted set of media queries defined in HTML 4. - 此属性指定链接资源适用的媒体。其值必须是媒体类型/[媒体查询](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries)。此属性在链接到外部样式表时尤其有用 - 它允许用户代理为其运行的设备选择最合适的样式表。 + 此属性指定链接资源适用的媒体。其值必须是媒体类型/[媒体查询](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Media_queries)。此属性在链接到外部样式表时尤其有用 - 它允许用户代理为其运行的设备选择最合适的样式表。 **注意:** -* 在 HTML 4 中,这只能是以空白分隔的简单媒体描述字面量列表,即此属性定义并允许作为值的[媒体类型和组](https://developer.mozilla.org/en-US/docs/Web/CSS/@media),例如 `print`、`screen`、`aural`、`braille`。HTML5 将其扩展为任何类型的[媒体查询](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries),后者是 HTML 4 允许值的超集。 -* 不支持 [CSS3 媒体查询](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries)的浏览器不一定能识别合适的链接 - 不要忘记设置回退链接,也就是 HTML 4 中定义的受限媒体查询集。 +* 在 HTML 4 中,这只能是以空白分隔的简单媒体描述字面量列表,即此属性定义并允许作为值的[媒体类型和组](https://developer.mozilla.org/zh-CN/docs/Web/CSS/@media),例如 `print`、`screen`、`aural`、`braille`。HTML5 将其扩展为任何类型的[媒体查询](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Media_queries),后者是 HTML 4 允许值的超集。 +* 不支持 [CSS3 媒体查询](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Media_queries)的浏览器不一定能识别合适的链接 - 不要忘记设置回退链接,也就是 HTML 4 中定义的受限媒体查询集。 @@ -708,13 +708,13 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page * `dialog`: Use when the form is inside a [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog "The HTML <dialog> element represents a dialog box or other interactive component, such as an inspector or window.") element to close the dialog when submitted. This value can be overridden by a [`formmethod`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formmethod) attribute on a [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "The HTML <button> element represents a clickable button, which can be used in forms or anywhere in a document that needs simple, standard button functionality.") or [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") element. - 浏览器用于提交表单的 [HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)方法。可选值包括: + 浏览器用于提交表单的 [HTTP](https://developer.mozilla.org/zh-CN/docs/Web/HTTP)方法。可选值包括: * `post`: 对应 HTTP [POST 方法](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5);表单数据包含在表单正文中并发送到服务器。 * `get`: 对应 HTTP [GET 方法](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3);表单数据会追加到 `action` 属性 URI 后面,以 '?' 作为分隔符,然后将生成的 URI 发送到服务器。当表单没有副作用且仅包含 ASCII 字符时,请使用此方法。 -* `dialog`: 当表单位于 [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog "HTML <dialog> 元素表示对话框或其他交互式组件,例如检查器或窗口。")元素中时使用,以便在提交时关闭对话框。 +* `dialog`: 当表单位于 [`<dialog>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/dialog "HTML <dialog> 元素表示对话框或其他交互式组件,例如检查器或窗口。")元素中时使用,以便在提交时关闭对话框。 -此值可被 [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "HTML <button> 元素表示一个可点击的按钮,可以在窗体或文档中任何需要简单、标准按钮功能的地方使用。")或 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 元素用于为基于 Web 的表单创建交互式控件,以便接受来自用户的数据;可用的输入数据和控件小组件类型很多,具体取决于设备和用户代理。")元素上的 [`formmethod`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formmethod)属性覆盖。 +此值可被 [`<button>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/button "HTML <button> 元素表示一个可点击的按钮,可以在窗体或文档中任何需要简单、标准按钮功能的地方使用。")或 [`<input>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input "HTML <input> 元素用于为基于 Web 的表单创建交互式控件,以便接受来自用户的数据;可用的输入数据和控件小组件类型很多,具体取决于设备和用户代理。")元素上的 [`formmethod`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/button#attr-formmethod)属性覆盖。 @@ -1517,7 +1517,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo **Note:** Placeholders should only be used to show an example of the type of data that should be entered into a form; they are _not_ a substitute for a proper [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label "The HTML <label> element represents a caption for an item in a user interface.") element tied to the input. See [Labels and placeholders](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Labels_and_placeholders "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") in [<input>: The Input (Form Input) element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") for a full explanation. 向用户提示可在控件中输入的内容。在呈现此提示时,占位符文本中的回车符或换行符必须被视为换行符。 -**注意:** 占位符只能用于显示应输入到表单中的数据类型示例;它们不能替代与输入关联的正确 [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label "HTML <label> 元素表示用户界面中某个项目的标题。")元素。请参阅 [<input>: Input (表单输入)元素](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 元素用于为基于 Web 的表单创建交互式控件;以便接收来自用户的数据;可用的输入数据和控件小组件类型很多,具体取决于设备和用户代理。")中的[标签和占位符](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Labels_and_placeholders "HTML <input> 元素用于为基于 Web 的表单创建交互式控件;以便接收来自用户的数据;可用的输入数据和控件小组件类型很多,具体取决于设备和用户代理。"),获取完整说明。 +**注意:** 占位符只能用于显示应输入到表单中的数据类型示例;它们不能替代与输入关联的正确 [`<label>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/label "HTML <label> 元素表示用户界面中某个项目的标题。")元素。请参阅 [<input>: Input (表单输入)元素](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input "HTML <input> 元素用于为基于 Web 的表单创建交互式控件;以便接收来自用户的数据;可用的输入数据和控件小组件类型很多,具体取决于设备和用户代理。")中的 [标签和占位符](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input#Labels_and_placeholders "HTML <input> 元素用于为基于 Web 的表单创建交互式控件;以便接收来自用户的数据;可用的输入数据和控件小组件类型很多,具体取决于设备和用户代理。"),获取完整说明。 @@ -1545,8 +1545,8 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo * `unsafe-url` means that the referrer will include the origin and the path (but not the fragment, password, or username). This case is unsafe because it can leak origins and paths from TLS-protected resources to insecure origins. 指示在提取资源时要使用哪个引用来源的字符串: -* `no-referrer` 表示不会发送 [`Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer "Referer 请求标头包含上一个网页的地址,用户通过该网页上的链接访问到当前请求的页面。Referer 标头允许服务器用户正在从哪里访问它们,并可将该数据用于分析、日志记录或优化缓存等用途。")标头。 -* `no-referrer-when-downgrade` 表示在导航到不使用 TLS (HTTPS)的源时,不会发送 [`Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer "Referer 请求标头包含上一个网页的地址,用户通过该网页上的链接访问到当前请求的页面。Referer 标头允许服务器用户正在从哪里访问它们,并可将该数据用于分析、日志记录或优化缓存等用途。")标头。如果未指定其他策略,这是用户代理的默认行为。 +* `no-referrer` 表示不会发送 [`Referer`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Referer "Referer 请求标头包含上一个网页的地址,用户通过该网页上的链接访问到当前请求的页面。Referer 标头允许服务器用户正在从哪里访问它们,并可将该数据用于分析、日志记录或优化缓存等用途。")标头。 +* `no-referrer-when-downgrade` 表示在导航到不使用 TLS (HTTPS)的源时,不会发送 [`Referer`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Referer "Referer 请求标头包含上一个网页的地址,用户通过该网页上的链接访问到当前请求的页面。Referer 标头允许服务器用户正在从哪里访问它们,并可将该数据用于分析、日志记录或优化缓存等用途。")标头。如果未指定其他策略,这是用户代理的默认行为。 * `origin` 表示引用来源将是页面的源,大致包括方案、主机和端口。 * `origin-when-cross-origin` 表示在导航到其他源时,仅使用方案、主机和端口;而在同一源内导航时,则会包含引用来源的路径。 * `unsafe-url` 表示引用来源将包含源和路径,但不包括片段、密码或用户名。此情况不安全,因为它可能会将受 TLS 保护的资源的源和路径泄露到不安全的源。 @@ -1596,15 +1596,15 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo 对框架中的内容应用额外限制。该属性的值可以为空,以应用所有限制;也可使用以空格分隔的标记来取消特定限制: * `allow-forms`: 允许资源提交表单。如果不使用此关键字,则会阻止表单提交。 -* `allow-modals`: 允许资源[打开模态窗口](https://html.spec.whatwg.org/multipage/origin.html#sandboxed-modals-flag)。 -* `allow-orientation-lock`: 允许资源[锁定屏幕方向](https://developer.mozilla.org/en-US/docs/Web/API/Screen/lockOrientation)。 -* `allow-pointer-lock`: 允许资源使用[指针锁 API](https://developer.mozilla.org/en-US/docs/WebAPI/Pointer_Lock)。 +* `allow-modals`: 允许资源 [打开模态窗口](https://html.spec.whatwg.org/multipage/origin.html#sandboxed-modals-flag)。 +* `allow-orientation-lock`: 允许资源 [锁定屏幕方向](https://developer.mozilla.org/zh-CN/docs/Web/API/Screen/lockOrientation)。 +* `allow-pointer-lock`: 允许资源使用[指针锁 API](https://developer.mozilla.org/zh-CN/docs/WebAPI/Pointer_Lock)。 * `allow-popups`: 允许弹出窗口,例如 `window.open()`、`target="_blank"` 或 `showModalDialog()`。如果不使用此关键字,弹出窗口会打开失败且不显示任何提示。 * `allow-popups-to-escape-sandbox`: 允许沙盒文档打开新窗口,而这些窗口不会继承沙盒限制。例如,可以安全地对广告进行沙盒处理,而不必对广告链接到的页面施加相同限制。 -* `allow-presentation`: 允许资源启动[演示会话](https://developer.mozilla.org/en-US/docs/Web/API/PresentationRequest)。 -* `allow-same-origin`: 如果不使用此标记,资源会被视为来自特殊源,并且始终无法通过[同源策略](https://developer.mozilla.org/en-US/docs/Glossary/same-origin_policy "同源策略: 同源策略是一项关键的安全机制,用于限制从一个源加载的文档或脚本与来自另一个源的资源进行交互的方式。")。 +* `allow-presentation`: 允许资源启动 [演示会话](https://developer.mozilla.org/zh-CN/docs/Web/API/PresentationRequest)。 +* `allow-same-origin`: 如果不使用此标记,资源会被视为来自特殊源,并且始终无法通过[同源策略](https://developer.mozilla.org/zh-CN/docs/Glossary/same-origin_policy "同源策略: 同源策略是一项关键的安全机制,用于限制从一个源加载的文档或脚本与来自另一个源的资源进行交互的方式。")。 * `allow-scripts`: 允许资源运行脚本,但不能创建弹出窗口。 -* `allow-storage-access-by-user-activation`: 允许资源通过[存储访问 API](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API)请求访问父级的存储功能。 +* `allow-storage-access-by-user-activation`: 允许资源通过 [存储访问 API](https://developer.mozilla.org/zh-CN/docs/Web/API/Storage_Access_API)请求访问父级的存储功能。 * `allow-top-navigation`: 允许资源导航顶级浏览上下文(即,名为 `_top` 的上下文)。 * `allow-top-navigation-by-user-activation`: 允许资源导航顶级浏览上下文,但前提是由用户手势触发。 @@ -1619,14 +1619,14 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo This attribute defines the scheme in which metadata is described. A scheme is a context leading to the correct interpretations of the [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) value, like a format. **Warning:** Do not use this value, as it is obsolete. There is no replacement as there was no real usage for it. - 此属性定义元数据的描述方案。方案是一种上下文,用于正确解释 [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content)值,例如某种格式。 + 此属性定义元数据的描述方案。方案是一种上下文,用于正确解释 [`content`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/meta#attr-content)值,例如某种格式。 **警告:** 请勿使用此值,因为它已经过时。由于它实际上从未被使用,因此没有替代方案。 If present, this Boolean attribute indicates that the option is initially selected. If the `<option>` element is the descendant of a [`<select>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select "The HTML <select> element represents a control that provides a menu of options") element whose [`multiple`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#attr-multiple) attribute is not set, only one single `<option>` of this [`<select>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select "The HTML <select> element represents a control that provides a menu of options") element may have the `selected` attribute. - 如果存在,此布尔特性会指示最初选择了该选项。如果 `<option>` 元素是未设置 [`multiple`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#attr-multiple)特性的 [`<select>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select "HTML <select> 元素表示一个提供选项菜单的控件")元素的后代,则此 [`<select>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select "HTML <select> 元素表示一个提供选项菜单的控件")元素中只能有一个 `<option>` 具有 `selected` 特性。 + 如果存在,此布尔特性会指示最初选择了该选项。如果 `<option>` 元素是未设置 [`multiple`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/select#attr-multiple)特性的 [`<select>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/select "HTML <select> 元素表示一个提供选项菜单的控件")元素的后代,则此 [`<select>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/select "HTML <select> 元素表示一个提供选项菜单的控件")元素中只能有一个 `<option>` 具有 `selected` 特性。 @@ -1645,12 +1645,12 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo * a white-space separated list of sizes, each in the format `_<width in pixels>_x_<height in pixels>_` or `_<width in pixels>_X_<height in pixels>_`. Each of these sizes must be contained in the resource. **Note:** Most icon formats are only able to store one single icon; therefore most of the time the [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-sizes) contains only one entry. MS's ICO format does, as well as Apple's ICNS. ICO is more ubiquitous; you should definitely use it. - 此属性定义资源中包含的视觉媒体图标的大小。只有当 [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-rel)包含 `icon` 或非标准类型(例如 Apple 的 `apple-touch-icon`)的值时,才应包含此属性。它可以具有以下值: + 此属性定义资源中包含的视觉媒体图标的大小。只有当 [`rel`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/link#attr-rel)包含 `icon` 或非标准类型(例如 Apple 的 `apple-touch-icon`)的值时,才应包含此属性。它可以具有以下值: * `any`,表示图标采用矢量格式时可缩放到任意大小,例如 `image/svg+xml`。 * 以空格分隔的大小列表,每个大小的格式为 `_<width in pixels>_x_<height in pixels>_` 或 `_<width in pixels>_X_<height in pixels>_`。这些大小都必须在资源中存在。 -**注意:** 大多数图标格式只能存储一个图标,因此在大多数情况下,[`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-sizes)只包含一个条目。MS 的 ICO 格式和 Apple 的 ICNS 都支持这一点。ICO 更常见,建议优先使用。 +**注意:** 大多数图标格式只能存储一个图标,因此在大多数情况下,[`sizes`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes#attr-sizes)只包含一个条目。MS 的 ICO 格式和 Apple 的 ICNS 都支持这一点。ICO 更常见,建议优先使用。 @@ -1712,16 +1712,16 @@ The user agent selects any one of the available sources at its discretion. This 在同一个 `srcset` 属性中混用宽度描述符和像素密度描述符属于错误行为。重复的描述符(例如,同一个 `srcset` 中两个都标有 `2x` 的源)也无效。 -用户代理可自行选择任意一个可用源。这让它们在很大程度上可根据用户偏好或带宽状况等因素灵活调整选择。有关示例,请参阅我们的[响应式图片](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images)教程。 +用户代理可自行选择任意一个可用源。这让它们在很大程度上可根据用户偏好或带宽状况等因素灵活调整选择。有关示例,请参阅我们的[响应式图片](https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images)教程。 This integer attribute specifies the start value for numbering the individual list items. Although the ordering type of list elements might be Roman numerals, such as XXXI, or letters, the value of start is always represented as a number. To start numbering elements from the letter "C", use `<ol start="3">`. **Note**: This attribute was deprecated in HTML4, but reintroduced in HTML5. - 此整数属性指定单个列表项编号的起始值。尽管列表元素的排序类型可能是罗马数字(如 XXXI)或字母,但 start 的值始终以数字表示。若要从字母 "C" 开始编号元素,请使用 `<ol start="3">`。 + 此整数属性指定单个列表项编号的起始值。尽管列表元素的排序类型可能是罗马数字(如 XXXI)或字母,但 start 的值始终以数字表示。要从字母 "C" 开始编号元素,请使用 `<ol start="3">`。 -**注意**: 此属性在 HTML4 中已废弃,但在 HTML5 中重新引入。 +**注意**: 此属性在 HTML4 中已弃用,但在 HTML5 中重新引入。 @@ -1786,9 +1786,9 @@ If this attribute is specified, this element must come before any other elements The partial URL (starting with '#') of an [image map](https://developer.mozilla.org/en-US/docs/HTML/Element/map) associated with the element. **Note:** You cannot use this attribute if the `<img>` element is a descendant of an [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "The HTML <a> element (or anchor element) creates a hyperlink to other web pages, files, locations within the same page, email addresses, or any other URL.") or [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "The HTML <button> element represents a clickable button, which can be used in forms or anywhere in a document that needs simple, standard button functionality.") element. - 与元素关联的[图像映射](https://developer.mozilla.org/en-US/docs/HTML/Element/map)的部分URL (以 "#" 开头)。 + 与元素关联的[图像映射](https://developer.mozilla.org/zh-CN/docs/HTML/Element/map)的部分URL (以 "#" 开头)。 -**注意:** 如果 `<img>` 元素是 [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "HTML <a> 元素(或 anchor 元素)创建指向其他网页、文件、同一页面内的位置、电子邮件地址或任何其他 URL 的超链接")或 [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "HTML <button> 元素表示一个可点击的按钮,可以在窗体或文档中任何需要简单、标准按钮功能的地方使用。")元素的后代,则不能使用此属性。 +**注意:** 如果 `<img>` 元素是 [`<a>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/a "HTML <a> 元素(或 anchor 元素)创建指向其他网页、文件、同一页面内的位置、电子邮件地址或任何其他 URL 的超链接")或 [`<button>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/button "HTML <button> 元素表示一个可点击的按钮,可以在窗体或文档中任何需要简单、标准按钮功能的地方使用。")元素的后代,则不能使用此属性。 @@ -1797,7 +1797,7 @@ If this attribute is specified, this element must come before any other elements **Note**: This attribute was deprecated in HTML4, but reintroduced in HTML5. **Note:** Prior to Gecko 9.0, negative values were incorrectly converted to 0. Starting in Gecko 9.0 all integer values are correctly parsed. - 此整数属性表示由 [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol "HTML <ol> 元素表示一个有序的项目列表,通常以项目符号列表形式呈现。")元素定义的列表项当前序号。此属性唯一允许的值是数字,即使列表显示为罗马数字或字母也是如此。其后的列表项会从此值集继续编号。value 属性对无序列表([`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul "HTML <ul> 元素表示一个无序的项目列表,通常以项目符号列表形式呈现。"))或菜单([`<menu>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu "HTML <menu> 元素表示一组用户可执行或激活的命令。其中包含列表菜单(可能跨屏幕顶部显示)和上下文菜单(例如单击后可能显示在按钮下方的上下文菜单)。"))来说没有意义。 + 此整数属性表示由 [`<ol>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/ol "HTML <ol> 元素表示一个有序的项目列表,通常以项目符号列表形式呈现。")元素定义的列表项当前序号。此属性唯一允许的值是数字,即使列表显示为罗马数字或字母也是如此。其后的列表项会从此值集继续编号。value 属性对无序列表([`<ul>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/ul "HTML <ul> 元素表示一个无序的项目列表,通常以项目符号列表形式呈现。"))或菜单([`<menu>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/menu "HTML <menu> 元素表示一组用户可执行或激活的命令。其中包含列表菜单(可能跨屏幕顶部显示)和上下文菜单(例如单击后可能显示在按钮下方的上下文菜单)。"))来说没有意义。 **注意**: 此属性在 HTML4 中已弃用,但在 HTML5 中重新引入。 @@ -1814,7 +1814,7 @@ If this attribute is specified, this element must come before any other elements * data: 默认值。该值作为字符串传递给对象的实现。 * ref: 该值是指向存储运行时值的资源的 URI。 -* object: 同一文档中另一个 [`<object>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object "HTML <object> 元素表示外部资源,可将其视为图像、嵌套浏览上下文或由插件处理的资源。")的 ID。 +* object: 同一文档中另一个 [`<object>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/object "HTML <object> 元素表示外部资源,可将其视为图像、嵌套浏览上下文或由插件处理的资源。")的 ID。 diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.zh-Hant.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.zh-Hant.xlf index 7a81ee6c813dd..b7f2ac0e62cf9 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.zh-Hant.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/HtmlDescriptions.zh-Hant.xlf @@ -43,7 +43,7 @@ In previous versions of HTML, the different character encodings could be delimit **使用方式注意事項** -* **請勿使用此屬性**,因為它已被棄用。[`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table "The HTML <table> element represents tabular data — that is, information presented in a two-dimensional table comprised of rows and columns of cells containing data.") 元素應使用 [CSS](https://developer.mozilla.org/en-US/docs/CSS) 進行樣式設定。將 [`margin-left`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left "margin-left CSS 屬性會設定元素左側的邊界區域。正值會使其離鄰居更遠,負值則會使其更靠近。") 和 [`margin-right`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right "margin-right CSS 屬性會設定元素右側的邊界區域。正值會使其離鄰居更遠,負值則會使其更靠近。") 設為 `auto`,或將 [`margin`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin "margin CSS 屬性會設定元素四邊的邊界區域。這是 margin-top、margin-right、margin-bottom 和 margin-left 的速記。") 到 `0 auto`,以達到類似 align 屬性的效果。 +* **請勿使用此屬性**,因為它已被棄用。[`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table "The HTML <table> element represents tabular data — that is, information presented in a two-dimensional table comprised of rows and columns of cells containing data.") 元素應使用 [CSS](https://developer.mozilla.org/en-US/docs/CSS) (英文) 進行樣式設定。將 [`margin-left`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left "margin-left CSS 屬性會設定元素左側的邊界區域。正值會使其離鄰居更遠,負值則會使其更靠近。") 和 [`margin-right`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right "margin-right CSS 屬性會設定元素右側的邊界區域。正值會使其離鄰居更遠,負值則會使其更靠近。") 設為 `auto`,或將 [`margin`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin "margin CSS 屬性會設定元素四邊的邊界區域。這是 margin-top、margin-right、margin-bottom 和 margin-left 的速記。") 到 `0 auto`,以達到類似 align 屬性的效果。 * 在 Firefox 4 之前,Firefox 也僅支援 quirks 模式中的 `middle`、`absmiddle` 和 `abscenter` 值做為 `center` 的同義字。 @@ -55,7 +55,7 @@ In previous versions of HTML, the different character encodings could be delimit Set to `true` if the `<iframe>` can activate fullscreen mode by calling the [`requestFullscreen()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen "The Element.requestFullscreen() method issues an asynchronous request to make the element be displayed in full-screen mode.") method. This attribute is considered a legacy attribute and redefined as `allow="fullscreen"`. - 如果 `<iframe>` 可透過呼叫 [`requestFullscreen()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen "The Element.requestFullscreen() method issues an asynchronous request to make the element be displayed in full-screen mode.") 方法啟動全螢幕模式,請設為 `true`。 + 如果 `<iframe>` 可透過呼叫 [`requestFullscreen()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen "Element.requestFullscreen() 方法會發出一個非同步要求,使該元素以全螢幕模式顯示。") 方法啟動全螢幕模式,請設為 `true`。 此屬性視為舊版屬性,已重新定義為 `allow="fullscreen"`。 @@ -67,7 +67,7 @@ This attribute is considered a legacy attribute and redefined as `allow="fullscr **Note:** Omitting this attribute altogether indicates that the image is a key part of the content, and no textual equivalent is available. Setting this attribute to an empty string (`alt=""`) indicates that this image is _not_ a key part of the content (decorative), and that non-visual browsers may omit it from rendering. 此屬性定義影像的替代文字描述。 -**注意:** 瀏覽器不一定顯示元素所參照的影像。在非圖形瀏覽器中 (包括視覺障礙者使用的瀏覽器),如果使用者選擇不顯示影像,或瀏覽器因影像無效或屬於 [不支援的類型](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/img#Supported_image_formats) 而無法顯示影像時,就會發生這種情況。在這些情況下,瀏覽器可能會以此元素 `alt` 屬性中定義的文字取代影像。由於這些及其他原因,您應該盡可能為 `alt` 提供有用的值。 +**注意:** 瀏覽器不一定顯示元素所參照的影像。在非圖形瀏覽器中 (包括視覺障礙者使用的瀏覽器),如果使用者選擇不顯示影像,或瀏覽器因影像無效或屬於[不支援的類型](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/img#Supported_image_formats) (英文) 而無法顯示影像時,就會發生這種情況。在這些情況下,瀏覽器可能會以此元素 `alt` 屬性中定義的文字取代影像。由於這些及其他原因,您應該盡可能為 `alt` 提供有用的值。 **注意:** 完全省略此屬性表示影像是內容的重要部分,而且沒有文字對等項目可用。將這個屬性設為空字串 (`alt=""`) 表示這個影像不是內容的重要部分 (屬於裝飾性),而非視覺瀏覽器可能會在呈現時省略它。 @@ -93,9 +93,9 @@ See [Browser compatibility](https://developer.mozilla.org/docs/Web/HTML/Referenc 瀏覽器通常會採用最壞情況,並在 HTML 剖析期間同步載入指令碼 (也就是 `async="false"`)。 -動態插入的指令碼 (使用 [`document.createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement "在 HTML 文件中,document.createElement() 方法會建立由 tagName 指定的 HTML 元素;如果 tagName 無法辨識,則會建立 HTMLUnknownElement。")) 預設會以非同步方式載入,因此若要啟用同步載入 (也就是讓指令碼依插入順序載入),請設定 `async="false"`。 +動態插入的指令碼 (使用 [`document.createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement "在 HTML 文件中,document.createElement() 方法會建立由 tagName 指定的 HTML 元素; 如果 tagName 無法辨識,則會建立 HTMLUnknownElement。")) 預設會以非同步方式載入,因此若要啟用同步載入 (也就是讓指令碼依插入順序載入),請設定 `async="false"`。 -如需瀏覽器支援的注意事項,請參閱 [瀏覽器相容性](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/script#Browser_compatibility)。另請參閱 [asm.js 的非同步指令碼](https://developer.mozilla.org/en-US/docs/Games/Techniques/Async_scripts)。 +如需瀏覽器支援的注意事項,請參閱[瀏覽器相容性](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/script#Browser_compatibility) (英文)。另請參閱 [asm.js 的非同步指令碼](https://developer.mozilla.org/en-US/docs/Games/Techniques/Async_scripts) (英文)。 @@ -123,11 +123,11 @@ For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) sett **Note:** If you set `autocomplete` to `off` in a form because the document provides its own auto-completion, then you should also set `autocomplete` to `off` for each of the form's `input` elements that the document can auto-complete. For details, see the note regarding Google Chrome in the [Browser Compatibility chart](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form#compatChart). 指出輸入元素在預設情況下,瀏覽器是否可以自動完成其值。表單中的元素可以使用 `autocomplete` 屬性覆寫此設定。可能的值為: -* `off`: 使用者必須在每個欄位中明確輸入值以供每次使用,或者文字提供自己的自動完成方法;瀏覽器不會自動完成輸入項目。 +* `off`: 使用者必須在每個欄位中明確輸入值以供每次使用,或者文字提供自己的自動完成方法; 瀏覽器不會自動完成輸入項目。 * `on`: 瀏覽器可以根據使用者先前在表單中輸入的值自動完成值。 -對於大多數現代瀏覽器 (包括 Firefox 38+、Google Chrome 34+、IE 11+),將 autocomplete 屬性設為 off 不會阻止瀏覽器的密碼管理員詢問使用者是否要儲存登入欄位 (使用者名稱和密碼)。如果使用者允許儲存,瀏覽器會在下次使用者造訪頁面時自動填入登入資訊。請參閱 [自動完成屬性和登入欄位](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion#The_autocomplete_attribute_and_login_fields)。 -**注意:** 如果您在表單中將 `autocomplete` 設定為 `off`,因為文件會提供自己的自動完成,則您也應該針對文件可自動完成的每個表單的 `input` 元素,將 `autocomplete` 設定為 `off`。如需詳細資訊,請參閱 [瀏覽器相容性圖表](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form#compatChart) 中有關 Google Chrome 的注意事項。 +對於大多數現代瀏覽器 (包括 Firefox 38+、Google Chrome 34+、IE 11+),將 autocomplete 屬性設為 off 不會阻止瀏覽器的密碼管理員詢問使用者是否要儲存登入欄位 (使用者名稱和密碼)。如果使用者允許儲存,瀏覽器會在下次使用者造訪頁面時自動填入登入資訊。請參閱[自動完成屬性和登入欄位](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion#The_autocomplete_attribute_and_login_fields) (英文)。 +**注意:** 如果您在表單中將 `autocomplete` 設定為 `off`,因為文件會提供自己的自動完成,則您也應該針對文件可自動完成的每個表單的 `input` 元素,將 `autocomplete` 設定為 `off`。如需詳細資訊,請參閱[瀏覽器相容性圖表](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form#compatChart) (英文) 中有關 Google Chrome 的注意事項。 @@ -146,7 +146,7 @@ For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) sett **Note:** Do not use this attribute as it is obsolete in the latest standard: use the [`scope`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th#attr-scope) attribute instead. 此屬性包含以空格分隔的字串清單。每個字串都是此標頭所適用之儲存格群組的 `id`。 -**注意:** 請勿使用此屬性,因為它在最新標準中已過時: 請改用 [`scope`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th#attr-scope) 屬性。 +**注意:** 請勿使用此屬性,因為它在最新標準中已過時: 請改用 [`scope`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th#attr-scope) (英文) 屬性。 @@ -167,23 +167,23 @@ For most modern browsers (including Firefox 38+, Google Chrome 34+, IE 11+) sett * This [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") element is only one part of the [algorithm to determine a page's character set](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Algorithm charset page"). The [`Content-Type` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) and any [Byte-Order Marks](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark "The definition of that term (Byte-Order Marks) has not been written yet; please consider contributing it!") override this element. * It is strongly recommended to define the character encoding. If a page's encoding is undefined, cross-scripting techniques are possible, such as the [`UTF-7` fallback cross-scripting technique](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7). * The [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "The HTML <meta> element represents metadata that cannot be represented by other HTML meta-related elements, like <base>, <link>, <script>, <style> or <title>.") element with a `charset` attribute is a synonym for the pre-HTML5 `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">`, where _`IANAcharset`_ contains the value of the equivalent [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) attribute. This syntax is still allowed, although no longer recommended. - 此屬性會宣告頁面的字元編碼。它必須包含 [字元編碼的標準 IANA MIME 名稱](https://www.iana.org/assignments/character-sets)。雖然標準不會要求特定編碼,但建議: + 此屬性會宣告頁面的字元編碼。它必須包含[字元編碼的標準 IANA MIME 名稱](https://www.iana.org/assignments/character-sets) (英文)。雖然標準不會要求特定編碼,但建議: -* 建議作者使用 [`UTF-8`](https://developer.mozilla.org/en-US/docs/Glossary/UTF-8)。 +* 建議作者使用 [`UTF-8`](https://developer.mozilla.org/en-US/docs/Glossary/UTF-8) (英文)。 * 作者不應使用 ASCII 不相容的編碼來避免安全性風險: 不支援此種編碼的瀏覽器可能會將有害的內容解譯為 HTML。這種情況會發生在 `JIS_C6226-1983`、`JIS_X0212-1990`、`HZ-GB-2312`、`JOHAB`、ISO-2022 系列和 EBCDIC 系列。 **注意: ** ASCII 不相容的編碼未將 8 位元程式碼點 `0x20` 對應至 `0x7E` 至 `0x0020` 至 `0x007E` Unicode 程式碼點) -* 已示範這些編碼的情況下,作者「不得」使用 `CESU-8`、`UTF-7`、`BOCU-1` 和/或 `SCSU` 做為 [跨網站指令碼](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting) 攻擊。 +* 已示範這些編碼的情況下,作者「不得」使用 `CESU-8`、`UTF-7`、`BOCU-1` 和/或 `SCSU` 作為[跨網站指令碼](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting) (英文) 攻擊。 * 作者不應使用 `UTF-32`,因為並非所有 HTML5 編碼演算法都可以將其與 `UTF-16` 區分。 **注意:** * 宣告的字元編碼必須與用以儲存頁面的字元編碼相符,以避免亂碼和安全漏洞。 -* 宣告編碼的 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta“HTML <meta> 元素代表無法由其他 HTML 中繼相關元素代表的中繼資料,例如 <base>、<link>、<script>、<style> 或 <title>。”) 元素必須在 [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head "HTML <head> 元素提供有關文件的一般資訊,包括其標題及指令碼和樣式表的連結。") 元素內部,以及在 HTML 的「前 1024 個位元組」內,因為有些瀏覽器只會在選擇編碼之前查看這些位元組。 -* 此 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta “HTML <meta> 元素代表無法由其他 HTML 中繼相關元素代表的中繼資料, 例如 <base>、<link>、<script>、<style> 或 <title>。”) 元素只是 [判斷頁面字元集的演算法](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Algorithm charset page") 的一部分。[`Content-Type` 標頭](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) 和任何 [Byte-Order 標記](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark "該術語的定義 (位元順序標記) 尚未撰寫;請考慮參與!") 會取代此元素。 +* 宣告編碼的 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> 元素代表無法由其他 HTML 中繼相關元素代表的中繼資料,例如 <base>、<link>、<script>、<style> 或 <title>。”) 元素必須在 [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head "HTML <head> 元素提供有關文件的一般資訊,包括其標題及指令碼和樣式表的連結。") 元素內部,以及在 HTML 的「前 1024 個位元組」內,因為有些瀏覽器只會在選擇編碼之前查看這些位元組。 +* 此 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> 元素代表無法由其他 HTML 中繼相關元素代表的中繼資料, 例如 <base>、<link>、<script>、<style> 或 <title>。”) 元素只是 [判斷頁面字元集的演算法](https://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#encoding-sniffing-algorithm "Algorithm charset page") 的一部分。[`Content-Type` 標頭](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) 和任何 [Byte-Order 標記](https://developer.mozilla.org/en-US/docs/Glossary/Byte-Order_Mark "該術語的定義 (位元順序標記) 尚未撰寫;請考慮參與!") 會取代此元素。 * 強烈建議您定義字元編碼。如果未定義頁面的編碼,則可能使用跨指令碼技術,例如 [`UTF-7` 後援跨指令碼技術](https://code.google.com/p/doctype-mirror/wiki/ArticleUtf7). -* [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta“HTML <meta> 元素代表無法由其他 HTML 中繼相關元素代表的中繼資料,例如 <base>、<link>、<script>、<style> 或 <title>。") 元素與 `charset` 屬性是前 HTML5 `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">`,其中 _`IANAcharset`_ 包含對等的 [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) 屬性值。雖然已不再建議,但仍允許此語法。 +* [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> 元素代表無法由其他 HTML 中繼相關元素代表的中繼資料,例如 <base>、<link>、<script>、<style> 或 <title>。") 元素與 `charset` 屬性是前 HTML5 `<meta http-equiv="Content-Type" content="text/html; charset=_IANAcharset_">`,其中 _`IANAcharset`_ 包含對等的 [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) 屬性值。雖然已不再建議,但仍允許此語法。 @@ -239,17 +239,17 @@ A cross-origin request (i.e. with an [`Origin`](https://developer.mozilla.org/en A cross-origin request (i.e. with an `Origin` HTTP header) is performed along with a credential sent (i.e. a cookie, certificate, and/or HTTP Basic authentication is performed). If the server does not give credentials to the origin site (through [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials "The Access-Control-Allow-Credentials response header tells browsers whether to expose the response to frontend JavaScript code when the request's credentials mode (Request.credentials) is "include".") HTTP header), the resource will be _tainted_ and its usage restricted. If the attribute is not present, the resource is fetched without a [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS: CORS (Cross-Origin Resource Sharing) is a system, consisting of transmitting HTTP headers, that determines whether browsers block frontend JavaScript code from accessing responses for cross-origin requests.") request (i.e. without sending the `Origin` HTTP header), preventing its non-tainted usage. If invalid, it is handled as if the enumerated keyword **anonymous** was used. See [CORS settings attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for additional information. - 這個列舉屬性指出在擷取資源時是否必須使用 [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS: CORS (跨原始來源資源共用) 是一個系統,由傳輸 HTTP 標頭所組成,決定瀏覽器是否禁止前端 JavaScript 程式碼存取跨原始來源要求的回應。”)。[啟用 CORS 的影像](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_Enabled_Image) 可以在 [`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas "使用 HTML <canvas> 元素,搭配畫布指令碼處理 API 或 WebGL API 以繪製圖形和動畫。") 元素中重複使用,而不會被 _tainted_。允許的值為: + 這個列舉屬性指出在擷取資源時是否必須使用 [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS: CORS (跨原始來源資源共用) 是一個系統,由傳輸 HTTP 標頭所組成,決定瀏覽器是否禁止前端 JavaScript 程式碼存取跨原始來源要求的回應。”)。[啟用 CORS 的影像](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_Enabled_Image) (英文) 可以在 [`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas "使用 HTML <canvas> 元素,搭配畫布指令碼處理 API 或 WebGL API 以繪製圖形和動畫。") 元素中重複使用,而不會被 _tainted_。允許的值為: `anonymous` -跨原始來源要求 (即具有 [`Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin "原始來源要求標頭指出擷取原自何處。它不包含任何路徑資訊,但只包含伺服器名稱。它會與 CORS 要求以及 POST 要求一起送出。它類似於 Referer 標頭,但與此標頭不同,它不會公開整個路徑。") HTTP 標頭),會執行,但不會送出任何認證 (也就是不包含 cookie、X.509 憑證或 HTTP Basic 驗證)。如果伺服器未提供認證給原始來源網站 (不要設定 [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin "Access-Control-Allow-Origin 回應標頭指出回應是否可與來自指定原始來源的要求程式碼共用。") HTTP 標頭),則影像會受到污染,且其使用方式受到限制。 +跨原始來源要求 (即具有 [`Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin "原始來源要求標頭指出擷取原自何處。它不包含任何路徑資訊,但只包含伺服器名稱。它會與 CORS 要求以及 POST 要求一起送出。它類似於 Referer 標頭,但與此標頭不同,它不會公開整個路徑。") HTTP 標頭),會執行,但不會送出任何認證 (也就是不包含 Cookie、X.509 憑證或 HTTP 基本驗證)。如果伺服器未提供認證給原始來源網站 (不要設定 [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin "Access-Control-Allow-Origin 回應標頭指出回應是否可與來自指定原始來源的要求程式碼共用。") HTTP 標頭),則影像會受到污染,且其使用方式受到限制。 `use-credentials` 跨原始來源要求 (即具有 `Origin` HTTP 標頭) 會隨著傳送的認證一起執行 (即執行 Cookie、憑證和/或 HTTP 基本驗證)。如果伺服器未透過 [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials "Access-Control-Allow-Credentials 回應標頭會告訴瀏覽器,當要求認證模式 (Request.credentials) 為 "include" 時,是否要將回應公開給前端 JavaScript 程式碼。") HTTP 標頭將認證提供給來源站台,資源就會被 _tainted_,其使用也會受限。 -如果屬性不存在,系統會在不發出 [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS: CORS (跨來源資源共用) 是一個系統,由傳輸 HTTP 標頭所組成,可決定瀏覽器是否封鎖前端 JavaScript 程式碼存取跨原始來源要求的回應。") 要求的情況下擷取資源 (也就是不送出 `Origin` HTTP 標頭),因此無法以未受污染的方式使用。如果值無效,系統會將其視為使用列舉關鍵字 **anonymous**。如需其他資訊,請參閱 [CORS 設定屬性](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes)。 +如果屬性不存在,系統會在不發出 [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS "CORS: CORS (跨來源資源共用) 是一個系統,由傳輸 HTTP 標頭所組成,可決定瀏覽器是否封鎖前端 JavaScript 程式碼存取跨原始來源要求的回應。") 要求的情況下擷取資源 (也就是不送出 `Origin` HTTP 標頭),因此無法以未受污染的方式使用。如果值無效,系統會將其視為使用列舉關鍵字 **anonymous**。如需其他資訊,請參閱 [CORS 設定屬性](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) (英文)。 @@ -311,7 +311,7 @@ To achieve a similar effect for dynamically inserted scripts use `async="false"` 具有 `defer` 屬性的指令碼會延後 `DOMContentLoaded` 事件,直到指令碼載入並完成評估為止。 -如果沒有 `src` 屬性,則不得使用此屬性 (也就是內嵌指令碼);在這種情況下,此屬性不會產生任何作用。 +如果沒有 `src` 屬性,則不得使用此屬性 (也就是內嵌指令碼); 在這種情況下,此屬性不會產生任何作用。 若要讓動態插入的指令碼達到類似效果,請改用 `async="false"`。具有 `defer` 屬性的指令碼會依照它們在文件中出現的順序執行。 @@ -343,13 +343,13 @@ To achieve a similar effect for dynamically inserted scripts use `async="false"` * Although HTTP(s) URLs need to be in the same-origin, [`blob:` URLs](https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL) and [`data:` URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) are allowed so that content generated by JavaScript, such as pictures created in an image-editor Web app, can be downloaded. * If the HTTP header [`Content-Disposition:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) gives a different filename than this attribute, the HTTP header takes priority over this attribute. * If `Content-Disposition:` is set to `inline`, Firefox prioritizes `Content-Disposition`, like the filename case, while Chrome prioritizes the `download` attribute. - 此屬性會指示瀏覽器下載 URL,而不是瀏覽至該 URL,因此系統會提示使用者將其儲存為本機檔案。如果此屬性有值,系統會在 [儲存] 提示中將其作為預先填入的檔案名稱 (使用者仍可視需要變更檔名)。允許的值沒有限制,但 `/` 和 `\` 會轉換為底線。大部分的檔案系統會限制檔名中的某些標點符號,而瀏覽器會據此調整建議的名稱。 + 此屬性會指示瀏覽器下載 URL,而不是瀏覽至該 URL,因此系統會提示使用者將其儲存為本機檔案。如果此屬性有值,系統會在「儲存」提示中將其作為預先填入的檔案名稱 (使用者仍可視需要變更檔名)。允許的值沒有限制,但 `/` 和 `\` 會轉換為底線。大部分的檔案系統會限制檔名中的某些標點符號,而瀏覽器會據此調整建議的名稱。 **注意:** -* 此屬性僅適用於 [相同原始來源的 URL](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)。 -* 雖然 HTTP URL 必須位於相同的原始來源中,但允許 [`blob:` URLs](https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL) 和 [`data:` URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs),以便下載 JavaScript 產生的內容,例如在影像編輯器 Web 應用程式中建立的圖片。 -* 如果 HTTP 標頭 [`Content-Disposition:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) 提供與此屬性不同的檔名,則 HTTP 標頭的優先順序高於此屬性。 +* 此屬性僅適用於[相同原始來源的 URL](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) (英文)。 +* 雖然 HTTP URL 必須位於相同的原始來源中,但允許 [`blob:` URLs](https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL) (英文) 和 [`data:` URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) (英文),以便下載 JavaScript 產生的內容,例如在影像編輯器 Web 應用程式中建立的圖片。 +* 如果 HTTP 標頭 [`Content-Disposition:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) (英文) 提供與此屬性不同的檔名,則 HTTP 標頭的優先順序高於此屬性。 * 如果 `Content-Disposition:` 設定為 `inline`,Firefox 會優先處理 `Content-Disposition` (例如檔名案例),而 Chrome 會優先處理 `download` 属性。 @@ -358,7 +358,7 @@ To achieve a similar effect for dynamically inserted scripts use `async="false"` * `true`, which indicates that the element may be dragged * `false`, which indicates that the element may not be dragged. - 列舉屬性,指出是否可以使用 [拖曳 API](https://developer.mozilla.org/docs/DragDrop/Drag_and_Drop) 拖曳元素。它可能有下列值: + 列舉屬性,指出是否可以使用[拖曳 API](https://developer.mozilla.org/docs/DragDrop/Drag_and_Drop) (英文) 拖曳元素。它可能有下列值: * `true`,表示可以拖曳元素 * `false`,表示不可以拖曳元素。 @@ -370,7 +370,7 @@ To achieve a similar effect for dynamically inserted scripts use `async="false"` * `copy`, which indicates that dropping will create a copy of the element that was dragged * `move`, which indicates that the element that was dragged will be moved to this new location. * `link`, will create a link to the dragged data. - 列舉屬性,指出可以使用 [拖曳 API](https://developer.mozilla.org/docs/DragDrop/Drag_and_Drop) 在元素上放置的內容類型。它可能有下列值: + 列舉屬性,指出可以使用[拖曳 API](https://developer.mozilla.org/docs/DragDrop/Drag_and_Drop) (英文) 在元素上放置的內容類型。它可能有下列值: * `copy`,表示置放會建立所拖曳元素的複本 * `move`,表示拖曳的元素將會移至這個新位置。 @@ -388,10 +388,10 @@ This value can be overridden by a [`formenctype`](https://developer.mozilla.org/ 當 `method` 屬性的值為 `post` 時,enctype 是用來將表單提交至伺服器的內容 [MIME 類型](https://en.wikipedia.org/wiki/Mime_type)。可能的值為: * `application/x-www-form-urlencoded`: 若未指定屬性則為預設值。 -* `multipart/form-data`: 此值用於 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 元素用於建立 Web 表單的互動式控制項,以便接受使用者的輸入;可用的輸入資料類型和控制 Widget 種類很多,取決於裝置和使用者代理程式。") 元素,其 `type` 屬性設定為 "file"。 +* `multipart/form-data`: 此值用於 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 元素用於建立 Web 表單的互動式控制項,以便接受使用者的輸入; 可用的輸入資料類型和控制 Widget 種類很多,取決於裝置和使用者代理程式。") 元素,其 `type` 屬性設定為 "file"。 * `text/plain`: (HTML5) -此值可由 [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "HTML <button> 元素代表可點選按鈕,可在表單或文件中需要簡單、標準按鈕功能表的任何位置中使用。") 或 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 元素用於建立網頁型表單的互動式控制項,以便接受使用者的資料;根據裝置和使用者代理程式,有各種不同的輸入資料和控制項小工具可供使用。") 元素上的 [`formenctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formenctype) 屬性覆寫。 +此值可由 [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "HTML <button> 元素代表可點選按鈕,可在表單或文件中需要簡單、標準按鈕功能表的任何位置中使用。") 或 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 元素用於建立網頁型表單的互動式控制項,以便接受使用者的資料; 根據裝置和使用者代理程式,有各種不同的輸入資料和控制項小工具可供使用。") 元素上的 [`formenctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formenctype) 屬性覆寫。 @@ -413,7 +413,7 @@ This value can be overridden by a [`formenctype`](https://developer.mozilla.org/ The [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-id) of a [labelable](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form_labelable) form-related element in the same document as the `<label>` element. The first element in the document with an `id` matching the value of the `for` attribute is the _labeled control_ for this label element, if it is a labelable element. If it is not labelable then the `for` attribute has no effect. If there are other elements which also match the `id` value, later in the document, they are not considered. **Note**: A `<label>` element can have both a `for` attribute and a contained control element, as long as the `for` attribute points to the contained control element. - 與 `<label>` 元素位於相同文件中的可標記 [labelable](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form_labelable) 表單相關元素的 [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-id)。如果文件中第一個 `id` 符合 `for` 屬性值的元素是可標記元素,則它就是此標籤元素的 _labeled control_。如果它不可標記,則 `for` 屬性沒有作用。如果有其他元素也符合 `id` 值,則稍後在文件中不會考慮它們。 + 與 `<label>` 元素位於相同文件中的可標記 [labelable](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form_labelable) (英文) 表單相關元素的 [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-id) (英文)。如果文件中第一個 `id` 符合 `for` 屬性值的元素是可標記元素,則它就是此標籤元素的 _labeled control_。如果它不可標記,則 `for` 屬性沒有作用。如果有其他元素也符合 `id` 值,則稍後在文件中不會考慮它們。 **注意**: `<label>` 元素可同時有 `for` 屬性和自主控制元素,只要 `for` 屬性指向自主控制元素即可。 @@ -439,10 +439,10 @@ If this attribute is specified, it overrides the [`enctype`](https://developer.m 如果按鈕是送出按鈕,這個屬性會指定用來將表單送出至伺服器的內容類型。可能的值為: * `application/x-www-form-urlencoded`: 若未指定屬性則為預設值。 -* `multipart/form-data`: 使用此值,前提是您正使用 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 元素用於建立 Web 表單的互動式控制項,以便接受使用者的輸入;可用的輸入資料類型和控制 Widget 種類很多,取決於裝置和使用者代理程式。") 元素,其 [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-type) 屬性設定為 `file`。 +* `multipart/form-data`: 使用此值,前提是您正使用 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 元素用於建立 Web 表單的互動式控制項,以便接受使用者的輸入; 可用的輸入資料類型和控制 Widget 種類很多,取決於裝置和使用者代理程式。") 元素,其 [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-type) (英文) 屬性設定為 `file`。 * `text/plain` -如果指定此屬性,它會覆寫按鈕表單擁有者的 [`enctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-enctype) 屬性。 +如果指定此屬性,它會覆寫按鈕表單擁有者的 [`enctype`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-enctype) (英文) 屬性。 @@ -472,7 +472,7 @@ If specified, this attribute overrides the [`method`](https://developer.mozilla. * `_blank`: Load the response into a new unnamed browsing context. * `_parent`: Load the response into the parent browsing context of the current one. If there is no parent, this option behaves the same way as `_self`. * `_top`: Load the response into the top-level browsing context (that is, the browsing context that is an ancestor of the current one, and has no parent). If there is no parent, this option behaves the same way as `_self`. - 如果按鈕是提交按鈕,則此屬性是名稱或關鍵字,用來指出在提交表單後,應將收到的回應顯示的位置。這是 _browsing context_ 的名稱或關鍵字 (例如索引標籤、視窗或內嵌框架)。如果指定此屬性,它會覆寫按鈕表單擁有者的 [`target`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-target) 屬性。下列關鍵字有特殊意義: + 如果按鈕是提交按鈕,則此屬性是名稱或關鍵字,用來指出在提交表單後,應將收到的回應顯示的位置。這是 _browsing context_ 的名稱或關鍵字 (例如索引標籤、視窗或內嵌框架)。如果指定此屬性,它會覆寫按鈕表單擁有者的 [`target`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-target) (英文) 屬性。下列關鍵字有特殊意義: * `_self`: 將回應載入至與目前內容相同的瀏覽內容。如果未指定屬性,則此值為預設值。 * `_blank`: 將回應載入至新的未命名瀏覽內容。 @@ -535,25 +535,25 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page * `"content-language"` 定義頁面的預設語言。您可以在任何元素上使用 [lang](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) 屬性覆寫它。 -**警告:** 請勿使用此值,因為它已過時。偏好 `lang` 屬性的 [`<html>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html“HTML <html> 元素代表 HTML 檔的根 (最上層元素),因此也稱為根元素。所有其他元素都必須是此元素的子系。") 元素。 +**警告:** 請勿使用此值,因為它已過時。偏好 `lang` 屬性的 [`<html>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html "HTML <html> 元素代表 HTML 檔的根 (最上層元素),因此也稱為根元素。所有其他元素都必須是此元素的子系。") 元素。 * `"content-security-policy"` -允許頁面作者定義目前頁面的 [內容原則](https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives)。內容原則大部分會指定允許的伺服器原則來源和指令碼端點,協助防範跨網站指令碼攻擊。 +允許頁面作者定義目前頁面的[內容原則](https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives) (英文)。內容原則大部分會指定允許的伺服器原則來源和指令碼端點,協助防範跨網站指令碼攻擊。 * `"content-type"` -定義文件的 [MIME 類型](https://developer.mozilla.org/en-US/docs/Glossary/MIME_type),後面接著其字元編碼。它的語法與 HTTP `content-type` entity-header 欄位相同,但因為它位於 HTML 頁面內,所以除了 `text/html` 之外,大多數值都不可能使用。因此,其 `content` 的有效語法為字串 `text/html`,後面接著具有下列語法的字元集:`; charset=_IANAcharset_`,其中 `IANAcharset` 是字元集的 _preferred MIME name_,如 [IANA 所定義](https://www.iana.org/assignments/character-sets)。 +定義文件的 [MIME 類型](https://developer.mozilla.org/en-US/docs/Glossary/MIME_type) (英文),後面接著其字元編碼。它的語法與 HTTP `content-type` entity-header 欄位相同,但因為它位於 HTML 頁面內,所以除了 `text/html` 之外,大多數值都不可能使用。因此,其 `content` 的有效語法為字串 `text/html`,後面接著具有下列語法的字元集:`; charset=_IANAcharset_`,其中 `IANAcharset` 是字元集的 _preferred MIME name_,如 [IANA 所定義](https://www.iana.org/assignments/character-sets) (英文) -**警告:** 請勿使用此值,因為它已過時。在 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> 元素代表無法由其他 HTML 中繼相關元素表示的中繼資料,例如 <base>、<link>、<script>、<style> 或 <title>。") 元素上使用 [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) 屬性。 +**警告:** 請勿使用此值,因為它已過時。在 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> 元素代表無法由其他 HTML 中繼相關元素表示的中繼資料,例如 <base>、<link>、<script>、<style> 或 <title>。") 元素上使用 [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) (英文) 屬性。 **注意:** 由於 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> 元素代表無法由其他 HTML 中繼相關元素表示的中繼資料,例如 <base>、<link>、<script>、<style> 或 <title>。") 無法變更 XHTML 或 HTML5 的 XHTML 序列化中的文件類型,切勿將 MIME 類型設定為具有 `<meta>` 的 XHTML MIME 類型。 * `"refresh"` 這個指示指定: -* 應該重新載入頁面的秒數 - 只有在 [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) 屬性包含正整數時。 -* 頁面應該重新導向至另一個頁面的秒數 - 只有在 [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) 屬性包含後面接著字串 '`;url=`' 的正整數以及有效的 URL 時。 +* 應該重新載入頁面的秒數 - 只有在 [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) (英文) 屬性包含正整數時。 +* 頁面應該重新導向至另一個頁面的秒數 - 只有在 [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) (英文) 屬性包含後面接著字串 '`;url=`' 的正整數以及有效的 URL 時。 * `"set-cookie"` -定義頁面的 [cookie](https://developer.mozilla.org/en-US/docs/cookie)。其內容必須遵循 [IETF HTTP Cookie 規格](https://tools.ietf.org/html/draft-ietf-httpstate-cookie-14) 中定義的語法。 +定義頁面的 [Cookie](https://developer.mozilla.org/en-US/docs/cookie) (英文)。其內容必須遵循 [IETF HTTP Cookie 規格](https://tools.ietf.org/html/draft-ietf-httpstate-cookie-14) (英文) 中定義的語法。 -**警告:** 請勿使用此指示,因為它已過時。請改為使用 HTTP 標頭 [`Set-Cookie`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie)。 +**警告:** 請勿使用此指示,因為它已過時。請改為使用 HTTP 標頭 [`Set-Cookie`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) (英文)。 @@ -647,7 +647,7 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page * `chapters` * 章節標題主要用於使用者正在瀏覽媒體資源時。 * `metadata` -* 指令碼使用的追蹤。使用者看不到。 +* 指令碼使用的追蹤。不會向使用者顯示。 @@ -692,12 +692,12 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page * In HTML 4, this can only be a simple white-space-separated list of media description literals, i.e., [media types and groups](https://developer.mozilla.org/en-US/docs/Web/CSS/@media), where defined and allowed as values for this attribute, such as `print`, `screen`, `aural`, `braille`. HTML5 extended this to any kind of [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries), which are a superset of the allowed values of HTML 4. * Browsers not supporting [CSS3 Media Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries) won't necessarily recognize the adequate link; do not forget to set fallback links, the restricted set of media queries defined in HTML 4. - 此屬性會指定連結資源適用的媒體。其值必須是媒體類型 / [media query](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries)。此屬性主要在連結至外部樣式表時很有用,它允許使用者代理程式針對其執行所在的裝置挑選最適合的樣式表。 + 此屬性會指定連結資源適用的媒體。其值必須是媒體類型 / [媒體查詢](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries) (英文)。此屬性主要在連結至外部樣式表時很有用,它允許使用者代理程式針對其執行所在的裝置挑選最適合的樣式表。 **注意:** -* 在 HTML 4 中,這只能是簡單以空格分隔的媒體描述常值清單,即 [媒體類型和群組](https://developer.mozilla.org/en-US/docs/Web/CSS/@media),其中已定義和允許做為此屬性的值,例如 `print`、`screen`、`aural`、`braille`。HTML5 將此延伸至任何種類的 [媒體查詢](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries),這是 HTML 4 允許值的超集。 -* 不支援 [CSS3 媒體查詢](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries) 的瀏覽器不一定會辨識適當的連結; 別忘了設定後援連結,這是 HTML 4 中定義的一組受限制的媒體查詢。 +* 在 HTML 4 中,這只能是簡單以空格分隔的媒體描述常值清單,即[媒體類型和群組](https://developer.mozilla.org/en-US/docs/Web/CSS/@media) (英文),其中已定義和允許做為此屬性的值,例如 `print`、`screen`、`aural`、`braille`。HTML5 將此延伸至任何種類的[媒體查詢](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries) (英文),這是 HTML 4 允許值的超集。 +* 不支援 [CSS3 媒體查詢](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_queries) (英文) 的瀏覽器不一定會辨識適當的連結; 別忘了設定後援連結,這是 HTML 4 中定義的一組受限制的媒體查詢。 @@ -708,13 +708,13 @@ Defines a [cookie](https://developer.mozilla.org/en-US/docs/cookie) for the page * `dialog`: Use when the form is inside a [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog "The HTML <dialog> element represents a dialog box or other interactive component, such as an inspector or window.") element to close the dialog when submitted. This value can be overridden by a [`formmethod`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formmethod) attribute on a [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "The HTML <button> element represents a clickable button, which can be used in forms or anywhere in a document that needs simple, standard button functionality.") or [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") element. - 瀏覽器用來提交表單的 [HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) 方法。可能的值為: + 瀏覽器用來提交表單的 [HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) (英文) 方法。可能的值為: -* `post`: 對應至 HTTP [POST 方法](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5) ; 表單資料會包含在表單內文中並傳送到伺服器。 -* `get`: 對應至 HTTP [GET 方法](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3); 表單資料會附加至 `action` 屬性 URI (以 '?' 做為分隔符號),而結果產生的 URI 會傳送到伺服器。當表單沒有副作用且只包含 ASCII 字元時,請使用此方法。 +* `post`: 對應至 HTTP [POST 方法](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5) (英文); 表單資料會包含在表單內文中並傳送到伺服器。 +* `get`: 對應至 HTTP [GET 方法](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3) (英文); 表單資料會附加至 `action` 屬性 URI (以 '?' 做為分隔符號),而結果產生的 URI 會傳送到伺服器。當表單沒有副作用且只包含 ASCII 字元時,請使用此方法。 * `dialog`:當表單位於 [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog "HTML <dialog> 元素代表對話方塊或其他互動元件,例如檢查程式或視窗。") 元素內時使用,提交時會關閉對話方塊。 -此值可由 [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "HTML <button> 元素代表可點選按鈕,可在表單或文件中需要簡單、標準按鈕功能表的任何位置中使用。") 或 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 元素用於建立網頁型表單的互動式控制項,以便接受使用者的資料;根據裝置和使用者代理程式,有各種不同的輸入資料和控制項小工具可供使用。") 元素上的 [`formmethod`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formmethod) 屬性覆寫。 +此值可由 [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "HTML <button> 元素代表可點選按鈕,可在表單或文件中需要簡單、標準按鈕功能表的任何位置中使用。") 或 [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 元素用於建立網頁型表單的互動式控制項,以便接受使用者的資料; 根據裝置和使用者代理程式,有各種不同的輸入資料和控制項小工具可供使用。") 元素上的 [`formmethod`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-formmethod) (英文) 屬性覆寫。 @@ -941,9 +941,9 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo * Though unstandardized, this declaration is respected by most mobile browsers due to de-facto dominance. * The default values may vary between devices and browsers. * To learn about this declaration in Firefox for Mobile, see [this article](https://developer.mozilla.org/en-US/docs/Mobile/Viewport_meta_tag "Mobile/Viewport meta tag"). - 此屬性定義文件層級中繼資料的一個名稱。如果也設定了 [`itemprop`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-itemprop)、[`http-equiv`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-http-equiv) 或 [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) 其中一個屬性,就不應設定此屬性。 + 此屬性定義文件層級中繼資料的一個名稱。如果也設定了 [`itemprop`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-itemprop) (英文)、[`http-equiv`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-http-equiv) (英文) 或 [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) (英文) 其中一個屬性,就不應設定此屬性。 -此中繼資料名稱會與 [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) 屬性的值相關聯。名稱屬性的可能值為: +此中繼資料名稱會與 [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) (英文) 屬性的值相關聯。名稱屬性的可能值為: * `application-name` 可定義在網頁中執行之應用程式名稱。 @@ -956,7 +956,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo * `description`,其中包含簡短且正確的頁面內容摘要。數個瀏覽器 (例如 Firefox 和 Opera) 會使用此瀏覽器做為已加入書籤頁面的預設描述。 * `generator`,其中包含產生頁面之軟體的識別碼。 * `keywords`,其中包含與頁面內容相關的字組 (以逗號分隔)。 -* `referrer` 可控制附加至從文件傳送之要求的 [`Referer` HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer): +* `referrer` 可控制附加至從文件傳送之要求的 [`Referer` HTTP 標頭](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer) (英文): `<meta name="referrer">` 的 `content` 屬性值 @@ -966,19 +966,19 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo `origin` -傳送文件的 [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin)。 +傳送文件的[來源](https://developer.mozilla.org/en-US/docs/Glossary/Origin) (英文)。 `no-referrer-when-downgrade` -將 [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin) 當作參考者傳送至與目前頁面一樣安全的 URL (https→https),但不將參考者傳送至較不安全的 URL (https→http)。這是預設行為。 +將[來源](https://developer.mozilla.org/en-US/docs/Glossary/Origin) (英文) 當作參考者傳送至與目前頁面一樣安全的 URL (https→https),但不將參考者傳送至較不安全的 URL (https→http)。這是預設行為。 `origin-when-cross-origin` -傳送相同原始來源要求的完整 URL (已移除參數),但只傳送其他案例的 [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin)。 +傳送相同原始來源要求的完整 URL (已移除參數),但只傳送其他案例的[來源](https://developer.mozilla.org/en-US/docs/Glossary/Origin) (英文)。 `same-origin` -將會針對 [相同網站的原始來源](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) 傳送參考者,但跨原始來源要求不會包含任何參考者資訊。 +將會針對[相同網站的原始來源](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) (英文) 傳送參考者,但跨原始來源要求不會包含任何參考者資訊。 `strict-origin` @@ -995,11 +995,11 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo **注意:** * 某些瀏覽器為參考者支援 `always`、`default` 和 `never` 的棄用值。 -* 動態插入 `<meta name="referrer">` (具有 [`document.write`](https://developer.mozilla.org/en-US/docs/Web/API/Document/write) 或 [`appendChild`](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild)) 會使參考者的行為無法預測。 +* 動態插入 `<meta name="referrer">` (具有 [`document.write`](https://developer.mozilla.org/en-US/docs/Web/API/Document/write) (英文) 或 [`appendChild`](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild)) (英文) 會使參考者的行為無法預測。 * 定義數個有衝突的原則時,會套用無參考者的原則。 -此屬性也可以使用 [WHATWG Wiki MetaExtensions 頁面](https://wiki.whatwg.org/wiki/MetaExtensions)中定義的延伸清單值。雖然尚未正式接受任何名稱,但一些常用的名稱如下: +此屬性也可以使用 [WHATWG Wiki MetaExtensions 頁面](https://wiki.whatwg.org/wiki/MetaExtensions) (英文) 中定義的延伸清單值。雖然尚未正式接受任何名稱,但一些常用的名稱如下: * `creator` 可定義文件的建立者名稱,例如組織或機構。如果有一個以上,則應該使用數個 [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta "HTML <meta> 元素代表無法由其他 HTML 中繼相關元素 例如 <base>、<link>、<script>、<style> 或 <title>。") 元素。 * `googlebot` (`robots` 的同義字) 後面只接著 Googlebot (Google 的索引編目程式)。 @@ -1042,37 +1042,37 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo 相當於 `noindex, nofollow` -[Google](https://support.google.com/webmasters/answer/79812) +[Google](https://support.google.com/webmasters/answer/79812) (英文) `noodp` -防止使用 [Open Directory Project](https://www.dmoz.org/) 描述 (如果有的話) 做為搜尋引擎結果中的頁面描述。 +防止使用[開啟目錄專案](https://www.dmoz.org/) (英文) 描述 (如果有的話) 做為搜尋引擎結果中的頁面描述。 -[Google](https://support.google.com/webmasters/answer/35624#nodmoz), [Yahoo](https://help.yahoo.com/kb/search-for-desktop/meta-tags-robotstxt-yahoo-search-sln2213.html#cont5), [Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) +[Google](https://support.google.com/webmasters/answer/35624#nodmoz), [Yahoo](https://help.yahoo.com/kb/search-for-desktop/meta-tags-robotstxt-yahoo-search-sln2213.html#cont5) (英文),[Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) (英文) `noarchive` 要求搜尋引擎不要快取頁面內容。 -[Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives), [Yahoo](https://help.yahoo.com/kb/search-for-desktop/SLN2213.html), [Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) +[Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives) (英文),[Yahoo](https://help.yahoo.com/kb/search-for-desktop/SLN2213.html) (英文),[Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) (英文) `nosnippet` 防止在搜尋引擎結果中顯示頁面的任何描述。 -[Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives), [Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) +[Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives) (英文),[Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) (英文) `noimageindex` 要求此頁面不要顯示為檢索影像的參考頁面。 -[Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives) +[Google](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag#valid-indexing--serving-directives) (英文) `nocache` `noarchive` 的同義字。 -[Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) +[Bing](https://www.bing.com/webmaster/help/which-robots-metatags-does-bing-support-5198d240) (英文) **注意:** @@ -1080,7 +1080,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo * 機器人仍然需要存取頁面,才能讀取這些規則。若要避免耗用頻寬,請使用 _[robots.txt](https://developer.mozilla.org/en-US/docs/Glossary/robots.txt "robots.txt: Robots.txt 是通常放在任何網站的根目錄的檔案。它會決定允許或禁止編目程式存取網站。")_ 檔案。 * 如果您想要移除頁面,`noindex` 將會運作,但只有在機器人再次造訪頁面之後。確保 `robots.txt` 檔案不會阻止重新造訪。 * 有些值互斥,例如 `index` 和 `noindex`,或 `follow` 和 `nofollow`。在這些情況下,機器人的行為未定義且在其間可能有所不同。 -* 某些編目程式機器人 (例如 Google、Yahoo 和 Bing) 支援 HTTP 標頭 `X-Robots-Tag` 的相同值;這可讓非 HTML 文件 (例如影像) 使用這些規則。 +* 某些編目程式機器人 (例如 Google、Yahoo 和 Bing) 支援 HTTP 標頭 `X-Robots-Tag` 的相同值; 這可讓非 HTML 文件 (例如影像) 使用這些規則。 * `slurp` 是 `robots` 的同義字,但僅適用於 Slurp - Yahoo 搜尋的編目程式。 * `viewport` 可對於目前所檢視的電腦圖形中 [viewport](https://developer.mozilla.org/en-US/docs/Glossary/viewport "viewport: 檢視區代表多邊形 (通常是矩形) 區域的初始大小,提供大小相關提示。在網頁瀏覽器的詞彙中,它是指您目前在其視窗中可見的檔部分 (或螢幕,如果文件以全螢幕模式檢視)。在捲動到檢視之前,無法在螢幕上看到檢視區外的內容。")。僅供行動裝置使用。 @@ -1136,7 +1136,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo 註解 [CSS 裝置調整 -該規格中 '<meta name="viewport">' in that specification.](https://drafts.csswg.org/css-device-adapt/#viewport-meta) 的定義。 +該規格中 '<meta name="viewport">' in that specification.](https://drafts.csswg.org/css-device-adapt/#viewport-meta) 的定義 處理中草稿 @@ -1148,7 +1148,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo * 雖然未標準化,但大多數行動瀏覽器都會遵守此宣告,因為實質支配地位。 * 預設值可能會因裝置和瀏覽器而異。 -* 若要了解 Firefox for Mobile 中的這項宣告,請參閱 [這篇文章](https://developer.mozilla.org/en-US/docs/Mobile/Viewport_meta_tag "Mobile/Viewport meta tag")。 +* 若要了解 Firefox for Mobile 中的這項宣告,請參閱[這篇文章](https://developer.mozilla.org/en-US/docs/Mobile/Viewport_meta_tag "Mobile/Viewport meta tag") (英文)。 @@ -1517,7 +1517,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo **Note:** Placeholders should only be used to show an example of the type of data that should be entered into a form; they are _not_ a substitute for a proper [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label "The HTML <label> element represents a caption for an item in a user interface.") element tied to the input. See [Labels and placeholders](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Labels_and_placeholders "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") in [<input>: The Input (Form Input) element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from the user; a wide variety of types of input data and control widgets are available, depending on the device and user agent.") for a full explanation. 提供使用者可在控制項中輸入內容的提示。預留位置文字中的歸位字元或換行字元,在呈現提示時必須視為換行符號。 -**注意: ** 預留位置應只用來顯示表單中應輸入資料類型的範例;它們不是與輸入欄位連結的適當 [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label "HTML <label> 元素代表使用者介面中項目的標題。") 元素的替代品。請參閱 [標籤與預留位置](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Labels_and_placeholders "HTML <input> 元素用於建立網頁型表單的互動式控制項,以便接收使用者的資料;根據裝置和使用者代理程式,有各種不同的輸入資料和控制項小工具可供使用。"),或在 [<input>:輸入(表單輸入)元素](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 元素用於建立網頁型表單的互動式控制項,以便接收使用者的資料;根據裝置和使用者代理程式,有各種不同的輸入資料和控制項小工具可供使用。") 中查看完整說明。 +**注意: ** 預留位置應只用來顯示表單中應輸入資料類型的範例; 它們不是與輸入欄位連結的適當 [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label "HTML <label> 元素代表使用者介面中項目的標題。") 元素的替代品。請參閱 [標籤與預留位置](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Labels_and_placeholders "HTML <input> 元素用於建立網頁型表單的互動式控制項,以便接收使用者的資料; 根據裝置和使用者代理程式,有各種不同的輸入資料和控制項小工具可供使用。"),或在 [<input>: 輸入 (表單輸入) 元素](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input "HTML <input> 元素用於建立網頁型表單的互動式控制項,以便接收使用者的資料; 根據裝置和使用者代理程式,有各種不同的輸入資料和控制項小工具可供使用。") 中查看完整說明。 @@ -1596,15 +1596,15 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo 對框架中的內容套用額外限制。屬性的值可以留空以套用所有限制,也可以使用以空格分隔的權杖來解除特定限制: * `allow-forms`: 允許資源提交表單。如果未使用此關鍵字,表單提交會遭到封鎖。 -* `allow-modals`: 讓資源 [開啟模型視窗](https://html.spec.whatwg.org/multipage/origin.html#sandboxed-modals-flag)。 -* `allow-orientation-lock`: 讓資源 [鎖定螢幕方向](https://developer.mozilla.org/en-US/docs/Web/API/Screen/lockOrientation)。 -* `allow-pointer-lock`: 讓資源使用 [指標鎖定 API](https://developer.mozilla.org/en-US/docs/WebAPI/Pointer_Lock)。 +* `allow-modals`: 讓資源[開啟模型視窗](https://html.spec.whatwg.org/multipage/origin.html#sandboxed-modals-flag) (英文)。 +* `allow-orientation-lock`: 讓資源[鎖定螢幕方向](https://developer.mozilla.org/en-US/docs/Web/API/Screen/lockOrientation) (英文)。 +* `allow-pointer-lock`: 讓資源使用[指標鎖定 API](https://developer.mozilla.org/en-US/docs/WebAPI/Pointer_Lock) (英文)。 * `allow-popups`: 允許快顯視窗 (例如 `window.open()`、`target="_blank"` 或 `showModalDialog()`)。如果未使用此關鍵字,快顯視窗將無法以無訊息方式開啟。 * `allow-popups-to-escape-sandbox`: 讓沙箱化文件開啟新的視窗,而不需要那些繼承沙箱的視窗。例如,這可將廣告安全地進行沙箱處理,而不需要在廣告連線至頁面時強制執行相同的限制。 -* `allow-presentation`: 讓資源啟動 [簡報工作階段](https://developer.mozilla.org/en-US/docs/Web/API/PresentationRequest)。 +* `allow-presentation`: 讓資源啟動[簡報工作階段](https://developer.mozilla.org/en-US/docs/Web/API/PresentationRequest) (英文)。 * `allow-same-origin`: 如果未使用此權杖,此資源被視為來自一直未通過 [相同原始來源的原則](https://developer.mozilla.org/en-US/docs/Glossary/same-origin_policy "same-origin policy: 相同原始來源的原則是重要的安全性機制,可限制從一個原則來源載入的文件或指令碼如何與其他原始來源的資源互動。") 的特殊原始來源。 * `allow-scripts`: 讓資源執行指令碼 (但不建立快顯視窗)。 -* `allow-storage-access-by-user-activation` : 讓資源要求透過 [儲存體存取 API](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API) 存取父代的儲存體功能。 +* `allow-storage-access-by-user-activation` : 讓資源要求透過[儲存體存取 API](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API) (英文) 存取父代的儲存體功能。 * `allow-top-navigation`: 讓資源瀏覽最上層瀏覽內容 (名為 `_top`)。 * `allow-top-navigation-by-user-activation`: 讓資源瀏覽最上層瀏覽內容,但只有在由使用者手勢初始化時。 @@ -1619,7 +1619,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo This attribute defines the scheme in which metadata is described. A scheme is a context leading to the correct interpretations of the [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) value, like a format. **Warning:** Do not use this value, as it is obsolete. There is no replacement as there was no real usage for it. - 此屬性定義描述中繼資料所使用的結構描述。結構描述是一種情境,可提供 [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) 值的正確解讀方式,例如格式。 + 此屬性定義描述中繼資料所使用的結構描述。結構描述是一種情境,可提供 [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) (英文) 值的正確解讀方式,例如格式。 **警告:** 請勿使用此值,因為它已過時。由於從未真正使用,也沒有替代方案。 @@ -1635,7 +1635,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo **Note:** According to the HTML5 specification, the default value for size should be 1; however, in practice, this has been found to break some web sites, and no other browser currently does that, so Mozilla has opted to continue to return 0 for the time being with Firefox. 如果控制項呈現為可捲動的清單方塊 (例如指定 `multiple` 時),此屬性表示清單中一次應顯示的列數。瀏覽器不一定要將選取元素呈現為可捲動的清單方塊。預設值為 0。 -**注意:** 根據 HTML5 規格,大小的預設值應為 1;不過,實務上這會導致某些網站出問題,而且目前沒有其他瀏覽器這麼做,因此 Mozilla 目前在 Firefox 中仍決定回傳 0。 +**注意:** 根據 HTML5 規格,大小的預設值應為 1; 不過,實務上這會導致某些網站出問題,而且目前沒有其他瀏覽器這麼做,因此 Mozilla 目前在 Firefox 中仍決定回傳 0。 @@ -1645,12 +1645,12 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo * a white-space separated list of sizes, each in the format `_<width in pixels>_x_<height in pixels>_` or `_<width in pixels>_X_<height in pixels>_`. Each of these sizes must be contained in the resource. **Note:** Most icon formats are only able to store one single icon; therefore most of the time the [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-sizes) contains only one entry. MS's ICO format does, as well as Apple's ICNS. ICO is more ubiquitous; you should definitely use it. - 此屬性定義資源中視覺媒體圖示的大小。只有在 [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-rel) 包含 `icon` 值,或非標準類型(例如 Apple 的 `apple-touch-icon`)時,才必須使用。它可能有下列值: + 此屬性定義資源中視覺媒體圖示的大小。只有在 [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-rel) (英文) 包含 `icon` 值,或非標準類型 (例如 Apple 的 `apple-touch-icon`) 時,才必須使用。它可能有下列值: * `any`,表示圖示可以縮放為任何大小,因為它採用向量格式,例如 `image/svg+xml`。 * 以空格分隔的大小清單,各自的格式為 `_<width in pixels>_x_<height in pixels>_` 或 `_<width in pixels>_X_<height in pixels>_`。每個大小都必須包含在資源中。 -**注意:** 大多數圖示格式只能儲存單一圖示,因此大多數情況下 [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-sizes) 只會包含一個項目。MS 的 ICO 格式可以,Apple 的 ICNS 也可以。ICO 更普遍;您應該優先使用它。 +**注意:** 大多數圖示格式只能儲存單一圖示,因此大多數情況下 [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-sizes) (英文) 只會包含一個項目。MS 的 ICO 格式可以,Apple 的 ICNS 也可以。ICO 更普遍; 您應該優先使用它。 @@ -1673,7 +1673,7 @@ See also: [`@viewport`](https://developer.mozilla.org/en-US/docs/Web/CSS/@viewpo This attribute specifies the URI of an external script; this can be used as an alternative to embedding a script directly within a document. If a `script` element has a `src` attribute specified, it should not have a script embedded inside its tags. - 此屬性指定外部指令碼的 URI;這可作為直接在文件中內嵌指令碼的替代方式。 + 此屬性指定外部指令碼的 URI; 這可作為直接在文件中內嵌指令碼的替代方式。 如果 `script` 元素已指定 `src` 屬性,就不應在其標籤內內嵌指令碼。 @@ -1712,7 +1712,7 @@ The user agent selects any one of the available sources at its discretion. This 在同一個 `srcset` 屬性中同時混用寬度描述項和像素密度描述項是錯誤的。重複的描述項也無效,例如同一個 `srcset` 中有兩個來源都標示為 `2x`。 -使用者代理程式可自行決定要選取哪一個可用來源。這讓它們有很大的彈性,可依使用者偏好或頻寬條件等因素調整選擇。請參閱我們的 [回應式影像](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images) 教學課程,查看範例。 +使用者代理程式可自行決定要選取哪一個可用來源。這讓它們有很大的彈性,可依使用者偏好或頻寬條件等因素調整選擇。請參閱我們的[回應式影像](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images) (英文) 教學課程,查看範例。 @@ -1739,7 +1739,7 @@ The user agent selects any one of the available sources at its discretion. This * _negative value_ 表示元素應為可成為焦點,但不得透過循序鍵盤瀏覽來連線; * `0` 表示元素應可透過循序鍵盤瀏覽成為焦點和連線,但其相對順序是由平台慣例定義; -* _positive value_ 表示元素應可透過循序鍵盤瀏覽成為焦點和連線; 元素的聚焦順序是 [**tabindex**](https://developer.mozilla.org/docs/Web/HTML/Reference/Global_attributes/tabindex#attr-tabindex) 的遞增值。如果多個元素具有相同的 tabindex,其相對順序會依文件中的相對位置決定。 +* _positive value_ 表示元素應可透過循序鍵盤瀏覽成為焦點和連線; 元素的聚焦順序是 [**tabindex**](https://developer.mozilla.org/docs/Web/HTML/Reference/Global_attributes/tabindex#attr-tabindex) (英文) 的遞增值。如果多個元素具有相同的 tabindex,其相對順序會依文件中的相對位置決定。 @@ -1786,7 +1786,7 @@ If this attribute is specified, this element must come before any other elements The partial URL (starting with '#') of an [image map](https://developer.mozilla.org/en-US/docs/HTML/Element/map) associated with the element. **Note:** You cannot use this attribute if the `<img>` element is a descendant of an [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "The HTML <a> element (or anchor element) creates a hyperlink to other web pages, files, locations within the same page, email addresses, or any other URL.") or [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "The HTML <button> element represents a clickable button, which can be used in forms or anywhere in a document that needs simple, standard button functionality.") element. - 與元素相關聯之 [影像地圖](https://developer.mozilla.org/en-US/docs/HTML/Element/map) 的部分 URL (以 '#' 開頭)。 + 與元素相關聯之[影像地圖](https://developer.mozilla.org/en-US/docs/HTML/Element/map) (英文) 的部分 URL (以 '#' 開頭)。 **注意: ** 如果 `<img>` 元素是 [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a "HTML <a> 元素 (或錨點元素) 可建立通往其他網頁、檔案、同一頁面中的位置、電子郵件地址或任何其他 URL 的超連結。") 或 [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button "HTML <button> 元素代表可點擊的按鈕,可用於表單或文件中任何需要簡單、標準按鈕功能的位置。") 元素的子系,就不能使用此屬性。 diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.cs.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.cs.xlf index 5bcfcde51c3a8..1b26da1c5c602 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.cs.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.cs.xlf @@ -14,27 +14,27 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Nově dostupné v hlavních prohlížečích{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (Standardní hodnoty od {0}) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ Široce dostupné v hlavních prohlížečích{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Atributy direktivy Blazor {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (a požadované atributy...) The term "req'd" is an abbreviation for "required" @@ -54,24 +54,24 @@ Diagnostics after: - Diagnostics after: + Diagnostika po: Diagnostics before: - Diagnostics before: + Diagnostika před: directive - directive + direktiva Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Vložení fragmentu kódu direktivy +[Tab] pro navigaci mezi elementy, [Enter] pro dokončení @@ -86,12 +86,12 @@ Edit at {0} adds the non-whitespace content '{1}'. - Edit at {0} adds the non-whitespace content '{1}'. + Úprava na {0} přidá neprázdný obsah {1}. Edit at {0} deletes the non-whitespace content '{1}'. - Edit at {0} deletes the non-whitespace content '{1}'. + Úprava na {0} odstraní neprázdný obsah {1}. @@ -111,17 +111,17 @@ A format operation is being abandoned because it would introduce or remove one or more diagnostics. - A format operation is being abandoned because it would introduce or remove one or more diagnostics. + Operace formátování je ukončována, protože by zavedla nebo odebrala jednu nebo více diagnostik. A format operation is being abandoned because it would add or delete non-whitespace content. - A format operation is being abandoned because it would add or delete non-whitespace content. + Operace formátování je ukončována, protože by přidala nebo odstranila neprázdný obsah. Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue - Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue + Chyba formátování Práce se přerušuje, aby nedošlo k poškození souboru, prosím nahlaste tento problém. Více informací zde: https://aka.ms/razor-formatting-issue @@ -141,12 +141,12 @@ {0} Keyword - {0} Keyword + Klíčové slovo {0} MDN Reference - MDN Reference + Referenční informace k MDN Link text for MDN documentation URL in HTML completion tooltips @@ -186,7 +186,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + Aktivovat znovu dokončení… @@ -216,7 +216,7 @@ Unknown mapping behavior - Unknown mapping behavior + Neznámé chování mapování diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.de.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.de.xlf index b9b95a0e15886..a6a90f90c4b14 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.de.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.de.xlf @@ -14,27 +14,27 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Neu verfügbar in gängigen Browsern{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (Basisplan seit {0}) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ In großen Browsern allgemein verfügbar{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Attribute für Blazor-Direktiven {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (und erforderliche Attribute) The term "req'd" is an abbreviation for "required" @@ -54,24 +54,24 @@ Diagnostics after: - Diagnostics after: + Diagnose nach: Diagnostics before: - Diagnostics before: + Diagnose vor: directive - directive + Direktive Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Fügen Sie einen Anweisungscodeschnipsel +[Registerkarte] ein, um zwischen Elementen zu navigieren, [EINGABETASTE], um den Vorgang abzuschließen @@ -86,12 +86,12 @@ Edit at {0} adds the non-whitespace content '{1}'. - Edit at {0} adds the non-whitespace content '{1}'. + Das Bearbeiten bei {0} fügt den Nicht-Leerraum-Inhalt "{1}" hinzu. Edit at {0} deletes the non-whitespace content '{1}'. - Edit at {0} deletes the non-whitespace content '{1}'. + Das Bearbeiten bei {0} löscht den Nicht-Leerraum-Inhalt "{1}". @@ -111,17 +111,17 @@ A format operation is being abandoned because it would introduce or remove one or more diagnostics. - A format operation is being abandoned because it would introduce or remove one or more diagnostics. + Ein Formatierungsvorgang wird abgebrochen, weil er eine oder mehrere Diagnosen einführen oder entfernen würde. A format operation is being abandoned because it would add or delete non-whitespace content. - A format operation is being abandoned because it would add or delete non-whitespace content. + Ein Formatierungsvorgang wird abgebrochen, da er Nicht-Leerraum-Inhalte hinzufügen oder löschen würde. Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue - Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue + Formatierungsfehler Die weitere Bearbeitung wird abgebrochen, um die Datei nicht zu beschädigen. Melden Sie dieses Problem. Weitere Informationen unter https://aka.ms/razor-formatting-issue @@ -141,12 +141,12 @@ {0} Keyword - {0} Keyword + {0}-Schlüsselwort MDN Reference - MDN Reference + MDN-Referenz Link text for MDN documentation URL in HTML completion tooltips @@ -186,7 +186,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "Abschlüsse erneut auslösen..." @@ -216,7 +216,7 @@ Unknown mapping behavior - Unknown mapping behavior + Unbekanntes Zuordnungsverhalten diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.es.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.es.xlf index fefb04341d6e4..0fee98650f554 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.es.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.es.xlf @@ -14,27 +14,27 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Disponible recientemente en los principales exploradores{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (Línea base desde {0}) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ Ampliamente disponible en los exploradores principales{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Atributos de directiva de Blazor {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (y atributos req'd...) The term "req'd" is an abbreviation for "required" @@ -54,24 +54,24 @@ Diagnostics after: - Diagnostics after: + Diagnósticos después de: Diagnostics before: - Diagnostics before: + Diagnósticos antes de: directive - directive + directiva Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Insertar un fragmento de código de directiva +[Tab] para navegar entre elementos, [Entrar] para completar @@ -86,12 +86,12 @@ Edit at {0} adds the non-whitespace content '{1}'. - Edit at {0} adds the non-whitespace content '{1}'. + Editar en {0}agregar el contenido que no es un espacio en blanco "{1}" . Edit at {0} deletes the non-whitespace content '{1}'. - Edit at {0} deletes the non-whitespace content '{1}'. + Editar en {0} eliminar el contenido que no es un espacio en blanco "{1}" . @@ -111,17 +111,17 @@ A format operation is being abandoned because it would introduce or remove one or more diagnostics. - A format operation is being abandoned because it would introduce or remove one or more diagnostics. + Se está abandonando una operación de formato porque introduciría o quitaría uno o varios diagnósticos. A format operation is being abandoned because it would add or delete non-whitespace content. - A format operation is being abandoned because it would add or delete non-whitespace content. + Se está abandonando una operación de formato porque agregaría o eliminaría contenido que no es un espacio en blanco. Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue - Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue + Error de formato. Si abandona el trabajo adicional para no dañar el archivo, notifique este problema. Consulte: https://aka.ms/razor-formatting-issue @@ -141,12 +141,12 @@ {0} Keyword - {0} Keyword + {0} Palabra clave MDN Reference - MDN Reference + Referencia de MDN Link text for MDN documentation URL in HTML completion tooltips @@ -186,7 +186,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "Volver a desencadenar las finalizaciones..." @@ -216,7 +216,7 @@ Unknown mapping behavior - Unknown mapping behavior + Comportamiento de asignación desconocido diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.fr.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.fr.xlf index 9a33ec644bf20..0671922f0829b 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.fr.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.fr.xlf @@ -14,27 +14,27 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Nouveauté disponible sur les principaux navigateurs{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (Base de référence depuis {0}) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ Largement disponible sur les principaux navigateurs{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Attributs de directive Blazor {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (et attributs requis...) The term "req'd" is an abbreviation for "required" @@ -54,24 +54,24 @@ Diagnostics after: - Diagnostics after: + Diagnostics après : Diagnostics before: - Diagnostics before: + Diagnostics avant : directive - directive + directif Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Insérer un extrait de code de directive +[Tab] pour naviguer entre les éléments, [Entrée] pour terminer @@ -86,12 +86,12 @@ Edit at {0} adds the non-whitespace content '{1}'. - Edit at {0} adds the non-whitespace content '{1}'. + Modifier à {0} ajoute le contenu autre qu’un espace « {1} ». Edit at {0} deletes the non-whitespace content '{1}'. - Edit at {0} deletes the non-whitespace content '{1}'. + Modifier à {0} supprime le contenu autre qu’un espace « {1} ». @@ -111,17 +111,17 @@ A format operation is being abandoned because it would introduce or remove one or more diagnostics. - A format operation is being abandoned because it would introduce or remove one or more diagnostics. + Une opération de format est en cours d’abandon, car elle introduit ou supprime un ou plusieurs diagnostics. A format operation is being abandoned because it would add or delete non-whitespace content. - A format operation is being abandoned because it would add or delete non-whitespace content. + Une opération de formatage est en cours d’abandon, car elle ajouterait ou supprimerait du contenu qui n’est pas un espace blanc. Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue - Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue + Erreur de mise en forme. Abandon du travail pour éviter de corrompre le fichier, veuillez signaler ce problème. Voir : https://aka.ms/razor-formatting-issue @@ -141,12 +141,12 @@ {0} Keyword - {0} Keyword + Mot clé {0} MDN Reference - MDN Reference + Référence MDN Link text for MDN documentation URL in HTML completion tooltips @@ -186,7 +186,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + « Déclencher à nouveau les complétions... » @@ -216,7 +216,7 @@ Unknown mapping behavior - Unknown mapping behavior + Comportement de mappage inconnu diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.it.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.it.xlf index 28058e17c1b94..124f48132ac7a 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.it.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.it.xlf @@ -14,27 +14,27 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Ora disponibile nei principali browser{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (Baseline dal {0}) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ Ampiamente disponibile nei principali browser{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Attributo per la direttiva Blazor {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (e gli attributi richiesti...) The term "req'd" is an abbreviation for "required" @@ -54,24 +54,24 @@ Diagnostics after: - Diagnostics after: + Diagnostica dopo: Diagnostics before: - Diagnostics before: + Diagnostica prima di: directive - directive + direttiva Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Consente di inserire un frammento di codice di direttiva +[TAB] per spostarsi tra gli elementi, [INVIO] per completare @@ -86,12 +86,12 @@ Edit at {0} adds the non-whitespace content '{1}'. - Edit at {0} adds the non-whitespace content '{1}'. + La modifica in {0} aggiunge contenuti diversi da spazi vuoti '{1}'. Edit at {0} deletes the non-whitespace content '{1}'. - Edit at {0} deletes the non-whitespace content '{1}'. + La modifica in {0} elimina contenuti diversi da spazi vuoti '{1}'. @@ -111,17 +111,17 @@ A format operation is being abandoned because it would introduce or remove one or more diagnostics. - A format operation is being abandoned because it would introduce or remove one or more diagnostics. + È in corso l'abbandono di un'operazione di formato perché comporterebbe l'introduzione o la rimozione di una o più operazioni di diagnostica. A format operation is being abandoned because it would add or delete non-whitespace content. - A format operation is being abandoned because it would add or delete non-whitespace content. + È in corso l'abbandono di un'operazione di formattazione perché aggiungerebbe o eliminerebbe contenuto diverso da spazi vuoti. Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue - Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue + Errore di formattazione. Per evitare di danneggiare il file, interrompere l'operazione e segnalare il problema. Vedere: https://aka.ms/razor-formatting-issue @@ -141,12 +141,12 @@ {0} Keyword - {0} Keyword + Parola chiave di {0} MDN Reference - MDN Reference + Riferimento notifica sulla ricezione del messaggio Link text for MDN documentation URL in HTML completion tooltips @@ -186,7 +186,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "Riattiva i completamenti..." @@ -216,7 +216,7 @@ Unknown mapping behavior - Unknown mapping behavior + Comportamento di mapping sconosciuto diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.ja.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.ja.xlf index d29a648bb440c..d1aa4e6c7e7a1 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.ja.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.ja.xlf @@ -14,27 +14,27 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 主要な各種ブラウザー上で新たに使用可能{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + ({0} 以降のベースライン) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ 主要なブラウザー間で広く利用できます{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Blazor ディレクティブ属性 {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (および必須属性...) The term "req'd" is an abbreviation for "required" @@ -54,24 +54,24 @@ Diagnostics after: - Diagnostics after: + 次の時間が経過した後の診断: Diagnostics before: - Diagnostics before: + 次の時間以前の診断: directive - directive + ディレクティブ Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + ディレクティブ コード スニペットを挿入する +[Tab] を押すと要素間を移動し、[Enter] を押すと完了します @@ -86,12 +86,12 @@ Edit at {0} adds the non-whitespace content '{1}'. - Edit at {0} adds the non-whitespace content '{1}'. + {0} で編集すると空白以外のスペース '{1}' が追加されます。 Edit at {0} deletes the non-whitespace content '{1}'. - Edit at {0} deletes the non-whitespace content '{1}'. + {0} で削除すると空白以外のスペース '{1}' が追加されます。 @@ -111,17 +111,17 @@ A format operation is being abandoned because it would introduce or remove one or more diagnostics. - A format operation is being abandoned because it would introduce or remove one or more diagnostics. + 書式整形操作の 1 つが、診断を 1 つ以上増減させる要因になっているため中止されます。 A format operation is being abandoned because it would add or delete non-whitespace content. - A format operation is being abandoned because it would add or delete non-whitespace content. + 書式設定操作は、空白以外のコンテンツを追加または削除するため、破棄されています。 Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue - Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue + 書式設定エラーです。ファイルを破損しないようにこれ以上の作業を中止してください。この問題を報告してください。参照: https://aka.ms/razor-formatting-issue @@ -141,12 +141,12 @@ {0} Keyword - {0} Keyword + {0} キーワード MDN Reference - MDN Reference + MDN リファレンス Link text for MDN documentation URL in HTML completion tooltips @@ -186,7 +186,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "再トリガーの完了..." @@ -216,7 +216,7 @@ Unknown mapping behavior - Unknown mapping behavior + 不明なマッピング動作 diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.ko.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.ko.xlf index c151c537c25f7..d94d032f2f182 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.ko.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.ko.xlf @@ -14,27 +14,27 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 주요 브라우저에서 새로 사용 가능{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + ({0} 이후 기준) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ 주요 브라우저에서 널리 사용 가능{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Blazor 지시문 특성 {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0}(및 필수 특성...) The term "req'd" is an abbreviation for "required" @@ -54,24 +54,24 @@ Diagnostics after: - Diagnostics after: + 다음 이후 진단: Diagnostics before: - Diagnostics before: + 다음 이전 진단: directive - directive + 지시문 Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + 지시문 코드 조각 삽입 +요소 간에 탐색하려면 [Tab], 완료하려면 [Enter] @@ -86,12 +86,12 @@ Edit at {0} adds the non-whitespace content '{1}'. - Edit at {0} adds the non-whitespace content '{1}'. + {0}에서 편집은 공백이 아닌 콘텐츠 '{1}'을(를) 추가합니다. Edit at {0} deletes the non-whitespace content '{1}'. - Edit at {0} deletes the non-whitespace content '{1}'. + {0}에서 편집은 공백이 아닌 콘텐츠 '{1}'을(를) 삭제합니다. @@ -111,17 +111,17 @@ A format operation is being abandoned because it would introduce or remove one or more diagnostics. - A format operation is being abandoned because it would introduce or remove one or more diagnostics. + 서식 작업이 중단되는 이유는 진단 중 하나를 더 도입하거나 제거할 수 있기 때문입니다. A format operation is being abandoned because it would add or delete non-whitespace content. - A format operation is being abandoned because it would add or delete non-whitespace content. + 공백이 아닌 콘텐츠를 추가하거나 삭제하기 때문에 형식 작업이 중단됩니다. Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue - Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue + 서식 오류입니다. 파일이 손상되지 않도록 추가 작업을 중단합니다. 이 문제를 보고하세요. 참조: https://aka.ms/razor-formatting-issue @@ -141,12 +141,12 @@ {0} Keyword - {0} Keyword + {0} 키워드 MDN Reference - MDN Reference + MDN 참조 Link text for MDN documentation URL in HTML completion tooltips @@ -186,7 +186,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "완료된 항목 다시 트리거" @@ -216,7 +216,7 @@ Unknown mapping behavior - Unknown mapping behavior + 알 수 없는 매핑 동작 diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.pl.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.pl.xlf index 52a235968e69f..55b553f695644 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.pl.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.pl.xlf @@ -14,27 +14,27 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Nowo dostępne w głównych przeglądarkach{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (Punkt odniesienia od {0}) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ Powszechnie dostępne w głównych przeglądarkach{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Atrybut dyrektywy Blazor {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (i atrybuty req'd...) The term "req'd" is an abbreviation for "required" @@ -54,24 +54,24 @@ Diagnostics after: - Diagnostics after: + Diagnostyka po: Diagnostics before: - Diagnostics before: + Diagnostyka przed: directive - directive + dyrektywa Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Wstaw fragment kodu dyrektywy +klawisz [Tab], aby przechodzić pomiędzy elementami, klawisz [Enter], aby zakończyć @@ -86,12 +86,12 @@ Edit at {0} adds the non-whitespace content '{1}'. - Edit at {0} adds the non-whitespace content '{1}'. + Edycja na stronie {0} dodaje zawartość bez białych znaków „{1}”. Edit at {0} deletes the non-whitespace content '{1}'. - Edit at {0} deletes the non-whitespace content '{1}'. + Edycja na stronie {0} usuwa zawartość bez białych znaków „{1}”. @@ -111,17 +111,17 @@ A format operation is being abandoned because it would introduce or remove one or more diagnostics. - A format operation is being abandoned because it would introduce or remove one or more diagnostics. + Operacja formatowania jest porzucana, ponieważ spowodowałaby wprowadzenie lub usunięcie co najmniej jednej diagnostyki. A format operation is being abandoned because it would add or delete non-whitespace content. - A format operation is being abandoned because it would add or delete non-whitespace content. + Operacja formatowania jest porzucana, ponieważ doda lub usunie zawartość bez białych znaków. Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue - Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue + Błąd formatowania. Porzucanie dalszej pracy, aby nie uszkodzić pliku, zgłoś ten problem. Zobacz: https://aka.ms/razor-formatting-issue @@ -141,12 +141,12 @@ {0} Keyword - {0} Keyword + Słowo kluczowe {0} MDN Reference - MDN Reference + Referencje MDN Link text for MDN documentation URL in HTML completion tooltips @@ -186,7 +186,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "Ponownie wyzwalaj uzupełniania..." @@ -216,7 +216,7 @@ Unknown mapping behavior - Unknown mapping behavior + Nieznane zachowanie mapowania diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.pt-BR.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.pt-BR.xlf index 50261dc0fd775..69d3fa69b7b69 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.pt-BR.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.pt-BR.xlf @@ -14,27 +14,27 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Recém-disponível nos principais navegadores{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (Linha de base desde {0}) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ Amplamente disponível nos principais navegadores{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Atributos da diretiva Blazor {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (e atributos exigidos...) The term "req'd" is an abbreviation for "required" @@ -54,24 +54,24 @@ Diagnostics after: - Diagnostics after: + Diagnóstico após: Diagnostics before: - Diagnostics before: + Diagnóstico antes: directive - directive + diretiva Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Inserir um snippet de código de diretiva +[Tab] para navegar entre os elementos, [Enter] para concluir @@ -86,12 +86,12 @@ Edit at {0} adds the non-whitespace content '{1}'. - Edit at {0} adds the non-whitespace content '{1}'. + Editar em {0} adiciona o conteúdo que não é espaço em branco ''{1}''. Edit at {0} deletes the non-whitespace content '{1}'. - Edit at {0} deletes the non-whitespace content '{1}'. + Editar em {0} exclui o conteúdo que não é espaço em branco ''{1}''. @@ -111,17 +111,17 @@ A format operation is being abandoned because it would introduce or remove one or more diagnostics. - A format operation is being abandoned because it would introduce or remove one or more diagnostics. + Uma operação de formatação está sendo abandonada porque introduziria ou removeria um ou mais diagnósticos. A format operation is being abandoned because it would add or delete non-whitespace content. - A format operation is being abandoned because it would add or delete non-whitespace content. + Uma operação de formato está sendo abandonada porque adicionaria ou excluiria conteúdo que não é espaço em branco. Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue - Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue + Erro de formatação. Interrompendo o trabalho para não corromper o arquivo. Reporte esse problema. Consulte: https://aka.ms/razor-formatting-issue @@ -141,12 +141,12 @@ {0} Keyword - {0} Keyword + {0} Palavra-chave MDN Reference - MDN Reference + Referência de MDN Link text for MDN documentation URL in HTML completion tooltips @@ -186,7 +186,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "Disparar conclusões novamente..." @@ -216,7 +216,7 @@ Unknown mapping behavior - Unknown mapping behavior + Comportamento de mapeamento desconhecido diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.ru.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.ru.xlf index 70ae8ff02d9e7..050aaf5c66147 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.ru.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.ru.xlf @@ -14,27 +14,27 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Теперь доступно во всех основных браузерах{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (Базовый план с {0}) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ Широко доступно в основных браузерах{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Атрибуты директивы Blazor {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (и обязательные атрибуты...) The term "req'd" is an abbreviation for "required" @@ -54,24 +54,24 @@ Diagnostics after: - Diagnostics after: + Диагностика после: Diagnostics before: - Diagnostics before: + Диагностика до: directive - directive + директива Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Вставьте фрагмент кода директивы +[TAB] для перемещения между элементами, нажмите [ВВОД] для завершения @@ -86,12 +86,12 @@ Edit at {0} adds the non-whitespace content '{1}'. - Edit at {0} adds the non-whitespace content '{1}'. + Редактирование {0} добавляет содержимое, не являющееся пробелами "{1}". Edit at {0} deletes the non-whitespace content '{1}'. - Edit at {0} deletes the non-whitespace content '{1}'. + Редактирование {0} удаляет содержимое, не являющееся пробелами "{1}". @@ -111,17 +111,17 @@ A format operation is being abandoned because it would introduce or remove one or more diagnostics. - A format operation is being abandoned because it would introduce or remove one or more diagnostics. + Операция форматирования отменяется, поскольку она приведет к появлению или удалению одного или нескольких диагностических сообщений. A format operation is being abandoned because it would add or delete non-whitespace content. - A format operation is being abandoned because it would add or delete non-whitespace content. + Операция форматирования отменяется, поскольку она может добавить или удалить содержимое, отличное от пробелов. Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue - Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue + Ошибка форматирования. Дальнейшая работа прекращается, чтобы не повредить файл. Сообщите об этой проблеме. См.: https://aka.ms/razor-formatting-issue @@ -141,12 +141,12 @@ {0} Keyword - {0} Keyword + Ключевое слово: {0} MDN Reference - MDN Reference + Справочник по MDN Link text for MDN documentation URL in HTML completion tooltips @@ -186,7 +186,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "Повторный запуск завершений..." @@ -216,7 +216,7 @@ Unknown mapping behavior - Unknown mapping behavior + Неизвестное поведение сопоставления diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.tr.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.tr.xlf index 40070a78dbedf..0939fa085767a 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.tr.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.tr.xlf @@ -14,27 +14,27 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 Başlıca tarayıcılarda yeni kullanıma sunuldu{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + ( {0} itibarıyla temel alınan) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + 🆕Başlıca tarayıcılarda yaygın olarak kullanıma sunuldu{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Blazor yönergesi öznitelikleri {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (ve gerekli öznitelikler...) The term "req'd" is an abbreviation for "required" @@ -54,24 +54,24 @@ Diagnostics after: - Diagnostics after: + Şundan sonraki tanılama: Diagnostics before: - Diagnostics before: + Şundan önceki tanılama: directive - directive + yönerge Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + Yönerge kod parçacığı ekle +Öğeler arasında gezinmek için [Tab], tamamlamak için [Enter] @@ -86,12 +86,12 @@ Edit at {0} adds the non-whitespace content '{1}'. - Edit at {0} adds the non-whitespace content '{1}'. + {0} konumunda düzenleme, '{1}' boşluk dışı içeriğini ekler. Edit at {0} deletes the non-whitespace content '{1}'. - Edit at {0} deletes the non-whitespace content '{1}'. + {0} konumunda düzenleme, '{1}' boşluk dışı içeriğini siler. @@ -111,17 +111,17 @@ A format operation is being abandoned because it would introduce or remove one or more diagnostics. - A format operation is being abandoned because it would introduce or remove one or more diagnostics. + Bir veya daha fazla tanılamayı ortaya çıkarabileceği veya kaldırabileceği için biçimlendirme işlemi bırakılıyor. A format operation is being abandoned because it would add or delete non-whitespace content. - A format operation is being abandoned because it would add or delete non-whitespace content. + Boşluk dışı içeriği ekleyebileceği veya silebileceği için biçimlendirme işlemi bırakıl. Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue - Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue + Biçimlendirme hatası. Dosyanın bozulmaması için çalışmaya devam etmeyin ve lütfen bu sorunu bildirin. Bkz.: https://aka.ms/razor-formatting-issue @@ -141,12 +141,12 @@ {0} Keyword - {0} Keyword + {0} Anahtar Sözcüğü MDN Reference - MDN Reference + MDN Başvurusu Link text for MDN documentation URL in HTML completion tooltips @@ -186,7 +186,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "Tamamlamaları yeniden tetikleyin..." @@ -216,7 +216,7 @@ Unknown mapping behavior - Unknown mapping behavior + Bilinmeyen eşleme davranışı diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.zh-Hans.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.zh-Hans.xlf index 3b57f42a0e975..34cf786a5782f 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.zh-Hans.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.zh-Hans.xlf @@ -14,27 +14,27 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕 在主流浏览器中全新上线{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (自 {0} 起为基线) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ 在主流浏览器中广泛提供{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Blazor 指令特性 {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (和必需属性...) The term "req'd" is an abbreviation for "required" @@ -54,24 +54,24 @@ Diagnostics after: - Diagnostics after: + 诊断后: Diagnostics before: - Diagnostics before: + 诊断前: directive - directive + 指令 Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + 插入指令代码片段 +按 [Tab] 在元素之间导航,按 [Enter] 以完成 @@ -86,12 +86,12 @@ Edit at {0} adds the non-whitespace content '{1}'. - Edit at {0} adds the non-whitespace content '{1}'. + 于 {0} 编辑,添加非空格内容“{1}”。 Edit at {0} deletes the non-whitespace content '{1}'. - Edit at {0} deletes the non-whitespace content '{1}'. + 于 {0} 编辑,删除非空格内容“{1}”。 @@ -111,17 +111,17 @@ A format operation is being abandoned because it would introduce or remove one or more diagnostics. - A format operation is being abandoned because it would introduce or remove one or more diagnostics. + 由于会引入或移除一个或多个诊断,正在放弃格式操作。 A format operation is being abandoned because it would add or delete non-whitespace content. - A format operation is being abandoned because it would add or delete non-whitespace content. + 正在放弃格式操作,因为它会添加或删除非空格内容。 Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue - Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue + 格式错误。为避免文件损坏,已停止后续操作,请报告此问题。详情请见:https://aka.ms/razor-formatting-issue @@ -141,12 +141,12 @@ {0} Keyword - {0} Keyword + {0} 关键字 MDN Reference - MDN Reference + MDN 参考 Link text for MDN documentation URL in HTML completion tooltips @@ -186,7 +186,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + “重新触发完成…” @@ -216,7 +216,7 @@ Unknown mapping behavior - Unknown mapping behavior + 未知映射行为 diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.zh-Hant.xlf b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.zh-Hant.xlf index 6916d9b124371..73a130407ed86 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.zh-Hant.xlf +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Resources/xlf/SR.zh-Hant.xlf @@ -14,27 +14,27 @@ 🆕 Newly available across major browsers{0} - 🆕 Newly available across major browsers{0} + 🆕現已於各大瀏覽器全面推出{0} {0} is " (Baseline since YYYY)" or empty (Baseline since {0}) - (Baseline since {0}) + (自 {0} 起的基準) {0} is the year the feature reached baseline status (e.g., "2015") ✅ Widely available across major browsers{0} - ✅ Widely available across major browsers{0} + ✅ 廣泛適用於主要瀏覽器{0} {0} is " (Baseline since YYYY)" or empty Blazor directive attributes - Blazor directive attributes + Blazor 指示詞屬性 {0} (and req'd attributes...) - {0} (and req'd attributes...) + {0} (和必要屬性...) The term "req'd" is an abbreviation for "required" @@ -54,24 +54,24 @@ Diagnostics after: - Diagnostics after: + 在以下事項之後的診斷: Diagnostics before: - Diagnostics before: + 在以下事項之前的診斷: directive - directive + 指示詞 Insert a directive code snippet [Tab] to navigate between elements, [Enter] to complete - Insert a directive code snippet -[Tab] to navigate between elements, [Enter] to complete + 插入指示詞程式碼片段 +[Tab] 以在元素之間瀏覽,[Enter] 以完成 @@ -86,12 +86,12 @@ Edit at {0} adds the non-whitespace content '{1}'. - Edit at {0} adds the non-whitespace content '{1}'. + 在 {0} 處編輯將新增非空白內容 '{1}'。 Edit at {0} deletes the non-whitespace content '{1}'. - Edit at {0} deletes the non-whitespace content '{1}'. + 在 {0} 處編輯將刪除非空白內容 '{1}'。 @@ -111,17 +111,17 @@ A format operation is being abandoned because it would introduce or remove one or more diagnostics. - A format operation is being abandoned because it would introduce or remove one or more diagnostics. + 將放棄格式化作業,因為它將引入或移除一個或多個診斷。 A format operation is being abandoned because it would add or delete non-whitespace content. - A format operation is being abandoned because it would add or delete non-whitespace content. + 將放棄格式化作業,因為它將新增或删除非空白內容。 Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue - Formatting error. Abandoning further work to not corrupt the file, please report this issue. See: https://aka.ms/razor-formatting-issue + 格式錯誤。為避免檔案損壞,已停止後續作業,請回報此問題。詳情請參閱: https://aka.ms/razor-formatting-issue @@ -141,12 +141,12 @@ {0} Keyword - {0} Keyword + {0} 關鍵字 MDN Reference - MDN Reference + MDN 參考資料 Link text for MDN documentation URL in HTML completion tooltips @@ -186,7 +186,7 @@ "Re-trigger completions..." - "Re-trigger completions..." + "重新觸發完成..." @@ -216,7 +216,7 @@ Unknown mapping behavior - Unknown mapping behavior + 未知的對應行為 diff --git a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.cs.xlf b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.cs.xlf index 55a6b6c1d0ea4..ccd9e873325ad 100644 --- a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.cs.xlf +++ b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.cs.xlf @@ -99,7 +99,7 @@ Show Generated C# Declaration - Show Generated C# Declaration + Zobrazit vygenerovanou deklaraci jazyka C# @@ -124,12 +124,12 @@ Show Generated File - Show Generated File + Zobrazit vygenerovaný soubor Show Tag Helpers - Show Tag Helpers + Zobrazit pomocné rutiny značek diff --git a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.de.xlf b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.de.xlf index fd42c2110c411..8226302106636 100644 --- a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.de.xlf +++ b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.de.xlf @@ -99,7 +99,7 @@ Show Generated C# Declaration - Show Generated C# Declaration + Generierte C#-Deklaration anzeigen @@ -124,12 +124,12 @@ Show Generated File - Show Generated File + Generierte Datei anzeigen Show Tag Helpers - Show Tag Helpers + Tag-Hilfsprogramme anzeigen diff --git a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.es.xlf b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.es.xlf index 7ab70c9c8d7a1..4cc59b39f8000 100644 --- a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.es.xlf +++ b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.es.xlf @@ -99,7 +99,7 @@ Show Generated C# Declaration - Show Generated C# Declaration + Mostrar declaración de C# generada @@ -124,12 +124,12 @@ Show Generated File - Show Generated File + Mostrar archivo generado Show Tag Helpers - Show Tag Helpers + Mostrar asistentes de etiquetas diff --git a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.fr.xlf b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.fr.xlf index fcaa7980ea123..668d437e00fff 100644 --- a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.fr.xlf +++ b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.fr.xlf @@ -99,7 +99,7 @@ Show Generated C# Declaration - Show Generated C# Declaration + Afficher la déclaration C# générée @@ -124,12 +124,12 @@ Show Generated File - Show Generated File + Afficher le fichier généré Show Tag Helpers - Show Tag Helpers + Afficher les Tag Helpers diff --git a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.it.xlf b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.it.xlf index 8aa00d5a8ce4b..2fbfe46038cb3 100644 --- a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.it.xlf +++ b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.it.xlf @@ -99,7 +99,7 @@ Show Generated C# Declaration - Show Generated C# Declaration + Mostra dichiarazione C# generata @@ -124,12 +124,12 @@ Show Generated File - Show Generated File + Mostra file generato Show Tag Helpers - Show Tag Helpers + Mostra helper tag diff --git a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.ja.xlf b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.ja.xlf index c95b9c25c1be8..9f1e13fc20730 100644 --- a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.ja.xlf +++ b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.ja.xlf @@ -99,7 +99,7 @@ Show Generated C# Declaration - Show Generated C# Declaration + 生成された C# 宣言の表示 @@ -124,12 +124,12 @@ Show Generated File - Show Generated File + 生成されたファイルの表示 Show Tag Helpers - Show Tag Helpers + タグ ヘルパーの表示 diff --git a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.ko.xlf b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.ko.xlf index fe75e8b860ccc..0e73c6c1f0383 100644 --- a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.ko.xlf +++ b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.ko.xlf @@ -99,7 +99,7 @@ Show Generated C# Declaration - Show Generated C# Declaration + 생성된 C# 선언 표시 @@ -124,12 +124,12 @@ Show Generated File - Show Generated File + 생성된 파일 표시 Show Tag Helpers - Show Tag Helpers + 태그 도우미 표시 diff --git a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.pl.xlf b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.pl.xlf index e52c6958333a5..f51abd49d9aae 100644 --- a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.pl.xlf +++ b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.pl.xlf @@ -99,7 +99,7 @@ Show Generated C# Declaration - Show Generated C# Declaration + Pokaż wygenerowaną deklarację języka C# @@ -124,12 +124,12 @@ Show Generated File - Show Generated File + Pokaż wygenerowany plik Show Tag Helpers - Show Tag Helpers + Pokaż pomocników tagów diff --git a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.pt-BR.xlf b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.pt-BR.xlf index 12f214393521c..5ee4490f8a71e 100644 --- a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.pt-BR.xlf +++ b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.pt-BR.xlf @@ -99,7 +99,7 @@ Show Generated C# Declaration - Show Generated C# Declaration + Mostrar Declaração C# Gerada @@ -124,12 +124,12 @@ Show Generated File - Show Generated File + Mostrar Arquivo Gerado Show Tag Helpers - Show Tag Helpers + Mostrar Auxiliares de Marca diff --git a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.ru.xlf b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.ru.xlf index d1f007ec8dc49..fad9b739fdeec 100644 --- a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.ru.xlf +++ b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.ru.xlf @@ -99,7 +99,7 @@ Show Generated C# Declaration - Show Generated C# Declaration + Показать сгенерированную декларацию C# @@ -124,12 +124,12 @@ Show Generated File - Show Generated File + Показать созданный файл Show Tag Helpers - Show Tag Helpers + Показать помощники по тегам diff --git a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.tr.xlf b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.tr.xlf index 91ac6f279f6cc..a2b6c9d694e63 100644 --- a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.tr.xlf +++ b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.tr.xlf @@ -99,7 +99,7 @@ Show Generated C# Declaration - Show Generated C# Declaration + Oluşturulan C# Bildirimini Göster @@ -124,12 +124,12 @@ Show Generated File - Show Generated File + Oluşturan Dosyayı Göster Show Tag Helpers - Show Tag Helpers + Etiket Yardımcılarını Göster diff --git a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.zh-Hans.xlf b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.zh-Hans.xlf index 6d3b8529adc29..822a9d5ab37e5 100644 --- a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.zh-Hans.xlf +++ b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.zh-Hans.xlf @@ -99,7 +99,7 @@ Show Generated C# Declaration - Show Generated C# Declaration + 显示生成的 C# 声明 @@ -124,12 +124,12 @@ Show Generated File - Show Generated File + 显示生成的文件 Show Tag Helpers - Show Tag Helpers + 显示标记帮助程序 diff --git a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.zh-Hant.xlf b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.zh-Hant.xlf index a463d495e9d45..cd44652a9a675 100644 --- a/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.zh-Hant.xlf +++ b/src/Razor/src/Razor/src/Microsoft.VisualStudio.RazorExtension/xlf/RazorContextMenu.vsct.zh-Hant.xlf @@ -99,7 +99,7 @@ Show Generated C# Declaration - Show Generated C# Declaration + 顯示已產生的 C# 宣告 @@ -124,12 +124,12 @@ Show Generated File - Show Generated File + 顯示已產生的檔案 Show Tag Helpers - Show Tag Helpers + 顯示標籤協助程式 diff --git a/src/Razor/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Cohosting/Microsoft.AspNetCore.Razor.Test.Common.Cohosting.csproj b/src/Razor/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Cohosting/Microsoft.AspNetCore.Razor.Test.Common.Cohosting.csproj index 7a284d352ff9d..c3d851e406b15 100644 --- a/src/Razor/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Cohosting/Microsoft.AspNetCore.Razor.Test.Common.Cohosting.csproj +++ b/src/Razor/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Cohosting/Microsoft.AspNetCore.Razor.Test.Common.Cohosting.csproj @@ -33,7 +33,6 @@ - diff --git a/src/Razor/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Microsoft.AspNetCore.Razor.Test.Common.Tooling.csproj b/src/Razor/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Microsoft.AspNetCore.Razor.Test.Common.Tooling.csproj index e8e0f8d75a900..5426c5e6c1010 100644 --- a/src/Razor/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Microsoft.AspNetCore.Razor.Test.Common.Tooling.csproj +++ b/src/Razor/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Microsoft.AspNetCore.Razor.Test.Common.Tooling.csproj @@ -44,7 +44,7 @@ - + diff --git a/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/CodeActions/ImplementInterfaceTests.cs b/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/CodeActions/ImplementInterfaceTests.cs index dcfcd5e03cc52..ec14868ff1eb4 100644 --- a/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/CodeActions/ImplementInterfaceTests.cs +++ b/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/CodeActions/ImplementInterfaceTests.cs @@ -364,9 +364,6 @@ @implements IDerived int IBase.this[int index] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } string IBase.Property1 { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - string IDerived.Property1 { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - - int IDerived.this[int index] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } event EventHandler IBase.Event1 { @@ -381,6 +378,10 @@ event EventHandler IBase.Event1 } } + int IDerived.this[int index] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + string IDerived.Property1 { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + event EventHandler IDerived.Event1 { add diff --git a/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/Endpoints/CohostTypeHierarchyEndpointTest.cs b/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/Endpoints/CohostTypeHierarchyEndpointTest.cs index 54ded0a89c607..df6ba8b305dac 100644 --- a/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/Endpoints/CohostTypeHierarchyEndpointTest.cs +++ b/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/Endpoints/CohostTypeHierarchyEndpointTest.cs @@ -262,8 +262,6 @@ private async Task GetSupertypesAsync(TextDocument document var request = new TypeHierarchySupertypesParams { Item = roundTrippedItem, - Position = roundTrippedItem.SelectionRange.Start, - TextDocument = new TextDocumentIdentifier { DocumentUri = roundTrippedItem.Uri }, }; return await endpoint.GetTestAccessor().HandleRequestAsync(request, document, DisposalToken) ?? []; @@ -277,8 +275,6 @@ private async Task GetSubtypesAsync(TextDocument document, var request = new TypeHierarchySubtypesParams { Item = roundTrippedItem, - Position = roundTrippedItem.SelectionRange.Start, - TextDocument = new TextDocumentIdentifier { DocumentUri = roundTrippedItem.Uri }, }; return await endpoint.GetTestAccessor().HandleRequestAsync(request, document, DisposalToken) ?? []; diff --git a/src/RoslynAnalyzers/Microsoft.CodeAnalysis.ResxSourceGenerator/Microsoft.CodeAnalysis.ResxSourceGenerator.UnitTests/Microsoft.CodeAnalysis.ResxSourceGenerator.UnitTests.csproj b/src/RoslynAnalyzers/Microsoft.CodeAnalysis.ResxSourceGenerator/Microsoft.CodeAnalysis.ResxSourceGenerator.UnitTests/Microsoft.CodeAnalysis.ResxSourceGenerator.UnitTests.csproj index 1a1d829966613..1a93b45f5f5f0 100644 --- a/src/RoslynAnalyzers/Microsoft.CodeAnalysis.ResxSourceGenerator/Microsoft.CodeAnalysis.ResxSourceGenerator.UnitTests/Microsoft.CodeAnalysis.ResxSourceGenerator.UnitTests.csproj +++ b/src/RoslynAnalyzers/Microsoft.CodeAnalysis.ResxSourceGenerator/Microsoft.CodeAnalysis.ResxSourceGenerator.UnitTests/Microsoft.CodeAnalysis.ResxSourceGenerator.UnitTests.csproj @@ -18,20 +18,18 @@ - - - - - - - - - - + + + + + + + + diff --git a/src/RoslynAnalyzers/PerformanceSensitiveAnalyzers/UnitTests/CallSiteImplicitAllocationAnalyzerTests.cs b/src/RoslynAnalyzers/PerformanceSensitiveAnalyzers/UnitTests/CallSiteImplicitAllocationAnalyzerTests.cs index 5ed9549069443..b7f57d8a9ca0b 100644 --- a/src/RoslynAnalyzers/PerformanceSensitiveAnalyzers/UnitTests/CallSiteImplicitAllocationAnalyzerTests.cs +++ b/src/RoslynAnalyzers/PerformanceSensitiveAnalyzers/UnitTests/CallSiteImplicitAllocationAnalyzerTests.cs @@ -19,45 +19,56 @@ public sealed class CallSiteImplicitAllocationAnalyzerTests { [Fact] public Task CallSiteImplicitAllocation_ParamAsync() - => VerifyCS.VerifyAnalyzerAsync(""" - using System; - using Roslyn.Utilities; - - public class MyClass + => new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net100, + TestState = { - [PerformanceSensitive("uri")] - public void Testing() + Sources = { + """ + using System; + using Roslyn.Utilities; + + public class MyClass + { + [PerformanceSensitive("uri")] + public void Testing() + { - Params(); //no allocation, because compiler will implicitly substitute Array.Empty() - Params(1, 2); - Params(new [] { 1, 2}); // explicit, so no warning - ParamsWithObjects(new [] { 1, 2}); // explicit, but converted to objects, so stil la warning?! + Params(); //no allocation, because compiler will implicitly substitute Array.Empty() + Params(1, 2); + Params(new [] { 1, 2}); // explicit, so no warning + ParamsWithObjects(new [] { 1, 2}); // explicit, but converted to objects, so stil la warning?! - // Only 4 args and above use the params overload of String.Format - var test = String.Format("Testing {0}, {1}, {2}, {3}", 1, "blah", 2.0m, 'c'); - } + // String.Format uses the params span overload, so it does not allocate an array. + var test = String.Format("Testing {0}, {1}, {2}, {3}", 1, "blah", 2.0m, 'c'); + } - public void Params(params int[] args) - { - } + public void Params(params int[] args) + { + } - public void ParamsWithObjects(params object[] args) + public void ParamsWithObjects(params object[] args) + { + } + } + """, + ("PerformanceSensitiveAttribute.cs", VerifyCS.PerformanceSensitiveAttributeSource), + }, + ExpectedDiagnostics = { - } - } - """, - // Test0.cs(11,9): warning HAA0101: This call site is calling into a function with a 'params' parameter. This results in an array allocation + // Test0.cs(11,9): warning HAA0101: This call site is calling into a function with a 'params' parameter. This results in an array allocation #pragma warning disable RS0030 // Do not use banned APIs - VerifyCS.Diagnostic(CallSiteImplicitAllocationAnalyzer.ParamsParameterRule).WithLocation(11, 9), + VerifyCS.Diagnostic(CallSiteImplicitAllocationAnalyzer.ParamsParameterRule).WithLocation(11, 9), #pragma warning restore RS0030 // Do not use banned APIs - // Test0.cs(13,9): warning HAA0101: This call site is calling into a function with a 'params' parameter. This results in an array allocation + // Test0.cs(13,9): warning HAA0101: This call site is calling into a function with a 'params' parameter. This results in an array allocation #pragma warning disable RS0030 // Do not use banned APIs - VerifyCS.Diagnostic(CallSiteImplicitAllocationAnalyzer.ParamsParameterRule).WithLocation(13, 9), + VerifyCS.Diagnostic(CallSiteImplicitAllocationAnalyzer.ParamsParameterRule).WithLocation(13, 9), #pragma warning restore RS0030 // Do not use banned APIs - // Test0.cs(16,20): warning HAA0101: This call site is calling into a function with a 'params' parameter. This results in an array allocation -#pragma warning disable RS0030 // Do not use banned APIs - VerifyCS.Diagnostic(CallSiteImplicitAllocationAnalyzer.ParamsParameterRule).WithLocation(16, 20)); + }, + }, + }.RunAsync(); [Fact, WorkItem(3272, "https://github.com/dotnet/roslyn-analyzers/issues/3272")] public Task EmptyParamsWithNetFramework45Async() @@ -204,4 +215,37 @@ public void SomeMethod() // Test0.cs(12,9): warning HAA0102: Non-overridden virtual method call on a value type adds a boxing or constrained instruction #pragma warning disable RS0030 // Do not use banned APIs VerifyCS.Diagnostic(CallSiteImplicitAllocationAnalyzer.ValueTypeNonOverridenCallRule).WithLocation(11, 9)); + + [Fact] + public Task StringFormatWithFourArgumentsAllocatesParamsArrayWithNetCoreApp31Async() + => new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.NetCore.NetCoreApp31, + TestState = + { + Sources = + { + """ + using System; + using Roslyn.Utilities; + + public class MyClass + { + [PerformanceSensitive("uri")] + public void Testing() + { + var test = {|#0:String.Format("Testing {0}, {1}, {2}, {3}", 1, "blah", 2.0m, 'c')|}; + } + } + """, + ("PerformanceSensitiveAttribute.cs", VerifyCS.PerformanceSensitiveAttributeSource), + }, + ExpectedDiagnostics = + { +#pragma warning disable RS0030 // Do not use banned APIs + VerifyCS.Diagnostic(CallSiteImplicitAllocationAnalyzer.ParamsParameterRule).WithLocation(0), +#pragma warning restore RS0030 // Do not use banned APIs + }, + }, + }.RunAsync(); } diff --git a/src/RoslynAnalyzers/PublicApiAnalyzers/UnitTests/DeclarePublicAPIAnalyzerTestsBase.cs b/src/RoslynAnalyzers/PublicApiAnalyzers/UnitTests/DeclarePublicAPIAnalyzerTestsBase.cs index ecd38b0b0c72e..2cc1358034653 100644 --- a/src/RoslynAnalyzers/PublicApiAnalyzers/UnitTests/DeclarePublicAPIAnalyzerTestsBase.cs +++ b/src/RoslynAnalyzers/PublicApiAnalyzers/UnitTests/DeclarePublicAPIAnalyzerTestsBase.cs @@ -1009,52 +1009,66 @@ public async Task TypeForwardsAreProcessed2Async() return; } -#if NETCOREAPP - var containingAssembly = "System.Runtime.Extensions"; - const string NonNullSuffix = "!"; - const string NullableSuffix = "?"; -#else - var containingAssembly = "mscorlib"; - const string NonNullSuffix = ""; - const string NullableSuffix = ""; -#endif - string shippedText = $""" - - System.StringComparer (forwarded, contained in {containingAssembly}) - static System.StringComparer.InvariantCulture.get -> System.StringComparer{NonNullSuffix} (forwarded, contained in {containingAssembly}) - static System.StringComparer.InvariantCultureIgnoreCase.get -> System.StringComparer{NonNullSuffix} (forwarded, contained in {containingAssembly}) - static System.StringComparer.CurrentCulture.get -> System.StringComparer{NonNullSuffix} (forwarded, contained in {containingAssembly}) - static System.StringComparer.CurrentCultureIgnoreCase.get -> System.StringComparer{NonNullSuffix} (forwarded, contained in {containingAssembly}) - static System.StringComparer.Ordinal.get -> System.StringComparer{NonNullSuffix} (forwarded, contained in {containingAssembly}) - static System.StringComparer.OrdinalIgnoreCase.get -> System.StringComparer{NonNullSuffix} (forwarded, contained in {containingAssembly}) - static System.StringComparer.Create(System.Globalization.CultureInfo{NonNullSuffix} culture, bool ignoreCase) -> System.StringComparer{NonNullSuffix} (forwarded, contained in {containingAssembly}) - System.StringComparer.Compare(object{NullableSuffix} x, object{NullableSuffix} y) -> int (forwarded, contained in {containingAssembly}) - System.StringComparer.Equals(object{NullableSuffix} x, object{NullableSuffix} y) -> bool (forwarded, contained in {containingAssembly}) - System.StringComparer.GetHashCode(object{NonNullSuffix} obj) -> int (forwarded, contained in {containingAssembly}) - abstract System.StringComparer.Compare(string{NullableSuffix} x, string{NullableSuffix} y) -> int (forwarded, contained in {containingAssembly}) - abstract System.StringComparer.Equals(string{NullableSuffix} x, string{NullableSuffix} y) -> bool (forwarded, contained in {containingAssembly}) - abstract System.StringComparer.GetHashCode(string{NonNullSuffix} obj) -> int (forwarded, contained in {containingAssembly}) - System.StringComparer.StringComparer() -> void (forwarded, contained in {containingAssembly}) + const string forwardedTypeAssemblyName = "ForwardedTypeAssembly"; + const string forwardedTypeSource = """ - """; + namespace TypeForwarding + { + public static class ForwardedType + { + public static int GetValue() => 0; + } + } -#if NETCOREAPP - shippedText = $""" + """; + const string forwardedTypeApi = """ - #nullable enable - {shippedText} - static System.StringComparer.Create(System.Globalization.CultureInfo{NonNullSuffix} culture, System.Globalization.CompareOptions options) -> System.StringComparer{NonNullSuffix} (forwarded, contained in {containingAssembly}) - static System.StringComparer.FromComparison(System.StringComparison comparisonType) -> System.StringComparer{NonNullSuffix} (forwarded, contained in {containingAssembly}) + TypeForwarding.ForwardedType + static TypeForwarding.ForwardedType.GetValue() -> int """; -#endif + var test = new CSharpCodeFixTest + { + ReferenceAssemblies = ReferenceAssemblies.Default, + TestState = + { + Sources = + { + """ - await VerifyCSharpAsync(""" + [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(TypeForwarding.ForwardedType))] + + """, + }, + AdditionalFiles = + { + (ShippedFileName, """ - [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.StringComparer))] + TypeForwarding.ForwardedType (forwarded, contained in ForwardedTypeAssembly) + static TypeForwarding.ForwardedType.GetValue() -> int (forwarded, contained in ForwardedTypeAssembly) - """, shippedText, $@""); + """), + (UnshippedFileName, ""), + }, + AdditionalProjects = + { + [forwardedTypeAssemblyName] = + { + Sources = { forwardedTypeSource }, + AdditionalFiles = + { + (ShippedFileName, forwardedTypeApi), + (UnshippedFileName, ""), + }, + }, + }, + AdditionalProjectReferences = { forwardedTypeAssemblyName }, + }, + }; + + test.DisabledDiagnostics.AddRange(DisabledDiagnostics); + await test.RunAsync(); } [Fact, WorkItem(1192, "https://github.com/dotnet/roslyn-analyzers/issues/1192")] diff --git a/src/RoslynAnalyzers/Roslyn.Diagnostics.Analyzers/UnitTests/CSharpDoNotUseDebugAssertForInterpolatedStringsTests.cs b/src/RoslynAnalyzers/Roslyn.Diagnostics.Analyzers/UnitTests/CSharpDoNotUseDebugAssertForInterpolatedStringsTests.cs index cfb246b02b28b..cf29fba130b43 100644 --- a/src/RoslynAnalyzers/Roslyn.Diagnostics.Analyzers/UnitTests/CSharpDoNotUseDebugAssertForInterpolatedStringsTests.cs +++ b/src/RoslynAnalyzers/Roslyn.Diagnostics.Analyzers/UnitTests/CSharpDoNotUseDebugAssertForInterpolatedStringsTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; using Test.Utilities; using Xunit; using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< @@ -40,7 +41,7 @@ public static void Assert(bool condition, string message) { } public Task InterpolatedString(string @string) => new VerifyCS.Test { - ReferenceAssemblies = AdditionalMetadataReferences.DefaultNetFramework, + ReferenceAssemblies = ReferenceAssemblies.NetFramework.Net472.Default, TestCode = $$""" using System.Diagnostics; @@ -75,7 +76,7 @@ void M() public Task NoCrashOnUsingStaticedAssert() => new VerifyCS.Test { - ReferenceAssemblies = AdditionalMetadataReferences.DefaultNetFramework, + ReferenceAssemblies = ReferenceAssemblies.NetFramework.Net472.Default, TestCode = $$""" using static System.Diagnostics.Debug; @@ -92,6 +93,27 @@ void M() LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp12, }.RunAsync(); + [Fact] + public Task NoAssertForInterpolatedStringHandler() + => new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net100, + TestCode = $$""" + using System.Diagnostics; + + class C + { + void M() + { + Debug.Assert(false, $"{0}"); + } + } + + {{RoslynDebug}} + """, + LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp12, + }.RunAsync(); + [Theory] [InlineData(""" $"{"0"}" diff --git a/src/RoslynAnalyzers/Test.Utilities/Test.Utilities.csproj b/src/RoslynAnalyzers/Test.Utilities/Test.Utilities.csproj index 93c0156842dda..255200081d2d8 100644 --- a/src/RoslynAnalyzers/Test.Utilities/Test.Utilities.csproj +++ b/src/RoslynAnalyzers/Test.Utilities/Test.Utilities.csproj @@ -18,17 +18,18 @@ - - - - - + + + + + + diff --git a/src/RoslynSdk/Directory.Packages.props b/src/RoslynSdk/Directory.Packages.props index 341ff519960fc..112ebf77cf531 100644 --- a/src/RoslynSdk/Directory.Packages.props +++ b/src/RoslynSdk/Directory.Packages.props @@ -3,7 +3,7 @@ - 7.0.3 + 6.14.3 6.3.4 diff --git a/src/RoslynSdk/Microsoft.CodeAnalysis.Testing/Directory.Build.targets b/src/RoslynSdk/Microsoft.CodeAnalysis.Testing/Directory.Build.targets index df598bc6d0d8a..8621deac6b67e 100644 --- a/src/RoslynSdk/Microsoft.CodeAnalysis.Testing/Directory.Build.targets +++ b/src/RoslynSdk/Microsoft.CodeAnalysis.Testing/Directory.Build.targets @@ -1,4 +1,8 @@ - \ No newline at end of file + + + false + + diff --git a/src/RoslynSdk/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ReferenceAssemblies.cs b/src/RoslynSdk/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ReferenceAssemblies.cs index 5c16abaa30025..a638db0617018 100644 --- a/src/RoslynSdk/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ReferenceAssemblies.cs +++ b/src/RoslynSdk/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ReferenceAssemblies.cs @@ -97,7 +97,7 @@ public static ReferenceAssemblies Default #elif NETCOREAPP3_1 return NetCore.NetCoreApp31; #elif NET10_0_OR_GREATER - return Net.Net100; + return NetCore.NetCoreApp31; #endif } } diff --git a/src/RoslynSdk/Microsoft.CodeAnalysis.Testing/Test/Microsoft.CodeAnalysis.Analyzer.Testing.UnitTests/MetadataReferenceTests.cs b/src/RoslynSdk/Microsoft.CodeAnalysis.Testing/Test/Microsoft.CodeAnalysis.Analyzer.Testing.UnitTests/MetadataReferenceTests.cs index 8dcf5aa6c44d6..0657e000a5784 100644 --- a/src/RoslynSdk/Microsoft.CodeAnalysis.Testing/Test/Microsoft.CodeAnalysis.Analyzer.Testing.UnitTests/MetadataReferenceTests.cs +++ b/src/RoslynSdk/Microsoft.CodeAnalysis.Testing/Test/Microsoft.CodeAnalysis.Analyzer.Testing.UnitTests/MetadataReferenceTests.cs @@ -14,6 +14,12 @@ namespace Microsoft.CodeAnalysis.Testing { public class MetadataReferenceTests { +#if NET + [Fact] + public void DefaultReferenceAssembliesUseNetCoreApp31() + => Assert.Same(ReferenceAssemblies.NetCore.NetCoreApp31, ReferenceAssemblies.Default); +#endif + [Fact] public async Task ResolveReferenceAssemblies_Net20() { diff --git a/src/RoslynSdk/Samples/Directory.Packages.props b/src/RoslynSdk/Samples/Directory.Packages.props index b93b46f597493..d50ed46068a24 100644 --- a/src/RoslynSdk/Samples/Directory.Packages.props +++ b/src/RoslynSdk/Samples/Directory.Packages.props @@ -5,6 +5,10 @@ + + + + diff --git a/src/Tools/Benchmarks/GenericTypeSubstitutionBenchmarks.cs b/src/Tools/Benchmarks/GenericTypeSubstitutionBenchmarks.cs new file mode 100644 index 0000000000000..25245b9cf0e61 --- /dev/null +++ b/src/Tools/Benchmarks/GenericTypeSubstitutionBenchmarks.cs @@ -0,0 +1,120 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Linq; +using Basic.Reference.Assemblies; +using BenchmarkDotNet.Attributes; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.Text; + +namespace Benchmarks; + +public enum GenericTypeSubstitutionCase +{ + NoChange0, + NoChange1, + NoChange2, + NoChange8, + FirstChanged1, + FirstChanged2, + FirstChanged8, + LastChanged2, + LastChanged8, + ContainingTypeChanged0, + ContainingTypeChanged1, + ContainingTypeChanged2, + ContainingTypeChanged8, + NestedArgumentChanged, +} + +[MemoryDiagnoser] +public class GenericTypeSubstitutionBenchmarks +{ + private TypeMap _map = null!; + private NamedTypeSymbol _type = null!; + + [ParamsAllValues] + public GenericTypeSubstitutionCase Case { get; set; } + + [GlobalSetup] + public void Setup() + { + var arity = Case switch + { + GenericTypeSubstitutionCase.NoChange0 or GenericTypeSubstitutionCase.ContainingTypeChanged0 => 0, + GenericTypeSubstitutionCase.NoChange1 or GenericTypeSubstitutionCase.FirstChanged1 or + GenericTypeSubstitutionCase.ContainingTypeChanged1 => 1, + GenericTypeSubstitutionCase.NoChange2 or GenericTypeSubstitutionCase.FirstChanged2 or + GenericTypeSubstitutionCase.LastChanged2 or GenericTypeSubstitutionCase.ContainingTypeChanged2 or + GenericTypeSubstitutionCase.NestedArgumentChanged => 2, + GenericTypeSubstitutionCase.NoChange8 or GenericTypeSubstitutionCase.FirstChanged8 or + GenericTypeSubstitutionCase.LastChanged8 or GenericTypeSubstitutionCase.ContainingTypeChanged8 => 8, + _ => throw new InvalidOperationException(), + }; + var typeParameters = arity == 0 + ? "" + : "<" + string.Join(", ", Enumerable.Range(0, arity).Select(i => $"T{i}")) + ">"; + var source = $$""" + public class Parameters { } + public class G{{typeParameters}} { } + public class Outer + { + public class Inner{{typeParameters}} { } + } + """; + var compilation = CSharpCompilation.Create( + nameof(GenericTypeSubstitutionBenchmarks), + [CSharpSyntaxTree.ParseText(SourceText.From(source))], + Net90.References.All, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + var errors = compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).ToArray(); + if (errors.Length != 0) + { + throw new InvalidOperationException(string.Join(Environment.NewLine, errors.Select(d => d.ToString()))); + } + + var parameter = compilation.GlobalNamespace.GetTypeMembers("Parameters").Single().TypeParameters.Single(); + var intType = compilation.GetSpecialType(SpecialType.System_Int32); + var stringType = compilation.GetSpecialType(SpecialType.System_String); + _map = new TypeMap( + ImmutableArray.Create(parameter), + ImmutableArray.Create(TypeWithAnnotations.Create(intType))); + + var definition = compilation.GlobalNamespace.GetTypeMembers("G").Single(); + if (Case is GenericTypeSubstitutionCase.ContainingTypeChanged0 or + GenericTypeSubstitutionCase.ContainingTypeChanged1 or + GenericTypeSubstitutionCase.ContainingTypeChanged2 or + GenericTypeSubstitutionCase.ContainingTypeChanged8) + { + definition = compilation.GlobalNamespace.GetTypeMembers("Outer").Single() + .Construct(parameter).GetTypeMembers("Inner").Single(); + } + + var arguments = Enumerable.Repeat(stringType, arity).ToArray(); + switch (Case) + { + case GenericTypeSubstitutionCase.FirstChanged1: + case GenericTypeSubstitutionCase.FirstChanged2: + case GenericTypeSubstitutionCase.FirstChanged8: + arguments[0] = parameter; + break; + case GenericTypeSubstitutionCase.LastChanged2: + case GenericTypeSubstitutionCase.LastChanged8: + arguments[arity - 1] = parameter; + break; + case GenericTypeSubstitutionCase.NestedArgumentChanged: + arguments[0] = definition.Construct(parameter, stringType); + break; + } + + _type = arity == 0 ? definition : definition.Construct(arguments); + } + + [Benchmark] + public object Substitute() => _map.SubstituteNamedType(_type); +} diff --git a/src/Tools/RunTests/HelixTestRunner.cs b/src/Tools/RunTests/HelixTestRunner.cs index 54a01c23bca0e..4b857a29a4c2d 100644 --- a/src/Tools/RunTests/HelixTestRunner.cs +++ b/src/Tools/RunTests/HelixTestRunner.cs @@ -324,6 +324,9 @@ static void AppendHelixWorkItemProject( File.CreateSymbolicLink( path: Path.Combine(workItemPayloadDir, "global.json"), pathToTarget: Path.Combine(artifactsDir, "..", "global.json")); + File.CreateSymbolicLink( + path: Path.Combine(workItemPayloadDir, "NuGet.config"), + pathToTarget: Path.Combine(artifactsDir, "..", "NuGet.config")); var (commandFileName, commandContent) = GetHelixCommandContent(assemblyRelativeFilePaths, rspFileName, testOS); File.WriteAllText(Path.Combine(workItemPayloadDir, commandFileName), commandContent); diff --git a/src/VisualStudio/Core/Def/Guids.cs b/src/VisualStudio/Core/Def/Guids.cs index e1f0cda08ed57..fae6b2c4d34bd 100644 --- a/src/VisualStudio/Core/Def/Guids.cs +++ b/src/VisualStudio/Core/Def/Guids.cs @@ -106,13 +106,6 @@ internal static class Guids public const string RoslynCommandSetIdString = "9ed8fbd1-02d6-4223-a99c-a938f97e6dbe"; public const string RoslynGroupIdString = "b61e1a20-8c13-49a9-a727-a0ec091647dd"; - public const string RoslynOptionPageFeatureManagerComponentsIdString = "6F738951-348C-4816-9BA4-F60D92D3E98E"; - public const string RoslynOptionPageFeatureManagerFeaturesIdString = "67989704-F8D7-454A-9053-8E1D3CFF679C"; - public const string RoslynOptionPagePerformanceFunctionIdIdString = "0C537218-3BDD-4CC8-AC4B-CEC152D4871A"; - public const string RoslynOptionPagePerformanceLoggersIdString = "236AC96F-A60D-4BD6-A480-D315151EDC2B"; - public const string RoslynOptionPageInternalDiagnosticsIdString = "48993C4C-C619-42AD-B1C8-79378AD8BEF2"; - public const string RoslynOptionPageInternalSolutionCrawlerIdString = "9702D3BD-F06C-4A6A-974B-7D0C2BC89A72"; - public static readonly Guid RoslynPackageId = new(RoslynPackageIdString); public static readonly Guid RoslynCommandSetId = new(RoslynCommandSetIdString); public static readonly Guid RoslynGroupId = new(RoslynGroupIdString); diff --git a/src/VisualStudio/Core/Def/Log/LoggerOptionsStorage.cs b/src/VisualStudio/Core/Def/Log/LoggerOptionsStorage.cs deleted file mode 100644 index 1c1ae22263b3d..0000000000000 --- a/src/VisualStudio/Core/Def/Log/LoggerOptionsStorage.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CodeAnalysis.Options; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation; - -internal sealed class LoggerOptionsStorage -{ - public static readonly Option2 EtwLoggerKey = new("visual_studio_etw_logger_key", defaultValue: true); - public static readonly Option2 TraceLoggerKey = new("visual_studio_trace_logger_key", defaultValue: false); - public static readonly Option2 OutputWindowLoggerKey = new("visual_studio_output_window_logger_key", defaultValue: false); -} diff --git a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj index 8ea29594e886d..d3fe21937918c 100644 --- a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj +++ b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj @@ -76,7 +76,6 @@ - diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs index 27bb2d171d873..40e864d2974a5 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs @@ -403,9 +403,6 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti {"visual_studio_key_binding_needs_reset", new LocalUserProfileStorage(@"Roslyn\Internal\KeybindingsStatus", "NeedsReset")}, {"visual_studio_key_binding_reset_never_show_again", new LocalUserProfileStorage(@"Roslyn\Internal\KeybindingsStatus", "NeverShowAgain")}, {"visual_studio_resharper_key_binding_status", new LocalUserProfileStorage(@"Roslyn\Internal\KeybindingsStatus", "ReSharperStatus")}, - {"visual_studio_etw_logger_key", new LocalUserProfileStorage(@"Roslyn\Internal\Performance\Logger", "EtwLogger")}, - {"visual_studio_output_window_logger_key", new LocalUserProfileStorage(@"Roslyn\Internal\Performance\Logger", "OutputWindowLogger")}, - {"visual_studio_trace_logger_key", new LocalUserProfileStorage(@"Roslyn\Internal\Performance\Logger", "TraceLogger")}, {"dotnet_enable_lsp_editor", new FeatureFlagStorage(@"Roslyn.LSP.Editor")}, {"dotnet_enable_lsp_semantic_tokens", new FeatureFlagStorage(@"Roslyn.LSP.SemanticTokens")}, {"dotnet_lsp_max_completion_list_size", new LocalUserProfileStorage(@"Roslyn\Internal\Lsp", "MaxCompletionListSize")}, diff --git a/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs b/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs index 95cd1f86b6af9..7313124694ab7 100644 --- a/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs +++ b/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs @@ -32,10 +32,11 @@ public void InitializeTelemetrySession(TelemetrySession telemetrySession, bool l Contract.ThrowIfFalse(CurrentSession is null); var metricSink = new VSMetricSink(telemetrySession); + Debug.Assert(RoslynTelemetry.IsDefault(RoslynTelemetry.Current)); _registrations = [ - .. CreateEventSinks(telemetrySession, logDelta).SelectAsArray(RoslynTelemetry.AddEventSink), - RoslynTelemetry.AddMetricSink(metricSink), + .. CreateEventSinks(telemetrySession, logDelta).SelectAsArray(RoslynTelemetry.Current.AddEventSink), + RoslynTelemetry.Current.AddMetricSink(metricSink), metricSink, ]; @@ -70,7 +71,7 @@ public void Dispose() { // Ensure any aggregate telemetry is flushed when the catalog is destroyed. // It is fine for this to be called multiple times - if telemetry has already been flushed this will no-op. - RoslynTelemetry.Flush(); + RoslynTelemetry.Current.Flush(); foreach (var registration in _registrations) registration.Dispose(); diff --git a/src/VisualStudio/Core/Impl/Microsoft.VisualStudio.LanguageServices.Implementation.csproj b/src/VisualStudio/Core/Impl/Microsoft.VisualStudio.LanguageServices.Implementation.csproj index d54df109ad905..7eb4e6d84cc8a 100644 --- a/src/VisualStudio/Core/Impl/Microsoft.VisualStudio.LanguageServices.Implementation.csproj +++ b/src/VisualStudio/Core/Impl/Microsoft.VisualStudio.LanguageServices.Implementation.csproj @@ -22,7 +22,6 @@ - diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index 1d433ee495a4b..4c732eaf2bdac 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -1949,7 +1949,7 @@ void M() Assert.Equal("CSharp.ConflictMarkerResolution.CSharpResolveConflictMarkerCodeFixProvider", result.CodeFixAnalysis.DiagnosticIdToProviderName["CS8300"].Single()); var logger = new TestTelemetryEventSink(); - using var _ = RoslynTelemetry.AddEventSink(logger); + using var _ = RoslynTelemetry.Current.AddEventSink(logger); TestTelemetryEventSink.TestScope scope; using (CopilotChangeAnalysisUtilities.LogCopilotChangeAnalysis("TestCode", accepted: true, "TestProposalId", result, CancellationToken.None)) { diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests_Telemetry.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests_Telemetry.cs deleted file mode 100644 index 5068de1d1a073..0000000000000 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests_Telemetry.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.Remote.Testing; -using Xunit; - -namespace Roslyn.VisualStudio.Next.UnitTests.Remote; - -public sealed partial class ServiceHubServicesTests -{ - /// - /// The Performance Loggers options page pushes diagnostic logger enablement into the OOP process - /// over this API. Covers that the remote host registers a sink while it is enabled and unregisters - /// it when it is not, observed through whether anything is listening for a given - /// - which is also what makes logging free when nothing is. - /// - [Fact] - public async Task TestRemoteProcessEnableLogging() - { - using var workspace = CreateWorkspace(); - using var client = await InProcRemoteHostClient.GetTestClientAsync(workspace).ConfigureAwait(false); - - Assert.False(AnythingIsListening(FunctionId.TestEvent_NotUsed)); - - Assert.True(await SetRemoteLoggingAsync([nameof(TraceEventSink)], [FunctionId.TestEvent_NotUsed])); - Assert.True(AnythingIsListening(FunctionId.TestEvent_NotUsed)); - - // The sink was built with a predicate covering only the requested ids. - Assert.False(AnythingIsListening(FunctionId.RemoteHost_Connect)); - - Assert.True(await SetRemoteLoggingAsync([], [])); - Assert.False(AnythingIsListening(FunctionId.TestEvent_NotUsed)); - - Task SetRemoteLoggingAsync(ImmutableArray loggerTypeNames, ImmutableArray functionIds) - => client.TryInvokeAsync( - (service, cancellationToken) => service.EnableLoggingAsync(loggerTypeNames, functionIds, cancellationToken), - CancellationToken.None).AsTask(); - - // RoslynTelemetry only builds a message when some sink is enabled for the id, so the message - // factory running is exactly "a sink is registered and wants this id". - static bool AnythingIsListening(FunctionId functionId) - { - var listening = false; - RoslynTelemetry.Log(functionId, () => - { - listening = true; - return string.Empty; - }); - - return listening; - } - } -} diff --git a/src/VisualStudio/EditorConfig/Command/Microsoft.VisualStudio.Templates.Editorconfig.Command.csproj b/src/VisualStudio/EditorConfig/Command/Microsoft.VisualStudio.Templates.Editorconfig.Command.csproj index 0d40657c388c8..8b3c2d63b2e62 100644 --- a/src/VisualStudio/EditorConfig/Command/Microsoft.VisualStudio.Templates.Editorconfig.Command.csproj +++ b/src/VisualStudio/EditorConfig/Command/Microsoft.VisualStudio.Templates.Editorconfig.Command.csproj @@ -4,6 +4,7 @@ net472 false enable + true diff --git a/src/VisualStudio/EditorConfig/Wizard/Microsoft.VisualStudio.Templates.Editorconfig.Wizard.csproj b/src/VisualStudio/EditorConfig/Wizard/Microsoft.VisualStudio.Templates.Editorconfig.Wizard.csproj index 66315e10d5425..60b82b2288082 100644 --- a/src/VisualStudio/EditorConfig/Wizard/Microsoft.VisualStudio.Templates.Editorconfig.Wizard.csproj +++ b/src/VisualStudio/EditorConfig/Wizard/Microsoft.VisualStudio.Templates.Editorconfig.Wizard.csproj @@ -3,6 +3,7 @@ net472 Templates.Editorconfig.Wizard + true false enable diff --git a/src/VisualStudio/EditorConfig/WizardSetup/Microsoft.VisualStudio.Templates.Editorconfig.Wizard.Setup.csproj b/src/VisualStudio/EditorConfig/WizardSetup/Microsoft.VisualStudio.Templates.Editorconfig.Wizard.Setup.csproj index 6efe47b469235..b9b42413158af 100644 --- a/src/VisualStudio/EditorConfig/WizardSetup/Microsoft.VisualStudio.Templates.Editorconfig.Wizard.Setup.csproj +++ b/src/VisualStudio/EditorConfig/WizardSetup/Microsoft.VisualStudio.Templates.Editorconfig.Wizard.Setup.csproj @@ -2,6 +2,7 @@ net472 + true true diff --git a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.cs.xlf b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.cs.xlf index af42399220b2b..22fdd4e6e0fa7 100644 --- a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.cs.xlf +++ b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.cs.xlf @@ -4,7 +4,7 @@ editorconfig File (empty) - editorconfig File (empty) + Soubor editorconfig (prázdný) diff --git a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.de.xlf b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.de.xlf index 5041ec579765c..d6b8e6f9965e9 100644 --- a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.de.xlf +++ b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.de.xlf @@ -4,7 +4,7 @@ editorconfig File (empty) - editorconfig File (empty) + editorconfig-Datei (leer) diff --git a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.es.xlf b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.es.xlf index 2759e1cae4155..eabfbe59fe151 100644 --- a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.es.xlf +++ b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.es.xlf @@ -4,7 +4,7 @@ editorconfig File (empty) - editorconfig File (empty) + Archivo editorconfig (vacío) diff --git a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.fr.xlf b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.fr.xlf index f1e41fe936e62..6187088b78103 100644 --- a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.fr.xlf +++ b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.fr.xlf @@ -4,7 +4,7 @@ editorconfig File (empty) - editorconfig File (empty) + Fichier editorconfig (vide) diff --git a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.it.xlf b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.it.xlf index 5a3d03bf38bab..977a908c6eaa5 100644 --- a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.it.xlf +++ b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.it.xlf @@ -4,7 +4,7 @@ editorconfig File (empty) - editorconfig File (empty) + File editorconfig (vuoto) diff --git a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.ja.xlf b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.ja.xlf index 6294de87f496a..5450c84c46caa 100644 --- a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.ja.xlf +++ b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.ja.xlf @@ -4,7 +4,7 @@ editorconfig File (empty) - editorconfig File (empty) + editorconfig ファイル (空) diff --git a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.ko.xlf b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.ko.xlf index a96f0153ef1bc..a6b91d998dace 100644 --- a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.ko.xlf +++ b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.ko.xlf @@ -4,7 +4,7 @@ editorconfig File (empty) - editorconfig File (empty) + editorconfig 파일(비어 있음) diff --git a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.pl.xlf b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.pl.xlf index a82ee615a02fd..d45f429b4e29d 100644 --- a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.pl.xlf +++ b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.pl.xlf @@ -4,7 +4,7 @@ editorconfig File (empty) - editorconfig File (empty) + Plik editorconfig (pusty) diff --git a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.pt-BR.xlf b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.pt-BR.xlf index b1497d80f06d5..f1c044005149e 100644 --- a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.pt-BR.xlf +++ b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.pt-BR.xlf @@ -4,7 +4,7 @@ editorconfig File (empty) - editorconfig File (empty) + Arquivo editorconfig (vazio) diff --git a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.ru.xlf b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.ru.xlf index e51389c59a058..5b6b1bbeb8889 100644 --- a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.ru.xlf +++ b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.ru.xlf @@ -4,7 +4,7 @@ editorconfig File (empty) - editorconfig File (empty) + Файл editorconfig (пустой) diff --git a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.tr.xlf b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.tr.xlf index b589d1c3fae52..0eee12f4d8829 100644 --- a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.tr.xlf +++ b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.tr.xlf @@ -4,7 +4,7 @@ editorconfig File (empty) - editorconfig File (empty) + editorconfig Dosyası (boş) diff --git a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.zh-Hans.xlf b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.zh-Hans.xlf index 356dea78e04aa..05a91c8ca3dfb 100644 --- a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.zh-Hans.xlf +++ b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.zh-Hans.xlf @@ -4,7 +4,7 @@ editorconfig File (empty) - editorconfig File (empty) + editorconfig 文件(空) diff --git a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.zh-Hant.xlf b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.zh-Hant.xlf index 856be006d5694..821e437f3fe1e 100644 --- a/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.zh-Hant.xlf +++ b/src/VisualStudio/EditorConfig/WizardSetup/xlf/VSPackage.zh-Hant.xlf @@ -4,7 +4,7 @@ editorconfig File (empty) - editorconfig File (empty) + editorconfig 檔案 (空白) diff --git a/src/VisualStudio/ExternalAccess/Core/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs b/src/VisualStudio/ExternalAccess/Core/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs index a051d255403d7..7a2092862c553 100644 --- a/src/VisualStudio/ExternalAccess/Core/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs +++ b/src/VisualStudio/ExternalAccess/Core/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs @@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.NavigateTo; [ExportLanguageService(typeof(INavigateToSearchService), LanguageNames.FSharp)] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal class FSharpNavigateToSearchService([Import(AllowDefault = true)] IFSharpNavigateToSearchService? service) : INavigateToSearchService +internal class FSharpNavigateToSearchService([Import(AllowDefault = true)] IFSharpNavigateToSearchService? service) : IAdvancedNavigateToSearchService { private readonly IFSharpNavigateToSearchService? _service = service; @@ -33,7 +33,7 @@ public async Task SearchDocumentAsync( Func, Task> onResultsFound, CancellationToken cancellationToken) { - if (_service == null) + if (_service is null) return; var results = await _service.SearchDocumentAsync(document, searchPattern, kinds, cancellationToken).ConfigureAwait(false); @@ -52,19 +52,66 @@ public async Task SearchProjectsAsync( Func onProjectCompleted, CancellationToken cancellationToken) { - if (_service == null) - return; - Contract.ThrowIfTrue(projects.IsEmpty); Contract.ThrowIfTrue(projects.Select(p => p.Language).Distinct().Count() != 1); foreach (var project in projects) { - var results = await _service.SearchProjectAsync(project, priorityDocuments, searchPattern, kinds, cancellationToken).ConfigureAwait(false); - if (results.Length > 0) - await onResultsFound(results.SelectAsArray(result => (INavigateToSearchResult)new InternalFSharpNavigateToSearchResult(result))).ConfigureAwait(false); + if (_service is not null) + { + var results = await _service.SearchProjectAsync(project, priorityDocuments, searchPattern, kinds, cancellationToken).ConfigureAwait(false); + if (results.Length > 0) + await onResultsFound(results.SelectAsArray(result => (INavigateToSearchResult)new InternalFSharpNavigateToSearchResult(result))).ConfigureAwait(false); + } await onProjectCompleted().ConfigureAwait(false); } } + + public async Task SearchCachedDocumentsAsync( + Solution solution, + ImmutableArray projects, + ImmutableArray priorityDocuments, + string searchPattern, + IImmutableSet kinds, + Document? activeDocument, + Func, Task> onResultsFound, + Func onProjectCompleted, + CancellationToken cancellationToken) + { + Contract.ThrowIfTrue(projects.IsEmpty); + Contract.ThrowIfTrue(projects.Select(p => p.Language).Distinct().Count() != 1); + + // if the service doesn't support searching cached docs, immediately transition the projects to the + // completed state. + if (_service is not IFSharpAdvancedNavigateToSearchService advancedService) + { + foreach (var _ in projects) + await onProjectCompleted().ConfigureAwait(false); + } + else + { + await advancedService.SearchCachedDocumentsAsync( + solution, projects, priorityDocuments, searchPattern, kinds, activeDocument, + results => onResultsFound(results.SelectAsArray(result => (INavigateToSearchResult)new InternalFSharpNavigateToSearchResult(result))), + onProjectCompleted, cancellationToken).ConfigureAwait(false); + } + } + + public async Task SearchGeneratedDocumentsAsync( + Solution solution, + ImmutableArray projects, + string searchPattern, + IImmutableSet kinds, + Document? activeDocument, + Func, Task> onResultsFound, + Func onProjectCompleted, + CancellationToken cancellationToken) + { + // Nothing to search: F# has no Roslyn source generators, and type providers give types during checking + // rather than documents. The projects still have to be reported for the progress the searcher added for + // them to complete. + foreach (var _ in projects) + await onProjectCompleted().ConfigureAwait(false); + } } diff --git a/src/VisualStudio/ExternalAccess/Core/FSharp/NavigateTo/IFSharpAdvancedNavigateToSearchService.cs b/src/VisualStudio/ExternalAccess/Core/FSharp/NavigateTo/IFSharpAdvancedNavigateToSearchService.cs new file mode 100644 index 0000000000000..860737cf82ace --- /dev/null +++ b/src/VisualStudio/ExternalAccess/Core/FSharp/NavigateTo/IFSharpAdvancedNavigateToSearchService.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo; + +/// +/// Optional expanded API for Navigate-To. An that also implements this +/// takes part in the search that runs while the solution is still loading; one that does not is skipped until the +/// solution is fully loaded, and so contributes nothing to that search. +/// +internal interface IFSharpAdvancedNavigateToSearchService : IFSharpNavigateToSearchService +{ + /// + /// Searches the documents inside for symbols that match . Results should be reported from a previously computed cache (even if that cache is out + /// of date) to produce results as quickly as possible. This is called for every project of the solution while it + /// loads, so it must not wait on anything that only becomes available once the project has loaded. + /// + /// + /// All the projects passed are for F#. Similarly, all the belong to these + /// projects. must be called exactly once per project, whether or not + /// anything was found in it. + /// + Task SearchCachedDocumentsAsync( + Solution solution, + ImmutableArray projects, + ImmutableArray priorityDocuments, + string searchPattern, + IImmutableSet kinds, + Document? activeDocument, + Func, Task> onResultsFound, + Func onProjectCompleted, + CancellationToken cancellationToken); +} diff --git a/src/VisualStudio/ExternalAccess/Core/FSharp/VS/IFSharpWorkspaceProjectContextFactory.cs b/src/VisualStudio/ExternalAccess/Core/FSharp/VS/IFSharpWorkspaceProjectContextFactory.cs index 2cfbebac7388f..007fb5c552adb 100644 --- a/src/VisualStudio/ExternalAccess/Core/FSharp/VS/IFSharpWorkspaceProjectContextFactory.cs +++ b/src/VisualStudio/ExternalAccess/Core/FSharp/VS/IFSharpWorkspaceProjectContextFactory.cs @@ -8,6 +8,7 @@ using System.Composition; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.VisualStudio.LanguageServices.ProjectSystem; @@ -18,6 +19,13 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp; internal interface IFSharpWorkspaceProjectContextFactory { IFSharpWorkspaceProjectContext CreateProjectContext(string filePath, string uniqueName); + + /// + /// Creates the project context without blocking. waits for + /// this through the main thread, so a caller that runs while the main thread is blocked — inside the Peek + /// broker's JoinableTaskFactory.Run, say — deadlocks on it. + /// + Task CreateProjectContextAsync(string filePath, string uniqueName, CancellationToken cancellationToken); } internal interface IFSharpWorkspaceProjectContext : IDisposable @@ -54,6 +62,9 @@ public FSharpWorkspaceProjectContextFactory(IWorkspaceProjectContextFactory fact IFSharpWorkspaceProjectContext IFSharpWorkspaceProjectContextFactory.CreateProjectContext(string filePath, string uniqueName) => CreateProjectContext(filePath, uniqueName); + async Task IFSharpWorkspaceProjectContextFactory.CreateProjectContextAsync(string filePath, string uniqueName, CancellationToken cancellationToken) + => await CreateProjectContextAsync(filePath, uniqueName, cancellationToken).ConfigureAwait(false); + public FSharpWorkspaceProjectContext CreateProjectContext(string filePath, string uniqueName) => CreateProjectContext( projectUniqueName: uniqueName, @@ -62,14 +73,27 @@ public FSharpWorkspaceProjectContext CreateProjectContext(string filePath, strin hierarchy: null, binOutputPath: null); + public Task CreateProjectContextAsync(string filePath, string uniqueName, CancellationToken cancellationToken) + => CreateProjectContextAsync( + projectUniqueName: uniqueName, + projectFilePath: filePath, + projectGuid: Guid.NewGuid(), + hierarchy: null, + binOutputPath: null, + cancellationToken); + public FSharpWorkspaceProjectContext CreateProjectContext(string projectUniqueName, string projectFilePath, Guid projectGuid, object? hierarchy, string? binOutputPath) - => new(_threadingContext.JoinableTaskFactory.Run(() => _factory.CreateProjectContextAsync( + => _threadingContext.JoinableTaskFactory.Run(() => CreateProjectContextAsync( + projectUniqueName, projectFilePath, projectGuid, hierarchy, binOutputPath, CancellationToken.None)); + + public async Task CreateProjectContextAsync(string projectUniqueName, string projectFilePath, Guid projectGuid, object? hierarchy, string? binOutputPath, CancellationToken cancellationToken) + => new(await _factory.CreateProjectContextAsync( id: projectGuid, uniqueName: projectUniqueName, languageName: LanguageNames.FSharp, data: new FSharpEvaluationData(projectFilePath, binOutputPath), hostObject: hierarchy, - CancellationToken.None))); + cancellationToken).ConfigureAwait(false)); private sealed class FSharpEvaluationData : EvaluationData { diff --git a/src/VisualStudio/ExternalAccess/Core/InternalAPI.Unshipped.txt b/src/VisualStudio/ExternalAccess/Core/InternalAPI.Unshipped.txt index 77c661ed5a023..749e7344e564c 100644 --- a/src/VisualStudio/ExternalAccess/Core/InternalAPI.Unshipped.txt +++ b/src/VisualStudio/ExternalAccess/Core/InternalAPI.Unshipped.txt @@ -142,6 +142,8 @@ Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContext.SetPr Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContextFactory Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContextFactory.CreateProjectContext(string! filePath, string! uniqueName) -> Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContext! Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContextFactory.CreateProjectContext(string! projectUniqueName, string! projectFilePath, System.Guid projectGuid, object? hierarchy, string? binOutputPath) -> Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContext! +Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContextFactory.CreateProjectContextAsync(string! filePath, string! uniqueName, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContextFactory.CreateProjectContextAsync(string! projectUniqueName, string! projectFilePath, System.Guid projectGuid, object? hierarchy, string? binOutputPath, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContextFactory.FSharpWorkspaceProjectContextFactory(Microsoft.VisualStudio.LanguageServices.ProjectSystem.IWorkspaceProjectContextFactory! factory, Microsoft.CodeAnalysis.Editor.Shared.Utilities.IThreadingContext! threadingContext) -> void Microsoft.CodeAnalysis.ExternalAccess.FSharp.IFSharpWorkspaceProjectContext Microsoft.CodeAnalysis.ExternalAccess.FSharp.IFSharpWorkspaceProjectContext.AddMetadataReference(string! referencePath) -> void @@ -158,6 +160,7 @@ Microsoft.CodeAnalysis.ExternalAccess.FSharp.IFSharpWorkspaceProjectContext.SetM Microsoft.CodeAnalysis.ExternalAccess.FSharp.IFSharpWorkspaceProjectContext.SetProjectReferences(System.Collections.Generic.IEnumerable! projRefs) -> void Microsoft.CodeAnalysis.ExternalAccess.FSharp.IFSharpWorkspaceProjectContextFactory Microsoft.CodeAnalysis.ExternalAccess.FSharp.IFSharpWorkspaceProjectContextFactory.CreateProjectContext(string! filePath, string! uniqueName) -> Microsoft.CodeAnalysis.ExternalAccess.FSharp.IFSharpWorkspaceProjectContext! +Microsoft.CodeAnalysis.ExternalAccess.FSharp.IFSharpWorkspaceProjectContextFactory.CreateProjectContextAsync(string! filePath, string! uniqueName, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.Unified.Apex.ApexAsynchronousOperationListenerProviderAccessor Microsoft.CodeAnalysis.ExternalAccess.Unified.Apex.ApexAsynchronousOperationListenerProviderAccessor.ApexAsynchronousOperationListenerProviderAccessor(Microsoft.CodeAnalysis.Shared.TestHooks.AsynchronousOperationListenerProvider! implementation, Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace? workspace) -> void Microsoft.CodeAnalysis.ExternalAccess.Unified.Apex.ApexAsynchronousOperationListenerProviderAccessor.WaitAllAsync(string![]? featureNames = null, System.Action? eventProcessingAction = null, System.TimeSpan? timeout = null) -> System.Threading.Tasks.Task! @@ -435,7 +438,9 @@ Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.Internal.NavigateTo.FSharpN Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.Internal.NavigateTo.FSharpNavigateToSearchService.CanFilter.get -> bool Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.Internal.NavigateTo.FSharpNavigateToSearchService.FSharpNavigateToSearchService(Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.NavigateTo.IFSharpNavigateToSearchService? service) -> void Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.Internal.NavigateTo.FSharpNavigateToSearchService.KindsProvided.get -> System.Collections.Immutable.IImmutableSet! +Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.Internal.NavigateTo.FSharpNavigateToSearchService.SearchCachedDocumentsAsync(Microsoft.CodeAnalysis.Solution! solution, System.Collections.Immutable.ImmutableArray projects, System.Collections.Immutable.ImmutableArray priorityDocuments, string! searchPattern, System.Collections.Immutable.IImmutableSet! kinds, Microsoft.CodeAnalysis.Document? activeDocument, System.Func, System.Threading.Tasks.Task!>! onResultsFound, System.Func! onProjectCompleted, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.Internal.NavigateTo.FSharpNavigateToSearchService.SearchDocumentAsync(Microsoft.CodeAnalysis.Document! document, string! searchPattern, System.Collections.Immutable.IImmutableSet! kinds, System.Func, System.Threading.Tasks.Task!>! onResultsFound, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.Internal.NavigateTo.FSharpNavigateToSearchService.SearchGeneratedDocumentsAsync(Microsoft.CodeAnalysis.Solution! solution, System.Collections.Immutable.ImmutableArray projects, string! searchPattern, System.Collections.Immutable.IImmutableSet! kinds, Microsoft.CodeAnalysis.Document? activeDocument, System.Func, System.Threading.Tasks.Task!>! onResultsFound, System.Func! onProjectCompleted, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.Internal.NavigateTo.FSharpNavigateToSearchService.SearchProjectsAsync(Microsoft.CodeAnalysis.Solution! solution, System.Collections.Immutable.ImmutableArray projects, System.Collections.Immutable.ImmutableArray priorityDocuments, string! searchPattern, System.Collections.Immutable.IImmutableSet! kinds, Microsoft.CodeAnalysis.Document? activeDocument, System.Func, System.Threading.Tasks.Task!>! onResultsFound, System.Func! onProjectCompleted, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.Internal.NavigateTo.InternalFSharpNavigateToSearchResult Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.Internal.NavigateTo.InternalFSharpNavigateToSearchResult.IsCaseSensitive.get -> bool @@ -480,7 +485,9 @@ Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.NavigateTo.FSharpNavigateTo Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.NavigateTo.FSharpNavigateToMatchKind.Substring = 2 -> Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.NavigateTo.FSharpNavigateToMatchKind Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.NavigateTo.FSharpNavigateToSearchResult Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.NavigateTo.FSharpNavigateToSearchResult.MatchKind.get -> Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.NavigateTo.FSharpNavigateToMatchKind +Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.NavigateTo.IFSharpAdvancedNavigateToSearchService Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.NavigateTo.IFSharpNavigateToSearchService +Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.NavigateTo.IFSharpAdvancedNavigateToSearchService.SearchCachedDocumentsAsync(Microsoft.CodeAnalysis.Solution! solution, System.Collections.Immutable.ImmutableArray projects, System.Collections.Immutable.ImmutableArray priorityDocuments, string! searchPattern, System.Collections.Immutable.IImmutableSet! kinds, Microsoft.CodeAnalysis.Document? activeDocument, System.Func, System.Threading.Tasks.Task!>! onResultsFound, System.Func! onProjectCompleted, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.NavigateTo.IFSharpNavigateToSearchService.CanFilter.get -> bool Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.NavigateTo.IFSharpNavigateToSearchService.KindsProvided.get -> System.Collections.Immutable.IImmutableSet! Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.NavigateTo.IFSharpNavigateToSearchService.SearchDocumentAsync(Microsoft.CodeAnalysis.Document! document, string! searchPattern, System.Collections.Immutable.IImmutableSet! kinds, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! @@ -843,7 +850,9 @@ Microsoft.VisualStudio.ExternalAccess.FSharp.Internal.NavigateTo.FSharpNavigateT Microsoft.VisualStudio.ExternalAccess.FSharp.Internal.NavigateTo.FSharpNavigateToSearchService.CanFilter.get -> bool Microsoft.VisualStudio.ExternalAccess.FSharp.Internal.NavigateTo.FSharpNavigateToSearchService.FSharpNavigateToSearchService(Microsoft.VisualStudio.ExternalAccess.FSharp.NavigateTo.IFSharpNavigateToSearchService! service) -> void Microsoft.VisualStudio.ExternalAccess.FSharp.Internal.NavigateTo.FSharpNavigateToSearchService.KindsProvided.get -> System.Collections.Immutable.IImmutableSet! +Microsoft.VisualStudio.ExternalAccess.FSharp.Internal.NavigateTo.FSharpNavigateToSearchService.SearchCachedDocumentsAsync(Microsoft.CodeAnalysis.Solution! solution, System.Collections.Immutable.ImmutableArray projects, System.Collections.Immutable.ImmutableArray priorityDocuments, string! searchPattern, System.Collections.Immutable.IImmutableSet! kinds, Microsoft.CodeAnalysis.Document? activeDocument, System.Func, System.Threading.Tasks.Task!>! onResultsFound, System.Func! onProjectCompleted, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.VisualStudio.ExternalAccess.FSharp.Internal.NavigateTo.FSharpNavigateToSearchService.SearchDocumentAsync(Microsoft.CodeAnalysis.Document! document, string! searchPattern, System.Collections.Immutable.IImmutableSet! kinds, System.Func, System.Threading.Tasks.Task!>! onResultsFound, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +Microsoft.VisualStudio.ExternalAccess.FSharp.Internal.NavigateTo.FSharpNavigateToSearchService.SearchGeneratedDocumentsAsync(Microsoft.CodeAnalysis.Solution! solution, System.Collections.Immutable.ImmutableArray projects, string! searchPattern, System.Collections.Immutable.IImmutableSet! kinds, Microsoft.CodeAnalysis.Document? activeDocument, System.Func, System.Threading.Tasks.Task!>! onResultsFound, System.Func! onProjectCompleted, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.VisualStudio.ExternalAccess.FSharp.Internal.NavigateTo.FSharpNavigateToSearchService.SearchProjectsAsync(Microsoft.CodeAnalysis.Solution! solution, System.Collections.Immutable.ImmutableArray projects, System.Collections.Immutable.ImmutableArray priorityDocuments, string! searchPattern, System.Collections.Immutable.IImmutableSet! kinds, Microsoft.CodeAnalysis.Document? activeDocument, System.Func, System.Threading.Tasks.Task!>! onResultsFound, System.Func! onProjectCompleted, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.VisualStudio.ExternalAccess.FSharp.Internal.NavigateTo.InternalFSharpNavigateToSearchResult Microsoft.VisualStudio.ExternalAccess.FSharp.Internal.NavigateTo.InternalFSharpNavigateToSearchResult.IsCaseSensitive.get -> bool @@ -888,7 +897,9 @@ Microsoft.VisualStudio.ExternalAccess.FSharp.NavigateTo.FSharpNavigateToMatchKin Microsoft.VisualStudio.ExternalAccess.FSharp.NavigateTo.FSharpNavigateToMatchKind.Substring = 2 ->Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.NavigateTo.FSharpNavigateToMatchKind Microsoft.VisualStudio.ExternalAccess.FSharp.NavigateTo.FSharpNavigateToSearchResult Microsoft.VisualStudio.ExternalAccess.FSharp.NavigateTo.FSharpNavigateToSearchResult.MatchKind.get ->Microsoft.CodeAnalysis.ExternalAccess.Unified.FSharp.NavigateTo.FSharpNavigateToMatchKind +Microsoft.VisualStudio.ExternalAccess.FSharp.NavigateTo.IFSharpAdvancedNavigateToSearchService Microsoft.VisualStudio.ExternalAccess.FSharp.NavigateTo.IFSharpNavigateToSearchService +Microsoft.VisualStudio.ExternalAccess.FSharp.NavigateTo.IFSharpAdvancedNavigateToSearchService.SearchCachedDocumentsAsync(Microsoft.CodeAnalysis.Solution! solution, System.Collections.Immutable.ImmutableArray projects, System.Collections.Immutable.ImmutableArray priorityDocuments, string! searchPattern, System.Collections.Immutable.IImmutableSet! kinds, Microsoft.CodeAnalysis.Document? activeDocument, System.Func, System.Threading.Tasks.Task!>! onResultsFound, System.Func! onProjectCompleted, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.VisualStudio.ExternalAccess.FSharp.NavigateTo.IFSharpNavigateToSearchService.CanFilter.get -> bool Microsoft.VisualStudio.ExternalAccess.FSharp.NavigateTo.IFSharpNavigateToSearchService.KindsProvided.get -> System.Collections.Immutable.IImmutableSet! Microsoft.VisualStudio.ExternalAccess.FSharp.NavigateTo.IFSharpNavigateToSearchService.SearchDocumentAsync(Microsoft.CodeAnalysis.Document! document, string! searchPattern, System.Collections.Immutable.IImmutableSet! kinds, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! diff --git a/src/VisualStudio/ExternalAccess/FSharp/InternalAPI.Unshipped.txt b/src/VisualStudio/ExternalAccess/FSharp/InternalAPI.Unshipped.txt index 7e0790c7be3d4..e5617fd985122 100644 --- a/src/VisualStudio/ExternalAccess/FSharp/InternalAPI.Unshipped.txt +++ b/src/VisualStudio/ExternalAccess/FSharp/InternalAPI.Unshipped.txt @@ -271,6 +271,8 @@ Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContext.SetPr Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContextFactory Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContextFactory.CreateProjectContext(string! filePath, string! uniqueName) -> Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContext! Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContextFactory.CreateProjectContext(string! projectUniqueName, string! projectFilePath, System.Guid projectGuid, object? hierarchy, string? binOutputPath) -> Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContext! +Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContextFactory.CreateProjectContextAsync(string! filePath, string! uniqueName, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContextFactory.CreateProjectContextAsync(string! projectUniqueName, string! projectFilePath, System.Guid projectGuid, object? hierarchy, string? binOutputPath, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpWorkspaceProjectContextFactory.FSharpWorkspaceProjectContextFactory(Microsoft.VisualStudio.LanguageServices.ProjectSystem.IWorkspaceProjectContextFactory! factory, Microsoft.CodeAnalysis.Editor.Shared.Utilities.IThreadingContext! threadingContext) -> void Microsoft.CodeAnalysis.ExternalAccess.FSharp.GoToDefinition.IFSharpFindDefinitionService Microsoft.CodeAnalysis.ExternalAccess.FSharp.GoToDefinition.IFSharpFindDefinitionService.FindDefinitionsAsync(Microsoft.CodeAnalysis.Document! document, int position, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! @@ -289,6 +291,7 @@ Microsoft.CodeAnalysis.ExternalAccess.FSharp.IFSharpWorkspaceProjectContext.SetM Microsoft.CodeAnalysis.ExternalAccess.FSharp.IFSharpWorkspaceProjectContext.SetProjectReferences(System.Collections.Generic.IEnumerable! projRefs) -> void Microsoft.CodeAnalysis.ExternalAccess.FSharp.IFSharpWorkspaceProjectContextFactory Microsoft.CodeAnalysis.ExternalAccess.FSharp.IFSharpWorkspaceProjectContextFactory.CreateProjectContext(string! filePath, string! uniqueName) -> Microsoft.CodeAnalysis.ExternalAccess.FSharp.IFSharpWorkspaceProjectContext! +Microsoft.CodeAnalysis.ExternalAccess.FSharp.IFSharpWorkspaceProjectContextFactory.CreateProjectContextAsync(string! filePath, string! uniqueName, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.FSharp.InlineHints.FSharpInlineHint Microsoft.CodeAnalysis.ExternalAccess.FSharp.InlineHints.FSharpInlineHint.FSharpInlineHint() -> void Microsoft.CodeAnalysis.ExternalAccess.FSharp.InlineHints.FSharpInlineHint.FSharpInlineHint(Microsoft.CodeAnalysis.Text.TextSpan span, System.Collections.Immutable.ImmutableArray displayParts, System.Func>!>? getDescriptionAsync = null) -> void @@ -313,7 +316,9 @@ Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.FSharpNavigateToMatchKin Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.FSharpNavigateToMatchKind.Substring = 2 -> Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.FSharpNavigateToMatchKind Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.FSharpNavigateToSearchResult Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.FSharpNavigateToSearchResult.MatchKind.get -> Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.FSharpNavigateToMatchKind +Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.IFSharpAdvancedNavigateToSearchService Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.IFSharpNavigateToSearchService +Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.IFSharpAdvancedNavigateToSearchService.SearchCachedDocumentsAsync(Microsoft.CodeAnalysis.Solution! solution, System.Collections.Immutable.ImmutableArray projects, System.Collections.Immutable.ImmutableArray priorityDocuments, string! searchPattern, System.Collections.Immutable.IImmutableSet! kinds, Microsoft.CodeAnalysis.Document? activeDocument, System.Func, System.Threading.Tasks.Task!>! onResultsFound, System.Func! onProjectCompleted, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.IFSharpNavigateToSearchService.CanFilter.get -> bool Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.IFSharpNavigateToSearchService.KindsProvided.get -> System.Collections.Immutable.IImmutableSet! Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.IFSharpNavigateToSearchService.SearchDocumentAsync(Microsoft.CodeAnalysis.Document! document, string! searchPattern, System.Collections.Immutable.IImmutableSet! kinds, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! diff --git a/src/VisualStudio/ExternalAccess/FSharp/TypeForwards.cs b/src/VisualStudio/ExternalAccess/FSharp/TypeForwards.cs index 5c8a84ca53f1c..b31873c348060 100644 --- a/src/VisualStudio/ExternalAccess/FSharp/TypeForwards.cs +++ b/src/VisualStudio/ExternalAccess/FSharp/TypeForwards.cs @@ -119,6 +119,7 @@ [assembly: TypeForwardedTo(typeof(Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.FSharpNavigateToItemKind))] [assembly: TypeForwardedTo(typeof(Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.FSharpNavigateToMatchKind))] [assembly: TypeForwardedTo(typeof(Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.FSharpNavigateToSearchResult))] +[assembly: TypeForwardedTo(typeof(Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.IFSharpAdvancedNavigateToSearchService))] [assembly: TypeForwardedTo(typeof(Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.IFSharpNavigateToSearchService))] [assembly: TypeForwardedTo(typeof(Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation.FSharpDocumentNavigationService))] [assembly: TypeForwardedTo(typeof(Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation.FSharpNavigableItem))] diff --git a/src/VisualStudio/IntegrationTest/Harness/SourceGeneratorUnitTests/Microsoft.VisualStudio.Extensibility.Testing.SourceGenerator.UnitTests.csproj b/src/VisualStudio/IntegrationTest/Harness/SourceGeneratorUnitTests/Microsoft.VisualStudio.Extensibility.Testing.SourceGenerator.UnitTests.csproj index 45ee676290221..84deb3a59eb55 100644 --- a/src/VisualStudio/IntegrationTest/Harness/SourceGeneratorUnitTests/Microsoft.VisualStudio.Extensibility.Testing.SourceGenerator.UnitTests.csproj +++ b/src/VisualStudio/IntegrationTest/Harness/SourceGeneratorUnitTests/Microsoft.VisualStudio.Extensibility.Testing.SourceGenerator.UnitTests.csproj @@ -8,7 +8,6 @@ - @@ -25,6 +24,7 @@ + diff --git a/src/VisualStudio/IntegrationTest/IntegrationTestBuildProject.csproj b/src/VisualStudio/IntegrationTest/IntegrationTestBuildProject.csproj index b82dc3ea2f4fc..53f238a309a9e 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTestBuildProject.csproj +++ b/src/VisualStudio/IntegrationTest/IntegrationTestBuildProject.csproj @@ -31,7 +31,6 @@ - diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ITextViewWindowVerifierInProcessExtensions.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ITextViewWindowVerifierInProcessExtensions.cs index 579af07307d92..1a884b8296537 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ITextViewWindowVerifierInProcessExtensions.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ITextViewWindowVerifierInProcessExtensions.cs @@ -101,7 +101,7 @@ public static async Task CodeActionAsync( if (!RoslynString.IsNullOrEmpty(applyFix)) { var codeActionLogger = new CodeActionLogger(); - using var loggerRegistration = RoslynTelemetry.AddEventSink(codeActionLogger); + using var loggerRegistration = RoslynTelemetry.Current.AddEventSink(codeActionLogger); var result = await textViewWindowVerifier.TestServices.Editor.ApplyLightBulbActionAsync(applyFix, fixAllScope, blockUntilComplete, cancellationToken); diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/DiagnosticsWindow.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/DiagnosticsWindow.cs deleted file mode 100644 index a829047a5c922..0000000000000 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/DiagnosticsWindow.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.InteropServices; -using System.Windows.Controls; -using Microsoft.CodeAnalysis; -using Microsoft.VisualStudio.LanguageServices; -using Microsoft.VisualStudio.Shell; -using Roslyn.Hosting.Diagnostics.PerfMargin; - -namespace Roslyn.VisualStudio.DiagnosticsWindow; - -[Guid("b2da68d7-fd1c-491a-a9a0-24f597b9f56c")] -public sealed class DiagnosticsWindow : ToolWindowPane -{ - public VisualStudioWorkspace? Workspace { get; private set; } - - /// - /// Standard constructor for the tool window. - /// - public DiagnosticsWindow(object _) - : base(null) - { - // Set the window title reading it from the resources. - Caption = Resources.ToolWindowTitle; - // Set the image that will appear on the tab of the window frame - // when docked with an other window - // The resource ID correspond to the one defined in the resx file - // while the Index is the offset in the bitmap strip. Each image in - // the strip being 16x16. - BitmapResourceID = 301; - BitmapIndex = 1; - - // This is the user control hosted by the tool window; Note that, even if this class implements IDisposable, - // we are not calling Dispose on this object. This is because ToolWindowPane calls Dispose on - // the object returned by the Content property. - var perfMarginPanel = new TabItem() - { - Header = "Perf", - Content = new PerfMarginPanel() - }; - - var telemetryPanel = new TabItem() - { - Header = "Telemetry", - Content = new TelemetryPanel() - }; - - var workspacePanel = new TabItem() - { - Header = "Workspace", - Content = new WorkspacePanel(this) - }; - - var tabControl = new TabControl - { - TabStripPlacement = Dock.Bottom - }; - - tabControl.Items.Add(perfMarginPanel); - tabControl.Items.Add(telemetryPanel); - tabControl.Items.Add(workspacePanel); - - Content = tabControl; - } - - public void Initialize(VisualStudioWorkspace workspace) - { - Contract.ThrowIfFalse(Workspace == null); - Workspace = workspace; - } -} diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Guids.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Guids.cs deleted file mode 100644 index 7dc2b9df9bef8..0000000000000 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Guids.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. -// See the LICENSE file in the project root for more information. - -#nullable disable - -// MUST match guids.h - -using System; - -namespace Roslyn.VisualStudio.DiagnosticsWindow; - -internal static class GuidList -{ - public const string guidVisualStudioDiagnosticsWindowPkgString = "49e24138-9ee3-49e0-8ede-6b39f49303bf"; - public const string guidVisualStudioDiagnosticsWindowCmdSetString = "f22c2499-790a-4b6c-b0fd-b6f0491e1c9c"; - public const string guidToolWindowPersistanceString = "b2da68d7-fd1c-491a-a9a0-24f597b9f56c"; - - public static readonly Guid guidVisualStudioDiagnosticsWindowCmdSet = new(guidVisualStudioDiagnosticsWindowCmdSetString); -}; diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Loggers/OutputWindowEventSink.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Loggers/OutputWindowEventSink.cs deleted file mode 100644 index 812097d913cf4..0000000000000 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Loggers/OutputWindowEventSink.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Threading; -using Microsoft.CodeAnalysis.Editor.Shared.Extensions; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.ComponentModelHost; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; - -namespace Microsoft.CodeAnalysis.Internal.Log; - -/// -/// Implementation of that output to output window -/// -internal sealed class OutputWindowEventSink : IEventSink -{ - private readonly Func _isEnabledPredicate; - - public OutputWindowEventSink(Func isEnabledPredicate) - { - _isEnabledPredicate = isEnabledPredicate; - } - - public bool IsEnabled(FunctionId functionId) - => _isEnabledPredicate(functionId); - - public void Log(FunctionId functionId, LogMessage logMessage) - { - OutputPane.WriteLine(string.Format("[{0}] {1} - {2}", Environment.CurrentManagedThreadId, functionId.ToString(), logMessage.GetMessage())); - } - - public void LogBlockStart(FunctionId functionId, LogMessage logMessage, int uniquePairId, CancellationToken cancellationToken) - { - OutputPane.WriteLine(string.Format("[{0}] Start({1}) : {2} - {3}", Environment.CurrentManagedThreadId, uniquePairId, functionId.ToString(), logMessage.GetMessage())); - } - - public void LogBlockEnd(FunctionId functionId, LogMessage logMessage, int uniquePairId, int delta, CancellationToken cancellationToken) - { - var functionString = functionId.ToString() + (cancellationToken.IsCancellationRequested ? " Canceled" : string.Empty); - OutputPane.WriteLine(string.Format("[{0}] End({1}) : [{2}ms] {3}", Environment.CurrentManagedThreadId, uniquePairId, delta, functionString)); - } - - private sealed class OutputPane - { - private static readonly Guid s_outputPaneGuid = new("BBAFF416-4AF5-41F2-9F93-91F283E43C3B"); - - public static readonly OutputPane s_instance = new(); - - private readonly IServiceProvider _serviceProvider; - private readonly IThreadingContext _threadingContext; - - public static void WriteLine(string value) - { - s_instance.WriteLineInternal(value); - } - - public OutputPane() - { - _serviceProvider = ServiceProvider.GlobalProvider; - - var componentModel = (IComponentModel)_serviceProvider.GetService(typeof(SComponentModel)); - _threadingContext = componentModel.GetService(); - } - - private IVsOutputWindowPane _doNotAccessDirectlyOutputPane; - - private void WriteLineInternal(string value) - { - var pane = GetPane(); - if (pane == null) - { - return; - } - - pane.OutputStringThreadSafe(value + Environment.NewLine); - } - - private IVsOutputWindowPane GetPane() - { - if (_doNotAccessDirectlyOutputPane == null) - { - _threadingContext.JoinableTaskFactory.Run(async () => - { - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); - - if (_doNotAccessDirectlyOutputPane != null) - { - // check whether other one already initialized output window. - // the output API already handle double initialization, so this is just quick bail - // rather than any functional issue - return; - } - - var outputWindow = (IVsOutputWindow)_serviceProvider.GetService(typeof(SVsOutputWindow)); - - // this should bring outout window to the front - _doNotAccessDirectlyOutputPane = CreateOutputPane(outputWindow); - }); - } - - return _doNotAccessDirectlyOutputPane; - } - - private IVsOutputWindowPane CreateOutputPane(IVsOutputWindow outputWindow) - { - _threadingContext.ThrowIfNotOnUIThread(); - - // Try to get the workspace pane if it has already been registered - var workspacePaneGuid = s_outputPaneGuid; - - // If the pane has already been created, CreatePane returns it - if (ErrorHandler.Succeeded(outputWindow.CreatePane(ref workspacePaneGuid, "Roslyn Logger Output", fInitVisible: 1, fClearWithSolution: 1)) && - ErrorHandler.Succeeded(outputWindow.GetPane(ref workspacePaneGuid, out var pane))) - { - return pane; - } - - return null; - } - } -} diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs deleted file mode 100644 index e38d53f5a0733..0000000000000 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs +++ /dev/null @@ -1,155 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Options; - -namespace Roslyn.VisualStudio.DiagnosticsWindow.OptionsPages; - -internal sealed class ForceLowMemoryMode -{ - public static readonly Option2 Enabled = new("ForceLowMemoryMode_Enabled", defaultValue: false); - public static readonly Option2 SizeInMegabytes = new("ForceLowMemoryMode_Enabled", defaultValue: 500); - - private readonly IGlobalOptionService _globalOptions; - private MemoryHogger? _hogger; - - public ForceLowMemoryMode(IGlobalOptionService globalOptions) - { - _globalOptions = globalOptions; - - globalOptions.AddOptionChangedHandler(this, Options_OptionChanged); - - RefreshFromSettings(); - } - - private void Options_OptionChanged(object sender, object target, OptionChangedEventArgs e) - { - if (e.HasOption(static option => option.Equals(Enabled) || option.Equals(SizeInMegabytes))) - { - RefreshFromSettings(); - } - } - - private void RefreshFromSettings() - { - var enabled = _globalOptions.GetOption(Enabled); - - if (_hogger != null) - { - _hogger.Cancel(); - _hogger = null; - } - - if (enabled) - { - _hogger = new MemoryHogger(); - _ = _hogger.PopulateAndMonitorAsync(_globalOptions.GetOption(SizeInMegabytes)); - } - } - - private sealed class MemoryHogger - { - private const int BlockSize = 1024 * 1024; // megabyte blocks - private const int MonitorDelay = 10000; // 10 seconds - - private readonly List _blocks = []; - private readonly CancellationTokenSource _cancellationTokenSource = new(); - - public MemoryHogger() - { - } - - public int Count - { - get { return _blocks.Count; } - } - - public void Cancel() - { - _cancellationTokenSource.Cancel(); - } - - public Task PopulateAndMonitorAsync(int size) - { - // run on background thread - return Task.Factory.StartNew(() => this.PopulateAndMonitorWorkerAsync(size), CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap(); - } - - private async Task PopulateAndMonitorWorkerAsync(int size) - { - try - { - try - { - for (var n = 0; n < size; n++) - { - _cancellationTokenSource.Token.ThrowIfCancellationRequested(); - - var block = new byte[BlockSize]; - - // initialize block bits (so the memory actually gets allocated.. silly runtime!) - for (var i = 0; i < BlockSize; i++) - { - block[i] = 0xFF; - } - - _blocks.Add(block); - - // don't hog the thread - await Task.Yield(); - } - } - catch (OutOfMemoryException) - { - } - - // monitor memory to keep it paged in - while (true) - { - _cancellationTokenSource.Token.ThrowIfCancellationRequested(); - - try - { - // access all block bytes - for (var b = 0; b < _blocks.Count; b++) - { - _cancellationTokenSource.Token.ThrowIfCancellationRequested(); - - var block = _blocks[b]; - - byte tmp; - for (var i = 0; i < block.Length; i++) - { - tmp = block[i]; - } - - // don't hog the thread - await Task.Yield(); - } - } - catch (OutOfMemoryException) - { - } - - await Task.Delay(MonitorDelay, _cancellationTokenSource.Token).ConfigureAwait(false); - } - } - catch (OperationCanceledException) - { - _blocks.Clear(); - - // force garbage collection - for (var i = 0; i < 5; i++) - { - GC.Collect(GC.MaxGeneration); - GC.WaitForPendingFinalizers(); - } - } - } - } -} diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryModePage.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryModePage.cs deleted file mode 100644 index bda476d521b8a..0000000000000 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryModePage.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Runtime.InteropServices; -using System.Windows.Controls; -using Microsoft.VisualStudio.LanguageServices; -using Microsoft.VisualStudio.LanguageServices.Implementation.Options; - -namespace Roslyn.VisualStudio.DiagnosticsWindow.OptionsPages; - -[Guid(Guids.RoslynOptionPageFeatureManagerFeaturesIdString)] -internal sealed class ForceLowMemoryModePage : AbstractOptionPage -{ - protected override AbstractOptionPageControl CreateOptionPage(IServiceProvider serviceProvider, OptionStore optionStore) - => new Control(optionStore); - - internal sealed class Control : InternalOptionsControl - { - public Control(OptionStore optionStore) - : base([], optionStore) - { - } - - protected override void AddOptions(Panel panel) - { - // add force low memory mode option - var lowMemoryGroup = new WrapPanel(); - - var cb = new CheckBox { Content = "Forced Low Memory Mode: allocate" }; - BindToOption(cb, ForceLowMemoryMode.Enabled); - lowMemoryGroup.Children.Add(cb); - - var textBox = new TextBox { MinWidth = 60 }; - BindToOption(textBox, ForceLowMemoryMode.SizeInMegabytes); - lowMemoryGroup.Children.Add(textBox); - - lowMemoryGroup.Children.Add(new TextBlock { Text = "megabytes of extra memory in devenv.exe" }); - - panel.Children.Add(lowMemoryGroup); - } - } -} diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/InternalOptionsControl.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/InternalOptionsControl.cs deleted file mode 100644 index ff997bd13c740..0000000000000 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/InternalOptionsControl.cs +++ /dev/null @@ -1,215 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Options; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options; - -internal partial class InternalOptionsControl : AbstractOptionPageControl -{ - private readonly IEnumerable _options; - - public InternalOptionsControl(IEnumerable options, OptionStore optionStore) - : base(optionStore) - { - _options = options; - - // options - var optionsPanel = new StackPanel(); - this.AddOptions(optionsPanel); - - // scroll - var viewer = new ScrollViewer() - { - VerticalScrollBarVisibility = ScrollBarVisibility.Auto, - HorizontalScrollBarVisibility = ScrollBarVisibility.Auto - }; - - viewer.Content = optionsPanel; - - // search - var searchBox = new TextBox() { MinWidth = 200, HorizontalAlignment = HorizontalAlignment.Stretch }; - - var searchButton = new Button() { Content = "Search" }; - searchButton.Click += (o, a) => - { - foreach (var item in optionsPanel.Children.OfType()) - { - if (item.Content is not string title) - { - continue; - } - - // pattern not match - if (title.IndexOf(searchBox.Text, StringComparison.OrdinalIgnoreCase) < 0) - { - // hide it - item.Visibility = Visibility.Collapsed; - } - else - { - item.Visibility = Visibility.Visible; - } - } - }; - - var clearButton = new Button() { Content = "Clear" }; - clearButton.Click += (o, a) => optionsPanel.Children.OfType().Do(c => c.Visibility = Visibility.Visible); - - var searchPanel = new StackPanel() - { - Orientation = Orientation.Horizontal, - Margin = new Thickness(5), - HorizontalAlignment = HorizontalAlignment.Stretch - }; - - searchPanel.Children.Add(searchBox); - searchPanel.Children.Add(searchButton); - searchPanel.Children.Add(clearButton); - - // button - var checkAllButton = new Button() { Content = "Check All" }; - checkAllButton.Click += (o, a) => optionsPanel.Children.OfType().Where(c => c.Visibility == Visibility.Visible).Do(c => c.IsChecked = true); - - var uncheckAllButton = new Button() { Content = "Uncheck All" }; - uncheckAllButton.Click += (o, a) => optionsPanel.Children.OfType().Where(c => c.Visibility == Visibility.Visible).Do(c => c.IsChecked = false); - - var selectionPanel = new StackPanel() - { - Orientation = Orientation.Horizontal, - Margin = new Thickness(5), - HorizontalAlignment = HorizontalAlignment.Stretch - }; - - selectionPanel.Children.Add(checkAllButton); - selectionPanel.Children.Add(uncheckAllButton); - - // main panel - var mainPanel = new DockPanel() { HorizontalAlignment = HorizontalAlignment.Stretch }; - mainPanel.Children.Add(searchPanel); - mainPanel.Children.Add(selectionPanel); - mainPanel.Children.Add(viewer); - - DockPanel.SetDock(searchPanel, Dock.Top); - DockPanel.SetDock(selectionPanel, Dock.Bottom); - - this.Content = mainPanel; - } - - protected virtual void AddOptions(Panel panel) - { - foreach (var option in _options) - { - if (!option.IsPerLanguage) - { - AddOption(panel, option); - } - else - { - AddPerLanguageOption(panel, option, LanguageNames.CSharp); - AddPerLanguageOption(panel, option, LanguageNames.VisualBasic); - } - } - } - - protected void AddOption(Panel panel, IOption2 option, string additional = null) - { - var uiElement = CreateControl(option, additional: additional); - if (uiElement != null) - { - panel.Children.Add(uiElement); - } - } - - protected void AddPerLanguageOption(Panel panel, IOption2 option, string languageName, string additional = null) - { - var uiElement = CreateControl(option, languageName, additional); - if (uiElement != null) - { - panel.Children.Add(uiElement); - } - } - - private UIElement CreateControl(IOption2 option, string languageName = null, string additional = null) - { - // Underscores in WPF mean that the next character is the access key for keyboard navigation - // but thats not why our option names have underscores. Also removing them looks nicer. - var optionDisplay = option.Definition.ConfigName.Replace('_', ' ') + GetLanguage(languageName) + GetAdditionalText(additional); - - if (option.Type == typeof(bool)) - { - var checkBox = new CheckBox() { Content = optionDisplay }; - BindToCheckBox(checkBox, option, languageName); - return checkBox; - } - - if (option.Type == typeof(int)) - { - var label = new Label() { Content = optionDisplay }; - var textBox = new TextBox(); - BindToTextBox(textBox, option, languageName); - - var panel = new StackPanel(); - panel.Children.Add(label); - panel.Children.Add(textBox); - - AddSearchHandler(label); - - return panel; - } - - return null; - } - - private static string GetAdditionalText(string additional) - { - if (additional == null) - { - return string.Empty; - } - - return " [" + additional + "]"; - } - - private static string GetLanguage(string languageName) - { - if (languageName == null) - { - return string.Empty; - } - - return " [" + languageName + "]"; - } - - private void BindToCheckBox(CheckBox checkBox, IOption2 option, string languageName = null) - { - if (languageName == null) - { - BindToOption(checkBox, (Option2)option); - return; - } - - BindToOption(checkBox, (PerLanguageOption2)option, languageName); - } - - private void BindToTextBox(TextBox textBox, IOption2 option, string languageName = null) - { - if (languageName == null) - { - BindToOption(textBox, (Option2)option); - return; - } - - BindToOption(textBox, (PerLanguageOption2)option, languageName); - } -} diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceFunctionIdPage.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceFunctionIdPage.cs deleted file mode 100644 index 37e2788a45444..0000000000000 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceFunctionIdPage.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. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.InteropServices; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.VisualStudio.LanguageServices; -using Microsoft.VisualStudio.LanguageServices.Implementation.Options; - -namespace Roslyn.VisualStudio.DiagnosticsWindow.OptionsPages; - -[Guid(Guids.RoslynOptionPagePerformanceFunctionIdIdString)] -internal sealed class PerformanceFunctionIdPage : AbstractOptionPage -{ - protected override AbstractOptionPageControl CreateOptionPage(IServiceProvider serviceProvider, OptionStore optionStore) - { - return new InternalOptionsControl(FunctionIdOptions.GetOptions(), optionStore); - } -} diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceLoggersPage.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceLoggersPage.cs deleted file mode 100644 index f7e63341c3bcd..0000000000000 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceLoggersPage.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Runtime.InteropServices; -using System.Threading; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Remote; -using Microsoft.VisualStudio.ComponentModelHost; -using Microsoft.VisualStudio.LanguageServices; -using Microsoft.VisualStudio.LanguageServices.Implementation; -using Microsoft.VisualStudio.LanguageServices.Implementation.Options; -using Microsoft.VisualStudio.LanguageServices.Telemetry; -using Roslyn.Utilities; - -namespace Roslyn.VisualStudio.DiagnosticsWindow.OptionsPages; - -[Guid(Guids.RoslynOptionPagePerformanceLoggersIdString)] -internal sealed class PerformanceLoggersPage : AbstractOptionPage -{ - private IGlobalOptionService _globalOptions; - private IThreadingContext _threadingContext; - private SolutionServices _workspaceServices; - - private static IDisposable s_etwRegistration; - private static IDisposable s_traceRegistration; - private static IDisposable s_outputWindowRegistration; - - protected override AbstractOptionPageControl CreateOptionPage(IServiceProvider serviceProvider, OptionStore optionStore) - { - if (_globalOptions == null) - { - var componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); - - _globalOptions = componentModel.GetService(); - _threadingContext = componentModel.GetService(); - - var workspace = componentModel.GetService(); - _workspaceServices = workspace.Services.SolutionServices; - } - - return new InternalOptionsControl(FunctionIdOptions.GetOptions(), optionStore); - } - - protected override void OnApply(PageApplyEventArgs e) - { - base.OnApply(e); - - SetLoggers(_globalOptions, _threadingContext, _workspaceServices); - } - - public static void SetLoggers(IGlobalOptionService globalOptions, IThreadingContext threadingContext, SolutionServices workspaceServices) - { - var isEnabled = FunctionIdOptions.CreateFunctionIsEnabledPredicate(globalOptions); - - var etwEnabled = globalOptions.GetOption(LoggerOptionsStorage.EtwLoggerKey); - var traceEnabled = globalOptions.GetOption(LoggerOptionsStorage.TraceLoggerKey); - var outputWindowEnabled = globalOptions.GetOption(LoggerOptionsStorage.OutputWindowLoggerKey); - - // These sinks exist only for this page, so each is registered while enabled and unregistered - // when not. isEnabled is a snapshot of the per-FunctionId options, which is why a fresh sink is - // built on every apply. - Register(ref s_etwRegistration, etwEnabled, () => new EtwEventSink(isEnabled)); - Register(ref s_traceRegistration, traceEnabled, () => new TraceEventSink(isEnabled)); - Register(ref s_outputWindowRegistration, outputWindowEnabled, () => new OutputWindowEventSink(isEnabled)); - - // update loggers in remote process - var client = threadingContext.JoinableTaskFactory.Run(() => RemoteHostClient.TryGetClientAsync(workspaceServices, CancellationToken.None)); - if (client != null) - { - var loggerTypeNames = ImmutableArray.Empty; - if (etwEnabled) - loggerTypeNames = loggerTypeNames.Add(nameof(EtwEventSink)); - if (traceEnabled) - loggerTypeNames = loggerTypeNames.Add(nameof(TraceEventSink)); - - var functionIds = Enum.GetValues().WhereAsArray(isEnabled); - - threadingContext.JoinableTaskFactory.Run(async () => _ = await client.TryInvokeAsync( - (service, cancellationToken) => service.EnableLoggingAsync(loggerTypeNames, functionIds, cancellationToken), - CancellationToken.None).ConfigureAwait(false)); - } - - static void Register(ref IDisposable registration, bool enabled, Func create) - { - Interlocked.Exchange(ref registration, enabled ? RoslynTelemetry.AddEventSink(create()) : null)?.Dispose(); - } - } -} diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/PackageRegistration.pkgdef b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/PackageRegistration.pkgdef deleted file mode 100644 index 70a262f84bc83..0000000000000 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/PackageRegistration.pkgdef +++ /dev/null @@ -1,74 +0,0 @@ -// ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad) -[$RootKey$\AutoLoadPackages\{f1536ef8-92ec-443c-9ed7-fdadf150da82}] -"{49e24138-9ee3-49e0-8ede-6b39f49303bf}"=dword:00000002 - -[$RootKey$\Menus] -"{49e24138-9ee3-49e0-8ede-6b39f49303bf}"=", Menus.ctmenu, 1" - -// The option page configuration is duplicated on VisualStudioDiagnosticsWindowPackage type definition. - -[$RootKey$\ToolsOptionsPages\Roslyn\FeatureManager] -@="#0" -"Package"="{49e24138-9ee3-49e0-8ede-6b39f49303bf}" - -[$RootKey$\AutomationProperties\Roslyn\FeatureManager] -"Package"="{49e24138-9ee3-49e0-8ede-6b39f49303bf}" - -[$RootKey$\ToolsOptionsPages\Roslyn\FeatureManager] -@="#0" -"Package"="{49e24138-9ee3-49e0-8ede-6b39f49303bf}" - -[$RootKey$\ToolsOptionsPages\Roslyn\FeatureManager\Features] -@="#0" -"Package"="{49e24138-9ee3-49e0-8ede-6b39f49303bf}" -"Page"="{67989704-f8d7-454a-9053-8e1d3cff679c}" - -[$RootKey$\AutomationProperties\Roslyn\FeatureManager] -"Package"="{49e24138-9ee3-49e0-8ede-6b39f49303bf}" - -[$RootKey$\AutomationProperties\Roslyn\FeatureManager\Features] -"Package"="{49e24138-9ee3-49e0-8ede-6b39f49303bf}" - -[$RootKey$\AutomationProperties\Roslyn\FeatureManager\Features] -"Name"="Roslyn\FeatureManager.Features" - -[$RootKey$\ToolsOptionsPages\Roslyn\Performance] -@="#0" -"Package"="{49e24138-9ee3-49e0-8ede-6b39f49303bf}" - -[$RootKey$\ToolsOptionsPages\Roslyn\Performance\FunctionId] -@="#0" -"Package"="{49e24138-9ee3-49e0-8ede-6b39f49303bf}" -"Page"="{0c537218-3bdd-4cc8-ac4b-cec152d4871a}" - -[$RootKey$\AutomationProperties\Roslyn\Performance] -"Package"="{49e24138-9ee3-49e0-8ede-6b39f49303bf}" - -[$RootKey$\AutomationProperties\Roslyn\Performance\FunctionId] -"Package"="{49e24138-9ee3-49e0-8ede-6b39f49303bf}" - -[$RootKey$\AutomationProperties\Roslyn\Performance\FunctionId] -"Name"="Roslyn\Performance.FunctionId" - -[$RootKey$\ToolsOptionsPages\Roslyn\Performance] -@="#0" -"Package"="{49e24138-9ee3-49e0-8ede-6b39f49303bf}" - -[$RootKey$\ToolsOptionsPages\Roslyn\Performance\Loggers] -@="#0" -"Package"="{49e24138-9ee3-49e0-8ede-6b39f49303bf}" -"Page"="{236ac96f-a60d-4bd6-a480-d315151edc2b}" - -[$RootKey$\AutomationProperties\Roslyn\Performance] -"Package"="{49e24138-9ee3-49e0-8ede-6b39f49303bf}" - -[$RootKey$\AutomationProperties\Roslyn\Performance\Loggers] -"Package"="{49e24138-9ee3-49e0-8ede-6b39f49303bf}" - -[$RootKey$\AutomationProperties\Roslyn\Performance\Loggers] -"Name"="Roslyn\Performance.Loggers" - -[$RootKey$\ToolWindows\{b2da68d7-fd1c-491a-a9a0-24f597b9f56c}] -@="{49e24138-9ee3-49e0-8ede-6b39f49303bf}" -"Name"="Roslyn.VisualStudio.DiagnosticsWindow.DiagnosticsWindow" - diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Panels/TelemetryPanel.xaml b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Panels/TelemetryPanel.xaml deleted file mode 100644 index 73ac3cc8c7930..0000000000000 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Panels/TelemetryPanel.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - -