Skip to content

Ps7 syntax #1426

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

Merged
merged 2 commits into from
Mar 31, 2020
Merged
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
217 changes: 162 additions & 55 deletions Rules/CompatibilityRules/UseCompatibleSyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,27 +183,34 @@ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressio
{
// Look for [typename]::new(...) and [typename]::$dynamicMethodName syntax

#if PSV7
if (!TargetsNonPS7())
{
return AstVisitAction.Continue;
}

if (methodCallAst.NullConditional)
{
AddDiagnostic(
methodCallAst,
"null-conditional method invocation",
"${x}?.Method()",
"3,4,5,6");
}
#endif

if (!_targetVersions.Contains(s_v3) && !_targetVersions.Contains(s_v4))
{
return AstVisitAction.Continue;
}

if (_targetVersions.Contains(s_v3) && methodCallAst.Member is VariableExpressionAst)
{
string message = string.Format(
CultureInfo.CurrentCulture,
Strings.UseCompatibleSyntaxError,
AddDiagnostic(
methodCallAst,
"dynamic method invocation",
methodCallAst.Extent.Text,
"$x.$method()",
"3");

_diagnosticAccumulator.Add(new DiagnosticRecord(
message,
methodCallAst.Extent,
_rule.GetName(),
_rule.Severity,
_analyzedFilePath
));
}

if (!(methodCallAst.Expression is TypeExpressionAst typeExpressionAst))
Expand All @@ -226,22 +233,7 @@ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressio
typeName,
methodCallAst.Arguments);

string message = string.Format(
CultureInfo.CurrentCulture,
Strings.UseCompatibleSyntaxError,
"constructor",
methodCallAst.Extent.Text,
"3,4");

_diagnosticAccumulator.Add(new DiagnosticRecord(
message,
methodCallAst.Extent,
_rule.GetName(),
_rule.Severity,
_analyzedFilePath,
ruleId: null,
suggestedCorrections: new [] { suggestedCorrection }
));
AddDiagnostic(methodCallAst, "constructor", "[type]::new()", "3,4", suggestedCorrection);

return AstVisitAction.Continue;
}
Expand All @@ -263,21 +255,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun
return AstVisitAction.Continue;
}

string message = string.Format(
CultureInfo.CurrentCulture,
Strings.UseCompatibleSyntaxError,
"workflow",
"workflow { ... }",
"6");

_diagnosticAccumulator.Add(
new DiagnosticRecord(
message,
functionDefinitionAst.Extent,
_rule.GetName(),
_rule.Severity,
_analyzedFilePath
));
AddDiagnostic(functionDefinitionAst, "workflow", "workflow { ... }", "6,7");

return AstVisitAction.Continue;
}
Expand All @@ -292,22 +270,12 @@ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatem
return AstVisitAction.Continue;
}

string message = string.Format(
CultureInfo.CurrentCulture,
Strings.UseCompatibleSyntaxError,
AddDiagnostic(
usingStatementAst,
"using statement",
"using ...;",
"3,4");

_diagnosticAccumulator.Add(
new DiagnosticRecord(
message,
usingStatementAst.Extent,
_rule.GetName(),
_rule.Severity,
_analyzedFilePath
));

return AstVisitAction.Continue;
}

Expand Down Expand Up @@ -340,6 +308,145 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit
}
#endif

#if PSV7
public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberExpressionAst)
{
if (!TargetsNonPS7())
{
return AstVisitAction.Continue;
}

if (memberExpressionAst.NullConditional)
{
AddDiagnostic(
memberExpressionAst,
"null-conditional member access",
"${x}?.Member",
"3,4,5,6");
}

return AstVisitAction.Continue;
}

public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst)
{
if (!TargetsNonPS7())
{
return AstVisitAction.Continue;
}

if (assignmentStatementAst.Operator == TokenKind.QuestionQuestionEquals)
{
AddDiagnostic(assignmentStatementAst, "null-conditional assignment", "$x ??= $y", "3,4,5,6");
}

return AstVisitAction.Continue;
}

public override AstVisitAction VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst)
{
if (!TargetsNonPS7())
{
return AstVisitAction.Continue;
}

if (binaryExpressionAst.Operator == TokenKind.QuestionQuestion)
{
AddDiagnostic(
binaryExpressionAst,
"null-coalescing operator",
"$x ?? $y",
"3,4,5,6");
}

return AstVisitAction.Continue;
}

public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst)
{
if (!TargetsNonPS7())
{
return AstVisitAction.Continue;
}

var correction = new CorrectionExtent(
ternaryExpressionAst.Extent,
$"if ({ternaryExpressionAst.Condition.Extent.Text}) {{ {ternaryExpressionAst.IfTrue.Extent.Text} }} else {{ {ternaryExpressionAst.IfFalse.Extent.Text} }}",
_analyzedFilePath);

AddDiagnostic(
ternaryExpressionAst,
"ternary expression",
"<test> ? <exp1> : <exp2>",
"3,4,5,6",
correction);

return AstVisitAction.Continue;
}

public override AstVisitAction VisitPipelineChain(PipelineChainAst statementChain)
{
if (!TargetsNonPS7())
{
return AstVisitAction.Continue;
}

AddDiagnostic(
statementChain,
"pipeline chain",
"<pipeline1> && <pipeline2> OR <pipeline1> || <pipeline2>",
"3,4,5,6");

return AstVisitAction.Continue;
}

private bool TargetsNonPS7()
{
return _targetVersions.Contains(s_v3)
|| _targetVersions.Contains(s_v4)
|| _targetVersions.Contains(s_v5)
|| _targetVersions.Contains(s_v6);
}
#endif

private void AddDiagnostic(
Ast offendingAst,
string syntaxName,
string syntaxExample,
string unsupportedVersions,
CorrectionExtent correction = null)
{
string message = string.Format(
CultureInfo.CurrentCulture,
Strings.UseCompatibleSyntaxError,
syntaxName,
syntaxExample,
unsupportedVersions);

if (correction == null)
{
_diagnosticAccumulator.Add(
new DiagnosticRecord(
message,
offendingAst.Extent,
_rule.GetName(),
_rule.Severity,
_analyzedFilePath));

return;
}

_diagnosticAccumulator.Add(
new DiagnosticRecord(
message,
offendingAst.Extent,
_rule.GetName(),
_rule.Severity,
_analyzedFilePath,
ruleId: null,
new[] { correction }));
}

private static CorrectionExtent CreateNewObjectCorrection(
string filePath,
IScriptExtent offendingExtent,
Expand Down
17 changes: 17 additions & 0 deletions Tests/Rules/UseCompatibleSyntax.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ Describe "PSUseCompatibleSyntax" {
@{ Script = 'workflow Banana { Do-ExpensiveCommandOnAnotherMachine -Argument "Banana" }'; Versions = @(6) }
)
}

if ($PSVersionTable.PSVersion.Major -ge 7)
{
$testCases += @(
@{ Script = '$x = $path ? (Get-Content -Raw $path) : "default"'; Versions = @(3,4,5,6) }
@{ Script = '$x ??= 7'; Versions = @(3,4,5,6) }
@{ Script = 'git pull origin master && git pull upstream master'; Versions = @(3,4,5,6) }
)

if ((Get-ExperimentalFeature -Name 'PSNullConditionalOperators').Enabled)
{
$testCases += @(
@{ Script = '${item}?.Invoke()'; Versions = @(3,4,5,6) }
@{ Script = '${object}?.Member'; Versions = @(3,4,5,6) }
)
}
}
}

foreach ($v in 3,4,5,6)
Expand Down