From 9874ac6855c4aaca9eb0e0de992e3cc7dc5bbdae Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Mon, 4 May 2026 15:17:23 -0700 Subject: [PATCH] chore(release): 0.4.0 --- CHANGELOG.md | 23 +++++ CLAUDE.md | 68 ++++++++++++++ GoodEnoughRules/GoodEnoughRules.psd1 | 2 +- .../Public/Measure-GremlinCharacter.ps1 | 76 ++++++++++++++++ .../Public/Measure-TODOComment.ps1 | 16 ++-- docs/en-US/Measure-GremlinCharacter.md | 89 +++++++++++++++++++ docs/en-US/Measure-TODOComment.md | 4 +- .../Measure-BasicWebRequestProperty.tests.ps1 | 1 - tests/Measure-GremlinCharacter.tests.ps1 | 76 ++++++++++++++++ ...ure-InvokeWebRequestWithoutBasic.tests.ps1 | 1 - tests/PSScriptAnalyzerRules.psm1 | 2 +- tests/fixtures/Gremlin.ps1 | 4 + tests/fixtures/GremlinSuppressed.ps1 | 10 +++ 13 files changed, 358 insertions(+), 14 deletions(-) create mode 100644 CLAUDE.md create mode 100644 GoodEnoughRules/Public/Measure-GremlinCharacter.ps1 create mode 100644 docs/en-US/Measure-GremlinCharacter.md create mode 100644 tests/Measure-GremlinCharacter.tests.ps1 create mode 100644 tests/fixtures/Gremlin.ps1 create mode 100644 tests/fixtures/GremlinSuppressed.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index df2233d..3dd35e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.4.0] 2026-05-04 + +### Added + +- `Measure-GremlinCharacter`: New rule to detect invisible or visually + deceptive Unicode characters (gremlins) such as zero-width spaces, + bidirectional overrides, and curly quotes. 19 characters flagged with + per-character severity (`Error`, `Warning`, or `Information`). + Inspired by [vscode-gremlins](https://github.com/nhoizey/vscode-gremlins). +- Tests for `Measure-GremlinCharacter` with per-character `-ForEach` + cases, a negative clean-code case, a fixture-based detection test, + and a suppression test using `SuppressMessageAttribute`. +- `CLAUDE.md` with project guidance for Claude Code. + +### Changed + +- `Measure-TODOComment`: Updated `Token` parameter type to `Token[]` + to match how PSScriptAnalyzer invokes token-based rules; renamed + `$matches` to `$regexMatches` to avoid collision with the automatic + `$Matches` variable; normalized keyword casing to lowercase. +- `tests/PSScriptAnalyzerRules.psm1`: Proxy module now loads explicitly + from the `Output\GoodEnoughRules` build directory. + ## [0.3.1] 2025-12-14 ### Fixed diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..cc7cb1e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,68 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Commands + +Bootstrap dependencies (first time or after `requirements.psd1` changes): +```powershell +.\build.ps1 -Bootstrap +``` + +Build (compiles module to `Output/`): +```powershell +.\build.ps1 +``` + +Run all tests: +```powershell +.\build.ps1 -Task Test +``` + +Run a single test file: +```powershell +.\build.ps1 -Task Build +Invoke-Pester -Path .\tests\Measure-TODOComment.tests.ps1 +``` + +List available tasks: +```powershell +.\build.ps1 -Help +``` + +Spell check: +```powershell +cspell lint "**/*.ps1" "**/*.md" +``` + +## Architecture + +This is a **PSScriptAnalyzer custom rules module**. The build uses `psake` → `PowerShellBuild`, which compiles all `Public/*.ps1` files into a single monolithic `GoodEnoughRules.psm1` in `Output//`. + +**Tests run against the compiled `Output/` module**, not the source files. Always build before running tests. + +### Rule conventions + +Each rule lives in `GoodEnoughRules/Public/Measure-.ps1` and exports one function named `Measure-`. + +PSScriptAnalyzer custom rules come in two shapes: + +- **AST rules** — parameter type is an AST node (e.g., `[ScriptBlockAst]`). PSScriptAnalyzer calls the rule once per matching node. +- **Token rules** — parameter type is `[System.Management.Automation.Language.Token[]]`. PSScriptAnalyzer passes **all tokens as a single array** in one call. The rule must iterate over `$Token` internally with `foreach ($tok in $Token)`. + +> **Important:** Token rules receive `Token[]` from `Invoke-ScriptAnalyzer`, but tests that call the function directly (e.g., `$tokens | ForEach-Object { Measure-X -Token $_ }`) pass one token per call. The `foreach ($tok in $Token)` pattern handles both. + +Rules return `[Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord]` objects via hashtable constructor with keys: `Message`, `Extent`, `RuleName` (`$PSCmdlet.MyInvocation.InvocationName`), `Severity` (`'Error'`, `'Warning'`, or `'Information'`). + +### Tests + +Each rule has a corresponding `tests/Measure-.tests.ps1`. Tests use Pester 5 and follow these patterns: + +- **Inline token tests** — parse a fake script string with `[Parser]::ParseInput(...)`, pipe tokens to the rule function directly, assert on the result. +- **Path tests** — use `Invoke-ScriptAnalyzer -Path` with `CustomRulePath` pointing to `$script:outputModVerModule` (the compiled `.psm1`). Fixture files live in `tests/fixtures/`. +- **Suppression test** — a fixture file with `[Diagnostics.CodeAnalysis.SuppressMessageAttribute('Measure-RuleName', '')]` on a function; run via `Invoke-ScriptAnalyzer -Path` and assert empty result. +- **`-ForEach` cases** — use hashtable entries (`@{ Key = Value }`) so test names interpolate as `'Detects '`. + +### Docs + +`docs/en-US/Measure-.md` — PlatyPS-style help. Kept in sync with the function's comment-based help. diff --git a/GoodEnoughRules/GoodEnoughRules.psd1 b/GoodEnoughRules/GoodEnoughRules.psd1 index a8d1243..4e4885e 100644 --- a/GoodEnoughRules/GoodEnoughRules.psd1 +++ b/GoodEnoughRules/GoodEnoughRules.psd1 @@ -12,7 +12,7 @@ RootModule = 'GoodEnoughRules.psm1' # Version number of this module. - ModuleVersion = '0.3.1' + ModuleVersion = '0.4.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/GoodEnoughRules/Public/Measure-GremlinCharacter.ps1 b/GoodEnoughRules/Public/Measure-GremlinCharacter.ps1 new file mode 100644 index 0000000..62e035f --- /dev/null +++ b/GoodEnoughRules/Public/Measure-GremlinCharacter.ps1 @@ -0,0 +1,76 @@ +function Measure-GremlinCharacter { + <# + .SYNOPSIS + Rule to detect invisible or visually deceptive Unicode characters (gremlins). + .DESCRIPTION + This rule detects Unicode characters that are invisible or visually similar to + legitimate characters, such as zero-width spaces, bidirectional overrides, and + curly quotes. These characters can introduce subtle bugs or security issues that + are nearly impossible to see in an editor. + .EXAMPLE + Measure-GremlinCharacter -Token $Token + + This will check if the given Token contains any gremlin characters. + .PARAMETER Token + The token to check for gremlin characters. + .INPUTS + [System.Management.Automation.Language.Token[]] + .OUTPUTS + [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]] + .NOTES + Inspired by https://github.com/nhoizey/vscode-gremlins + #> + [CmdletBinding()] + [OutputType([Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.Token[]] + $Token + ) + + begin { + # Maps Unicode char to description + PSScriptAnalyzer severity + $script:gremlins = [ordered]@{ + [char]0x0003 = @{ Description = 'end of text'; Severity = 'Warning' } + [char]0x000B = @{ Description = 'line tabulation'; Severity = 'Warning' } + [char]0x00A0 = @{ Description = 'non-breaking space'; Severity = 'Information' } + [char]0x00AD = @{ Description = 'soft hyphen'; Severity = 'Information' } + [char]0x200B = @{ Description = 'zero width space'; Severity = 'Error' } + [char]0x200C = @{ Description = 'zero width non-joiner'; Severity = 'Warning' } + [char]0x200E = @{ Description = 'left-to-right mark'; Severity = 'Error' } + [char]0x2013 = @{ Description = 'en dash'; Severity = 'Warning' } + [char]0x2018 = @{ Description = 'left single quotation mark'; Severity = 'Warning' } + [char]0x2019 = @{ Description = 'right single quotation mark'; Severity = 'Warning' } + [char]0x201C = @{ Description = 'left double quotation mark'; Severity = 'Warning' } + [char]0x201D = @{ Description = 'right double quotation mark'; Severity = 'Warning' } + [char]0x2029 = @{ Description = 'paragraph separator'; Severity = 'Error' } + [char]0x2066 = @{ Description = 'left-to-right isolate'; Severity = 'Error' } + [char]0x2069 = @{ Description = 'pop directional isolate'; Severity = 'Error' } + [char]0x202C = @{ Description = 'pop directional formatting'; Severity = 'Error' } + [char]0x202D = @{ Description = 'left-to-right override'; Severity = 'Error' } + [char]0x202E = @{ Description = 'right-to-left override'; Severity = 'Error' } + [char]0xFFFC = @{ Description = 'object replacement character'; Severity = 'Error' } + } + } + + process { + foreach ($tok in $Token) { + $text = $tok.Extent.Text + + foreach ($char in $script:gremlins.Keys) { + if (-not $text.Contains($char)) { + continue + } + $info = $script:gremlins[$char] + [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord]@{ + 'Message' = "Gremlin character found: U+{0:X4} ({1}). This character may be invisible or visually deceptive." -f [int]$char, $info.Description + 'Extent' = $tok.Extent + 'RuleName' = $PSCmdlet.MyInvocation.InvocationName + 'Severity' = $info.Severity + } + } + } + } +} diff --git a/GoodEnoughRules/Public/Measure-TODOComment.ps1 b/GoodEnoughRules/Public/Measure-TODOComment.ps1 index c105fe2..833fb27 100644 --- a/GoodEnoughRules/Public/Measure-TODOComment.ps1 +++ b/GoodEnoughRules/Public/Measure-TODOComment.ps1 @@ -11,7 +11,7 @@ function Measure-TODOComment { .PARAMETER Token The token to check for TODO comments. .INPUTS - [System.Management.Automation.Language.Token] + [System.Management.Automation.Language.Token[]] .OUTPUTS [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]] .NOTES @@ -19,15 +19,15 @@ function Measure-TODOComment { #> [CmdletBinding()] [OutputType([Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord])] - Param + param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [System.Management.Automation.Language.Token] + [System.Management.Automation.Language.Token[]] $Token ) - Begin { + begin { $toDoIndicators = @( 'TODO', 'FIXME', @@ -42,21 +42,21 @@ function Measure-TODOComment { $regEx = [regex]::new($regExPattern, $regExOptions) } - Process { + process { if (-not $Token.Type -ne 'Comment') { return } #region Finds ASTs that match the predicates. foreach ($i in $Token.Extent.Text) { try { - $matches = $regEx.Matches($i) + $regexMatches = $regEx.Matches($i) } catch { $PSCmdlet.ThrowTerminatingError($PSItem) } - if ($matches.Count -eq 0) { + if ($regexMatches.Count -eq 0) { continue } - $matches | ForEach-Object { + $regexMatches | ForEach-Object { [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord]@{ 'Message' = "TODO comment found. Please consider removing it or tracking with issue." 'Extent' = $Token.Extent diff --git a/docs/en-US/Measure-GremlinCharacter.md b/docs/en-US/Measure-GremlinCharacter.md new file mode 100644 index 0000000..f60e62e --- /dev/null +++ b/docs/en-US/Measure-GremlinCharacter.md @@ -0,0 +1,89 @@ +--- +external help file: GoodEnoughRules-help.xml +Module Name: GoodEnoughRules +online version: +schema: 2.0.0 +--- + +# Measure-GremlinCharacter + +## SYNOPSIS +Rule to detect invisible or visually deceptive Unicode characters (gremlins). + +## SYNTAX + +``` +Measure-GremlinCharacter [-Token] [-ProgressAction ] [] +``` + +## DESCRIPTION +This rule detects Unicode characters that are invisible or visually similar to +legitimate characters, such as zero-width spaces, bidirectional overrides, and +curly quotes. These characters can introduce subtle bugs or security issues that +are nearly impossible to see in an editor. + +Severity levels reflect how dangerous the character is: + +- **Error** - Bidirectional overrides, zero-width spaces, and control characters + that can actively obscure code intent or enable Trojan-source attacks. +- **Warning** - Typographic characters (curly quotes, en dash) that are unlikely + to be intentional in source code and may cause parse errors. +- **Information** - Characters like non-breaking spaces that are rarely intentional + but generally harmless. + +Inspired by the [vscode-gremlins](https://github.com/nhoizey/vscode-gremlins) extension. + +## EXAMPLES + +### EXAMPLE 1 +``` +Measure-GremlinCharacter -Token $Token +``` + +This will check if the given Token contains any gremlin characters. + +## PARAMETERS + +### -Token +The token to check for gremlin characters. + +```yaml +Type: Token[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### [System.Management.Automation.Language.Token] +## OUTPUTS + +### [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]] +## NOTES +Inspired by https://github.com/nhoizey/vscode-gremlins + +## RELATED LINKS diff --git a/docs/en-US/Measure-TODOComment.md b/docs/en-US/Measure-TODOComment.md index 234dfa3..aa01f33 100644 --- a/docs/en-US/Measure-TODOComment.md +++ b/docs/en-US/Measure-TODOComment.md @@ -13,7 +13,7 @@ Rule to detect if TODO style comments are present. ## SYNTAX ``` -Measure-TODOComment [-Token] [-ProgressAction ] [] +Measure-TODOComment [-Token] [-ProgressAction ] [] ``` ## DESCRIPTION @@ -34,7 +34,7 @@ This would check if the given ScriptBlockAst contains any TODO comments. The token to check for TODO comments. ```yaml -Type: Token +Type: Token[] Parameter Sets: (All) Aliases: diff --git a/tests/Measure-BasicWebRequestProperty.tests.ps1 b/tests/Measure-BasicWebRequestProperty.tests.ps1 index 79031f0..282f624 100644 --- a/tests/Measure-BasicWebRequestProperty.tests.ps1 +++ b/tests/Measure-BasicWebRequestProperty.tests.ps1 @@ -14,7 +14,6 @@ Describe 'Measure-BasicWebRequestProperty' { # Remove all versions of the module from the session. Pester can't handle multiple versions. Get-Module $env:BHProjectName | Remove-Module -Force -ErrorAction Ignore Import-Module -Name $outputModVerManifest -Verbose:$false -ErrorAction Stop - Import-Module -Name 'PSScriptAnalyzer' -Verbose:$false -ErrorAction Inquire } Context 'Method usage with Invoke-WebRequest and UseBasicParsing' { It 'Detects bad method usage' { diff --git a/tests/Measure-GremlinCharacter.tests.ps1 b/tests/Measure-GremlinCharacter.tests.ps1 new file mode 100644 index 0000000..e1bd3fb --- /dev/null +++ b/tests/Measure-GremlinCharacter.tests.ps1 @@ -0,0 +1,76 @@ +Describe 'Measure-GremlinCharacter' { + BeforeAll { + if (-not $env:BHPSModuleManifest) { + Set-BuildEnvironment -Path "$PSScriptRoot\.." -Force + } + $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest + $outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output' + $outputModDir = Join-Path -Path $outputDir -ChildPath $env:BHProjectName + $outputModVerDir = Join-Path -Path $outputModDir -ChildPath $manifest.ModuleVersion + $script:outputModVerModule = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psm1" + $outputModVerManifest = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psd1" + + Get-Module $env:BHProjectName | Remove-Module -Force -ErrorAction Ignore + Import-Module -Name $outputModVerManifest -Verbose:$false -ErrorAction Stop + $script:fixturesDir = Join-Path -Path $PSScriptRoot -ChildPath 'fixtures' + } + + It 'Detects (U+)' -ForEach @( + @{ Char = [char]0x0003; CodePoint = '0003'; Description = 'end of text'; Severity = 'Warning' } + @{ Char = [char]0x000B; CodePoint = '000B'; Description = 'line tabulation'; Severity = 'Warning' } + @{ Char = [char]0x00A0; CodePoint = '00A0'; Description = 'non-breaking space'; Severity = 'Information' } + @{ Char = [char]0x00AD; CodePoint = '00AD'; Description = 'soft hyphen'; Severity = 'Information' } + @{ Char = [char]0x200B; CodePoint = '200B'; Description = 'zero width space'; Severity = 'Error' } + @{ Char = [char]0x200C; CodePoint = '200C'; Description = 'zero width non-joiner'; Severity = 'Warning' } + @{ Char = [char]0x200E; CodePoint = '200E'; Description = 'left-to-right mark'; Severity = 'Error' } + @{ Char = [char]0x2013; CodePoint = '2013'; Description = 'en dash'; Severity = 'Warning' } + @{ Char = [char]0x2018; CodePoint = '2018'; Description = 'left single quotation mark'; Severity = 'Warning' } + @{ Char = [char]0x2019; CodePoint = '2019'; Description = 'right single quotation mark'; Severity = 'Warning' } + @{ Char = [char]0x201C; CodePoint = '201C'; Description = 'left double quotation mark'; Severity = 'Warning' } + @{ Char = [char]0x201D; CodePoint = '201D'; Description = 'right double quotation mark'; Severity = 'Warning' } + @{ Char = [char]0x2029; CodePoint = '2029'; Description = 'paragraph separator'; Severity = 'Error' } + @{ Char = [char]0x2066; CodePoint = '2066'; Description = 'left-to-right isolate'; Severity = 'Error' } + @{ Char = [char]0x2069; CodePoint = '2069'; Description = 'pop directional isolate'; Severity = 'Error' } + @{ Char = [char]0x202C; CodePoint = '202C'; Description = 'pop directional formatting'; Severity = 'Error' } + @{ Char = [char]0x202D; CodePoint = '202D'; Description = 'left-to-right override'; Severity = 'Error' } + @{ Char = [char]0x202E; CodePoint = '202E'; Description = 'right-to-left override'; Severity = 'Error' } + @{ Char = [char]0xFFFC; CodePoint = 'FFFC'; Description = 'object replacement character'; Severity = 'Error' } + ) { + $fakeScript = "Write-Host '${Char}hello'" + $tokens = $null + [System.Management.Automation.Language.Parser]::ParseInput($fakeScript, [ref]$tokens, [ref]$null) | Out-Null + $result = $tokens | ForEach-Object { Measure-GremlinCharacter -Token $_ } + $result.Count | Should -BeExactly 1 + $result[0].Message | Should -Be "Gremlin character found: U+${CodePoint} (${Description}). This character may be invisible or visually deceptive." + $result[0].Severity | Should -Be $Severity + } + + It 'Does not flag clean code' { + $fakeScript = "Write-Host 'Hello, World!'" + $tokens = $null + [System.Management.Automation.Language.Parser]::ParseInput($fakeScript, [ref]$tokens, [ref]$null) | Out-Null + $result = $tokens | ForEach-Object { Measure-GremlinCharacter -Token $_ } + $result | Should -BeNullOrEmpty + } + + It 'Can be suppressed with SuppressMessageAttribute' { + $invokeScriptAnalyzerSplat = @{ + Path = "$script:fixturesDir\GremlinSuppressed.ps1" + IncludeRule = 'Measure-GremlinCharacter' + CustomRulePath = $script:outputModVerModule + } + $result = Invoke-ScriptAnalyzer @invokeScriptAnalyzerSplat + $result | Should -BeNullOrEmpty + } + + It 'Can Detect on Path' { + $invokeScriptAnalyzerSplat = @{ + Path = "$script:fixturesDir\Gremlin.ps1" + IncludeRule = 'Measure-GremlinCharacter' + CustomRulePath = $script:outputModVerModule + } + $result = Invoke-ScriptAnalyzer @invokeScriptAnalyzerSplat + $result.Count | Should -BeExactly 1 + $result[0].Message | Should -Be "Gremlin character found: U+2013 (en dash). This character may be invisible or visually deceptive." + } +} diff --git a/tests/Measure-InvokeWebRequestWithoutBasic.tests.ps1 b/tests/Measure-InvokeWebRequestWithoutBasic.tests.ps1 index 04b28cf..40f8e9a 100644 --- a/tests/Measure-InvokeWebRequestWithoutBasic.tests.ps1 +++ b/tests/Measure-InvokeWebRequestWithoutBasic.tests.ps1 @@ -14,7 +14,6 @@ Describe 'Measure-InvokeWebRequestWithoutBasic' { # Remove all versions of the module from the session. Pester can't handle multiple versions. Get-Module $env:BHProjectName | Remove-Module -Force -ErrorAction Ignore Import-Module -Name $outputModVerManifest -Verbose:$false -ErrorAction Stop - Import-Module -Name 'PSScriptAnalyzer' -Verbose:$false -ErrorAction Inquire } Context 'When Invoke-WebRequest is used without UseBasicParsing' { diff --git a/tests/PSScriptAnalyzerRules.psm1 b/tests/PSScriptAnalyzerRules.psm1 index e93db18..4725bfc 100644 --- a/tests/PSScriptAnalyzerRules.psm1 +++ b/tests/PSScriptAnalyzerRules.psm1 @@ -1,2 +1,2 @@ -$rules = Import-Module -Name 'GoodEnoughRules' -PassThru +$rules = Import-Module "$PSScriptRoot\..\Output\GoodEnoughRules" -PassThru Export-ModuleMember -Function @($rules.ExportedCommands.Keys) diff --git a/tests/fixtures/Gremlin.ps1 b/tests/fixtures/Gremlin.ps1 new file mode 100644 index 0000000..8c906f5 --- /dev/null +++ b/tests/fixtures/Gremlin.ps1 @@ -0,0 +1,4 @@ +function Test-Suppressed { + param() + Write-Host '–hello' +} diff --git a/tests/fixtures/GremlinSuppressed.ps1 b/tests/fixtures/GremlinSuppressed.ps1 new file mode 100644 index 0000000..971a194 --- /dev/null +++ b/tests/fixtures/GremlinSuppressed.ps1 @@ -0,0 +1,10 @@ +function Test-Suppressed { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'GoodEnoughRules\Measure-GremlinCharacter', + '', + Scope = 'Function', + Justification = 'We love gremlins and want to use them in our tests.' + )] + param() + return '–hello' +}