Skip to content

fix(validation): validate inline expressions via the condition AST instead of reporting them as one missing variable - #233

Merged
vaceslav merged 1 commit into
mainfrom
fix/validation-inline-expressions
Sep 27, 2026
Merged

vaceslav merged 1 commit into
mainfrom
fix/validation-inline-expressions

Conversation

@vaceslav

Copy link
Copy Markdown
Contributor

Problem

ValidateTemplate (Word TemplateValidator/ScopedVariableValidator, OpenDocument OdtTemplateValidator, and the TemplateProcessor facade) treated an inline expression placeholder such as {{(A and B)}}, {{(IsActive):yesno}} or {{(Price > 100)}} as a single variable named (A and B):

  • with data, it reported Variable '(A and B)' is referenced in the template but not provided in the data. as a MissingVariable error, and listed (A and B) in MissingVariables, even when every operand was present. So IsValid was false for a correct template.
  • AllPlaceholders contained the expression text ((A and B)) and not the variables it uses.
  • expressions that cannot be parsed ({{(A and)}}, {{(A && B)}}) were not reported. Processing leaves them unreplaced with an ExpressionFailed warning.

Fix

The new Core/InlineExpressionValidation.cs is shared by both validators. It parses inline expressions with the same parser processing uses (ConditionAstCache.InlineExpressions, which also allows single-quoted strings):

  • Syntax: an expression that cannot be parsed is an InvalidConditionalExpression error (Invalid expression '(A and)': ..., location = the placeholder). It is reported with or without data, once per message.
  • AllPlaceholders: gets the variables the expression references, taken from the AST (as for {{#if}} conditions). Operators, literals and keywords are not listed. An expression that cannot be parsed adds nothing. Word and OpenDocument behave the same.
  • Missing variables (with data): a MissingVariable error (Variable 'X' is referenced in expression '(...)' but not provided in the data.) and a MissingVariables entry are reported for each variable that processing reads from the data without a fallback and that cannot be resolved in the loop scope. Item properties, named iteration variables, @ metadata and ./this resolve as in processing. The checked variables are:
    • operands tested for truthiness: the whole expression, and the operands of and/or/not. A missing one evaluates to false.
    • variable operands of in, contains, startswith and endswith. These are resolved strictly, so a missing one is null.
  • Not reported (mirrors processing):
    • comparison operands and list items. The inline dialect uses the shared ResolveOrLiteral, so an unresolved bareword is a string literal ({{(Status = Active)}}, {{(Missing > 100)}}).
    • operands of exists, is empty and is not empty.
  • MissingConditionVariable warning (feat(validation): warn about missing variables used as bare condition operands #231): not added for inline expressions. Their missing truthiness operands are already MissingVariable errors, so the warning would repeat them.

Tests

  • New Integration/ValidationInlineExpressionTests.cs. Every case is validated as Word and as OpenDocument, directly and through the facade, and the results must be identical. Cases:
    • valid expressions (logical, format specifiers, comparisons, nested parentheses, single and double quotes, bareword literals, in/contains/startswith, exists/is empty, literals). For each one the test also processes the template and asserts no missing variables and no warnings, so validation predicts processing.
    • AllPlaceholders content.
    • missing operands (truthiness, nested, dotted, in/string operators, (Missing) = true not reported).
    • no data.
    • unparsable expressions, with and without data. For these the test also processes the template and asserts an ExpressionFailed warning and that the text is left unchanged.
    • duplicates.
    • loops: item properties, named variables, metadata, globals, a missing item property, an item property used outside the loop, an empty loop.
    • conditional branches, table cells and headers.
  • OdtValidationParityTests: new "inline expressions" template.
  • ValidationMissingConditionVariableTests: the inline test now also asserts the MissingVariable error.
  • The Release build in CI mode has 0 warnings. All tests pass on net10/net9/net8 (plus Converter and Tools tests). Validation tests also pass under tr-TR and ar-SA. dotnet format --verify-no-changes, dotnet pack and mkdocs build --strict pass.

Docs: docs/for-developers/quick-start.md, TriasDev.Templify/README.md (ValidationResult), ARCHITECTURE.md.

Public API impact: none (validation results: false errors removed)

This is a bug fix to validation results of the shipped ValidateTemplate API. There are no signature changes.

  • False MissingVariable errors and MissingVariables entries for inline expressions whose operands are present are gone.
  • Missing operands are now reported by their real names (B instead of (A and B)).
  • AllPlaceholders lists an expression's variables instead of the expression text.
  • New correct errors: MissingVariable for genuinely missing operands, and InvalidConditionalExpression for expressions that processing cannot evaluate (also without data).

…stead of reporting them as one missing variable
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 98.50746% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...iasDev.Templify/Core/InlineExpressionValidation.cs 97.67% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@vaceslav
vaceslav merged commit d3aca9e into main Sep 27, 2026
12 checks passed
@vaceslav
vaceslav deleted the fix/validation-inline-expressions branch September 27, 2026 13:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants