Skip to content

Commit

Permalink
d2fmt: lowercase reserved keywords
Browse files Browse the repository at this point in the history
  • Loading branch information
alixander committed Sep 15, 2024
1 parent 5e96290 commit de518f5
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 261 deletions.
201 changes: 201 additions & 0 deletions d2ast/keywords.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package d2ast

import "oss.terrastruct.com/d2/lib/label"

// All reserved keywords. See init below.
var ReservedKeywords map[string]struct{}

// Non Style/Holder keywords.
var SimpleReservedKeywords = map[string]struct{}{
"label": {},
"desc": {},
"shape": {},
"icon": {},
"constraint": {},
"tooltip": {},
"link": {},
"near": {},
"width": {},
"height": {},
"direction": {},
"top": {},
"left": {},
"grid-rows": {},
"grid-columns": {},
"grid-gap": {},
"vertical-gap": {},
"horizontal-gap": {},
"class": {},
"vars": {},
}

// ReservedKeywordHolders are reserved keywords that are meaningless on its own and must hold composites
var ReservedKeywordHolders = map[string]struct{}{
"style": {},
"source-arrowhead": {},
"target-arrowhead": {},
}

// CompositeReservedKeywords are reserved keywords that can hold composites
var CompositeReservedKeywords = map[string]struct{}{
"classes": {},
"constraint": {},
"label": {},
"icon": {},
}

// StyleKeywords are reserved keywords which cannot exist outside of the "style" keyword
var StyleKeywords = map[string]struct{}{
"opacity": {},
"stroke": {},
"fill": {},
"fill-pattern": {},
"stroke-width": {},
"stroke-dash": {},
"border-radius": {},

// Only for text
"font": {},
"font-size": {},
"font-color": {},
"bold": {},
"italic": {},
"underline": {},
"text-transform": {},

// Only for shapes
"shadow": {},
"multiple": {},
"double-border": {},

// Only for squares
"3d": {},

// Only for edges
"animated": {},
"filled": {},
}

// TODO maybe autofmt should allow other values, and transform them to conform
// e.g. left-center becomes center-left
var NearConstantsArray = []string{
"top-left",
"top-center",
"top-right",

"center-left",
"center-right",

"bottom-left",
"bottom-center",
"bottom-right",
}
var NearConstants map[string]struct{}

// LabelPositionsArray are the values that labels and icons can set `near` to
var LabelPositionsArray = []string{
"top-left",
"top-center",
"top-right",

"center-left",
"center-center",
"center-right",

"bottom-left",
"bottom-center",
"bottom-right",

"outside-top-left",
"outside-top-center",
"outside-top-right",

"outside-left-top",
"outside-left-center",
"outside-left-bottom",

"outside-right-top",
"outside-right-center",
"outside-right-bottom",

"outside-bottom-left",
"outside-bottom-center",
"outside-bottom-right",
}
var LabelPositions map[string]struct{}

var LabelPositionsMapping = map[string]label.Position{
"top-left": label.InsideTopLeft,
"top-center": label.InsideTopCenter,
"top-right": label.InsideTopRight,

"center-left": label.InsideMiddleLeft,
"center-center": label.InsideMiddleCenter,
"center-right": label.InsideMiddleRight,

"bottom-left": label.InsideBottomLeft,
"bottom-center": label.InsideBottomCenter,
"bottom-right": label.InsideBottomRight,

"outside-top-left": label.OutsideTopLeft,
"outside-top-center": label.OutsideTopCenter,
"outside-top-right": label.OutsideTopRight,

"outside-left-top": label.OutsideLeftTop,
"outside-left-center": label.OutsideLeftMiddle,
"outside-left-bottom": label.OutsideLeftBottom,

"outside-right-top": label.OutsideRightTop,
"outside-right-center": label.OutsideRightMiddle,
"outside-right-bottom": label.OutsideRightBottom,

"outside-bottom-left": label.OutsideBottomLeft,
"outside-bottom-center": label.OutsideBottomCenter,
"outside-bottom-right": label.OutsideBottomRight,
}

var FillPatterns = []string{
"none",
"dots",
"lines",
"grain",
"paper",
}

var TextTransforms = []string{"none", "uppercase", "lowercase", "capitalize"}

// BoardKeywords contains the keywords that create new boards.
var BoardKeywords = map[string]struct{}{
"layers": {},
"scenarios": {},
"steps": {},
}

