From 22cce86892eba35f4dc8b36e9c46676254c06166 Mon Sep 17 00:00:00 2001 From: donglixiaoche Date: Wed, 22 Mar 2023 13:43:25 +0800 Subject: [PATCH] fix: cr, add validation for near connectioins --- d2compiler/compile.go | 32 ++ d2compiler/compile_test.go | 11 + ...near_descendant_conent_to_outside.exp.json | 387 ++++++++++++++++++ ...ear_descendant_connect_to_outside.exp.json | 12 + 4 files changed, 442 insertions(+) create mode 100644 testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.exp.json create mode 100644 testdata/d2compiler/TestCompile/near_descendant_connect_to_outside.exp.json diff --git a/d2compiler/compile.go b/d2compiler/compile.go index 0900072f9..424c2795d 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -735,6 +735,20 @@ func (c *compiler) validateNear(g *d2graph.Graph) { c.errorf(obj.Attributes.NearKey, "constant near keys can only be set on root level shapes") continue } + + descendantsMap := getNearDescendants(obj) + connectToOutside := false + for _, edge := range g.Edges { + if (descendantsMap[edge.Src] && !descendantsMap[edge.Dst]) || + (!descendantsMap[edge.Src] && descendantsMap[edge.Dst]) { + connectToOutside = true + } + } + + if connectToOutside { + c.errorf(obj.Attributes.NearKey, "a child of a near container cannot connect to outside") + continue + } } else { c.errorf(obj.Attributes.NearKey, "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 @@ -743,6 +757,24 @@ func (c *compiler) validateNear(g *d2graph.Graph) { } } +func getNearDescendants(nearObj *d2graph.Object) map[*d2graph.Object]bool { + descendantsMap := make(map[*d2graph.Object]bool) + + var helper func(obj *d2graph.Object) + + helper = func(obj *d2graph.Object) { + if obj.ChildrenArray != nil { + for _, child := range obj.ChildrenArray { + descendantsMap[child] = true + helper(child) + } + } + } + + helper(nearObj) + return descendantsMap +} + func (c *compiler) validateBoardLinks(g *d2graph.Graph) { for _, obj := range g.Objects { if obj.Attributes.Link == nil { diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index 2cc385bc9..c8636dd5d 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -1535,6 +1535,17 @@ d2/testdata/d2compiler/TestCompile/near-invalid.d2:14:9: near keys cannot be set `, expErr: `d2/testdata/d2compiler/TestCompile/near_bad_connected.d2:3:12: constant near keys cannot be set on connected shapes`, }, + { + name: "near_descendant_connect_to_outside", + text: ` + x: { + near: top-left + y + } + x.y -> z + `, + expErr: "d2/testdata/d2compiler/TestCompile/near_descendant_connect_to_outside.d2:3:12: a child of a near container cannot connect to outside", + }, { name: "nested_near_constant", diff --git a/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.exp.json b/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.exp.json new file mode 100644 index 000000000..3af3dc2c5 --- /dev/null +++ b/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.exp.json @@ -0,0 +1,387 @@ +{ + "graph": { + "name": "", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,0:0:0-5:3:39", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,1:4:5-3:5:22", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,1:4:5-1:5:6", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,1:4:5-1:5:6", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,1:7:8-3:4:21", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,2:5:15-2:6:16", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,2:5:15-2:6:16", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,2:5:15-2:6:16", + "value": [ + { + "string": "y", + "raw_string": "y" + } + ] + } + } + ] + }, + "primary": {}, + "value": {} + } + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,4:4:27-4:12:35", + "edges": [ + { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,4:4:27-4:12:35", + "src": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,4:4:27-4:8:31", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,4:4:27-4:5:28", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,4:6:29-4:7:30", + "value": [ + { + "string": "y", + "raw_string": "y" + } + ] + } + } + ] + }, + "src_arrow": "", + "dst": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,4:10:33-4:12:35", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,4:11:34-4:12:35", + "value": [ + { + "string": "z", + "raw_string": "z" + } + ] + } + } + ] + }, + "dst_arrow": ">" + } + ], + "primary": {}, + "value": {} + } + } + ] + }, + "root": { + "id": "", + "id_val": "", + "label_dimensions": { + "width": 0, + "height": 0 + }, + "attributes": { + "label": { + "value": "" + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": { + "value": "" + } + }, + "zIndex": 0 + }, + "edges": [ + { + "index": 0, + "minWidth": 0, + "minHeight": 0, + "label_dimensions": { + "width": 0, + "height": 0 + }, + "isCurve": false, + "src_arrow": false, + "dst_arrow": true, + "references": [ + { + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "" + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": { + "value": "" + } + }, + "zIndex": 0 + } + ], + "objects": [ + { + "id": "x", + "id_val": "x", + "label_dimensions": { + "width": 0, + "height": 0 + }, + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,1:4:5-1:5:6", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,1:4:5-1:5:6", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + }, + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,4:4:27-4:8:31", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,4:4:27-4:5:28", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,4:6:29-4:7:30", + "value": [ + { + "string": "y", + "raw_string": "y" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "x" + }, + "style": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": { + "value": "" + } + }, + "zIndex": 0 + }, + { + "id": "y", + "id_val": "y", + "label_dimensions": { + "width": 0, + "height": 0 + }, + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,2:5:15-2:6:16", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,2:5:15-2:6:16", + "value": [ + { + "string": "y", + "raw_string": "y" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + }, + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,4:4:27-4:8:31", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,4:4:27-4:5:28", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,4:6:29-4:7:30", + "value": [ + { + "string": "y", + "raw_string": "y" + } + ] + } + } + ] + }, + "key_path_index": 1, + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "y" + }, + "style": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": { + "value": "" + } + }, + "zIndex": 0 + }, + { + "id": "z", + "id_val": "z", + "label_dimensions": { + "width": 0, + "height": 0 + }, + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,4:10:33-4:12:35", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_conent_to_outside.d2,4:11:34-4:12:35", + "value": [ + { + "string": "z", + "raw_string": "z" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "z" + }, + "style": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": { + "value": "" + } + }, + "zIndex": 0 + } + ] + }, + "err": null +} diff --git a/testdata/d2compiler/TestCompile/near_descendant_connect_to_outside.exp.json b/testdata/d2compiler/TestCompile/near_descendant_connect_to_outside.exp.json new file mode 100644 index 000000000..d43007599 --- /dev/null +++ b/testdata/d2compiler/TestCompile/near_descendant_connect_to_outside.exp.json @@ -0,0 +1,12 @@ +{ + "graph": null, + "err": { + "ioerr": null, + "errs": [ + { + "range": "d2/testdata/d2compiler/TestCompile/near_descendant_connect_to_outside.d2,2:11:21-2:19:29", + "errmsg": "d2/testdata/d2compiler/TestCompile/near_descendant_connect_to_outside.d2:3:12: a child of a near container cannot connect to outside" + } + ] + } +}