diff --git a/d2compiler/compile.go b/d2compiler/compile.go index 9afa11e6c..50ad03f54 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -327,8 +327,6 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) { attrs.Link = &d2graph.Scalar{} attrs.Link.Value = scalar.ScalarString() attrs.Link.MapKey = f.LastPrimaryKey() - // TODO I think all the attributes need the range actually - attrs.Link.MapKey.Range = scalar.GetRange() case "direction": dirs := []string{"up", "down", "right", "left"} if !go2.Contains(dirs, scalar.ScalarString()) { @@ -729,7 +727,6 @@ func (c *compiler) validateBoardLinks(g *d2graph.Graph) { } linkKey, err := d2parser.ParseKey(obj.Attributes.Link.Value) - // Links can be urls if err != nil { continue } @@ -743,7 +740,7 @@ func (c *compiler) validateBoardLinks(g *d2graph.Graph) { root = root.Parent } if !hasBoard(root, linkKey.IDA()) { - c.errorf(obj.Attributes.Link.MapKey, "link key to board not found") + c.errorf(obj.Attributes.Link.MapKey, "linked board not found") continue } } @@ -756,6 +753,7 @@ func hasBoard(root *d2graph.Graph, ida []string) bool { for i := 0; i < len(ida); i += 2 { id := ida[i] if id == "root" { + i-- continue } if i == len(ida)-1 { @@ -765,19 +763,19 @@ func hasBoard(root *d2graph.Graph, ida []string) bool { if id == "layers" { for _, b := range root.Layers { if b.Name == nextID { - return hasBoard(b, ida[i:]) + return hasBoard(b, ida[i+1:]) } } } else if id == "scenarios" { for _, b := range root.Scenarios { if b.Name == nextID { - return hasBoard(b, ida[i:]) + return hasBoard(b, ida[i+1:]) } } } else if id == "steps" { for _, b := range root.Steps { if b.Name == nextID { - return hasBoard(b, ida[i:]) + return hasBoard(b, ida[i+1:]) } } } diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index 8a6f66cd1..4c08bd92f 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -2063,7 +2063,7 @@ scenarios: { name: "link-board-not-found", text: `x.link: layers.x `, - expErr: `d2/testdata/d2compiler/TestCompile/link-board-not-found.d2:1:9: link key "layers.x" to board not found`, + expErr: `d2/testdata/d2compiler/TestCompile/link-board-not-found.d2:1:1: linked board not found`, }, { name: "link-board-not-board", @@ -2074,7 +2074,7 @@ layers: { y } }`, - expErr: `d2/testdata/d2compiler/TestCompile/link-board-not-board.d2:2:9: link key "layers.x.y" to board not found`, + expErr: `d2/testdata/d2compiler/TestCompile/link-board-not-board.d2:2:1: linked board not found`, }, { name: "link-board-nested", @@ -2139,7 +2139,7 @@ layers: { } } }`, - expErr: `d2/testdata/d2compiler/TestCompile/link-board-underscore-not-found.d2:7:5: board referenced by link not found`, + expErr: `d2/testdata/d2compiler/TestCompile/link-board-underscore-not-found.d2:7:5: linked board not found`, }, } diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index eb1701585..782cd831e 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -56,28 +56,6 @@ func NewGraph() *Graph { return d } -func (g *Graph) AbsID() string { - if g.Parent == nil { - return g.Name - } - for _, l := range g.Parent.Layers { - if l.Name == g.Name { - return g.Parent.AbsID() + ".layers." + g.Name - } - } - for _, s := range g.Parent.Scenarios { - if s.Name == g.Name { - return g.Parent.AbsID() + ".scenarios." + g.Name - } - } - for _, s := range g.Parent.Steps { - if s.Name == g.Name { - return g.Parent.AbsID() + ".steps." + g.Name - } - } - return "" -} - // TODO consider having different Scalar types // Right now we'll hold any types in Value and just convert, e.g. floats type Scalar struct { diff --git a/d2ir/compile.go b/d2ir/compile.go index f6e654612..b6035cb46 100644 --- a/d2ir/compile.go +++ b/d2ir/compile.go @@ -131,31 +131,7 @@ func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext) } else if refctx.Key.Value.ScalarBox().Unbox() != nil { // If these are boards, we transform into absolute paths if f.Name == "link" { - link, err := d2parser.ParseKey(refctx.Key.Value.ScalarBox().Unbox().ScalarString()) - if err == nil { - scopeID, _ := d2parser.ParseKey(refctx.ScopeMap.AbsID()) - scopeIDA := scopeID.IDA() - for i := len(scopeIDA) - 1; i > 0; i-- { - if scopeIDA[i-1] == "layers" || scopeIDA[i-1] == "scenarios" || scopeIDA[i-1] == "steps" || scopeIDA[i-1] == "root" { - scopeIDA = scopeIDA[:i+1] - break - } - } - linkIDA := link.IDA() - if len(linkIDA) > 0 { - for len(linkIDA) > 0 && linkIDA[0] == "_" { - if len(scopeIDA) <= 2 { - c.errorf(refctx.Key.Key, "board referenced by link not found") - return - } - // pop 2 off path per one underscore - scopeIDA = scopeIDA[:len(scopeIDA)-2] - linkIDA = linkIDA[1:] - } - scopeIDA = append(scopeIDA, linkIDA...) - refctx.Key.Value = d2ast.MakeValueBox(d2ast.RawString(strings.Join(scopeIDA, "."), true)) - } - } + c.compileLink(refctx) } f.Primary_ = &Scalar{ parent: f, @@ -164,6 +140,61 @@ func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext) } } +func (c *compiler) compileLink(refctx *RefContext) { + val := refctx.Key.Value.ScalarBox().Unbox().ScalarString() + link, err := d2parser.ParseKey(val) + if err != nil { + return + } + + scopeID, _ := d2parser.ParseKey(refctx.ScopeMap.AbsID()) + scopeIDA := scopeID.IDA() + + if len(scopeIDA) == 0 { + return + } + + linkIDA := link.IDA() + if len(linkIDA) == 0 { + return + } + + // If it doesn't start with one of these reserved words, the link may be a URL or local path or something + if linkIDA[0] != "layers" && linkIDA[0] != "scenarios" && 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" { + scopeIDA = scopeIDA[:i+1] + break + } + if scopeIDA[i-1] == "root" { + scopeIDA = scopeIDA[:i] + break + } + } + + // Resolve underscores + for len(linkIDA) > 0 && linkIDA[0] == "_" { + if len(scopeIDA) < 2 { + c.errorf(refctx.Key.Key, "linked board not found") + return + } + // pop 2 off path per one underscore + scopeIDA = scopeIDA[:len(scopeIDA)-2] + linkIDA = linkIDA[1:] + } + if len(scopeIDA) == 0 { + scopeIDA = []string{"root"} + } + + // Create the absolute path by appending scope path with value specified + scopeIDA = append(scopeIDA, linkIDA...) + refctx.Key.Value = d2ast.MakeValueBox(d2ast.RawString(strings.Join(scopeIDA, "."), true)) +} + func (c *compiler) compileEdges(refctx *RefContext) { if refctx.Key.Key != nil { f, err := refctx.ScopeMap.EnsureField(refctx.Key.Key, refctx) diff --git a/testdata/d2compiler/TestCompile/link-board-key-nested.exp.json b/testdata/d2compiler/TestCompile/link-board-key-nested.exp.json index a31d63b38..9095c2773 100644 --- a/testdata/d2compiler/TestCompile/link-board-key-nested.exp.json +++ b/testdata/d2compiler/TestCompile/link-board-key-nested.exp.json @@ -31,7 +31,7 @@ "nodes": [ { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/link-board-key-nested.d2,1:2:7-1:18:23", "key": { "range": "d2/testdata/d2compiler/TestCompile/link-board-key-nested.d2,1:2:7-1:8:13", "path": [ @@ -65,7 +65,7 @@ "range": ",0:0:0-0:0:0", "value": [ { - "string": "root.x.layers.x" + "string": "root.layers.x" } ] } @@ -280,7 +280,7 @@ }, "style": {}, "link": { - "value": "root.x.layers.x" + "value": "root.layers.x" }, "near_key": null, "shape": { @@ -329,7 +329,7 @@ "nodes": [ { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/link-board-key-nested.d2,1:2:7-1:18:23", "key": { "range": "d2/testdata/d2compiler/TestCompile/link-board-key-nested.d2,1:2:7-1:8:13", "path": [ @@ -363,7 +363,7 @@ "range": ",0:0:0-0:0:0", "value": [ { - "string": "root.x.layers.x" + "string": "root.layers.x" } ] } diff --git a/testdata/d2compiler/TestCompile/link-board-mixed.exp.json b/testdata/d2compiler/TestCompile/link-board-mixed.exp.json index ef65c9ac2..ac393f695 100644 --- a/testdata/d2compiler/TestCompile/link-board-mixed.exp.json +++ b/testdata/d2compiler/TestCompile/link-board-mixed.exp.json @@ -40,7 +40,7 @@ }, { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/link-board-mixed.d2,1:0:31-1:25:56", "key": { "range": "d2/testdata/d2compiler/TestCompile/link-board-mixed.d2,1:0:31-1:13:44", "path": [ @@ -461,7 +461,7 @@ }, { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/link-board-mixed.d2,1:0:31-1:25:56", "key": { "range": "d2/testdata/d2compiler/TestCompile/link-board-mixed.d2,1:0:31-1:13:44", "path": [ @@ -932,7 +932,7 @@ }, { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/link-board-mixed.d2,1:0:31-1:25:56", "key": { "range": "d2/testdata/d2compiler/TestCompile/link-board-mixed.d2,1:0:31-1:13:44", "path": [ diff --git a/testdata/d2compiler/TestCompile/link-board-nested.exp.json b/testdata/d2compiler/TestCompile/link-board-nested.exp.json index 5f2926494..40561e63c 100644 --- a/testdata/d2compiler/TestCompile/link-board-nested.exp.json +++ b/testdata/d2compiler/TestCompile/link-board-nested.exp.json @@ -7,7 +7,7 @@ "nodes": [ { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/link-board-nested.d2,0:0:0-0:25:25", "key": { "range": "d2/testdata/d2compiler/TestCompile/link-board-nested.d2,0:0:0-0:6:6", "path": [ @@ -287,7 +287,7 @@ "nodes": [ { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/link-board-nested.d2,0:0:0-0:25:25", "key": { "range": "d2/testdata/d2compiler/TestCompile/link-board-nested.d2,0:0:0-0:6:6", "path": [ @@ -505,7 +505,7 @@ "nodes": [ { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/link-board-nested.d2,0:0:0-0:25:25", "key": { "range": "d2/testdata/d2compiler/TestCompile/link-board-nested.d2,0:0:0-0:6:6", "path": [ diff --git a/testdata/d2compiler/TestCompile/link-board-not-board.exp.json b/testdata/d2compiler/TestCompile/link-board-not-board.exp.json index e37e9b481..d8f1db764 100644 --- a/testdata/d2compiler/TestCompile/link-board-not-board.exp.json +++ b/testdata/d2compiler/TestCompile/link-board-not-board.exp.json @@ -4,8 +4,8 @@ "ioerr": null, "errs": [ { - "range": "d2/testdata/d2compiler/TestCompile/link-board-not-board.d2,1:8:12-1:18:22", - "errmsg": "d2/testdata/d2compiler/TestCompile/link-board-not-board.d2:2:9: link key \"layers.x.y\" to board not found" + "range": "d2/testdata/d2compiler/TestCompile/link-board-not-board.d2,1:0:4-1:18:22", + "errmsg": "d2/testdata/d2compiler/TestCompile/link-board-not-board.d2:2:1: linked board not found" } ] } diff --git a/testdata/d2compiler/TestCompile/link-board-not-found.exp.json b/testdata/d2compiler/TestCompile/link-board-not-found.exp.json index 8c8509b5d..29ff2fdde 100644 --- a/testdata/d2compiler/TestCompile/link-board-not-found.exp.json +++ b/testdata/d2compiler/TestCompile/link-board-not-found.exp.json @@ -4,8 +4,8 @@ "ioerr": null, "errs": [ { - "range": "d2/testdata/d2compiler/TestCompile/link-board-not-found.d2,0:8:8-0:16:16", - "errmsg": "d2/testdata/d2compiler/TestCompile/link-board-not-found.d2:1:9: link key \"layers.x\" to board not found" + "range": "d2/testdata/d2compiler/TestCompile/link-board-not-found.d2,0:0:0-0:16:16", + "errmsg": "d2/testdata/d2compiler/TestCompile/link-board-not-found.d2:1:1: linked board not found" } ] } diff --git a/testdata/d2compiler/TestCompile/link-board-ok.exp.json b/testdata/d2compiler/TestCompile/link-board-ok.exp.json index c704c8a7c..61cf43cd7 100644 --- a/testdata/d2compiler/TestCompile/link-board-ok.exp.json +++ b/testdata/d2compiler/TestCompile/link-board-ok.exp.json @@ -7,7 +7,7 @@ "nodes": [ { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/link-board-ok.d2,0:0:0-0:16:16", "key": { "range": "d2/testdata/d2compiler/TestCompile/link-board-ok.d2,0:0:0-0:6:6", "path": [ @@ -229,7 +229,7 @@ "nodes": [ { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/link-board-ok.d2,0:0:0-0:16:16", "key": { "range": "d2/testdata/d2compiler/TestCompile/link-board-ok.d2,0:0:0-0:6:6", "path": [ diff --git a/testdata/d2compiler/TestCompile/link-board-underscore-not-found.exp.json b/testdata/d2compiler/TestCompile/link-board-underscore-not-found.exp.json index 040712eda..b1ef0359c 100644 --- a/testdata/d2compiler/TestCompile/link-board-underscore-not-found.exp.json +++ b/testdata/d2compiler/TestCompile/link-board-underscore-not-found.exp.json @@ -5,7 +5,7 @@ "errs": [ { "range": "d2/testdata/d2compiler/TestCompile/link-board-underscore-not-found.d2,6:4:50-6:14:60", - "errmsg": "d2/testdata/d2compiler/TestCompile/link-board-underscore-not-found.d2:7:5: board referenced by link not found" + "errmsg": "d2/testdata/d2compiler/TestCompile/link-board-underscore-not-found.d2:7:5: linked board not found" } ] } diff --git a/testdata/d2compiler/TestCompile/link-board-underscore.exp.json b/testdata/d2compiler/TestCompile/link-board-underscore.exp.json index fa2ef2725..7f12b70d7 100644 --- a/testdata/d2compiler/TestCompile/link-board-underscore.exp.json +++ b/testdata/d2compiler/TestCompile/link-board-underscore.exp.json @@ -149,7 +149,7 @@ "nodes": [ { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/link-board-underscore.d2,6:4:50-6:28:74", "key": { "range": "d2/testdata/d2compiler/TestCompile/link-board-underscore.d2,6:4:50-6:14:60", "path": [ @@ -192,7 +192,7 @@ }, { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/link-board-underscore.d2,7:4:79-7:15:90", "key": { "range": "d2/testdata/d2compiler/TestCompile/link-board-underscore.d2,7:4:79-7:12:87", "path": [ @@ -481,7 +481,7 @@ "nodes": [ { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/link-board-underscore.d2,6:4:50-6:28:74", "key": { "range": "d2/testdata/d2compiler/TestCompile/link-board-underscore.d2,6:4:50-6:14:60", "path": [ @@ -524,7 +524,7 @@ }, { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/link-board-underscore.d2,7:4:79-7:15:90", "key": { "range": "d2/testdata/d2compiler/TestCompile/link-board-underscore.d2,7:4:79-7:12:87", "path": [ @@ -813,7 +813,7 @@ "nodes": [ { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/link-board-underscore.d2,6:4:50-6:28:74", "key": { "range": "d2/testdata/d2compiler/TestCompile/link-board-underscore.d2,6:4:50-6:14:60", "path": [ @@ -856,7 +856,7 @@ }, { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/link-board-underscore.d2,7:4:79-7:15:90", "key": { "range": "d2/testdata/d2compiler/TestCompile/link-board-underscore.d2,7:4:79-7:12:87", "path": [ diff --git a/testdata/d2compiler/TestCompile/path_link.exp.json b/testdata/d2compiler/TestCompile/path_link.exp.json index 69eba972b..9c08d24ad 100644 --- a/testdata/d2compiler/TestCompile/path_link.exp.json +++ b/testdata/d2compiler/TestCompile/path_link.exp.json @@ -31,7 +31,7 @@ "nodes": [ { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/path_link.d2,1:2:7-1:39:44", "key": { "range": "d2/testdata/d2compiler/TestCompile/path_link.d2,1:2:7-1:6:11", "path": [ @@ -50,11 +50,12 @@ }, "primary": {}, "value": { - "double_quoted_string": { - "range": ",0:0:0-0:0:0", + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/path_link.d2,1:8:13-1:39:44", "value": [ { - "string": "root.x.Overview.Untitled board 7.zzzzz" + "string": "Overview.Untitled board 7.zzzzz", + "raw_string": "Overview.Untitled board 7.zzzzz" } ] } @@ -130,7 +131,7 @@ }, "style": {}, "link": { - "value": "root.x.Overview.Untitled board 7.zzzzz" + "value": "Overview.Untitled board 7.zzzzz" }, "near_key": null, "shape": { diff --git a/testdata/d2compiler/TestCompile/url_link.exp.json b/testdata/d2compiler/TestCompile/url_link.exp.json index 2f056a514..f800be68e 100644 --- a/testdata/d2compiler/TestCompile/url_link.exp.json +++ b/testdata/d2compiler/TestCompile/url_link.exp.json @@ -31,7 +31,7 @@ "nodes": [ { "map_key": { - "range": ",0:0:0-0:0:0", + "range": "d2/testdata/d2compiler/TestCompile/url_link.d2,1:2:7-1:26:31", "key": { "range": "d2/testdata/d2compiler/TestCompile/url_link.d2,1:2:7-1:6:11", "path": [ @@ -50,11 +50,12 @@ }, "primary": {}, "value": { - "double_quoted_string": { - "range": ",0:0:0-0:0:0", + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile/url_link.d2,1:8:13-1:26:31", "value": [ { - "string": "root.x.https" + "string": "https://google.com", + "raw_string": "https://google.com" } ] } @@ -130,7 +131,7 @@ }, "style": {}, "link": { - "value": "root.x.https" + "value": "https://google.com" }, "near_key": null, "shape": {