-
Notifications
You must be signed in to change notification settings - Fork 410
Add new MissingTryBlock rule #2179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
iRon7
wants to merge
8
commits into
PowerShell:main
Choose a base branch
from
iRon7:#2098MissingTryBlock
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
1da47c4
#2098 Add new MissingTryBlock rule
iRon7 e9a2365
Changed severity level of MissingTryBlock rule from Error to Warning …
iRon7 50f751f
Updated tests for MissingTryBlock rule to reflect severity change fro…
iRon7 79e80aa
Update Rules/MissingTryBlock.cs
iRon7 47507bb
Grammar: “which is likely a mistake and result in … error” is ungramm…
iRon7 449fdd1
Merge branch '#2098MissingTryBlock' of https://github.com/iRon7/PSScr…
iRon7 e1a8d3f
Update Rules/Strings.resx
iRon7 a420820
Update Rules/MissingTryBlock.cs
iRon7 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Globalization; | ||
| using System.Management.Automation.Language; | ||
|
|
||
| #if !CORECLR | ||
| using System.ComponentModel.Composition; | ||
| #endif | ||
|
|
||
| namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules | ||
| { | ||
| #if !CORECLR | ||
| [Export(typeof(IScriptRule))] | ||
| #endif | ||
|
|
||
| /// <summary> | ||
| /// Rule that warns when catch or finally blocks are used without a corresponding try block | ||
| /// </summary> | ||
| public class MissingTryBlock : IScriptRule | ||
| { | ||
| /// <summary> | ||
| /// Analyzes the PowerShell AST for catch or finally blocks that miss the try block. | ||
| /// </summary> | ||
| /// <param name="ast">The PowerShell Abstract Syntax Tree to analyze.</param> | ||
| /// <param name="fileName">The name of the file being analyzed (for diagnostic reporting).</param> | ||
| /// <returns>A collection of diagnostic records for each violation.</returns> | ||
| public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName) | ||
| { | ||
| if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); | ||
|
|
||
| // Find all FunctionDefinitionAst in the Ast | ||
| var missingTryAsts = ast.FindAll(testAst => | ||
| // Normally should be part of a TryStatementAst | ||
| testAst is StringConstantExpressionAst stringAst && | ||
| // Catch or finally are reserved keywords and should be bare words | ||
| stringAst.StringConstantType == StringConstantType.BareWord && | ||
| ( | ||
| String.Equals(stringAst.Value, "catch", StringComparison.OrdinalIgnoreCase) || | ||
| String.Equals(stringAst.Value, "finally", StringComparison.OrdinalIgnoreCase) | ||
| ) && | ||
| stringAst.Parent is CommandAst commandAst && | ||
| // Only violate if the catch or finally is the first command element | ||
| commandAst.CommandElements[0] == stringAst, | ||
| true | ||
| ); | ||
|
|
||
| foreach (StringConstantExpressionAst missingTryAst in missingTryAsts) | ||
| { | ||
| yield return new DiagnosticRecord( | ||
| string.Format( | ||
| CultureInfo.CurrentCulture, | ||
| Strings.MissingTryBlockError, | ||
| CultureInfo.CurrentCulture.TextInfo.ToTitleCase(missingTryAst.Value)), | ||
| missingTryAst.Extent, | ||
| GetName(), | ||
| DiagnosticSeverity.Warning, | ||
| fileName, | ||
| missingTryAst.Value | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| public string GetCommonName() => Strings.MissingTryBlockCommonName; | ||
|
|
||
| public string GetDescription() => Strings.MissingTryBlockDescription; | ||
|
|
||
| public string GetName() => string.Format( | ||
| CultureInfo.CurrentCulture, | ||
| Strings.NameSpaceFormat, | ||
| GetSourceName(), | ||
| Strings.MissingTryBlockName); | ||
|
|
||
| public RuleSeverity GetSeverity() => RuleSeverity.Warning; | ||
|
|
||
| public string GetSourceName() => Strings.SourceName; | ||
|
|
||
| public SourceType GetSourceType() => SourceType.Builtin; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT License. | ||
|
|
||
| [Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'False positive')] | ||
| param() | ||
|
|
||
| BeforeAll { | ||
| $ruleName = "PSMissingTryBlock" | ||
| } | ||
|
|
||
| Describe "MissingTryBlock" { | ||
| Context "Violates" { | ||
| It "Catch is missing a try block" { | ||
| $scriptDefinition = { catch { "An error occurred." } }.ToString() | ||
| $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) | ||
| $violations.Count | Should -Be 1 | ||
| $violations.Severity | Should -Be Warning | ||
| $violations.Extent.Text | Should -Be catch | ||
| $violations.Message | Should -Be 'Catch is missing a try block' | ||
| $violations.RuleSuppressionID | Should -Be catch | ||
|
iRon7 marked this conversation as resolved.
|
||
| } | ||
|
|
||
| It "Finally is missing a try block" { | ||
| $scriptDefinition = { finally { "Finalizing..." } }.ToString() | ||
| $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) | ||
| $violations.Count | Should -Be 1 | ||
| $violations.Severity | Should -Be Warning | ||
| $violations.Extent.Text | Should -Be finally | ||
| $violations.Message | Should -Be 'Finally is missing a try block' | ||
| $violations.RuleSuppressionID | Should -Be finally | ||
| } | ||
|
|
||
| It "Single line catch and finally is missing a try block" { | ||
| $scriptDefinition = { | ||
| catch { "An error occurred." } finally { "Finalizing..." } | ||
| }.ToString() | ||
| $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) | ||
| $violations.Count | Should -Be 1 | ||
| $violations.Severity | Should -Be Warning | ||
| $violations.Extent.Text | Should -Be catch | ||
| $violations.Message | Should -Be 'Catch is missing a try block' | ||
| $violations.RuleSuppressionID | Should -Be catch | ||
| } | ||
|
|
||
| It "Multi line catch and finally is missing a try block" { | ||
| $scriptDefinition = { | ||
| catch { "An error occurred." } | ||
| finally { "Finalizing..." } | ||
| }.ToString() | ||
| $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) | ||
| $violations.Count | Should -Be 2 | ||
| $violations[0].Severity | Should -Be Warning | ||
| $violations[0].Extent.Text | Should -Be catch | ||
| $violations[0].Message | Should -Be 'Catch is missing a try block' | ||
| $violations[0].RuleSuppressionID | Should -Be catch | ||
| $violations[1].Severity | Should -Be Warning | ||
| $violations[1].Extent.Text | Should -Be finally | ||
| $violations[1].Message | Should -Be 'Finally is missing a try block' | ||
| $violations[1].RuleSuppressionID | Should -Be finally | ||
| } | ||
| } | ||
|
|
||
| Context "Compliant" { | ||
| It "try-catch block" { | ||
| $scriptDefinition = { | ||
| try { NonsenseString } | ||
| catch { "An error occurred." } | ||
| }.ToString() | ||
| $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) | ||
| $violations | Should -BeNullOrEmpty | ||
| } | ||
|
|
||
| It "try-catch-final statement" { | ||
| $scriptDefinition = { | ||
| try { NonsenseString } | ||
| catch { "An error occurred." } | ||
| finally { "Finalizing..." } | ||
| }.ToString() | ||
| $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) | ||
| $violations | Should -BeNullOrEmpty | ||
| } | ||
|
|
||
| It "Single line try statement" { | ||
| $scriptDefinition = { | ||
| try { NonsenseString } catch { "An error occurred." } finally { "Finalizing..." } | ||
| }.ToString() | ||
| $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) | ||
| $violations | Should -BeNullOrEmpty | ||
| } | ||
|
|
||
| It "Catch as parameter" { | ||
| $scriptDefinition = { Write-Host Catch }.ToString() | ||
| $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) | ||
| $violations | Should -BeNullOrEmpty | ||
| } | ||
|
|
||
| It "Catch as double quoted string" { | ||
| $scriptDefinition = { "Catch" }.ToString() | ||
| $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) | ||
| $violations | Should -BeNullOrEmpty | ||
| } | ||
|
|
||
| It "Catch as single quoted string" { | ||
| $scriptDefinition = { 'Catch' }.ToString() | ||
| $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) | ||
| $violations | Should -BeNullOrEmpty | ||
| } | ||
| } | ||
|
|
||
| Context "Suppressed" { | ||
| It "Multi line catch and finally is missing a try block" { | ||
| $scriptDefinition = { | ||
| [Diagnostics.CodeAnalysis.SuppressMessage('PSMissingTryBlock', '', Justification = 'Test')] | ||
| param() | ||
| catch { "An error occurred." } | ||
| finally { "Finalizing..." } | ||
| }.ToString() | ||
| $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) | ||
| $violations | Should -BeNullOrEmpty | ||
| } | ||
|
|
||
| It "Multi line catch and finally is missing a try block for catch only" { | ||
| $scriptDefinition = { | ||
| [Diagnostics.CodeAnalysis.SuppressMessage('PSMissingTryBlock', 'finally', Justification = 'Test')] | ||
| param() | ||
| catch { "An error occurred." } | ||
| finally { "Finalizing..." } | ||
| }.ToString() | ||
| $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) | ||
| $violations.Count | Should -Be 1 | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| --- | ||
| description: Missing Try Block | ||
| ms.date: 04/22/2026 | ||
| ms.topic: reference | ||
| title: MissingTryBlock | ||
| --- | ||
| # MissingTryBlock | ||
|
|
||
| **Severity Level: Warning** | ||
|
|
||
| ## Description | ||
|
|
||
| The `catch` and `finally` blocks should be preceded by a `try` block. | ||
| Otherwise, the `catch` and `finally` blocks will be interpreted as commands, which is likely a mistake and | ||
| will result in a "*The term 'catch' is not recognized as a name of a cmdlet*" error at runtime. | ||
|
|
||
| ## How | ||
|
|
||
| Add a `try` block before the `catch` and `finally` blocks. | ||
|
|
||
| ## Example | ||
|
|
||
| ### Wrong | ||
|
|
||
| ```powershell | ||
| catch { "An error occurred." } | ||
| ``` | ||
|
|
||
| ### Correct | ||
|
|
||
| ```powershell | ||
| try { $a = 1 / $b } | ||
| catch { "Attempted to divide by zero." } | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.