Skip to content

Commit dd67039

Browse files
Sync eng/common directory with azure-sdk-tools for PR 1946 (Azure#20656)
* Add ability to exit gracefully when all files in the diff are excluded * Address case where cspell exits with an error when all files from the 'files' config list are excluded by the expressions in 'ignorePaths' * Add tests * Review feedback: impl goes at the bottom and should be treated as a script, logic for testing should happen above that and exit appropriately if running tests * Import common instead of logging * Enable strict mode Co-authored-by: Daniel Jurek <[email protected]>
1 parent 7b2aeaf commit dd67039

File tree

1 file changed

+259
-65
lines changed

1 file changed

+259
-65
lines changed

eng/common/scripts/check-spelling-in-changed-files.ps1

Lines changed: 259 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,54 @@
1+
<#
2+
.SYNOPSIS
3+
Uses cspell (from NPM) to check spelling of recently changed files
4+
5+
.DESCRIPTION
6+
This script checks files that have changed relative to a base branch (default
7+
branch) for spelling errors. Dictionaries and spelling configurations reside
8+
in a configurable `cspell.json` location.
9+
10+
This script uses `npx` and assumes that NodeJS (and by extension `npm` and
11+
`npx`) are installed on the machine. If it does not detect `npx` it will warn
12+
the user and exit with an error.
13+
14+
The entire file is scanned, not just changed sections. Spelling errors in parts
15+
of the file not touched will still be shown.
16+
17+
This script copies the config file supplied in CspellConfigPath to a temporary
18+
location, mutates the config file to include only the files that have changed,
19+
and then uses the mutated config file to call cspell. In the case of success
20+
the temporary file is deleted. In the case of failure the temporary file, whose
21+
location was logged to the console, remains on disk.
22+
23+
.PARAMETER TargetBranch
24+
Git ref to compare changes. This is usually the "base" (GitHub) or "target"
25+
(DevOps) branch for which a pull request would be opened.
26+
27+
.PARAMETER SourceBranch
28+
Git ref to use instead of changes in current repo state. Use `HEAD` here to
29+
check spelling of files that have been committed and exclude any new files or
30+
modified files that are not committed. This is most useful in CI scenarios where
31+
builds may have modified the state of the repo. Leaving this parameter blank
32+
includes files for whom changes have not been committed.
33+
34+
.PARAMETER CspellConfigPath
35+
Optional location to use for cspell.json path. Default value is
36+
`./.vscode/cspell.json`
37+
38+
.PARAMETER ExitWithError
39+
Exit with error code 1 if spelling errors are detected.
40+
41+
.PARAMETER Test
42+
Run test functions against the script logic
43+
44+
.EXAMPLE
45+
./eng/common/scripts/check-spelling-in-changed-files.ps1 -TargetBranch 'target_branch_name'
46+
47+
This will run spell check with changes in the current branch with respect to
48+
`target_branch_name`
49+
50+
#>
51+
152
[CmdletBinding()]
253
Param (
354
[Parameter()]
@@ -10,18 +61,205 @@ Param (
1061
[string] $CspellConfigPath = "./.vscode/cspell.json",
1162

1263
[Parameter()]
13-
[switch] $ExitWithError
64+
[switch] $ExitWithError,
65+
66+
[Parameter()]
67+
[switch] $Test
1468
)
1569

70+
Set-StrictMode -Version 3.0
71+
72+
function TestSpellChecker() {
73+
Test-Exit0WhenAllFilesExcluded
74+
ResetTest
75+
Test-Exit1WhenIncludedFileHasSpellingError
76+
ResetTest
77+
Test-Exit0WhenIncludedFileHasNoSpellingError
78+
ResetTest
79+
Test-Exit1WhenChangedFileAlreadyHasSpellingError
80+
ResetTest
81+
Test-Exit0WhenUnalteredFileHasSpellingError
82+
ResetTest
83+
Test-Exit0WhenSpellingErrorsAndNoExitWithError
84+
}
85+
86+
function Test-Exit0WhenAllFilesExcluded() {
87+
# Arrange
88+
"sepleing errrrrorrrrr" > ./excluded/excluded-file.txt
89+
git add -A
90+
git commit -m "One change"
91+
92+
# Act
93+
&"$PSScriptRoot/check-spelling-in-changed-files.ps1" `
94+
-TargetBranch HEAD~1 `
95+
-ExitWithError
96+
97+
# Assert
98+
if ($LASTEXITCODE -ne 0) {
99+
throw "`$LASTEXITCODE != 0"
100+
}
101+
}
102+
103+
function Test-Exit1WhenIncludedFileHasSpellingError() {
104+
# Arrange
105+
"sepleing errrrrorrrrr" > ./included/included-file.txt
106+
git add -A
107+
git commit -m "One change"
108+
109+
# Act
110+
&"$PSScriptRoot/check-spelling-in-changed-files.ps1" `
111+
-TargetBranch HEAD~1 `
112+
-ExitWithError
113+
114+
# Assert
115+
if ($LASTEXITCODE -ne 1) {
116+
throw "`$LASTEXITCODE != 1"
117+
}
118+
}
119+
120+
function Test-Exit0WhenIncludedFileHasNoSpellingError() {
121+
# Arrange
122+
"correct spelling" > ./included/included-file.txt
123+
git add -A
124+
git commit -m "One change"
125+
126+
# Act
127+
&"$PSScriptRoot/check-spelling-in-changed-files.ps1" `
128+
-TargetBranch HEAD~1 `
129+
-ExitWithError
130+
131+
# Assert
132+
if ($LASTEXITCODE -ne 0) {
133+
throw "`$LASTEXITCODE != 0"
134+
}
135+
}
136+
137+
function Test-Exit1WhenChangedFileAlreadyHasSpellingError() {
138+
# Arrange
139+
"sepleing errrrrorrrrr" > ./included/included-file.txt
140+
git add -A
141+
git commit -m "First change"
142+
143+
"A statement without spelling errors" >> ./included/included-file.txt
144+
git add -A
145+
git commit -m "Second change"
146+
147+
# Act
148+
&"$PSScriptRoot/check-spelling-in-changed-files.ps1" `
149+
-TargetBranch HEAD~1 `
150+
-ExitWithError
151+
152+
# Assert
153+
if ($LASTEXITCODE -ne 1) {
154+
throw "`$LASTEXITCODE != 1"
155+
}
156+
}
157+
158+
function Test-Exit0WhenUnalteredFileHasSpellingError() {
159+
# Arrange
160+
"sepleing errrrrorrrrr" > ./included/included-file-1.txt
161+
git add -A
162+
git commit -m "One change"
163+
164+
"A statement without spelling errors" > ./included/included-file-2.txt
165+
git add -A
166+
git commit -m "Second change"
167+
168+
# Act
169+
&"$PSScriptRoot/check-spelling-in-changed-files.ps1" `
170+
-TargetBranch HEAD~1 `
171+
-ExitWithError
172+
173+
# Assert
174+
if ($LASTEXITCODE -ne 0) {
175+
throw "`$LASTEXITCODE != 0"
176+
}
177+
}
178+
179+
function Test-Exit0WhenSpellingErrorsAndNoExitWithError() {
180+
# Arrange
181+
"sepleing errrrrorrrrr" > ./included/included-file-1.txt
182+
git add -A
183+
git commit -m "One change"
184+
185+
# Act
186+
&"$PSScriptRoot/check-spelling-in-changed-files.ps1" `
187+
-TargetBranch HEAD~1
188+
189+
# Assert
190+
if ($LASTEXITCODE -ne 0) {
191+
throw "`$LASTEXITCODE != 0"
192+
}
193+
}
194+
195+
function SetupTest($workingDirectory) {
196+
Write-Host "Create test temp dir: $workingDirectory"
197+
New-Item -ItemType Directory -Force -Path $workingDirectory | Out-Null
198+
199+
Push-Location $workingDirectory | Out-Null
200+
git init
201+
202+
New-Item -ItemType Directory -Force -Path "./excluded"
203+
New-Item -ItemType Directory -Force -Path "./included"
204+
New-Item -ItemType Directory -Force -Path "./.vscode"
205+
206+
"Placeholder" > "./excluded/placeholder.txt"
207+
"Placeholder" > "./included/placeholder.txt"
208+
209+
$configJsonContent = @"
210+
{
211+
"version": "0.1",
212+
"language": "en",
213+
"ignorePaths": [
214+
".vscode/cspell.json",
215+
"excluded/**"
216+
]
217+
}
218+
"@
219+
$configJsonContent > "./.vscode/cspell.json"
220+
221+
git add -A
222+
git commit -m "Init"
223+
}
224+
225+
function ResetTest() {
226+
# Empty out the working tree
227+
git checkout .
228+
git clean -xdf
229+
230+
$revCount = git rev-list --count HEAD
231+
if ($revCount -gt 1) {
232+
# Reset N-1 changes so there is only the initial commit
233+
$revisionsToReset = $revCount - 1
234+
git reset --hard HEAD~$revisionsToReset
235+
}
236+
}
237+
238+
function TeardownTest($workingDirectory) {
239+
Pop-Location | Out-Null
240+
Write-Host "Remove test temp dir: $workingDirectory"
241+
Remove-Item -Path $workingDirectory -Recurse -Force | Out-Null
242+
}
243+
244+
if ($Test) {
245+
$workingDirectory = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())
246+
247+
SetupTest $workingDirectory
248+
TestSpellChecker
249+
TeardownTest $workingDirectory
250+
Write-Host "Test complete"
251+
exit 0
252+
}
253+
16254
$ErrorActionPreference = "Continue"
17-
. $PSScriptRoot/logging.ps1
255+
. $PSScriptRoot/common.ps1
18256

