diff --git a/d2compiler/compile.go b/d2compiler/compile.go index 9ca656098..9ca373d81 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -850,9 +850,14 @@ func (c *compiler) validateKeys(obj *d2graph.Object, m *d2ast.Map) { func (c *compiler) validateNear(g *d2graph.Graph) { for _, obj := range g.Objects { if obj.Attributes.NearKey != nil { - _, ok := g.Root.HasChild(d2graph.Key(obj.Attributes.NearKey)) - if !ok { - c.errorf(obj.Attributes.NearKey.GetRange().Start, obj.Attributes.NearKey.GetRange().End, "near key %#v does not exist. It must be the absolute path to a shape.", d2format.Format(obj.Attributes.NearKey)) + _, isKey := g.Root.HasChild(d2graph.Key(obj.Attributes.NearKey)) + _, isConst := d2graph.NearConstants[d2graph.Key(obj.Attributes.NearKey)[0]] + if !isKey && !isConst { + c.errorf(obj.Attributes.NearKey.GetRange().Start, obj.Attributes.NearKey.GetRange().End, "near key %#v must be the absolute path to a shape or one of the following constants: %s", d2format.Format(obj.Attributes.NearKey), strings.Join(d2graph.NearConstantsArray, ", ")) + continue + } + if !isKey && isConst && obj.Parent != g.Root { + c.errorf(obj.Attributes.NearKey.GetRange().Start, obj.Attributes.NearKey.GetRange().End, "constant near keys can only be set on root level shapes") continue } } diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index afe9ceec4..d6459f34d 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -1266,6 +1266,28 @@ x -> y: { } }, }, + { + name: "near_constant", + + text: `x.near: top-center +`, + }, + { + name: "near_bad_constant", + + text: `x.near: txop-center +`, + expErr: `d2/testdata/d2compiler/TestCompile/near_bad_constant.d2:1:1: near key "txop-center" must be the absolute path to a shape or one of the following constants: top-left, top-center, top-right, center-left, center-right, bottom-left, bottom-center, bottom-right +`, + }, + { + name: "nested_near_constant", + + text: `x.y.near: top-center +`, + expErr: `d2/testdata/d2compiler/TestCompile/nested_near_constant.d2:1:1: constant near keys can only be set on root level shapes +`, + }, { name: "reserved_icon_near_style", @@ -1312,7 +1334,7 @@ y expErr: `d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:3:9: bad icon url "::????:::%%orange": parse "::????:::%%orange": missing protocol scheme d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:4:18: expected "opacity" to be a number between 0.0 and 1.0 d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:5:18: expected "opacity" to be a number between 0.0 and 1.0 -d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:1:1: near key "y" does not exist. It must be the absolute path to a shape. +d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:1:1: near key "y" must be the absolute path to a shape or one of the following constants: top-left, top-center, top-right, center-left, center-right, bottom-left, bottom-center, bottom-right `, }, { diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index e60aa17c0..05dd13831 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -1189,6 +1189,32 @@ var StyleKeywords = map[string]struct{}{ "filled": {}, } +var NearConstants = map[string]struct{}{ + "top-left": {}, + "top-center": {}, + "top-right": {}, + + "center-left": {}, + "center-right": {}, + + "bottom-left": {}, + "bottom-center": {}, + "bottom-right": {}, +} + +var NearConstantsArray = []string{ + "top-left", + "top-center", + "top-right", + + "center-left", + "center-right", + + "bottom-left", + "bottom-center", + "bottom-right", +} + func init() { for k, v := range StyleKeywords { ReservedKeywords[k] = v diff --git a/testdata/d2compiler/TestCompile/errors/reserved_icon_style.exp.json b/testdata/d2compiler/TestCompile/errors/reserved_icon_style.exp.json index 4591c13df..3fa65dd2a 100644 --- a/testdata/d2compiler/TestCompile/errors/reserved_icon_style.exp.json +++ b/testdata/d2compiler/TestCompile/errors/reserved_icon_style.exp.json @@ -17,7 +17,7 @@ }, { "range": "d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2,0:0:0-0:1:1", - "errmsg": "d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:1:1: near key \"y\" does not exist. It must be the absolute path to a shape." + "errmsg": "d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:1:1: near key \"y\" must be the absolute path to a shape or one of the following constants: top-left, top-center, top-right, center-left, center-right, bottom-left, bottom-center, bottom-right" } ] } diff --git a/testdata/d2compiler/TestCompile/near_bad_constant.exp.json b/testdata/d2compiler/TestCompile/near_bad_constant.exp.json new file mode 100644 index 000000000..7026afd59 --- /dev/null +++ b/testdata/d2compiler/TestCompile/near_bad_constant.exp.json @@ -0,0 +1,12 @@ +{ + "graph": null, + "err": { + "ioerr": null, + "errs": [ + { + "range": "d2/testdata/d2compiler/TestCompile/near_bad_constant.d2,0:0:0-0:11:11", + "errmsg": "d2/testdata/d2compiler/TestCompile/near_bad_constant.d2:1:1: near key \"txop-center\" must be the absolute path to a shape or one of the following constants: top-left, top-center, top-right, center-left, center-right, bottom-left, bottom-center, bottom-right" + } + ] + } +} diff --git a/testdata/d2compiler/TestCompile/near_constant.exp.json b/testdata/d2compiler/TestCompile/near_constant.exp.json new file mode 100644 index 000000000..87cc9ff2b --- /dev/null +++ b/testdata/d2compiler/TestCompile/near_constant.exp.json @@ -0,0 +1,149 @@ +{ + "graph": { + "ast": { + "range": "d2/testdata/d2compiler/TestCompile/near_constant.d2,0:0:0-1:0:19", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/near_constant.d2,0:0:0-0:18:18", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/near_constant.d2,0:0:0-0:6:6", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_constant.d2,0:0:0-0:1:1", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_constant.d2,0:2:2-0:6:6", + "value": [ + { + "string": "near", + "raw_string": "near" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_constant.d2,0:8:8-0:18:18", + "value": [ + { + "string": "top-center", + "raw_string": "top-center" + } + ] + } + } + } + } + ] + }, + "root": { + "id": "", + "id_val": "", + "label_dimensions": { + "width": 0, + "height": 0 + }, + "attributes": { + "label": { + "value": "" + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + } + }, + "zIndex": 0 + }, + "edges": null, + "objects": [ + { + "id": "x", + "id_val": "x", + "label_dimensions": { + "width": 0, + "height": 0 + }, + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile/near_constant.d2,0:0:0-0:6:6", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_constant.d2,0:0:0-0:1:1", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_constant.d2,0:2:2-0:6:6", + "value": [ + { + "string": "near", + "raw_string": "near" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "x" + }, + "style": {}, + "near_key": { + "range": ",0:0:0-0:10:10", + "path": [ + { + "unquoted_string": { + "range": ",0:0:0-0:10:10", + "value": [ + { + "string": "top-center", + "raw_string": "top-center" + } + ] + } + } + ] + }, + "shape": { + "value": "" + }, + "direction": { + "value": "" + } + }, + "zIndex": 0 + } + ] + }, + "err": null +} diff --git a/testdata/d2compiler/TestCompile/nested_near_constant.exp.json b/testdata/d2compiler/TestCompile/nested_near_constant.exp.json new file mode 100644 index 000000000..ac0653715 --- /dev/null +++ b/testdata/d2compiler/TestCompile/nested_near_constant.exp.json @@ -0,0 +1,12 @@ +{ + "graph": null, + "err": { + "ioerr": null, + "errs": [ + { + "range": "d2/testdata/d2compiler/TestCompile/nested_near_constant.d2,0:0:0-0:10:10", + "errmsg": "d2/testdata/d2compiler/TestCompile/nested_near_constant.d2:1:1: constant near keys can only be set on root level shapes" + } + ] + } +}