Skip to content

Commit

Permalink
fix: retain bug-compatibility for referencing missing variables in unary
Browse files Browse the repository at this point in the history
operators (`!` and `typeof`).

Older versions of garden did not throw an error when referencing
missing variables from unary operators like `!` and `typeof`. This
commit restores backwards-compatiblity that has been broken in #6685
  • Loading branch information
stefreak committed Dec 9, 2024
1 parent 3ff5ee0 commit 6709d6b
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 5 deletions.
28 changes: 23 additions & 5 deletions core/src/template-string/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,26 +160,44 @@ export abstract class UnaryExpression extends TemplateExpression {
}

override evaluate(args: EvaluateArgs): TemplateEvaluationResult<TemplatePrimitive> {
const inner = this.innerExpression.evaluate(args)
const inner = this.innerExpression.evaluate({
...args,
// For backwards compatibility with older versions of Garden, unary expressions do not throw errors if context lookup expressions fail.
// `!var.doesNotExist` evaluates to false and `typeof var.doesNotExist` evaluates to the string "undefined".
// TODO(0.14): Remove the following line. other methods exist to make variables optional, for example using the logical or operator.
optional: true,
})

if (typeof inner === "symbol") {
if (inner === CONTEXT_RESOLVE_KEY_AVAILABLE_LATER) {
return inner
}

return this.transform(inner)
}

abstract transform(value: CollectionOrValue<TemplatePrimitive>): TemplatePrimitive
abstract transform(
value: CollectionOrValue<TemplatePrimitive> | typeof CONTEXT_RESOLVE_KEY_NOT_FOUND
): TemplatePrimitive | typeof CONTEXT_RESOLVE_KEY_NOT_FOUND
}

export class TypeofExpression extends UnaryExpression {
override transform(value: CollectionOrValue<TemplatePrimitive>): string {
override transform(
value: CollectionOrValue<TemplatePrimitive> | typeof CONTEXT_RESOLVE_KEY_NOT_FOUND
): string | typeof CONTEXT_RESOLVE_KEY_NOT_FOUND {
if (isNotFound(value)) {
return "undefined"
}
return typeof value
}
}

export class NotExpression extends UnaryExpression {
override transform(value: CollectionOrValue<TemplatePrimitive>): boolean {
override transform(
value: CollectionOrValue<TemplatePrimitive> | typeof CONTEXT_RESOLVE_KEY_NOT_FOUND
): boolean | typeof CONTEXT_RESOLVE_KEY_NOT_FOUND {
if (isNotFound(value)) {
return true
}
return !value
}
}
Expand Down
31 changes: 31 additions & 0 deletions core/test/unit/src/template-string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,37 @@ describe("resolveTemplateString", () => {
})
})

it("should throw an error if var lookup fails combined with json encode", () => {
const c = new GenericContext({ var: {} })

void expectError(() => resolveTemplateString({ string: "${jsonEncode(var.missing)}", context: c }), {
contains: "Invalid template string (${jsonEncode(var.missing)}): Could not find key missing under var.",
})
})

it("We need to remain bug-compatible with older versions of garden and not throw when variable does not exist with certain operators", () => {
const testCases = {
"${!var.doesNotExist}": true,
"${typeof var.doesNotExist}": "undefined",
"${! (var.doesNotExistOne && var.doesNotExistTwo)}": true,
"${var.doesNotExistOne && var.doesNotExistTwo}": false,
}

for (const [template, expectation] of Object.entries(testCases)) {
const result = resolveTemplateString({
string: template,
contextOpts: {
allowPartial: false,
},
context: new GenericContext({ var: {} }),
})
expect(result).to.eq(
expectation,
`Template "${template}" did not resolve to expected value ${JSON.stringify(expectation)}`
)
}
})

context("allowPartial=true", () => {
it("passes through template strings with missing key", () => {
const res = resolveTemplateString({
Expand Down

0 comments on commit 6709d6b

Please sign in to comment.