From 34959b9ff0b622822cd34933eb9cc1a29edda452 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 26 Jul 2019 09:05:36 -0700 Subject: [PATCH] Backport PRs post 2019.5.0 (#2102) * Edit snippets to support $TM_SELECTED_TEXT (#1945) Edit all-and-only applicable snippets to support $TM_SELECTED_TEXT, where "applicable" is approximated by whether a snippet contains a user-specified PowerShell expression, block, or body. Do not add, remove, or otherwise change any placeholder number or name in order to preserve backwards-compatibility. Edit the following snippets (listed by name, not prefix): - Class - Constructor - Method - Enum - Cmdlet - Function-Advanced - DSC Resource Provider (class-based) - DSC Resource Provider (function-based) - comment block - do-until - do-while - while - for - for-reversed - foreach - function - Function-Inline - if - elseif - else - switch - try-catch - try-catch-finally - try-finally - Workflow - Workflow ForEachParallel - Workflow InlineScript - Workflow Parallel - Workflow Sequence - Region Block - IfShouldProcess - CalculatedProperty - PesterDescribeContextIt - PesterDescribeBlock - PesterContextIt - PesterContext - PesterIt * Add ArgumentCompleter snippets (#1946) * Define snippet named 'ArgumentCompleterAttribute with ScriptBlock' * Define snippet named 'IArgumentCompleter Class' * Define snippet named 'ArgumentCompleterAttribute ScriptBlock' * Add #Requires snippets (#1974) * Add script requirement directive snippets Adds the following snippets (listed by name, not prefix): - Requires Assembly - Requires Assembly Path - Requires Assembly Version - Requires Module - Requires Module RequiredVersion - Requires Module Version - Requires PSEdition - Requires PSSnapin - Requires PSSnapin Version - Requires RunAsAdministrator - Requires ShellId - Requires Version * Fix node version detect logic to handle node v10 (#2025) * #1019: Get format settings from document editor instead of global. (#2035) * Update PSSA docs Url to point to master branch because master is now the default branch (#2037) * add machine scope (#2039) * add machine scope * use a different setting for test and add user setting test * remove isExecutable and remove powershell.developer.powerShellExePath * Add param-block snippet (#2081) --- build.ps1 | 11 +- package.json | 12 +- snippets/PowerShell.json | 205 +++++++++++++++++++++++++----- src/features/CodeActions.ts | 2 +- src/features/DocumentFormatter.ts | 11 +- test/settings.test.ts | 17 ++- 6 files changed, 201 insertions(+), 57 deletions(-) diff --git a/build.ps1 b/build.ps1 index 3f8a20d416..fdd5479f4b 100644 --- a/build.ps1 +++ b/build.ps1 @@ -45,12 +45,17 @@ function needsVSCode () { function needsNodeJS () { try { - $nodeJSVersion = (node -v) - + $nodeJSVersion = node -v } catch { return $true } - return ($nodeJSVersion.Substring(1,1) -lt 6) + + if ($nodeJSVersion -notmatch 'v(\d+\.\d+\.\d+)') { + return $true + } + + $nodeVer = [System.Version]$matches[1] + return ($nodeVer.Major -lt 6) } function needsPowerShellGet () { diff --git a/package.json b/package.json index 5d8558c214..443ff10f48 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "publisher": "ms-vscode", "description": "Develop PowerShell scripts in Visual Studio Code!", "engines": { - "vscode": "^1.31.0" + "vscode": "^1.34.0" }, "license": "SEE LICENSE IN LICENSE.txt", "homepage": "https://github.com/PowerShell/vscode-powershell/blob/master/README.md", @@ -545,13 +545,13 @@ "powershell.powerShellExePath": { "type": "string", "default": "", - "isExecutable": true, + "scope": "machine", "description": "Specifies the full path to a PowerShell executable. Changes the installation of PowerShell used for language and debugging services." }, "powershell.powerShellAdditionalExePaths": { "type": "array", "description": "Specifies an array of versionName / exePath pairs where exePath points to a non-standard install location for PowerShell and versionName can be used to reference this path with the powershell.powerShellDefaultVersion setting.", - "isExecutable": true, + "scope": "machine", "uniqueItems": true, "items": { "type": "object", @@ -751,12 +751,6 @@ "type": "boolean", "default": false, "description": "Indicates that the powerShellExePath points to a developer build of Windows PowerShell and configures it for development." - }, - "powershell.developer.powerShellExePath": { - "type": "string", - "default": "", - "isExecutable": true, - "description": "Deprecated. Please use the 'powershell.powerShellExePath' setting instead" } } }, diff --git a/snippets/PowerShell.json b/snippets/PowerShell.json index dd7317d89b..02ac2849f0 100644 --- a/snippets/PowerShell.json +++ b/snippets/PowerShell.json @@ -1,3 +1,4 @@ +// The "Requires *" snippets should be removed if-and-when intellisense is implemented for the script requirement directive syntax. { "ModuleManifest": { "prefix": "manifest", @@ -466,7 +467,7 @@ "prefix": "class", "body": [ "class ${1:ClassName} {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "Class definition snippet" @@ -475,7 +476,7 @@ "prefix": "ctor", "body": [ "${1:ClassName}(${2:OptionalParameters}) {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "Class constructor definition snippet" @@ -498,7 +499,7 @@ "prefix": "method", "body": [ "[${1:void}] ${2:MethodName}($${3:OptionalParameters}) {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "Class method definition snippet" @@ -507,7 +508,7 @@ "prefix": "enum", "body": [ "enum ${1:EnumName} {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "Enum definition snippet" @@ -522,12 +523,15 @@ "\t)", "\t", "\tbegin {", + "\t\t", "\t}", "\t", "\tprocess {", + "\t\t$TM_SELECTED_TEXT", "\t}", "\t", "\tend {", + "\t\t", "\t}", "}" ], @@ -543,12 +547,15 @@ "\t)", "\t", "\tbegin {", + "\t\t", "\t}", "\t", "\tprocess {", + "\t\t$TM_SELECTED_TEXT", "\t}", "\t", "\tend {", + "\t\t", "\t}", "}" ], @@ -585,6 +592,17 @@ ], "description": "Parameter declaration snippet" }, + "Parameter_Block" : { + "prefix": "param-block", + "body": ["[CmdletBinding()]", + "param (", + " [Parameter()]", + " [${1:TypeName}]", + " $${2:ParameterName}$0", + ")" + ], + "description": "A Parameter block to get you started." + }, "Parameter-Path": { "prefix": "parameter-path", "body": [ @@ -658,7 +676,7 @@ "\t", "\t# Gets the resource's current state.", "\t[${ResourceName:NameOfResource}] Get() {", - "\t\t$0", + "\t\t${0:$TM_SELECTED_TEXT}", "\t\treturn \\$this", "\t}", "\t", @@ -682,7 +700,7 @@ "\tparam (", "\t)", "\t", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}", "function Set-TargetResource {", "\tparam (", @@ -701,7 +719,7 @@ "prefix": "comment", "body": [ "<#", - " # $0", + " # ${0:$TM_SELECTED_TEXT}", " #>" ], "description": "Comment block snippet" @@ -710,7 +728,7 @@ "prefix": "do-until", "body": [ "do {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "} until (${1:condition})" ], "description": "do-until loop snippet" @@ -719,7 +737,7 @@ "prefix": "do-while", "body": [ "do {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "} while (${1:condition})" ], "description": "do-while loop snippet" @@ -728,7 +746,7 @@ "prefix": "while", "body": [ "while (${1:condition}) {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "while loop snippet" @@ -737,7 +755,7 @@ "prefix": "for", "body": [ "for ($${1:i} = 0; $${1:i} -lt $${2:array}.Count; $${1:i}++) {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "for loop snippet" @@ -746,7 +764,7 @@ "prefix": "forr", "body": [ "for ($${1:i} = $${2:array}.Count - 1; $${1:i} -ge 0 ; $${1:i}--) {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "reversed for loop snippet" @@ -755,7 +773,7 @@ "prefix": "foreach", "body": [ "foreach ($${1:item} in $${2:collection}) {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "foreach loop snippet" @@ -767,7 +785,7 @@ "\tparam (", "\t\t${2:OptionalParameters}", "\t)", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "Function definition snippet that contains a param block" @@ -776,7 +794,7 @@ "prefix": "Function-Inline", "body": [ "function ${1:FunctionName} (${2:OptionalParameters}) {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "Function definition snippet that does not contain a param block, but defines parameters inline. This syntax is commonly used in other languages" @@ -785,7 +803,7 @@ "prefix": "if", "body": [ "if (${1:condition}) {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "if statement snippet" @@ -794,7 +812,7 @@ "prefix": "elseif", "body": [ "elseif (${1:condition}) {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "elseif statement snippet" @@ -803,7 +821,7 @@ "prefix": "else", "body": [ "else {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "else statement snippet" @@ -812,7 +830,7 @@ "prefix": "switch", "body": [ "switch (${1:\\$x}) {", - "\t${2:condition} { $0 }", + "\t${2:condition} { ${0:$TM_SELECTED_TEXT} }", "\tDefault {}", "}" ], @@ -822,7 +840,7 @@ "prefix": "try", "body": [ "try {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}", "catch {", "\t", @@ -834,7 +852,7 @@ "prefix": "trycf", "body": [ "try {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}", "catch {", "\t", @@ -849,7 +867,7 @@ "prefix": "tryf", "body": [ "try {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}", "finally {", "\t", @@ -864,7 +882,7 @@ "\tparam (", "\t)", "", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "workflow snippet" @@ -873,7 +891,7 @@ "prefix": "workflow foreach-parallel", "body": [ "foreach -parallel ($${variable:item} in $${collection:collection}) {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "foreach-parallel snippet (for use inside a workflow)" @@ -882,7 +900,7 @@ "prefix": "workflow inlinescript", "body": [ "inlineScript {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "inlinescript snippet (for use inside a workflow)" @@ -891,7 +909,7 @@ "prefix": "workflow parallel", "body": [ "parallel {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "parallel snippet (for use inside a workflow)" @@ -900,7 +918,7 @@ "prefix": "workflow sequence", "body": [ "sequence {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "sequence snippet (for use inside a workflow)" @@ -948,7 +966,7 @@ "prefix": "#region", "body": [ "#region ${1}", - "$0", + "${0:$TM_SELECTED_TEXT}", "#endregion" ], "description": "Region Block for organizing and folding of your code" @@ -957,7 +975,7 @@ "prefix": "IfShouldProcess", "body": [ "if (\\$PSCmdlet.ShouldProcess(\"${1:Target}\", \"${2:Operation}\")) {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "Creates ShouldProcess block" @@ -965,7 +983,7 @@ "CalculatedProperty": { "prefix": "Calculated-Property", "body": [ - "@{name='${1:PropertyName}';expression={${2:\\$_.PropertyValue}}}$0" + "@{name='${1:PropertyName}';expression={${2:${TM_SELECTED_TEXT:\\$_.PropertyValue}}}}$0" ], "description": "Creates a Calculated Property typically used with Select-Object." }, @@ -975,7 +993,7 @@ "Describe \"${1:DescribeName}\" {", "\tContext \"${2:ContextName}\" {", "\t\tIt \"${3:ItName}\" {", - "\t\t\t${4:Assertion}", + "\t\t\t${4:${TM_SELECTED_TEXT:Assertion}}", "\t\t}$0", "\t}", "}" @@ -986,7 +1004,7 @@ "prefix": "Describe-Pester", "body": [ "Describe \"${1:DescribeName}\" {", - "\t$0", + "\t${0:TM_SELECTED_TEXT}", "}" ], "description": "Pester Describe block" @@ -996,7 +1014,7 @@ "body": [ "Context \"${1:ContextName}\" {", "\tIt \"${2:ItName}\" {", - "\t\t${3:Assertion}", + "\t\t${3:${TM_SELECTED_TEXT:Assertion}}", "\t}$0", "}" ], @@ -1006,7 +1024,7 @@ "prefix": "Context-Pester", "body": [ "Context \"${1:ContextName}\" {", - "\t$0", + "\t${0:$TM_SELECTED_TEXT}", "}" ], "description": "Pester - Context block" @@ -1015,9 +1033,126 @@ "prefix": "It-Pester", "body": [ "It \"${1:ItName}\" {", - "\t${2:Assertion}", + "\t${2:${TM_SELECTED_TEXT:Assertion}}", "}$0" ], "description": "Pester - It block" + }, + "ArgumentCompleterAttribute with ScriptBlock": { + "prefix": "completer-attribute", + "body": [ + "[ArgumentCompleter({", + "\t[OutputType([System.Management.Automation.CompletionResult])] # zero to many", + "\tparam(", + "\t\t[string] \\$CommandName,", + "\t\t[string] \\$ParameterName,", + "\t\t[string] \\$WordToComplete,", + "\t\t[System.Management.Automation.Language.CommandAst] \\$CommandAst,", + "\t\t[System.Collections.IDictionary] \\$FakeBoundParameters", + "\t)", + "\t", + "\t${0:$TM_SELECTED_TEXT}", + "})]" + ], + "description": "ArgumentCompleter parameter attribute with script block definition" + }, + "ArgumentCompleterAttribute ScriptBlock": { + "prefix": "completer-scriptblock", + "body": [ + "{", + "\t[OutputType([System.Management.Automation.CompletionResult])] # zero to many", + "\tparam(", + "\t\t[string] \\$CommandName,", + "\t\t[string] \\$ParameterName,", + "\t\t[string] \\$WordToComplete,", + "\t\t[System.Management.Automation.Language.CommandAst] \\$CommandAst,", + "\t\t[System.Collections.IDictionary] \\$FakeBoundParameters", + "\t)", + "\t", + "\t${0:$TM_SELECTED_TEXT}", + "}" + ], + "description": "ArgumentCompleter parameter attribute script block definition" + }, + "IArgumentCompleter Class": { + "prefix": "completer-class", + "body": [ + "class ${1:ArgumentCompleter} : System.Management.Automation.IArgumentCompleter {", + "\t[System.Collections.Generic.IEnumerable[System.Management.Automation.CompletionResult]] CompleteArgument(", + "\t\t[string] \\$CommandName,", + "\t\t[string] \\$ParameterName,", + "\t\t[string] \\$WordToComplete,", + "\t\t[System.Management.Automation.Language.CommandAst] \\$CommandAst,", + "\t\t[System.Collections.IDictionary] \\$FakeBoundParameters", + "\t) {", + "\t\t\\$CompletionResults = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new()", + "\t\t", + "\t\t${0:$TM_SELECTED_TEXT}", + "\t\t", + "\t\treturn \\$CompletionResults", + "\t}", + "}" + ], + "description": "IArgumentCompleter implementation class definition" + }, + "Requires Assembly": { + "prefix": "requires-assembly", + "body": "#Requires -Assembly '${1:${TM_SELECTED_TEXT:fully-qualified-name}}'", + "description": "Requires an assembly (by name) in order to execute the containing script file." + }, + "Requires Assembly Path": { + "prefix": "requires-assembly-path", + "body": "#Requires -Assembly ${0:${TM_SELECTED_TEXT:path/to/assembly.dll}}", + "description": "Requires an assembly (by relative or absolute path) in order to execute the containing script file." + }, + "Requires Assembly Version": { + "prefix": "requires-assembly-version", + "body": "#Requires -Assembly '${1:${TM_SELECTED_TEXT:fully-qualified-name}}, Version=${2:1.0.0.0}'", + "description": "Requires an assembly (by name and minimum version) in order to execute the containing script file." + }, + "Requires Module": { + "prefix": "requires-module", + "body": "#Requires -Module ${0:${TM_SELECTED_TEXT:fully-qualified-name}}", + "description": "Requires a module (by name) in order to execute the containing script file." + }, + "Requires Module RequiredVersion": { + "prefix": "requires-module-required-version", + "body": "#Requires -Module @{ ModuleName = '${1:${TM_SELECTED_TEXT:fully-qualified-name}}'; RequiredVersion = '${2:exact-required-version}' }", + "description": "Requires a module (by name and exact version) in order to execute the containing script file." + }, + "Requires Module Version": { + "prefix": "requires-module-version", + "body": "#Requires -Module @{ ModuleName = '${1:${TM_SELECTED_TEXT:fully-qualified-name}}'; ModuleVersion = '${2:minimum-acceptable-version}' }", + "description": "Requires a module (by name and minimum version) in order to execute the containing script file." + }, + "Requires PSEdition": { + "prefix": "requires-ps-edition", + "body": "#Requires -PSEdition ${1|Core,Desktop|}", + "description": "Requires a specific edition of PowerShell in order to execute the containing script file." + }, + "Requires PSSnapin": { + "prefix": "requires-ps-snapin", + "body": "#Requires -PSSnapin ${0:${TM_SELECTED_TEXT:fully-qualified-name}}", + "description": "Requires a PowerShell snap-in (by name) in order to execute the containing script file." + }, + "Requires PSSnapin Version": { + "prefix": "requires-ps-snapin-version", + "body": "#Requires -PSSnapin ${1:${TM_SELECTED_TEXT:fully-qualified-name}} -Version ${2:minimum-acceptable-version}", + "description": "Requires a PowerShell snap-in (by name and minimum version) in order to execute the containing script file." + }, + "Requires RunAsAdministrator": { + "prefix": "requires-run-as-administrator", + "body": "#Requires -RunAsAdministrator", + "description": "Requires elevated user rights in order to execute the containing script file. Ignored on non-Windows systems. On Windows systems, it requires that the PowerShell session in which the containing script file is run must have been started with elevated user rights (\"Run as Administrator\")." + }, + "Requires ShellId": { + "prefix": "requires-shell-id", + "body": "#Requires -ShellId ${0:${TM_SELECTED_TEXT:shell-id}}", + "description": "Requires a specific shell id in order to execute the containing script file. The current shell id may be determined by querying the $ShellId automatic variable." + }, + "Requires Version": { + "prefix": "requires-version", + "body": "#Requires -Version ${0:${TM_SELECTED_TEXT:minimum-acceptable-version}}", + "description": "Requires a minimum version of PowerShell in order to execute the containing script file." } } diff --git a/src/features/CodeActions.ts b/src/features/CodeActions.ts index 53f18fd1db..d830bba195 100644 --- a/src/features/CodeActions.ts +++ b/src/features/CodeActions.ts @@ -42,7 +42,7 @@ export class CodeActionsFeature implements IFeature { } public showRuleDocumentation(ruleId: string) { - const pssaDocBaseURL = "https://github.com/PowerShell/PSScriptAnalyzer/blob/development/RuleDocumentation"; + const pssaDocBaseURL = "https://github.com/PowerShell/PSScriptAnalyzer/blob/master/RuleDocumentation"; if (!ruleId) { this.log.writeWarning("Cannot show documentation for code action, no ruleName was supplied."); diff --git a/src/features/DocumentFormatter.ts b/src/features/DocumentFormatter.ts index 11dac853c2..cb801fa5dd 100644 --- a/src/features/DocumentFormatter.ts +++ b/src/features/DocumentFormatter.ts @@ -246,7 +246,7 @@ class PSDocumentFormattingEditProvider implements const requestParams: DocumentRangeFormattingParams = { textDocument: TextDocumentIdentifier.create(document.uri.toString()), range: rangeParam, - options: this.getEditorSettings(), + options: this.getEditorSettings(editor), }; const formattingStartTime = new Date().valueOf(); @@ -309,11 +309,12 @@ class PSDocumentFormattingEditProvider implements PSDocumentFormattingEditProvider.documentLocker.lock(document, unlockWhenDone); } - private getEditorSettings(): { insertSpaces: boolean, tabSize: number } { - const editorConfiguration = vscode.workspace.getConfiguration("editor"); + private getEditorSettings(editor: TextEditor): { insertSpaces: boolean, tabSize: number } { + // Writing the editor options allows string or strong types going in, but always + // resolves to an appropriate value on read. return { - insertSpaces: editorConfiguration.get("insertSpaces"), - tabSize: editorConfiguration.get("tabSize"), + insertSpaces: editor.options.insertSpaces as boolean, + tabSize: editor.options.tabSize as number, }; } } diff --git a/test/settings.test.ts b/test/settings.test.ts index 258850cd48..6117d170f2 100644 --- a/test/settings.test.ts +++ b/test/settings.test.ts @@ -12,11 +12,20 @@ suite("Settings module", () => { test("Settings update correctly", async () => { // then syntax - Settings.change("powerShellExePath", "dummypath1", false).then(() => - assert.strictEqual(Settings.load().powerShellExePath, "dummypath1")); + Settings.change("helpCompletion", "BlockComment", false).then(() => + assert.strictEqual(Settings.load().helpCompletion, "BlockComment")); // async/await syntax - await Settings.change("powerShellExePath", "dummypath2", false); - assert.strictEqual(Settings.load().powerShellExePath, "dummypath2"); + await Settings.change("helpCompletion", "LineComment", false); + assert.strictEqual(Settings.load().helpCompletion, "LineComment"); + }); + + test("Settings that can only be user settings update correctly", async () => { + // set to false means it's set as a workspace-level setting so this should throw. + assert.rejects(async () => await Settings.change("powerShellExePath", "dummyPath", false)); + + // set to true means it's a user-level setting so this should not throw. + await Settings.change("powerShellExePath", "dummyPath", true); + assert.strictEqual(Settings.load().powerShellExePath, "dummyPath"); }); });