Skip to content

Commit c75dcd8

Browse files
authored
Merge pull request #1276 from michaeltlombardi/gh-538/main/vscode_keywords
(GH-538) Add VS Code keywords, vocabulary, and dialect to `dsc-lib-jsonschema`
2 parents 9312bc1 + 024ec49 commit c75dcd8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+6477
-102
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ utfx = { version = "0.1" }
200200
uuid = { version = "1.18", features = ["v4"] }
201201
# dsc-lib, dsc-lib-jsonschema
202202
url = { version = "2.5" }
203-
# dsc-lib
203+
# dsc-lib, dsc-lib-jsonschema
204204
urlencoding = { version = "2.1" }
205205
# dsc-lib
206206
which = { version = "8.0" }

build.helpers.psm1

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,16 @@ function Install-PowerShellTestPrerequisite {
644644
Write-Verbose "Installing module 'Pester'"
645645
Install-PSResource Pester -WarningAction Ignore -Repository $repository -TrustRepository
646646
}
647+
648+
if (-not (Get-Module -ListAvailable -Name YaYaml)) {
649+
Write-Verbose "Installing module 'YaYaml'"
650+
Install-PSResource YaYaml -WarningAction Ignore -Repository $repository -TrustRepository
651+
}
652+
653+
if (-not (Get-Module -ListAvailable -Name PSToml)) {
654+
Write-Verbose "Installing module 'PSToml'"
655+
Install-PSResource PSToml -WarningAction Ignore -Repository $repository -TrustRepository
656+
}
647657
}
648658
}
649659

dsc/tests/dsc_i18n.tests.ps1

Lines changed: 233 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,252 @@
22
# Licensed under the MIT License.
33

