From 695b47f9004536365a8680e9c68424ef7c38b060 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Thu, 1 Aug 2019 23:26:38 +1000 Subject: [PATCH] Fix synopsis comment capture #214 (#258) --- CHANGELOG.md | 1 + ThirdPartyNotices.txt | 2 ++ src/PSRule/Host/HostHelper.cs | 31 +++++++--------------- src/PSRule/Pipeline/PipelineContext.cs | 20 ++++++++++++++ tests/PSRule.Tests/FromFile.Rule.ps1 | 10 ++++--- tests/PSRule.Tests/PSRule.Common.Tests.ps1 | 22 ++++++++++++++- 6 files changed, 61 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b9013ba3b..b66843463c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Fix module reloading with different versions. [#254](https://github.com/BernieWhite/PSRule/issues/254) - Fix not finding rules in current path by default. [#256](https://github.com/BernieWhite/PSRule/issues/256) +- Fix rule synopsis comment capture. [#214](https://github.com/BernieWhite/PSRule/issues/214) ## v0.8.0-B190742 (pre-release) diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 3de85c93b9..754ecf7bfd 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -61,6 +61,8 @@ File: Manatee.Json https://github.com/gregsdennis/Manatee.Json +The MIT License (MIT) + Copyright (c) 2016 Little Crab Solutions (Greg Dennis) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/src/PSRule/Host/HostHelper.cs b/src/PSRule/Host/HostHelper.cs index a162fbbf52..e7f0d7c99e 100644 --- a/src/PSRule/Host/HostHelper.cs +++ b/src/PSRule/Host/HostHelper.cs @@ -1,14 +1,11 @@ using PSRule.Annotations; using PSRule.Pipeline; -using PSRule.Resources; using PSRule.Rules; using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.IO; using System.Linq; using System.Management.Automation; -using System.Text; namespace PSRule.Host { @@ -39,22 +36,22 @@ public static DependencyGraph GetRuleBlockGraph(RuleSource[] source, /// public static BlockMetadata GetCommentMeta(string path, int lineNumber, int offset) { - if (lineNumber == 1) + var context = PipelineContext.CurrentThread; + + if (lineNumber <= 2 || context.ExecutionScope == ExecutionScope.None || context.SourceContentCache == null) { return new BlockMetadata(); } - var lines = File.ReadAllLines(path, Encoding.UTF8); + var lines = context.SourceContentCache; - var i = lineNumber - 1; + var i = lineNumber - 2; var comments = new List(); - for (; i >= 0; i--) + // Back track lines with comments immediately before block + for (; i >= 0 && lines[i].Contains("#"); i--) { - if (lines[i].Contains("#")) - { - comments.Insert(0, lines[i]); - } + comments.Insert(0, lines[i]); } var metadata = new BlockMetadata(); @@ -68,13 +65,11 @@ public static BlockMetadata GetCommentMeta(string path, int lineNumber, int offs { metadata.Synopsis = comment.Substring(15); } - if (comment.StartsWith("# Synopsis: ")) { metadata.Synopsis = comment.Substring(12); } } - } return metadata; @@ -106,14 +101,8 @@ private static IEnumerable GetLanguageBlock(RuleSource[] sources { ps.Commands.Clear(); - if (!File.Exists(source.Path)) - { - throw new FileNotFoundException(PSRuleResources.ScriptNotFound, source.Path); - } - - PipelineContext.CurrentThread.Source = source; PipelineContext.CurrentThread.VerboseRuleDiscovery(path: source.Path); - //PipelineContext.CurrentThread.UseSource(source: source); + PipelineContext.CurrentThread.EnterSourceScope(source: source); // Invoke script ps.AddScript(string.Concat("& '", source.Path, "'"), true); @@ -144,7 +133,7 @@ private static IEnumerable GetLanguageBlock(RuleSource[] sources { PipelineContext.CurrentThread.Logger.ExitScope(); PipelineContext.CurrentThread.ExecutionScope = ExecutionScope.None; - PipelineContext.CurrentThread.Source = null; + PipelineContext.CurrentThread.ExitSourceScope(); ps.Runspace = null; ps.Dispose(); } diff --git a/src/PSRule/Pipeline/PipelineContext.cs b/src/PSRule/Pipeline/PipelineContext.cs index 5b241aaf06..4072926432 100644 --- a/src/PSRule/Pipeline/PipelineContext.cs +++ b/src/PSRule/Pipeline/PipelineContext.cs @@ -7,9 +7,11 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Security.Cryptography; +using System.Text; namespace PSRule.Pipeline { @@ -52,6 +54,7 @@ internal sealed class PipelineContext : IDisposable, IBindingContext internal PSRuleOption Option; internal RuleSource Source; internal Dictionary DataCache; + internal string[] SourceContentCache; internal ExecutionScope ExecutionScope; public HashAlgorithm ObjectHashAlgorithm @@ -138,6 +141,23 @@ internal Runspace GetRunspace() return _Runspace; } + internal void EnterSourceScope(RuleSource source) + { + if (!File.Exists(source.Path)) + { + throw new FileNotFoundException(PSRuleResources.ScriptNotFound, source.Path); + } + + Source = source; + SourceContentCache = File.ReadAllLines(source.Path, Encoding.UTF8); + } + + internal void ExitSourceScope() + { + Source = null; + SourceContentCache = null; + } + public void Pass() { if (_PassStream == OutcomeLogStream.None) diff --git a/tests/PSRule.Tests/FromFile.Rule.ps1 b/tests/PSRule.Tests/FromFile.Rule.ps1 index 462f378ab2..24e6729406 100644 --- a/tests/PSRule.Tests/FromFile.Rule.ps1 +++ b/tests/PSRule.Tests/FromFile.Rule.ps1 @@ -164,12 +164,16 @@ Rule 'WithPSCommandPath' { } Rule 'WithCsv' { - $True; + $True } Rule 'WithSleep' { - Start-Sleep -Milliseconds 50; - $True; + Start-Sleep -Milliseconds 50 + $True +} + +Rule 'WithNoSynopsis' { + $True } # Synopsis: Test for Recommend keyword diff --git a/tests/PSRule.Tests/PSRule.Common.Tests.ps1 b/tests/PSRule.Tests/PSRule.Common.Tests.ps1 index 7e7f8c7f70..c3c0451841 100644 --- a/tests/PSRule.Tests/PSRule.Common.Tests.ps1 +++ b/tests/PSRule.Tests/PSRule.Common.Tests.ps1 @@ -997,17 +997,25 @@ Describe 'Get-PSRule' -Tag 'Get-PSRule','Common' { Mock -CommandName 'GetCulture' -ModuleName 'PSRule' -Verifiable -MockWith { return 'en-ZZ'; } + # From markdown $result = Get-PSRule -Path $ruleFilePath -Name 'FromFile1'; $result | Should -Not -BeNullOrEmpty; $result.RuleName | Should -Be 'FromFile1'; $result.Synopsis | Should -Be 'This is a synopsis.'; $result.Info.Annotations.culture | Should -Be 'en-ZZ'; + Assert-VerifiableMock; + # From comments $result = Get-PSRule -Path $ruleFilePath -Name 'FromFile2'; $result | Should -Not -BeNullOrEmpty; $result.RuleName | Should -Be 'FromFile2'; $result.Synopsis | Should -Be 'Test rule 2'; - Assert-VerifiableMock; + + # No comments + $result = Get-PSRule -Path $ruleFilePath -Name 'WithNoSynopsis'; + $result | Should -Not -BeNullOrEmpty; + $result.RuleName | Should -Be 'WithNoSynopsis'; + $result.Synopsis | Should -BeNullOrEmpty; } It 'Handles empty path' { @@ -1074,6 +1082,10 @@ Describe 'Get-PSRule' -Tag 'Get-PSRule','Common' { $result.RuleName | Should -BeIn 'M1.Rule1', 'M1.Rule2'; } + if ($Null -ne (Get-Module -Name TestModule -ErrorAction SilentlyContinue)) { + $Null = Remove-Module -Name TestModule; + } + It 'Handles path spaces' { # Copy file $testParentPath = Join-Path -Path $outputPath -ChildPath 'Program Files\'; @@ -1113,6 +1125,10 @@ Describe 'Get-PSRule' -Tag 'Get-PSRule','Common' { $result.RuleName | Should -BeIn 'M1.Rule1', 'M1.Rule2'; } + if ($Null -ne (Get-Module -Name TestModule -ErrorAction SilentlyContinue)) { + $Null = Remove-Module -Name TestModule; + } + It 'Read from documentation' { Mock -CommandName 'GetCulture' -ModuleName 'PSRule' -MockWith { return 'en-US'; @@ -1149,6 +1165,10 @@ Describe 'Get-PSRule' -Tag 'Get-PSRule','Common' { $result[0].Description | Should -Be 'Synopsis en-AU.'; $result[0].Info.Annotations.culture | Should -Be 'en-AU'; } + + if ($Null -ne (Get-Module -Name TestModule -ErrorAction SilentlyContinue)) { + $Null = Remove-Module -Name TestModule; + } } # Context 'Get rule with invalid path' {