diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateUnraveler.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateUnraveler.cs index bcf8f97b95f..c4d3ed2f8e9 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateUnraveler.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateUnraveler.cs @@ -894,14 +894,31 @@ private void StartIfInsertion() var parentSequenceState = expressionState.Parent as SequenceState; bool skip = false; bool metastate = false; - if(expressionState.Value.Type == TokenType.IfExpression || (parentMappingState != null && parentMappingState.IfExpressionResults.TryGetValue(parentMappingState.Index - 1, out skip) || parentSequenceState != null && parentSequenceState.IfExpressionResults.TryGetValue(parentSequenceState.Index - 1, out skip)) && skip) { + Func shouldRun = () => { + if(parentMappingState != null) { + if(parentMappingState.IfExpressionResults.TryGetValue(parentMappingState.Index - 1, out skip)) { + return skip; + } + m_context.Error(expressionState.Value, $"This {expressionState.Value.ToString()} must be chained after an if token in the parent mapping"); + } else if(parentSequenceState != null) { + if(parentSequenceState.IfExpressionResults.TryGetValue(parentSequenceState.Index - 1, out skip)) { + return skip; + } + m_context.Error(expressionState.Value, $"This {expressionState.Value.ToString()} must be chained after an if token in the parent sequence"); + } + return false; + }; + if(expressionState.Value.Type == TokenType.IfExpression || shouldRun()) { metastate = skip = expressionState.Value.Type == TokenType.ElseExpression ? false : !PipelineTemplateConverter.ConvertToIfResult(m_context, new BasicExpressionToken(expressionState.Value.FileId, expressionState.Value.Line, expressionState.Value.Column, expressionState.Value.Condition).EvaluateTemplateToken(m_context, out _)); } else { skip = true; metastate = false; } if(parentSequenceState == null) { - parentMappingState.IfExpressionResults[parentMappingState.Index] = metastate; + if(expressionState.Value.Type != TokenType.ElseExpression) { + // Don't set this for ${{ else }}: tokens, because an else token is the last in the sequence + parentMappingState.IfExpressionResults[parentMappingState.Index] = metastate; + } var nestedValue = parentMappingState.Value[parentMappingState.Index].Value; var nestedMapping = nestedValue as MappingToken; var removeBytes = 0; @@ -948,7 +965,10 @@ private void StartIfInsertion() expressionState.End(); } } else { - parentSequenceState.IfExpressionResults[parentSequenceState.Index] = metastate; + if(expressionState.Value.Type != TokenType.ElseExpression) { + // Don't set this for ${{ else }}: tokens, because an else token is the last in the sequence + parentSequenceState.IfExpressionResults[parentSequenceState.Index] = metastate; + } var nestedValue = (parentSequenceState.Value[parentSequenceState.Index] as MappingToken)[0].Value; var nestedSequence = nestedValue as SequenceToken; var removeBytes = 0; diff --git a/testworkflows/azpipelines/else-after-else-error/pipeline.yml b/testworkflows/azpipelines/else-after-else-error/pipeline.yml new file mode 100644 index 00000000000..65f1e820223 --- /dev/null +++ b/testworkflows/azpipelines/else-after-else-error/pipeline.yml @@ -0,0 +1,9 @@ +# ExpectedException: TemplateValidationException +# ExpectedErrorMessage: else +steps: +- ${{ if true }}: + - ${{ if false }}: + - ${{ else }}: + - ${{ else }}: +- ${{ else }}: + - script: noop \ No newline at end of file diff --git a/testworkflows/azpipelines/else-with-colon-exec-error/pipeline.yml b/testworkflows/azpipelines/else-with-colon-exec-error/pipeline.yml new file mode 100644 index 00000000000..8873d365785 --- /dev/null +++ b/testworkflows/azpipelines/else-with-colon-exec-error/pipeline.yml @@ -0,0 +1,8 @@ +# ExpectedException: TemplateValidationException +# ExpectedErrorMessage: else +steps: +- ${{ if true }}: + # This else token is an error if not skipped by if + - ${{ else }}: +- ${{ else }}: + - script: noop \ No newline at end of file diff --git a/testworkflows/azpipelines/else-with-colon-no-error/pipeline.yml b/testworkflows/azpipelines/else-with-colon-no-error/pipeline.yml new file mode 100644 index 00000000000..b33226a513e --- /dev/null +++ b/testworkflows/azpipelines/else-with-colon-no-error/pipeline.yml @@ -0,0 +1,6 @@ +steps: +- ${{ if false }}: + # This else token is not an error if skipped by if + - ${{ else }}: +- ${{ else }}: + - script: noop \ No newline at end of file diff --git a/testworkflows/azpipelines/else-without-colon-error/pipeline.yml b/testworkflows/azpipelines/else-without-colon-error/pipeline.yml new file mode 100644 index 00000000000..24658478626 --- /dev/null +++ b/testworkflows/azpipelines/else-without-colon-error/pipeline.yml @@ -0,0 +1,7 @@ +# ExpectedException: TemplateValidationException +# ExpectedErrorMessage: else +steps: +- ${{ if false }}: + - ${{ else }} +- ${{ else }}: + - script: noop \ No newline at end of file