Skip to content

Commit f6d123d

Browse files
committed
Merge upstream/main into main - resolve Strings.resx conflict
2 parents 33fad88 + 67ffb24 commit f6d123d

23 files changed

+1267
-134
lines changed

.github/CODEOWNERS

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Default owners
2-
* @andyleejordan @bergmeister
2+
* @PowerShell/extension @bergmeister
33

44
# Version bumps and documentation updates
55
Directory.Build.props @sdwheeler @michaeltlombardi
6-
/docs/ @sdwheeler @michaeltlombardi
6+
/docs/ @PowerShell/extension @sdwheeler @michaeltlombardi

Directory.Packages.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
<PackageVersion Include="Microsoft.PowerShell.5.ReferenceAssemblies" Version="1.1.0" />
77
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
88
<!-- The version of Newtonsoft.Json needs to be newer than the version in the oldest supported version of PowerShell 7: https://github.com/PowerShell/PowerShell/blob/v7.2.17/src/System.Management.Automation/System.Management.Automation.csproj#L15 -->
9-
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
9+
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
1010
<PackageVersion Include="Pluralize.NET" Version="1.0.2" />
1111
<PackageVersion Include="PowerShellStandard.Library" Version="5.1.1" />
1212
<!-- Please update minimumPowerShellCoreVersion in PSScriptAnalyzer.psm1 when updating below SMA version for better user-facing error message -->
13-
<PackageVersion Include="System.Management.Automation" Version="7.4.7" />
13+
<PackageVersion Include="System.Management.Automation" Version="7.4.13" />
1414
<PackageVersion Include="System.Reflection.TypeExtensions" Version="4.7.0" />
1515
</ItemGroup>
1616
</Project>

