diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index bf5e51949..743ebcb24 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -8,6 +8,7 @@ - Improved readability of connection labels when they overlap another connection. [#447](https://github.com/terrastruct/d2/pull/447) - Error message when `shape` is given a composite [#1415](https://github.com/terrastruct/d2/pull/1415) - Improved rendering and text measurement for code shapes. [#1425](https://github.com/terrastruct/d2/pull/1425) +- The autoformatter moves board declarations to the bottom of its scope. [#1424](https://github.com/terrastruct/d2/pull/1424) #### Bugfixes ⛑️ diff --git a/d2ast/d2ast.go b/d2ast/d2ast.go index eee17d368..a5be3fa98 100644 --- a/d2ast/d2ast.go +++ b/d2ast/d2ast.go @@ -794,6 +794,18 @@ func (mb MapNodeBox) Unbox() MapNode { } } +func (mb MapNodeBox) IsBoardNode() bool { + if mb.MapKey == nil || mb.MapKey.Key == nil || len(mb.MapKey.Key.Path) != 1 { + return false + } + switch mb.MapKey.Key.Path[0].Unbox().ScalarString() { + case "layers", "scenarios", "steps": + return true + default: + return false + } +} + // ArrayNodeBox is used to box ArrayNode for JSON persistence. type ArrayNodeBox struct { Comment *Comment `json:"comment,omitempty"` diff --git a/d2format/format.go b/d2format/format.go index b915c2638..dc7430ec2 100644 --- a/d2format/format.go +++ b/d2format/format.go @@ -275,10 +275,27 @@ func (p *printer) _map(m *d2ast.Map) { } } + layerNodes := []d2ast.MapNodeBox{} + scenarioNodes := []d2ast.MapNodeBox{} + stepNodes := []d2ast.MapNodeBox{} + prev := d2ast.Node(m) for i := 0; i < len(m.Nodes); i++ { nb := m.Nodes[i] n := nb.Unbox() + // extract out layer, scenario, and step nodes and skip + if nb.IsBoardNode() { + switch nb.MapKey.Key.Path[0].Unbox().ScalarString() { + case "layers": + layerNodes = append(layerNodes, nb) + case "scenarios": + scenarioNodes = append(scenarioNodes, nb) + case "steps": + stepNodes = append(stepNodes, nb) + } + prev = n + continue + } // Handle inline comments. if i > 0 && (nb.Comment != nil || nb.BlockComment != nil) { @@ -306,6 +323,26 @@ func (p *printer) _map(m *d2ast.Map) { prev = n } + boards := []d2ast.MapNodeBox{} + boards = append(boards, layerNodes...) + boards = append(boards, scenarioNodes...) + boards = append(boards, stepNodes...) + + // draw board nodes + for i := 0; i < len(boards); i++ { + n := boards[i].Unbox() + // if this board is the very first line of the file, don't add an extra newline + if n.GetRange().Start.Line != 0 { + p.newline() + } + // if scope only has boards, don't newline the first board + if i != 0 || len(m.Nodes) > len(boards) { + p.newline() + } + p.node(n) + prev = n + } + if !m.IsFileMap() { if !m.Range.OneLine() { p.deindent() diff --git a/d2format/format_test.go b/d2format/format_test.go index 3c957129d..889b01537 100644 --- a/d2format/format_test.go +++ b/d2format/format_test.go @@ -152,14 +152,14 @@ meow representation: {type: jsonb} diagram: int {constraint: foreign_key} } - + meow <- diagrams.id + steps: { shape: sql_table id: {type: int; constraint: primary_key} representation: {type: jsonb} diagram: int {constraint: foreign_key} } - meow <- diagrams.id } D2 AST Parser: { @@ -657,6 +657,159 @@ x: @../file x: @"x/../file" `, exp: `x: @file +`, + }, + { + name: "layers_scenarios_steps_bottom_simple", + in: `layers: { + b: { + e + scenarios: { + p: { + x + } + } + } + steps: { + a + } +} +`, + exp: `layers: { + b: { + e + + scenarios: { + p: { + x + } + } + } + + steps: { + a + } +} +`, + }, + { + name: "layers_scenarios_steps_bottom_complex", + in: `a + +scenarios: { + + + + scenario-1: { + steps: { + step-1: { + Test + } + step-2 + } + non-step + } +} + +layers: { + Test super nested: { + base-layer + layers: { + layers: { + grand-child-layer: { + grand-child-board + } + } + layer-board + } + last-layer + } +} +b +steps: { + 1: { + step-1-content + } +} + + +scenarios: { + scenario-2: { + scenario-2-content + } +} + +c +d + +only-layers: { + layers: { + X + Y + } + layers: { + Z + } +} +`, + exp: `a +b + +c +d + +only-layers: { + layers: { + X + Y + } + + layers: { + Z + } +} + +layers: { + Test super nested: { + base-layer + last-layer + + layers: { + layer-board + + layers: { + grand-child-layer: { + grand-child-board + } + } + } + } +} + +scenarios: { + scenario-1: { + non-step + + steps: { + step-1: { + Test + } + step-2 + } + } +} + +scenarios: { + scenario-2: { + scenario-2-content + } +} + +steps: { + 1: { + step-1-content + } +} `, }, }