19-
if ((Get-Command git | Measure-Object).Count -eq 0) {
257+
if ((Get-Command git | Measure-Object).Count -eq 0) {
20258
LogError "Could not locate git. Install git https://git-scm.com/downloads"
21259
exit 1
22260
}
23261

24-
if ((Get-Command npx | Measure-Object).Count -eq 0) {
262+
if ((Get-Command npx | Measure-Object).Count -eq 0) {
25263
LogError "Could not locate npx. Install NodeJS (includes npx and npx) https://nodejs.org/en/download/"
26264
exit 1
27265
}
@@ -31,7 +269,7 @@ if (!(Test-Path $CspellConfigPath)) {
31269
exit 1
32270
}
33271

34-
# Lists names of files that were in some way changed between the
272+
# Lists names of files that were in some way changed between the
35273
# current $SourceBranch and $TargetBranch. Excludes files that were deleted to
36274
# prevent errors in Resolve-Path
37275
Write-Host "git diff --diff-filter=d --name-only $TargetBranch $SourceBranch"
@@ -51,11 +289,12 @@ foreach ($file in $changedFiles) {
51289
$changedFilePaths += $file.Path
52290
}
53291

54-
# Using GetTempPath because it works on linux and windows. Setting .json
55-
# extension because cspell requires the file have a .json or .jsonc extension
56-
$cspellConfigTemporaryPath = Join-Path `
57-
([System.IO.Path]::GetTempPath()) `
58-
"$([System.IO.Path]::GetRandomFileName()).json"
292+
# The "files" list must always contain a file which exists, is not empty, and is
293+
# not excluded in ignorePaths. In this case it will be a file with the contents
294+
# "1" (no spelling errors will be detected)
295+
$notExcludedFile = (New-TemporaryFile).ToString()
296+
"1" >> $notExcludedFile
297+
$changedFilePaths += $notExcludedFile
59298

60299
$cspellConfigContent = Get-Content $CspellConfigPath -Raw
61300
$cspellConfig = ConvertFrom-Json $cspellConfigContent
@@ -74,22 +313,25 @@ Add-Member `
74313
# Set the temporary config file with the mutated configuration. The temporary
75314
# location is used to configure the command and the original file remains
76315
# unchanged.
77-
Write-Host "Setting config in: $cspellConfigTemporaryPath"
316+
Write-Host "Setting config in: $CspellConfigPath"
78317
Set-Content `
79-
-Path $cspellConfigTemporaryPath `
318+
-Path $CspellConfigPath `
80319
-Value (ConvertTo-Json $cspellConfig -Depth 100)
81320

82321
# Use the mutated configuration file when calling cspell
83-
Write-Host "npx cspell lint --config $cspellConfigTemporaryPath"
84-
$spellingErrors = npx cspell lint --config $cspellConfigTemporaryPath
322+
Write-Host "npx cspell lint --config $CspellConfigPath --no-must-find-files "
323+
$spellingErrors = npx cspell lint --config $CspellConfigPath --no-must-find-files
324+
325+
Write-Host "cspell run complete, restoring original configuration."
326+
Set-Content -Path $CspellConfigPath -Value $cspellConfigContent -NoNewLine
85327

86328
if ($spellingErrors) {
87329
$errorLoggingFunction = Get-Item 'Function:LogWarning'
88330
if ($ExitWithError) {
89331
$errorLoggingFunction = Get-Item 'Function:LogError'
90332
}
91333

92-
foreach ($spellingError in $spellingErrors) {
334+
foreach ($spellingError in $spellingErrors) {
93335
&$errorLoggingFunction $spellingError
94336
}
95337
&$errorLoggingFunction "Spelling errors detected. To correct false positives or learn about spell checking see: https://aka.ms/azsdk/engsys/spellcheck"
@@ -98,56 +340,8 @@ if ($spellingErrors) {
98340
exit 1
99341
}
100342
} else {
101-
Write-Host "No spelling errors detected. Removing temporary config file."
102-
Remove-Item -Path $cspellConfigTemporaryPath -Force
343+
Write-Host "No spelling errors detected. Removing temporary file."
344+
Remove-Item -Path $notExcludedFile -Force | Out-Null
103345
}
104346

105347
exit 0
106-
107-
<#
108-
.SYNOPSIS
109-
Uses cspell (from NPM) to check spelling of recently changed files
110-
111-
.DESCRIPTION
112-
This script checks files that have changed relative to a base branch (default
113-
branch) for spelling errors. Dictionaries and spelling configurations reside
114-
in a configurable `cspell.json` location.
115-
116-
This script uses `npx` and assumes that NodeJS (and by extension `npm` and
117-
`npx`) are installed on the machine. If it does not detect `npx` it will warn
118-
the user and exit with an error.
119-
120-
The entire file is scanned, not just changed sections. Spelling errors in parts
121-
of the file not touched will still be shown.
122-
123-
This script copies the config file supplied in CspellConfigPath to a temporary
124-
location, mutates the config file to include only the files that have changed,
125-
and then uses the mutated config file to call cspell. In the case of success
126-
the temporary file is deleted. In the case of failure the temporary file, whose
127-
location was logged to the console, remains on disk.
128-
129-
.PARAMETER TargetBranch
130-
Git ref to compare changes. This is usually the "base" (GitHub) or "target"
131-
(DevOps) branch for which a pull request would be opened.
132-
133-
.PARAMETER SourceBranch
134-
Git ref to use instead of changes in current repo state. Use `HEAD` here to
135-
check spelling of files that have been committed and exclude any new files or
136-
modified files that are not committed. This is most useful in CI scenarios where
137-
builds may have modified the state of the repo. Leaving this parameter blank
138-
includes files for whom changes have not been committed.
139-
140-
.PARAMETER CspellConfigPath
141-
Optional location to use for cspell.json path. Default value is
142-
`./.vscode/cspell.json`
143-
144-
.PARAMETER ExitWithError
145-
Exit with error code 1 if spelling errors are detected.
146-
147-
.EXAMPLE
148-
./eng/common/scripts/check-spelling-in-changed-files.ps1 -TargetBranch 'target_branch_name'
149-
150-
This will run spell check with changes in the current branch with respect to
151-
`target_branch_name`
152-
153-
#>

0 commit comments

Comments
 (0)