PowerShell AST 修改和范围扩展

6
我目前正在尝试使用PowerShell 3.0中引入的AST功能来修改ScriptBlock。我的要求是ScriptBlock参数块中的所有参数都要添加[Parameter(Mandatory)]属性。
基本上,代码应该修改为:
Param([string]$x)

Write-Host $x

转换为:

Param([Parameter(Mandatory)][string]$x)

Write-Host $x

然而,当我添加新属性时遇到了一个问题,因为它需要一个IScriptExtent,而我不确定应该如何创建一个新的IScriptExtent
我应该如何创建一个新的脚本范围?可以使用哪些位置值?我是否需要更改所有后续范围的位置?
我尝试只重用我修改的每个参数的范围,但不幸的是,这似乎没有产生应有的结果(例如,当我在修改后的ScriptBlock上调用ToString时,我看不到任何变化)。
到目前为止,我的实现基于这里找到的ICustomAstVisitor
最重要的方法看起来像这样:
public object VisitParameter(ParameterAst parameterAst)
{
   var newName = VisitElement(parameterAst.Name);

   var extent = // What to do here?

   var mandatoryArg = new AttributeAst(extent, new ReflectionTypeName(typeof (ParameterAttribute)),
        new ExpressionAst[0],
        new[] {new NamedAttributeArgumentAst(extent, "Mandatory", new ConstantExpressionAst(extent, true), true)});

   var newAttributes = new[] {mandatoryArg}.Concat(VisitElements(parameterAst.Attributes));
   var newDefaultValue = VisitElement(parameterAst.DefaultValue);
      return new ParameterAst(parameterAst.Extent, newName, newAttributes, newDefaultValue);
}
2个回答

3

I开头的名称通常是接口。它们不是你创建实例的类,而是某种合同,指定特定类实现了一定已知功能集。

例如,[hashtable]实现IEnumerable。这意味着任何知道如何使用IEnumerable接口并在该类上操作的内容;您可以创建自己的类来实现该接口,并且从未知道您的类或其功能的代码仍然可以按照IEnumerable定义的方式与其交互(在此情况下是一种迭代的方式)。

因此,当函数声明具有接口类型的参数时,它不是在查找任何一个特定的类,而是在查找实现该接口的任何类。

然后下一步是找出哪些类型实现该接口。以下是我用于查找这些类型的PowerShell代码:

[System.AppDomain]::CurrentDomain.GetAssemblies().GetTypes() | Where-Object { 
    [System.Management.Automation.Language.IScriptExtent].IsAssignableFrom($_) 
}

从这里,我们可以看到以下内容:
IsPublic IsSerial Name                                     BaseType                                                    
-------- -------- ----                                     --------                                                    
True     False    IScriptExtent                                                                                        
False    False    InternalScriptExtent                     System.Object                                               
False    False    EmptyScriptExtent                        System.Object                                               
True     False    ScriptExtent                             System.Object                                               

第一个清单是接口本身。而其他三个中的两个不是公开的,因此只剩下 ScriptExtent

您可以使用 New-Object 创建其中之一,但需要提供起始和结束位置作为 [ScriptPosition] 对象。如果没有看到您的代码的更多内容,我不完全确定这些应该是什么。


我知道什么是接口,但我的问题是在PowerShell中创建新代码时无法完全弄清楚extents的工作方式(有很多人修改代码并重用extents的示例,但我找不到任何创建新代码的示例)。 - chrischu
1
@chrischu,从你的问题中并不清楚你是否熟悉接口,因为你问如何创建一个新的IScriptExtent,所以我认为最好保险起见解释一下,特别是对于那些发现你的问题但不知道什么是接口的其他访问者可能会有帮助。你也可以考虑在你的问题中包含你已经尝试过什么。 - briantist

3
脚本范围主要用于错误报告,但也用于调试(例如设置行断点)。一般来说,合成脚本的选项(如您的示例)有:重用现有ast,可能接近/相关于您要添加的ast;使用空ast(基本上创建ScriptExtent和ScriptPosition的实例,没有文件,空行);创建有助于调试的合成范围,可能具有一些特殊内容。在您的示例中,以上任何一种都适用。第二个选项最简单。第三个选项只是第二个选项的变体,但您需要将内容设置为一些有用的东西,例如:
<#Generated: [Parameter(Mandatory)] #>

有趣。但我遇到的一个问题是,在修改后的AST上调用ToString()会返回旧代码,我的想法是这是因为旧范围的原因。是否有另一种好的方法将AST转换回源代码,而不依赖于正确的范围? - chrischu
并不容易 - 根据AST实现一个漂亮的打印机是完全合理的事情,但我不知道除了我很久以前编写的一些示例代码之外还有什么其他的选择,而这些代码实际上并没有很好地进行格式化。 - Jason Shirk
2
“漂亮打印机”是整个问题的关键:我甚至不需要代码变得漂亮,我只需要在修改AST后获得代码,但似乎没有提供“正确”的范围就无法实现这一点。某种程度上,使用字符串修改来修改代码似乎更容易,而不是涉及AST,这将是非常遗憾的事情。 - chrischu
@chrischu,你有找到解决方案吗?我目前也面临同样的困境。 - milquetoastable
@milquetoastable 不好意思,我没有。 - chrischu
@chrischu 我现在不确定它有什么用处,但我被指向了https://code.msdn.microsoft.com/Script-Line-Profiler-Sample-80380291 - 这是PowerShell v3 SDK的一部分,在每个语句之前插入回调。 - milquetoastable

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接