diff --git a/src/System.Management.Automation/engine/parser/SemanticChecks.cs b/src/System.Management.Automation/engine/parser/SemanticChecks.cs index b108e6330b..20d0a552cf 100644 --- a/src/System.Management.Automation/engine/parser/SemanticChecks.cs +++ b/src/System.Management.Automation/engine/parser/SemanticChecks.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; +using System.Text.RegularExpressions; using Microsoft.PowerShell; using System.Management.Automation.Security; @@ -17,7 +18,7 @@ using Microsoft.PowerShell.DesiredStateConfiguration.Internal; namespace System.Management.Automation.Language { - internal sealed class SemanticChecks : AstVisitor2, IAstPostVisitHandler + internal sealed partial class SemanticChecks : AstVisitor2, IAstPostVisitHandler { private readonly Parser _parser; @@ -1319,20 +1320,50 @@ namespace System.Management.Automation.Language public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatementAst) { - bool usingKindSupported = usingStatementAst.UsingStatementKind == UsingStatementKind.Namespace || - usingStatementAst.UsingStatementKind == UsingStatementKind.Assembly || - usingStatementAst.UsingStatementKind == UsingStatementKind.Module; - if (!usingKindSupported || - usingStatementAst.Alias != null) + UsingStatementKind kind = usingStatementAst.UsingStatementKind; + bool usingKindSupported = kind is UsingStatementKind.Namespace or UsingStatementKind.Assembly or UsingStatementKind.Module; + if (!usingKindSupported || usingStatementAst.Alias != null) { - _parser.ReportError(usingStatementAst.Extent, + _parser.ReportError( + usingStatementAst.Extent, nameof(ParserStrings.UsingStatementNotSupported), ParserStrings.UsingStatementNotSupported); } + if (kind is UsingStatementKind.Namespace) + { + Regex nsPattern = NamespacePattern(); + if (!nsPattern.IsMatch(usingStatementAst.Name.Value)) + { + _parser.ReportError( + usingStatementAst.Name.Extent, + nameof(ParserStrings.InvalidNamespaceValue), + ParserStrings.InvalidNamespaceValue); + } + } + return AstVisitAction.Continue; } + /// + /// This regular expression is for validating if a namespace string is valid. + /// + /// In C#, a legit namespace is defined as `identifier ('.' identifier)*` [see https://learn.microsoft.com/dotnet/csharp/language-reference/language-specification/namespaces#143-namespace-declarations]. + /// And `identifier` is defined in https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/identifier-names#naming-rules, summarized below: + /// - Identifiers must start with a letter or underscore (_). + /// - Identifiers can contain + /// * Unicode letter characters (categories: Lu, Ll, Lt, Lm, Lo or Nl); + /// * decimal digit characters (category: Nd); + /// * Unicode connecting characters (category: Pc); + /// * Unicode combining characters (categories: Mn, Mc); + /// * Unicode formatting characters (category: Cf). + /// + /// For details about how Unicode categories are represented in regular expression, see the "Unicode Categories" section in the following article: + /// - https://www.regular-expressions.info/unicode.html + /// + [GeneratedRegex(@"^[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}\p{Pc}\p{Mn}\p{Mc}\p{Cf}_]*(?:\.[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}\p{Pc}\p{Mn}\p{Mc}\p{Cf}_]*)*$")] + private static partial Regex NamespacePattern(); + public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { // diff --git a/src/System.Management.Automation/resources/ParserStrings.resx b/src/System.Management.Automation/resources/ParserStrings.resx index a45a5165b0..aaf7d75860 100644 --- a/src/System.Management.Automation/resources/ParserStrings.resx +++ b/src/System.Management.Automation/resources/ParserStrings.resx @@ -1229,6 +1229,9 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent This syntax of the 'using' statement is not supported. + + The specified namespace in the 'using' statement contains invalid characters. + information stream diff --git a/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 b/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 index 58d9b8738c..0370a8c5e1 100644 --- a/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 +++ b/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 @@ -128,7 +128,10 @@ Describe "Using Namespace" -Tags "CI" { ShouldBeParseError "1; using namespace System" UsingMustBeAtStartOfScript 3 ShouldBeParseError "using namespace Foo = System" UsingStatementNotSupported 0 + ShouldBeParseError "using namespace ''" InvalidNamespaceValue 16 + ShouldBeParseError "using namespace [System]" InvalidNamespaceValue 16 + ShouldBeParseError "using namespace ',System'" InvalidNamespaceValue 16 + ShouldBeParseError "using namespace ']System'" InvalidNamespaceValue 16 # TODO: add diagnostic (low pri) # ShouldBeParseError "using namespace System; using namespace System" UsingNamespaceAlreadySpecified 24 } -