Skip to content
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
2 changes: 2 additions & 0 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@

### Bug Fixes

- Environment declarations are now returned even in the face of syntax errors.

### Breaking changes
2 changes: 1 addition & 1 deletion ast/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ func parseFromBase64(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr
func parseSecret(node *syntax.ObjectNode, name *StringExpr, value Expr) (Expr, syntax.Diagnostics) {
if arg, ok := value.(*ObjectExpr); ok && len(arg.Entries) == 1 {
kvp := arg.Entries[0]
if kvp.Key.Value == "ciphertext" {
if kvp.Key.GetValue() == "ciphertext" {
if str, ok := kvp.Value.(*StringExpr); ok {
return CiphertextSyntax(node, name, arg, str), nil
}
Expand Down
17 changes: 8 additions & 9 deletions eval/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,6 @@ func LoadYAMLBytes(filename string, source []byte) (*ast.EnvironmentDecl, syntax

t, tdiags := ast.ParseEnvironment(source, syn)
diags.Extend(tdiags...)
if tdiags.HasErrors() {
return nil, diags, nil
}

Comment on lines -73 to -76
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the core of the fix. the other edits in this PR are changes to accommodate nil object keys. I don't think we have other places that need nil checks, as everything else goes through declare, and declare will produce missingExpr values for nil AST nodes.

return t, diags, nil
}

Expand Down Expand Up @@ -391,11 +387,14 @@ func declare[Expr exprNode](e *evalContext, path string, x Expr, base *value) *e
case *ast.ObjectExpr:
properties := make(map[string]*expr, len(x.Entries))
for _, entry := range x.Entries {
k := entry.Key.Value
if _, ok := properties[k]; ok {
e.errorf(entry.Key, "duplicate key %q", k)
} else {
properties[k] = declare(e, util.JoinKey(path, k), entry.Value, base.property(entry.Key, k))
// Possible during parse errors. We elide properties with nil keys.
if entry.Key != nil {
k := entry.Key.Value
if _, ok := properties[k]; ok {
e.errorf(entry.Key, "duplicate key %q", k)
} else {
properties[k] = declare(e, util.JoinKey(path, k), entry.Value, base.property(entry.Key, k))
}
}
}
repr := &objectExpr{node: x, properties: properties}
Expand Down
38 changes: 38 additions & 0 deletions eval/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ type testEnvironments struct {
}

func (e *testEnvironments) LoadEnvironment(ctx context.Context, name string) ([]byte, Decrypter, error) {
if e.root == "" {
return nil, nil, os.ErrNotExist
}

bytes, err := os.ReadFile(filepath.Join(e.root, name+".yaml"))
if err != nil {
return nil, nil, err
Expand Down Expand Up @@ -600,3 +604,37 @@ func BenchmarkEvalEnvLoad(b *testing.B) {
func BenchmarkEvalAll(b *testing.B) {
benchmarkEval(b, 10*time.Millisecond, 10*time.Millisecond)
}

// TestSyntaxErrorCheck provides additional insurance that we are able to parse and analyze environments that contain
// syntax errors. It is important that we are able to provide as much information about an environment as we can so
// that tools that depend on an environment's typed AST or implied schema needs this fix can operate properly, even on
// environment definitions that contain syntax errors. Most notably we need this for autocomplete, but other
// type/schema-based features also benefit (signature help, go-to-def, et cetera).
//
// This is also covered by tests under TestEval, but it can be easy to miss the magnitude of changes to those tests.
func TestSyntaxErrorCheck(t *testing.T) {
environmentName := "syntax-error"
envBytes := []byte(`values:
foo:
bar: ${foo.}
`)

env, loadDiags, err := LoadYAMLBytes(environmentName, envBytes)
require.NoError(t, err)
sortEnvironmentDiagnostics(loadDiags)

execContext, err := esc.NewExecContext(map[string]esc.Value{
"pulumi": esc.NewValue(map[string]esc.Value{
"user": esc.NewValue(map[string]esc.Value{
"id": esc.NewValue("USER_123"),
}),
}),
})
assert.NoError(t, err)

check, checkDiags := CheckEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{},
&testEnvironments{}, execContext, false)

assert.True(t, checkDiags.HasErrors())
assert.NotNil(t, check)
}
4 changes: 3 additions & 1 deletion eval/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,9 @@ func (x *expr) export(environment string) esc.Expr {
case *objectExpr:
ex.KeyRanges = make(map[string]esc.Range, len(repr.node.Entries))
for _, kvp := range repr.node.Entries {
ex.KeyRanges[kvp.Key.Value] = convertRange(kvp.Key.Syntax().Syntax().Range(), environment)
if kvp.Key != nil {
ex.KeyRanges[kvp.Key.Value] = convertRange(kvp.Key.Syntax().Syntax().Range(), environment)
}
}

ex.Object = make(map[string]esc.Expr, len(repr.properties))
Expand Down
Loading
Loading