Skip to content

Commit

Permalink
Merge pull request #1446 from alixander/null
Browse files Browse the repository at this point in the history
Null keyword
  • Loading branch information
alixander authored Jun 27, 2023
2 parents a5b3ad9 + 7973e49 commit f7a83fa
Show file tree
Hide file tree
Showing 21 changed files with 3,390 additions and 136 deletions.
3 changes: 0 additions & 3 deletions d2compiler/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,9 +340,6 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
func (c *compiler) compileLabel(attrs *d2graph.Attributes, f d2ir.Node) {
scalar := f.Primary().Value
switch scalar := scalar.(type) {
case *d2ast.Null:
// TODO: Delete instead.
attrs.Label.Value = scalar.ScalarString()
case *d2ast.BlockString:
if strings.TrimSpace(scalar.ScalarString()) == "" {
c.errorf(f.LastPrimaryKey(), "block string cannot be empty")
Expand Down
232 changes: 232 additions & 0 deletions d2compiler/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2743,6 +2743,7 @@ func TestCompile2(t *testing.T) {

t.Run("boards", testBoards)
t.Run("seqdiagrams", testSeqDiagrams)
t.Run("nulls", testNulls)
}

func testBoards(t *testing.T) {
Expand Down Expand Up @@ -2923,6 +2924,237 @@ Office chatter: {
})
}

func testNulls(t *testing.T) {
t.Parallel()

t.Run("basic", func(t *testing.T) {
t.Parallel()

tca := []struct {
name string
skip bool
run func(t *testing.T)
}{
{
name: "shape",
run: func(t *testing.T) {
g := assertCompile(t, `
a
a: null
`, "")
assert.Equal(t, 0, len(g.Objects))
},
},
{
name: "edge",
run: func(t *testing.T) {
g := assertCompile(t, `
a -> b
(a -> b)[0]: null
`, "")
assert.Equal(t, 2, len(g.Objects))
assert.Equal(t, 0, len(g.Edges))
},
},
{
name: "attribute",
run: func(t *testing.T) {
g := assertCompile(t, `
a.style.opacity: 0.2
a.style.opacity: null
`, "")
assert.Equal(t, (*d2graph.Scalar)(nil), g.Objects[0].Attributes.Style.Opacity)
},
},
}

for _, tc := range tca {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if tc.skip {
t.SkipNow()
}
tc.run(t)
})
}
})

t.Run("reappear", func(t *testing.T) {
t.Parallel()

tca := []struct {
name string
skip bool
run func(t *testing.T)
}{
{
name: "shape",
run: func(t *testing.T) {
g := assertCompile(t, `
a
a: null
a
`, "")
assert.Equal(t, 1, len(g.Objects))
},
},
{
name: "edge",
run: func(t *testing.T) {
g := assertCompile(t, `
a -> b
(a -> b)[0]: null
a -> b
`, "")
assert.Equal(t, 2, len(g.Objects))
assert.Equal(t, 1, len(g.Edges))
},
},
{
name: "attribute-reset",
run: func(t *testing.T) {
g := assertCompile(t, `
a.style.opacity: 0.2
a: null
a
`, "")
assert.Equal(t, 1, len(g.Objects))
assert.Equal(t, (*d2graph.Scalar)(nil), g.Objects[0].Attributes.Style.Opacity)
},
},
{
name: "edge-reset",
run: func(t *testing.T) {
g := assertCompile(t, `
a -> b
a: null
a
`, "")
assert.Equal(t, 2, len(g.Objects))
assert.Equal(t, 0, len(g.Edges))
},
},
{
name: "children-reset",
run: func(t *testing.T) {
g := assertCompile(t, `
a.b.c
a.b: null
a.b
`, "")
assert.Equal(t, 2, len(g.Objects))
},
},
}

for _, tc := range tca {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if tc.skip {
t.SkipNow()
}
tc.run(t)
})
}
})

t.Run("implicit", func(t *testing.T) {
t.Parallel()

tca := []struct {
name string
skip bool
run func(t *testing.T)
}{
{
name: "delete-connection",
run: func(t *testing.T) {
g := assertCompile(t, `
x -> y
y: null
`, "")
assert.Equal(t, 1, len(g.Objects))
assert.Equal(t, 0, len(g.Edges))
},
},
{
name: "no-delete-connection",
run: func(t *testing.T) {
g := assertCompile(t, `
y: null
x -> y
`, "")
assert.Equal(t, 2, len(g.Objects))
assert.Equal(t, 1, len(g.Edges))
},
},
{
name: "delete-children",
run: func(t *testing.T) {
g := assertCompile(t, `
x.y.z
a.b.c
x: null
a.b: null
`, "")
assert.Equal(t, 1, len(g.Objects))
},
},
}

for _, tc := range tca {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if tc.skip {
t.SkipNow()
}
tc.run(t)
})
}
})

