From 10ddacc1e3511f733e594ae101441a46726ffc34 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Sun, 21 May 2017 20:56:58 -0700 Subject: [PATCH 01/38] Fix PlaceOpenBrace documentation --- RuleDocumentation/PlaceOpenBrace.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RuleDocumentation/PlaceOpenBrace.md b/RuleDocumentation/PlaceOpenBrace.md index d2df3e80f..31949901c 100644 --- a/RuleDocumentation/PlaceOpenBrace.md +++ b/RuleDocumentation/PlaceOpenBrace.md @@ -29,7 +29,7 @@ Enable or disable the rule during ScriptAnalyzer invocation. #### OnSameLine: bool (Default value is `$true`) -# true +Enforce open brace to be on the same line as that of its preceding keyword. #### NewLineAfter: bool (Default value is `$true`) From b71bcd7d13ed2269f4911918b30d7af39703996e Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Mon, 22 May 2017 19:28:56 -0700 Subject: [PATCH 02/38] Add a skeleteon of a cmdlet to format script --- Engine/Commands/InvokeFormatterCommand.cs | 144 ++++++++++++++++++++++ Engine/PSScriptAnalyzer.psd1 | 2 +- 2 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 Engine/Commands/InvokeFormatterCommand.cs diff --git a/Engine/Commands/InvokeFormatterCommand.cs b/Engine/Commands/InvokeFormatterCommand.cs new file mode 100644 index 000000000..f91b0da09 --- /dev/null +++ b/Engine/Commands/InvokeFormatterCommand.cs @@ -0,0 +1,144 @@ +// +// Copyright (c) Microsoft Corporation. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; +using System.Collections; +using System.Globalization; +using System.Linq; +using System.Management.Automation; + +namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands +{ + [Cmdlet(VerbsLifecycle.Invoke, "Formatter")] + public class InvokeFormatterCommand : PSCmdlet, IOutputWriter + { + private const string defaultSettingsPreset = "CodeFormatting"; + private Settings defaultSettings; + private Settings inputSettings; + + + [ParameterAttribute(Mandatory = true)] + [ValidateNotNull] + public string ScriptDefinition { get; set; } + + [Parameter(Mandatory = false)] + [ValidateNotNull] + public object Settings { get; set; } + + protected override void BeginProcessing() + { + // todo move to a common initalize session method + Helper.Instance = new Helper(SessionState.InvokeCommand, this); + Helper.Instance.Initialize(); + object settingsFound; + var settingsMode = PowerShell.ScriptAnalyzer.Settings.FindSettingsMode( + Settings, + null, + out settingsFound); + + switch (settingsMode) + { + case SettingsMode.Auto: + this.WriteVerbose( + String.Format( + CultureInfo.CurrentCulture, + Strings.SettingsNotProvided, + "")); + this.WriteVerbose( + String.Format( + CultureInfo.CurrentCulture, + Strings.SettingsAutoDiscovered, + (string)settingsFound)); + break; + + case SettingsMode.Preset: + case SettingsMode.File: + this.WriteVerbose( + String.Format( + CultureInfo.CurrentCulture, + Strings.SettingsUsingFile, + (string)settingsFound)); + break; + + case SettingsMode.Hashtable: + this.WriteVerbose( + String.Format( + CultureInfo.CurrentCulture, + Strings.SettingsUsingHashtable)); + break; + + default: // case SettingsMode.None + this.WriteVerbose( + String.Format( + CultureInfo.CurrentCulture, + Strings.SettingsCannotFindFile)); + break; + } + + try + { + defaultSettings = new Settings(defaultSettingsPreset); + if (settingsMode != SettingsMode.None) + { + inputSettings = new Settings(settingsFound); + ValidateInputSettings(); + } + else + { + inputSettings = defaultSettings; + } + } + catch + { + this.WriteWarning(String.Format(CultureInfo.CurrentCulture, Strings.SettingsNotParsable)); + return; + } + } + + + protected override void ProcessRecord() + { + var ruleOrder = new string[] + { + "PSPlaceCloseBrace", + "PSPlaceOpenBrace", + "PSUseConsistentWhitespace", + "PSUseConsistentIndentation", + "PSAlignAssignmentStatement" + }; + + foreach (var rule in ruleOrder) + { + if (!inputSettings.RuleArguments.ContainsKey(rule)) + { + continue; + } + + this.WriteVerbose("Running " + rule); + var currentSettingsHashtable = new Hashtable(); + currentSettingsHashtable.Add("IncludeRules", new string[] { rule }); + var ruleSettings = new Hashtable(); + ruleSettings.Add(rule, new Hashtable(inputSettings.RuleArguments[rule])); + currentSettingsHashtable.Add("Rules", ruleSettings); + var currentSettings = new Settings(currentSettingsHashtable); + ScriptAnalyzer.Instance.UpdateSettings(currentSettings); + ScriptAnalyzer.Instance.Initialize(this, null, null, null, null, false, false); + } + } + + private void ValidateInputSettings() + { + // todo implement this + return; + } + } +} diff --git a/Engine/PSScriptAnalyzer.psd1 b/Engine/PSScriptAnalyzer.psd1 index fceb75f35..57aab3d82 100644 --- a/Engine/PSScriptAnalyzer.psd1 +++ b/Engine/PSScriptAnalyzer.psd1 @@ -65,7 +65,7 @@ FormatsToProcess = @('ScriptAnalyzer.format.ps1xml') FunctionsToExport = @() # Cmdlets to export from this module -CmdletsToExport = @('Get-ScriptAnalyzerRule','Invoke-ScriptAnalyzer') +CmdletsToExport = @('Get-ScriptAnalyzerRule', 'Invoke-ScriptAnalyzer', 'Invoke-Formatter') # Variables to export from this module VariablesToExport = @() From f36a956f50d779a992de233a0baf557db64dba0a Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Tue, 23 May 2017 17:46:36 -0700 Subject: [PATCH 03/38] Apply edits in a sequential manner --- Engine/Commands/InvokeFormatterCommand.cs | 73 ++++++++++++++++++++++- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/Engine/Commands/InvokeFormatterCommand.cs b/Engine/Commands/InvokeFormatterCommand.cs index f91b0da09..d1b65784c 100644 --- a/Engine/Commands/InvokeFormatterCommand.cs +++ b/Engine/Commands/InvokeFormatterCommand.cs @@ -12,12 +12,16 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Management.Automation; +using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands { + using PSSASettings = Microsoft.Windows.PowerShell.ScriptAnalyzer.Settings; + [Cmdlet(VerbsLifecycle.Invoke, "Formatter")] public class InvokeFormatterCommand : PSCmdlet, IOutputWriter { @@ -30,12 +34,37 @@ public class InvokeFormatterCommand : PSCmdlet, IOutputWriter [ValidateNotNull] public string ScriptDefinition { get; set; } + // todo make this settings and maybe rename Settings class [Parameter(Mandatory = false)] [ValidateNotNull] public object Settings { get; set; } +#if DEBUG + [Parameter(Mandatory = false)] + public SwitchParameter AttachAndDebug + { + get { return attachAndDebug; } + set { attachAndDebug = value; } + } + private bool attachAndDebug = false; +#endif + protected override void BeginProcessing() { +#if DEBUG + if (attachAndDebug) + { + if (System.Diagnostics.Debugger.IsAttached) + { + System.Diagnostics.Debugger.Break(); + } + else + { + System.Diagnostics.Debugger.Launch(); + } + } +#endif + // todo move to a common initalize session method Helper.Instance = new Helper(SessionState.InvokeCommand, this); Helper.Instance.Initialize(); @@ -86,10 +115,12 @@ protected override void BeginProcessing() try { - defaultSettings = new Settings(defaultSettingsPreset); + defaultSettings = new PSSASettings( + defaultSettingsPreset, + PSSASettings.GetSettingPresetFilePath); if (settingsMode != SettingsMode.None) { - inputSettings = new Settings(settingsFound); + inputSettings = new PSSASettings(settingsFound); ValidateInputSettings(); } else @@ -116,6 +147,7 @@ protected override void ProcessRecord() "PSAlignAssignmentStatement" }; + var text = new EditableText(ScriptDefinition); foreach (var rule in ruleOrder) { if (!inputSettings.RuleArguments.ContainsKey(rule)) @@ -131,8 +163,43 @@ protected override void ProcessRecord() currentSettingsHashtable.Add("Rules", ruleSettings); var currentSettings = new Settings(currentSettingsHashtable); ScriptAnalyzer.Instance.UpdateSettings(currentSettings); - ScriptAnalyzer.Instance.Initialize(this, null, null, null, null, false, false); + ScriptAnalyzer.Instance.Initialize(this, null, null, null, null, true, false); + + var corrections = new List(); + var records = Enumerable.Empty(); + + // todo add a check for this while loop so that it doesn't go into a black hole + do + { + var correctionApplied = new HashSet(); + foreach (var correction in corrections) + { + // apply only one edit per line + if (correctionApplied.Contains(correction.StartLineNumber)) + { + continue; + } + + correctionApplied.Add(correction.StartLineNumber); + text.ApplyEdit(correction); + } + + records = ScriptAnalyzer.Instance.AnalyzeScriptDefinition(text.ToString()); + corrections = records.Select(r => r.SuggestedCorrections.ElementAt(0)).ToList(); + + // get unique correction instances + // sort them by line numbers + corrections.Sort((x, y) => + { + return x.StartLineNumber < x.StartLineNumber ? + 1 : + (x.StartLineNumber == x.StartLineNumber ? 0 : -1); + }); + + } while (records.Any()); } + + this.WriteObject(text.ToString()); } private void ValidateInputSettings() From 50f7e2fe2a271f3fb07a1f2f1a61908b85b43332 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Tue, 23 May 2017 17:47:23 -0700 Subject: [PATCH 04/38] Add InvokeFormatterCommand file to csproj --- Engine/ScriptAnalyzerEngine.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Engine/ScriptAnalyzerEngine.csproj b/Engine/ScriptAnalyzerEngine.csproj index 1503e06e1..cfe5d7c1a 100644 --- a/Engine/ScriptAnalyzerEngine.csproj +++ b/Engine/ScriptAnalyzerEngine.csproj @@ -67,6 +67,7 @@ + From 2778ba37f48b711874b481efec11d94670cf4a2e Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Tue, 23 May 2017 17:55:07 -0700 Subject: [PATCH 05/38] Return self instead of new object ater EditableText.ApplyEdit --- Engine/EditableText.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Engine/EditableText.cs b/Engine/EditableText.cs index 12b755f1c..b93dc33dd 100644 --- a/Engine/EditableText.cs +++ b/Engine/EditableText.cs @@ -100,7 +100,8 @@ public EditableText ApplyEdit(TextEdit textEdit) currentLineNumber++; } - return new EditableText(String.Join(NewLine, lines)); + // returning self allows us to chain ApplyEdit calls. + return this; } // TODO Add a method that takes multiple edits, checks if they are unique and applies them. From 6aab926422324c13d41fbc37605327c3feca1c04 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Tue, 23 May 2017 18:06:46 -0700 Subject: [PATCH 06/38] Add checks to prevent formatter loop from getting stuck --- Engine/Commands/InvokeFormatterCommand.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Engine/Commands/InvokeFormatterCommand.cs b/Engine/Commands/InvokeFormatterCommand.cs index d1b65784c..5183f941a 100644 --- a/Engine/Commands/InvokeFormatterCommand.cs +++ b/Engine/Commands/InvokeFormatterCommand.cs @@ -29,17 +29,18 @@ public class InvokeFormatterCommand : PSCmdlet, IOutputWriter private Settings defaultSettings; private Settings inputSettings; - [ParameterAttribute(Mandatory = true)] [ValidateNotNull] public string ScriptDefinition { get; set; } - // todo make this settings and maybe rename Settings class [Parameter(Mandatory = false)] [ValidateNotNull] public object Settings { get; set; } #if DEBUG + /// + /// Attaches to an instance of a .Net debugger + /// [Parameter(Mandatory = false)] public SwitchParameter AttachAndDebug { @@ -167,8 +168,8 @@ protected override void ProcessRecord() var corrections = new List(); var records = Enumerable.Empty(); + var numPreviousCorrections = corrections.Count; - // todo add a check for this while loop so that it doesn't go into a black hole do { var correctionApplied = new HashSet(); @@ -186,6 +187,16 @@ protected override void ProcessRecord() records = ScriptAnalyzer.Instance.AnalyzeScriptDefinition(text.ToString()); corrections = records.Select(r => r.SuggestedCorrections.ElementAt(0)).ToList(); + if (numPreviousCorrections > 0 && numPreviousCorrections == corrections.Count) + { + this.ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException(), + "FORMATTER_ERROR", + ErrorCategory.InvalidOperation, + corrections)); + } + + numPreviousCorrections = corrections.Count; // get unique correction instances // sort them by line numbers @@ -196,7 +207,7 @@ protected override void ProcessRecord() (x.StartLineNumber == x.StartLineNumber ? 0 : -1); }); - } while (records.Any()); + } while (numPreviousCorrections > 0); } this.WriteObject(text.ToString()); From 0e933488da384f51bcde77d5180cca4d4a38dbe4 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Tue, 23 May 2017 18:33:14 -0700 Subject: [PATCH 07/38] Add test to verify formatter implementation --- Tests/Engine/InvokeFormatter.tests.ps1 | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Tests/Engine/InvokeFormatter.tests.ps1 diff --git a/Tests/Engine/InvokeFormatter.tests.ps1 b/Tests/Engine/InvokeFormatter.tests.ps1 new file mode 100644 index 000000000..1e2ca8bd1 --- /dev/null +++ b/Tests/Engine/InvokeFormatter.tests.ps1 @@ -0,0 +1,36 @@ +$directory = Split-Path -Parent $MyInvocation.MyCommand.Path +$testRootDirectory = Split-Path -Parent $directory + +Import-Module PSScriptAnalyzer +Import-Module (Join-Path $testRootDirectory "PSScriptAnalyzerTestHelper.psm1") + +Describe "Invoke-Formatter Cmdlet" { + Context "When no settings are given" { + It "Should format using default settings" { + $def = @' +function foo +{ +get-childitem +$x=1+2 +$hashtable = @{ +property1 = "value" + anotherProperty = "another value" +} +} +'@ + $expected = @' +function foo { + get-childitem + $x = 1 + 2 + $hashtable = @{ + property1 = "value" + anotherProperty = "another value" + } +} +'@ + + Invoke-Formatter -ScriptDefinition $def | Should Be $expected + } + } + +} From 71dfcd0ec893f8067eaf9ecedd4f81a0686fe96d Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 24 May 2017 13:56:21 -0700 Subject: [PATCH 08/38] Populate CodeFormatting settings with default values --- Engine/Settings/CodeFormatting.psd1 | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Engine/Settings/CodeFormatting.psd1 b/Engine/Settings/CodeFormatting.psd1 index ba5d3a1aa..c6c94129c 100644 --- a/Engine/Settings/CodeFormatting.psd1 +++ b/Engine/Settings/CodeFormatting.psd1 @@ -7,34 +7,37 @@ 'PSAlignAssignmentStatement' ) - Rules = @{ - PSPlaceOpenBrace = @{ - Enable = $true - OnSameLine = $true - NewLineAfter = $true + Rules = @{ + PSPlaceOpenBrace = @{ + Enable = $true + OnSameLine = $true + NewLineAfter = $true + IgnoreOneLineBlock = $true } - PSPlaceCloseBrace = @{ - Enable = $true + PSPlaceCloseBrace = @{ + Enable = $true + NewLineAfter = $true + IgnoreOneLineBlock = $true + NoEmptyLineBefore = $false } PSUseConsistentIndentation = @{ - Enable = $true + Enable = $true IndentationSize = 4 } - PSUseConsistentWhitespace = @{ - Enable = $true + PSUseConsistentWhitespace = @{ + Enable = $true CheckOpenBrace = $true CheckOpenParen = $true - CheckOperator = $true + CheckOperator = $true CheckSeparator = $true } PSAlignAssignmentStatement = @{ - Enable = $true + Enable = $true CheckHashtable = $true - CheckDSCConfiguration = $true } } } From d11b6264ba1abd898c38f0c982310ff33558f56d Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Thu, 25 May 2017 13:51:52 -0700 Subject: [PATCH 09/38] Move settings creation to settings file --- Engine/Commands/InvokeFormatterCommand.cs | 64 ++++------------------- Engine/Settings.cs | 52 ++++++++++++++++++ 2 files changed, 62 insertions(+), 54 deletions(-) diff --git a/Engine/Commands/InvokeFormatterCommand.cs b/Engine/Commands/InvokeFormatterCommand.cs index 5183f941a..25ec677bf 100644 --- a/Engine/Commands/InvokeFormatterCommand.cs +++ b/Engine/Commands/InvokeFormatterCommand.cs @@ -66,67 +66,21 @@ protected override void BeginProcessing() } #endif + // var settings = PSSASettings.Create(Settings); + // formatter = new Formatter(settings); + // todo move to a common initalize session method Helper.Instance = new Helper(SessionState.InvokeCommand, this); Helper.Instance.Initialize(); - object settingsFound; - var settingsMode = PowerShell.ScriptAnalyzer.Settings.FindSettingsMode( - Settings, - null, - out settingsFound); - - switch (settingsMode) - { - case SettingsMode.Auto: - this.WriteVerbose( - String.Format( - CultureInfo.CurrentCulture, - Strings.SettingsNotProvided, - "")); - this.WriteVerbose( - String.Format( - CultureInfo.CurrentCulture, - Strings.SettingsAutoDiscovered, - (string)settingsFound)); - break; - - case SettingsMode.Preset: - case SettingsMode.File: - this.WriteVerbose( - String.Format( - CultureInfo.CurrentCulture, - Strings.SettingsUsingFile, - (string)settingsFound)); - break; - - case SettingsMode.Hashtable: - this.WriteVerbose( - String.Format( - CultureInfo.CurrentCulture, - Strings.SettingsUsingHashtable)); - break; - - default: // case SettingsMode.None - this.WriteVerbose( - String.Format( - CultureInfo.CurrentCulture, - Strings.SettingsCannotFindFile)); - break; - } try { - defaultSettings = new PSSASettings( - defaultSettingsPreset, - PSSASettings.GetSettingPresetFilePath); - if (settingsMode != SettingsMode.None) + inputSettings = PSSASettings.Create(Settings, null, this); + if (inputSettings == null) { - inputSettings = new PSSASettings(settingsFound); - ValidateInputSettings(); - } - else - { - inputSettings = defaultSettings; + inputSettings = new PSSASettings( + defaultSettingsPreset, + PSSASettings.GetSettingPresetFilePath); } } catch @@ -139,6 +93,8 @@ protected override void BeginProcessing() protected override void ProcessRecord() { + // this.WriteObject(formatter.Format(ScriptDefinition)); + var ruleOrder = new string[] { "PSPlaceCloseBrace", diff --git a/Engine/Settings.cs b/Engine/Settings.cs index 539a97fda..3be3155ff 100644 --- a/Engine/Settings.cs +++ b/Engine/Settings.cs @@ -159,6 +159,58 @@ public static string GetSettingPresetFilePath(string settingPreset) return null; } + public static Settings Create(object settingsObj, string cwd, IOutputWriter outputWriter) + { + object settingsFound; + var settingsMode = FindSettingsMode(settingsObj, cwd, out settingsFound); + + switch (settingsMode) + { + case SettingsMode.Auto: + outputWriter?.WriteVerbose( + String.Format( + CultureInfo.CurrentCulture, + Strings.SettingsNotProvided, + "")); + outputWriter?.WriteVerbose( + String.Format( + CultureInfo.CurrentCulture, + Strings.SettingsAutoDiscovered, + (string)settingsFound)); + break; + + case SettingsMode.Preset: + case SettingsMode.File: + outputWriter?.WriteVerbose( + String.Format( + CultureInfo.CurrentCulture, + Strings.SettingsUsingFile, + (string)settingsFound)); + break; + + case SettingsMode.Hashtable: + outputWriter?.WriteVerbose( + String.Format( + CultureInfo.CurrentCulture, + Strings.SettingsUsingHashtable)); + break; + + default: // case SettingsMode.None + outputWriter?.WriteVerbose( + String.Format( + CultureInfo.CurrentCulture, + Strings.SettingsCannotFindFile)); + break; + } + + if (settingsMode != SettingsMode.None) + { + return new Settings(settingsFound); + } + + return null; + } + /// /// Recursively convert hashtable to dictionary /// From 45e7f9304e79daf3382ae5ae6da6b2047918a7f6 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Thu, 25 May 2017 13:59:59 -0700 Subject: [PATCH 10/38] Make InvokeSA command to use common settings logic --- .../Commands/InvokeScriptAnalyzerCommand.cs | 73 +++++-------------- 1 file changed, 18 insertions(+), 55 deletions(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index c52776a3a..c230c1071 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -30,12 +30,14 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands { + using PSSASettings = Microsoft.Windows.PowerShell.ScriptAnalyzer.Settings; + /// /// InvokeScriptAnalyzerCommand: Cmdlet to statically check PowerShell scripts. /// [Cmdlet(VerbsLifecycle.Invoke, "ScriptAnalyzer", - DefaultParameterSetName="File", + DefaultParameterSetName = "File", HelpUri = "http://go.microsoft.com/fwlink/?LinkId=525914")] public class InvokeScriptAnalyzerCommand : PSCmdlet, IOutputWriter { @@ -208,6 +210,9 @@ public SwitchParameter SaveDscDependency #endif // !PSV3 #if DEBUG + /// + /// Attaches to an instance of a .Net debugger + /// [Parameter(Mandatory = false)] public SwitchParameter AttachAndDebug { @@ -260,64 +265,22 @@ protected override void BeginProcessing() ProcessPath(); } - object settingsFound; - var settingsMode = PowerShell.ScriptAnalyzer.Settings.FindSettingsMode( - this.settings, - processedPaths == null || processedPaths.Count == 0 ? null : processedPaths[0], - out settingsFound); - - switch (settingsMode) - { - case SettingsMode.Auto: - this.WriteVerbose( - String.Format( - CultureInfo.CurrentCulture, - Strings.SettingsNotProvided, - path)); - this.WriteVerbose( - String.Format( - CultureInfo.CurrentCulture, - Strings.SettingsAutoDiscovered, - (string)settingsFound)); - break; - - case SettingsMode.Preset: - case SettingsMode.File: - this.WriteVerbose( - String.Format( - CultureInfo.CurrentCulture, - Strings.SettingsUsingFile, - (string)settingsFound)); - break; - - case SettingsMode.Hashtable: - this.WriteVerbose( - String.Format( - CultureInfo.CurrentCulture, - Strings.SettingsUsingHashtable)); - break; - - default: // case SettingsMode.None - this.WriteVerbose( - String.Format( - CultureInfo.CurrentCulture, - Strings.SettingsCannotFindFile)); - break; - } - - if (settingsMode != SettingsMode.None) + try { - try + var settingsObj = PSSASettings.Create( + settings, + processedPaths == null || processedPaths.Count == 0 ? null : processedPaths[0], + this); + if (settingsObj != null) { - var settingsObj = new Settings(settingsFound); ScriptAnalyzer.Instance.UpdateSettings(settingsObj); } - catch - { - this.WriteWarning(String.Format(CultureInfo.CurrentCulture, Strings.SettingsNotParsable)); - stopProcessing = true; - return; - } + } + catch + { + this.WriteWarning(String.Format(CultureInfo.CurrentCulture, Strings.SettingsNotParsable)); + stopProcessing = true; + return; } ScriptAnalyzer.Instance.Initialize( From 71a54ffcd7f390623491ca19d3921811a7074984 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Fri, 26 May 2017 19:13:44 -0700 Subject: [PATCH 11/38] Add Formatter class to provide code formatting capability --- Engine/Formatter.cs | 106 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 Engine/Formatter.cs diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs new file mode 100644 index 000000000..2afd6d134 --- /dev/null +++ b/Engine/Formatter.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; + +namespace Microsoft.Windows.PowerShell.ScriptAnalyzer +{ + public class Formatter + { + private Settings settings; + + private Formatter(Settings settings) + { + this.settings = settings; + } + + public static string Format(string scriptDefinition, Settings settings) + { + throw new NotImplementedException(); + } + + public static string Format( + string scriptDefinition, + Hashtable settingsHashtable, + Runspace runspace, + IOutputWriter outputWriter) + { + var inputSettings = new Settings(settingsHashtable); + var ruleOrder = new string[] + { + "PSPlaceCloseBrace", + "PSPlaceOpenBrace", + "PSUseConsistentWhitespace", + "PSUseConsistentIndentation", + "PSAlignAssignmentStatement" + }; + + var text = new EditableText(scriptDefinition); + foreach (var rule in ruleOrder) + { + if (!inputSettings.RuleArguments.ContainsKey(rule)) + { + continue; + } + + outputWriter.WriteVerbose("Running " + rule); + var currentSettingsHashtable = new Hashtable(); + currentSettingsHashtable.Add("IncludeRules", new string[] { rule }); + var ruleSettings = new Hashtable(); + ruleSettings.Add(rule, new Hashtable(inputSettings.RuleArguments[rule])); + currentSettingsHashtable.Add("Rules", ruleSettings); + var currentSettings = new Settings(currentSettingsHashtable); + ScriptAnalyzer.Instance.UpdateSettings(inputSettings); + ScriptAnalyzer.Instance.Initialize(runspace, outputWriter); + + var corrections = new List(); + var records = Enumerable.Empty(); + var numPreviousCorrections = corrections.Count; + + do + { + var correctionApplied = new HashSet(); + foreach (var correction in corrections) + { + // apply only one edit per line + if (correctionApplied.Contains(correction.StartLineNumber)) + { + continue; + } + + correctionApplied.Add(correction.StartLineNumber); + text.ApplyEdit(correction); + } + + records = ScriptAnalyzer.Instance.AnalyzeScriptDefinition(text.ToString()); + corrections = records.Select(r => r.SuggestedCorrections.ElementAt(0)).ToList(); + if (numPreviousCorrections > 0 && numPreviousCorrections == corrections.Count) + { + outputWriter.ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException(), + "FORMATTER_ERROR", + ErrorCategory.InvalidOperation, + corrections)); + } + + numPreviousCorrections = corrections.Count; + + // get unique correction instances + // sort them by line numbers + corrections.Sort((x, y) => + { + return x.StartLineNumber < x.StartLineNumber ? + 1 : + (x.StartLineNumber == x.StartLineNumber ? 0 : -1); + }); + + } while (numPreviousCorrections > 0); + } + + return text.ToString(); + } + } +} From d924831563bf845dae2c5189237c2c2c93835984 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 31 May 2017 17:56:02 -0700 Subject: [PATCH 12/38] Parameterize format method --- Engine/Formatter.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index 2afd6d134..32e9dec4d 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -22,11 +22,10 @@ public static string Format(string scriptDefinition, Settings settings) throw new NotImplementedException(); } - public static string Format( + public static string Format( string scriptDefinition, - Hashtable settingsHashtable, - Runspace runspace, - IOutputWriter outputWriter) + Settings inputSettings, + TCmdlet cmdlet) where TCmdlet : PSCmdlet, IOutputWriter { var inputSettings = new Settings(settingsHashtable); var ruleOrder = new string[] @@ -46,7 +45,7 @@ public static string Format( continue; } - outputWriter.WriteVerbose("Running " + rule); + cmdlet.WriteVerbose("Running " + rule); var currentSettingsHashtable = new Hashtable(); currentSettingsHashtable.Add("IncludeRules", new string[] { rule }); var ruleSettings = new Hashtable(); @@ -54,7 +53,7 @@ public static string Format( currentSettingsHashtable.Add("Rules", ruleSettings); var currentSettings = new Settings(currentSettingsHashtable); ScriptAnalyzer.Instance.UpdateSettings(inputSettings); - ScriptAnalyzer.Instance.Initialize(runspace, outputWriter); + ScriptAnalyzer.Instance.Initialize(cmdlet, null, null, null, null, true, false); var corrections = new List(); var records = Enumerable.Empty(); @@ -79,7 +78,7 @@ public static string Format( corrections = records.Select(r => r.SuggestedCorrections.ElementAt(0)).ToList(); if (numPreviousCorrections > 0 && numPreviousCorrections == corrections.Count) { - outputWriter.ThrowTerminatingError(new ErrorRecord( + cmdlet.ThrowTerminatingError(new ErrorRecord( new InvalidOperationException(), "FORMATTER_ERROR", ErrorCategory.InvalidOperation, From 2b92851178b4d2af355c7d6a23f6c8c70d767727 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 31 May 2017 17:58:23 -0700 Subject: [PATCH 13/38] Remove unused members from formatter class --- Engine/Formatter.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index 32e9dec4d..97926c088 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -10,24 +10,11 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer { public class Formatter { - private Settings settings; - - private Formatter(Settings settings) - { - this.settings = settings; - } - - public static string Format(string scriptDefinition, Settings settings) - { - throw new NotImplementedException(); - } - public static string Format( string scriptDefinition, Settings inputSettings, TCmdlet cmdlet) where TCmdlet : PSCmdlet, IOutputWriter { - var inputSettings = new Settings(settingsHashtable); var ruleOrder = new string[] { "PSPlaceCloseBrace", From 87aac09c8be809fdfb869cc0689d524d10f18a2d Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 31 May 2017 18:22:27 -0700 Subject: [PATCH 14/38] Add Formatter.cs file to engine csproj --- Engine/ScriptAnalyzerEngine.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Engine/ScriptAnalyzerEngine.csproj b/Engine/ScriptAnalyzerEngine.csproj index cfe5d7c1a..375bcdf5e 100644 --- a/Engine/ScriptAnalyzerEngine.csproj +++ b/Engine/ScriptAnalyzerEngine.csproj @@ -71,6 +71,7 @@ + From f0b5fea2f4624a7c98864012aa1360782b9d83e3 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 31 May 2017 18:22:54 -0700 Subject: [PATCH 15/38] Fix updating settings in invoke-formatter --- Engine/Formatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index 97926c088..781bed83a 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -39,7 +39,7 @@ public static string Format( ruleSettings.Add(rule, new Hashtable(inputSettings.RuleArguments[rule])); currentSettingsHashtable.Add("Rules", ruleSettings); var currentSettings = new Settings(currentSettingsHashtable); - ScriptAnalyzer.Instance.UpdateSettings(inputSettings); + ScriptAnalyzer.Instance.UpdateSettings(currentSettings); ScriptAnalyzer.Instance.Initialize(cmdlet, null, null, null, null, true, false); var corrections = new List(); From fac76329d7ae9f86ada10dcb2280e736e9894cf0 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 31 May 2017 18:24:23 -0700 Subject: [PATCH 16/38] Move formatting to its own class --- Engine/Commands/InvokeFormatterCommand.cs | 81 +---------------------- 1 file changed, 2 insertions(+), 79 deletions(-) diff --git a/Engine/Commands/InvokeFormatterCommand.cs b/Engine/Commands/InvokeFormatterCommand.cs index 25ec677bf..24ed7fc7f 100644 --- a/Engine/Commands/InvokeFormatterCommand.cs +++ b/Engine/Commands/InvokeFormatterCommand.cs @@ -66,10 +66,6 @@ protected override void BeginProcessing() } #endif - // var settings = PSSASettings.Create(Settings); - // formatter = new Formatter(settings); - - // todo move to a common initalize session method Helper.Instance = new Helper(SessionState.InvokeCommand, this); Helper.Instance.Initialize(); @@ -90,83 +86,10 @@ protected override void BeginProcessing() } } - protected override void ProcessRecord() { - // this.WriteObject(formatter.Format(ScriptDefinition)); - - var ruleOrder = new string[] - { - "PSPlaceCloseBrace", - "PSPlaceOpenBrace", - "PSUseConsistentWhitespace", - "PSUseConsistentIndentation", - "PSAlignAssignmentStatement" - }; - - var text = new EditableText(ScriptDefinition); - foreach (var rule in ruleOrder) - { - if (!inputSettings.RuleArguments.ContainsKey(rule)) - { - continue; - } - - this.WriteVerbose("Running " + rule); - var currentSettingsHashtable = new Hashtable(); - currentSettingsHashtable.Add("IncludeRules", new string[] { rule }); - var ruleSettings = new Hashtable(); - ruleSettings.Add(rule, new Hashtable(inputSettings.RuleArguments[rule])); - currentSettingsHashtable.Add("Rules", ruleSettings); - var currentSettings = new Settings(currentSettingsHashtable); - ScriptAnalyzer.Instance.UpdateSettings(currentSettings); - ScriptAnalyzer.Instance.Initialize(this, null, null, null, null, true, false); - - var corrections = new List(); - var records = Enumerable.Empty(); - var numPreviousCorrections = corrections.Count; - - do - { - var correctionApplied = new HashSet(); - foreach (var correction in corrections) - { - // apply only one edit per line - if (correctionApplied.Contains(correction.StartLineNumber)) - { - continue; - } - - correctionApplied.Add(correction.StartLineNumber); - text.ApplyEdit(correction); - } - - records = ScriptAnalyzer.Instance.AnalyzeScriptDefinition(text.ToString()); - corrections = records.Select(r => r.SuggestedCorrections.ElementAt(0)).ToList(); - if (numPreviousCorrections > 0 && numPreviousCorrections == corrections.Count) - { - this.ThrowTerminatingError(new ErrorRecord( - new InvalidOperationException(), - "FORMATTER_ERROR", - ErrorCategory.InvalidOperation, - corrections)); - } - - numPreviousCorrections = corrections.Count; - - // get unique correction instances - // sort them by line numbers - corrections.Sort((x, y) => - { - return x.StartLineNumber < x.StartLineNumber ? - 1 : - (x.StartLineNumber == x.StartLineNumber ? 0 : -1); - }); - - } while (numPreviousCorrections > 0); - } - - this.WriteObject(text.ToString()); + var text = Formatter.Format(ScriptDefinition, inputSettings, this); + this.WriteObject(text); } private void ValidateInputSettings() From 7fa659deecfdc9c1a0ec0e9ce8ab72e597eac463 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 31 May 2017 18:33:19 -0700 Subject: [PATCH 17/38] Move helper initialization to format method --- Engine/Commands/InvokeFormatterCommand.cs | 3 --- Engine/Formatter.cs | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Engine/Commands/InvokeFormatterCommand.cs b/Engine/Commands/InvokeFormatterCommand.cs index 24ed7fc7f..cd6b99fed 100644 --- a/Engine/Commands/InvokeFormatterCommand.cs +++ b/Engine/Commands/InvokeFormatterCommand.cs @@ -66,9 +66,6 @@ protected override void BeginProcessing() } #endif - Helper.Instance = new Helper(SessionState.InvokeCommand, this); - Helper.Instance.Initialize(); - try { inputSettings = PSSASettings.Create(Settings, null, this); diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index 781bed83a..e7ac55281 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -15,6 +15,9 @@ public static string Format( Settings inputSettings, TCmdlet cmdlet) where TCmdlet : PSCmdlet, IOutputWriter { + Helper.Instance = new Helper(cmdlet.SessionState.InvokeCommand, cmdlet); + Helper.Instance.Initialize(); + var ruleOrder = new string[] { "PSPlaceCloseBrace", From 3057c036f76b4a16ac70a3e0e6a527109703030a Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 31 May 2017 18:35:47 -0700 Subject: [PATCH 18/38] Create a method to get current settings --- Engine/Formatter.cs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index e7ac55281..6af9da262 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -10,9 +10,10 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer { public class Formatter { + // TODO add a method that takes range parameter public static string Format( string scriptDefinition, - Settings inputSettings, + Settings settings, TCmdlet cmdlet) where TCmdlet : PSCmdlet, IOutputWriter { Helper.Instance = new Helper(cmdlet.SessionState.InvokeCommand, cmdlet); @@ -30,18 +31,14 @@ public static string Format( var text = new EditableText(scriptDefinition); foreach (var rule in ruleOrder) { - if (!inputSettings.RuleArguments.ContainsKey(rule)) + if (!settings.RuleArguments.ContainsKey(rule)) { continue; } cmdlet.WriteVerbose("Running " + rule); - var currentSettingsHashtable = new Hashtable(); - currentSettingsHashtable.Add("IncludeRules", new string[] { rule }); - var ruleSettings = new Hashtable(); - ruleSettings.Add(rule, new Hashtable(inputSettings.RuleArguments[rule])); - currentSettingsHashtable.Add("Rules", ruleSettings); - var currentSettings = new Settings(currentSettingsHashtable); + + var currentSettings = GetCurrentSettings(settings, rule); ScriptAnalyzer.Instance.UpdateSettings(currentSettings); ScriptAnalyzer.Instance.Initialize(cmdlet, null, null, null, null, true, false); @@ -51,6 +48,7 @@ public static string Format( do { + // TODO create better verbose messages var correctionApplied = new HashSet(); foreach (var correction in corrections) { @@ -91,5 +89,18 @@ public static string Format( return text.ToString(); } + + private static Settings GetCurrentSettings(Settings settings, string rule) + { + var currentSettingsHashtable = new Hashtable(); + currentSettingsHashtable.Add("IncludeRules", new string[] { rule }); + + var ruleSettings = new Hashtable(); + ruleSettings.Add(rule, new Hashtable(settings.RuleArguments[rule])); + currentSettingsHashtable.Add("Rules", ruleSettings); + + var currentSettings = new Settings(currentSettingsHashtable); + return currentSettings; + } } } From 73aefd82f913ef00fe617aee5c9e8e8944a19cb5 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Tue, 6 Jun 2017 14:09:26 -0700 Subject: [PATCH 19/38] Move the fixing in formatting to ScriptAnalyzer class --- Engine/Formatter.cs | 46 +-------------------- Engine/ScriptAnalyzer.cs | 88 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 45 deletions(-) diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index 6af9da262..57748f121 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -37,54 +37,10 @@ public static string Format( } cmdlet.WriteVerbose("Running " + rule); - var currentSettings = GetCurrentSettings(settings, rule); ScriptAnalyzer.Instance.UpdateSettings(currentSettings); ScriptAnalyzer.Instance.Initialize(cmdlet, null, null, null, null, true, false); - - var corrections = new List(); - var records = Enumerable.Empty(); - var numPreviousCorrections = corrections.Count; - - do - { - // TODO create better verbose messages - var correctionApplied = new HashSet(); - foreach (var correction in corrections) - { - // apply only one edit per line - if (correctionApplied.Contains(correction.StartLineNumber)) - { - continue; - } - - correctionApplied.Add(correction.StartLineNumber); - text.ApplyEdit(correction); - } - - records = ScriptAnalyzer.Instance.AnalyzeScriptDefinition(text.ToString()); - corrections = records.Select(r => r.SuggestedCorrections.ElementAt(0)).ToList(); - if (numPreviousCorrections > 0 && numPreviousCorrections == corrections.Count) - { - cmdlet.ThrowTerminatingError(new ErrorRecord( - new InvalidOperationException(), - "FORMATTER_ERROR", - ErrorCategory.InvalidOperation, - corrections)); - } - - numPreviousCorrections = corrections.Count; - - // get unique correction instances - // sort them by line numbers - corrections.Sort((x, y) => - { - return x.StartLineNumber < x.StartLineNumber ? - 1 : - (x.StartLineNumber == x.StartLineNumber ? 0 : -1); - }); - - } while (numPreviousCorrections > 0); + text = ScriptAnalyzer.Instance.Fix(text); } return text.ToString(); diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 61fdd6ec7..24cc9e150 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -1527,6 +1527,94 @@ public IEnumerable AnalyzeScriptDefinition(string scriptDefini return this.AnalyzeSyntaxTree(scriptAst, scriptTokens, String.Empty); } + /// + /// Fix the violations in the given script text. + /// + /// The script text to be fixed. + /// The fixed script text. + public string Fix(string scriptDefinition) + { + if (scriptDefinition == null) + { + throw new ArgumentNullException(nameof(scriptDefinition)); + } + + return Fix(new EditableText(scriptDefinition)).ToString(); + } + + /// + /// Fix the violations in the given script text. + /// + /// An object of type `EditableText` that encapsulates the script text to be fixed. + /// The same instance of `EditableText` that was passed to the method, but the instance encapsulates the fixed script text. This helps in chaining the Fix method. + public EditableText Fix(EditableText text) + { + if (text == null) + { + throw new ArgumentNullException(nameof(text)); + } + + var previousUnusedCorrections = 0; + do + { + var records = AnalyzeScriptDefinition(text.ToString()); + var corrections = records + .Select(r => r.SuggestedCorrections) + .Where(sc => sc != null && sc.Any()) + .Select(sc => sc.First()) + .ToList(); + + int unusedCorrections; + Fix(text, corrections, out unusedCorrections); + + // This is an indication of an infinite loop. There is a small chance of this. + // It is better to abort the fixing operation at this point. + if (previousUnusedCorrections > 0 && previousUnusedCorrections == unusedCorrections) + { + this.outputWriter.ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException(), + "FIX_ERROR", + ErrorCategory.InvalidOperation, + corrections)); + } + + previousUnusedCorrections = unusedCorrections; + } while (previousUnusedCorrections > 0); + + return text; + } + + private static IEnumerable GetCorrectionExtentsForFix( + IEnumerable correctionExtents) + { + var ceList = correctionExtents.ToList(); + ceList.Sort((x, y) => + { + return x.StartLineNumber < x.StartLineNumber ? + 1 : + (x.StartLineNumber == x.StartLineNumber ? 0 : -1); + }); + + return ceList.GroupBy(ce => ce.StartLineNumber).Select(g => g.First()); + } + + private static EditableText Fix( + EditableText text, + IEnumerable correctionExtents, + out int unusedCorrections) + { + var correctionsToUse = GetCorrectionExtentsForFix(correctionExtents); + var count = 0; + foreach (var correction in correctionsToUse) + { + count++; + text.ApplyEdit(correction); + } + + unusedCorrections = correctionExtents.Count() - count; + return text; + } + private void BuildScriptPathList( string path, bool searchRecursively, From 4ae32a3e48936b9fae89192054202fb84d14ba58 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Tue, 6 Jun 2017 19:07:59 -0700 Subject: [PATCH 20/38] Fix correction extent sorting logic --- Engine/ScriptAnalyzer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 24cc9e150..962309894 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -1590,9 +1590,9 @@ private static IEnumerable GetCorrectionExtentsForFix( var ceList = correctionExtents.ToList(); ceList.Sort((x, y) => { - return x.StartLineNumber < x.StartLineNumber ? + return x.StartLineNumber < y.StartLineNumber ? 1 : - (x.StartLineNumber == x.StartLineNumber ? 0 : -1); + (x.StartLineNumber == y.StartLineNumber ? 0 : -1); }); return ceList.GroupBy(ce => ce.StartLineNumber).Select(g => g.First()); From 4ae9a9008fe4e83061e9b778a72eaa76b993f3c2 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Tue, 6 Jun 2017 23:54:44 -0700 Subject: [PATCH 21/38] Add range formatting capability --- Engine/Commands/InvokeFormatterCommand.cs | 20 +++++++++++++- Engine/Formatter.cs | 3 ++- Engine/ScriptAnalyzer.cs | 32 +++++++++++++++++++++-- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/Engine/Commands/InvokeFormatterCommand.cs b/Engine/Commands/InvokeFormatterCommand.cs index cd6b99fed..083436069 100644 --- a/Engine/Commands/InvokeFormatterCommand.cs +++ b/Engine/Commands/InvokeFormatterCommand.cs @@ -37,6 +37,18 @@ public class InvokeFormatterCommand : PSCmdlet, IOutputWriter [ValidateNotNull] public object Settings { get; set; } + // [Parameter(Mandatory = false)] + // public Range range { get; set; } + + [Parameter(Mandatory = false)] + public int StartLineNumber { get; set; } = -1; + [Parameter(Mandatory = false)] + public int StartColumnNumber { get; private set; } = -1; + [Parameter(Mandatory = false)] + public int EndLineNumber { get; private set; } = -1; + [Parameter(Mandatory = false)] + public int EndColumnNumber { get; private set; } = -1; + #if DEBUG /// /// Attaches to an instance of a .Net debugger @@ -47,6 +59,7 @@ public SwitchParameter AttachAndDebug get { return attachAndDebug; } set { attachAndDebug = value; } } + private bool attachAndDebug = false; #endif @@ -85,7 +98,12 @@ protected override void BeginProcessing() protected override void ProcessRecord() { - var text = Formatter.Format(ScriptDefinition, inputSettings, this); + // todo add range parameter + // todo add tests to check range formatting + var range = StartLineNumber == -1 ? + null : + new Range(StartLineNumber, StartColumnNumber, EndLineNumber, EndColumnNumber); + var text = Formatter.Format(ScriptDefinition, inputSettings, range, this); this.WriteObject(text); } diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index 57748f121..018abe9c7 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -14,6 +14,7 @@ public class Formatter public static string Format( string scriptDefinition, Settings settings, + Range range, TCmdlet cmdlet) where TCmdlet : PSCmdlet, IOutputWriter { Helper.Instance = new Helper(cmdlet.SessionState.InvokeCommand, cmdlet); @@ -40,7 +41,7 @@ public static string Format( var currentSettings = GetCurrentSettings(settings, rule); ScriptAnalyzer.Instance.UpdateSettings(currentSettings); ScriptAnalyzer.Instance.Initialize(cmdlet, null, null, null, null, true, false); - text = ScriptAnalyzer.Instance.Fix(text); + text = ScriptAnalyzer.Instance.Fix(text, range); } return text.ToString(); diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 962309894..aa9e7dc26 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -1539,7 +1539,7 @@ public string Fix(string scriptDefinition) throw new ArgumentNullException(nameof(scriptDefinition)); } - return Fix(new EditableText(scriptDefinition)).ToString(); + return Fix(new EditableText(scriptDefinition), null).ToString(); } /// @@ -1547,13 +1547,17 @@ public string Fix(string scriptDefinition) /// /// An object of type `EditableText` that encapsulates the script text to be fixed. /// The same instance of `EditableText` that was passed to the method, but the instance encapsulates the fixed script text. This helps in chaining the Fix method. - public EditableText Fix(EditableText text) + public EditableText Fix(EditableText text, Range range) { if (text == null) { throw new ArgumentNullException(nameof(text)); } + // todo validate range + var isRangeNull = range == null; + range = isRangeNull ? null : SnapToEdges(text, range); + var previousLineCount = text.Lines.Length; var previousUnusedCorrections = 0; do { @@ -1562,6 +1566,7 @@ public EditableText Fix(EditableText text) .Select(r => r.SuggestedCorrections) .Where(sc => sc != null && sc.Any()) .Select(sc => sc.First()) + .Where(sc => isRangeNull || (sc.Start >= range.Start && sc.End <= range.End)) .ToList(); int unusedCorrections; @@ -1579,11 +1584,34 @@ public EditableText Fix(EditableText text) } previousUnusedCorrections = unusedCorrections; + + // todo add a TextLines.NumLines property because accessing TextLines.Lines is expensive + var lineCount = text.Lines.Length; + if (!isRangeNull && lineCount != previousLineCount) + { + range = new Range( + range.Start, + range.End.Shift(lineCount - previousLineCount, 0)); + range = SnapToEdges(text, range); + } + + previousLineCount = lineCount; } while (previousUnusedCorrections > 0); return text; } + private static Range SnapToEdges(EditableText text, Range range) + { + // todo add TextLines.Validate(range) and TextLines.Validate(position) + // todo TextLines.Lines should return IList instead of array because TextLines.Lines is expensive + return new Range( + range.Start.Line, + Math.Min(range.Start.Column, 1), + range.End.Line, + Math.Max(range.End.Column, text.Lines[range.End.Line - 1].Length)); + } + private static IEnumerable GetCorrectionExtentsForFix( IEnumerable correctionExtents) { From dd381e6e0c36b4cf9ac3bedc2190b64ef5808b82 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 7 Jun 2017 12:33:15 -0700 Subject: [PATCH 22/38] Add an out parameter to relay updated range Whenever PSSA makes a fix, it may change the number of lines in the script, thereby invalidating the original range. We need relay the updated range so that future calls to fix can use the updated range. --- Engine/Formatter.cs | 5 ++++- Engine/ScriptAnalyzer.cs | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index 018abe9c7..909042639 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -41,7 +41,10 @@ public static string Format( var currentSettings = GetCurrentSettings(settings, rule); ScriptAnalyzer.Instance.UpdateSettings(currentSettings); ScriptAnalyzer.Instance.Initialize(cmdlet, null, null, null, null, true, false); - text = ScriptAnalyzer.Instance.Fix(text, range); + + Range updatedRange; + text = ScriptAnalyzer.Instance.Fix(text, range, out updatedRange); + range = updatedRange; } return text.ToString(); diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index aa9e7dc26..9cb5f19ce 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -1539,15 +1539,17 @@ public string Fix(string scriptDefinition) throw new ArgumentNullException(nameof(scriptDefinition)); } - return Fix(new EditableText(scriptDefinition), null).ToString(); + Range updatedRange; + return Fix(new EditableText(scriptDefinition), null, out updatedRange).ToString(); } /// /// Fix the violations in the given script text. /// /// An object of type `EditableText` that encapsulates the script text to be fixed. + /// The updated range after the fixes have been applied. /// The same instance of `EditableText` that was passed to the method, but the instance encapsulates the fixed script text. This helps in chaining the Fix method. - public EditableText Fix(EditableText text, Range range) + public EditableText Fix(EditableText text, Range range, out Range updatedRange) { if (text == null) { @@ -1598,6 +1600,7 @@ public EditableText Fix(EditableText text, Range range) previousLineCount = lineCount; } while (previousUnusedCorrections > 0); + updatedRange = range; return text; } From db12b5ebfc072e8acb7d7e800ac439c283654116 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 7 Jun 2017 16:54:48 -0700 Subject: [PATCH 23/38] Add range parameter to InvokeFormatter cmdlet --- Engine/Commands/InvokeFormatterCommand.cs | 27 +++++++++++++---------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/Engine/Commands/InvokeFormatterCommand.cs b/Engine/Commands/InvokeFormatterCommand.cs index 083436069..e38d335cc 100644 --- a/Engine/Commands/InvokeFormatterCommand.cs +++ b/Engine/Commands/InvokeFormatterCommand.cs @@ -37,17 +37,17 @@ public class InvokeFormatterCommand : PSCmdlet, IOutputWriter [ValidateNotNull] public object Settings { get; set; } - // [Parameter(Mandatory = false)] - // public Range range { get; set; } - [Parameter(Mandatory = false)] + public Range Range { get; set; } + + [Parameter(Mandatory = false, ParameterSetName = "NoRange")] public int StartLineNumber { get; set; } = -1; - [Parameter(Mandatory = false)] - public int StartColumnNumber { get; private set; } = -1; - [Parameter(Mandatory = false)] - public int EndLineNumber { get; private set; } = -1; - [Parameter(Mandatory = false)] - public int EndColumnNumber { get; private set; } = -1; + [Parameter(Mandatory = false, ParameterSetName = "NoRange")] + public int StartColumnNumber { get; set; } = -1; + [Parameter(Mandatory = false, ParameterSetName = "NoRange")] + public int EndLineNumber { get; set; } = -1; + [Parameter(Mandatory = false, ParameterSetName = "NoRange")] + public int EndColumnNumber { get; set; } = -1; #if DEBUG /// @@ -100,9 +100,12 @@ protected override void ProcessRecord() { // todo add range parameter // todo add tests to check range formatting - var range = StartLineNumber == -1 ? - null : - new Range(StartLineNumber, StartColumnNumber, EndLineNumber, EndColumnNumber); + var range = Range; + if (this.ParameterSetName.Equals("NoRange")) + { + range = new Range(StartLineNumber, StartColumnNumber, EndLineNumber, EndColumnNumber); + } + var text = Formatter.Format(ScriptDefinition, inputSettings, range, this); this.WriteObject(text); } From 82a277fff9c08f7550f5b1d73e52ef8d6a7c9881 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 7 Jun 2017 16:58:21 -0700 Subject: [PATCH 24/38] Update Formatter.GetCurrentSettings implementation --- Engine/Formatter.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index 909042639..e57af7d6d 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -10,13 +10,13 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer { public class Formatter { - // TODO add a method that takes range parameter public static string Format( string scriptDefinition, Settings settings, Range range, TCmdlet cmdlet) where TCmdlet : PSCmdlet, IOutputWriter { + // todo add argument check Helper.Instance = new Helper(cmdlet.SessionState.InvokeCommand, cmdlet); Helper.Instance.Initialize(); @@ -52,15 +52,11 @@ public static string Format( private static Settings GetCurrentSettings(Settings settings, string rule) { - var currentSettingsHashtable = new Hashtable(); - currentSettingsHashtable.Add("IncludeRules", new string[] { rule }); - - var ruleSettings = new Hashtable(); - ruleSettings.Add(rule, new Hashtable(settings.RuleArguments[rule])); - currentSettingsHashtable.Add("Rules", ruleSettings); - - var currentSettings = new Settings(currentSettingsHashtable); - return currentSettings; + return new Settings(new Hashtable() + { + {"IncludeRules", new string[] {rule}}, + {"Rules", new Hashtable() { { rule, new Hashtable(settings.RuleArguments[rule]) } } } + }); } } } From 32dde3066392292cb0903092a0c237735a44459d Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 7 Jun 2017 16:58:40 -0700 Subject: [PATCH 25/38] Remove unused usings from formatter.cs --- Engine/Formatter.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index e57af7d6d..4a13508a7 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -1,10 +1,5 @@ -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; using System.Management.Automation; -using System.Management.Automation.Runspaces; -using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer { From a4eb6f3fa002445e41a58924afd6e607a29d9ea3 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 7 Jun 2017 17:08:27 -0700 Subject: [PATCH 26/38] Remove unused usings from invokeformattercommand.cs --- Engine/Commands/InvokeFormatterCommand.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Engine/Commands/InvokeFormatterCommand.cs b/Engine/Commands/InvokeFormatterCommand.cs index e38d335cc..006f6f32c 100644 --- a/Engine/Commands/InvokeFormatterCommand.cs +++ b/Engine/Commands/InvokeFormatterCommand.cs @@ -11,12 +11,8 @@ // using System; -using System.Collections; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Management.Automation; -using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands { From 585fbdf63618b439f98cfea72e8bd01b9bb304a5 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Thu, 8 Jun 2017 14:11:20 -0700 Subject: [PATCH 27/38] Enable InvokeFormatter range only in debug build --- Engine/Commands/InvokeFormatterCommand.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Engine/Commands/InvokeFormatterCommand.cs b/Engine/Commands/InvokeFormatterCommand.cs index 006f6f32c..dea34766f 100644 --- a/Engine/Commands/InvokeFormatterCommand.cs +++ b/Engine/Commands/InvokeFormatterCommand.cs @@ -33,6 +33,7 @@ public class InvokeFormatterCommand : PSCmdlet, IOutputWriter [ValidateNotNull] public object Settings { get; set; } +#if DEBUG [Parameter(Mandatory = false)] public Range Range { get; set; } @@ -45,7 +46,6 @@ public class InvokeFormatterCommand : PSCmdlet, IOutputWriter [Parameter(Mandatory = false, ParameterSetName = "NoRange")] public int EndColumnNumber { get; set; } = -1; -#if DEBUG /// /// Attaches to an instance of a .Net debugger /// @@ -94,16 +94,20 @@ protected override void BeginProcessing() protected override void ProcessRecord() { - // todo add range parameter // todo add tests to check range formatting + string formattedScriptDefinition; +#if DEBUG var range = Range; if (this.ParameterSetName.Equals("NoRange")) { range = new Range(StartLineNumber, StartColumnNumber, EndLineNumber, EndColumnNumber); } - var text = Formatter.Format(ScriptDefinition, inputSettings, range, this); - this.WriteObject(text); + formattedScriptDefinition = Formatter.Format(ScriptDefinition, inputSettings, range, this); +#endif // DEBUG + + formattedScriptDefinition = Formatter.Format(ScriptDefinition, inputSettings, null, this); + this.WriteObject(formattedScriptDefinition); } private void ValidateInputSettings() From 30cd2b52f9e5b9839c5fcaee42490ead1bec2c5c Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Thu, 8 Jun 2017 15:31:50 -0700 Subject: [PATCH 28/38] Add external help markdown for invoke-formatter cmdlet --- docs/markdown/Invoke-Formatter.md | 99 +++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 docs/markdown/Invoke-Formatter.md diff --git a/docs/markdown/Invoke-Formatter.md b/docs/markdown/Invoke-Formatter.md new file mode 100644 index 000000000..cce30bd72 --- /dev/null +++ b/docs/markdown/Invoke-Formatter.md @@ -0,0 +1,99 @@ +--- +external help file: Microsoft.Windows.PowerShell.ScriptAnalyzer.dll-Help.xml +schema: 2.0.0 +--- + +# Invoke-Formatter + +## SYNOPSIS +Formats a script text based on the input settings or default settings. + +## SYNTAX + +``` +Invoke-Formatter [-ScriptDefinition] [-Settings ] +``` + +## DESCRIPTION + +The `Invoke-Formatter` cmdlet takes a string parameter named `ScriptDefinition` and formats it according to the input settings parameter `Settings`. If no `Settings` parameter is provided, the cmdlet assumes the default code formatting settings as defined in `Settings/CodeFormatting.psd`. + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- +``` +$scriptDefinition = @' +function foo { +"hello" + } +'@ + +Invoke-Formatter -ScriptDefinition $scriptDefinition +``` + +This command formats the input script text using the default settings. + +### -------------------------- EXAMPLE 2 -------------------------- +``` +$scriptDefinition = @' +function foo { +"hello" +} +'@ + +$settings = @{ + IncludeRules = @("PSPlaceOpenBrace", "PSUseConsistentIndentation") + Rules = @{ + PSPlaceOpenBrace = @{ + Enable = $true + OnSameLine = $false + } + PSUseConsistentIndentation = @{ + Enable = $true + } + } +} + +Invoke-Formatter -ScriptDefinition $scriptDefinition -Settings $settings +``` + +This command formats the input script text using the settings defined in the `$settings` hashtable. + +### -------------------------- EXAMPLE 3 -------------------------- +``` +S> Invoke-Formatter -ScriptDefinition $scriptDefinition -Settings /path/to/settings.psd1 + +This command formats the input script text using the settings defined in the `settings.psd1` file. + +## PARAMETERS + +### -ScriptDefinition +The script text to be formated. + +*NOTE*: Unlike ScriptBlock parameter, the ScriptDefinition parameter require a string value. + +```yaml +Type: String +Parameter Sets: +Aliases: + +Required: True +Position: Named +Default value: +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Settings +A settings hashtable or a path to a PowerShell data file (.psd1) file that contains the settings. + +```yaml +Type: Object +Parameter Sets: + +Required: False +Position: Named +Default value: +Accept pipeline input: False +Accept wildcard characters: False +``` From d6d3eef293d301f1f4f9ef51a7191827160ec2b8 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Thu, 8 Jun 2017 15:32:51 -0700 Subject: [PATCH 29/38] Remove online help link from markdown help and fix typo --- docs/markdown/Get-ScriptAnalyzerRule.md | 1 - docs/markdown/Invoke-ScriptAnalyzer.md | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/markdown/Get-ScriptAnalyzerRule.md b/docs/markdown/Get-ScriptAnalyzerRule.md index 3f75e04cb..ee3e04267 100644 --- a/docs/markdown/Get-ScriptAnalyzerRule.md +++ b/docs/markdown/Get-ScriptAnalyzerRule.md @@ -1,6 +1,5 @@ --- external help file: Microsoft.Windows.PowerShell.ScriptAnalyzer.dll-Help.xml -online version: http://go.microsoft.com/fwlink/?LinkId=525913 schema: 2.0.0 --- diff --git a/docs/markdown/Invoke-ScriptAnalyzer.md b/docs/markdown/Invoke-ScriptAnalyzer.md index 142367dbf..17ce0526c 100644 --- a/docs/markdown/Invoke-ScriptAnalyzer.md +++ b/docs/markdown/Invoke-ScriptAnalyzer.md @@ -1,6 +1,5 @@ --- external help file: Microsoft.Windows.PowerShell.ScriptAnalyzer.dll-Help.xml -online version: http://go.microsoft.com/fwlink/?LinkId=525914 schema: 2.0.0 --- @@ -35,7 +34,7 @@ Invoke-ScriptAnalyzer evaluates only .ps1 and .psm1 files. If you specify a path with multiple file types, the .ps1 and .psm1 files are tested; all other file types are ignored. -Invoke-ScriptAnalzyer comes with a set of built-in rules, but you can also use customized rules that you write in +Invoke-ScriptAnalyzer comes with a set of built-in rules, but you can also use customized rules that you write in Windows PowerShell scripts, or compile in assemblies by using C#. Just as with the built-in rules, you can add the ExcludeRule and IncludeRule parameters to your Invoke-ScriptAnalyzer command to exclude or include custom rules. From 06a5de2554c3126c52daa1b5810139ebfe6427da Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Thu, 8 Jun 2017 15:33:40 -0700 Subject: [PATCH 30/38] Add argument completer for invoke-formatter cmdlet --- Engine/PSScriptAnalyzer.psm1 | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Engine/PSScriptAnalyzer.psm1 b/Engine/PSScriptAnalyzer.psm1 index 03f4a44e6..78a1bbecf 100644 --- a/Engine/PSScriptAnalyzer.psm1 +++ b/Engine/PSScriptAnalyzer.psm1 @@ -14,8 +14,7 @@ $binaryModuleRoot = $PSModuleRoot if (($PSVersionTable.Keys -contains "PSEdition") -and ($PSVersionTable.PSEdition -ne 'Desktop')) { $binaryModuleRoot = Join-Path -Path $PSModuleRoot -ChildPath 'coreclr' } -else -{ +else { if ($PSVersionTable.PSVersion -lt [Version]'5.0') { $binaryModuleRoot = Join-Path -Path $PSModuleRoot -ChildPath 'PSv3' } @@ -29,9 +28,8 @@ $PSModule.OnRemove = { Remove-Module -ModuleInfo $binaryModule } -if (Get-Command Register-ArgumentCompleter -ErrorAction Ignore) -{ - Register-ArgumentCompleter -CommandName 'Invoke-ScriptAnalyzer' -ParameterName 'Settings' -ScriptBlock { +if (Get-Command Register-ArgumentCompleter -ErrorAction Ignore) { + $settingPresetCompleter = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParmeter) [Microsoft.Windows.PowerShell.ScriptAnalyzer.Settings]::GetSettingPresets() | ` @@ -39,8 +37,14 @@ if (Get-Command Register-ArgumentCompleter -ErrorAction Ignore) ForEach-Object { New-Object System.Management.Automation.CompletionResult $_ } } - Function RuleNameCompleter - { + @('Invoke-ScriptAnalyzer', 'Invoke-Formatter') | ForEach-Object { + Register-ArgumentCompleter -CommandName $_ ` + -ParameterName 'Settings' ` + -ScriptBlock $settingPresetCompleter + + } + + Function RuleNameCompleter { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParmeter) Get-ScriptAnalyzerRule *$wordToComplete* | ` @@ -50,4 +54,4 @@ if (Get-Command Register-ArgumentCompleter -ErrorAction Ignore) Register-ArgumentCompleter -CommandName 'Invoke-ScriptAnalyzer' -ParameterName 'IncludeRule' -ScriptBlock $Function:RuleNameCompleter Register-ArgumentCompleter -CommandName 'Invoke-ScriptAnalyzer' -ParameterName 'ExcludeRule' -ScriptBlock $Function:RuleNameCompleter Register-ArgumentCompleter -CommandName 'Get-ScriptAnalyzerRule' -ParameterName 'Name' -ScriptBlock $Function:RuleNameCompleter -} \ No newline at end of file +} From 2f8b6daa3eb90782f514c8eb2edff88fc459003b Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Thu, 8 Jun 2017 16:41:32 -0700 Subject: [PATCH 31/38] Add xml documentation --- Engine/Commands/InvokeFormatterCommand.cs | 11 +++++++++++ Engine/Formatter.cs | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/Engine/Commands/InvokeFormatterCommand.cs b/Engine/Commands/InvokeFormatterCommand.cs index dea34766f..72aa905a8 100644 --- a/Engine/Commands/InvokeFormatterCommand.cs +++ b/Engine/Commands/InvokeFormatterCommand.cs @@ -18,6 +18,9 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands { using PSSASettings = Microsoft.Windows.PowerShell.ScriptAnalyzer.Settings; + /// + /// A cmdlet to format a PowerShell script text. + /// [Cmdlet(VerbsLifecycle.Invoke, "Formatter")] public class InvokeFormatterCommand : PSCmdlet, IOutputWriter { @@ -25,10 +28,18 @@ public class InvokeFormatterCommand : PSCmdlet, IOutputWriter private Settings defaultSettings; private Settings inputSettings; + /// + /// The script text to be formated. + /// + /// *NOTE*: Unlike ScriptBlock parameter, the ScriptDefinition parameter require a string value. + /// [ParameterAttribute(Mandatory = true)] [ValidateNotNull] public string ScriptDefinition { get; set; } + /// + /// A settings hashtable or a path to a PowerShell data file (.psd1) file that contains the settings. + /// [Parameter(Mandatory = false)] [ValidateNotNull] public object Settings { get; set; } diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index 4a13508a7..5651c7912 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -3,8 +3,19 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer { + /// + /// A class to provide code formatting capability. + /// public class Formatter { + /// + /// Format a powershell script. + /// + /// A string representing a powershell script. + /// Settings to be used for formatting + /// The range in which formatting should take place. + /// The cmdlet object that calls this method. + /// public static string Format( string scriptDefinition, Settings settings, From 88da14b6e2fc567298ce674b5240054750cacf35 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Thu, 8 Jun 2017 16:53:58 -0700 Subject: [PATCH 32/38] Update verbose messages for invoke-formatter --- Engine/Formatter.cs | 1 - Engine/ScriptAnalyzer.cs | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index 5651c7912..32d7282d2 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -43,7 +43,6 @@ public static string Format( continue; } - cmdlet.WriteVerbose("Running " + rule); var currentSettings = GetCurrentSettings(settings, rule); ScriptAnalyzer.Instance.UpdateSettings(currentSettings); ScriptAnalyzer.Instance.Initialize(cmdlet, null, null, null, null, true, false); diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 9cb5f19ce..2231dfee8 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -1571,8 +1571,10 @@ public EditableText Fix(EditableText text, Range range, out Range updatedRange) .Where(sc => isRangeNull || (sc.Start >= range.Start && sc.End <= range.End)) .ToList(); + this.outputWriter.WriteVerbose($"Found {corrections.Count} violations."); int unusedCorrections; Fix(text, corrections, out unusedCorrections); + this.outputWriter.WriteVerbose($"Fixed {corrections.Count - unusedCorrections} violations."); // This is an indication of an infinite loop. There is a small chance of this. // It is better to abort the fixing operation at this point. From 768e734659546b51455e22ce0699de37df48773e Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Thu, 8 Jun 2017 17:59:10 -0700 Subject: [PATCH 33/38] Make netstandard1.6 pdb portable type --- Engine/project.json | 3 ++- Rules/project.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Engine/project.json b/Engine/project.json index a53e3160e..8083584b7 100644 --- a/Engine/project.json +++ b/Engine/project.json @@ -45,7 +45,8 @@ "Strings.Designer.Core.cs", "Commands/GetScriptAnalyzerLoggerCommand.cs" ] - } + }, + "debugType": "portable" } } } diff --git a/Rules/project.json b/Rules/project.json index 6d036aa1d..7e7dbaa3f 100644 --- a/Rules/project.json +++ b/Rules/project.json @@ -46,7 +46,8 @@ "Strings.Designer.Core.cs", "UseSingularNouns.cs" ] - } + }, + "debugType": "portable" }, "dependencies": { "Microsoft.NETCore.App": { From 7d00f7bc722e8e4244fdb176dbe19532f866c070 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Thu, 8 Jun 2017 18:01:51 -0700 Subject: [PATCH 34/38] Add argument checks for Formatter.Format method --- Engine/Formatter.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index 32d7282d2..098e772a9 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Management.Automation; @@ -22,7 +23,11 @@ public static string Format( Range range, TCmdlet cmdlet) where TCmdlet : PSCmdlet, IOutputWriter { - // todo add argument check + // todo implement notnull attribute for such a check + ValidateNotNull(scriptDefinition, "scriptDefinition"); + ValidateNotNull(settings, "settings"); + ValidateNotNull(cmdlet, "cmdlet"); + Helper.Instance = new Helper(cmdlet.SessionState.InvokeCommand, cmdlet); Helper.Instance.Initialize(); @@ -55,6 +60,14 @@ public static string Format( return text.ToString(); } + private static void ValidateNotNull(T obj, string name) + { + if (obj == null) + { + throw new ArgumentNullException(name); + } + } + private static Settings GetCurrentSettings(Settings settings, string rule) { return new Settings(new Hashtable() From e0bf96128e149685e6f25a642d3f3f3764608fc3 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Thu, 8 Jun 2017 18:11:04 -0700 Subject: [PATCH 35/38] Update nuget.config --- NuGet.Config | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/NuGet.Config b/NuGet.Config index 1dc373452..d9071f66f 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -1,17 +1,9 @@  - - - - - - - - + + + - - - - \ No newline at end of file + From b0fa911fe7b2f11e9db075abd970ad8dbea46b04 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Thu, 8 Jun 2017 18:12:00 -0700 Subject: [PATCH 36/38] Update s.m.a version in project.json files --- Engine/project.json | 2 +- Rules/project.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/project.json b/Engine/project.json index 8083584b7..39f7daaa8 100644 --- a/Engine/project.json +++ b/Engine/project.json @@ -2,7 +2,7 @@ "name": "Microsoft.Windows.PowerShell.ScriptAnalyzer", "version": "1.13.0", "dependencies": { -"System.Management.Automation": "1.0.0-alpha12" +"System.Management.Automation": "6.0.0-alpha13" }, "configurations": { diff --git a/Rules/project.json b/Rules/project.json index 7e7dbaa3f..e5f22f917 100644 --- a/Rules/project.json +++ b/Rules/project.json @@ -2,7 +2,7 @@ "name": "Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules", "version": "1.13.0", "dependencies": { - "System.Management.Automation": "1.0.0-alpha12", + "System.Management.Automation": "6.0.0-alpha13", "Engine": "1.13.0", "Newtonsoft.Json": "9.0.1" }, From 3b41088a21a7256918bc865fbc51ef9d71194f94 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Thu, 8 Jun 2017 18:28:34 -0700 Subject: [PATCH 37/38] Add xml documentation --- Engine/ScriptAnalyzer.cs | 1 + Engine/Settings.cs | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 2231dfee8..ab60859b5 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -1547,6 +1547,7 @@ public string Fix(string scriptDefinition) /// Fix the violations in the given script text. /// /// An object of type `EditableText` that encapsulates the script text to be fixed. + /// The range in which the fixes are allowed. /// The updated range after the fixes have been applied. /// The same instance of `EditableText` that was passed to the method, but the instance encapsulates the fixed script text. This helps in chaining the Fix method. public EditableText Fix(EditableText text, Range range, out Range updatedRange) diff --git a/Engine/Settings.cs b/Engine/Settings.cs index 3be3155ff..83cc12f69 100644 --- a/Engine/Settings.cs +++ b/Engine/Settings.cs @@ -23,6 +23,9 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer { internal enum SettingsMode { None = 0, Auto, File, Hashtable, Preset }; + /// + /// A class to represent the settings provided to ScriptAnalyzer class. + /// public class Settings { private string filePath; @@ -37,6 +40,11 @@ public class Settings public IEnumerable Severities { get { return severities; } } public Dictionary> RuleArguments { get { return ruleArguments; } } + /// + /// Create a settings object from the input object. + /// + /// An input object of type Hashtable or string. + /// A function that takes in a preset and resolves it to a path. public Settings(object settings, Func presetResolver) { if (settings == null) @@ -90,6 +98,10 @@ public Settings(object settings, Func presetResolver) } } + /// + /// Create a Settings object from the input object. + /// + /// An input object of type Hashtable or string. public Settings(object settings) : this(settings, null) { } @@ -159,6 +171,13 @@ public static string GetSettingPresetFilePath(string settingPreset) return null; } + /// + /// Create a settings object from an input object. + /// + /// An input object of type Hashtable or string. + /// The path in which to search for a settings file. + /// An output writer. + /// An object of Settings type. public static Settings Create(object settingsObj, string cwd, IOutputWriter outputWriter) { object settingsFound; From 2bd30aecb91d6ec86c00114093e337a9e862658a Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Thu, 8 Jun 2017 18:39:22 -0700 Subject: [PATCH 38/38] Fix invoke-formatter markdown help file --- docs/markdown/Invoke-Formatter.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/markdown/Invoke-Formatter.md b/docs/markdown/Invoke-Formatter.md index cce30bd72..3a925616d 100644 --- a/docs/markdown/Invoke-Formatter.md +++ b/docs/markdown/Invoke-Formatter.md @@ -62,6 +62,7 @@ This command formats the input script text using the settings defined in the `$s ### -------------------------- EXAMPLE 3 -------------------------- ``` S> Invoke-Formatter -ScriptDefinition $scriptDefinition -Settings /path/to/settings.psd1 +``` This command formats the input script text using the settings defined in the `settings.psd1` file.