Skip to content

Commit 774977e

Browse files
authored
Merge pull request #114 from johlju/f/add-improved-alias-auto-export
Add support for exporting aliases using `*-Alias` in public scripts
2 parents 6a76fd2 + 598d4be commit 774977e

File tree

6 files changed

+323
-25
lines changed

6 files changed

+323
-25
lines changed

Source/Private/GetCommandAlias.ps1

Lines changed: 114 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,126 @@
1+
# This is used only to parse the parameters to New|Set|Remove-Alias
2+
class AliasParameterVisitor : System.Management.Automation.Language.AstVisitor {
3+
[string]$Parameter = $null
4+
[string]$Command = $null
5+
[string]$Name = $null
6+
[string]$Value = $null
7+
[string]$Scope = $null
8+
9+
# Parameter Names
10+
[System.Management.Automation.Language.AstVisitAction] VisitCommandParameter([System.Management.Automation.Language.CommandParameterAst]$ast) {
11+
$this.Parameter = $ast.ParameterName
12+
return [System.Management.Automation.Language.AstVisitAction]::Continue
13+
}
14+
15+
# Parameter Values
16+
[System.Management.Automation.Language.AstVisitAction] VisitStringConstantExpression([System.Management.Automation.Language.StringConstantExpressionAst]$ast) {
17+
# The FIRST command element is always the command name
18+
if (!$this.Command) {
19+
$this.Command = $ast.Value
20+
return [System.Management.Automation.Language.AstVisitAction]::Continue
21+
} else {
22+
switch ($this.Parameter) {
23+
"Scope" {
24+
$this.Scope = $ast.Value
25+
}
26+
"Name" {
27+
$this.Name = $ast.Value
28+
}
29+
"Value" {
30+
$this.Value = $ast.Value
31+
}
32+
"Force" {
33+
if ($ast.Value) {
34+
# Force parameter was passed as named parameter with a positional parameter after it which is alias name
35+
$this.Name = $ast.Value
36+
}
37+
}
38+
default {
39+
if (!$this.Parameter) {
40+
# For bare arguments, the order is Name, Value:
41+
if (!$this.Name) {
42+
$this.Name = $ast.Value
43+
} else {
44+
$this.Value = $ast.Value
45+
}
46+
}
47+
}
48+
}
49+
50+
$this.Parameter = $null
51+
52+
# If we have enough information, stop the visit
53+
# For -Scope global or Remove-Alias, we don't want to export these
54+
if ($this.Name -and $this.Command -eq "Remove-Alias") {
55+
$this.Command = "Remove-Alias"
56+
return [System.Management.Automation.Language.AstVisitAction]::StopVisit
57+
} elseif ($this.Name -and $this.Scope -eq "Global") {
58+
return [System.Management.Automation.Language.AstVisitAction]::StopVisit
59+
}
60+
return [System.Management.Automation.Language.AstVisitAction]::Continue
61+
}
62+
}
63+
64+
[AliasParameterVisitor] Clear() {
65+
$this.Command = $null
66+
$this.Parameter = $null
67+
$this.Name = $null
68+
$this.Value = $null
69+
$this.Scope = $null
70+
return $this
71+
}
72+
}
73+
74+
# This visits everything at the top level of the script
75+
class AliasVisitor : System.Management.Automation.Language.AstVisitor {
76+
[System.Collections.Hashtable]$Aliases = @{}
77+
[AliasParameterVisitor]$Parameters = @{}
78+
79+
# The [Alias(...)] attribute on functions matters, but we can't export aliases that are defined inside a function
80+
[System.Management.Automation.Language.AstVisitAction] VisitFunctionDefinition([System.Management.Automation.Language.FunctionDefinitionAst]$ast) {
81+
$this.Aliases[$ast.Name] = @($ast.Body.ParamBlock.Attributes.Where{ $_.TypeName.Name -eq "Alias" }.PositionalArguments.Value)
82+
return [System.Management.Automation.Language.AstVisitAction]::SkipChildren
83+
}
84+
85+
# Top-level commands matter, but only if they're alias commands
86+
[System.Management.Automation.Language.AstVisitAction] VisitCommand([System.Management.Automation.Language.CommandAst]$ast) {
87+
if ($ast.CommandElements[0].Value -imatch "(New|Set|Remove)-Alias") {
88+
$ast.Visit($this.Parameters.Clear())
89+
if ($this.Parameters.Command -ieq "Remove-Alias") {
90+
Write-Warning -Message "Found an alias '$($this.Parameters.Name)' that is removed using $($this.Parameters.Command), assuming the alias should not be exported."
91+
92+
$this.Aliases.Remove($this.Parameters.Name)
93+
} elseif ($this.Parameters.Scope -ine 'Global') {
94+
if ($this.Parameters.Name -notin $this.Aliases.Keys)
95+
{
96+
$this.Aliases[$this.Parameters.Name] = $this.Parameters.Name
97+
}
98+
}
99+
}
100+
return [System.Management.Automation.Language.AstVisitAction]::SkipChildren
101+
}
102+
}
103+
1104
function GetCommandAlias {
105+
<#
106+
.SYNOPSIS
107+
Parses one or more files for aliases and returns a list of alias names.
108+
#>
2109
[CmdletBinding()]
110+
[OutputType([System.Collections.Hashtable])]
3111
param(
4-
# Path to the PSM1 file to amend
112+
# The AST to find aliases in
5113
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline)]
6-
[System.Management.Automation.Language.Ast]$AST
114+
[System.Management.Automation.Language.Ast]$Ast
7115
)
8116
begin {
9-
$Result = [Ordered]@{}
117+
$Visitor = [AliasVisitor]::new()
10118
}
11119
process {
12-
foreach($function in $AST.FindAll(
13-
{ $Args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] },
14-
$false )
15-
) {
16-
$Result[$function.Name] = $function.Body.ParamBlock.Attributes.Where{
17-
$_.TypeName.Name -eq "Alias" }.PositionalArguments.Value
18-
}
120+
$Ast.Visit($Visitor)
19121
}
20122
end {
21-
$Result
123+
$Visitor.Aliases
22124
}
23125
}
126+

