From f430faa16b8799efb282ac26dd490543b3a7d7f2 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Sun, 30 Mar 2025 08:33:31 -0600 Subject: [PATCH 1/9] add test --- d2compiler/compile_test.go | 14 + .../TestCompile2/globs/level-filter.exp.json | 798 +++++++++++++++++- 2 files changed, 793 insertions(+), 19 deletions(-) diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index 5a56d1e89..591184f53 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -5653,7 +5653,18 @@ d: { &level: 1 style.stroke: yellow } +(** -> **)[*]: { + &src.level: 0 + &dst.level: 0 + style.stroke: blue +} a.b.c + +x -> y +a: { + 1 -> 2 +} +a.1 -> x `, ``) assert.Equal(t, "a", g.Objects[0].ID) assert.Equal(t, "red", g.Objects[0].Attributes.Style.Fill.Value) @@ -5666,6 +5677,9 @@ a.b.c assert.Equal(t, "c", g.Objects[2].ID) assert.Equal(t, (*d2graph.Scalar)(nil), g.Objects[2].Attributes.Style.Fill) assert.Equal(t, (*d2graph.Scalar)(nil), g.Objects[2].Attributes.Style.Stroke) + + assert.Equal(t, "(x -> y)[0]", g.Edges[0].AbsID()) + assert.Equal(t, "blue", g.Edges[0].Attributes.Style.Stroke) }, }, { diff --git a/testdata/d2compiler/TestCompile2/globs/level-filter.exp.json b/testdata/d2compiler/TestCompile2/globs/level-filter.exp.json index 6f03fe650..414d3eee0 100644 --- a/testdata/d2compiler/TestCompile2/globs/level-filter.exp.json +++ b/testdata/d2compiler/TestCompile2/globs/level-filter.exp.json @@ -3,7 +3,7 @@ "name": "", "isFolderOnly": false, "ast": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,0:0:0-10:0:88", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,0:0:0-21:0:193", "nodes": [ { "map_key": { @@ -223,13 +223,206 @@ }, { "map_key": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:0:82-9:5:87", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:0:82-13:1:153", + "edges": [ + { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:1:83-9:9:91", + "src": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:1:83-9:3:85", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:1:83-9:3:85", + "value": [ + { + "string": "**", + "raw_string": "**" + } + ], + "pattern": [ + "*", + "", + "*" + ] + } + } + ] + }, + "src_arrow": "", + "dst": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:7:89-9:9:91", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:7:89-9:9:91", + "value": [ + { + "string": "**", + "raw_string": "**" + } + ], + "pattern": [ + "*", + "", + "*" + ] + } + } + ] + }, + "dst_arrow": ">" + } + ], + "edge_index": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:10:92-9:13:95", + "int": null, + "glob": true + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:15:97-13:1:153", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,10:2:101-10:15:114", + "ampersand": true, + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,10:3:102-10:12:111", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,10:3:102-10:6:105", + "value": [ + { + "string": "src", + "raw_string": "src" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,10:7:106-10:12:111", + "value": [ + { + "string": "level", + "raw_string": "level" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "number": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,10:14:113-10:15:114", + "raw": "0", + "value": "0" + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,11:2:117-11:15:130", + "ampersand": true, + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,11:3:118-11:12:127", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,11:3:118-11:6:121", + "value": [ + { + "string": "dst", + "raw_string": "dst" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,11:7:122-11:12:127", + "value": [ + { + "string": "level", + "raw_string": "level" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "number": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,11:14:129-11:15:130", + "raw": "0", + "value": "0" + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,12:2:133-12:20:151", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,12:2:133-12:14:145", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,12:2:133-12:7:138", + "value": [ + { + "string": "style", + "raw_string": "style" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,12:8:139-12:14:145", + "value": [ + { + "string": "stroke", + "raw_string": "stroke" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,12:16:147-12:20:151", + "value": [ + { + "string": "blue", + "raw_string": "blue" + } + ] + } + } + } + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:0:154-14:5:159", "key": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:0:82-9:5:87", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:0:154-14:5:159", "path": [ { "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:0:82-9:1:83", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:0:154-14:1:155", "value": [ { "string": "a", @@ -240,7 +433,7 @@ }, { "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:2:84-9:3:85", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:2:156-14:3:157", "value": [ { "string": "b", @@ -251,7 +444,7 @@ }, { "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:4:86-9:5:87", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:4:158-14:5:159", "value": [ { "string": "c", @@ -265,6 +458,184 @@ "primary": {}, "value": {} } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,16:0:161-16:6:167", + "edges": [ + { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,16:0:161-16:6:167", + "src": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,16:0:161-16:1:162", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,16:0:161-16:1:162", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "src_arrow": "", + "dst": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,16:5:166-16:6:167", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,16:5:166-16:6:167", + "value": [ + { + "string": "y", + "raw_string": "y" + } + ] + } + } + ] + }, + "dst_arrow": ">" + } + ], + "primary": {}, + "value": {} + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,17:0:168-19:1:183", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,17:0:168-17:1:169", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,17:0:168-17:1:169", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,17:3:171-19:1:183", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,18:2:175-18:8:181", + "edges": [ + { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,18:2:175-18:8:181", + "src": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,18:2:175-18:3:176", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,18:2:175-18:3:176", + "value": [ + { + "string": "1", + "raw_string": "1" + } + ] + } + } + ] + }, + "src_arrow": "", + "dst": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,18:7:180-18:8:181", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,18:7:180-18:8:181", + "value": [ + { + "string": "2", + "raw_string": "2" + } + ] + } + } + ] + }, + "dst_arrow": ">" + } + ], + "primary": {}, + "value": {} + } + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,20:0:184-20:8:192", + "edges": [ + { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,20:0:184-20:8:192", + "src": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,20:0:184-20:3:187", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,20:0:184-20:1:185", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,20:2:186-20:3:187", + "value": [ + { + "string": "1", + "raw_string": "1" + } + ] + } + } + ] + }, + "src_arrow": "", + "dst": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,20:7:191-20:8:192", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,20:7:191-20:8:192", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "dst_arrow": ">" + } + ], + "primary": {}, + "value": {} + } } ] }, @@ -292,7 +663,98 @@ }, "zIndex": 0 }, - "edges": null, + "edges": [ + { + "index": 0, + "isCurve": false, + "src_arrow": false, + "dst_arrow": true, + "references": [ + { + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + { + "index": 0, + "isCurve": false, + "src_arrow": false, + "dst_arrow": true, + "references": [ + { + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + { + "index": 0, + "isCurve": false, + "src_arrow": false, + "dst_arrow": true, + "references": [ + { + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + } + ], "objects": [ { "id": "a", @@ -300,11 +762,11 @@ "references": [ { "key": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:0:82-9:5:87", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:0:154-14:5:159", "path": [ { "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:0:82-9:1:83", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:0:154-14:1:155", "value": [ { "string": "a", @@ -315,7 +777,7 @@ }, { "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:2:84-9:3:85", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:2:156-14:3:157", "value": [ { "string": "b", @@ -326,7 +788,7 @@ }, { "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:4:86-9:5:87", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:4:158-14:5:159", "value": [ { "string": "c", @@ -339,6 +801,57 @@ }, "key_path_index": 0, "map_key_edge_index": -1 + }, + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,17:0:168-17:1:169", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,17:0:168-17:1:169", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + }, + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,20:0:184-20:3:187", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,20:0:184-20:1:185", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,20:2:186-20:3:187", + "value": [ + { + "string": "1", + "raw_string": "1" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": 0 } ], "attributes": { @@ -372,11 +885,11 @@ "references": [ { "key": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:0:82-9:5:87", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:0:154-14:5:159", "path": [ { "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:0:82-9:1:83", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:0:154-14:1:155", "value": [ { "string": "a", @@ -387,7 +900,7 @@ }, { "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:2:84-9:3:85", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:2:156-14:3:157", "value": [ { "string": "b", @@ -398,7 +911,7 @@ }, { "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:4:86-9:5:87", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:4:158-14:5:159", "value": [ { "string": "c", @@ -444,11 +957,11 @@ "references": [ { "key": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:0:82-9:5:87", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:0:154-14:5:159", "path": [ { "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:0:82-9:1:83", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:0:154-14:1:155", "value": [ { "string": "a", @@ -459,7 +972,7 @@ }, { "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:2:84-9:3:85", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:2:156-14:3:157", "value": [ { "string": "b", @@ -470,7 +983,7 @@ }, { "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,9:4:86-9:5:87", + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,14:4:158-14:5:159", "value": [ { "string": "c", @@ -505,6 +1018,253 @@ "constraint": null }, "zIndex": 0 + }, + { + "id": "x", + "id_val": "x", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,16:0:161-16:1:162", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,16:0:161-16:1:162", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": 0 + }, + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,20:7:191-20:8:192", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,20:7:191-20:8:192", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "x" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": { + "fill": { + "value": "red" + } + }, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + { + "id": "y", + "id_val": "y", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,16:5:166-16:6:167", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,16:5:166-16:6:167", + "value": [ + { + "string": "y", + "raw_string": "y" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "y" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": { + "fill": { + "value": "red" + } + }, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + { + "id": "1", + "id_val": "1", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,18:2:175-18:3:176", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,18:2:175-18:3:176", + "value": [ + { + "string": "1", + "raw_string": "1" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": 0 + }, + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,20:0:184-20:3:187", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,20:0:184-20:1:185", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,20:2:186-20:3:187", + "value": [ + { + "string": "1", + "raw_string": "1" + } + ] + } + } + ] + }, + "key_path_index": 1, + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "1" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": { + "stroke": { + "value": "yellow" + } + }, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + { + "id": "2", + "id_val": "2", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,18:7:180-18:8:181", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/level-filter.d2,18:7:180-18:8:181", + "value": [ + { + "string": "2", + "raw_string": "2" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "2" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": { + "stroke": { + "value": "yellow" + } + }, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 } ] }, From db3e4c21a76b2aaef20c87d483394a33c7a52716 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Mon, 31 Mar 2025 11:49:07 -0600 Subject: [PATCH 2/9] implement --- d2compiler/compile_test.go | 8 +- d2ir/compile.go | 122 +++++++++++------- .../TestCompile2/globs/level-filter.exp.json | 16 ++- 3 files changed, 97 insertions(+), 49 deletions(-) diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index 591184f53..da5e686ba 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -5679,7 +5679,13 @@ a.1 -> x assert.Equal(t, (*d2graph.Scalar)(nil), g.Objects[2].Attributes.Style.Stroke) assert.Equal(t, "(x -> y)[0]", g.Edges[0].AbsID()) - assert.Equal(t, "blue", g.Edges[0].Attributes.Style.Stroke) + assert.Equal(t, "blue", g.Edges[0].Attributes.Style.Stroke.Value) + + assert.Equal(t, "a.(1 -> 2)[0]", g.Edges[1].AbsID()) + assert.Equal(t, (*d2graph.Scalar)(nil), g.Edges[1].Attributes.Style.Stroke) + + assert.Equal(t, "(a.1 -> x)[0]", g.Edges[2].AbsID()) + assert.Equal(t, (*d2graph.Scalar)(nil), g.Edges[2].Attributes.Style.Stroke) }, }, { diff --git a/d2ir/compile.go b/d2ir/compile.go index 47031ddfe..04754ce48 100644 --- a/d2ir/compile.go +++ b/d2ir/compile.go @@ -755,6 +755,13 @@ func (c *compiler) ampersandFilter(refctx *RefContext) bool { return false } + secondPart := keyPath.Path[1].Unbox().ScalarString() + value := refctx.Key.Value.ScalarBox().Unbox().ScalarString() + + if len(keyPath.Path) == 2 && c._ampersandPropertyFilter(secondPart, value, node, refctx.Key) { + return true + } + propKeyPath := &d2ast.KeyPath{ Path: keyPath.Path[1:], } @@ -840,53 +847,7 @@ func (c *compiler) ampersandFilter(refctx *RefContext) bool { }, } return c._ampersandFilter(f, refctx) - case "leaf": - raw := refctx.Key.Value.ScalarBox().Unbox().ScalarString() - boolVal, err := strconv.ParseBool(raw) - if err != nil { - c.errorf(refctx.Key, `&leaf must be "true" or "false", got %q`, raw) - return false - } - f := refctx.ScopeMap.Parent().(*Field) - isLeaf := f.Map() == nil || !f.Map().IsContainer() - return isLeaf == boolVal - case "level": - raw := refctx.Key.Value.ScalarBox().Unbox().ScalarString() - levelVal, err := strconv.Atoi(raw) - if err != nil { - c.errorf(refctx.Key, `&level must be a non-negative integer, got %q`, raw) - return false - } - if levelVal < 0 { - c.errorf(refctx.Key, `&level must be a non-negative integer, got %d`, levelVal) - return false - } - - f := refctx.ScopeMap.Parent().(*Field) - level := 0 - parent := ParentField(f) - for parent != nil && parent.Name.ScalarString() != "root" && NodeBoardKind(parent) == "" { - level++ - parent = ParentField(parent) - } - return level == levelVal - case "connected": - raw := refctx.Key.Value.ScalarBox().Unbox().ScalarString() - boolVal, err := strconv.ParseBool(raw) - if err != nil { - c.errorf(refctx.Key, `&connected must be "true" or "false", got %q`, raw) - return false - } - f := refctx.ScopeMap.Parent().(*Field) - isConnected := false - for _, r := range f.References { - if r.InEdge() { - isConnected = true - break - } - } - return isConnected == boolVal case "label": f := &Field{} n := refctx.ScopeMap.Parent() @@ -978,7 +939,10 @@ func (c *compiler) ampersandFilter(refctx *RefContext) bool { return dstPath == filterValue default: - return false + f := refctx.ScopeMap.Parent().(*Field) + propName := refctx.Key.Key.Last().ScalarString() + value := refctx.Key.Value.ScalarBox().Unbox().ScalarString() + return c._ampersandPropertyFilter(propName, value, f, refctx.Key) } } for _, f := range fa { @@ -990,6 +954,70 @@ func (c *compiler) ampersandFilter(refctx *RefContext) bool { return true } +// handles filters that are not based on fields +func (c *compiler) _ampersandPropertyFilter(propName string, value string, node *Field, key *d2ast.Key) bool { + switch propName { + case "level": + levelVal, err := strconv.Atoi(value) + if err != nil { + c.errorf(key, `&level must be a non-negative integer, got %q`, value) + return false + } + if levelVal < 0 { + c.errorf(key, `&level must be a non-negative integer, got %d`, levelVal) + return false + } + + level := 0 + parent := ParentField(node) + for parent != nil && parent.Name.ScalarString() != "root" && NodeBoardKind(parent) == "" { + level++ + parent = ParentField(parent) + } + return level == levelVal + case "leaf": + boolVal, err := strconv.ParseBool(value) + if err != nil { + c.errorf(key, `&leaf must be "true" or "false", got %q`, value) + return false + } + isLeaf := node.Map() == nil || !node.Map().IsContainer() + return isLeaf == boolVal + case "connected": + boolVal, err := strconv.ParseBool(value) + if err != nil { + c.errorf(key, `&connected must be "true" or "false", got %q`, value) + return false + } + isConnected := false + for _, r := range node.References { + if r.InEdge() { + isConnected = true + break + } + } + return isConnected == boolVal + case "label": + f := &Field{} + if node.Primary() == nil { + f.Primary_ = &Scalar{ + Value: node.Name, + } + } else { + f.Primary_ = node.Primary() + } + propKey := &d2ast.Key{ + Key: key.Key, + Value: key.Value, + } + propRefCtx := &RefContext{ + Key: propKey, + } + return c._ampersandFilter(f, propRefCtx) + } + return false +} + func (c *compiler) _ampersandFilter(f *Field, refctx *RefContext) bool { if refctx.Key.Value.ScalarBox().Unbox() == nil { c.errorf(refctx.Key, "glob filters cannot be composites") diff --git a/testdata/d2compiler/TestCompile2/globs/level-filter.exp.json b/testdata/d2compiler/TestCompile2/globs/level-filter.exp.json index 414d3eee0..acec6846c 100644 --- a/testdata/d2compiler/TestCompile2/globs/level-filter.exp.json +++ b/testdata/d2compiler/TestCompile2/globs/level-filter.exp.json @@ -670,6 +670,9 @@ "src_arrow": false, "dst_arrow": true, "references": [ + { + "map_key_edge_index": 0 + }, { "map_key_edge_index": 0 } @@ -682,7 +685,12 @@ "width": 0, "height": 0 }, - "style": {}, + "style": { + "stroke": { + "value": "blue" + } + }, + "iconStyle": {}, "near_key": null, "shape": { "value": "" @@ -713,6 +721,7 @@ "height": 0 }, "style": {}, + "iconStyle": {}, "near_key": null, "shape": { "value": "" @@ -743,6 +752,7 @@ "height": 0 }, "style": {}, + "iconStyle": {}, "near_key": null, "shape": { "value": "" @@ -1077,6 +1087,7 @@ "value": "red" } }, + "iconStyle": {}, "near_key": null, "shape": { "value": "rectangle" @@ -1126,6 +1137,7 @@ "value": "red" } }, + "iconStyle": {}, "near_key": null, "shape": { "value": "rectangle" @@ -1206,6 +1218,7 @@ "value": "yellow" } }, + "iconStyle": {}, "near_key": null, "shape": { "value": "rectangle" @@ -1255,6 +1268,7 @@ "value": "yellow" } }, + "iconStyle": {}, "near_key": null, "shape": { "value": "rectangle" From 9ea0101dbd32eb18da933ef95573a91cb6563b32 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Thu, 3 Apr 2025 18:39:42 -0600 Subject: [PATCH 3/9] add test --- d2compiler/compile_test.go | 19 ++ .../TestCompile/import-nested-var.exp.json | 191 ++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 testdata/d2compiler/TestCompile/import-nested-var.exp.json diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index da5e686ba..f077c5abe 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -1714,6 +1714,25 @@ steps: { assert.Equal(t, 1, len(g.Layers[0].Steps)) }, }, + { + name: "import-nested-var", + + text: `...@models.environment +`, + files: map[string]string{ + "models.d2": ` +vars: { + c: { + k + } +} + +environment: { + ...${c} +} +`, + }, + }, { name: "import-connections", diff --git a/testdata/d2compiler/TestCompile/import-nested-var.exp.json b/testdata/d2compiler/TestCompile/import-nested-var.exp.json new file mode 100644 index 000000000..243379fb6 --- /dev/null +++ b/testdata/d2compiler/TestCompile/import-nested-var.exp.json @@ -0,0 +1,191 @@ +{ + "graph": { + "name": "", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:0:0-3:0:42", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:0:0-2:1:41", + "key": { + "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:0:0-0:11:11", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:0:0-0:11:11", + "value": [ + { + "string": "development", + "raw_string": "development" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:13:13-2:1:41", + "nodes": [ + { + "import": { + "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,1:2:17-1:24:39", + "spread": true, + "pre": "", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,1:6:21-1:12:27", + "value": [ + { + "string": "models", + "raw_string": "models" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,1:13:28-1:24:39", + "value": [ + { + "string": "environment", + "raw_string": "environment" + } + ] + } + } + ] + } + } + ] + } + } + } + } + ] + }, + "root": { + "id": "", + "id_val": "", + "attributes": { + "label": { + "value": "" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "iconStyle": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + "edges": null, + "objects": [ + { + "id": "development", + "id_val": "development", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:0:0-0:11:11", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:0:0-0:11:11", + "value": [ + { + "string": "development", + "raw_string": "development" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + } + ], + "attributes": { + "label": { + "value": "development" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "iconStyle": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + { + "id": "k", + "id_val": "k", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile/models.d2,8:2:56-8:3:57", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/models.d2,8:2:56-8:3:57", + "value": [ + { + "string": "k", + "raw_string": "k" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + } + ], + "attributes": { + "label": { + "value": "k" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "iconStyle": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + } + ] + }, + "err": null +} From 1acd6ef34ab1556743752a62c94c1b4122623e3e Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Thu, 3 Apr 2025 19:52:41 -0600 Subject: [PATCH 4/9] fix --- d2compiler/compile_test.go | 4 + d2ir/import.go | 8 ++ .../TestCompile/import-nested-var.exp.json | 133 ++++-------------- 3 files changed, 41 insertions(+), 104 deletions(-) diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index f077c5abe..44c4d1bb2 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -1732,6 +1732,10 @@ environment: { } `, }, + assertions: func(t *testing.T, g *d2graph.Graph) { + assert.Equal(t, 1, len(g.Objects)) + assert.Equal(t, "k", g.Objects[0].AbsID()) + }, }, { name: "import-connections", diff --git a/d2ir/import.go b/d2ir/import.go index 6f593c5c7..2d6630203 100644 --- a/d2ir/import.go +++ b/d2ir/import.go @@ -111,6 +111,14 @@ func (c *compiler) __import(imp *d2ast.Import) (*Map, bool) { c.compileMap(ir, ast, ast) + // We attempt to resolve variables in the imported file scope first + // But ignore errors, in case the variable is meant to be resolved at the + // importer + savedErrors := make([]d2ast.Error, len(c.err.Errors)) + copy(savedErrors, c.err.Errors) + c.compileSubstitutions(ir, nil) + c.err.Errors = savedErrors + c.seenImports[impPath] = struct{}{} return ir, true diff --git a/testdata/d2compiler/TestCompile/import-nested-var.exp.json b/testdata/d2compiler/TestCompile/import-nested-var.exp.json index 243379fb6..a2ec401d7 100644 --- a/testdata/d2compiler/TestCompile/import-nested-var.exp.json +++ b/testdata/d2compiler/TestCompile/import-nested-var.exp.json @@ -3,66 +3,37 @@ "name": "", "isFolderOnly": false, "ast": { - "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:0:0-3:0:42", + "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:0:0-1:0:23", "nodes": [ { - "map_key": { - "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:0:0-2:1:41", - "key": { - "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:0:0-0:11:11", - "path": [ - { - "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:0:0-0:11:11", - "value": [ - { - "string": "development", - "raw_string": "development" - } - ] - } - } - ] - }, - "primary": {}, - "value": { - "map": { - "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:13:13-2:1:41", - "nodes": [ - { - "import": { - "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,1:2:17-1:24:39", - "spread": true, - "pre": "", - "path": [ - { - "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,1:6:21-1:12:27", - "value": [ - { - "string": "models", - "raw_string": "models" - } - ] - } - }, - { - "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,1:13:28-1:24:39", - "value": [ - { - "string": "environment", - "raw_string": "environment" - } - ] - } - } - ] + "import": { + "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:0:0-0:22:22", + "spread": true, + "pre": "", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:4:4-0:10:10", + "value": [ + { + "string": "models", + "raw_string": "models" } - } - ] + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:11:11-0:22:22", + "value": [ + { + "string": "environment", + "raw_string": "environment" + } + ] + } } - } + ] } } ] @@ -93,63 +64,17 @@ }, "edges": null, "objects": [ - { - "id": "development", - "id_val": "development", - "references": [ - { - "key": { - "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:0:0-0:11:11", - "path": [ - { - "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile/import-nested-var.d2,0:0:0-0:11:11", - "value": [ - { - "string": "development", - "raw_string": "development" - } - ] - } - } - ] - }, - "key_path_index": 0, - "map_key_edge_index": -1 - } - ], - "attributes": { - "label": { - "value": "development" - }, - "labelDimensions": { - "width": 0, - "height": 0 - }, - "style": {}, - "iconStyle": {}, - "near_key": null, - "shape": { - "value": "rectangle" - }, - "direction": { - "value": "" - }, - "constraint": null - }, - "zIndex": 0 - }, { "id": "k", "id_val": "k", "references": [ { "key": { - "range": "d2/testdata/d2compiler/TestCompile/models.d2,8:2:56-8:3:57", + "range": "d2/testdata/d2compiler/TestCompile/models.d2,3:4:20-3:5:21", "path": [ { "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile/models.d2,8:2:56-8:3:57", + "range": "d2/testdata/d2compiler/TestCompile/models.d2,3:4:20-3:5:21", "value": [ { "string": "k", From a829683431501f9d4a94f036012d9af6dce090c8 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Thu, 3 Apr 2025 19:54:25 -0600 Subject: [PATCH 5/9] next --- ci/release/changelogs/next.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 092a1ff80..8f68152a0 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -18,6 +18,7 @@ - Watch mode ignores backup files (e.g. files created by certain editors like Helix). [#2131](https://github.com/terrastruct/d2/issues/2131) - Compiler: - `link`s can be set to root path, e.g. `/xyz`. [#2357](https://github.com/terrastruct/d2/issues/2357) + - When importing a file, attempt resolving substitutions at the imported file scope first [#2482](https://github.com/terrastruct/d2/pull/2482) - Parser: - impose max key length. It's almost certainly a mistake if an ID gets too long, e.g. missing quotes [#2465](https://github.com/terrastruct/d2/pull/2465) - Render: From 8813b1771304208ac5743cb158c60f31bedd98fb Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Sun, 6 Apr 2025 23:45:36 -0700 Subject: [PATCH 6/9] plugin: ignore name casing --- d2plugin/plugin.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/d2plugin/plugin.go b/d2plugin/plugin.go index 6708e5e03..fdf2ec25f 100644 --- a/d2plugin/plugin.go +++ b/d2plugin/plugin.go @@ -9,6 +9,7 @@ import ( "context" "encoding/json" "os/exec" + "strings" "oss.terrastruct.com/util-go/xexec" "oss.terrastruct.com/util-go/xmain" @@ -170,7 +171,7 @@ func FindPlugin(ctx context.Context, ps []Plugin, name string) (Plugin, error) { if err != nil { return nil, err } - if info.Name == name { + if strings.EqualFold(info.Name, name) { return p, nil } } From 25f90e9bd7314ef2546f8846a873b71bf5c11045 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Sun, 6 Apr 2025 23:47:31 -0700 Subject: [PATCH 7/9] next --- ci/release/changelogs/next.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 7f7f63458..2d814312d 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -17,6 +17,7 @@ - Support `validate` command. [#2415](https://github.com/terrastruct/d2/pull/2415) - Watch mode ignores backup files (e.g. files created by certain editors like Helix). [#2131](https://github.com/terrastruct/d2/issues/2131) - Support for `--omit-version` flag. [#2377](https://github.com/terrastruct/d2/issues/2377) + - Casing is ignored for plugin names [#2486](https://github.com/terrastruct/d2/pull/2486) - Compiler: - `link`s can be set to root path, e.g. `/xyz`. [#2357](https://github.com/terrastruct/d2/issues/2357) - When importing a file, attempt resolving substitutions at the imported file scope first [#2482](https://github.com/terrastruct/d2/pull/2482) From c366dd8a4294de910b484d6a75f20e5b95f5ab59 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Sat, 12 Apr 2025 02:14:33 -0700 Subject: [PATCH 8/9] d2ir: fix error handling for composite glob filter --- d2compiler/compile_test.go | 12 ++++++++++++ d2ir/compile.go | 2 +- .../TestCompile/composite-glob-filter.exp.json | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 testdata/d2compiler/TestCompile/composite-glob-filter.exp.json diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index 44c4d1bb2..702295099 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -1714,6 +1714,18 @@ steps: { assert.Equal(t, 1, len(g.Layers[0].Steps)) }, }, + { + name: "composite-glob-filter", + + text: ` +*: { + &shape: [a; b] +} +k +`, + expErr: `d2/testdata/d2compiler/TestCompile/composite-glob-filter.d2:3:3: glob filters cannot be composites +d2/testdata/d2compiler/TestCompile/composite-glob-filter.d2:3:3: glob filters cannot be composites`, + }, { name: "import-nested-var", diff --git a/d2ir/compile.go b/d2ir/compile.go index 04754ce48..91c0739e8 100644 --- a/d2ir/compile.go +++ b/d2ir/compile.go @@ -796,7 +796,7 @@ func (c *compiler) ampersandFilter(refctx *RefContext) bool { return false } if len(fa) == 0 { - if refctx.Key.Value.ScalarBox().Unbox().ScalarString() == "*" { + if refctx.Key.Value.ScalarBox().Unbox() != nil && refctx.Key.Value.ScalarBox().Unbox().ScalarString() == "*" { return false } // The field/edge has no value for this filter diff --git a/testdata/d2compiler/TestCompile/composite-glob-filter.exp.json b/testdata/d2compiler/TestCompile/composite-glob-filter.exp.json new file mode 100644 index 000000000..f7cc9c42a --- /dev/null +++ b/testdata/d2compiler/TestCompile/composite-glob-filter.exp.json @@ -0,0 +1,15 @@ +{ + "graph": null, + "err": { + "errs": [ + { + "range": "d2/testdata/d2compiler/TestCompile/composite-glob-filter.d2,2:2:8-2:16:22", + "errmsg": "d2/testdata/d2compiler/TestCompile/composite-glob-filter.d2:3:3: glob filters cannot be composites" + }, + { + "range": "d2/testdata/d2compiler/TestCompile/composite-glob-filter.d2,2:2:8-2:16:22", + "errmsg": "d2/testdata/d2compiler/TestCompile/composite-glob-filter.d2:3:3: glob filters cannot be composites" + } + ] + } +} From 84fcab4adf316a6f9f20fda7f44b73b08c426c43 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Sat, 12 Apr 2025 02:15:33 -0700 Subject: [PATCH 9/9] next --- ci/release/changelogs/next.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 2d814312d..794f22164 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -36,6 +36,7 @@ - fixes inconsistencies when objects were double quoted [#2390](https://github.com/terrastruct/d2/pull/2390) - fixes globs not applying to spread substitutions [#2426](https://github.com/terrastruct/d2/issues/2426) - fixes panic when classes were mixed with layers incorrectly [#2448](https://github.com/terrastruct/d2/pull/2448) + - fixes panic using glob ampersand filters with composite values [#2489](https://github.com/terrastruct/d2/pull/2489) - Formatter: - fixes substitutions in quotes surrounded by text [#2462](https://github.com/terrastruct/d2/pull/2462) - CLI: fetch and render remote images of mimetype octet-stream correctly [#2370](https://github.com/terrastruct/d2/pull/2370)