diff --git a/Engine/Commands/InvokeFormatterCommand.cs b/Engine/Commands/InvokeFormatterCommand.cs new file mode 100644 index 000000000..72aa905a8 --- /dev/null +++ b/Engine/Commands/InvokeFormatterCommand.cs @@ -0,0 +1,130 @@ +// +// 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.Globalization; +using System.Management.Automation; + +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 + { + private const string defaultSettingsPreset = "CodeFormatting"; + 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; } + +#if DEBUG + [Parameter(Mandatory = false)] + public Range Range { get; set; } + + [Parameter(Mandatory = false, ParameterSetName = "NoRange")] + public int StartLineNumber { get; 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; + + /// + /// Attaches to an instance of a .Net debugger + /// + [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 + + try + { + inputSettings = PSSASettings.Create(Settings, null, this); + if (inputSettings == null) + { + inputSettings = new PSSASettings( + defaultSettingsPreset, + PSSASettings.GetSettingPresetFilePath); + } + } + catch + { + this.WriteWarning(String.Format(CultureInfo.CurrentCulture, Strings.SettingsNotParsable)); + return; + } + } + + protected override void ProcessRecord() + { + // 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); + } + + formattedScriptDefinition = Formatter.Format(ScriptDefinition, inputSettings, range, this); +#endif // DEBUG + + formattedScriptDefinition = Formatter.Format(ScriptDefinition, inputSettings, null, this); + this.WriteObject(formattedScriptDefinition); + } + + private void ValidateInputSettings() + { + // todo implement this + return; + } + } +} 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( 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. diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs new file mode 100644 index 000000000..098e772a9 --- /dev/null +++ b/Engine/Formatter.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections; +using System.Management.Automation; + +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, + Range range, + TCmdlet cmdlet) where TCmdlet : PSCmdlet, IOutputWriter + { + // 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(); + + var ruleOrder = new string[] + { + "PSPlaceCloseBrace", + "PSPlaceOpenBrace", + "PSUseConsistentWhitespace", + "PSUseConsistentIndentation", + "PSAlignAssignmentStatement" + }; + + var text = new EditableText(scriptDefinition); + foreach (var rule in ruleOrder) + { + if (!settings.RuleArguments.ContainsKey(rule)) + { + continue; + } + + var currentSettings = GetCurrentSettings(settings, rule); + ScriptAnalyzer.Instance.UpdateSettings(currentSettings); + ScriptAnalyzer.Instance.Initialize(cmdlet, null, null, null, null, true, false); + + Range updatedRange; + text = ScriptAnalyzer.Instance.Fix(text, range, out updatedRange); + range = updatedRange; + } + + 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() + { + {"IncludeRules", new string[] {rule}}, + {"Rules", new Hashtable() { { rule, new Hashtable(settings.RuleArguments[rule]) } } } + }); + } + } +} 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 = @() 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 +} diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 61fdd6ec7..ab60859b5 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -1527,6 +1527,128 @@ 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)); + } + + 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 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) + { + 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 + { + var records = AnalyzeScriptDefinition(text.ToString()); + var corrections = records + .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(); + + 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. + if (previousUnusedCorrections > 0 && previousUnusedCorrections == unusedCorrections) + { + this.outputWriter.ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException(), + "FIX_ERROR", + ErrorCategory.InvalidOperation, + corrections)); + } + + 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); + + updatedRange = range; + 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) + { + var ceList = correctionExtents.ToList(); + ceList.Sort((x, y) => + { + return x.StartLineNumber < y.StartLineNumber ? + 1 : + (x.StartLineNumber == y.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, diff --git a/Engine/ScriptAnalyzerEngine.csproj b/Engine/ScriptAnalyzerEngine.csproj index 1503e06e1..375bcdf5e 100644 --- a/Engine/ScriptAnalyzerEngine.csproj +++ b/Engine/ScriptAnalyzerEngine.csproj @@ -67,9 +67,11 @@ + + diff --git a/Engine/Settings.cs b/Engine/Settings.cs index 539a97fda..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,65 @@ 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; + 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 /// 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 } } } diff --git a/Engine/project.json b/Engine/project.json index a53e3160e..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": { @@ -45,7 +45,8 @@ "Strings.Designer.Core.cs", "Commands/GetScriptAnalyzerLoggerCommand.cs" ] - } + }, + "debugType": "portable" } } } 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 + 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`) diff --git a/Rules/project.json b/Rules/project.json index 6d036aa1d..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" }, @@ -46,7 +46,8 @@ "Strings.Designer.Core.cs", "UseSingularNouns.cs" ] - } + }, + "debugType": "portable" }, "dependencies": { "Microsoft.NETCore.App": { 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 + } + } + +} 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-Formatter.md b/docs/markdown/Invoke-Formatter.md new file mode 100644 index 000000000..3a925616d --- /dev/null +++ b/docs/markdown/Invoke-Formatter.md @@ -0,0 +1,100 @@ +--- +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 +``` 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.