Source/Public/Build-Module.ps1

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,10 @@ function Build-Module {
108108
# A switch that allows you to disable the update of the AliasesToExport
109109
# By default, (if PublicFilter is not empty, and this is not set)
110110
# Build-Module updates the module manifest FunctionsToExport and AliasesToExport
111-
# with the combination of all the values in [Alias()] attributes on public functions in the module
112-
[switch]$IgnoreAliasAttribute,
111+
# with the combination of all the values in [Alias()] attributes on public functions
112+
# and aliases created with `New-ALias` or `Set-Alias` at script level in the module
113+
[Alias("IgnoreAliasAttribute")]
114+
[switch]$IgnoreAlias,
113115

114116
# File encoding for output RootModule (defaults to UTF8)
115117
# Converted to System.Text.Encoding for PowerShell 6 (and something else for PowerShell 5)
@@ -208,19 +210,23 @@ function Build-Module {
208210
# We have to force the Encoding to string because PowerShell Core made up encodings
209211
SetModuleContent -Source (@($ModuleInfo.Prefix) + $AllScripts.FullName + @($ModuleInfo.Suffix)).Where{$_} -Output $RootModule -Encoding "$($ModuleInfo.Encoding)"
210212

213+
$ParseResult = ConvertToAst $RootModule
214+
$ParseResult | MoveUsingStatements -Encoding "$($ModuleInfo.Encoding)"
215+
216+
if (-not $ModuleInfo.IgnoreAlias) {
217+
$AliasesToExport = $ParseResult | GetCommandAlias
218+
}
219+
211220
# If there is a PublicFilter, update ExportedFunctions
212221
if ($ModuleInfo.PublicFilter) {
213222
# SilentlyContinue because there don't *HAVE* to be public functions
214223
if (($PublicFunctions = Get-ChildItem $ModuleInfo.PublicFilter -Recurse -ErrorAction SilentlyContinue | Where-Object BaseName -in $AllScripts.BaseName | Select-Object -ExpandProperty BaseName)) {
215-
Update-Metadata -Path $OutputManifest -PropertyName FunctionsToExport -Value $PublicFunctions
224+
Update-Metadata -Path $OutputManifest -PropertyName FunctionsToExport -Value ($PublicFunctions | Where-Object {$_ -notin $AliasesToExport.Values})
216225
}
217226
}
218227

219-
$ParseResult = ConvertToAst $RootModule
220-
$ParseResult | MoveUsingStatements -Encoding "$($ModuleInfo.Encoding)"
221-
222-
if ($PublicFunctions -and -not $ModuleInfo.IgnoreAliasAttribute) {
223-
if (($AliasesToExport = ($ParseResult | GetCommandAlias)[$PublicFunctions] | ForEach-Object { $_ } | Select-Object -Unique)) {
228+
if ($PublicFunctions -and -not $ModuleInfo.IgnoreAlias) {
229+
if (($AliasesToExport = $AliasesToExport[$PublicFunctions] | ForEach-Object { $_ } | Select-Object -Unique)) {
224230
Update-Metadata -Path $OutputManifest -PropertyName AliasesToExport -Value $AliasesToExport
225231
}
226232
}

Tests/Integration/Source1.Tests.ps1

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ Describe "Regression test for #55: I can pass SourceDirectories and PublicFilter
6262
$Metadata = Import-Metadata $Output.Path
6363

6464
It "Should not have any FunctionsToExport if SourceDirectories don't match the PublicFilter" {
65-
$Metadata.FunctionsToExport | Should -Be @("GetFinale", "GetPreview")
65+
$Metadata.FunctionsToExport | Should -Be @("Get-TestNotExportedAliases", "GetFinale", "GetPreview")
6666
}
6767

6868
It "Should update AliasesToExport in the manifest" {
@@ -84,7 +84,7 @@ Describe "Regression test for #84: Multiple Aliases per command will Export" -Ta
8484
$Metadata = Import-Metadata $Output.Path
8585

8686
It "Should update AliasesToExport in the manifest" {
87-
$Metadata.AliasesToExport | Should -Be @("GS","GSou", "SS", "SSou")
87+
$Metadata.AliasesToExport | Should -Be @("Get-MyAlias","GS","GSou", "SS", "SSou")
8888
}
8989
}
9090

@@ -118,7 +118,7 @@ Describe "Supports building without a build.psd1" -Tag Integration {
118118
}
119119

120120
It "Should update AliasesToExport in the manifest" {
121-
$Build.Metadata.AliasesToExport | Should -Be @("GS", "GSou", "SS", "SSou")
121+
$Build.Metadata.AliasesToExport | Should -Be @("Get-MyAlias","GS", "GSou", "SS", "SSou")
122122
}
123123

124124
It "Should update FunctionsToExport in the manifest" {
@@ -168,7 +168,7 @@ Describe "Defaults to VersionedOutputDirectory" -Tag Integration {
168168
}
169169

170170
It "Should update AliasesToExport in the manifest" {
171-
$Build.Metadata.AliasesToExport | Should -Be @("GS", "GSou", "SS", "SSou")
171+
$Build.Metadata.AliasesToExport | Should -Be @("Get-MyAlias","GS", "GSou", "SS", "SSou")
172172
}
173173

174174
It "Should update FunctionsToExport in the manifest" {
@@ -205,7 +205,7 @@ Describe "Supports building discovering the module without a build.psd1" -Tag In
205205
}
206206

207207
It "Should update AliasesToExport in the manifest" {
208-
$Build.Metadata.AliasesToExport | Should -Be @("GS", "GSou", "SS", "SSou")
208+
$Build.Metadata.AliasesToExport | Should -Be @("Get-MyAlias","GS", "GSou", "SS", "SSou")
209209
}
210210

211211
It "Should update FunctionsToExport in the manifest" {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function Get-TestNotExportedAliases {
2+
[CmdletBinding()]
3+
param()
4+
5+
New-Alias -Name 'New-NotExportedAlias1' -Value 'Write-Verbose'
6+
Set-Alias -Name 'New-NotExportedAlias2' -Value 'Write-Verbose'
7+
}
8+
9+
New-Alias -Name 'New-NotExportedAlias3' -Value 'Write-Verbose' -Scope Global
10+
Set-Alias -Name 'New-NotExportedAlias4' -Value 'Write-Verbose' -Scope Global
11+
12+
New-Alias -Name 'New-NotExportedAlias5' -Value 'Write-Verbose'
13+
Remove-Alias -Name 'New-NotExportedAlias5'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
New-Alias -Name 'Get-MyAlias' -Value 'Get-ChildItem'

0 commit comments

Comments
 (0)