func init() {
ReservedKeywords = make(map[string]struct{})
for k, v := range SimpleReservedKeywords {
ReservedKeywords[k] = v
}
for k, v := range StyleKeywords {
ReservedKeywords[k] = v
}
for k, v := range ReservedKeywordHolders {
CompositeReservedKeywords[k] = v
}
for k, v := range BoardKeywords {
CompositeReservedKeywords[k] = v
}
for k, v := range CompositeReservedKeywords {
ReservedKeywords[k] = v
}

NearConstants = make(map[string]struct{}, len(NearConstantsArray))
for _, k := range NearConstantsArray {
NearConstants[k] = struct{}{}
}

LabelPositions = make(map[string]struct{}, len(LabelPositionsArray))
for _, k := range LabelPositionsArray {
LabelPositions[k] = struct{}{}
}
}
30 changes: 15 additions & 15 deletions d2compiler/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ func (c *compiler) compileMap(obj *d2graph.Object, m *d2ir.Map) {
if f.Name == "shape" {
continue
}
if _, ok := d2graph.BoardKeywords[f.Name]; ok {
if _, ok := d2ast.BoardKeywords[f.Name]; ok {
continue
}
c.compileField(obj, f)
Expand All @@ -293,12 +293,12 @@ func (c *compiler) compileMap(obj *d2graph.Object, m *d2ir.Map) {

func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
keyword := strings.ToLower(f.Name)
_, isStyleReserved := d2graph.StyleKeywords[keyword]
_, isStyleReserved := d2ast.StyleKeywords[keyword]
if isStyleReserved {
c.errorf(f.LastRef().AST(), "%v must be style.%v", f.Name, f.Name)
return
}
_, isReserved := d2graph.SimpleReservedKeywords[keyword]
_, isReserved := d2ast.SimpleReservedKeywords[keyword]
if f.Name == "classes" {
if f.Map() != nil {
if len(f.Map().Edges) > 0 {
Expand All @@ -309,7 +309,7 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
continue
}
for _, cf := range classesField.Map().Fields {
if _, ok := d2graph.ReservedKeywords[cf.Name]; !ok {
if _, ok := d2ast.ReservedKeywords[cf.Name]; !ok {
c.errorf(cf.LastRef().AST(), "%s is an invalid class field, must be reserved keyword", cf.Name)
}
if cf.Name == "class" {
Expand Down Expand Up @@ -440,7 +440,7 @@ func (c *compiler) compilePosition(attrs *d2graph.Attributes, f *d2ir.Field) {
case *d2ast.Null:
attrs.LabelPosition = nil
default:
if _, ok := d2graph.LabelPositions[scalar.ScalarString()]; !ok {
if _, ok := d2ast.LabelPositions[scalar.ScalarString()]; !ok {
c.errorf(f.LastPrimaryKey(), `invalid "near" field`)
} else {
switch name {
Expand Down Expand Up @@ -686,7 +686,7 @@ func (c *compiler) compileStyle(attrs *d2graph.Attributes, m *d2ir.Map) {
}

func (c *compiler) compileStyleField(attrs *d2graph.Attributes, f *d2ir.Field) {
if _, ok := d2graph.StyleKeywords[strings.ToLower(f.Name)]; !ok {
if _, ok := d2ast.StyleKeywords[strings.ToLower(f.Name)]; !ok {
c.errorf(f.LastRef().AST(), `invalid style keyword: "%s"`, f.Name)
return
}
Expand Down Expand Up @@ -814,7 +814,7 @@ func (c *compiler) compileEdgeMap(edge *d2graph.Edge, m *d2ir.Map) {
}
}
for _, f := range m.Fields {
_, ok := d2graph.ReservedKeywords[f.Name]
_, ok := d2ast.ReservedKeywords[f.Name]
if !ok {
c.errorf(f.References[0].AST(), `edge map keys must be reserved keywords`)
continue
Expand All @@ -825,12 +825,12 @@ func (c *compiler) compileEdgeMap(edge *d2graph.Edge, m *d2ir.Map) {

func (c *compiler) compileEdgeField(edge *d2graph.Edge, f *d2ir.Field) {
keyword := strings.ToLower(f.Name)
_, isStyleReserved := d2graph.StyleKeywords[keyword]
_, isStyleReserved := d2ast.StyleKeywords[keyword]
if isStyleReserved {
c.errorf(f.LastRef().AST(), "%v must be style.%v", f.Name, f.Name)
return
}
_, isReserved := d2graph.SimpleReservedKeywords[keyword]
_, isReserved := d2ast.SimpleReservedKeywords[keyword]
if isReserved {
c.compileReserved(&edge.Attributes, f)
return
Expand Down Expand Up @@ -868,7 +868,7 @@ func (c *compiler) compileArrowheads(edge *d2graph.Edge, f *d2ir.Field) {
if f.Map() != nil {
for _, f2 := range f.Map().Fields {
keyword := strings.ToLower(f2.Name)
_, isReserved := d2graph.SimpleReservedKeywords[keyword]
_, isReserved := d2ast.SimpleReservedKeywords[keyword]
if isReserved {
c.compileReserved(attrs, f2)
continue
Expand Down Expand Up @@ -985,7 +985,7 @@ func (c *compiler) compileSQLTable(obj *d2graph.Object) {

func (c *compiler) validateKeys(obj *d2graph.Object, m *d2ir.Map) {
for _, f := range m.Fields {
if _, ok := d2graph.BoardKeywords[f.Name]; ok {
if _, ok := d2ast.BoardKeywords[f.Name]; ok {
continue
}
c.validateKey(obj, f)
Expand All @@ -994,7 +994,7 @@ func (c *compiler) validateKeys(obj *d2graph.Object, m *d2ir.Map) {

func (c *compiler) validateKey(obj *d2graph.Object, f *d2ir.Field) {
keyword := strings.ToLower(f.Name)
_, isReserved := d2graph.ReservedKeywords[keyword]
_, isReserved := d2ast.ReservedKeywords[keyword]
if isReserved {
switch obj.Shape.Value {
case d2target.ShapeCircle, d2target.ShapeSquare:
Expand Down Expand Up @@ -1067,7 +1067,7 @@ func (c *compiler) validateNear(g *d2graph.Graph) {
for _, obj := range g.Objects {
if obj.NearKey != nil {
nearObj, isKey := g.Root.HasChild(d2graph.Key(obj.NearKey))
_, isConst := d2graph.NearConstants[d2graph.Key(obj.NearKey)[0]]
_, isConst := d2ast.NearConstants[d2graph.Key(obj.NearKey)[0]]
if isKey {
// Doesn't make sense to set near to an ancestor or descendant
nearIsAncestor := false
Expand Down Expand Up @@ -1097,7 +1097,7 @@ func (c *compiler) validateNear(g *d2graph.Graph) {
continue
}
if nearObj.NearKey != nil {
_, nearObjNearIsConst := d2graph.NearConstants[d2graph.Key(nearObj.NearKey)[0]]
_, nearObjNearIsConst := d2ast.NearConstants[d2graph.Key(nearObj.NearKey)[0]]
if nearObjNearIsConst {
c.errorf(obj.NearKey, "near keys cannot be set to an object with a constant near key")
continue
Expand All @@ -1117,7 +1117,7 @@ func (c *compiler) validateNear(g *d2graph.Graph) {
continue
}
} else {
c.errorf(obj.NearKey, "near key %#v must be the absolute path to a shape or one of the following constants: %s", d2format.Format(obj.NearKey), strings.Join(d2graph.NearConstantsArray, ", "))
c.errorf(obj.NearKey, "near key %#v must be the absolute path to a shape or one of the following constants: %s", d2format.Format(obj.NearKey), strings.Join(d2ast.NearConstantsArray, ", "))
continue
}
}
Expand Down
6 changes: 6 additions & 0 deletions d2format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ func (p *printer) interpolationBoxes(boxes []d2ast.InterpolationBox, isDoubleStr
}
b.StringRaw = &s
}
if !isDoubleString {
if _, ok := d2ast.ReservedKeywords[strings.ToLower(*b.StringRaw)]; ok {
s := strings.ToLower(*b.StringRaw)
b.StringRaw = &s
}
}
p.sb.WriteString(*b.StringRaw)
}
}
Expand Down
30 changes: 29 additions & 1 deletion d2format/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ x -> y
exp: `x -> y
`,
},

{
name: "complex",
in: `
Expand Down Expand Up @@ -853,6 +852,35 @@ jeremy: {
!&shape: rectangle
label: I'm not a rectangle
}
`,
},
{
name: "lowercase-reserved",
in: `jacob: {
SHAPE: circle
}
jeremy.SHAPE: rectangle
alice.STYLE.fill: red
bob.style.FILL: red
carmen.STYLE.FILL: red
coop: {
STYLE: {
FILL: blue
}
}
`,
exp: `jacob: {
shape: circle
}
jeremy.shape: rectangle
alice.style.fill: red
bob.style.fill: red
carmen.style.fill: red
coop: {
style: {
fill: blue
}
}
`,
},
}
Expand Down
Loading

0 comments on commit de518f5

Please sign in to comment.