44
BeforeDiscovery {
5-
$tomls = Get-ChildItem $PSScriptRoot/../../en-us.toml -Recurse -File
6-
$projects = @()
7-
$tomls | ForEach-Object {
8-
$projectName = (Split-Path $_ -Parent | Split-Path -Parent)
9-
$projects += @{ project = $projectName; toml = $_ }
10-
}
11-
}
5+
#region Rust i18n type definitions and functions
6+
class DscProjectRustTranslationInfo {
7+
[System.Collections.Generic.Dictionary[string, string]] $Table
8+
[System.Collections.Generic.HashSet[string]] $DuplicateTranslations
9+
[System.Collections.Generic.HashSet[string]] $MissingTranslations
10+
[System.Collections.Generic.HashSet[string]] $UnusedTranslations
11+
[System.Collections.Generic.HashSet[string]] $UsedTranslations
1212

13-
Describe 'Internationalization tests' {
14-
It 'Project <project> uses i18n strings from <toml>' -TestCases $projects {
15-
param($project, $toml)
13+
[string] GetTranslationString([string]$translationKey) {
14+
if ($null -eq $this.Table) {
15+
$this.Initialize()
16+
}
17+
return $this.Table[$translationKey]
18+
}
1619

17-
$i18n = [System.Collections.Hashtable]::new([System.StringComparer]::Ordinal)
18-
$prefix = ''
19-
Get-Content -Path $toml | ForEach-Object {
20-
if ($_ -match '\[(?<prefix>.*?)\]') {
21-
$prefix = $Matches['prefix']
20+
[void] Initialize() {
21+
if ($null -ne $this.Table) {
22+
return
2223
}
23-
elseif ($_ -match '^(?<key>\w+)\s?=\s?"(?<value>.*?)"') {
24-
$key = $prefix + '.' + $Matches['key']
25-
$i18n[$key] = 0
24+
$this.Table = [System.Collections.Generic.Dictionary[string, string]]::new()
25+
}
26+
27+
[void] ProcessData(
28+
[System.Collections.Specialized.OrderedDictionary]$data,
29+
[string]$prefix,
30+
[int]$version
31+
) {
32+
foreach ($key in $data.Keys) {
33+
$keyData = $data[$key]
34+
$workingKey = @($prefix, $key) -join '.'
35+
36+
if ($keyData -is [System.Collections.Specialized.OrderedDictionary]) {
37+
$this.ProcessData($keyData, $workingKey, $version)
38+
} elseif ($keyData -is [string]) {
39+
if ($key -match '^[a-zA-Z]+-[a-zA-Z]+' -and $version -eq 2) {
40+
if ($key -eq 'en-us') {
41+
if (-not [string]::IsNullOrEmpty($this.Table[$prefix])) {
42+
$this.DuplicateTranslations.Add($prefix)
43+
}
44+
$this.Table[$prefix] = $keyData
45+
}
46+
continue
47+
}
48+
49+
if (-not [string]::IsNullOrEmpty($this.Table[$workingKey])) {
50+
$this.DuplicateTranslations.Add($workingKey)
51+
}
52+
$this.Table[$workingKey] = $keyData
53+
}
2654
}
2755
}
2856

29-
$patterns = @{
30-
t = '(?s)\bt\!\(\s*"(?<key>.*?)".*?\)'
31-
panic_t = '(?s)\bpanic_t\!\(\s*"(?<key>.*?)".*?\)'
32-
assert_t = '(?s)\bassert_t\!\(\s*.*?,\s*"(?<key>.*?)".*?\)'
57+
[void] LoadData([System.Collections.Specialized.OrderedDictionary]$data) {
58+
$this.Initialize()
59+
60+
[ValidateRange(1,2)][int]$version = 1
61+
if ($data['_version'] -is [int]) {
62+
$version = $data['_version']
63+
}
64+
65+
foreach ($key in $data.Keys) {
66+
if ($key -eq '_version') {
67+
continue
68+
}
69+
$keyData = $data[$key]
70+
71+
if ($keyData -is [string]) {
72+
if (-not [string]::IsNullOrEmpty($this.Table[$key])) {
73+
$this.DuplicateTranslations.Add($key)
74+
}
75+
$this.Table[$key] = $keyData
76+
} elseif ($keyData -is [System.Collections.Specialized.OrderedDictionary]) {
77+
$this.ProcessData($keyData, $key, $version)
78+
}
79+
}
3380
}
3481

35-
$missing = @()
36-
Get-ChildItem -Recurse -Path $project -Include *.rs -File | ForEach-Object {
37-
$content = Get-Content -Path $_ -Raw
38-
foreach ($pattern in $patterns.keys) {
39-
($content | Select-String -Pattern $patterns[$pattern] -AllMatches).Matches | ForEach-Object {
40-
# write-verbose -verbose "Line: $_"
41-
if ($null -ne $_) {
42-
$key = $_.Groups['key'].Value
43-
if ($i18n.ContainsKey($key)) {
44-
$i18n[$key] = 1
45-
}
46-
else {
47-
$missing += $key
82+
[void] LoadFile([System.IO.FileInfo]$file) {
83+
$content = Get-Content -Path $file.FullName -Raw
84+
$extension = $file.Extension.Substring(1)
85+
86+
$fileData = switch ($extension) {
87+
'toml' {
88+
$content | PSToml\ConvertFrom-Toml
89+
break
90+
}
91+
'yaml' {
92+
$content | YaYaml\ConvertFrom-Yaml
93+
break
94+
}
95+
default {
96+
throw "Unsupported translation file format '$extension' - must be TOML or YAML."
97+
}
98+
}
99+
100+
$this.LoadData($fileData)
101+
}
102+
103+
[void] CheckTranslations([System.IO.DirectoryInfo]$projectFolder) {
104+
$this.UsedTranslations = Get-TranslationKey -ProjectDirectory $projectFolder
105+
$definedKeys = [System.Collections.Generic.HashSet[string]]$this.Table.Keys
106+
$this.MissingTranslations = $this.UsedTranslations.Where({ $_ -notin $definedKeys })
107+
$this.UnusedTranslations = $definedKeys.Where({ $_ -notin $this.UsedTranslations })
108+
}
109+
110+
DscProjectRustTranslationInfo([System.Collections.Specialized.OrderedDictionary]$data) {
111+
$this.LoadData($data)
112+
}
113+
114+
DscProjectRustTranslationInfo([System.IO.FileInfo]$file) {
115+
$this.LoadFile($file)
116+
}
117+
DscProjectRustTranslationInfo([System.IO.DirectoryInfo]$directory) {
118+
$localesFolder = if ($directory.BaseName -eq 'locales') {
119+
$directory
120+
} else {
121+
Join-Path -Path $directory -ChildPath 'locales'
122+
}
123+
$projectFolder = Split-Path -Path $localesFolder -Parent
124+
125+
if (-not (Test-Path $localesFolder)) {
126+
throw "Unable to find valid locales folder from in directory '$directory'"
127+
}
128+
129+
$tomlFile = Join-Path -Path $localesFolder -ChildPath 'en-us.toml'
130+
if (Test-Path -Path $tomlFile) {
131+
$this.LoadFile((Get-Item -Path $tomlFile))
132+
}
133+
$yamlFiles = Get-ChildItem -Path $localesFolder | Where-Object Extension -match 'ya?ml'
134+
foreach ($yamlFile in $yamlFiles) {
135+
$this.LoadFile($yamlFile)
136+
}
137+
138+
$this.CheckTranslations($projectFolder)
139+
}
140+
}
141+
142+
function Get-TranslationKey {
143+
[cmdletbinding()]
144+
[OutputType([string[]])]
145+
param(
146+
[Parameter(Mandatory)]
147+
[string]$ProjectDirectory
148+
)
149+
150+
begin {
151+
$patterns = @{
152+
t = '(?s)\bt\!\(\s*"(?<key>.*?)".*?\)'
153+
panic_t = '(?s)\bpanic_t\!\(\s*"(?<key>.*?)".*?\)'
154+
assert_t = '(?s)\bassert_t\!\(\s*.*?,\s*"(?<key>.*?)".*?\)'
155+
}
156+
[string[]]$keys = @()
157+
}
158+
159+
process {
160+
if (-not (Test-Path $ProjectDirectory -PathType Container)) {
161+
throw "Invalid target, '$ProjectDirectory' isn't a directory or doesn't exist."
162+
}
163+
164+
Get-ChildItem -Recurse -Path $ProjectDirectory -Include *.rs -File | ForEach-Object {
165+
$file = $_
166+
$content = Get-Content -Path $file -Raw
167+
foreach ($pattern in $patterns.keys) {
168+
($content | Select-String -Pattern $patterns[$pattern] -AllMatches).Matches | ForEach-Object {
169+
if ($null -ne $_) {
170+
$key = $_.Groups['key'].Value
171+
$keys += $key
48172
}
49173
}
50174
}
51175
}
52176
}
53177

54-
$missing | Should -BeNullOrEmpty -Because "The following i18n keys are missing from $toml :`n$($missing | Out-String)"
55-
$unused = $i18n.GetEnumerator() | Where-Object { $_.Value -eq 0 } | ForEach-Object { $_.Key }
56-
$unused | Should -BeNullOrEmpty -Because "The following i18n keys are unused in the project:`n$($unused | Out-String)"
178+
end {
179+
$keys
180+
}
181+
}
182+
#endregion Rust i18n type definitions and functions
183+
184+
# Limit the folders to recursively search for rust i18n translation strings
185+
$rootFolders = @(
186+
'adapters'
187+
'dsc'
188+
'extensions'
189+
'grammars'
190+
'lib'
191+
'pal'
192+
'resources'
193+
'tools'
194+
'y2j'
195+
)
196+
$localeFolders = $rootFolders | ForEach-Object -Process {
197+
Get-ChildItem $PSScriptRoot/../../$_/locales -Recurse -Directory
198+
}
199+
200+
$projects = @()
201+
$localeFolders | ForEach-Object -Process {
202+
$projects += @{
203+
project = Split-Path $_ -Parent
204+
translationInfo = [DscProjectRustTranslationInfo]::new($_)
205+
}
206+
}
207+
}
208+
209+
Describe 'Internationalization tests' {
210+
Context '<project>' -ForEach $projects {
211+
It 'Uses translation strings' {
212+
$check = @{
213+
Not = $true
214+
BeNullOrEmpty = $true
215+
Because = "'$project' defines at least one translation file"
216+
}
217+
$translationInfo.UsedTranslations | Should @check
218+
}
219+
It 'Does not define any duplicate translation strings' {
220+
$check = @{
221+
BeNullOrEmpty = $true
222+
Because = (@(
223+
"The following translation keys are defined more than once:"
224+
$translationInfo.DuplicateTranslations | ConvertTo-Json -Depth 2
225+
) -join ' ')
226+
}
227+
228+
$translationInfo.DuplicateTranslations | Should @check
229+
}
230+
231+
It 'Uses every defined translation string' {
232+
$check = @{
233+
BeNullOrEmpty = $true
234+
Because = (@(
235+
"The following translation keys are defined but not used:"
236+
$translationInfo.UnusedTranslations | ConvertTo-Json -Depth 2
237+
) -join ' ')
238+
}
239+
$translationInfo.UnusedTranslations | Should @check
240+
}
241+
242+
It 'Defines every used translation string' {
243+
$check = @{
244+
BeNullOrEmpty = $true
245+
Because = (@(
246+
"The following translation keys are used but not defined:"
247+
$translationInfo.MissingTranslations | ConvertTo-Json -Depth 2
248+
) -join ' ')
249+
}
250+
$translationInfo.MissingTranslations | Should @check
251+
}
57252
}
58253
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
doc-valid-idents = ["IntelliSense", ".."]

lib/dsc-lib-jsonschema/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ edition = "2024"
77
doctest = false # Disable doc tests by default for compilation speed
88

99
[dependencies]
10+
jsonschema = { workspace = true }
1011
regex = { workspace = true }
1112
rust-i18n = { workspace = true }
1213
schemars = { workspace = true }
1314
serde = { workspace = true }
1415
serde_json = { workspace = true }
1516
tracing = { workspace = true }
1617
url = { workspace = true }
18+
urlencoding = { workspace = true }
1719

1820
[dev-dependencies]
1921
# Helps review complex comparisons, like schemas

0 commit comments

Comments
 (0)