powershellabstract-syntax-treeextent

PowerShell AST Modification and Extents


I am currently trying to use the AST functionality introduced in PowerShell 3.0 to modify a ScriptBlock. My requirement is that all the parameters in the parameter block of the ScriptBlock get a [Parameter(Mandatory)] attribute.

Basically the code should modify this:

Param([string]$x)

Write-Host $x

to this:

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

Write-Host $x

However, I ran into a problem when adding that new attribute, since it expects an IScriptExtent and I am not sure how I should create a new IScriptExtent.

How can I create a new script extent? What values can I use for the position? Do I have to change the position of all following extents?

I tried just reusing the extent of each parameter I am modifying, but unfortunately this does not seem to yield the results it should (e.g. when I am calling ToString on the modified ScriptBlock I don't see any changes).

My implementation so far is based on the ICustomAstVisitor found here.

The most important method looks like this:

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);
}

Solution

  • The script extent is used primarily for error reporting, but is also used for debugging (for example, setting a line breakpoint.)

    In general, the options for synthesized script (like your example) are:

    In your example, any of the above are suitable. The second option is the simplest. The third option is just a variant of the second, but you would set the content to something useful, e.g.

    <#Generated: [Parameter(Mandatory)] #>