Skip to content

Commit b04b04d

Browse files
authored
1 parent c144099 commit b04b04d

File tree

2 files changed

+225
-0
lines changed

2 files changed

+225
-0
lines changed

UseASCII.Tests.ps1

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#Requires -Modules @{ModuleName="Pester"; ModuleVersion="5.0.0"}
2+
3+
Describe 'UseASCII' {
4+
5+
BeforeAll {
6+
$TemporaryFile = [System.IO.Path]::ChangeExtension((New-TemporaryFile), '.ps1')
7+
}
8+
9+
Context 'Positives' {
10+
11+
It 'Smart characters' {
12+
$Result = Invoke-ScriptAnalyzer -CustomRulePath .\UseASCII.psm1 -ScriptDefinition { Write-Host 'coöperate' }.ToString()
13+
$Result.RuleName | Should -Be 'PSUseASCII'
14+
$Result.Severity | Should -Be 'Information'
15+
}
16+
17+
It 'Fix' {
18+
Set-Content -LiteralPath $TemporaryFile -Encoding utf8 -NoNewline -Value {
19+
<#
20+
.SYNOPSIS
21+
Use ASCII test
22+
.DESCRIPTION
23+
The main use of diacritics in Latin script is to change the sound-values of the letters to which they are added.
24+
Historically, English has used the diaeresis diacritic to indicate the correct pronunciation of ambiguous words,
25+
such as "coöperate", without which the <oo> letter sequence could be misinterpreted to be pronounced
26+
#>
27+
28+
# [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseAscii', 'coöperate')]
29+
Param()
30+
31+
Write-Host test –ForegroundColor Red -BackgroundColor Green
32+
Write-Host 'No-break space'
33+
}.ToString()
34+
Invoke-ScriptAnalyzer -Fix -CustomRulePath .\UseASCII.psm1 -Path $TemporaryFile
35+
Get-Content -Raw -Literal $TemporaryFile | Should -be {
36+
<#
37+
.SYNOPSIS
38+
Use ASCII test
39+
.DESCRIPTION
40+
The main use of diacritics in Latin script is to change the sound-values of the letters to which they are added.
41+
Historically, English has used the diaeresis diacritic to indicate the correct pronunciation of ambiguous words,
42+
such as "cooperate", without which the <oo> letter sequence could be misinterpreted to be pronounced
43+
#>
44+
45+
# [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseAscii', 'cooperate')]
46+
Param()
47+
48+
Write-Host "test" -ForegroundColor 'Red' -BackgroundColor 'Green'
49+
Write-Host 'No-break space'
50+
}.ToString()
51+
}
52+
53+
It 'Suppress' {
54+
Set-Content -LiteralPath $TemporaryFile -Encoding utf8 -NoNewline -Value {
55+
<#
56+
.SYNOPSIS
57+
Use ASCII test
58+
.DESCRIPTION
59+
The main use of diacritics in Latin script is to change the sound-values of the letters to which they are added.
60+
Historically, English has used the diaeresis diacritic to indicate the correct pronunciation of ambiguous words,
61+
such as "coöperate", without which the <oo> letter sequence could be misinterpreted to be pronounced
62+
#>
63+
64+
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseAscii', 'coöperate')]
65+
Param()
66+
67+
Write-Host test –ForegroundColor Red -BackgroundColor Green
68+
Write-Host 'No-break space'
69+
}.ToString()
70+
Invoke-ScriptAnalyzer -Fix -CustomRulePath .\UseASCII.psm1 -Path $TemporaryFile -ErrorAction SilentlyContinue
71+
Get-Content -Raw -Literal $TemporaryFile | Should -be {
72+
<#
73+
.SYNOPSIS
74+
Use ASCII test
75+
.DESCRIPTION
76+
The main use of diacritics in Latin script is to change the sound-values of the letters to which they are added.
77+
Historically, English has used the diaeresis diacritic to indicate the correct pronunciation of ambiguous words,
78+
such as "coöperate", without which the <oo> letter sequence could be misinterpreted to be pronounced
79+
#>
80+
81+
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseAscii', 'coöperate')]
82+
Param()
83+
84+
Write-Host "test" -ForegroundColor 'Red' -BackgroundColor 'Green'
85+
Write-Host 'No-break space'
86+
}.ToString()
87+
}
88+
}
89+
90+
AfterAll {
91+
# if (Test-Path -LiteralPath $TemporaryFile) { Remove-Item -LiteralPath $TemporaryFile }
92+
}
93+
}

