diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 2a4f40c5a..7e2eff173 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -3,5 +3,5 @@ #### Improvements 🧹 #### Bugfixes ⛑️ - - Accept absolute paths again on the CLI. [#979](https://github.com/terrastruct/d2/pull/979) +- Fixes some rare undefined behavior using capitalized reserved keywords [#978](https://github.com/terrastruct/d2/pull/978) diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index 4ae917bb6..451e82a1a 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -285,6 +285,17 @@ containers: { `, expErr: `d2/testdata/d2compiler/TestCompile/image_non_style.d2:4:3: image shapes cannot have children.`, }, + { + name: "image_children_Steps", + + text: `x: { + icon: https://icons.terrastruct.com/aws/_Group%20Icons/EC2-instance-container_light-bg.svg + shape: image + Steps +} +`, + expErr: `d2/testdata/d2compiler/TestCompile/image_children_Steps.d2:4:3: steps is only allowed at a board root`, + }, { name: "stroke-width", diff --git a/d2ir/compile.go b/d2ir/compile.go index 41e54dadc..fdd638a19 100644 --- a/d2ir/compile.go +++ b/d2ir/compile.go @@ -1,6 +1,8 @@ package d2ir import ( + "strings" + "oss.terrastruct.com/d2/d2ast" "oss.terrastruct.com/d2/d2format" "oss.terrastruct.com/d2/d2parser" @@ -163,13 +165,13 @@ func (c *compiler) compileLink(refctx *RefContext) { } // If it doesn't start with one of these reserved words, the link is definitely not a board link. - if linkIDA[0] != "layers" && linkIDA[0] != "scenarios" && linkIDA[0] != "steps" && linkIDA[0] != "_" { + if !strings.EqualFold(linkIDA[0], "layers") && !strings.EqualFold(linkIDA[0], "scenarios") && !strings.EqualFold(linkIDA[0], "steps") && linkIDA[0] != "_" { return } // Chop off the non-board portion of the scope, like if this is being defined on a nested object (e.g. `x.y.z`) for i := len(scopeIDA) - 1; i > 0; i-- { - if scopeIDA[i-1] == "layers" || scopeIDA[i-1] == "scenarios" || scopeIDA[i-1] == "steps" { + if strings.EqualFold(scopeIDA[i-1], "layers") || strings.EqualFold(scopeIDA[i-1], "scenarios") || strings.EqualFold(scopeIDA[i-1], "steps") { scopeIDA = scopeIDA[:i+1] break } diff --git a/d2ir/d2ir.go b/d2ir/d2ir.go index 8a687bbdf..6fc8c34ca 100644 --- a/d2ir/d2ir.go +++ b/d2ir/d2ir.go @@ -655,6 +655,10 @@ func (m *Map) EnsureField(kp *d2ast.KeyPath, refctx *RefContext) (*Field, error) func (m *Map) ensureField(i int, kp *d2ast.KeyPath, refctx *RefContext) (*Field, error) { head := kp.Path[i].Unbox().ScalarString() + if _, ok := d2graph.ReservedKeywords[strings.ToLower(head)]; ok { + head = strings.ToLower(head) + } + if head == "_" { return nil, d2parser.Errorf(kp.Path[i].Unbox(), `parent "_" can only be used in the beginning of paths, e.g. "_.x"`) } diff --git a/testdata/d2compiler/TestCompile/image_children.exp.json b/testdata/d2compiler/TestCompile/image_children.exp.json new file mode 100644 index 000000000..8e7392934 --- /dev/null +++ b/testdata/d2compiler/TestCompile/image_children.exp.json @@ -0,0 +1,12 @@ +{ + "graph": null, + "err": { + "ioerr": null, + "errs": [ + { + "range": "d2/testdata/d2compiler/TestCompile/image_children.d2,3:2:115-3:3:116", + "errmsg": "d2/testdata/d2compiler/TestCompile/image_children.d2:4:3: image shapes cannot have children." + } + ] + } +} diff --git a/testdata/d2compiler/TestCompile/image_children_Steps.exp.json b/testdata/d2compiler/TestCompile/image_children_Steps.exp.json new file mode 100644 index 000000000..7958d0e1b --- /dev/null +++ b/testdata/d2compiler/TestCompile/image_children_Steps.exp.json @@ -0,0 +1,12 @@ +{ + "graph": null, + "err": { + "ioerr": null, + "errs": [ + { + "range": "d2/testdata/d2compiler/TestCompile/image_children_Steps.d2,3:2:115-3:7:120", + "errmsg": "d2/testdata/d2compiler/TestCompile/image_children_Steps.d2:4:3: steps is only allowed at a board root" + } + ] + } +}