Engine/CommandInfoCache.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,23 +80,43 @@ public CommandInfo GetCommandInfo(string commandName, CommandTypes? commandTypes
8080
/// <returns>Returns null if command does not exists</returns>
8181
private CommandInfo GetCommandInfoInternal(string cmdName, CommandTypes? commandType)
8282
{
83+
string moduleName = null;
84+
string actualCmdName = cmdName;
85+
86+
// Check if cmdName is in the format "moduleName\CmdletName" (exactly one backslash)
87+
int backslashIndex = cmdName.IndexOf('\\');
88+
if (
89+
backslashIndex > 0 &&
90+
backslashIndex == cmdName.LastIndexOf('\\') &&
91+
backslashIndex != cmdName.Length - 1 &&
92+
backslashIndex != 0
93+
)
94+
{
95+
moduleName = cmdName.Substring(0, backslashIndex);
96+
actualCmdName = cmdName.Substring(backslashIndex + 1);
97+
}
8398
// 'Get-Command ?' would return % for example due to PowerShell interpreting is a single-character-wildcard search and not just the ? alias.
8499
// For more details see https://github.com/PowerShell/PowerShell/issues/9308
85-
cmdName = WildcardPattern.Escape(cmdName);
100+
actualCmdName = WildcardPattern.Escape(actualCmdName);
86101

87102
using (var ps = System.Management.Automation.PowerShell.Create())
88103
{
89104
ps.RunspacePool = _runspacePool;
90105

91106
ps.AddCommand("Get-Command")
92-
.AddParameter("Name", cmdName)
107+
.AddParameter("Name", actualCmdName)
93108
.AddParameter("ErrorAction", "SilentlyContinue");
94109

95110
if (commandType != null)
96111
{
97112
ps.AddParameter("CommandType", commandType);
98113
}
99114

115+
if (!string.IsNullOrEmpty(moduleName))
116+
{
117+
ps.AddParameter("Module", moduleName);
118+
}
119+
100120
return ps.Invoke<CommandInfo>()
101121
.FirstOrDefault();
102122
}

Engine/Generic/RuleSuppression.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,12 @@ public RuleSuppression(AttributeAst attrAst, int start, int end)
193193
}
194194
else if (argumentName.Equals("rulesuppressionid", StringComparison.OrdinalIgnoreCase))
195195
{
196-
if (!String.IsNullOrWhiteSpace(RuleName))
196+
if (!String.IsNullOrWhiteSpace(RuleSuppressionID))
197197
{
198198
Error = String.Format(Strings.NamedAndPositionalArgumentsConflictError, name);
199199
}
200200

201-
RuleName = (name.Argument as StringConstantExpressionAst).Value;
201+
RuleSuppressionID = (name.Argument as StringConstantExpressionAst).Value;
202202
}
203203
else if (argumentName.Equals("scope", StringComparison.OrdinalIgnoreCase))
204204
{
@@ -333,12 +333,12 @@ public static List<RuleSuppression> GetSuppressions(IEnumerable<AttributeAst> at
333333
{
334334
targetAsts = scopeAst.FindAll(ast => ast is FunctionDefinitionAst && reg.IsMatch((ast as FunctionDefinitionAst).Name), true);
335335
}
336-
#if !(PSV3 || PSV4)
336+
#if !(PSV3 || PSV4)
337337
else if (scope.Equals("class", StringComparison.OrdinalIgnoreCase))
338338
{
339339
targetAsts = scopeAst.FindAll(ast => ast is TypeDefinitionAst && reg.IsMatch((ast as TypeDefinitionAst).Name), true);
340340
}
341-
#endif
341+
#endif
342342

343343
if (targetAsts != null)
344344
{

Engine/Helper.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -761,8 +761,15 @@ public IScriptExtent GetScriptExtentForFunctionName(FunctionDefinitionAst functi
761761
token =>
762762
ContainsExtent(functionDefinitionAst.Extent, token.Extent)
763763
&& token.Text.Equals(functionDefinitionAst.Name));
764-
var funcNameToken = funcNameTokens.FirstOrDefault();
765-
return funcNameToken == null ? null : funcNameToken.Extent;
764+
765+
// If the functions name is 'function' then the first token in the
766+
// list is the function keyword itself, so we need to skip it
767+
if (functionDefinitionAst.Name.Equals("function", StringComparison.OrdinalIgnoreCase))
768+
{
769+
var funcNameToken = funcNameTokens.Skip(1).FirstOrDefault() ?? funcNameTokens.FirstOrDefault();
770+
return funcNameToken?.Extent;
771+
}
772+
return funcNameTokens.FirstOrDefault()?.Extent;
766773
}
767774

768775
/// <summary>

Engine/PSScriptAnalyzer.psm1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ $PSModuleRoot = $PSModule.ModuleBase
99

1010
# Import the appropriate nested binary module based on the current PowerShell version
1111
$binaryModuleRoot = $PSModuleRoot
12-
[Version] $minimumPowerShellCoreVersion = '7.4.7'
12+
[Version] $minimumPowerShellCoreVersion = '7.4.13'
1313
if ($PSVersionTable.PSVersion.Major -ge 6) {
1414
$binaryModuleRoot = Join-Path -Path $PSModuleRoot -ChildPath "PSv$($PSVersionTable.PSVersion.Major)"
1515
# Minimum PowerShell Core version given by PowerShell Core support itself and

Engine/TokenOperations.cs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,5 +245,165 @@ public Ast GetAstPosition(Token token)
245245
return findAstVisitor.AstPosition;
246246
}
247247

248+
/// <summary>
249+
/// Returns a list of non-overlapping ranges (startOffset,endOffset) representing the start
250+
/// and end of braced member access expressions. These are member accesses where the name is
251+
/// enclosed in braces. The contents of such braces are treated literally as a member name.
252+
/// Altering the contents of these braces by formatting is likely to break code.
253+
/// </summary>
254+
public List<Tuple<int, int>> GetBracedMemberAccessRanges()
255+
{
256+
// A list of (startOffset, endOffset) pairs representing the start
257+
// and end braces of braced member access expressions.
258+
var ranges = new List<Tuple<int, int>>();
259+
260+
var node = tokensLL.Value.First;
261+
while (node != null)
262+
{
263+
switch (node.Value.Kind)
264+
{
265+
#if CORECLR
266+
// TokenKind added in PS7
267+
case TokenKind.QuestionDot:
268+
#endif
269+
case TokenKind.Dot:
270+
break;
271+
default:
272+
node = node.Next;
273+
continue;
274+
}
275+
276+
// Note: We don't check if the dot is part of an existing range. When we find
277+
// a valid range, we skip all tokens inside it - so we won't ever evaluate a token
278+
// which already part of a previously found range.
279+
280+
// Backward scan:
281+
// Determine if this 'dot' is part of a member access.
282+
// Walk left over contiguous comment tokens that are 'touching'.
283+
// After skipping comments, the preceding non-comment token must also be 'touching'
284+
// and one of the expected TokenKinds.
285+
var leftToken = node.Previous;
286+
var rightToken = node;
287+
while (leftToken != null && leftToken.Value.Kind == TokenKind.Comment)
288+
{
289+
if (leftToken.Value.Extent.EndOffset != rightToken.Value.Extent.StartOffset)
290+
{
291+
leftToken = null;
292+
break;
293+
}
294+
rightToken = leftToken;
295+
leftToken = leftToken.Previous;
296+
}
297+
if (leftToken == null)
298+
{
299+
// We ran out of tokens before finding a non-comment token to the left or there
300+
// was intervening whitespace.
301+
node = node.Next;
302+
continue;
303+
}
304+
305+
if (leftToken.Value.Extent.EndOffset != rightToken.Value.Extent.StartOffset)
306+
{
307+
// There's whitespace between the two tokens
308+
node = node.Next;
309+
continue;
310+
}
311+
312+
// Limit to valid token kinds that can precede a 'dot' in a member access.
313+
switch (leftToken.Value.Kind)
314+
{
315+
// Note: TokenKind.Number isn't in the list as 5.{Prop} is a syntax error
316+
// (Unexpected token). Numbers also have no properties - only methods.
317+
case TokenKind.Variable:
318+
case TokenKind.Identifier:
319+
case TokenKind.StringLiteral:
320+
case TokenKind.StringExpandable:
321+
case TokenKind.HereStringLiteral:
322+
case TokenKind.HereStringExpandable:
323+
case TokenKind.RParen:
324+
case TokenKind.RCurly:
325+
case TokenKind.RBracket:
326+
// allowed
327+
break;
328+
default:
329+
// not allowed
330+
node = node.Next;
331+
continue;
332+
}
333+
334+
// Forward Scan:
335+
// Check that the next significant token is an LCurly
336+
// Starting from the token after the 'dot', walk right skipping trivia tokens:
337+
// - Comment
338+
// - NewLine
339+
// - LineContinuation (`)
340+
// These may be multi-line and need not be 'touching' the dot.
341+
// The first non-trivia token encountered must be an opening curly brace (LCurly) for
342+
// this dot to begin a braced member access. If it is not LCurly or we run out
343+
// of tokens, this dot is ignored.
344+
var scan = node.Next;
345+
while (scan != null)
346+
{
347+
if (
348+
scan.Value.Kind == TokenKind.Comment ||
349+
scan.Value.Kind == TokenKind.NewLine ||
350+
scan.Value.Kind == TokenKind.LineContinuation
351+
)
352+
{
353+
scan = scan.Next;
354+
continue;
355+
}
356+
break;
357+
}
358+
359+
// If we reached the end without finding a significant token, or if the found token
360+
// is not LCurly, continue.
361+
if (scan == null || scan.Value.Kind != TokenKind.LCurly)
362+
{
363+
node = node.Next;
364+
continue;
365+
}
366+
367+
// We have a valid token, followed by a dot, followed by an LCurly.
368+
// Find the matching RCurly and create the range.
369+
var lCurlyNode = scan;
370+
371+
// Depth count braces to find the RCurly which closes the LCurly.
372+
int depth = 0;
373+
LinkedListNode<Token> rcurlyNode = null;
374+
while (scan != null)
375+
{
376+
if (scan.Value.Kind == TokenKind.LCurly) depth++;
377+
else if (scan.Value.Kind == TokenKind.RCurly)
378+
{
379+
depth--;
380+
if (depth == 0)
381+
{
382+
rcurlyNode = scan;
383+
break;
384+
}
385+
}
386+
scan = scan.Next;
387+
}
388+
389+
// If we didn't find a matching RCurly, something has gone wrong.
390+
// Should an unmatched pair be caught by the parser as a parse error?
391+
if (rcurlyNode == null)
392+
{
393+
node = node.Next;
394+
continue;
395+
}
396+
397+
ranges.Add(new Tuple<int, int>(
398+
lCurlyNode.Value.Extent.StartOffset,
399+
rcurlyNode.Value.Extent.EndOffset
400+
));
401+
402+
// Skip all tokens inside the excluded range.
403+
node = rcurlyNode.Next;
404+
}
405+
406+
return ranges;
407+
}
248408
}
249409
}

0 commit comments

Comments
 (0)