Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .config/LocalizationValidationAllowlist.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"These culture/key pairs intentionally match the English source text.",
"Each pair was verified in the internal LCL source as localized (Stat=Loc, Orig=New). Remove an entry when its localized value changes."
],
"AbstractionsAllowedEnglishValueMatches": {},
"AllowedEnglishValueMatches": {
"Strings.cs.resx": [
"ADP_InvalidMultipartName",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Respect this graph when modifying build stages:

Validation runs in three places and shares one gating switch.

- **Localization** — `steps/validate-localization-step.yml`, in the SqlClient build job before the driver is built. Reports missing or obsolete keys, empty localized values whose English value is non-empty, and untranslated resources. Approved identical translations are listed by culture and resource key in `.config/LocalizationValidationAllowlist.json`.
- **Localization** — `steps/validate-localization-step.yml`, in the SqlClient and Abstractions build jobs before compilation. Callers explicitly supply the resource directory and allowlist section. Reports missing or obsolete keys, empty localized values whose English value is non-empty, and untranslated resources. Approved identical translations are listed by culture and resource key in `.config/LocalizationValidationAllowlist.json`: `AllowedEnglishValueMatches` for the driver and `AbstractionsAllowedEnglishValueMatches` for Abstractions.
- **XML documentation** — `steps/validate-xml-docs-step.yml`, in three modes: snippet sources before each snippet-consuming project is built (SqlClient, SqlServer, Abstractions, and Azure), generated documentation after documented projects are built (including the separate SqlClient reference-assembly output), and assembled packages in `package_validation`. Reports malformed documentation IDs, unresolved cross-references, and `lib/` vs `ref/` documentation-trimming defects.
- **Packages** — `steps/validate-packages-step.yml`, in `package_validation`. Runs `tools/PackageValidator` across the whole drop so its cross-package version and dependency rules apply.

Expand Down
12 changes: 10 additions & 2 deletions eng/pipelines/onebranch/jobs/build-buildproj-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,19 @@ jobs:
steps:
- template: /eng/pipelines/onebranch/steps/script-output-environment-variables-step.yml@self

# Localized resources ship with the SqlClient driver. Validate them before analysis and
# building so missing or untranslated strings fail every SqlClient build.
# Validate each resource-owning assembly before analysis and building.
- ${{ if eq(parameters.packageShortName, 'SqlClient') }}:
- template: /eng/pipelines/onebranch/steps/validate-localization-step.yml@self
parameters:
resourcesDirectory: src/Microsoft.Data.SqlClient/src/Resources
allowlistSection: AllowedEnglishValueMatches
failOnValidationError: ${{ parameters.failOnValidationError }}

- ${{ if eq(parameters.packageShortName, 'Abstractions') }}:
- template: /eng/pipelines/onebranch/steps/validate-localization-step.yml@self
parameters:
resourcesDirectory: src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Resources
allowlistSection: AbstractionsAllowedEnglishValueMatches
failOnValidationError: ${{ parameters.failOnValidationError }}

# Validate the documentation snippet sources. This requires no build, so a malformed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,41 @@ BeforeAll {
}

Describe 'Validation step templates' {
It 'requires an explicit localization <Parameter> without a default' -ForEach @(
@{ Parameter = 'resourcesDirectory' }
@{ Parameter = 'allowlistSection' }
) {
$content = Get-Content -LiteralPath (Join-Path $script:stepsPath 'validate-localization-step.yml') -Raw
$declaration = [regex]::Match(
$content,
"(?ms)^ - name: $Parameter\r?\n(?<settings>.*?)(?=^ - name:|^steps:)")
$declaration.Success | Should -BeTrue
$declaration.Groups['settings'].Value | Should -Match '(?m)^ type: string\s*$'
$declaration.Groups['settings'].Value | Should -Not -Match '(?m)^\s+default:'
}

It 'passes explicit localization inputs for <Package>' -ForEach @(
@{
Package = 'SqlClient'
ResourcesDirectory = 'src/Microsoft.Data.SqlClient/src/Resources'
AllowlistSection = 'AllowedEnglishValueMatches'
}
@{
Package = 'Abstractions'
ResourcesDirectory = 'src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Resources'
AllowlistSection = 'AbstractionsAllowedEnglishValueMatches'
}
) {
$content = Get-Content -LiteralPath (Join-Path $script:jobsPath 'build-buildproj-job.yml') -Raw
$pattern = '(?ms)^ - \$\{\{ if eq\(parameters\.packageShortName, ''' + $Package +
'''\) \}\}:\r?\n(?<steps>.*?)(?=^ - |\z)'
$block = [regex]::Match($content, $pattern)
$block.Success | Should -BeTrue
$block.Groups['steps'].Value | Should -Match 'validate-localization-step\.yml@self'
$block.Groups['steps'].Value | Should -Match ([regex]::Escape("resourcesDirectory: $ResourcesDirectory"))
$block.Groups['steps'].Value | Should -Match ([regex]::Escape("allowlistSection: $AllowlistSection"))
}

It 'never passes a switch using the -Switch:Value form' -ForEach @(
@{ Template = 'validate-xml-docs-step.yml' }
@{ Template = 'validate-localization-step.yml' }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,30 @@ BeforeAll {
}

Describe 'validate-localization.ps1' {
It 'selects an independent allowlist for a second resource assembly' {
$resources = New-ResourcesDirectory
Set-ResourceFile (Join-Path $resources 'Strings.resx') @{ Greeting = 'Hello' }
Set-ResourceFile (Join-Path $resources 'Strings.fr.resx') @{ Greeting = 'Bonjour' }
$allowlist = Join-Path $resources 'allowlist.json'
@{
AllowedEnglishValueMatches = @{ 'Strings.fr.resx' = @('UnrelatedDriverKey') }
AbstractionsAllowedEnglishValueMatches = @{}
} | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $allowlist
{ & $scriptPath -ResourcesDirectory $resources -AllowlistPath $allowlist `
-AllowlistSection AbstractionsAllowedEnglishValueMatches } | Should -Not -Throw
}

It 'rejects a missing resource-set allowlist section rather than borrowing another assembly allowlist' {
$resources = New-ResourcesDirectory
Set-ResourceFile (Join-Path $resources 'Strings.resx') @{ Greeting = 'Hello' }
Set-ResourceFile (Join-Path $resources 'Strings.fr.resx') @{ Greeting = 'Bonjour' }
$allowlist = Join-Path $resources 'allowlist.json'
@{ AllowedEnglishValueMatches = @{} } | ConvertTo-Json | Set-Content -LiteralPath $allowlist
{ & $scriptPath -ResourcesDirectory $resources -AllowlistPath $allowlist `
-AllowlistSection AbstractionsAllowedEnglishValueMatches } |
Should -Throw "*must define 'AbstractionsAllowedEnglishValueMatches'*"
}

It 'accepts complete localized files with translated values' {
$resources = New-ResourcesDirectory
Set-ResourceFile (Join-Path $resources 'Strings.resx') @{ Greeting = 'Hello'; Farewell = 'Goodbye' }
Expand Down
11 changes: 8 additions & 3 deletions eng/pipelines/onebranch/scripts/validate-localization.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
.PARAMETER ReportOnly
Report validation findings as warnings without failing. Malformed or missing inputs still fail,
because a run that could not examine the resources has produced no result to report.

.PARAMETER AllowlistSection
Property containing the allowlist for the resource set being validated.
#>

# Licensed to the .NET Foundation under one or more agreements.
Expand All @@ -25,7 +28,9 @@ param(

[string]$AllowlistPath,

[switch]$ReportOnly
[switch]$ReportOnly,

[string]$AllowlistSection = 'AllowedEnglishValueMatches'
)

Set-StrictMode -Version Latest
Expand Down Expand Up @@ -83,9 +88,9 @@ if (-not [string]::IsNullOrWhiteSpace($AllowlistPath)) {
}

$configuration = Get-Content -LiteralPath $AllowlistPath -Raw | ConvertFrom-Json
$englishValueMatchesProperty = $configuration.PSObject.Properties['AllowedEnglishValueMatches']
$englishValueMatchesProperty = $configuration.PSObject.Properties[$AllowlistSection]
if ($null -eq $englishValueMatchesProperty) {
throw "Localization allowlist file '$AllowlistPath' must define 'AllowedEnglishValueMatches'."
throw "Localization allowlist file '$AllowlistPath' must define '$AllowlistSection'."
}

foreach ($fileProperty in $englishValueMatchesProperty.Value.PSObject.Properties) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ parameters:
# the step succeeds.
- name: failOnValidationError
type: boolean
- name: resourcesDirectory
type: string
- name: allowlistSection
type: string

steps:
# Compose the argument line first so the optional switch can be appended without repeating the
Expand All @@ -21,8 +25,9 @@ steps:
# other than True or False is rejected rather than assumed, so an unrecognised value cannot
# silently downgrade validation to warnings.
- pwsh: |
$arguments = '-ResourcesDirectory "$(Build.SourcesDirectory)/src/Microsoft.Data.SqlClient/src/Resources"' +
' -AllowlistPath "$(Build.SourcesDirectory)/.config/LocalizationValidationAllowlist.json"'
$arguments = '-ResourcesDirectory "$(Build.SourcesDirectory)/${{ parameters.resourcesDirectory }}"' +
' -AllowlistPath "$(Build.SourcesDirectory)/.config/LocalizationValidationAllowlist.json"' +
' -AllowlistSection "${{ parameters.allowlistSection }}"'

$failOnValidationError = '${{ parameters.failOnValidationError }}'
if ($failOnValidationError -eq 'False') {
Expand Down
Loading