UseASCII.psm1

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#Requires -Version 3.0
2+
3+
function Measure-UseASCII {
4+
<#
5+
.SYNOPSIS
6+
Use UTF-8 Characters
7+
.DESCRIPTION
8+
Validates if only ASCII characters are used and reveal the position of any violation.
9+
.INPUTS
10+
[System.Management.Automation.Language.ScriptBlockAst]
11+
.OUTPUTS
12+
[Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord]
13+
#>
14+
15+
[CmdletBinding()]
16+
[OutputType([Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord])]
17+
Param (
18+
[Parameter(Mandatory = $true)]
19+
[ValidateNotNullOrEmpty()]
20+
[System.Management.Automation.Language.ScriptBlockAst]
21+
$ScriptBlockAst
22+
)
23+
Begin {
24+
function GetNonASCIIPositions ([String]$Text) {
25+
$LF = [Char]0x0A
26+
$DEL = [Char]0x7F
27+
$LineNumber = 1; $ColumnNumber = 1
28+
for ($Offset = 0; $Offset -lt $Text.Length; $Offset++) {
29+
$Character = $Text[$Offset]
30+
if ($Character -eq $Lf) {
31+
$LineNumber++
32+
$ColumnNumber = 0
33+
}
34+
else {
35+
$ColumnNumber++
36+
if ($Character -gt $Del) {
37+
[PSCustomObject]@{
38+
Character = $Character
39+
Offset = $Offset
40+
LineNumber = $LineNumber
41+
ColumnNumber = $ColumnNumber
42+
}
43+
}
44+
}
45+
}
46+
}
47+
48+
function CharToHex([Char]$Char) {
49+
([Int][Char]$Char).ToString('x4')
50+
}
51+
function SuggestedASCII([Char]$Char) {
52+
switch ([Int]$Char) {
53+
0x00A0 { ' ' }
54+
0x1806 { '-' }
55+
0x2010 { '-' }
56+
0x2011 { '-' }
57+
0x2012 { '-' }
58+
0x2013 { '-' }
59+
0x2014 { '-' }
60+
0x2015 { '-' }
61+
0x2016 { '-' }
62+
0x2212 { '-' }
63+
0x2018 { "'" }
64+
0x2019 { "'" }
65+
0x201A { "'" }
66+
0x201B { "'" }
67+
0x201C { '"' }
68+
0x201D { '"' }
69+
0x201E { '"' }
70+
0x201F { '"' }
71+
Default {
72+
$ASCII = $Char.ToString().Normalize([System.text.NormalizationForm]::FormD)[0]
73+
if ($ASCII -le 0x7F) { $ASCII } else { '_' }
74+
}
75+
76+
}
77+
}
78+
}
79+
80+
Process {
81+
# As the AST parser, tokenize doesn't capture (smart) quotes
82+
# $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptBlockAst.Extent.Text, [ref]$null)
83+
# $Violations = $Tokens.where{ $_.Content -cMatch '[\u0100-\uFFFF]' }
84+
$Violations = GetNonASCIIPositions $ScriptBlockAst.Extent.Text
85+
[Collections.Generic.List[Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord]]@(
86+
Foreach ($Violation in $Violations) {
87+
$Text = $ScriptBlockAst.Extent.Text
88+
For ($i = $Violation.Offset - 1; $i -ge 0; $i--) { if ($Text[$i] -NotMatch '\w') { break } }
89+
$Start = $i + 1
90+
For ($i = $Violation.Offset + 1; $i -lt $Text.Length; $i++) { if ($Text[$i] -NotMatch '\w') { break } }
91+
$Length = $i - $Start
92+
$Word = $Text.SubString($Start, $Length)
93+
94+
$StartPosition = [System.Management.Automation.Language.ScriptPosition]::new(
95+
$Null,
96+
$Violation.LineNumber,
97+
$Violation.ColumnNumber,
98+
$ScriptBlockAst.Extent.Text
99+
)
100+
$EndPosition = [System.Management.Automation.Language.ScriptPosition]::new(
101+
$Null,
102+
$Violation.LineNumber,
103+
($Violation.ColumnNumber + 1),
104+
$ScriptBlockAst.Extent.Text
105+
)
106+
$Extent = [System.Management.Automation.Language.ScriptExtent]::new($StartPosition, $EndPosition)
107+
$Character = $Violation.Character
108+
$UniCode = "U+$(CharToHex $Character)"
109+
$SuggestedASCII = SuggestedASCII $Character
110+
$AscCode = "U+$(CharToHex $SuggestedASCII)"
111+
[Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord]@{
112+
Message = "Non-ASCII character $UniCode found in: $Word"
113+
Extent = $Extent
114+
RuleName = 'PSUseASCII'
115+
Severity = 'Information'
116+
RuleSuppressionID = $Word
117+
SuggestedCorrections = [System.Collections.ObjectModel.Collection[Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.CorrectionExtent]](
118+
[Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.CorrectionExtent]::New(
119+
$Violation.LineNumber,
120+
$Violation.LineNumber,
121+
$Violation.ColumnNumber,
122+
($Violation.ColumnNumber + 1),
123+
"$SuggestedASCII",
124+
"Replace '$Character' ($UniCode) with '$SuggestedASCII' ($AscCode)"
125+
)
126+
)
127+
}
128+
}
129+
)
130+
}
131+
}
132+
Export-ModuleMember -Function Measure-*

0 commit comments

Comments
 (0)