-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Fix globbing for native commands #4489
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,12 +15,14 @@ internal sealed class CommandParameterInternal | |
| { | ||
| private class Parameter | ||
| { | ||
| internal Ast ast; | ||
| internal IScriptExtent extent; | ||
| internal string parameterName; | ||
| internal string parameterText; | ||
| } | ||
| private class Argument | ||
| { | ||
| internal Ast ast; | ||
| internal IScriptExtent extent; | ||
|
||
| internal object value; | ||
| internal bool splatted; | ||
|
|
@@ -73,6 +75,10 @@ internal IScriptExtent ParameterExtent | |
| get { return _parameter != null ? _parameter.extent : PositionUtilities.EmptyExtent; } | ||
| } | ||
|
|
||
| internal Ast ParameterAst { | ||
| get { return _parameter != null ? _parameter.ast : null; } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// The extent of the optional argument, if one was specified. | ||
| /// </summary> | ||
|
|
@@ -81,6 +87,11 @@ internal IScriptExtent ArgumentExtent | |
| get { return _argument != null ? _argument.extent : PositionUtilities.EmptyExtent; } | ||
| } | ||
|
|
||
| internal Ast ArgumentAst | ||
| { | ||
| get { return _argument != null ? _argument.ast : null; } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// The value of the optional argument, if one was specified, otherwise UnboundParameter.Value. | ||
| /// </summary> | ||
|
|
@@ -141,30 +152,56 @@ internal IScriptExtent ErrorExtent | |
| /// <summary> | ||
| /// Create a parameter when no argument has been specified. | ||
| /// </summary> | ||
| /// <param name="ast">The ast for this parameter</param> | ||
| /// <param name="extent">The extent in script of the parameter.</param> | ||
| /// <param name="parameterName">The parameter name (with no leading dash).</param> | ||
| /// <param name="parameterText">The text of the parameter, as it did, or would, appear in script.</param> | ||
| internal static CommandParameterInternal CreateParameter( | ||
| internal static CommandParameterInternal CreateParameterWithAst( | ||
| Ast ast, | ||
| IScriptExtent extent, | ||
| string parameterName, | ||
| string parameterText) | ||
| { | ||
| Diagnostics.Assert(extent != null, "Caller to verify extent argument"); | ||
| return new CommandParameterInternal | ||
| { | ||
| _parameter = | ||
| new Parameter { extent = extent, parameterName = parameterName, parameterText = parameterText } | ||
| _parameter = new Parameter | ||
| { | ||
| ast = ast, | ||
| extent = extent, | ||
| parameterName = parameterName, | ||
| parameterText = parameterText | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| internal static CommandParameterInternal CreateParameter( | ||
| IScriptExtent extent, | ||
| string parameterName, | ||
| string parameterText) | ||
| { | ||
| return CreateParameterWithAst(null, extent, parameterName, parameterText); | ||
| } | ||
|
|
||
| internal static CommandParameterInternal CreateParameterAstOnly( | ||
| Ast ast, | ||
| string parameterName, | ||
| string parameterText | ||
| ) | ||
| { | ||
| return CreateParameterWithAst(ast, ast.Extent, parameterName, parameterText); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Create a positional argument to a command. | ||
| /// </summary> | ||
| /// <param name="ast">The ast for this argument</param> | ||
| /// <param name="extent">The extent of the argument value in the script.</param> | ||
| /// <param name="value">The argument value.</param> | ||
| /// <param name="splatted">True if the argument value is to be splatted, false otherwise.</param> | ||
| /// <param name="arrayIsSingleArgumentForNativeCommand">If the command is native, pass the string with commas instead of multiple arguments</param> | ||
| internal static CommandParameterInternal CreateArgument( | ||
| internal static CommandParameterInternal CreateArgumentWithAst( | ||
| Ast ast, | ||
| IScriptExtent extent, | ||
| object value, | ||
| bool splatted = false, | ||
|
|
@@ -175,6 +212,7 @@ internal static CommandParameterInternal CreateArgument( | |
| { | ||
| _argument = new Argument | ||
| { | ||
| ast = ast, | ||
| extent = extent, | ||
| value = value, | ||
| splatted = splatted, | ||
|
|
@@ -183,6 +221,24 @@ internal static CommandParameterInternal CreateArgument( | |
| }; | ||
| } | ||
|
|
||
| internal static CommandParameterInternal CreateArgument( | ||
| IScriptExtent extent, | ||
| object value, | ||
| bool splatted = false, | ||
| bool arrayIsSingleArgumentForNativeCommand = false) | ||
| { | ||
| return CreateArgumentWithAst(null, extent, value, splatted, arrayIsSingleArgumentForNativeCommand); | ||
| } | ||
|
|
||
| internal static CommandParameterInternal CreateArgumentAstOnly( | ||
| Ast ast, | ||
| object value, | ||
| bool splatted = false, | ||
| bool arrayIsSingleArgumentForNativeCommand = false) | ||
| { | ||
| return CreateArgumentWithAst(ast, ast.Extent, value, splatted, arrayIsSingleArgumentForNativeCommand); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Create an named argument, where the parameter name is known. This can happen when: | ||
| /// * The user uses the ':' syntax, as in | ||
|
|
@@ -193,14 +249,16 @@ internal static CommandParameterInternal CreateArgument( | |
| /// * In the parameter binder when it resolves a positional argument | ||
| /// * Other random places that manually construct command processors and know their arguments. | ||
| /// </summary> | ||
| /// <param name="ast">The ast for this parameter/argument pair</param> | ||
| /// <param name="parameterExtent">The extent in script of the parameter.</param> | ||
| /// <param name="parameterName">The parameter name (with no leading dash).</param> | ||
| /// <param name="parameterText">The text of the parameter, as it did, or would, appear in script.</param> | ||
| /// <param name="argumentExtent">The extent of the argument value in the script.</param> | ||
| /// <param name="value">The argument value.</param> | ||
| /// <param name="spaceAfterParameter">Used in native commands to correctly handle -foo:bar vs. -foo: bar</param> | ||
| /// <param name="arrayIsSingleArgumentForNativeCommand">If the command is native, pass the string with commas instead of multiple arguments</param> | ||
| internal static CommandParameterInternal CreateParameterWithArgument( | ||
| internal static CommandParameterInternal CreateParameterWithArgumentWithAst( | ||
| Ast ast, | ||
| IScriptExtent parameterExtent, | ||
| string parameterName, | ||
| string parameterText, | ||
|
|
@@ -213,13 +271,51 @@ internal static CommandParameterInternal CreateParameterWithArgument( | |
| Diagnostics.Assert(argumentExtent != null, "Caller to verify argumentExtent argument"); | ||
| return new CommandParameterInternal | ||
| { | ||
| _parameter = new Parameter { extent = parameterExtent, parameterName = parameterName, parameterText = parameterText }, | ||
| _argument = new Argument { extent = argumentExtent, value = value, arrayIsSingleArgumentForNativeCommand = arrayIsSingleArgumentForNativeCommand }, | ||
| _parameter = new Parameter | ||
| { | ||
| ast = ast, | ||
| extent = parameterExtent, | ||
| parameterName = parameterName, | ||
| parameterText = parameterText | ||
| }, | ||
| _argument = new Argument | ||
| { | ||
| ast = ast != null && ast is CommandParameterAst ? ((CommandParameterAst)ast).Argument : null, | ||
| extent = argumentExtent, | ||
| value = value, | ||
| arrayIsSingleArgumentForNativeCommand = arrayIsSingleArgumentForNativeCommand | ||
| }, | ||
| _spaceAfterParameter = spaceAfterParameter | ||
| }; | ||
| } | ||
|
|
||
| #endregion ctor | ||
| internal static CommandParameterInternal CreateParameterWithArgument( | ||
| IScriptExtent parameterExtent, | ||
| string parameterName, | ||
| string parameterText, | ||
| IScriptExtent argumentExtent, | ||
| object value, | ||
| bool spaceAfterParameter, | ||
| bool arrayIsSingleArgumentForNativeCommand = false) | ||
| { | ||
| return CreateParameterWithArgumentWithAst(null, parameterExtent, parameterName, parameterText, argumentExtent, value, | ||
| spaceAfterParameter, arrayIsSingleArgumentForNativeCommand); | ||
| } | ||
|
|
||
| internal static CommandParameterInternal CreateParameterWithArgumentAstOnly( | ||
| Ast ast, | ||
| string parameterName, | ||
| string parameterText, | ||
| object value, | ||
| bool spaceAfterParameter, | ||
| bool arrayIsSingleArgumentForNativeCommand = false | ||
| ) | ||
| { | ||
| var pAst = (CommandParameterAst) ast; | ||
| return CreateParameterWithArgumentWithAst(pAst, pAst.Extent, parameterName, parameterText, pAst.Argument.Extent, value, | ||
| spaceAfterParameter, arrayIsSingleArgumentForNativeCommand); | ||
| } | ||
| #endregion ctor | ||
|
|
||
| internal bool IsDashQuestion() | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,12 @@ Describe 'Native UNIX globbing tests' -tags "CI" { | |
| It 'Should return the original pattern if there are no matches' { | ||
| /bin/echo $TESTDRIVE/*.nosuchfile | Should Match "\*\.nosuchfile$" | ||
| } | ||
| It 'Should not expand double-quoted strings' { | ||
| /bin/echo "*" | Should BeExactly "*" | ||
| } | ||
| It 'Should not expand single-quoted strings' { | ||
| /bin/echo '*' | Should BeExactly "*" | ||
| } | ||
|
||
| # Test the behavior in non-filesystem drives | ||
| It 'Should not expand patterns on non-filesystem drives' { | ||
| /bin/echo env:ps* | Should BeExactly "env:ps*" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
astmakes storing theextentno longer necessary, right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean -
extentshould always beast.Extent, so we could remove the field and use a property instead.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This API is used in many places where the AST is not available e.g. the PowerShell API, F&O. In those cases dummy extents are explicitly passed in. So both the Ast and extent members are required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could introduce a dummy ast for the dummy position.
My thinking - if the normal case is a script, we optimize for that case. If the field is typically redundant, we use less memory, generate less code (the push of the extra parameter), and reduce the number of fields gc needs to visit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a dummy AST is overkill. Reworking the API to use dummy extents if the AST is null, etc. is probably the best solution but that's out of scope for this issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding a new parameter will bloat the generated code significantly - this is an extra parameter for every argument on every command invocation. We should avoid that if there are alternatives.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still not happy with storing 2 references a typical script has a ton of these instances.
What if we use something like: