Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,31 @@ private static Token InterstingTokenBeforeCursorOrDefault(IReadOnlyList<Token> t
return null;
}

private static Token GetTokenBeforeCursor(IReadOnlyList<Token> tokens, IScriptPosition cursorPosition, out Token tokenAtCursor)
{
Token tokenBeforeCursor = null;
tokenAtCursor = null;
for (int i = tokens.Count - 1; i >= 0; i--)
{
if (tokens[i].Kind == TokenKind.LineContinuation)
{
continue;
}

if (IsCursorAfterExtent(cursorPosition, tokens[i].Extent))
{
tokenBeforeCursor = tokens[i];
break;
}
else if (IsCursorWithinOrJustAfterExtent(cursorPosition, tokens[i].Extent))
{
tokenAtCursor = tokens[i];
}
}

return tokenBeforeCursor;
}

private static Ast GetLastAstAtCursor(ScriptBlockAst scriptBlockAst, IScriptPosition cursorPosition)
{
var asts = AstSearcher.FindAll(scriptBlockAst, ast => IsCursorRightAfterExtent(cursorPosition, ast.Extent), searchNestedScriptBlocks: true);
Expand Down Expand Up @@ -423,10 +448,20 @@ internal List<CompletionResult> GetResultHelper(CompletionContext completionCont
case TokenKind.Identifier:
if (!tokenAtCursor.TokenFlags.HasFlag(TokenFlags.TypeName))
{
result = CompleteUsingKeywords(completionContext.CursorPosition.Offset, _tokens, ref replacementIndex, ref replacementLength);
if (result is not null)
var tokenBeforeCursor = GetTokenBeforeCursor(_tokens, _cursorPosition, out _);
if (tokenBeforeCursor is not null)
{
return result;
if (tokenBeforeCursor.Kind == TokenKind.Namespace && tokenAtCursor is StringToken stringToken)
{
completionContext.WordToComplete = stringToken.Value;
return CompletionCompleters.CompleteNamespace(completionContext);
}

result = CompleteUsingKeywords(completionContext.CursorPosition.Offset, tokenBeforeCursor, tokenAtCursor, ref replacementIndex, ref replacementLength);
if (result is not null)
{
return result;
}
}

result = GetResultForIdentifier(completionContext, ref replacementIndex, ref replacementLength, isQuotedString);
Expand Down Expand Up @@ -780,7 +815,8 @@ internal List<CompletionResult> GetResultHelper(CompletionContext completionCont
}
break;
default:
result = CompleteUsingKeywords(completionContext.CursorPosition.Offset, _tokens, ref replacementIndex, ref replacementLength);
var tokenBeforeCursor2 = GetTokenBeforeCursor(_tokens, _cursorPosition, out var tokenAtCursor2);
result = CompleteUsingKeywords(completionContext.CursorPosition.Offset, tokenBeforeCursor2, tokenAtCursor2, ref replacementIndex, ref replacementLength);
if (result is not null)
{
return result;
Expand Down Expand Up @@ -1023,7 +1059,7 @@ internal List<CompletionResult> GetResultHelper(CompletionContext completionCont
}

case TokenKind.Using:
return CompleteUsingKeywords(completionContext.CursorPosition.Offset, _tokens, ref replacementIndex, ref replacementLength);
return CompleteUsingKeywords(completionContext.CursorPosition.Offset, tokenBeforeCursor, tokenAtCursor, ref replacementIndex, ref replacementLength);

case TokenKind.Dot:
case TokenKind.ColonColon:
Expand Down Expand Up @@ -2528,28 +2564,18 @@ private static List<CompletionResult> CompleteLoopLabel(CompletionContext comple

return result;
}

private static List<CompletionResult> CompleteUsingKeywords(int cursorOffset, Token[] tokens, ref int replacementIndex, ref int replacementLength)
{
var result = new List<CompletionResult>();
Token tokenBeforeCursor = null;
Token tokenAtCursor = null;

for (int i = tokens.Length - 1; i >= 0; i--)
{
if (tokens[i].Extent.EndOffset < cursorOffset && tokens[i].Kind != TokenKind.LineContinuation)
{
tokenBeforeCursor = tokens[i];
break;
}
else if (tokens[i].Extent.StartOffset <= cursorOffset && tokens[i].Extent.EndOffset >= cursorOffset && tokens[i].Kind != TokenKind.LineContinuation)
{
tokenAtCursor = tokens[i];
}
}

if (tokenBeforeCursor is not null && tokenBeforeCursor.Kind == TokenKind.Using)
private static List<CompletionResult> CompleteUsingKeywords(
int cursorOffset,
Token tokenBeforeCursor,
Token tokenAtCursor,
ref int replacementIndex,
ref int replacementLength)
{
if ((tokenBeforeCursor is not null && tokenBeforeCursor.Kind == TokenKind.Using)
|| (tokenAtCursor is not null && tokenAtCursor.Kind is TokenKind.Assembly or TokenKind.Module or TokenKind.Namespace or TokenKind.Type))
{
var result = new List<CompletionResult>();
string wordToComplete = null;
if (tokenAtCursor is not null)
{
Expand All @@ -2570,11 +2596,11 @@ private static List<CompletionResult> CompleteUsingKeywords(int cursorOffset, To
result.Add(new CompletionResult(keyword, keyword, CompletionResultType.Keyword, GetUsingKeywordToolTip(keyword)));
}
}
}

if (result.Count > 0)
{
return result;
if (result.Count > 0)
{
return result;
}
}

return null;
Expand Down
15 changes: 15 additions & 0 deletions src/System.Management.Automation/engine/parser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.Management.Automation.Subsystem;
using System.Management.Automation.Subsystem.DSC;
using Dsc = Microsoft.PowerShell.DesiredStateConfiguration.Internal;
using System.Text.RegularExpressions;

namespace System.Management.Automation.Language
{
Expand Down Expand Up @@ -4944,6 +4945,20 @@ private StatementAst UsingStatementRule(Token usingToken)
return new ErrorStatementAst(ExtentOf(usingToken, itemToken.Extent));
}

// Validates that it's a valid namespace/type identifier, see:
// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/namespaces#133-namespace-declarations
// https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/identifier-names#naming-rules
if (kind is UsingStatementKind.Namespace or UsingStatementKind.Type
&& itemAst is StringConstantExpressionAst stringAst
&& !Regex.IsMatch(stringAst.Value, @"^([\p{L}_][\p{L}\p{Pc}\p{Nd}\p{Cf}\p{M}]*)(\.[\p{L}_][\p{L}\p{Pc}\p{Nd}\p{Cf}\p{M}]*)*$"))
{
ReportError(ExtentFromFirstOf(stringAst, itemToken),
nameof(ParserStrings.InvalidValueForUsingItemName),
ParserStrings.InvalidValueForUsingItemName,
stringAst.Value);
return new ErrorStatementAst(ExtentOf(usingToken, ExtentFromFirstOf(stringAst, itemToken)));
}

if (itemAst is not StringConstantExpressionAst
&& (kind != UsingStatementKind.Module || itemAst is not HashtableAst))
{
Expand Down
4 changes: 4 additions & 0 deletions test/powershell/Language/Parser/UsingNamespace.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ Describe "Using Namespace" -Tags "CI" {

ShouldBeParseError "1; using namespace System" UsingMustBeAtStartOfScript 3
ShouldBeParseError "using namespace Foo = System" UsingStatementNotSupported 0
ShouldBeParseError "using namespace ''" InvalidValueForUsingItemName 16
ShouldBeParseError "using namespace [System]" InvalidValueForUsingItemName 16
ShouldBeParseError "using namespace ',System'" InvalidValueForUsingItemName 16
ShouldBeParseError "using namespace ]System" InvalidValueForUsingItemName 16
# TODO: add diagnostic (low pri)
# ShouldBeParseError "using namespace System; using namespace System" UsingNamespaceAlreadySpecified 24
}
Expand Down