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 ()]
253Param (
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
37275Write-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 "
78317Set-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
86328if ($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
105347exit 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