t.Run("multiboard", func(t *testing.T) {
t.Parallel()

tca := []struct {
name string
skip bool
run func(t *testing.T)
}{
{
name: "scenario",
run: func(t *testing.T) {
g := assertCompile(t, `
x
scenarios: {
a: {
x: null
}
}
`, "")
assert.Equal(t, 0, len(g.Scenarios[0].Objects))
},
},
}

for _, tc := range tca {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if tc.skip {
t.SkipNow()
}
tc.run(t)
})
}
})
}

func assertCompile(t *testing.T, text string, expErr string) *d2graph.Graph {
d2Path := fmt.Sprintf("d2/testdata/d2compiler/%v.d2", t.Name())
g, err := d2compiler.Compile(d2Path, strings.NewReader(text), nil)
Expand Down
9 changes: 9 additions & 0 deletions d2ir/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ func (c *compiler) compileKey(refctx *RefContext) {
}

func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext) {
if refctx.Key != nil && len(refctx.Key.Edges) == 0 && refctx.Key.Value.Null != nil {
dst.DeleteField(kp.IDA()...)
return
}
f, err := dst.EnsureField(kp, refctx)
if err != nil {
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
Expand Down Expand Up @@ -353,6 +357,11 @@ func (c *compiler) compileEdges(refctx *RefContext) {

eida := NewEdgeIDs(refctx.Key)
for i, eid := range eida {
if refctx.Key != nil && refctx.Key.Value.Null != nil {
refctx.ScopeMap.DeleteEdge(eid)
continue
}

refctx = refctx.Copy()
refctx.Edge = refctx.Key.Edges[i]

Expand Down
6 changes: 1 addition & 5 deletions d2ir/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,7 @@ func testCompileFields(t *testing.T) {
m, err := compile(t, `pq: pq
pq: null`)
assert.Success(t, err)
assertQuery(t, m, 1, 0, nil, "")
// null doesn't delete pq from *Map so that for language tooling
// we maintain the references.
// Instead d2compiler will ensure it doesn't get rendered.
assertQuery(t, m, 0, 0, nil, "pq")
assertQuery(t, m, 0, 0, nil, "")
},
},
}
Expand Down
40 changes: 40 additions & 0 deletions d2ir/d2ir.go
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,20 @@ func (m *Map) ensureField(i int, kp *d2ast.KeyPath, refctx *RefContext) (*Field,
return f.Map().ensureField(i+1, kp, refctx)
}

func (m *Map) DeleteEdge(eid *EdgeID) *Edge {
if eid == nil {
return nil
}

for i, e := range m.Edges {
if e.ID.Match(eid) {
m.Edges = append(m.Edges[:i], m.Edges[i+1:]...)
return e
}
}
return nil
}

func (m *Map) DeleteField(ida ...string) *Field {
if len(ida) == 0 {
return nil
Expand All @@ -751,7 +765,33 @@ func (m *Map) DeleteField(ida ...string) *Field {
continue
}
if len(rest) == 0 {
for _, fr := range f.References {
for i, e := range m.Edges {
for _, er := range e.References {
if er.Context == fr.Context {
m.DeleteEdge(e.ID)
i--
break
}
}
}
}
m.Fields = append(m.Fields[:i], m.Fields[i+1:]...)

// If a field was deleted from a keyword-holder keyword and that holder is empty,
// then that holder becomes meaningless and should be deleted too
parent := ParentField(f)
for keyword := range d2graph.ReservedKeywordHolders {
if parent != nil && parent.Name == keyword && len(parent.Map().Fields) == 0 {
styleParentMap := ParentMap(parent)
for i, f := range styleParentMap.Fields {
if f.Name == keyword {
styleParentMap.Fields = append(styleParentMap.Fields[:i], styleParentMap.Fields[i+1:]...)
break
}
}
}
}
return f
}
if f.Map() != nil {
Expand Down
Loading

0 comments on commit f7a83fa

Please sign in to comment.