diff --git a/go.mod b/go.mod index 2be59e4c50..39d862e444 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/rivo/uniseg v0.2.1-0.20211004051800-57c86be7915a github.com/schollz/progressbar/v3 v3.8.3 github.com/stretchr/testify v1.7.1 - github.com/turbolent/prettier v0.0.0-20210613180524-3a3f5a5b49ba + github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d go.uber.org/goleak v1.1.10 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 golang.org/x/text v0.3.7 diff --git a/go.sum b/go.sum index 6dce08559b..8895ff92f1 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/turbolent/prettier v0.0.0-20210613180524-3a3f5a5b49ba h1:GPg+SVJURgCt6b4IwuRQupixdBM+KzjXPGvawnaQ15E= -github.com/turbolent/prettier v0.0.0-20210613180524-3a3f5a5b49ba/go.mod h1:Nlx5Y115XQvNcIdIy7dZXaNSUpzwBSge4/Ivk93/Yog= +github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d h1:5JInRQbk5UBX8JfUvKh2oYTLMVwj3p6n+wapDDm7hko= +github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d/go.mod h1:Nlx5Y115XQvNcIdIy7dZXaNSUpzwBSge4/Ivk93/Yog= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= diff --git a/runtime/ast/argument.go b/runtime/ast/argument.go index 636627f211..19c479f155 100644 --- a/runtime/ast/argument.go +++ b/runtime/ast/argument.go @@ -20,7 +20,8 @@ package ast import ( "encoding/json" - "strings" + + "github.com/turbolent/prettier" "github.com/onflow/cadence/runtime/common" ) @@ -68,13 +69,7 @@ func (a *Argument) EndPosition(memoryGauge common.MemoryGauge) Position { } func (a *Argument) String() string { - var builder strings.Builder - if a.Label != "" { - builder.WriteString(a.Label) - builder.WriteString(": ") - } - builder.WriteString(a.Expression.String()) - return builder.String() + return Prettier(a) } func (a *Argument) MarshalJSON() ([]byte, error) { @@ -87,3 +82,14 @@ func (a *Argument) MarshalJSON() ([]byte, error) { Alias: (*Alias)(a), }) } + +func (a *Argument) Doc() prettier.Doc { + argumentDoc := a.Expression.Doc() + if a.Label == "" { + return argumentDoc + } + return prettier.Concat{ + prettier.Text(a.Label + ": "), + argumentDoc, + } +} diff --git a/runtime/ast/argument_test.go b/runtime/ast/argument_test.go index 56466dc654..4bf74b2808 100644 --- a/runtime/ast/argument_test.go +++ b/runtime/ast/argument_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/turbolent/prettier" ) func TestArgument_MarshalJSON(t *testing.T) { @@ -108,3 +109,86 @@ func TestArgument_MarshalJSON(t *testing.T) { ) }) } + +func TestArgument_Doc(t *testing.T) { + + t.Parallel() + + t.Run("without label", func(t *testing.T) { + + t.Parallel() + + argument := &Argument{ + Expression: &BoolExpression{ + Value: false, + }, + } + + require.Equal( + t, + prettier.Text("false"), + argument.Doc(), + ) + }) + + t.Run("with label", func(t *testing.T) { + + t.Parallel() + + argument := &Argument{ + Label: "ok", + Expression: &BoolExpression{ + Value: false, + }, + } + + require.Equal( + t, + prettier.Concat{ + prettier.Text("ok: "), + prettier.Text("false"), + }, + argument.Doc(), + ) + }) +} + +func TestArgument_String(t *testing.T) { + + t.Parallel() + + t.Run("without label", func(t *testing.T) { + + t.Parallel() + + argument := &Argument{ + Expression: &BoolExpression{ + Value: false, + }, + } + + require.Equal( + t, + "false", + argument.String(), + ) + }) + + t.Run("with label", func(t *testing.T) { + + t.Parallel() + + argument := &Argument{ + Label: "ok", + Expression: &BoolExpression{ + Value: false, + }, + } + + require.Equal( + t, + "ok: false", + argument.String(), + ) + }) +} diff --git a/runtime/ast/block.go b/runtime/ast/block.go index 0b122a687b..3e4d33b232 100644 --- a/runtime/ast/block.go +++ b/runtime/ast/block.go @@ -78,23 +78,21 @@ func (b *Block) Doc() prettier.Doc { } func StatementsDoc(statements []Statement) prettier.Doc { - var statementsDoc prettier.Concat + var doc prettier.Concat for _, statement := range statements { - // TODO: replace once Statement implements Doc - hasDoc, ok := statement.(interface{ Doc() prettier.Doc }) - if !ok { - continue - } - - statementsDoc = append( - statementsDoc, + doc = append( + doc, prettier.HardLine{}, - hasDoc.Doc(), + statement.Doc(), ) } - return statementsDoc + return doc +} + +func (b *Block) String() string { + return Prettier(b) } func (b *Block) MarshalJSON() ([]byte, error) { @@ -174,6 +172,61 @@ func (b *FunctionBlock) EndPosition(common.MemoryGauge) Position { return b.Block.EndPos } +var preConditionsKeywordDoc = prettier.Text("pre") +var postConditionsKeywordDoc = prettier.Text("post") + +func (b *FunctionBlock) Doc() prettier.Doc { + if b.IsEmpty() { + return blockEmptyDoc + } + + var conditionDocs []prettier.Doc + + if conditionsDoc := b.PreConditions.Doc(preConditionsKeywordDoc); conditionsDoc != nil { + conditionDocs = append( + conditionDocs, + prettier.HardLine{}, + conditionsDoc, + ) + } + + if conditionsDoc := b.PostConditions.Doc(postConditionsKeywordDoc); conditionsDoc != nil { + conditionDocs = append( + conditionDocs, + prettier.HardLine{}, + conditionsDoc, + ) + } + + var bodyDoc prettier.Doc + + statementsDoc := StatementsDoc(b.Block.Statements) + + if len(conditionDocs) > 0 { + bodyConcatDoc := prettier.Concat(conditionDocs) + bodyConcatDoc = append( + bodyConcatDoc, + statementsDoc, + ) + bodyDoc = bodyConcatDoc + } else { + bodyDoc = statementsDoc + } + + return prettier.Concat{ + blockStartDoc, + prettier.Indent{ + Doc: bodyDoc, + }, + prettier.HardLine{}, + blockEndDoc, + } +} + +func (b *FunctionBlock) String() string { + return Prettier(b) +} + // Condition type Condition struct { @@ -182,6 +235,26 @@ type Condition struct { Message Expression } +func (c Condition) Doc() prettier.Doc { + doc := c.Test.Doc() + if c.Message != nil { + doc = prettier.Concat{ + doc, + prettier.Text(":"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + c.Message.Doc(), + }, + }, + } + } + + return prettier.Group{ + Doc: doc, + } +} + // Conditions type Conditions []*Condition @@ -189,3 +262,32 @@ type Conditions []*Condition func (c *Conditions) IsEmpty() bool { return c == nil || len(*c) == 0 } + +func (c *Conditions) Doc(keywordDoc prettier.Doc) prettier.Doc { + if c.IsEmpty() { + return nil + } + + var doc prettier.Concat + + for _, condition := range *c { + doc = append( + doc, + prettier.HardLine{}, + condition.Doc(), + ) + } + + return prettier.Group{ + Doc: prettier.Concat{ + keywordDoc, + prettier.Space, + blockStartDoc, + prettier.Indent{ + Doc: doc, + }, + prettier.HardLine{}, + blockEndDoc, + }, + } +} diff --git a/runtime/ast/block_test.go b/runtime/ast/block_test.go index 0edc20ce97..197e539c3c 100644 --- a/runtime/ast/block_test.go +++ b/runtime/ast/block_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/turbolent/prettier" ) func TestBlock_MarshalJSON(t *testing.T) { @@ -76,6 +77,73 @@ func TestBlock_MarshalJSON(t *testing.T) { ) } +func TestBlock_Doc(t *testing.T) { + + t.Parallel() + + block := &Block{ + Statements: []Statement{ + &ExpressionStatement{ + Expression: &BoolExpression{ + Value: false, + }, + }, + &ExpressionStatement{ + Expression: &StringExpression{ + Value: "test", + }, + }, + }, + } + + require.Equal( + t, + prettier.Concat{ + prettier.Text("{"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Text("false"), + prettier.HardLine{}, + prettier.Text("\"test\""), + }, + }, + prettier.HardLine{}, + prettier.Text("}"), + }, + block.Doc(), + ) +} + +func TestBlock_String(t *testing.T) { + + t.Parallel() + + block := &Block{ + Statements: []Statement{ + &ExpressionStatement{ + Expression: &BoolExpression{ + Value: false, + }, + }, + &ExpressionStatement{ + Expression: &StringExpression{ + Value: "test", + }, + }, + }, + } + + require.Equal( + t, + "{\n"+ + " false\n"+ + ` "test"`+"\n"+ + "}", + block.String(), + ) +} + func TestFunctionBlock_MarshalJSON(t *testing.T) { t.Parallel() @@ -244,3 +312,259 @@ func TestFunctionBlock_MarshalJSON(t *testing.T) { ) }) } + +func TestFunctionBlock_Doc(t *testing.T) { + + t.Parallel() + + t.Run("with statements", func(t *testing.T) { + + t.Parallel() + + block := &FunctionBlock{ + Block: &Block{ + Statements: []Statement{ + &ExpressionStatement{ + Expression: &BoolExpression{ + Value: false, + }, + }, + &ExpressionStatement{ + Expression: &StringExpression{ + Value: "test", + }, + }, + }, + }, + } + + require.Equal( + t, + prettier.Concat{ + prettier.Text("{"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Text("false"), + prettier.HardLine{}, + prettier.Text("\"test\""), + }}, + prettier.HardLine{}, + prettier.Text("}"), + }, + block.Doc(), + ) + }) + + t.Run("with preconditions and postconditions", func(t *testing.T) { + + t.Parallel() + + block := &FunctionBlock{ + Block: &Block{ + Statements: []Statement{ + &ExpressionStatement{ + Expression: &BoolExpression{ + Value: false, + }, + }, + &ExpressionStatement{ + Expression: &StringExpression{ + Value: "test", + }, + }, + }, + }, + PreConditions: &Conditions{ + { + Kind: ConditionKindPre, + Test: &BoolExpression{ + Value: false, + }, + Message: &StringExpression{ + Value: "Pre failed", + }, + }, + }, + PostConditions: &Conditions{ + { + Kind: ConditionKindPost, + Test: &BoolExpression{ + Value: true, + }, + Message: &StringExpression{ + Value: "Post failed", + }, + }, + }, + } + + require.Equal( + t, + prettier.Concat{ + prettier.Text("{"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("pre"), + prettier.Text(" "), + prettier.Text("{"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("false"), + prettier.Text(":"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Text("\"Pre failed\""), + }, + }, + }, + }, + }, + }, + prettier.HardLine{}, + prettier.Text("}"), + }}, + prettier.HardLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("post"), + prettier.Text(" "), + prettier.Text("{"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("true"), + prettier.Text(":"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Text("\"Post failed\""), + }, + }, + }, + }, + }, + }, + prettier.HardLine{}, + prettier.Text("}"), + }}, + prettier.Concat{ + prettier.HardLine{}, + prettier.Text("false"), + prettier.HardLine{}, + prettier.Text("\"test\""), + }, + }}, + prettier.HardLine{}, + prettier.Text("}"), + }, + block.Doc(), + ) + }) +} + +func TestFunctionBlock_String(t *testing.T) { + + t.Parallel() + + t.Run("with statements", func(t *testing.T) { + + t.Parallel() + + block := &FunctionBlock{ + Block: &Block{ + Statements: []Statement{ + &ExpressionStatement{ + Expression: &BoolExpression{ + Value: false, + }, + }, + &ExpressionStatement{ + Expression: &StringExpression{ + Value: "test", + }, + }, + }, + }, + } + + require.Equal( + t, + "{\n"+ + " false\n"+ + " \"test\"\n"+ + "}", + block.String(), + ) + }) + + t.Run("with preconditions and postconditions", func(t *testing.T) { + + t.Parallel() + + block := &FunctionBlock{ + Block: &Block{ + Statements: []Statement{ + &ExpressionStatement{ + Expression: &BoolExpression{ + Value: false, + }, + }, + &ExpressionStatement{ + Expression: &StringExpression{ + Value: "test", + }, + }, + }, + }, + PreConditions: &Conditions{ + { + Kind: ConditionKindPre, + Test: &BoolExpression{ + Value: false, + }, + Message: &StringExpression{ + Value: "Pre failed", + }, + }, + }, + PostConditions: &Conditions{ + { + Kind: ConditionKindPost, + Test: &BoolExpression{ + Value: true, + }, + Message: &StringExpression{ + Value: "Post failed", + }, + }, + }, + } + + require.Equal( + t, + "{\n"+ + " pre {\n"+ + " false:\n"+ + " \"Pre failed\"\n"+ + " }\n"+ + " post {\n"+ + " true:\n"+ + " \"Post failed\"\n"+ + " }\n"+ + " false\n"+ + " \"test\"\n"+ + "}", + block.String(), + ) + }) +} diff --git a/runtime/ast/composite.go b/runtime/ast/composite.go index 50561ddf65..7b16b5c95a 100644 --- a/runtime/ast/composite.go +++ b/runtime/ast/composite.go @@ -21,7 +21,10 @@ package ast import ( "encoding/json" + "github.com/turbolent/prettier" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" ) // CompositeDeclaration @@ -115,6 +118,150 @@ func (d *CompositeDeclaration) MarshalJSON() ([]byte, error) { }) } +func (d *CompositeDeclaration) Doc() prettier.Doc { + + if d.CompositeKind == common.CompositeKindEvent { + return d.EventDoc() + } + + return CompositeDocument( + d.Access, + d.CompositeKind, + false, + d.Identifier.Identifier, + d.Conformances, + d.Members, + ) +} + +func (d *CompositeDeclaration) EventDoc() prettier.Doc { + var doc prettier.Concat + + if d.Access != AccessNotSpecified { + doc = append( + doc, + prettier.Text(d.Access.Keyword()), + prettier.Space, + ) + } + + doc = append( + doc, + prettier.Text(d.CompositeKind.Keyword()), + prettier.Space, + prettier.Text(d.Identifier.Identifier), + ) + + initializers := d.Members.Initializers() + if len(initializers) != 1 { + return nil + } + + initializer := initializers[0] + paramsDoc := initializer.FunctionDeclaration.ParameterList.Doc() + + return append(doc, paramsDoc) +} + +func (d *CompositeDeclaration) String() string { + return Prettier(d) +} + +var interfaceKeywordSpaceDoc = prettier.Text("interface ") +var compositeConformancesSeparatorDoc = prettier.Text(":") +var compositeConformanceSeparatorDoc prettier.Doc = prettier.Concat{ + prettier.Text(","), + prettier.Line{}, +} + +func CompositeDocument( + access Access, + kind common.CompositeKind, + isInterface bool, + identifier string, + conformances []*NominalType, + members *Members, +) prettier.Doc { + + var doc prettier.Concat + + if access != AccessNotSpecified { + doc = append( + doc, + prettier.Text(access.Keyword()), + prettier.Space, + ) + } + + doc = append( + doc, + prettier.Text(kind.Keyword()), + prettier.Space, + ) + + if isInterface { + doc = append( + doc, + interfaceKeywordSpaceDoc, + ) + } + + doc = append( + doc, + prettier.Text(identifier), + ) + + if len(conformances) > 0 { + + conformancesDoc := prettier.Concat{ + prettier.Line{}, + } + + for i, conformance := range conformances { + if i > 0 { + conformancesDoc = append( + conformancesDoc, + compositeConformanceSeparatorDoc, + ) + } + + conformancesDoc = append( + conformancesDoc, + conformance.Doc(), + ) + } + + conformancesDoc = append( + conformancesDoc, + prettier.Dedent{ + Doc: prettier.Concat{ + prettier.Line{}, + members.Doc(), + }, + }, + ) + + doc = append( + doc, + compositeConformancesSeparatorDoc, + prettier.Group{ + Doc: prettier.Indent{ + Doc: conformancesDoc, + }, + }, + ) + + } else { + doc = append( + doc, + prettier.Space, + members.Doc(), + ) + } + + return doc +} + // FieldDeclaration type FieldDeclaration struct { @@ -196,6 +343,74 @@ func (d *FieldDeclaration) MarshalJSON() ([]byte, error) { }) } +func VariableKindDoc(kind VariableKind) prettier.Doc { + switch kind { + case VariableKindNotSpecified: + return nil + case VariableKindConstant: + return letKeywordDoc + case VariableKindVariable: + return varKeywordDoc + default: + panic(errors.NewUnreachableError()) + } +} + +func (d *FieldDeclaration) Doc() prettier.Doc { + identifierTypeDoc := prettier.Concat{ + prettier.Text(d.Identifier.Identifier), + } + + if d.TypeAnnotation != nil { + identifierTypeDoc = append( + identifierTypeDoc, + typeSeparatorSpaceDoc, + d.TypeAnnotation.Doc(), + ) + } + + var docs []prettier.Doc + + if d.Access != AccessNotSpecified { + docs = append( + docs, + prettier.Text(d.Access.Keyword()), + ) + } + + keywordDoc := VariableKindDoc(d.VariableKind) + + if keywordDoc != nil { + docs = append( + docs, + keywordDoc, + ) + } + + var doc prettier.Doc + + if len(docs) > 0 { + docs = append( + docs, + prettier.Group{ + Doc: identifierTypeDoc, + }, + ) + + doc = prettier.Join(prettier.Space, docs...) + } else { + doc = identifierTypeDoc + } + + return prettier.Group{ + Doc: doc, + } +} + +func (d *FieldDeclaration) String() string { + return Prettier(d) +} + // EnumCaseDeclaration type EnumCaseDeclaration struct { @@ -279,3 +494,27 @@ func (d *EnumCaseDeclaration) MarshalJSON() ([]byte, error) { Alias: (*Alias)(d), }) } + +const enumCaseKeywordSpaceDoc = prettier.Text("case ") + +func (d *EnumCaseDeclaration) Doc() prettier.Doc { + var doc prettier.Concat + + if d.Access != AccessNotSpecified { + doc = append( + doc, + prettier.Text(d.Access.Keyword()), + prettier.Space, + ) + } + + return append( + doc, + enumCaseKeywordSpaceDoc, + prettier.Text(d.Identifier.Identifier), + ) +} + +func (d *EnumCaseDeclaration) String() string { + return Prettier(d) +} diff --git a/runtime/ast/composite_test.go b/runtime/ast/composite_test.go index 5fe33c9c8e..5886ea30e8 100644 --- a/runtime/ast/composite_test.go +++ b/runtime/ast/composite_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/turbolent/prettier" "github.com/onflow/cadence/runtime/common" ) @@ -32,7 +33,7 @@ func TestFieldDeclaration_MarshalJSON(t *testing.T) { t.Parallel() - expr := &FieldDeclaration{ + decl := &FieldDeclaration{ Access: AccessPublic, VariableKind: VariableKindConstant, Identifier: Identifier{ @@ -56,7 +57,7 @@ func TestFieldDeclaration_MarshalJSON(t *testing.T) { }, } - actual, err := json.Marshal(expr) + actual, err := json.Marshal(decl) require.NoError(t, err) assert.JSONEq(t, @@ -94,11 +95,288 @@ func TestFieldDeclaration_MarshalJSON(t *testing.T) { ) } +func TestFieldDeclaration_Doc(t *testing.T) { + + t.Parallel() + + t.Run("with access, with kind", func(t *testing.T) { + + t.Parallel() + + decl := &FieldDeclaration{ + Access: AccessPublic, + VariableKind: VariableKindConstant, + Identifier: Identifier{ + Identifier: "xyz", + }, + TypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "CD", + }, + }, + }, + } + + require.Equal( + t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("pub"), + prettier.Text(" "), + prettier.Text("let"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("xyz"), + prettier.Text(": "), + prettier.Concat{ + prettier.Text("@"), + prettier.Text("CD"), + }, + }, + }, + }, + }, + decl.Doc(), + ) + }) + + t.Run("without access, with kind", func(t *testing.T) { + + t.Parallel() + + decl := &FieldDeclaration{ + VariableKind: VariableKindConstant, + Identifier: Identifier{ + Identifier: "xyz", + }, + TypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "CD", + }, + }, + }, + } + + require.Equal( + t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("let"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("xyz"), + prettier.Text(": "), + prettier.Concat{ + prettier.Text("@"), + prettier.Text("CD"), + }, + }, + }, + }, + }, + decl.Doc(), + ) + }) + + t.Run("with access, without kind", func(t *testing.T) { + + t.Parallel() + + decl := &FieldDeclaration{ + Access: AccessPublic, + Identifier: Identifier{ + Identifier: "xyz", + }, + TypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "CD", + }, + }, + }, + } + + require.Equal( + t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("pub"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("xyz"), + prettier.Text(": "), + prettier.Concat{ + prettier.Text("@"), + prettier.Text("CD"), + }, + }, + }, + }, + }, + decl.Doc(), + ) + }) + + t.Run("without access, without kind", func(t *testing.T) { + + t.Parallel() + + decl := &FieldDeclaration{ + Identifier: Identifier{ + Identifier: "xyz", + }, + TypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "CD", + }, + }, + }, + } + + require.Equal( + t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("xyz"), + prettier.Text(": "), + prettier.Concat{ + prettier.Text("@"), + prettier.Text("CD"), + }, + }, + }, + decl.Doc(), + ) + }) + +} + +func TestFieldDeclaration_String(t *testing.T) { + + t.Parallel() + + t.Run("with access, with kind", func(t *testing.T) { + + t.Parallel() + + decl := &FieldDeclaration{ + Access: AccessPublic, + VariableKind: VariableKindConstant, + Identifier: Identifier{ + Identifier: "xyz", + }, + TypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "CD", + }, + }, + }, + } + + require.Equal( + t, + "pub let xyz: @CD", + decl.String(), + ) + }) + + t.Run("without access, with kind", func(t *testing.T) { + + t.Parallel() + + decl := &FieldDeclaration{ + VariableKind: VariableKindConstant, + Identifier: Identifier{ + Identifier: "xyz", + }, + TypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "CD", + }, + }, + }, + } + + require.Equal( + t, + "let xyz: @CD", + decl.String(), + ) + }) + + t.Run("with access, without kind", func(t *testing.T) { + + t.Parallel() + + decl := &FieldDeclaration{ + Access: AccessPublic, + Identifier: Identifier{ + Identifier: "xyz", + }, + TypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "CD", + }, + }, + }, + } + + require.Equal( + t, + "pub xyz: @CD", + decl.String(), + ) + + }) + + t.Run("without access, without kind", func(t *testing.T) { + + t.Parallel() + + decl := &FieldDeclaration{ + Identifier: Identifier{ + Identifier: "xyz", + }, + TypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "CD", + }, + }, + }, + } + + require.Equal( + t, + "xyz: @CD", + decl.String(), + ) + }) + +} + func TestCompositeDeclaration_MarshalJSON(t *testing.T) { t.Parallel() - expr := &CompositeDeclaration{ + decl := &CompositeDeclaration{ Access: AccessPublic, CompositeKind: common.CompositeKindResource, Identifier: Identifier{ @@ -121,7 +399,7 @@ func TestCompositeDeclaration_MarshalJSON(t *testing.T) { }, } - actual, err := json.Marshal(expr) + actual, err := json.Marshal(decl) require.NoError(t, err) assert.JSONEq(t, @@ -158,3 +436,473 @@ func TestCompositeDeclaration_MarshalJSON(t *testing.T) { string(actual), ) } + +func TestCompositeDeclaration_Doc(t *testing.T) { + + t.Parallel() + + t.Run("no members, conformances", func(t *testing.T) { + + t.Parallel() + + decl := &CompositeDeclaration{ + Access: AccessPublic, + CompositeKind: common.CompositeKindResource, + Identifier: Identifier{ + Identifier: "AB", + }, + Conformances: []*NominalType{ + { + Identifier: Identifier{ + Identifier: "CD", + }, + }, + { + Identifier: Identifier{ + Identifier: "EF", + }, + }, + }, + Members: NewMembers(nil, []Declaration{}), + } + + require.Equal( + t, + prettier.Concat{ + prettier.Text("pub"), + prettier.Text(" "), + prettier.Text("resource"), + prettier.Text(" "), + prettier.Text("AB"), + prettier.Text(":"), + prettier.Group{ + Doc: prettier.Indent{ + Doc: prettier.Concat{ + prettier.Line{}, + prettier.Text("CD"), + prettier.Concat{ + prettier.Text(","), + prettier.Line{}, + }, + prettier.Text("EF"), + prettier.Dedent{ + Doc: prettier.Concat{ + prettier.Line{}, + prettier.Text("{}"), + }, + }, + }, + }, + }, + }, + decl.Doc(), + ) + }) + + t.Run("members, conformances", func(t *testing.T) { + + t.Parallel() + + decl := &CompositeDeclaration{ + Access: AccessPublic, + CompositeKind: common.CompositeKindResource, + Identifier: Identifier{ + Identifier: "AB", + }, + Conformances: []*NominalType{ + { + Identifier: Identifier{ + Identifier: "CD", + }, + }, + { + Identifier: Identifier{ + Identifier: "EF", + }, + }, + }, + Members: NewMembers(nil, []Declaration{ + &FieldDeclaration{ + Identifier: Identifier{ + Identifier: "x", + }, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "X", + }, + }, + }, + }, + }), + } + + require.Equal( + t, + prettier.Concat{ + prettier.Text("pub"), + prettier.Text(" "), + prettier.Text("resource"), + prettier.Text(" "), + prettier.Text("AB"), + prettier.Text(":"), + prettier.Group{ + Doc: prettier.Indent{ + Doc: prettier.Concat{ + prettier.Line{}, + prettier.Text("CD"), + prettier.Concat{ + prettier.Text(","), + prettier.Line{}, + }, + prettier.Text("EF"), + prettier.Dedent{ + Doc: prettier.Concat{ + prettier.Line{}, + prettier.Concat{ + prettier.Text("{"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("x"), + prettier.Text(": "), + prettier.Text("X"), + }, + }, + }, + }, + prettier.HardLine{}, + prettier.Text("}"), + }, + }, + }, + }, + }, + }, + }, + decl.Doc(), + ) + }) + + t.Run("event", func(t *testing.T) { + + t.Parallel() + + decl := &CompositeDeclaration{ + Access: AccessPublic, + CompositeKind: common.CompositeKindEvent, + Identifier: Identifier{ + Identifier: "AB", + }, + Members: NewMembers(nil, []Declaration{ + &SpecialFunctionDeclaration{ + Kind: common.DeclarationKindInitializer, + FunctionDeclaration: &FunctionDeclaration{ + ParameterList: &ParameterList{ + Parameters: []*Parameter{ + { + Identifier: Identifier{Identifier: "e"}, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{Identifier: "E"}, + }, + }, + }, + }, + }, + }, + }, + }), + } + + require.Equal( + t, + prettier.Concat{ + prettier.Text("pub"), + prettier.Text(" "), + prettier.Text("event"), + prettier.Text(" "), + prettier.Text("AB"), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Concat{ + prettier.Text("e"), + prettier.Text(": "), + prettier.Text("E"), + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + }, + decl.Doc(), + ) + }) + + t.Run("enum", func(t *testing.T) { + + t.Parallel() + + decl := &CompositeDeclaration{ + Access: AccessPublic, + CompositeKind: common.CompositeKindEnum, + Identifier: Identifier{ + Identifier: "AB", + }, + Conformances: []*NominalType{ + { + Identifier: Identifier{ + Identifier: "CD", + }, + }, + }, + Members: NewMembers(nil, []Declaration{ + &EnumCaseDeclaration{ + Identifier: Identifier{ + Identifier: "x", + }, + }, + }), + } + + require.Equal( + t, + prettier.Concat{ + prettier.Text("pub"), + prettier.Text(" "), + prettier.Text("enum"), + prettier.Text(" "), + prettier.Text("AB"), + prettier.Text(":"), + prettier.Group{ + Doc: prettier.Indent{ + Doc: prettier.Concat{ + prettier.Line{}, + prettier.Text("CD"), + prettier.Dedent{ + Doc: prettier.Concat{ + prettier.Line{}, + prettier.Concat{ + prettier.Text("{"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Concat{ + prettier.Text("case "), + prettier.Text("x"), + }, + }, + }, + prettier.HardLine{}, + prettier.Text("}"), + }, + }, + }, + }, + }, + }, + }, + decl.Doc(), + ) + }) +} + +func TestCompositeDeclaration_String(t *testing.T) { + + t.Parallel() + + t.Run("no members, conformances", func(t *testing.T) { + + t.Parallel() + + decl := &CompositeDeclaration{ + Access: AccessPublic, + CompositeKind: common.CompositeKindResource, + Identifier: Identifier{ + Identifier: "AB", + }, + Conformances: []*NominalType{ + { + Identifier: Identifier{ + Identifier: "CD", + }, + }, + { + Identifier: Identifier{ + Identifier: "EF", + }, + }, + }, + Members: NewMembers(nil, []Declaration{}), + } + + require.Equal( + t, + "pub resource AB: CD, EF {}", + decl.String(), + ) + }) + + t.Run("members, conformances", func(t *testing.T) { + + t.Parallel() + + decl := &CompositeDeclaration{ + Access: AccessPublic, + CompositeKind: common.CompositeKindResource, + Identifier: Identifier{ + Identifier: "AB", + }, + Conformances: []*NominalType{ + { + Identifier: Identifier{ + Identifier: "CD", + }, + }, + { + Identifier: Identifier{ + Identifier: "EF", + }, + }, + }, + Members: NewMembers(nil, []Declaration{ + &FieldDeclaration{ + Identifier: Identifier{ + Identifier: "x", + }, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "X", + }, + }, + }, + }, + }), + } + + require.Equal( + t, + "pub resource AB: CD, EF {\n"+ + " x: X\n"+ + "}", + decl.String(), + ) + }) + + t.Run("event", func(t *testing.T) { + + t.Parallel() + + decl := &CompositeDeclaration{ + Access: AccessPublic, + CompositeKind: common.CompositeKindEvent, + Identifier: Identifier{ + Identifier: "AB", + }, + Members: NewMembers(nil, []Declaration{ + &SpecialFunctionDeclaration{ + Kind: common.DeclarationKindInitializer, + FunctionDeclaration: &FunctionDeclaration{ + ParameterList: &ParameterList{ + Parameters: []*Parameter{ + { + Identifier: Identifier{Identifier: "e"}, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{Identifier: "E"}, + }, + }, + }, + }, + }, + }, + }, + }), + } + + require.Equal( + t, + "pub event AB(e: E)", + decl.String(), + ) + }) + + t.Run("enum", func(t *testing.T) { + + t.Parallel() + + decl := &CompositeDeclaration{ + Access: AccessPublic, + CompositeKind: common.CompositeKindEnum, + Identifier: Identifier{ + Identifier: "AB", + }, + Conformances: []*NominalType{ + { + Identifier: Identifier{ + Identifier: "CD", + }, + }, + }, + Members: NewMembers(nil, []Declaration{ + &EnumCaseDeclaration{ + Identifier: Identifier{ + Identifier: "x", + }, + }, + }), + } + + require.Equal( + t, + "pub enum AB: CD {\n"+ + " case x\n"+ + "}", + decl.String(), + ) + }) +} + +func TestEnumCaseDeclaration_Doc(t *testing.T) { + + t.Parallel() + + decl := &EnumCaseDeclaration{ + Identifier: Identifier{ + Identifier: "x", + }, + } + + require.Equal(t, + prettier.Concat{ + prettier.Text("case "), + prettier.Text("x"), + }, + decl.Doc(), + ) +} + +func TestEnumCaseDeclaration_String(t *testing.T) { + + t.Parallel() + + decl := &EnumCaseDeclaration{ + Identifier: Identifier{ + Identifier: "x", + }, + } + + require.Equal(t, + "case x", + decl.String(), + ) +} diff --git a/runtime/ast/declaration.go b/runtime/ast/declaration.go index 1e56881fa1..53a0b81282 100644 --- a/runtime/ast/declaration.go +++ b/runtime/ast/declaration.go @@ -18,14 +18,22 @@ package ast -import "github.com/onflow/cadence/runtime/common" +import ( + "fmt" + + "github.com/turbolent/prettier" + + "github.com/onflow/cadence/runtime/common" +) type Declaration interface { Element + fmt.Stringer isDeclaration() DeclarationIdentifier() *Identifier DeclarationKind() common.DeclarationKind DeclarationAccess() Access DeclarationMembers() *Members DeclarationDocString() string + Doc() prettier.Doc } diff --git a/runtime/ast/expression.go b/runtime/ast/expression.go index 4d0b01f91f..9be0e2f632 100644 --- a/runtime/ast/expression.go +++ b/runtime/ast/expression.go @@ -26,6 +26,8 @@ import ( "github.com/turbolent/prettier" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/common" ) @@ -38,6 +40,7 @@ type Expression interface { isExpression() AcceptExp(ExpressionVisitor) Repr Doc() prettier.Doc + precedence() precedence } // BoolExpression @@ -79,10 +82,7 @@ func (e *BoolExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *BoolExpression) String() string { - if e.Value { - return "true" - } - return "false" + return Prettier(e) } var boolExpressionTrueDoc prettier.Doc = prettier.Text("true") @@ -107,6 +107,10 @@ func (e *BoolExpression) MarshalJSON() ([]byte, error) { }) } +func (*BoolExpression) precedence() precedence { + return precedenceLiteral +} + // NilExpression type NilExpression struct { @@ -144,7 +148,7 @@ func (e *NilExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *NilExpression) String() string { - return NilConstant + return Prettier(e) } var nilExpressionDoc prettier.Doc = prettier.Text("nil") @@ -174,6 +178,10 @@ func (e *NilExpression) MarshalJSON() ([]byte, error) { }) } +func (*NilExpression) precedence() precedence { + return precedenceLiteral +} + // StringExpression type StringExpression struct { @@ -215,7 +223,7 @@ func (e *StringExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *StringExpression) String() string { - return QuoteString(e.Value) + return Prettier(e) } func (e *StringExpression) Doc() prettier.Doc { @@ -233,6 +241,10 @@ func (e *StringExpression) MarshalJSON() ([]byte, error) { }) } +func (*StringExpression) precedence() precedence { + return precedenceLiteral +} + // IntegerExpression type IntegerExpression struct { @@ -283,11 +295,7 @@ func (e *IntegerExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *IntegerExpression) String() string { - literal := e.PositiveLiteral - if e.Value.Sign() < 0 { - literal = "-" + literal - } - return literal + return Prettier(e) } func (e *IntegerExpression) Doc() prettier.Doc { @@ -311,6 +319,10 @@ func (e *IntegerExpression) MarshalJSON() ([]byte, error) { }) } +func (*IntegerExpression) precedence() precedence { + return precedenceLiteral +} + // FixedPointExpression type FixedPointExpression struct { @@ -367,12 +379,16 @@ func (e *FixedPointExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *FixedPointExpression) String() string { + return Prettier(e) +} + +func (e *FixedPointExpression) Doc() prettier.Doc { literal := e.PositiveLiteral if literal != "" { if e.Negative { literal = "-" + literal } - return literal + return prettier.Text(literal) } var builder strings.Builder @@ -386,15 +402,7 @@ func (e *FixedPointExpression) String() string { builder.WriteRune('0') } builder.WriteString(fractional) - return builder.String() -} - -func (e *FixedPointExpression) Doc() prettier.Doc { - literal := e.PositiveLiteral - if e.Negative { - literal = "-" + literal - } - return prettier.Text(literal) + return prettier.Text(builder.String()) } func (e *FixedPointExpression) MarshalJSON() ([]byte, error) { @@ -412,6 +420,10 @@ func (e *FixedPointExpression) MarshalJSON() ([]byte, error) { }) } +func (*FixedPointExpression) precedence() precedence { + return precedenceLiteral +} + // ArrayExpression type ArrayExpression struct { @@ -457,16 +469,7 @@ func (e *ArrayExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *ArrayExpression) String() string { - var builder strings.Builder - builder.WriteString("[") - for i, value := range e.Values { - if i > 0 { - builder.WriteString(", ") - } - builder.WriteString(value.String()) - } - builder.WriteString("]") - return builder.String() + return Prettier(e) } var arrayExpressionSeparatorDoc prettier.Doc = prettier.Concat{ @@ -500,6 +503,10 @@ func (e *ArrayExpression) MarshalJSON() ([]byte, error) { }) } +func (*ArrayExpression) precedence() precedence { + return precedenceLiteral +} + // DictionaryExpression type DictionaryExpression struct { @@ -547,18 +554,7 @@ func (e *DictionaryExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *DictionaryExpression) String() string { - var builder strings.Builder - builder.WriteString("{") - for i, entry := range e.Entries { - if i > 0 { - builder.WriteString(", ") - } - builder.WriteString(entry.Key.String()) - builder.WriteString(": ") - builder.WriteString(entry.Value.String()) - } - builder.WriteString("}") - return builder.String() + return Prettier(e) } var dictionaryExpressionSeparatorDoc prettier.Doc = prettier.Concat{ @@ -593,6 +589,10 @@ func (e *DictionaryExpression) MarshalJSON() ([]byte, error) { }) } +func (*DictionaryExpression) precedence() precedence { + return precedenceLiteral +} + type DictionaryEntry struct { Key Expression Value Expression @@ -681,7 +681,7 @@ func (e *IdentifierExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *IdentifierExpression) String() string { - return e.Identifier.Identifier + return Prettier(e) } func (e *IdentifierExpression) Doc() prettier.Doc { @@ -709,21 +709,39 @@ func (e *IdentifierExpression) EndPosition(memoryGauge common.MemoryGauge) Posit return e.Identifier.EndPosition(memoryGauge) } +func (*IdentifierExpression) precedence() precedence { + return precedenceLiteral +} + // Arguments type Arguments []*Argument func (args Arguments) String() string { - var builder strings.Builder - builder.WriteRune('(') + return Prettier(args) +} + +var argumentsSeparatorDoc prettier.Doc = prettier.Concat{ + prettier.Text(","), + prettier.Line{}, +} + +func (args Arguments) Doc() prettier.Doc { + if len(args) == 0 { + return prettier.Text("()") + } + + argumentDocs := make([]prettier.Doc, len(args)) for i, argument := range args { - if i > 0 { - builder.WriteString(", ") - } - builder.WriteString(argument.String()) + argumentDocs[i] = argument.Doc() } - builder.WriteRune(')') - return builder.String() + return prettier.WrapParentheses( + prettier.Join( + argumentsSeparatorDoc, + argumentDocs..., + ), + prettier.SoftLine{}, + ) } // InvocationExpression @@ -782,27 +800,16 @@ func (e *InvocationExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *InvocationExpression) String() string { - var builder strings.Builder - builder.WriteString(e.InvokedExpression.String()) - if len(e.TypeArguments) > 0 { - builder.WriteRune('<') - for i, ty := range e.TypeArguments { - if i > 0 { - builder.WriteString(", ") - } - builder.WriteString(ty.String()) - } - builder.WriteRune('>') - } - builder.WriteString(e.Arguments.String()) - return builder.String() + return Prettier(e) } func (e *InvocationExpression) Doc() prettier.Doc { result := prettier.Concat{ - // TODO: potentially parenthesize - e.InvokedExpression.Doc(), + parenthesizedExpressionDoc( + e.InvokedExpression, + e.precedence(), + ), } if len(e.TypeArguments) > 0 { @@ -821,28 +828,7 @@ func (e *InvocationExpression) Doc() prettier.Doc { ) } - var argumentsDoc prettier.Doc - if len(e.Arguments) == 0 { - argumentsDoc = prettier.Text("()") - } else { - argumentDocs := make([]prettier.Doc, len(e.Arguments)) - for i, argument := range e.Arguments { - argumentDoc := argument.Expression.Doc() - if argument.Label != "" { - argumentDoc = prettier.Concat{ - prettier.Text(argument.Label + ": "), - argumentDoc, - } - } - argumentDocs[i] = argumentDoc - } - argumentsDoc = prettier.WrapParentheses( - prettier.Join(arrayExpressionSeparatorDoc, argumentDocs...), - prettier.SoftLine{}, - ) - } - - result = append(result, argumentsDoc) + result = append(result, e.Arguments.Doc()) return result } @@ -868,6 +854,10 @@ func (e *InvocationExpression) MarshalJSON() ([]byte, error) { }) } +func (*InvocationExpression) precedence() precedence { + return precedenceAccess +} + // AccessExpression type AccessExpression interface { @@ -934,14 +924,7 @@ func (e *MemberExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *MemberExpression) String() string { - optional := "" - if e.Optional { - optional = "?" - } - return fmt.Sprintf( - "%s%s.%s", - e.Expression, optional, e.Identifier, - ) + return Prettier(e) } var memberExpressionSeparatorDoc prettier.Doc = prettier.Text(".") @@ -954,9 +937,12 @@ func (e *MemberExpression) Doc() prettier.Doc { } else { separatorDoc = memberExpressionSeparatorDoc } + return prettier.Concat{ - // TODO: potentially parenthesize - e.Expression.Doc(), + parenthesizedExpressionDoc( + e.Expression, + e.precedence(), + ), prettier.Group{ Doc: prettier.Indent{ Doc: prettier.Concat{ @@ -994,6 +980,10 @@ func (e *MemberExpression) MarshalJSON() ([]byte, error) { }) } +func (*MemberExpression) precedence() precedence { + return precedenceAccess +} + // IndexExpression type IndexExpression struct { @@ -1047,17 +1037,15 @@ func (e *IndexExpression) AcceptExp(visitor ExpressionVisitor) Repr { return visitor.VisitIndexExpression(e) } func (e *IndexExpression) String() string { - return fmt.Sprintf( - "%s[%s]", - e.TargetExpression, - e.IndexingExpression, - ) + return Prettier(e) } func (e *IndexExpression) Doc() prettier.Doc { return prettier.Concat{ - // TODO: potentially parenthesize - e.TargetExpression.Doc(), + parenthesizedExpressionDoc( + e.TargetExpression, + e.precedence(), + ), prettier.WrapBrackets( e.IndexingExpression.Doc(), prettier.SoftLine{}, @@ -1076,6 +1064,10 @@ func (e *IndexExpression) MarshalJSON() ([]byte, error) { }) } +func (*IndexExpression) precedence() precedence { + return precedenceAccess +} + // ConditionalExpression type ConditionalExpression struct { @@ -1126,10 +1118,7 @@ func (e *ConditionalExpression) AcceptExp(visitor ExpressionVisitor) Repr { return visitor.VisitConditionalExpression(e) } func (e *ConditionalExpression) String() string { - return fmt.Sprintf( - "(%s ? %s : %s)", - e.Test, e.Then, e.Else, - ) + return Prettier(e) } var conditionalExpressionTestSeparatorDoc prettier.Doc = prettier.Concat{ @@ -1142,14 +1131,30 @@ var conditionalExpressionBranchSeparatorDoc prettier.Doc = prettier.Concat{ } func (e *ConditionalExpression) Doc() prettier.Doc { - // TODO: potentially parenthesize + ownPrecedence := e.precedence() + + // NOTE: right associative + testDoc := e.Test.Doc() + testPrecedence := e.Test.precedence() + + if ownPrecedence >= testPrecedence { + testDoc = prettier.WrapParentheses(testDoc, prettier.SoftLine{}) + } - // TODO: potentially parenthesize thenDoc := e.Then.Doc() + thenPrecedence := e.Then.precedence() + + if ownPrecedence >= thenPrecedence { + thenDoc = prettier.WrapParentheses(thenDoc, prettier.SoftLine{}) + } - // TODO: potentially parenthesize elseDoc := e.Else.Doc() + elsePrecedence := e.Else.precedence() + + if ownPrecedence > elsePrecedence { + elseDoc = prettier.WrapParentheses(elseDoc, prettier.SoftLine{}) + } return prettier.Group{ Doc: prettier.Concat{ @@ -1191,6 +1196,10 @@ func (e *ConditionalExpression) MarshalJSON() ([]byte, error) { }) } +func (*ConditionalExpression) precedence() precedence { + return precedenceTernary +} + // UnaryExpression type UnaryExpression struct { @@ -1238,18 +1247,28 @@ func (e *UnaryExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *UnaryExpression) String() string { - return fmt.Sprintf( - "%s%s", - e.Operation.Symbol(), - e.Expression, + return Prettier(e) +} + +func parenthesizedExpressionDoc(e Expression, parentPrecedence precedence) prettier.Doc { + doc := e.Doc() + subPrecedence := e.precedence() + if parentPrecedence <= subPrecedence { + return doc + } + return prettier.WrapParentheses( + doc, + prettier.SoftLine{}, ) } func (e *UnaryExpression) Doc() prettier.Doc { return prettier.Concat{ prettier.Text(e.Operation.Symbol()), - // TODO: potentially parenthesize - e.Expression.Doc(), + parenthesizedExpressionDoc( + e.Expression, + e.precedence(), + ), } } @@ -1274,6 +1293,10 @@ func (e *UnaryExpression) MarshalJSON() ([]byte, error) { }) } +func (*UnaryExpression) precedence() precedence { + return precedenceUnaryPrefix +} + // BinaryExpression type BinaryExpression struct { @@ -1322,18 +1345,32 @@ func (e *BinaryExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *BinaryExpression) String() string { - return fmt.Sprintf( - "(%s %s %s)", - e.Left, e.Operation.Symbol(), e.Right, - ) + return Prettier(e) } func (e *BinaryExpression) Doc() prettier.Doc { - // TODO: potentially parenthesize + + ownPrecedence := e.precedence() + isLeftAssociative := e.IsLeftAssociative() + isRightAssociative := !isLeftAssociative + leftDoc := e.Left.Doc() + leftPrecedence := e.Left.precedence() + + if (isLeftAssociative && ownPrecedence > leftPrecedence) || + (isRightAssociative && ownPrecedence >= leftPrecedence) { + + leftDoc = prettier.WrapParentheses(leftDoc, prettier.SoftLine{}) + } - // TODO: potentially parenthesize rightDoc := e.Right.Doc() + rightPrecedence := e.Right.precedence() + + if (isLeftAssociative && ownPrecedence >= rightPrecedence) || + (isRightAssociative && ownPrecedence > rightPrecedence) { + + rightDoc = prettier.WrapParentheses(rightDoc, prettier.SoftLine{}) + } return prettier.Group{ Doc: prettier.Concat{ @@ -1371,6 +1408,42 @@ func (e *BinaryExpression) MarshalJSON() ([]byte, error) { }) } +func (e *BinaryExpression) precedence() precedence { + switch e.Operation { + case OperationOr: + return precedenceLogicalOr + case OperationAnd: + return precedenceLogicalAnd + case OperationEqual, + OperationNotEqual, + OperationLess, + OperationLessEqual, + OperationGreater, + OperationGreaterEqual: + return precedenceComparison + case OperationNilCoalesce: + return precedenceNilCoalescing + case OperationBitwiseOr: + return precedenceBitwiseOr + case OperationBitwiseXor: + return precedenceBitwiseXor + case OperationBitwiseAnd: + return precedenceBitwiseAnd + case OperationBitwiseLeftShift, OperationBitwiseRightShift: + return precedenceBitwiseShift + case OperationPlus, OperationMinus: + return precedenceAddition + case OperationMul, OperationDiv, OperationMod: + return precedenceMultiplication + default: + panic(errors.NewUnreachableError()) + } +} + +func (e *BinaryExpression) IsLeftAssociative() bool { + return e.Operation != OperationNilCoalesce +} + // FunctionExpression type FunctionExpression struct { @@ -1423,92 +1496,94 @@ func (e *FunctionExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *FunctionExpression) String() string { - // TODO: - return "func ..." + return Prettier(e) } -var functionExpressionFunKeywordDoc prettier.Doc = prettier.Text("fun ") -var functionExpressionParameterSeparatorDoc prettier.Doc = prettier.Concat{ - prettier.Text(","), - prettier.Line{}, -} +var functionFunKeywordSpaceDoc prettier.Doc = prettier.Text("fun ") -var typeSeparatorDoc prettier.Doc = prettier.Text(": ") var functionExpressionEmptyBlockDoc prettier.Doc = prettier.Text(" {}") -func (e *FunctionExpression) Doc() prettier.Doc { - - signatureDoc := e.parametersDoc() +func FunctionDocument( + access Access, + includeKeyword bool, + identifier string, + parameterList *ParameterList, + returnTypeAnnotation *TypeAnnotation, + block *FunctionBlock, +) prettier.Doc { + + var signatureDoc prettier.Concat + if parameterList != nil { + signatureDoc = append( + signatureDoc, + parameterList.Doc(), + ) - if e.ReturnTypeAnnotation != nil && - !IsEmptyType(e.ReturnTypeAnnotation.Type) { + if returnTypeAnnotation != nil && + !IsEmptyType(returnTypeAnnotation.Type) { - signatureDoc = prettier.Concat{ - signatureDoc, - typeSeparatorDoc, - e.ReturnTypeAnnotation.Doc(), + signatureDoc = append( + signatureDoc, + typeSeparatorSpaceDoc, + returnTypeAnnotation.Doc(), + ) } } - doc := prettier.Concat{ - functionExpressionFunKeywordDoc, - prettier.Group{ - Doc: signatureDoc, - }, - } - - if e.FunctionBlock.IsEmpty() { - return append(doc, functionExpressionEmptyBlockDoc) - } else { - // TODO: pre-conditions - // TODO: post-conditions - - blockDoc := e.FunctionBlock.Block.Doc() + var doc prettier.Concat - return append( + if access != AccessNotSpecified { + doc = append( doc, + prettier.Text(access.Keyword()), prettier.Space, - blockDoc, ) } -} - -func (e *FunctionExpression) parametersDoc() prettier.Doc { - if e.ParameterList == nil || - len(e.ParameterList.Parameters) == 0 { - - return prettier.Text("()") + if includeKeyword { + doc = append( + doc, + functionFunKeywordSpaceDoc, + ) } - parameterDocs := make([]prettier.Doc, 0, len(e.ParameterList.Parameters)) + if identifier != "" { + doc = append( + doc, + prettier.Text(identifier), + ) + } - for _, parameter := range e.ParameterList.Parameters { - var parameterDoc prettier.Concat + if signatureDoc != nil { + doc = append( + doc, + prettier.Group{ + Doc: signatureDoc, + }, + ) + } - if parameter.Label != "" { - parameterDoc = append(parameterDoc, - prettier.Text(parameter.Label), - prettier.Space, - ) - } + if block.IsEmpty() { + return append(doc, functionExpressionEmptyBlockDoc) + } else { + blockDoc := block.Doc() - parameterDoc = append( - parameterDoc, - prettier.Text(parameter.Identifier.Identifier), - typeSeparatorDoc, - parameter.TypeAnnotation.Doc(), + return append( + doc, + prettier.Space, + blockDoc, ) - - parameterDocs = append(parameterDocs, parameterDoc) } +} - return prettier.WrapParentheses( - prettier.Join( - functionExpressionParameterSeparatorDoc, - parameterDocs..., - ), - prettier.SoftLine{}, +func (e *FunctionExpression) Doc() prettier.Doc { + return FunctionDocument( + AccessNotSpecified, + true, + "", + e.ParameterList, + e.ReturnTypeAnnotation, + e.FunctionBlock, ) } @@ -1533,6 +1608,10 @@ func (e *FunctionExpression) MarshalJSON() ([]byte, error) { }) } +func (*FunctionExpression) precedence() precedence { + return precedenceLiteral +} + // CastingExpression type CastingExpression struct { @@ -1583,15 +1662,14 @@ func (e *CastingExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *CastingExpression) String() string { - return fmt.Sprintf( - "(%s %s %s)", - e.Expression, e.Operation.Symbol(), e.TypeAnnotation, - ) + return Prettier(e) } func (e *CastingExpression) Doc() prettier.Doc { - // TODO: potentially parenthesize - doc := e.Expression.Doc() + doc := parenthesizedExpressionDoc( + e.Expression, + e.precedence(), + ) return prettier.Group{ Doc: prettier.Concat{ @@ -1627,6 +1705,10 @@ func (e *CastingExpression) MarshalJSON() ([]byte, error) { }) } +func (*CastingExpression) precedence() precedence { + return precedenceCasting +} + // CreateExpression type CreateExpression struct { @@ -1671,16 +1753,14 @@ func (e *CreateExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *CreateExpression) String() string { - return fmt.Sprintf( - "(create %s)", - e.InvocationExpression, - ) + return Prettier(e) } +var createKeywordSpaceDoc = prettier.Text("create ") + func (e *CreateExpression) Doc() prettier.Doc { return prettier.Concat{ - prettier.Text("create "), - // TODO: potentially parenthesize + createKeywordSpaceDoc, e.InvocationExpression.Doc(), } } @@ -1706,6 +1786,10 @@ func (e *CreateExpression) MarshalJSON() ([]byte, error) { }) } +func (*CreateExpression) precedence() precedence { + return precedenceUnaryPrefix +} + // DestroyExpression type DestroyExpression struct { @@ -1750,10 +1834,7 @@ func (e *DestroyExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *DestroyExpression) String() string { - return fmt.Sprintf( - "(destroy %s)", - e.Expression, - ) + return Prettier(e) } const destroyExpressionKeywordDoc = prettier.Text("destroy ") @@ -1761,8 +1842,10 @@ const destroyExpressionKeywordDoc = prettier.Text("destroy ") func (e *DestroyExpression) Doc() prettier.Doc { return prettier.Concat{ destroyExpressionKeywordDoc, - // TODO: potentially parenthesize - e.Expression.Doc(), + parenthesizedExpressionDoc( + e.Expression, + e.precedence(), + ), } } @@ -1787,6 +1870,10 @@ func (e *DestroyExpression) MarshalJSON() ([]byte, error) { }) } +func (*DestroyExpression) precedence() precedence { + return precedenceUnaryPrefix +} + // ReferenceExpression type ReferenceExpression struct { @@ -1835,19 +1922,17 @@ func (e *ReferenceExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *ReferenceExpression) String() string { - return fmt.Sprintf( - "(&%s as %s)", - e.Expression, - e.Type, - ) + return Prettier(e) } var referenceExpressionRefOperatorDoc prettier.Doc = prettier.Text("&") var referenceExpressionAsOperatorDoc prettier.Doc = prettier.Text("as") func (e *ReferenceExpression) Doc() prettier.Doc { - // TODO: potentially parenthesize - doc := e.Expression.Doc() + doc := parenthesizedExpressionDoc( + e.Expression, + e.precedence(), + ) return prettier.Group{ Doc: prettier.Concat{ @@ -1884,6 +1969,10 @@ func (e *ReferenceExpression) MarshalJSON() ([]byte, error) { }) } +func (*ReferenceExpression) precedence() precedence { + return precedenceUnaryPrefix +} + // ForceExpression type ForceExpression struct { @@ -1928,15 +2017,17 @@ func (e *ForceExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *ForceExpression) String() string { - return fmt.Sprintf("%s!", e.Expression) + return Prettier(e) } const forceExpressionOperatorDoc = prettier.Text("!") func (e *ForceExpression) Doc() prettier.Doc { return prettier.Concat{ - // TODO: potentially parenthesize - e.Expression.Doc(), + parenthesizedExpressionDoc( + e.Expression, + e.precedence(), + ), forceExpressionOperatorDoc, } } @@ -1962,6 +2053,10 @@ func (e *ForceExpression) MarshalJSON() ([]byte, error) { }) } +func (*ForceExpression) precedence() precedence { + return precedenceUnaryPostfix +} + // PathExpression type PathExpression struct { @@ -2009,11 +2104,18 @@ func (e *PathExpression) AcceptExp(visitor ExpressionVisitor) Repr { } func (e *PathExpression) String() string { - return fmt.Sprintf("/%s/%s", e.Domain, e.Identifier) + return Prettier(e) } +var pathSeparatorDoc = prettier.Text("/") + func (e *PathExpression) Doc() prettier.Doc { - return prettier.Text(e.String()) + return prettier.Concat{ + pathSeparatorDoc, + prettier.Text(e.Domain.String()), + pathSeparatorDoc, + prettier.Text(e.Identifier.String()), + } } func (e *PathExpression) StartPosition() Position { @@ -2036,3 +2138,7 @@ func (e *PathExpression) MarshalJSON() ([]byte, error) { Alias: (*Alias)(e), }) } + +func (*PathExpression) precedence() precedence { + return precedenceLiteral +} diff --git a/runtime/ast/expression_test.go b/runtime/ast/expression_test.go index 64738c4f1f..04c96c8b0d 100644 --- a/runtime/ast/expression_test.go +++ b/runtime/ast/expression_test.go @@ -71,6 +71,21 @@ func TestBoolExpression_Doc(t *testing.T) { ) } +func TestBoolExpression_String(t *testing.T) { + + t.Parallel() + + assert.Equal(t, + "true", + (&BoolExpression{Value: true}).String(), + ) + + assert.Equal(t, + "false", + (&BoolExpression{Value: false}).String(), + ) +} + func TestNilExpression_MarshalJSON(t *testing.T) { t.Parallel() @@ -104,6 +119,16 @@ func TestNilExpression_Doc(t *testing.T) { ) } +func TestNilExpression_String(t *testing.T) { + + t.Parallel() + + assert.Equal(t, + "nil", + (&NilExpression{}).String(), + ) +} + func TestStringExpression_MarshalJSON(t *testing.T) { t.Parallel() @@ -142,6 +167,16 @@ func TestStringExpression_Doc(t *testing.T) { ) } +func TestStringExpression_String(t *testing.T) { + + t.Parallel() + + assert.Equal(t, + `"test"`, + (&StringExpression{Value: "test"}).String(), + ) +} + func TestIntegerExpression_MarshalJSON(t *testing.T) { t.Parallel() @@ -259,6 +294,91 @@ func TestIntegerExpression_Doc(t *testing.T) { }) } +func TestIntegerExpression_String(t *testing.T) { + + t.Parallel() + + t.Run("decimal", func(t *testing.T) { + + t.Parallel() + + expr := &IntegerExpression{ + PositiveLiteral: "4_2", + Value: big.NewInt(42), + Base: 10, + } + + assert.Equal(t, + "4_2", + expr.String(), + ) + }) + + t.Run("negative", func(t *testing.T) { + + t.Parallel() + + expr := &IntegerExpression{ + PositiveLiteral: "4_2", + Value: big.NewInt(-42), + Base: 10, + } + + assert.Equal(t, + "-4_2", + expr.String(), + ) + }) + + t.Run("binary", func(t *testing.T) { + + t.Parallel() + + expr := &IntegerExpression{ + PositiveLiteral: "0b10_10_10", + Value: big.NewInt(42), + Base: 2, + } + + assert.Equal(t, + "0b10_10_10", + expr.String(), + ) + }) + + t.Run("octal", func(t *testing.T) { + + t.Parallel() + + expr := &IntegerExpression{ + PositiveLiteral: "0o5_2", + Value: big.NewInt(42), + Base: 8, + } + + assert.Equal(t, + "0o5_2", + expr.String(), + ) + }) + + t.Run("hex", func(t *testing.T) { + + t.Parallel() + + expr := &IntegerExpression{ + PositiveLiteral: "0x2_A", + Value: big.NewInt(42), + Base: 16, + } + + assert.Equal(t, + "0x2_A", + expr.String(), + ) + }) +} + func TestFixedPointExpression_MarshalJSON(t *testing.T) { t.Parallel() @@ -333,6 +453,44 @@ func TestFixedPointExpression_Doc(t *testing.T) { }) } +func TestFixedPointExpression_String(t *testing.T) { + + t.Parallel() + + t.Run("positive", func(t *testing.T) { + + t.Parallel() + + expr := &FixedPointExpression{ + PositiveLiteral: "1_2.3_4", + UnsignedInteger: big.NewInt(42), + Scale: 2, + } + + assert.Equal(t, + "1_2.3_4", + expr.String(), + ) + }) + + t.Run("negative", func(t *testing.T) { + + t.Parallel() + + expr := &FixedPointExpression{ + PositiveLiteral: "1_2.3_4", + Negative: true, + UnsignedInteger: big.NewInt(42), + Scale: 2, + } + + assert.Equal(t, + "-1_2.3_4", + expr.String(), + ) + }) +} + func TestArrayExpression_MarshalJSON(t *testing.T) { t.Parallel() @@ -441,6 +599,39 @@ func TestArrayExpression_Doc(t *testing.T) { }) } +func TestArrayExpression_String(t *testing.T) { + + t.Parallel() + + t.Run("empty", func(t *testing.T) { + + t.Parallel() + + assert.Equal(t, + "[]", + (&ArrayExpression{}).String(), + ) + }) + + t.Run("non-empty", func(t *testing.T) { + + t.Parallel() + + expr := &ArrayExpression{ + Values: []Expression{ + &NilExpression{}, + &BoolExpression{Value: true}, + &StringExpression{Value: "test"}, + }, + } + + assert.Equal(t, + `[nil, true, "test"]`, + expr.String(), + ) + }) +} + func TestDictionaryExpression_MarshalJSON(t *testing.T) { t.Parallel() @@ -573,6 +764,45 @@ func TestDictionaryExpression_Doc(t *testing.T) { } +func TestDictionaryExpression_String(t *testing.T) { + + t.Parallel() + + t.Run("empty", func(t *testing.T) { + + t.Parallel() + + assert.Equal(t, + "{}", + (&DictionaryExpression{}).String(), + ) + }) + + t.Run("non-empty", func(t *testing.T) { + + t.Parallel() + + expr := &DictionaryExpression{ + Entries: []DictionaryEntry{ + { + Key: &StringExpression{Value: "foo"}, + Value: &NilExpression{}, + }, + { + Key: &StringExpression{Value: "bar"}, + Value: &BoolExpression{Value: true}, + }, + }, + } + + assert.Equal(t, + `{"foo": nil, "bar": true}`, + expr.String(), + ) + }) + +} + func TestIdentifierExpression_MarshalJSON(t *testing.T) { t.Parallel() @@ -618,6 +848,20 @@ func TestIdentifierExpression_Doc(t *testing.T) { ) } +func TestIdentifierExpression_String(t *testing.T) { + + t.Parallel() + + assert.Equal(t, + "test", + (&IdentifierExpression{ + Identifier: Identifier{ + Identifier: "test", + }, + }).String(), + ) +} + func TestPathExpression_MarshalJSON(t *testing.T) { t.Parallel() @@ -673,11 +917,35 @@ func TestPathExpression_Doc(t *testing.T) { } assert.Equal(t, - prettier.Text("/storage/test"), + prettier.Concat{ + prettier.Text("/"), + prettier.Text("storage"), + prettier.Text("/"), + prettier.Text("test"), + }, expr.Doc(), ) } +func TestPathExpression_String(t *testing.T) { + + t.Parallel() + + expr := &PathExpression{ + Domain: Identifier{ + Identifier: "storage", + }, + Identifier: Identifier{ + Identifier: "test", + }, + } + + assert.Equal(t, + "/storage/test", + expr.String(), + ) +} + func TestMemberExpression_MarshalJSON(t *testing.T) { t.Parallel() @@ -794,180 +1062,241 @@ func TestMemberExpression_Doc(t *testing.T) { expr.Doc(), ) }) -} -func TestIndexExpression_MarshalJSON(t *testing.T) { + t.Run("nested, same precedence", func(t *testing.T) { - t.Parallel() + t.Parallel() - expr := &IndexExpression{ - TargetExpression: &BoolExpression{ - Value: true, - Range: Range{ - StartPos: Position{Offset: 1, Line: 2, Column: 3}, - EndPos: Position{Offset: 4, Line: 5, Column: 6}, + expr := &MemberExpression{ + Expression: &MemberExpression{ + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + Identifier: Identifier{ + Identifier: "bar", + }, }, - }, - IndexingExpression: &NilExpression{ - Pos: Position{Offset: 7, Line: 8, Column: 9}, - }, - Range: Range{ - StartPos: Position{Offset: 10, Line: 11, Column: 12}, - EndPos: Position{Offset: 13, Line: 14, Column: 15}, - }, - } - - actual, err := json.Marshal(expr) - require.NoError(t, err) + Identifier: Identifier{ + Identifier: "baz", + }, + } - assert.JSONEq(t, - ` - { - "Type": "IndexExpression", - "TargetExpression": { - "Type": "BoolExpression", - "Value": true, - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 4, "Line": 5, "Column": 6} - }, - "IndexingExpression": { - "Type": "NilExpression", - "StartPos": {"Offset": 7, "Line": 8, "Column": 9}, - "EndPos": {"Offset": 9, "Line": 8, "Column": 11} - }, - "StartPos": {"Offset": 10, "Line": 11, "Column": 12}, - "EndPos": {"Offset": 13, "Line": 14, "Column": 15} - } - `, - string(actual), - ) -} + assert.Equal(t, + prettier.Concat{ + prettier.Concat{ + prettier.Text("foo"), + prettier.Group{ + Doc: prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Text("."), + prettier.Text("bar"), + }, + }, + }, + }, + prettier.Group{ + Doc: prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Text("."), + prettier.Text("baz"), + }, + }, + }, + }, + expr.Doc(), + ) + }) -func TestIndexExpression_Doc(t *testing.T) { + t.Run("nested, lower precedence", func(t *testing.T) { - t.Parallel() + t.Parallel() - expr := &IndexExpression{ - TargetExpression: &IdentifierExpression{ - Identifier: Identifier{ - Identifier: "foo", + expr := &MemberExpression{ + Expression: &BinaryExpression{ + Operation: OperationMinus, + Left: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + Right: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, + }, }, - }, - IndexingExpression: &IdentifierExpression{ Identifier: Identifier{ - Identifier: "bar", + Identifier: "baz", }, - }, - } + } - assert.Equal(t, - prettier.Concat{ - prettier.Text("foo"), - prettier.Group{ - Doc: prettier.Concat{ - prettier.Text("["), - prettier.Indent{ + assert.Equal(t, + prettier.Concat{ + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Text("foo"), + }, + prettier.Line{}, + prettier.Text("-"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Text("bar"), + }, + }, + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + prettier.Group{ + Doc: prettier.Indent{ Doc: prettier.Concat{ prettier.SoftLine{}, - prettier.Text("bar"), + prettier.Text("."), + prettier.Text("baz"), }, }, - prettier.SoftLine{}, - prettier.Text("]"), }, }, - }, - expr.Doc(), - ) + expr.Doc(), + ) + }) } -func TestUnaryExpression_MarshalJSON(t *testing.T) { +func TestMemberExpression_String(t *testing.T) { t.Parallel() - expr := &UnaryExpression{ - Operation: OperationNegate, - Expression: &IntegerExpression{ - PositiveLiteral: "42", - Value: big.NewInt(42), - Base: 10, - Range: Range{ - StartPos: Position{Offset: 1, Line: 2, Column: 3}, - EndPos: Position{Offset: 4, Line: 5, Column: 6}, + t.Run("non-optional", func(t *testing.T) { + + t.Parallel() + + expr := &MemberExpression{ + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, }, - }, - StartPos: Position{Offset: 7, Line: 8, Column: 9}, - } + Identifier: Identifier{ + Identifier: "bar", + }, + } - actual, err := json.Marshal(expr) - require.NoError(t, err) + assert.Equal(t, + "foo.bar", + expr.String(), + ) + }) - assert.JSONEq(t, - ` - { - "Type": "UnaryExpression", - "Operation": "OperationNegate", - "Expression": { - "Type": "IntegerExpression", - "PositiveLiteral": "42", - "Value": "42", - "Base": 10, - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 4, "Line": 5, "Column": 6} - }, - "StartPos": {"Offset": 7, "Line": 8, "Column": 9}, - "EndPos": {"Offset": 4, "Line": 5, "Column": 6} - } - `, - string(actual), - ) -} + t.Run("optional", func(t *testing.T) { -func TestUnaryExpression_Doc(t *testing.T) { + t.Parallel() - t.Parallel() + expr := &MemberExpression{ + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + Optional: true, + Identifier: Identifier{ + Identifier: "bar", + }, + } - expr := &UnaryExpression{ - Operation: OperationMinus, - Expression: &IdentifierExpression{ + assert.Equal(t, + "foo?.bar", + expr.String(), + ) + }) + + t.Run("nested, same precedence", func(t *testing.T) { + + t.Parallel() + + expr := &MemberExpression{ + Expression: &MemberExpression{ + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + Identifier: Identifier{ + Identifier: "bar", + }, + }, Identifier: Identifier{ - Identifier: "foo", + Identifier: "baz", }, - }, - } + } - assert.Equal(t, - prettier.Concat{ - prettier.Text("-"), - prettier.Text("foo"), - }, - expr.Doc(), - ) + assert.Equal(t, + "foo.bar.baz", + expr.String(), + ) + }) + + t.Run("nested, lower precedence", func(t *testing.T) { + + t.Parallel() + + expr := &MemberExpression{ + Expression: &BinaryExpression{ + Operation: OperationMinus, + Left: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + Right: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, + }, + }, + Identifier: Identifier{ + Identifier: "baz", + }, + } + + assert.Equal(t, + "(foo - bar).baz", + expr.String(), + ) + }) } -func TestBinaryExpression_MarshalJSON(t *testing.T) { +func TestIndexExpression_MarshalJSON(t *testing.T) { t.Parallel() - expr := &BinaryExpression{ - Operation: OperationPlus, - Left: &IntegerExpression{ - PositiveLiteral: "42", - Value: big.NewInt(42), - Base: 10, + expr := &IndexExpression{ + TargetExpression: &BoolExpression{ + Value: true, Range: Range{ StartPos: Position{Offset: 1, Line: 2, Column: 3}, EndPos: Position{Offset: 4, Line: 5, Column: 6}, }, }, - Right: &IntegerExpression{ - PositiveLiteral: "99", - Value: big.NewInt(99), - Base: 10, - Range: Range{ - StartPos: Position{Offset: 7, Line: 8, Column: 9}, - EndPos: Position{Offset: 10, Line: 11, Column: 12}, - }, + IndexingExpression: &NilExpression{ + Pos: Position{Offset: 7, Line: 8, Column: 9}, + }, + Range: Range{ + StartPos: Position{Offset: 10, Line: 11, Column: 12}, + EndPos: Position{Offset: 13, Line: 14, Column: 15}, }, } @@ -977,80 +1306,305 @@ func TestBinaryExpression_MarshalJSON(t *testing.T) { assert.JSONEq(t, ` { - "Type": "BinaryExpression", - "Operation": "OperationPlus", - "Left": { - "Type": "IntegerExpression", - "PositiveLiteral": "42", - "Value": "42", - "Base": 10, + "Type": "IndexExpression", + "TargetExpression": { + "Type": "BoolExpression", + "Value": true, "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, "EndPos": {"Offset": 4, "Line": 5, "Column": 6} }, - "Right": { - "Type": "IntegerExpression", - "PositiveLiteral": "99", - "Value": "99", - "Base": 10, + "IndexingExpression": { + "Type": "NilExpression", "StartPos": {"Offset": 7, "Line": 8, "Column": 9}, - "EndPos": {"Offset": 10, "Line": 11, "Column": 12} + "EndPos": {"Offset": 9, "Line": 8, "Column": 11} }, - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 10, "Line": 11, "Column": 12} - } + "StartPos": {"Offset": 10, "Line": 11, "Column": 12}, + "EndPos": {"Offset": 13, "Line": 14, "Column": 15} + } `, string(actual), ) } -func TestBinaryExpression_Doc(t *testing.T) { +func TestIndexExpression_Doc(t *testing.T) { t.Parallel() - expr := &BinaryExpression{ - Operation: OperationPlus, - Left: &IntegerExpression{ - PositiveLiteral: "42", - Value: big.NewInt(42), - Base: 10, - }, - Right: &IntegerExpression{ - PositiveLiteral: "99", - Value: big.NewInt(99), - Base: 10, - }, - } + t.Run("simple", func(t *testing.T) { - assert.Equal(t, - prettier.Group{ - Doc: prettier.Concat{ + t.Parallel() + + expr := &IndexExpression{ + TargetExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + IndexingExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, + }, + } + + assert.Equal(t, + prettier.Concat{ + prettier.Text("foo"), prettier.Group{ - Doc: prettier.Text("42"), + Doc: prettier.Concat{ + prettier.Text("["), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Text("bar"), + }, + }, + prettier.SoftLine{}, + prettier.Text("]"), + }, + }, + }, + expr.Doc(), + ) + }) + + t.Run("nested, same precedence", func(t *testing.T) { + + t.Parallel() + + expr := &IndexExpression{ + TargetExpression: &IndexExpression{ + TargetExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + IndexingExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, + }, + }, + IndexingExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "baz", + }, + }, + } + + assert.Equal(t, + prettier.Concat{ + prettier.Concat{ + prettier.Text("foo"), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("["), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Text("bar"), + }, + }, + prettier.SoftLine{}, + prettier.Text("]"), + }, + }, }, - prettier.Line{}, - prettier.Text("+"), - prettier.Space, prettier.Group{ - Doc: prettier.Text("99"), + Doc: prettier.Concat{ + prettier.Text("["), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Text("baz"), + }, + }, + prettier.SoftLine{}, + prettier.Text("]"), + }, }, }, - }, - expr.Doc(), - ) + expr.Doc(), + ) + }) + + t.Run("nested, lower precedence", func(t *testing.T) { + + t.Parallel() + + expr := &IndexExpression{ + TargetExpression: &BinaryExpression{ + Operation: OperationMinus, + Left: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + Right: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, + }, + }, + IndexingExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "baz", + }, + }, + } + + assert.Equal(t, + prettier.Concat{ + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Text("foo"), + }, + prettier.Line{}, + prettier.Text("-"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Text("bar"), + }, + }, + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("["), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Text("baz"), + }, + }, + prettier.SoftLine{}, + prettier.Text("]"), + }, + }, + }, + expr.Doc(), + ) + }) + } -func TestDestroyExpression_MarshalJSON(t *testing.T) { +func TestIndexExpression_String(t *testing.T) { t.Parallel() - expr := &DestroyExpression{ - Expression: &IdentifierExpression{ - Identifier: Identifier{ - Identifier: "foobar", - Pos: Position{Offset: 1, Line: 2, Column: 3}, + t.Run("simple", func(t *testing.T) { + + t.Parallel() + + expr := &IndexExpression{ + TargetExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + IndexingExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, + }, + } + + assert.Equal(t, + "foo[bar]", + expr.String(), + ) + }) + + t.Run("nested, same precedence", func(t *testing.T) { + + t.Parallel() + + expr := &IndexExpression{ + TargetExpression: &IndexExpression{ + TargetExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + IndexingExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, + }, + }, + IndexingExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "baz", + }, + }, + } + + assert.Equal(t, + "foo[bar][baz]", + expr.String(), + ) + }) + + t.Run("nested, lower precedence", func(t *testing.T) { + + t.Parallel() + + expr := &IndexExpression{ + TargetExpression: &BinaryExpression{ + Operation: OperationMinus, + Left: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + Right: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, + }, + }, + IndexingExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "baz", + }, + }, + } + + assert.Equal(t, + "(foo - bar)[baz]", + expr.String(), + ) + }) +} + +func TestUnaryExpression_MarshalJSON(t *testing.T) { + + t.Parallel() + + expr := &UnaryExpression{ + Operation: OperationNegate, + Expression: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + Range: Range{ + StartPos: Position{Offset: 1, Line: 2, Column: 3}, + EndPos: Position{Offset: 4, Line: 5, Column: 6}, }, }, - StartPos: Position{Offset: 4, Line: 5, Column: 6}, + StartPos: Position{Offset: 7, Line: 8, Column: 9}, } actual, err := json.Marshal(expr) @@ -1059,133 +1613,228 @@ func TestDestroyExpression_MarshalJSON(t *testing.T) { assert.JSONEq(t, ` { - "Type": "DestroyExpression", + "Type": "UnaryExpression", + "Operation": "OperationNegate", "Expression": { - "Type": "IdentifierExpression", - "Identifier": { - "Identifier": "foobar", - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 6, "Line": 2, "Column": 8} - }, + "Type": "IntegerExpression", + "PositiveLiteral": "42", + "Value": "42", + "Base": 10, "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 6, "Line": 2, "Column": 8} + "EndPos": {"Offset": 4, "Line": 5, "Column": 6} }, - "StartPos": {"Offset": 4, "Line": 5, "Column": 6}, - "EndPos": {"Offset": 6, "Line": 2, "Column": 8} + "StartPos": {"Offset": 7, "Line": 8, "Column": 9}, + "EndPos": {"Offset": 4, "Line": 5, "Column": 6} } `, string(actual), ) } -func TestDestroyExpression_Doc(t *testing.T) { +func TestUnaryExpression_Doc(t *testing.T) { t.Parallel() - d := &DestroyExpression{ - Expression: &IdentifierExpression{ - Identifier: Identifier{ - Identifier: "foo", - }, - }, - } + t.Run("simple", func(t *testing.T) { - assert.Equal(t, - prettier.Concat{ - prettier.Text("destroy "), - prettier.Text("foo"), - }, - d.Doc(), - ) -} + t.Parallel() -func TestForceExpression_MarshalJSON(t *testing.T) { + expr := &UnaryExpression{ + Operation: OperationMinus, + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + } - t.Parallel() + assert.Equal(t, + prettier.Concat{ + prettier.Text("-"), + prettier.Text("foo"), + }, + expr.Doc(), + ) + }) - expr := &ForceExpression{ - Expression: &IdentifierExpression{ - Identifier: Identifier{ - Identifier: "foobar", - Pos: Position{Offset: 1, Line: 2, Column: 3}, + t.Run("nested, same precedence", func(t *testing.T) { + + t.Parallel() + + expr := &UnaryExpression{ + Operation: OperationMinus, + Expression: &UnaryExpression{ + Operation: OperationMinus, + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, }, - }, - EndPos: Position{Offset: 4, Line: 5, Column: 6}, - } + } - actual, err := json.Marshal(expr) - require.NoError(t, err) + assert.Equal(t, + prettier.Concat{ + prettier.Text("-"), + prettier.Concat{ + prettier.Text("-"), + prettier.Text("foo"), + }, + }, + expr.Doc(), + ) + }) - assert.JSONEq(t, - ` - { - "Type": "ForceExpression", - "Expression": { - "Type": "IdentifierExpression", - "Identifier": { - "Identifier": "foobar", - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 6, "Line": 2, "Column": 8} - }, - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 6, "Line": 2, "Column": 8} - }, - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 4, "Line": 5, "Column": 6} - } - `, - string(actual), - ) + t.Run("nested, lower precedence", func(t *testing.T) { + + t.Parallel() + + expr := &UnaryExpression{ + Operation: OperationMinus, + Expression: &BinaryExpression{ + Operation: OperationMinus, + Left: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + Right: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, + }, + }, + } + + assert.Equal(t, + prettier.Concat{ + prettier.Text("-"), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Text("foo"), + }, + prettier.Line{}, + prettier.Text("-"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Text("bar"), + }, + }, + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + }, + expr.Doc(), + ) + }) } -func TestForceExpression_Doc(t *testing.T) { +func TestUnaryExpression_String(t *testing.T) { t.Parallel() - expr := &ForceExpression{ - Expression: &IdentifierExpression{ - Identifier: Identifier{ - Identifier: "foo", + t.Run("simple", func(t *testing.T) { + + t.Parallel() + + expr := &UnaryExpression{ + Operation: OperationMinus, + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, }, - }, - } - assert.Equal(t, - prettier.Concat{ - prettier.Text("foo"), - prettier.Text("!"), - }, - expr.Doc(), - ) + } + + assert.Equal(t, + "-foo", + expr.String(), + ) + }) + + t.Run("nested, same precedence", func(t *testing.T) { + + t.Parallel() + + expr := &UnaryExpression{ + Operation: OperationMinus, + Expression: &UnaryExpression{ + Operation: OperationMinus, + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + }, + } + + assert.Equal(t, + "--foo", + expr.String(), + ) + }) + + t.Run("nested, lower precedence", func(t *testing.T) { + + t.Parallel() + + expr := &UnaryExpression{ + Operation: OperationMinus, + Expression: &BinaryExpression{ + Operation: OperationMinus, + Left: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + Right: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, + }, + }, + } + + assert.Equal(t, + "-(foo - bar)", + expr.String(), + ) + }) } -func TestConditionalExpression_MarshalJSON(t *testing.T) { +func TestBinaryExpression_MarshalJSON(t *testing.T) { t.Parallel() - expr := &ConditionalExpression{ - Test: &BoolExpression{ - Value: false, - Range: Range{ - StartPos: Position{Offset: 1, Line: 2, Column: 3}, - EndPos: Position{Offset: 4, Line: 5, Column: 6}, - }, - }, - Then: &IntegerExpression{ + expr := &BinaryExpression{ + Operation: OperationPlus, + Left: &IntegerExpression{ PositiveLiteral: "42", Value: big.NewInt(42), Base: 10, Range: Range{ - StartPos: Position{Offset: 7, Line: 8, Column: 9}, - EndPos: Position{Offset: 10, Line: 11, Column: 12}, + StartPos: Position{Offset: 1, Line: 2, Column: 3}, + EndPos: Position{Offset: 4, Line: 5, Column: 6}, }, }, - Else: &IntegerExpression{ + Right: &IntegerExpression{ PositiveLiteral: "99", Value: big.NewInt(99), Base: 10, Range: Range{ - StartPos: Position{Offset: 13, Line: 14, Column: 15}, - EndPos: Position{Offset: 16, Line: 17, Column: 18}, + StartPos: Position{Offset: 7, Line: 8, Column: 9}, + EndPos: Position{Offset: 10, Line: 11, Column: 12}, }, }, } @@ -1196,125 +1845,1780 @@ func TestConditionalExpression_MarshalJSON(t *testing.T) { assert.JSONEq(t, ` { - "Type": "ConditionalExpression", - "Test": { - "Type": "BoolExpression", - "Value": false, - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 4, "Line": 5, "Column": 6} - }, - "Then": { + "Type": "BinaryExpression", + "Operation": "OperationPlus", + "Left": { "Type": "IntegerExpression", "PositiveLiteral": "42", "Value": "42", "Base": 10, - "StartPos": {"Offset": 7, "Line": 8, "Column": 9}, - "EndPos": {"Offset": 10, "Line": 11, "Column": 12} + "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, + "EndPos": {"Offset": 4, "Line": 5, "Column": 6} }, - "Else": { + "Right": { "Type": "IntegerExpression", "PositiveLiteral": "99", "Value": "99", "Base": 10, - "StartPos": {"Offset": 13, "Line": 14, "Column": 15}, - "EndPos": {"Offset": 16, "Line": 17, "Column": 18} + "StartPos": {"Offset": 7, "Line": 8, "Column": 9}, + "EndPos": {"Offset": 10, "Line": 11, "Column": 12} }, "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 16, "Line": 17, "Column": 18} + "EndPos": {"Offset": 10, "Line": 11, "Column": 12} } `, string(actual), ) } -func TestConditionalExpression_Doc(t *testing.T) { +func TestBinaryExpression_Doc(t *testing.T) { + + t.Parallel() + + t.Run("simple", func(t *testing.T) { + + t.Parallel() + + expr := &BinaryExpression{ + Operation: OperationPlus, + Left: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Right: &IntegerExpression{ + PositiveLiteral: "99", + Value: big.NewInt(99), + Base: 10, + }, + } + + assert.Equal(t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Text("42"), + }, + prettier.Line{}, + prettier.Text("+"), + prettier.Space, + prettier.Group{ + Doc: prettier.Text("99"), + }, + }, + }, + expr.Doc(), + ) + }) + + t.Run("nested, same precedence, left associative", func(t *testing.T) { + + t.Parallel() + + expr := &BinaryExpression{ + Operation: OperationPlus, + Left: &BinaryExpression{ + Operation: OperationPlus, + Left: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Right: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(42), + Base: 10, + }, + }, + Right: &IntegerExpression{ + PositiveLiteral: "99", + Value: big.NewInt(99), + Base: 10, + }, + } + + assert.Equal(t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Text("42"), + }, + prettier.Line{}, + prettier.Text("+"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Text("1"), + }, + }, + }, + }, + prettier.Line{}, + prettier.Text("+"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Text("99"), + }, + }, + }, + expr.Doc(), + ) + }) + + t.Run("nested, lower precedence, left associative", func(t *testing.T) { + + t.Parallel() + + expr := &BinaryExpression{ + Operation: OperationPlus, + Left: &BinaryExpression{ + Operation: OperationBitwiseOr, + Left: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Right: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(42), + Base: 10, + }, + }, + Right: &IntegerExpression{ + PositiveLiteral: "99", + Value: big.NewInt(99), + Base: 10, + }, + } + + assert.Equal(t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Text("42"), + }, + prettier.Line{}, + prettier.Text("|"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Text("1"), + }, + }, + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + }, + prettier.Line{}, + prettier.Text("+"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Text("99"), + }, + }, + }, + expr.Doc(), + ) + }) + + t.Run("nested, same precedence, right associative", func(t *testing.T) { + + t.Parallel() + + expr := &BinaryExpression{ + Operation: OperationNilCoalesce, + Left: &IntegerExpression{ + PositiveLiteral: "99", + Value: big.NewInt(99), + Base: 10, + }, + Right: &BinaryExpression{ + Operation: OperationNilCoalesce, + Left: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Right: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(42), + Base: 10, + }, + }, + } + + assert.Equal(t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Text("99"), + }, + prettier.Line{}, + prettier.Text("??"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Text("42"), + }, + prettier.Line{}, + prettier.Text("??"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Text("1"), + }, + }, + }, + }, + }, + }, + expr.Doc(), + ) + }) + + t.Run("nested, lower precedence, right associative", func(t *testing.T) { + + t.Parallel() + + expr := &BinaryExpression{ + Operation: OperationNilCoalesce, + Left: &BinaryExpression{ + Operation: OperationOr, + Left: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Right: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(42), + Base: 10, + }, + }, + Right: &IntegerExpression{ + PositiveLiteral: "99", + Value: big.NewInt(99), + Base: 10, + }, + } + + assert.Equal(t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Text("42"), + }, + prettier.Line{}, + prettier.Text("||"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Text("1"), + }, + }, + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + }, + prettier.Line{}, + prettier.Text("??"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Text("99"), + }, + }, + }, + expr.Doc(), + ) + }) + +} + +func TestBinaryExpression_String(t *testing.T) { + + t.Parallel() + + t.Run("simple", func(t *testing.T) { + + t.Parallel() + + expr := &BinaryExpression{ + Operation: OperationPlus, + Left: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Right: &IntegerExpression{ + PositiveLiteral: "99", + Value: big.NewInt(99), + Base: 10, + }, + } + + assert.Equal(t, + "42 + 99", + expr.String(), + ) + }) + + t.Run("nested, same precedence, left associative", func(t *testing.T) { + + t.Parallel() + + expr := &BinaryExpression{ + Operation: OperationPlus, + Left: &BinaryExpression{ + Operation: OperationPlus, + Left: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Right: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(42), + Base: 10, + }, + }, + Right: &IntegerExpression{ + PositiveLiteral: "99", + Value: big.NewInt(99), + Base: 10, + }, + } + + assert.Equal(t, + "42 + 1 + 99", + expr.String(), + ) + }) + + t.Run("nested, lower precedence, left associative", func(t *testing.T) { + + t.Parallel() + + expr := &BinaryExpression{ + Operation: OperationPlus, + Left: &BinaryExpression{ + Operation: OperationBitwiseOr, + Left: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Right: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(42), + Base: 10, + }, + }, + Right: &IntegerExpression{ + PositiveLiteral: "99", + Value: big.NewInt(99), + Base: 10, + }, + } + + assert.Equal(t, + "(42 | 1) + 99", + expr.String(), + ) + }) + + t.Run("nested, same precedence, right associative", func(t *testing.T) { + + t.Parallel() + + expr := &BinaryExpression{ + Operation: OperationNilCoalesce, + Left: &IntegerExpression{ + PositiveLiteral: "99", + Value: big.NewInt(99), + Base: 10, + }, + Right: &BinaryExpression{ + Operation: OperationNilCoalesce, + Left: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Right: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(42), + Base: 10, + }, + }, + } + + assert.Equal(t, + "99 ?? 42 ?? 1", + expr.String(), + ) + }) + + t.Run("nested, lower precedence, right associative", func(t *testing.T) { + + t.Parallel() + + expr := &BinaryExpression{ + Operation: OperationNilCoalesce, + Left: &BinaryExpression{ + Operation: OperationOr, + Left: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Right: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(42), + Base: 10, + }, + }, + Right: &IntegerExpression{ + PositiveLiteral: "99", + Value: big.NewInt(99), + Base: 10, + }, + } + + assert.Equal(t, + "(42 || 1) ?? 99", + expr.String(), + ) + }) + +} + +func TestDestroyExpression_MarshalJSON(t *testing.T) { + + t.Parallel() + + expr := &DestroyExpression{ + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foobar", + Pos: Position{Offset: 1, Line: 2, Column: 3}, + }, + }, + StartPos: Position{Offset: 4, Line: 5, Column: 6}, + } + + actual, err := json.Marshal(expr) + require.NoError(t, err) + + assert.JSONEq(t, + ` + { + "Type": "DestroyExpression", + "Expression": { + "Type": "IdentifierExpression", + "Identifier": { + "Identifier": "foobar", + "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, + "EndPos": {"Offset": 6, "Line": 2, "Column": 8} + }, + "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, + "EndPos": {"Offset": 6, "Line": 2, "Column": 8} + }, + "StartPos": {"Offset": 4, "Line": 5, "Column": 6}, + "EndPos": {"Offset": 6, "Line": 2, "Column": 8} + } + `, + string(actual), + ) +} + +func TestDestroyExpression_Doc(t *testing.T) { + + t.Parallel() + + t.Run("simple", func(t *testing.T) { + + t.Parallel() + + expr := &DestroyExpression{ + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + } + + assert.Equal(t, + prettier.Concat{ + prettier.Text("destroy "), + prettier.Text("foo"), + }, + expr.Doc(), + ) + }) + + t.Run("nested, same precedence", func(t *testing.T) { + + t.Parallel() + + expr := &DestroyExpression{ + Expression: &UnaryExpression{ + Operation: OperationMinus, + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + }, + } + + assert.Equal(t, + prettier.Concat{ + prettier.Text("destroy "), + prettier.Concat{ + prettier.Text("-"), + prettier.Text("foo"), + }, + }, + expr.Doc(), + ) + }) + + t.Run("nested, lower precedence", func(t *testing.T) { + + t.Parallel() + + expr := &DestroyExpression{ + Expression: &BinaryExpression{ + Operation: OperationMinus, + Left: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + Right: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, + }, + }, + } + + assert.Equal(t, + prettier.Concat{ + prettier.Text("destroy "), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Text("foo"), + }, + prettier.Line{}, + prettier.Text("-"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Text("bar"), + }, + }, + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + }, + expr.Doc(), + ) + }) +} + +func TestDestroyExpression_String(t *testing.T) { + + t.Parallel() + + t.Run("simple", func(t *testing.T) { + + t.Parallel() + + expr := &DestroyExpression{ + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + } + + assert.Equal(t, + "destroy foo", + expr.String(), + ) + }) + + t.Run("nested, same precedence", func(t *testing.T) { + + t.Parallel() + + expr := &DestroyExpression{ + Expression: &UnaryExpression{ + Operation: OperationMinus, + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + }, + } + + assert.Equal(t, + "destroy -foo", + expr.String(), + ) + }) + + t.Run("nested, lower precedence", func(t *testing.T) { + + t.Parallel() + + expr := &DestroyExpression{ + Expression: &BinaryExpression{ + Operation: OperationMinus, + Left: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + Right: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, + }, + }, + } + + assert.Equal(t, + "destroy (foo - bar)", + expr.String(), + ) + }) +} + +func TestForceExpression_MarshalJSON(t *testing.T) { + + t.Parallel() + + expr := &ForceExpression{ + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foobar", + Pos: Position{Offset: 1, Line: 2, Column: 3}, + }, + }, + EndPos: Position{Offset: 4, Line: 5, Column: 6}, + } + + actual, err := json.Marshal(expr) + require.NoError(t, err) + + assert.JSONEq(t, + ` + { + "Type": "ForceExpression", + "Expression": { + "Type": "IdentifierExpression", + "Identifier": { + "Identifier": "foobar", + "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, + "EndPos": {"Offset": 6, "Line": 2, "Column": 8} + }, + "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, + "EndPos": {"Offset": 6, "Line": 2, "Column": 8} + }, + "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, + "EndPos": {"Offset": 4, "Line": 5, "Column": 6} + } + `, + string(actual), + ) +} + +func TestForceExpression_Doc(t *testing.T) { + + t.Parallel() + + t.Run("simple", func(t *testing.T) { + + t.Parallel() + + expr := &ForceExpression{ + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + } + + assert.Equal(t, + prettier.Concat{ + prettier.Text("foo"), + prettier.Text("!"), + }, + expr.Doc(), + ) + }) + + t.Run("nested, same precedence", func(t *testing.T) { + + t.Parallel() + + expr := &ForceExpression{ + Expression: &ForceExpression{ + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + }, + } + + assert.Equal(t, + prettier.Concat{ + prettier.Concat{ + prettier.Text("foo"), + prettier.Text("!"), + }, + prettier.Text("!"), + }, + expr.Doc(), + ) + }) + + t.Run("nested, lower precedence", func(t *testing.T) { + + t.Parallel() + + expr := &ForceExpression{ + Expression: &UnaryExpression{ + Operation: OperationMinus, + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + }, + } + + assert.Equal(t, + prettier.Concat{ + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Concat{ + prettier.Text("-"), + prettier.Text("foo"), + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + prettier.Text("!"), + }, + expr.Doc(), + ) + }) +} + +func TestForceExpression_String(t *testing.T) { + + t.Parallel() + + t.Run("simple", func(t *testing.T) { + + t.Parallel() + + expr := &ForceExpression{ + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + } + + assert.Equal(t, + "foo!", + expr.String(), + ) + }) + + t.Run("nested, same precedence", func(t *testing.T) { + + t.Parallel() + + expr := &ForceExpression{ + Expression: &ForceExpression{ + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + }, + } + + assert.Equal(t, + "foo!!", + expr.String(), + ) + }) + + t.Run("nested, lower precedence", func(t *testing.T) { + + t.Parallel() + + expr := &ForceExpression{ + Expression: &UnaryExpression{ + Operation: OperationMinus, + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + }, + } + + assert.Equal(t, + "(-foo)!", + expr.String(), + ) + }) +} + +func TestConditionalExpression_MarshalJSON(t *testing.T) { + + t.Parallel() + + expr := &ConditionalExpression{ + Test: &BoolExpression{ + Value: false, + Range: Range{ + StartPos: Position{Offset: 1, Line: 2, Column: 3}, + EndPos: Position{Offset: 4, Line: 5, Column: 6}, + }, + }, + Then: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + Range: Range{ + StartPos: Position{Offset: 7, Line: 8, Column: 9}, + EndPos: Position{Offset: 10, Line: 11, Column: 12}, + }, + }, + Else: &IntegerExpression{ + PositiveLiteral: "99", + Value: big.NewInt(99), + Base: 10, + Range: Range{ + StartPos: Position{Offset: 13, Line: 14, Column: 15}, + EndPos: Position{Offset: 16, Line: 17, Column: 18}, + }, + }, + } + + actual, err := json.Marshal(expr) + require.NoError(t, err) + + assert.JSONEq(t, + ` + { + "Type": "ConditionalExpression", + "Test": { + "Type": "BoolExpression", + "Value": false, + "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, + "EndPos": {"Offset": 4, "Line": 5, "Column": 6} + }, + "Then": { + "Type": "IntegerExpression", + "PositiveLiteral": "42", + "Value": "42", + "Base": 10, + "StartPos": {"Offset": 7, "Line": 8, "Column": 9}, + "EndPos": {"Offset": 10, "Line": 11, "Column": 12} + }, + "Else": { + "Type": "IntegerExpression", + "PositiveLiteral": "99", + "Value": "99", + "Base": 10, + "StartPos": {"Offset": 13, "Line": 14, "Column": 15}, + "EndPos": {"Offset": 16, "Line": 17, "Column": 18} + }, + "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, + "EndPos": {"Offset": 16, "Line": 17, "Column": 18} + } + `, + string(actual), + ) +} + +func TestConditionalExpression_Doc(t *testing.T) { + + t.Parallel() + + t.Run("simple", func(t *testing.T) { + + t.Parallel() + + expr := &ConditionalExpression{ + Test: &BoolExpression{ + Value: false, + }, + Then: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Else: &IntegerExpression{ + PositiveLiteral: "99", + Value: big.NewInt(99), + Base: 10, + }, + } + + assert.Equal(t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text(`false`), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.Concat{ + prettier.Line{}, + prettier.Text("? "), + }, + prettier.Indent{ + Doc: prettier.Text(`42`), + }, + prettier.Concat{ + prettier.Line{}, + prettier.Text(": "), + }, + prettier.Indent{ + Doc: prettier.Text(`99`), + }, + }, + }, + }, + }, + expr.Doc(), + ) + + }) + + t.Run("nested test, same precedence", func(t *testing.T) { + + t.Parallel() + + expr := &ConditionalExpression{ + Test: &ConditionalExpression{ + Test: &BoolExpression{ + Value: false, + }, + Then: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(1), + Base: 10, + }, + Else: &IntegerExpression{ + PositiveLiteral: "2", + Value: big.NewInt(2), + Base: 10, + }, + }, + Then: &IntegerExpression{ + PositiveLiteral: "3", + Value: big.NewInt(3), + Base: 10, + }, + Else: &IntegerExpression{ + PositiveLiteral: "4", + Value: big.NewInt(4), + Base: 10, + }, + } + + assert.Equal(t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("false"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.Concat{ + prettier.Line{}, + prettier.Text("? "), + }, + prettier.Indent{ + Doc: prettier.Text("1"), + }, + prettier.Concat{ + prettier.Line{}, + prettier.Text(": "), + }, + prettier.Indent{ + Doc: prettier.Text("2"), + }, + }, + }, + }, + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + prettier.Indent{ + Doc: prettier.Concat{ + prettier.Concat{ + prettier.Line{}, + prettier.Text("? "), + }, + prettier.Indent{ + Doc: prettier.Text("3"), + }, + prettier.Concat{ + prettier.Line{}, + prettier.Text(": "), + }, + prettier.Indent{ + Doc: prettier.Text("4"), + }, + }, + }, + }, + }, + expr.Doc(), + ) + }) + + t.Run("nested then, same precedence", func(t *testing.T) { + + t.Parallel() + + expr := &ConditionalExpression{ + Test: &BoolExpression{ + Value: false, + }, + Then: &ConditionalExpression{ + Test: &BoolExpression{ + Value: false, + }, + Then: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(1), + Base: 10, + }, + Else: &IntegerExpression{ + PositiveLiteral: "2", + Value: big.NewInt(2), + Base: 10, + }, + }, + Else: &IntegerExpression{ + PositiveLiteral: "3", + Value: big.NewInt(3), + Base: 10, + }, + } + + assert.Equal(t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("false"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.Concat{ + prettier.Line{}, + prettier.Text("? "), + }, + prettier.Indent{ + Doc: prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("false"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.Concat{ + prettier.Line{}, + prettier.Text("? "), + }, + prettier.Indent{ + Doc: prettier.Text("1"), + }, + prettier.Concat{ + prettier.Line{}, + prettier.Text(": "), + }, + prettier.Indent{ + Doc: prettier.Text("2"), + }, + }, + }, + }, + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + }, + prettier.Concat{ + prettier.Line{}, + prettier.Text(": "), + }, + prettier.Indent{ + Doc: prettier.Text("3"), + }, + }, + }, + }, + }, + expr.Doc(), + ) + }) + + t.Run("nested else, same precedence", func(t *testing.T) { + + t.Parallel() + + expr := &ConditionalExpression{ + Test: &BoolExpression{ + Value: false, + }, + Then: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(1), + Base: 10, + }, + Else: &ConditionalExpression{ + Test: &BoolExpression{ + Value: false, + }, + Then: &IntegerExpression{ + PositiveLiteral: "2", + Value: big.NewInt(2), + Base: 10, + }, + Else: &IntegerExpression{ + PositiveLiteral: "3", + Value: big.NewInt(3), + Base: 10, + }, + }, + } + + assert.Equal(t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("false"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.Concat{ + prettier.Line{}, + prettier.Text("? "), + }, + prettier.Indent{ + Doc: prettier.Text("1"), + }, + prettier.Concat{ + prettier.Line{}, + prettier.Text(": "), + }, + prettier.Indent{ + Doc: prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("false"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.Concat{ + prettier.Line{}, + prettier.Text("? "), + }, + prettier.Indent{ + Doc: prettier.Text("2"), + }, + prettier.Concat{ + prettier.Line{}, + prettier.Text(": "), + }, + prettier.Indent{ + Doc: prettier.Text("3"), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expr.Doc(), + ) + }) + +} + +func TestConditionalExpression_String(t *testing.T) { + + t.Parallel() + + t.Run("simple", func(t *testing.T) { + + t.Parallel() + + expr := &ConditionalExpression{ + Test: &BoolExpression{ + Value: false, + }, + Then: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Else: &IntegerExpression{ + PositiveLiteral: "99", + Value: big.NewInt(99), + Base: 10, + }, + } + + assert.Equal(t, + "false ? 42 : 99", + expr.String(), + ) + }) + + t.Run("nested test, same precedence", func(t *testing.T) { + + t.Parallel() + + expr := &ConditionalExpression{ + Test: &ConditionalExpression{ + Test: &BoolExpression{ + Value: false, + }, + Then: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(1), + Base: 10, + }, + Else: &IntegerExpression{ + PositiveLiteral: "2", + Value: big.NewInt(2), + Base: 10, + }, + }, + Then: &IntegerExpression{ + PositiveLiteral: "3", + Value: big.NewInt(3), + Base: 10, + }, + Else: &IntegerExpression{ + PositiveLiteral: "4", + Value: big.NewInt(4), + Base: 10, + }, + } + + assert.Equal(t, + "(false ? 1 : 2) ? 3 : 4", + expr.String(), + ) + }) + + t.Run("nested then, same precedence", func(t *testing.T) { + + t.Parallel() + + expr := &ConditionalExpression{ + Test: &BoolExpression{ + Value: false, + }, + Then: &ConditionalExpression{ + Test: &BoolExpression{ + Value: false, + }, + Then: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(1), + Base: 10, + }, + Else: &IntegerExpression{ + PositiveLiteral: "2", + Value: big.NewInt(2), + Base: 10, + }, + }, + Else: &IntegerExpression{ + PositiveLiteral: "3", + Value: big.NewInt(3), + Base: 10, + }, + } + + assert.Equal(t, + "false ? (false ? 1 : 2) : 3", + expr.String(), + ) + }) + + t.Run("nested else, same precedence", func(t *testing.T) { + + t.Parallel() + + expr := &ConditionalExpression{ + Test: &BoolExpression{ + Value: false, + }, + Then: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(1), + Base: 10, + }, + Else: &ConditionalExpression{ + Test: &BoolExpression{ + Value: false, + }, + Then: &IntegerExpression{ + PositiveLiteral: "2", + Value: big.NewInt(2), + Base: 10, + }, + Else: &IntegerExpression{ + PositiveLiteral: "3", + Value: big.NewInt(3), + Base: 10, + }, + }, + } + + assert.Equal(t, + "false ? 1 : false ? 2 : 3", + expr.String(), + ) + }) + +} + +func TestInvocationExpression_MarshalJSON(t *testing.T) { + + t.Parallel() + + expr := &InvocationExpression{ + InvokedExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foobar", + Pos: Position{Offset: 1, Line: 2, Column: 3}, + }, + }, + TypeArguments: []*TypeAnnotation{ + { + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AB", + Pos: Position{Offset: 4, Line: 5, Column: 6}, + }, + }, + StartPos: Position{Offset: 7, Line: 8, Column: 9}, + }, + }, + Arguments: []*Argument{ + { + Label: "ok", + LabelStartPos: &Position{Offset: 10, Line: 11, Column: 12}, + LabelEndPos: &Position{Offset: 13, Line: 14, Column: 15}, + Expression: &BoolExpression{ + Value: false, + Range: Range{ + StartPos: Position{Offset: 16, Line: 17, Column: 18}, + EndPos: Position{Offset: 19, Line: 20, Column: 21}, + }, + }, + TrailingSeparatorPos: Position{Offset: 25, Line: 26, Column: 27}, + }, + }, + ArgumentsStartPos: Position{Offset: 28, Line: 29, Column: 30}, + EndPos: Position{Offset: 22, Line: 23, Column: 24}, + } + + actual, err := json.Marshal(expr) + require.NoError(t, err) + + assert.JSONEq(t, + ` + { + "Type": "InvocationExpression", + "InvokedExpression": { + "Type": "IdentifierExpression", + "Identifier": { + "Identifier": "foobar", + "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, + "EndPos": {"Offset": 6, "Line": 2, "Column": 8} + }, + "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, + "EndPos": {"Offset": 6, "Line": 2, "Column": 8} + }, + "TypeArguments": [ + { + "IsResource": true, + "AnnotatedType": { + "Type": "NominalType", + "Identifier": { + "Identifier": "AB", + "StartPos": {"Offset": 4, "Line": 5, "Column": 6}, + "EndPos": {"Offset": 5, "Line": 5, "Column": 7} + }, + "StartPos": {"Offset": 4, "Line": 5, "Column": 6}, + "EndPos": {"Offset": 5, "Line": 5, "Column": 7} + }, + "StartPos": {"Offset": 7, "Line": 8, "Column": 9}, + "EndPos": {"Offset": 5, "Line": 5, "Column": 7} + } + ], + "Arguments": [ + { + "Label": "ok", + "LabelStartPos": {"Offset": 10, "Line": 11, "Column": 12}, + "LabelEndPos": {"Offset": 13, "Line": 14, "Column": 15}, + "Expression": { + "Type": "BoolExpression", + "Value": false, + "StartPos": {"Offset": 16, "Line": 17, "Column": 18}, + "EndPos": {"Offset": 19, "Line": 20, "Column": 21} + }, + "StartPos": {"Offset": 10, "Line": 11, "Column": 12}, + "EndPos": {"Offset": 19, "Line": 20, "Column": 21}, + "TrailingSeparatorPos": {"Offset": 25, "Line": 26, "Column": 27} + } + ], + "ArgumentsStartPos": {"Offset": 28, "Line": 29, "Column": 30}, + "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, + "EndPos": {"Offset": 22, "Line": 23, "Column": 24} + } + `, + string(actual), + ) +} + +func TestInvocationExpression_Doc(t *testing.T) { + + t.Parallel() + + t.Run("without type arguments and arguments", func(t *testing.T) { + + t.Parallel() + + expr := &InvocationExpression{ + InvokedExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foobar", + }, + }, + } + + assert.Equal(t, + prettier.Concat{ + prettier.Text("foobar"), + prettier.Text("()"), + }, + expr.Doc(), + ) + }) + + t.Run("target expression with lower precedence", func(t *testing.T) { + + t.Parallel() + + expr := &InvocationExpression{ + InvokedExpression: &CastingExpression{ + Operation: OperationCast, + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "T", + }, + }, + }, + }, + } + + assert.Equal(t, + prettier.Concat{ + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Text("foo"), + }, + prettier.Line{}, + prettier.Text("as"), + prettier.Line{}, + prettier.Text("T"), + }, + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + prettier.Text("()"), + }, + expr.Doc(), + ) + }) + + t.Run("with type argument and argument", func(t *testing.T) { + + t.Parallel() + + expr := &InvocationExpression{ + InvokedExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foobar", + }, + }, + TypeArguments: []*TypeAnnotation{ + { + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AB", + }, + }, + }, + }, + Arguments: []*Argument{ + { + Label: "ok", + Expression: &BoolExpression{ + Value: false, + }, + }, + }, + } + + assert.Equal(t, + prettier.Concat{ + prettier.Text("foobar"), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("<"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Concat{ + prettier.Text("@"), + prettier.Text("AB"), + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(">"), + }, + }, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Concat{ + prettier.Text("ok: "), + prettier.Text("false"), + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + }, + expr.Doc(), + ) + }) +} + +func TestInvocationExpression_String(t *testing.T) { + + t.Parallel() + + t.Run("without type arguments and arguments", func(t *testing.T) { + + t.Parallel() + + expr := &InvocationExpression{ + InvokedExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foobar", + }, + }, + } + + assert.Equal(t, + "foobar()", + expr.String(), + ) + }) + + t.Run("target expression with lower precedence", func(t *testing.T) { + + t.Parallel() + + expr := &InvocationExpression{ + InvokedExpression: &CastingExpression{ + Operation: OperationCast, + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "T", + }, + }, + }, + }, + } + + assert.Equal(t, + "(foo as T)()", + expr.String(), + ) + }) - t.Parallel() + t.Run("with type argument and argument", func(t *testing.T) { - expr := &ConditionalExpression{ - Test: &BoolExpression{ - Value: false, - }, - Then: &IntegerExpression{ - PositiveLiteral: "42", - Value: big.NewInt(42), - Base: 10, - }, - Else: &IntegerExpression{ - PositiveLiteral: "99", - Value: big.NewInt(99), - Base: 10, - }, - } + t.Parallel() - assert.Equal(t, - prettier.Group{ - Doc: prettier.Concat{ - prettier.Text(`false`), - prettier.Indent{ - Doc: prettier.Concat{ - prettier.Concat{ - prettier.Line{}, - prettier.Text("? "), - }, - prettier.Indent{ - Doc: prettier.Text(`42`), - }, - prettier.Concat{ - prettier.Line{}, - prettier.Text(": "), - }, - prettier.Indent{ - Doc: prettier.Text(`99`), + expr := &InvocationExpression{ + InvokedExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foobar", + }, + }, + TypeArguments: []*TypeAnnotation{ + { + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AB", }, }, }, }, - }, - expr.Doc(), - ) + Arguments: []*Argument{ + { + Label: "ok", + Expression: &BoolExpression{ + Value: false, + }, + }, + }, + } + + assert.Equal(t, + "foobar<@AB>(ok: false)", + expr.String(), + ) + }) } -func TestInvocationExpression_MarshalJSON(t *testing.T) { +func TestCastingExpression_MarshalJSON(t *testing.T) { t.Parallel() - expr := &InvocationExpression{ - InvokedExpression: &IdentifierExpression{ + expr := &CastingExpression{ + Expression: &IdentifierExpression{ Identifier: Identifier{ Identifier: "foobar", Pos: Position{Offset: 1, Line: 2, Column: 3}, }, }, - TypeArguments: []*TypeAnnotation{ - { - IsResource: true, - Type: &NominalType{ - Identifier: Identifier{ - Identifier: "AB", - Pos: Position{Offset: 4, Line: 5, Column: 6}, - }, - }, - StartPos: Position{Offset: 7, Line: 8, Column: 9}, - }, - }, - Arguments: []*Argument{ - { - Label: "ok", - LabelStartPos: &Position{Offset: 10, Line: 11, Column: 12}, - LabelEndPos: &Position{Offset: 13, Line: 14, Column: 15}, - Expression: &BoolExpression{ - Value: false, - Range: Range{ - StartPos: Position{Offset: 16, Line: 17, Column: 18}, - EndPos: Position{Offset: 19, Line: 20, Column: 21}, - }, + Operation: OperationForceCast, + TypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AB", + Pos: Position{Offset: 4, Line: 5, Column: 6}, }, - TrailingSeparatorPos: Position{Offset: 25, Line: 26, Column: 27}, }, + StartPos: Position{Offset: 7, Line: 8, Column: 9}, }, - ArgumentsStartPos: Position{Offset: 28, Line: 29, Column: 30}, - EndPos: Position{Offset: 22, Line: 23, Column: 24}, } actual, err := json.Marshal(expr) @@ -1323,8 +3627,8 @@ func TestInvocationExpression_MarshalJSON(t *testing.T) { assert.JSONEq(t, ` { - "Type": "InvocationExpression", - "InvokedExpression": { + "Type": "CastingExpression", + "Expression": { "Type": "IdentifierExpression", "Identifier": { "Identifier": "foobar", @@ -1334,247 +3638,293 @@ func TestInvocationExpression_MarshalJSON(t *testing.T) { "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, "EndPos": {"Offset": 6, "Line": 2, "Column": 8} }, - "TypeArguments": [ - { - "IsResource": true, - "AnnotatedType": { - "Type": "NominalType", - "Identifier": { - "Identifier": "AB", - "StartPos": {"Offset": 4, "Line": 5, "Column": 6}, - "EndPos": {"Offset": 5, "Line": 5, "Column": 7} - }, + "Operation": "OperationForceCast", + "TypeAnnotation": { + "IsResource": true, + "AnnotatedType": { + "Type": "NominalType", + "Identifier": { + "Identifier": "AB", "StartPos": {"Offset": 4, "Line": 5, "Column": 6}, "EndPos": {"Offset": 5, "Line": 5, "Column": 7} }, - "StartPos": {"Offset": 7, "Line": 8, "Column": 9}, + "StartPos": {"Offset": 4, "Line": 5, "Column": 6}, "EndPos": {"Offset": 5, "Line": 5, "Column": 7} - } - ], - "Arguments": [ - { - "Label": "ok", - "LabelStartPos": {"Offset": 10, "Line": 11, "Column": 12}, - "LabelEndPos": {"Offset": 13, "Line": 14, "Column": 15}, - "Expression": { - "Type": "BoolExpression", - "Value": false, - "StartPos": {"Offset": 16, "Line": 17, "Column": 18}, - "EndPos": {"Offset": 19, "Line": 20, "Column": 21} - }, - "StartPos": {"Offset": 10, "Line": 11, "Column": 12}, - "EndPos": {"Offset": 19, "Line": 20, "Column": 21}, - "TrailingSeparatorPos": {"Offset": 25, "Line": 26, "Column": 27} - } - ], - "ArgumentsStartPos": {"Offset": 28, "Line": 29, "Column": 30}, + }, + "StartPos": {"Offset": 7, "Line": 8, "Column": 9}, + "EndPos": {"Offset": 5, "Line": 5, "Column": 7} + }, "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 22, "Line": 23, "Column": 24} + "EndPos": {"Offset": 5, "Line": 5, "Column": 7} } `, string(actual), ) } -func TestInvocationExpression_Doc(t *testing.T) { +func TestCastingExpression_Doc(t *testing.T) { t.Parallel() - t.Run("without type arguments and arguments", func(t *testing.T) { + t.Run("simple", func(t *testing.T) { t.Parallel() - expr := &InvocationExpression{ - InvokedExpression: &IdentifierExpression{ - Identifier: Identifier{ - Identifier: "foobar", + expr := &CastingExpression{ + Expression: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Operation: OperationFailableCast, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "Int", + }, }, }, } assert.Equal(t, - prettier.Concat{ - prettier.Text("foobar"), - prettier.Text("()"), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Text("42"), + }, + prettier.Line{}, + prettier.Text("as?"), + prettier.Line{}, + prettier.Text("Int"), + }, }, expr.Doc(), ) }) - t.Run("with type argument and argument", func(t *testing.T) { + t.Run("nested, same precedence", func(t *testing.T) { t.Parallel() - expr := &InvocationExpression{ - InvokedExpression: &IdentifierExpression{ - Identifier: Identifier{ - Identifier: "foobar", + expr := &CastingExpression{ + Expression: &CastingExpression{ + Expression: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, }, - }, - TypeArguments: []*TypeAnnotation{ - { - IsResource: true, + Operation: OperationFailableCast, + TypeAnnotation: &TypeAnnotation{ Type: &NominalType{ Identifier: Identifier{ - Identifier: "AB", + Identifier: "AnyStruct", }, }, }, }, - Arguments: []*Argument{ - { - Label: "ok", - Expression: &BoolExpression{ - Value: false, + Operation: OperationFailableCast, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "Int", }, }, }, } assert.Equal(t, - prettier.Concat{ - prettier.Text("foobar"), - prettier.Group{ - Doc: prettier.Concat{ - prettier.Text("<"), - prettier.Indent{ + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Group{ Doc: prettier.Concat{ - prettier.SoftLine{}, - prettier.Concat{ - prettier.Text("@"), - prettier.Text("AB"), + prettier.Group{ + Doc: prettier.Text("42"), }, + prettier.Line{}, + prettier.Text("as?"), + prettier.Line{}, + prettier.Text("AnyStruct"), }, }, - prettier.SoftLine{}, - prettier.Text(">"), }, + prettier.Line{}, + prettier.Text("as?"), + prettier.Line{}, + prettier.Text("Int"), }, - prettier.Group{ - Doc: prettier.Concat{ - prettier.Text("("), - prettier.Indent{ + }, + expr.Doc(), + ) + }) + + t.Run("nested, lower precedence", func(t *testing.T) { + + t.Parallel() + + expr := &CastingExpression{ + Expression: &BinaryExpression{ + Operation: OperationMinus, + Left: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + Right: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, + }, + }, + Operation: OperationFailableCast, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "Int", + }, + }, + }, + } + + assert.Equal(t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Group{ Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Text("foo"), + }, + prettier.Line{}, + prettier.Text("-"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Text("bar"), + }, + }, + }, + }}, prettier.SoftLine{}, - prettier.Concat{ - prettier.Text("ok: "), - prettier.Text("false"), - }, + prettier.Text(")"), }, }, - prettier.SoftLine{}, - prettier.Text(")"), + }, + prettier.Line{}, + prettier.Text("as?"), + prettier.Line{}, + prettier.Text("Int"), + }, + }, + expr.Doc(), + ) + }) +} + +func TestCastingExpression_String(t *testing.T) { + + t.Parallel() + + t.Run("simple", func(t *testing.T) { + + t.Parallel() + + expr := &CastingExpression{ + Expression: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Operation: OperationFailableCast, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "Int", }, }, }, - expr.Doc(), + } + + assert.Equal(t, + "42 as? Int", + expr.String(), ) }) -} -func TestCastingExpression_MarshalJSON(t *testing.T) { + t.Run("nested, same precedence", func(t *testing.T) { - t.Parallel() + t.Parallel() - expr := &CastingExpression{ - Expression: &IdentifierExpression{ - Identifier: Identifier{ - Identifier: "foobar", - Pos: Position{Offset: 1, Line: 2, Column: 3}, + expr := &CastingExpression{ + Expression: &CastingExpression{ + Expression: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Operation: OperationFailableCast, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AnyStruct", + }, + }, + }, }, - }, - Operation: OperationForceCast, - TypeAnnotation: &TypeAnnotation{ - IsResource: true, - Type: &NominalType{ - Identifier: Identifier{ - Identifier: "AB", - Pos: Position{Offset: 4, Line: 5, Column: 6}, + Operation: OperationFailableCast, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "Int", + }, }, }, - StartPos: Position{Offset: 7, Line: 8, Column: 9}, - }, - } - - actual, err := json.Marshal(expr) - require.NoError(t, err) + } - assert.JSONEq(t, - ` - { - "Type": "CastingExpression", - "Expression": { - "Type": "IdentifierExpression", - "Identifier": { - "Identifier": "foobar", - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 6, "Line": 2, "Column": 8} - }, - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 6, "Line": 2, "Column": 8} - }, - "Operation": "OperationForceCast", - "TypeAnnotation": { - "IsResource": true, - "AnnotatedType": { - "Type": "NominalType", - "Identifier": { - "Identifier": "AB", - "StartPos": {"Offset": 4, "Line": 5, "Column": 6}, - "EndPos": {"Offset": 5, "Line": 5, "Column": 7} - }, - "StartPos": {"Offset": 4, "Line": 5, "Column": 6}, - "EndPos": {"Offset": 5, "Line": 5, "Column": 7} - }, - "StartPos": {"Offset": 7, "Line": 8, "Column": 9}, - "EndPos": {"Offset": 5, "Line": 5, "Column": 7} - }, - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 5, "Line": 5, "Column": 7} - } - `, - string(actual), - ) -} + assert.Equal(t, + "42 as? AnyStruct as? Int", + expr.String(), + ) + }) -func TestCastingExpression_Doc(t *testing.T) { + t.Run("nested, lower precedence", func(t *testing.T) { - t.Parallel() + t.Parallel() - expr := &CastingExpression{ - Expression: &IntegerExpression{ - PositiveLiteral: "42", - Value: big.NewInt(42), - Base: 10, - }, - Operation: OperationFailableCast, - TypeAnnotation: &TypeAnnotation{ - IsResource: true, - Type: &NominalType{ - Identifier: Identifier{ - Identifier: "R", + expr := &CastingExpression{ + Expression: &BinaryExpression{ + Operation: OperationMinus, + Left: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, }, - }, - }, - } - - assert.Equal(t, - prettier.Group{ - Doc: prettier.Concat{ - prettier.Group{ - Doc: prettier.Text("42"), + Right: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, }, - prettier.Line{}, - prettier.Text("as?"), - prettier.Line{}, - prettier.Concat{ - prettier.Text("@"), - prettier.Text("R"), + }, + Operation: OperationFailableCast, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "Int", + }, }, }, - }, - expr.Doc(), - ) + } + + assert.Equal(t, + "(foo - bar) as? Int", + expr.String(), + ) + }) } func TestCreateExpression_MarshalJSON(t *testing.T) { @@ -1712,6 +4062,26 @@ func TestCreateExpression_Doc(t *testing.T) { ) } +func TestCreateExpression_String(t *testing.T) { + + t.Parallel() + + expr := &CreateExpression{ + InvocationExpression: &InvocationExpression{ + InvokedExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + }, + } + + assert.Equal(t, + "create foo()", + expr.String(), + ) +} + func TestReferenceExpression_MarshalJSON(t *testing.T) { expr := &ReferenceExpression{ @@ -1765,45 +4135,289 @@ func TestReferenceExpression_MarshalJSON(t *testing.T) { ) } -func TestReferenceExpression_Doc(t *testing.T) { +func TestReferenceExpression_Doc(t *testing.T) { + + t.Parallel() + + t.Run("simple", func(t *testing.T) { + + t.Parallel() + + expr := &ReferenceExpression{ + Expression: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Type: &ReferenceType{ + Authorized: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "Int", + }, + }, + }, + } + + assert.Equal(t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("&"), + prettier.Group{ + Doc: prettier.Text("42"), + }, + prettier.Line{}, + prettier.Text("as"), + prettier.Line{}, + prettier.Concat{ + prettier.Text("auth "), + prettier.Text("&"), + prettier.Text("Int"), + }, + }, + }, + expr.Doc(), + ) + }) + + t.Run("nested, same precedence", func(t *testing.T) { + + t.Parallel() + + expr := &ReferenceExpression{ + Expression: &ReferenceExpression{ + Expression: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Type: &ReferenceType{ + Authorized: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AnyStruct", + }, + }, + }, + }, + Type: &ReferenceType{ + Authorized: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "XYZ", + }, + }, + }, + } + + assert.Equal(t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("&"), + prettier.Group{ + Doc: prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("&"), + prettier.Group{ + Doc: prettier.Text("42"), + }, + prettier.Line{}, + prettier.Text("as"), + prettier.Line{}, + prettier.Concat{ + prettier.Text("auth "), + prettier.Text("&"), + prettier.Text("AnyStruct"), + }, + }, + }, + }, + prettier.Line{}, + prettier.Text("as"), + prettier.Line{}, + prettier.Concat{ + prettier.Text("auth "), + prettier.Text("&"), + prettier.Text("XYZ"), + }, + }, + }, + expr.Doc(), + ) + }) + + t.Run("nested, lower precedence", func(t *testing.T) { + + t.Parallel() + + expr := &ReferenceExpression{ + Expression: &BinaryExpression{ + Operation: OperationMinus, + Left: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + Right: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, + }, + }, + Type: &ReferenceType{ + Authorized: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "Int", + }, + }, + }, + } + + assert.Equal(t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("&"), + prettier.Group{ + Doc: prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Text("foo"), + }, + prettier.Line{}, + prettier.Text("-"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Text("bar"), + }, + }, + }, + }}, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + }, + prettier.Line{}, + prettier.Text("as"), + prettier.Line{}, + prettier.Concat{ + prettier.Text("auth "), + prettier.Text("&"), + prettier.Text("Int"), + }, + }, + }, + expr.Doc(), + ) + }) +} + +func TestReferenceExpression_String(t *testing.T) { + + t.Parallel() + + t.Run("simple", func(t *testing.T) { + + t.Parallel() + + expr := &ReferenceExpression{ + Expression: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Type: &ReferenceType{ + Authorized: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "Int", + }, + }, + }, + } + + assert.Equal(t, + "&42 as auth &Int", + expr.String(), + ) + }) + + t.Run("nested, same precedence", func(t *testing.T) { - t.Parallel() + t.Parallel() - expr := &ReferenceExpression{ - Expression: &IntegerExpression{ - PositiveLiteral: "42", - Value: big.NewInt(42), - Base: 10, - }, - Type: &ReferenceType{ - Authorized: true, - Type: &NominalType{ - Identifier: Identifier{ - Identifier: "Int", + expr := &ReferenceExpression{ + Expression: &ReferenceExpression{ + Expression: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + Type: &ReferenceType{ + Authorized: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AnyStruct", + }, + }, }, }, - }, - } + Type: &ReferenceType{ + Authorized: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "XYZ", + }, + }, + }, + } - assert.Equal(t, - prettier.Group{ - Doc: prettier.Concat{ - prettier.Text("&"), - prettier.Group{ - Doc: prettier.Text("42"), + assert.Equal(t, + "&&42 as auth &AnyStruct as auth &XYZ", + expr.String(), + ) + }) + + t.Run("nested, lower precedence", func(t *testing.T) { + + t.Parallel() + + expr := &ReferenceExpression{ + Expression: &BinaryExpression{ + Operation: OperationMinus, + Left: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, }, - prettier.Line{}, - prettier.Text("as"), - prettier.Line{}, - prettier.Concat{ - prettier.Text("auth "), - prettier.Text("&"), - prettier.Text("Int"), + Right: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, }, }, - }, - expr.Doc(), - ) + Type: &ReferenceType{ + Authorized: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "Int", + }, + }, + }, + } + + assert.Equal(t, + "&(foo - bar) as auth &Int", + expr.String(), + ) + }) } func TestFunctionExpression_MarshalJSON(t *testing.T) { @@ -1942,6 +4556,7 @@ func TestFunctionExpression_Doc(t *testing.T) { t.Parallel() expr := &FunctionExpression{ + ParameterList: &ParameterList{}, FunctionBlock: &FunctionBlock{ Block: &Block{ Statements: []Statement{}, @@ -1952,7 +4567,9 @@ func TestFunctionExpression_Doc(t *testing.T) { expected := prettier.Concat{ prettier.Text("fun "), prettier.Group{ - Doc: prettier.Text("()"), + Doc: prettier.Concat{ + prettier.Text("()"), + }, }, prettier.Text(" {}"), } @@ -2079,4 +4696,296 @@ func TestFunctionExpression_Doc(t *testing.T) { assert.Equal(t, expected, expr.Doc()) }) + + t.Run("pre-conditions and post-conditions", func(t *testing.T) { + + t.Parallel() + + expr := &FunctionExpression{ + ParameterList: &ParameterList{}, + ReturnTypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "Void", + }, + }, + }, + FunctionBlock: &FunctionBlock{ + PreConditions: &Conditions{ + { + Kind: ConditionKindPre, + Test: &BoolExpression{ + Value: true, + }, + Message: &StringExpression{ + Value: "pre", + }, + }, + }, + PostConditions: &Conditions{ + { + Kind: ConditionKindPre, + Test: &BoolExpression{ + Value: false, + }, + Message: &StringExpression{ + Value: "post", + }, + }, + }, + Block: &Block{ + Statements: []Statement{ + &ReturnStatement{ + Expression: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(1), + Base: 10, + }, + }, + }, + }, + }, + } + + expected := prettier.Concat{ + prettier.Text("fun "), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("()"), + prettier.Text(": "), + prettier.Text("Void"), + }, + }, + prettier.Text(" "), + prettier.Concat{ + prettier.Text("{"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("pre"), + prettier.Text(" "), + prettier.Text("{"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("true"), + prettier.Text(":"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Text("\"pre\""), + }, + }, + }, + }, + }, + }, + prettier.HardLine{}, + prettier.Text("}"), + }, + }, + prettier.HardLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("post"), + prettier.Text(" "), + prettier.Text("{"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("false"), + prettier.Text(":"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Text("\"post\""), + }, + }, + }, + }, + }, + }, + prettier.HardLine{}, + prettier.Text("}"), + }, + }, + prettier.Concat{ + prettier.HardLine{}, + prettier.Concat{ + prettier.Text("return "), + prettier.Text("1"), + }, + }, + }, + }, + prettier.HardLine{}, + prettier.Text("}"), + }, + } + + assert.Equal(t, expected, expr.Doc()) + }) +} + +func TestFunctionExpression_String(t *testing.T) { + + t.Parallel() + + t.Run("no parameters, no return type, no statements", func(t *testing.T) { + + t.Parallel() + + expr := &FunctionExpression{ + ParameterList: &ParameterList{}, + FunctionBlock: &FunctionBlock{ + Block: &Block{ + Statements: []Statement{}, + }, + }, + } + + assert.Equal(t, + "fun () {}", + expr.String(), + ) + }) + + t.Run("multiple parameters, return type, statements", func(t *testing.T) { + + t.Parallel() + + // TODO: pre-conditions and post-conditions + + expr := &FunctionExpression{ + ParameterList: &ParameterList{ + Parameters: []*Parameter{ + { + Label: "a", + Identifier: Identifier{ + Identifier: "b", + }, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "C", + }, + }, + }, + }, + { + Identifier: Identifier{ + Identifier: "d", + }, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "E", + }, + }, + }, + }, + }, + }, + ReturnTypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "R", + }, + }, + }, + FunctionBlock: &FunctionBlock{ + Block: &Block{ + Statements: []Statement{ + &ReturnStatement{ + Expression: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(1), + Base: 10, + }, + }, + }, + }, + }, + } + + assert.Equal(t, + "fun (a b: C, d: E): @R {\n"+ + " return 1\n"+ + "}", + expr.String(), + ) + }) + + t.Run("pre-conditions and post-conditions", func(t *testing.T) { + + t.Parallel() + + expr := &FunctionExpression{ + ParameterList: &ParameterList{}, + ReturnTypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "Void", + }, + }, + }, + FunctionBlock: &FunctionBlock{ + PreConditions: &Conditions{ + { + Kind: ConditionKindPre, + Test: &BoolExpression{ + Value: true, + }, + Message: &StringExpression{ + Value: "pre", + }, + }, + }, + PostConditions: &Conditions{ + { + Kind: ConditionKindPre, + Test: &BoolExpression{ + Value: false, + }, + Message: &StringExpression{ + Value: "post", + }, + }, + }, + Block: &Block{ + Statements: []Statement{ + &ReturnStatement{ + Expression: &IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(1), + Base: 10, + }, + }, + }, + }, + }, + } + + assert.Equal(t, + "fun (): Void {\n"+ + " pre {\n"+ + " true:\n"+ + " \"pre\"\n"+ + " }\n"+ + " post {\n"+ + " false:\n"+ + " \"post\"\n"+ + " }\n"+ + " return 1\n"+ + "}", + expr.String(), + ) + }) } diff --git a/runtime/ast/function_declaration.go b/runtime/ast/function_declaration.go index 46c2d6156a..54824a9e68 100644 --- a/runtime/ast/function_declaration.go +++ b/runtime/ast/function_declaration.go @@ -21,6 +21,8 @@ package ast import ( "encoding/json" + "github.com/turbolent/prettier" + "github.com/onflow/cadence/runtime/common" ) @@ -61,6 +63,10 @@ func NewFunctionDeclaration( } } +func (*FunctionDeclaration) isDeclaration() {} + +func (*FunctionDeclaration) isStatement() {} + func (*FunctionDeclaration) ElementType() ElementType { return ElementTypeFunctionDeclaration } @@ -91,9 +97,6 @@ func (d *FunctionDeclaration) Walk(walkChild func(Element)) { } } -func (*FunctionDeclaration) isDeclaration() {} -func (*FunctionDeclaration) isStatement() {} - func (d *FunctionDeclaration) DeclarationIdentifier() *Identifier { return &d.Identifier } @@ -124,6 +127,17 @@ func (d *FunctionDeclaration) DeclarationDocString() string { return d.DocString } +func (d *FunctionDeclaration) Doc() prettier.Doc { + return FunctionDocument( + d.Access, + true, + d.Identifier.Identifier, + d.ParameterList, + d.ReturnTypeAnnotation, + d.FunctionBlock, + ) +} + func (d *FunctionDeclaration) MarshalJSON() ([]byte, error) { type Alias FunctionDeclaration return json.Marshal(&struct { @@ -137,6 +151,10 @@ func (d *FunctionDeclaration) MarshalJSON() ([]byte, error) { }) } +func (d *FunctionDeclaration) String() string { + return Prettier(d) +} + // SpecialFunctionDeclaration type SpecialFunctionDeclaration struct { @@ -159,7 +177,11 @@ func NewSpecialFunctionDeclaration( Kind: kind, FunctionDeclaration: funcDecl, } + } +func (*SpecialFunctionDeclaration) isDeclaration() {} + +func (*SpecialFunctionDeclaration) isStatement() {} func (*SpecialFunctionDeclaration) ElementType() ElementType { return ElementTypeSpecialFunctionDeclaration @@ -181,9 +203,6 @@ func (d *SpecialFunctionDeclaration) Walk(walkChild func(Element)) { d.FunctionDeclaration.Walk(walkChild) } -func (*SpecialFunctionDeclaration) isDeclaration() {} -func (*SpecialFunctionDeclaration) isStatement() {} - func (d *SpecialFunctionDeclaration) DeclarationIdentifier() *Identifier { return d.FunctionDeclaration.DeclarationIdentifier() } @@ -204,6 +223,17 @@ func (d *SpecialFunctionDeclaration) DeclarationDocString() string { return d.FunctionDeclaration.DeclarationDocString() } +func (d *SpecialFunctionDeclaration) Doc() prettier.Doc { + return FunctionDocument( + d.FunctionDeclaration.Access, + false, + d.Kind.Keywords(), + d.FunctionDeclaration.ParameterList, + d.FunctionDeclaration.ReturnTypeAnnotation, + d.FunctionDeclaration.FunctionBlock, + ) +} + func (d *SpecialFunctionDeclaration) MarshalJSON() ([]byte, error) { type Alias SpecialFunctionDeclaration return json.Marshal(&struct { @@ -216,3 +246,7 @@ func (d *SpecialFunctionDeclaration) MarshalJSON() ([]byte, error) { Alias: (*Alias)(d), }) } + +func (d *SpecialFunctionDeclaration) String() string { + return Prettier(d) +} diff --git a/runtime/ast/function_declaration_test.go b/runtime/ast/function_declaration_test.go index a109adccaa..0daa6445b1 100644 --- a/runtime/ast/function_declaration_test.go +++ b/runtime/ast/function_declaration_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/turbolent/prettier" "github.com/onflow/cadence/runtime/common" ) @@ -32,7 +33,7 @@ func TestFunctionDeclaration_MarshalJSON(t *testing.T) { t.Parallel() - expr := &FunctionDeclaration{ + decl := &FunctionDeclaration{ Access: AccessPublic, Identifier: Identifier{ Identifier: "xyz", @@ -89,7 +90,7 @@ func TestFunctionDeclaration_MarshalJSON(t *testing.T) { StartPos: Position{Offset: 34, Line: 35, Column: 36}, } - actual, err := json.Marshal(expr) + actual, err := json.Marshal(decl) require.NoError(t, err) assert.JSONEq(t, @@ -168,11 +169,139 @@ func TestFunctionDeclaration_MarshalJSON(t *testing.T) { ) } +func TestFunctionDeclaration_Doc(t *testing.T) { + + t.Parallel() + + decl := &FunctionDeclaration{ + Access: AccessPublic, + Identifier: Identifier{ + Identifier: "xyz", + }, + ParameterList: &ParameterList{ + Parameters: []*Parameter{ + { + Label: "ok", + Identifier: Identifier{ + Identifier: "foobar", + }, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AB", + }, + }, + }, + }, + }, + }, + ReturnTypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "CD", + }, + }, + }, + FunctionBlock: &FunctionBlock{ + Block: &Block{ + Statements: []Statement{}, + }, + }, + } + + require.Equal(t, + prettier.Concat{ + prettier.Text("pub"), + prettier.Space, + prettier.Text("fun "), + prettier.Text("xyz"), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Concat{ + prettier.Text("ok"), + prettier.Text(" "), + prettier.Text("foobar"), + prettier.Text(": "), + prettier.Text("AB"), + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + prettier.Text(": "), + prettier.Concat{ + prettier.Text("@"), + prettier.Text("CD"), + }, + }, + }, + prettier.Text(" {}"), + }, + decl.Doc(), + ) +} + +func TestFunctionDeclaration_String(t *testing.T) { + + t.Parallel() + + decl := &FunctionDeclaration{ + Access: AccessPublic, + Identifier: Identifier{ + Identifier: "xyz", + }, + ParameterList: &ParameterList{ + Parameters: []*Parameter{ + { + Label: "ok", + Identifier: Identifier{ + Identifier: "foobar", + }, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AB", + }, + }, + }, + }, + }, + }, + ReturnTypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "CD", + }, + }, + }, + FunctionBlock: &FunctionBlock{ + Block: &Block{ + Statements: []Statement{}, + }, + }, + } + + require.Equal(t, + "pub fun xyz(ok foobar: AB): @CD {}", + decl.String(), + ) +} + func TestSpecialFunctionDeclaration_MarshalJSON(t *testing.T) { t.Parallel() - expr := &SpecialFunctionDeclaration{ + decl := &SpecialFunctionDeclaration{ Kind: common.DeclarationKindInitializer, FunctionDeclaration: &FunctionDeclaration{ Access: AccessNotSpecified, @@ -232,7 +361,7 @@ func TestSpecialFunctionDeclaration_MarshalJSON(t *testing.T) { }, } - actual, err := json.Marshal(expr) + actual, err := json.Marshal(decl) require.NoError(t, err) assert.JSONEq(t, @@ -316,3 +445,134 @@ func TestSpecialFunctionDeclaration_MarshalJSON(t *testing.T) { string(actual), ) } + +func TestSpecialFunctionDeclaration_Doc(t *testing.T) { + + t.Parallel() + + decl := &SpecialFunctionDeclaration{ + Kind: common.DeclarationKindInitializer, + FunctionDeclaration: &FunctionDeclaration{ + Access: AccessNotSpecified, + Identifier: Identifier{ + Identifier: "xyz", + }, + ParameterList: &ParameterList{ + Parameters: []*Parameter{ + { + Label: "ok", + Identifier: Identifier{ + Identifier: "foobar", + }, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AB", + }, + }, + }, + }, + }, + }, + ReturnTypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "CD", + }, + }, + }, + FunctionBlock: &FunctionBlock{ + Block: &Block{ + Statements: []Statement{}, + }, + }, + }, + } + + require.Equal(t, + prettier.Concat{ + prettier.Text("init"), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Concat{ + prettier.Text("ok"), + prettier.Text(" "), + prettier.Text("foobar"), + prettier.Text(": "), + prettier.Text("AB"), + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + prettier.Text(": "), + prettier.Concat{ + prettier.Text("@"), + prettier.Text("CD"), + }, + }, + }, + prettier.Text(" {}"), + }, + decl.Doc(), + ) +} + +func TestSpecialFunctionDeclaration_String(t *testing.T) { + + t.Parallel() + + decl := &SpecialFunctionDeclaration{ + Kind: common.DeclarationKindInitializer, + FunctionDeclaration: &FunctionDeclaration{ + Access: AccessNotSpecified, + Identifier: Identifier{ + Identifier: "xyz", + }, + ParameterList: &ParameterList{ + Parameters: []*Parameter{ + { + Label: "ok", + Identifier: Identifier{ + Identifier: "foobar", + }, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AB", + }, + }, + }, + }, + }, + }, + ReturnTypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "CD", + }, + }, + }, + FunctionBlock: &FunctionBlock{ + Block: &Block{ + Statements: []Statement{}, + }, + }, + }, + } + + require.Equal(t, + "init(ok foobar: AB): @CD {}", + decl.String(), + ) +} diff --git a/runtime/ast/import.go b/runtime/ast/import.go index 642b21cf29..4b9f9523c1 100644 --- a/runtime/ast/import.go +++ b/runtime/ast/import.go @@ -21,6 +21,8 @@ package ast import ( "encoding/json" + "github.com/turbolent/prettier" + "github.com/onflow/cadence/runtime/common" ) @@ -99,3 +101,80 @@ func (d *ImportDeclaration) MarshalJSON() ([]byte, error) { Alias: (*Alias)(d), }) } + +const importDeclarationImportKeywordDoc = prettier.Text("import") +const importDeclarationFromKeywordDoc = prettier.Text("from ") + +var importDeclarationSeparatorDoc prettier.Doc = prettier.Concat{ + prettier.Text(","), + prettier.Line{}, +} + +func (d *ImportDeclaration) Doc() prettier.Doc { + doc := prettier.Concat{ + importDeclarationImportKeywordDoc, + } + + if len(d.Identifiers) > 0 { + + identifiersDoc := prettier.Concat{ + prettier.Line{}, + } + + for i, identifier := range d.Identifiers { + if i > 0 { + identifiersDoc = append( + identifiersDoc, + importDeclarationSeparatorDoc, + ) + } + + identifiersDoc = append( + identifiersDoc, + prettier.Text(identifier.Identifier), + ) + } + + identifiersDoc = append( + identifiersDoc, + prettier.Line{}, + importDeclarationFromKeywordDoc, + ) + + doc = append( + doc, + prettier.Group{ + Doc: prettier.Indent{ + Doc: identifiersDoc, + }, + }, + ) + } else { + doc = append( + doc, + prettier.Space, + ) + } + + return append( + doc, + LocationDoc(d.Location), + ) +} + +func (d *ImportDeclaration) String() string { + return Prettier(d) +} + +func LocationDoc(location common.Location) prettier.Doc { + switch location := location.(type) { + case common.AddressLocation: + return prettier.Text(location.Address.ShortHexWithPrefix()) + case common.IdentifierLocation: + return prettier.Text(location) + case common.StringLocation: + return prettier.Text(QuoteString(string(location))) + default: + return nil + } +} diff --git a/runtime/ast/import_test.go b/runtime/ast/import_test.go index 561fef9b16..6b299654e1 100644 --- a/runtime/ast/import_test.go +++ b/runtime/ast/import_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/turbolent/prettier" "github.com/onflow/cadence/runtime/common" ) @@ -32,7 +33,7 @@ func TestImportDeclaration_MarshalJSON(t *testing.T) { t.Parallel() - ty := &ImportDeclaration{ + decl := &ImportDeclaration{ Identifiers: []Identifier{ { Identifier: "foo", @@ -47,7 +48,7 @@ func TestImportDeclaration_MarshalJSON(t *testing.T) { }, } - actual, err := json.Marshal(ty) + actual, err := json.Marshal(decl) require.NoError(t, err) assert.JSONEq(t, @@ -73,3 +74,168 @@ func TestImportDeclaration_MarshalJSON(t *testing.T) { string(actual), ) } + +func TestImportDeclaration_Doc(t *testing.T) { + + t.Parallel() + + t.Run("no identifiers", func(t *testing.T) { + + t.Parallel() + + decl := &ImportDeclaration{ + Location: common.StringLocation("test"), + } + + require.Equal( + t, + prettier.Concat{ + prettier.Text("import"), + prettier.Text(" "), + prettier.Text("\"test\""), + }, + decl.Doc(), + ) + }) + + t.Run("one identifier", func(t *testing.T) { + + t.Parallel() + + decl := &ImportDeclaration{ + Identifiers: []Identifier{ + { + Identifier: "foo", + }, + }, + Location: common.AddressLocation{ + Address: common.MustBytesToAddress([]byte{0x1}), + }, + } + + require.Equal( + t, + prettier.Concat{ + prettier.Text("import"), + prettier.Group{ + Doc: prettier.Indent{ + Doc: prettier.Concat{ + prettier.Line{}, + prettier.Text("foo"), + prettier.Line{}, + prettier.Text("from "), + }, + }, + }, + prettier.Text("0x1"), + }, + decl.Doc(), + ) + }) + + t.Run("two identifiers", func(t *testing.T) { + + t.Parallel() + + decl := &ImportDeclaration{ + Identifiers: []Identifier{ + { + Identifier: "foo", + }, + { + Identifier: "bar", + }, + }, + Location: common.IdentifierLocation("test"), + } + + require.Equal( + t, + prettier.Concat{ + prettier.Text("import"), + prettier.Group{ + Doc: prettier.Indent{ + Doc: prettier.Concat{ + prettier.Line{}, + prettier.Text("foo"), + prettier.Concat{ + prettier.Text(","), + prettier.Line{}, + }, + prettier.Text("bar"), + prettier.Line{}, + prettier.Text("from "), + }, + }, + }, + prettier.Text("test"), + }, + decl.Doc(), + ) + }) +} + +func TestImportDeclaration_String(t *testing.T) { + + t.Parallel() + + t.Run("no identifiers", func(t *testing.T) { + + t.Parallel() + + decl := &ImportDeclaration{ + Location: common.StringLocation("test"), + } + + require.Equal( + t, + `import "test"`, + decl.String(), + ) + }) + + t.Run("one identifier", func(t *testing.T) { + + t.Parallel() + + decl := &ImportDeclaration{ + Identifiers: []Identifier{ + { + Identifier: "foo", + }, + }, + Location: common.AddressLocation{ + Address: common.MustBytesToAddress([]byte{0x1}), + }, + } + + require.Equal( + t, + `import foo from 0x1`, + decl.String(), + ) + }) + + t.Run("two identifiers", func(t *testing.T) { + + t.Parallel() + + decl := &ImportDeclaration{ + Identifiers: []Identifier{ + { + Identifier: "foo", + }, + { + Identifier: "bar", + }, + }, + Location: common.IdentifierLocation("test"), + } + + require.Equal( + t, + `import foo, bar from test`, + decl.String(), + ) + }) +} diff --git a/runtime/ast/interface.go b/runtime/ast/interface.go index 249e65eec1..5315fa9cd5 100644 --- a/runtime/ast/interface.go +++ b/runtime/ast/interface.go @@ -21,6 +21,8 @@ package ast import ( "encoding/json" + "github.com/turbolent/prettier" + "github.com/onflow/cadence/runtime/common" ) @@ -109,3 +111,18 @@ func (d *InterfaceDeclaration) MarshalJSON() ([]byte, error) { Alias: (*Alias)(d), }) } + +func (d *InterfaceDeclaration) Doc() prettier.Doc { + return CompositeDocument( + d.Access, + d.CompositeKind, + true, + d.Identifier.Identifier, + nil, + d.Members, + ) +} + +func (d *InterfaceDeclaration) String() string { + return Prettier(d) +} diff --git a/runtime/ast/interface_test.go b/runtime/ast/interface_test.go index 57ddaa019c..a0f04c9465 100644 --- a/runtime/ast/interface_test.go +++ b/runtime/ast/interface_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/turbolent/prettier" "github.com/onflow/cadence/runtime/common" ) @@ -32,7 +33,7 @@ func TestInterfaceDeclaration_MarshalJSON(t *testing.T) { t.Parallel() - expr := &InterfaceDeclaration{ + decl := &InterfaceDeclaration{ Access: AccessPublic, CompositeKind: common.CompositeKindResource, Identifier: Identifier{ @@ -47,7 +48,7 @@ func TestInterfaceDeclaration_MarshalJSON(t *testing.T) { }, } - actual, err := json.Marshal(expr) + actual, err := json.Marshal(decl) require.NoError(t, err) assert.JSONEq(t, @@ -72,3 +73,159 @@ func TestInterfaceDeclaration_MarshalJSON(t *testing.T) { string(actual), ) } + +func TestInterfaceDeclaration_Doc(t *testing.T) { + + t.Parallel() + + t.Run("no members", func(t *testing.T) { + + t.Parallel() + + decl := &InterfaceDeclaration{ + Access: AccessPublic, + CompositeKind: common.CompositeKindResource, + Identifier: Identifier{ + Identifier: "AB", + }, + Members: NewMembers(nil, []Declaration{}), + } + + require.Equal( + t, + prettier.Concat{ + prettier.Text("pub"), + prettier.Text(" "), + prettier.Text("resource"), + prettier.Text(" "), + prettier.Text("interface "), + prettier.Text("AB"), + prettier.Text(" "), + prettier.Text("{}"), + }, + decl.Doc(), + ) + + }) + + t.Run("members", func(t *testing.T) { + + t.Parallel() + + decl := &InterfaceDeclaration{ + Access: AccessPublic, + CompositeKind: common.CompositeKindResource, + Identifier: Identifier{ + Identifier: "AB", + }, + Members: NewMembers(nil, []Declaration{ + &FieldDeclaration{ + Identifier: Identifier{ + Identifier: "x", + }, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "X", + }, + }, + }, + }, + }), + } + + require.Equal( + t, + prettier.Concat{ + prettier.Text("pub"), + prettier.Text(" "), + prettier.Text("resource"), + prettier.Text(" "), + prettier.Text("interface "), + prettier.Text("AB"), + prettier.Text(" "), + prettier.Concat{ + prettier.Text("{"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("x"), + prettier.Text(": "), + prettier.Text("X"), + }, + }, + }, + }, + prettier.HardLine{}, + prettier.Text("}"), + }, + }, + decl.Doc(), + ) + + }) +} + +func TestInterfaceDeclaration_String(t *testing.T) { + + t.Parallel() + + t.Run("no members", func(t *testing.T) { + + t.Parallel() + + decl := &InterfaceDeclaration{ + Access: AccessPublic, + CompositeKind: common.CompositeKindResource, + Identifier: Identifier{ + Identifier: "AB", + }, + Members: NewMembers(nil, []Declaration{}), + } + + require.Equal( + t, + "pub resource interface AB {}", + decl.String(), + ) + + }) + + t.Run("members", func(t *testing.T) { + + t.Parallel() + + decl := &InterfaceDeclaration{ + Access: AccessPublic, + CompositeKind: common.CompositeKindResource, + Identifier: Identifier{ + Identifier: "AB", + }, + Members: NewMembers(nil, []Declaration{ + &FieldDeclaration{ + Identifier: Identifier{ + Identifier: "x", + }, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "X", + }, + }, + }, + }, + }), + } + + require.Equal( + t, + "pub resource interface AB {\n"+ + " x: X\n"+ + "}", + decl.String(), + ) + + }) +} diff --git a/runtime/ast/members.go b/runtime/ast/members.go index acd25141d5..51bf532b23 100644 --- a/runtime/ast/members.go +++ b/runtime/ast/members.go @@ -21,6 +21,8 @@ package ast import ( "encoding/json" + "github.com/turbolent/prettier" + "github.com/onflow/cadence/runtime/common" ) @@ -125,3 +127,37 @@ func (m *Members) MarshalJSON() ([]byte, error) { Alias: (*Alias)(m), }) } + +var membersStartDoc prettier.Doc = prettier.Text("{") +var membersEndDoc prettier.Doc = prettier.Text("}") +var membersEmptyDoc prettier.Doc = prettier.Text("{}") + +func (m *Members) Doc() prettier.Doc { + if len(m.declarations) == 0 { + return membersEmptyDoc + } + + var docs []prettier.Doc + + for _, decl := range m.declarations { + docs = append( + docs, + prettier.Concat{ + prettier.HardLine{}, + decl.Doc(), + }, + ) + } + + return prettier.Concat{ + membersStartDoc, + prettier.Indent{ + Doc: prettier.Join( + prettier.HardLine{}, + docs..., + ), + }, + prettier.HardLine{}, + membersEndDoc, + } +} diff --git a/runtime/ast/parameterlist.go b/runtime/ast/parameterlist.go index d06c79223c..f910343f7e 100644 --- a/runtime/ast/parameterlist.go +++ b/runtime/ast/parameterlist.go @@ -21,6 +21,8 @@ package ast import ( "sync" + "github.com/turbolent/prettier" + "github.com/onflow/cadence/runtime/common" ) @@ -70,3 +72,56 @@ func (l *ParameterList) initialize() { } l._parametersByIdentifier = parametersByIdentifier } + +func (l *ParameterList) IsEmpty() bool { + return l == nil || len(l.Parameters) == 0 +} + +const parameterListEmptyDoc = prettier.Text("()") + +var parameterSeparatorDoc prettier.Doc = prettier.Concat{ + prettier.Text(","), + prettier.Line{}, +} + +func (l *ParameterList) Doc() prettier.Doc { + + if len(l.Parameters) == 0 { + return parameterListEmptyDoc + } + + parameterDocs := make([]prettier.Doc, 0, len(l.Parameters)) + + for _, parameter := range l.Parameters { + var parameterDoc prettier.Concat + + if parameter.Label != "" { + parameterDoc = append( + parameterDoc, + prettier.Text(parameter.Label), + prettier.Space, + ) + } + + parameterDoc = append( + parameterDoc, + prettier.Text(parameter.Identifier.Identifier), + typeSeparatorSpaceDoc, + parameter.TypeAnnotation.Doc(), + ) + + parameterDocs = append(parameterDocs, parameterDoc) + } + + return prettier.WrapParentheses( + prettier.Join( + parameterSeparatorDoc, + parameterDocs..., + ), + prettier.SoftLine{}, + ) +} + +func (l *ParameterList) String() string { + return Prettier(l) +} diff --git a/runtime/ast/parameterlist_test.go b/runtime/ast/parameterlist_test.go index a5eea32862..8adc833c5e 100644 --- a/runtime/ast/parameterlist_test.go +++ b/runtime/ast/parameterlist_test.go @@ -23,6 +23,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/turbolent/prettier" ) func TestParameterList_ParametersByIdentifier(t *testing.T) { @@ -83,3 +84,125 @@ func TestParameterList_ParametersByIdentifier(t *testing.T) { wg.Wait() }) } + +func TestParameterList_Doc(t *testing.T) { + + t.Parallel() + + params := &ParameterList{ + Parameters: []*Parameter{ + { + Identifier: Identifier{Identifier: "e"}, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{Identifier: "E"}, + }, + }, + }, + { + Label: "c", + Identifier: Identifier{Identifier: "d"}, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{Identifier: "D"}, + }, + }, + }, + { + Label: "a", + Identifier: Identifier{Identifier: "b"}, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{Identifier: "B"}, + }, + }, + }, + }, + } + + require.Equal(t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Concat{ + prettier.Concat{ + prettier.Text("e"), + prettier.Text(": "), + prettier.Text("E"), + }, + prettier.Concat{ + prettier.Text(","), + prettier.Line{}, + }, + prettier.Concat{ + prettier.Text("c"), + prettier.Text(" "), + prettier.Text("d"), + prettier.Text(": "), + prettier.Text("D"), + }, + prettier.Concat{ + prettier.Text(","), + prettier.Line{}, + }, + prettier.Concat{ + prettier.Text("a"), + prettier.Text(" "), + prettier.Text("b"), + prettier.Text(": "), + prettier.Text("B"), + }, + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + params.Doc(), + ) +} + +func TestParameterList_String(t *testing.T) { + + t.Parallel() + + params := &ParameterList{ + Parameters: []*Parameter{ + { + Identifier: Identifier{Identifier: "e"}, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{Identifier: "E"}, + }, + }, + }, + { + Label: "c", + Identifier: Identifier{Identifier: "d"}, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{Identifier: "D"}, + }, + }, + }, + { + Label: "a", + Identifier: Identifier{Identifier: "b"}, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{Identifier: "B"}, + }, + }, + }, + }, + } + + require.Equal(t, + "(e: E, c d: D, a b: B)", + params.String(), + ) +} diff --git a/runtime/ast/pragma.go b/runtime/ast/pragma.go index c5502650cf..29519263f4 100644 --- a/runtime/ast/pragma.go +++ b/runtime/ast/pragma.go @@ -21,6 +21,8 @@ package ast import ( "encoding/json" + "github.com/turbolent/prettier" + "github.com/onflow/cadence/runtime/common" ) @@ -89,3 +91,14 @@ func (d *PragmaDeclaration) MarshalJSON() ([]byte, error) { Alias: (*Alias)(d), }) } + +func (d *PragmaDeclaration) Doc() prettier.Doc { + return prettier.Concat{ + prettier.Text("#"), + d.Expression.Doc(), + } +} + +func (d *PragmaDeclaration) String() string { + return Prettier(d) +} diff --git a/runtime/ast/pragma_test.go b/runtime/ast/pragma_test.go index 9e19d5e18a..51ab8b01c9 100644 --- a/runtime/ast/pragma_test.go +++ b/runtime/ast/pragma_test.go @@ -24,13 +24,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/turbolent/prettier" ) func TestPragmaDeclaration_MarshalJSON(t *testing.T) { t.Parallel() - members := &PragmaDeclaration{ + decl := &PragmaDeclaration{ Expression: &BoolExpression{ Value: false, Range: Range{ @@ -44,7 +45,7 @@ func TestPragmaDeclaration_MarshalJSON(t *testing.T) { }, } - actual, err := json.Marshal(members) + actual, err := json.Marshal(decl) require.NoError(t, err) assert.JSONEq(t, @@ -64,3 +65,40 @@ func TestPragmaDeclaration_MarshalJSON(t *testing.T) { string(actual), ) } + +func TestPragmaDeclaration_Doc(t *testing.T) { + + t.Parallel() + + decl := &PragmaDeclaration{ + Expression: &BoolExpression{ + Value: false, + }, + } + + require.Equal( + t, + prettier.Concat{ + prettier.Text("#"), + prettier.Text("false"), + }, + decl.Doc(), + ) +} + +func TestPragmaDeclaration_String(t *testing.T) { + + t.Parallel() + + decl := &PragmaDeclaration{ + Expression: &BoolExpression{ + Value: false, + }, + } + + require.Equal( + t, + "#false", + decl.String(), + ) +} diff --git a/runtime/ast/precedence.go b/runtime/ast/precedence.go new file mode 100644 index 0000000000..a633db86bc --- /dev/null +++ b/runtime/ast/precedence.go @@ -0,0 +1,95 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ast + +//go:generate go run golang.org/x/tools/cmd/stringer -type=precedence + +// precedence is the order of importance of expressions / operators. +// NOTE: this enumeration does *NOT* influence parsing, +// and should be kept in sync with the binding powers in the parser +// +type precedence uint + +const ( + precedenceUnknown precedence = iota + // precedenceTernary is the precedence of + // - ConditionalExpression. right associative! + precedenceTernary + // precedenceLogicalOr is the precedence of + // - BinaryExpression, with OperationOr + precedenceLogicalOr + // precedenceLogicalAnd is the precedence of + // - BinaryExpression, with OperationAnd + precedenceLogicalAnd + // precedenceComparison is the precedence of + // - BinaryExpression, with OperationEqual, OperationNotEqual, + // OperationLessEqual, OperationLess, + // OperationGreater, or OperationGreaterEqual + precedenceComparison + // precedenceNilCoalescing is the precedence of + // - BinaryExpression, with OperationNilCoalesce. right associative! + precedenceNilCoalescing + // precedenceBitwiseOr is the precedence of + // - BinaryExpression, with OperationBitwiseOr + precedenceBitwiseOr + // precedenceBitwiseXor is the precedence of + // - BinaryExpression, with OperationBitwiseXor + precedenceBitwiseXor + // precedenceBitwiseAnd is the precedence of + // - BinaryExpression, with OperationBitwiseAnd + precedenceBitwiseAnd + // precedenceBitwiseShift is the precedence of + // - BinaryExpression, with OperationBitwiseLeftShift or OperationBitwiseRightShift + precedenceBitwiseShift + // precedenceAddition is the precedence of + // - BinaryExpression, with OperationPlus or OperationMinus + precedenceAddition + // precedenceMultiplication is the precedence of + // - BinaryExpression, with OperationMul, OperationMod, or OperationDiv + precedenceMultiplication + // precedenceCasting is the precedence of + // - CastingExpression + precedenceCasting + // precedenceUnaryPrefix is the precedence of + // - UnaryExpression + // - CreateExpression + // - DestroyExpression + // - ReferenceExpression + precedenceUnaryPrefix + // precedenceUnaryPostfix is the precedence of + // - ForceExpression + precedenceUnaryPostfix + // precedenceAccess is the precedence of + // - InvocationExpression + // - IndexExpression + // - MemberExpression + precedenceAccess + // precedenceLiteral is the precedence of + // - BoolExpression + // - NilExpression + // - StringExpression + // - IntegerExpression + // - FixedPointExpression + // - ArrayExpression + // - DictionaryExpression + // - IdentifierExpression + // - FunctionExpression + // - PathExpression + precedenceLiteral +) diff --git a/runtime/ast/precedence_string.go b/runtime/ast/precedence_string.go new file mode 100644 index 0000000000..9077fecd67 --- /dev/null +++ b/runtime/ast/precedence_string.go @@ -0,0 +1,39 @@ +// Code generated by "stringer -type=precedence"; DO NOT EDIT. + +package ast + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[precedenceUnknown-0] + _ = x[precedenceTernary-1] + _ = x[precedenceLogicalOr-2] + _ = x[precedenceLogicalAnd-3] + _ = x[precedenceComparison-4] + _ = x[precedenceNilCoalescing-5] + _ = x[precedenceBitwiseOr-6] + _ = x[precedenceBitwiseXor-7] + _ = x[precedenceBitwiseAnd-8] + _ = x[precedenceBitwiseShift-9] + _ = x[precedenceAddition-10] + _ = x[precedenceMultiplication-11] + _ = x[precedenceCasting-12] + _ = x[precedenceUnaryPrefix-13] + _ = x[precedenceUnaryPostfix-14] + _ = x[precedenceAccess-15] + _ = x[precedenceLiteral-16] +} + +const _precedence_name = "precedenceUnknownprecedenceTernaryprecedenceLogicalOrprecedenceLogicalAndprecedenceComparisonprecedenceNilCoalescingprecedenceBitwiseOrprecedenceBitwiseXorprecedenceBitwiseAndprecedenceBitwiseShiftprecedenceAdditionprecedenceMultiplicationprecedenceCastingprecedenceUnaryPrefixprecedenceUnaryPostfixprecedenceAccessprecedenceLiteral" + +var _precedence_index = [...]uint16{0, 17, 34, 53, 73, 93, 116, 135, 155, 175, 197, 215, 239, 256, 277, 299, 315, 332} + +func (i precedence) String() string { + if i >= precedence(len(_precedence_index)-1) { + return "precedence(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _precedence_name[_precedence_index[i]:_precedence_index[i+1]] +} diff --git a/runtime/ast/prettier.go b/runtime/ast/prettier.go new file mode 100644 index 0000000000..6c12078413 --- /dev/null +++ b/runtime/ast/prettier.go @@ -0,0 +1,32 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ast + +import ( + "strings" + + "github.com/turbolent/prettier" +) + +func Prettier(element interface{ Doc() prettier.Doc }) string { + var builder strings.Builder + doc := element.Doc().Flatten() + prettier.Prettier(&builder, doc, 80, " ") + return builder.String() +} diff --git a/runtime/ast/program.go b/runtime/ast/program.go index 2dba9051cb..4bda3fa79f 100644 --- a/runtime/ast/program.go +++ b/runtime/ast/program.go @@ -21,6 +21,8 @@ package ast import ( "encoding/json" + "github.com/turbolent/prettier" + "github.com/onflow/cadence/runtime/common" ) @@ -178,3 +180,20 @@ func (p *Program) MarshalJSON() ([]byte, error) { Alias: (*Alias)(p), }) } + +var programSeparatorDoc = prettier.Concat{ + prettier.HardLine{}, + prettier.HardLine{}, +} + +func (p *Program) Doc() prettier.Doc { + declarations := p.Declarations() + + docs := make([]prettier.Doc, 0, len(declarations)) + + for _, declaration := range declarations { + docs = append(docs, declaration.Doc()) + } + + return prettier.Join(programSeparatorDoc, docs...) +} diff --git a/runtime/ast/statement.go b/runtime/ast/statement.go index 0084e5cf91..d7bf729461 100644 --- a/runtime/ast/statement.go +++ b/runtime/ast/statement.go @@ -20,6 +20,7 @@ package ast import ( "encoding/json" + "fmt" "github.com/turbolent/prettier" @@ -28,7 +29,9 @@ import ( type Statement interface { Element + fmt.Stringer isStatement() + Doc() prettier.Doc } // ReturnStatement @@ -75,11 +78,14 @@ func (s *ReturnStatement) Doc() prettier.Doc { return prettier.Concat{ returnStatementKeywordSpaceDoc, - // TODO: potentially parenthesize s.Expression.Doc(), } } +func (s *ReturnStatement) String() string { + return Prettier(s) +} + func (s *ReturnStatement) MarshalJSON() ([]byte, error) { type Alias ReturnStatement return json.Marshal(&struct { @@ -127,6 +133,10 @@ func (*BreakStatement) Doc() prettier.Doc { return breakStatementKeywordDoc } +func (s *BreakStatement) String() string { + return Prettier(s) +} + func (s *BreakStatement) MarshalJSON() ([]byte, error) { type Alias BreakStatement return json.Marshal(&struct { @@ -174,6 +184,10 @@ func (*ContinueStatement) Doc() prettier.Doc { return continueStatementKeywordDoc } +func (s *ContinueStatement) String() string { + return Prettier(s) +} + func (s *ContinueStatement) MarshalJSON() ([]byte, error) { type Alias ContinueStatement return json.Marshal(&struct { @@ -190,6 +204,7 @@ func (s *ContinueStatement) MarshalJSON() ([]byte, error) { type IfStatementTest interface { Element isIfStatementTest() + Doc() prettier.Doc } // IfStatement @@ -253,12 +268,7 @@ const ifStatementIfKeywordSpaceDoc = prettier.Text("if ") const ifStatementSpaceElseKeywordSpaceDoc = prettier.Text(" else ") func (s *IfStatement) Doc() prettier.Doc { - var testDoc prettier.Doc - // TODO: replace once IfStatementTest implements Doc - testWithDoc, ok := s.Test.(interface{ Doc() prettier.Doc }) - if ok { - testDoc = testWithDoc.Doc() - } + testDoc := s.Test.Doc() doc := prettier.Concat{ ifStatementIfKeywordSpaceDoc, @@ -267,7 +277,7 @@ func (s *IfStatement) Doc() prettier.Doc { s.Then.Doc(), } - if s.Else != nil { + if s.Else != nil && len(s.Else.Statements) > 0 { var elseDoc prettier.Doc if len(s.Else.Statements) == 1 { if elseIfStatement, ok := s.Else.Statements[0].(*IfStatement); ok { @@ -292,6 +302,10 @@ func (s *IfStatement) Doc() prettier.Doc { } } +func (s *IfStatement) String() string { + return Prettier(s) +} + func (s *IfStatement) MarshalJSON() ([]byte, error) { type Alias IfStatement return json.Marshal(&struct { @@ -368,6 +382,10 @@ func (s *WhileStatement) Doc() prettier.Doc { } } +func (s *WhileStatement) String() string { + return Prettier(s) +} + func (s *WhileStatement) MarshalJSON() ([]byte, error) { type Alias WhileStatement return json.Marshal(&struct { @@ -466,6 +484,10 @@ func (s *ForStatement) Doc() prettier.Doc { } } +func (s *ForStatement) String() string { + return Prettier(s) +} + func (s *ForStatement) MarshalJSON() ([]byte, error) { type Alias ForStatement return json.Marshal(&struct { @@ -528,11 +550,14 @@ const emitStatementKeywordSpaceDoc = prettier.Text("emit ") func (s *EmitStatement) Doc() prettier.Doc { return prettier.Concat{ emitStatementKeywordSpaceDoc, - // TODO: potentially parenthesize s.InvocationExpression.Doc(), } } +func (s *EmitStatement) String() string { + return Prettier(s) +} + func (s *EmitStatement) MarshalJSON() ([]byte, error) { type Alias EmitStatement return json.Marshal(&struct { @@ -611,6 +636,10 @@ func (s *AssignmentStatement) Doc() prettier.Doc { } } +func (s *AssignmentStatement) String() string { + return Prettier(s) +} + func (s *AssignmentStatement) MarshalJSON() ([]byte, error) { type Alias AssignmentStatement return json.Marshal(&struct { @@ -677,6 +706,10 @@ func (s *SwapStatement) Doc() prettier.Doc { } } +func (s *SwapStatement) String() string { + return Prettier(s) +} + func (s *SwapStatement) MarshalJSON() ([]byte, error) { type Alias SwapStatement return json.Marshal(&struct { @@ -745,6 +778,10 @@ func (s *ExpressionStatement) MarshalJSON() ([]byte, error) { }) } +func (s *ExpressionStatement) String() string { + return Prettier(s) +} + // SwitchStatement type SwitchStatement struct { @@ -827,6 +864,10 @@ func (s *SwitchStatement) Doc() prettier.Doc { } } +func (s *SwitchStatement) String() string { + return Prettier(s) +} + func (s *SwitchStatement) MarshalJSON() ([]byte, error) { type Alias SwitchStatement return json.Marshal(&struct { diff --git a/runtime/ast/statement_test.go b/runtime/ast/statement_test.go index 2203d26ae8..fef5f4a9de 100644 --- a/runtime/ast/statement_test.go +++ b/runtime/ast/statement_test.go @@ -78,6 +78,22 @@ func TestExpressionStatement_Doc(t *testing.T) { ) } +func TestExpressionStatement_String(t *testing.T) { + + t.Parallel() + + stmt := &ExpressionStatement{ + Expression: &BoolExpression{ + Value: false, + }, + } + + assert.Equal(t, + "false", + stmt.String(), + ) +} + func TestReturnStatement_MarshalJSON(t *testing.T) { t.Parallel() @@ -153,6 +169,39 @@ func TestReturnStatement_Doc(t *testing.T) { }) } +func TestReturnStatement_String(t *testing.T) { + + t.Parallel() + + t.Run("value", func(t *testing.T) { + + t.Parallel() + + stmt := &ReturnStatement{ + Expression: &BoolExpression{ + Value: false, + }, + } + + require.Equal(t, + "return false", + stmt.String(), + ) + }) + + t.Run("no value", func(t *testing.T) { + + t.Parallel() + + stmt := &ReturnStatement{} + + require.Equal(t, + "return", + stmt.String(), + ) + }) +} + func TestBreakStatement_MarshalJSON(t *testing.T) { t.Parallel() @@ -189,6 +238,16 @@ func TestBreakStatement_Doc(t *testing.T) { ) } +func TestBreakStatement_String(t *testing.T) { + + t.Parallel() + + assert.Equal(t, + "break", + (&BreakStatement{}).String(), + ) +} + func TestContinueStatement_MarshalJSON(t *testing.T) { t.Parallel() @@ -225,6 +284,16 @@ func TestContinueStatement_Doc(t *testing.T) { ) } +func TestContinueStatement_String(t *testing.T) { + + t.Parallel() + + assert.Equal(t, + "continue", + (&ContinueStatement{}).String(), + ) +} + func TestIfStatement_MarshalJSON(t *testing.T) { t.Parallel() @@ -314,10 +383,6 @@ func TestIfStatement_Doc(t *testing.T) { prettier.Text("false"), prettier.Text(" "), prettier.Text("{}"), - prettier.Text(" else "), - prettier.Group{ - Doc: prettier.Text("{}"), - }, }, }, stmt.Doc(), @@ -374,6 +439,64 @@ func TestIfStatement_Doc(t *testing.T) { }) } +func TestIfStatement_String(t *testing.T) { + + t.Parallel() + + t.Run("empty if-else", func(t *testing.T) { + + t.Parallel() + + stmt := &IfStatement{ + Test: &BoolExpression{ + Value: false, + }, + Then: &Block{ + Statements: []Statement{}, + }, + Else: &Block{ + Statements: []Statement{}, + }, + } + + assert.Equal(t, + "if false {}", + stmt.String(), + ) + }) + + t.Run("if-else if", func(t *testing.T) { + + t.Parallel() + + stmt := &IfStatement{ + Test: &BoolExpression{ + Value: false, + }, + Then: &Block{ + Statements: []Statement{}, + }, + Else: &Block{ + Statements: []Statement{ + &IfStatement{ + Test: &BoolExpression{ + Value: true, + }, + Then: &Block{ + Statements: []Statement{}, + }, + }, + }, + }, + } + + assert.Equal(t, + "if false {} else if true {}", + stmt.String(), + ) + }) +} + func TestWhileStatement_MarshalJSON(t *testing.T) { t.Parallel() @@ -449,6 +572,25 @@ func TestWhileStatement_Doc(t *testing.T) { ) } +func TestWhileStatement_String(t *testing.T) { + + t.Parallel() + + stmt := &WhileStatement{ + Test: &BoolExpression{ + Value: false, + }, + Block: &Block{ + Statements: []Statement{}, + }, + } + + assert.Equal(t, + "while false {}", + stmt.String(), + ) +} + func TestForStatement_MarshalJSON(t *testing.T) { t.Parallel() @@ -623,27 +765,16 @@ func TestForStatement_Doc(t *testing.T) { stmt := &ForStatement{ Index: &Identifier{ Identifier: "i", - Pos: Position{Offset: 1, Line: 2, Column: 3}, }, Identifier: Identifier{ Identifier: "foobar", - Pos: Position{Offset: 4, Line: 5, Column: 6}, }, Value: &BoolExpression{ Value: false, - Range: Range{ - StartPos: Position{Offset: 7, Line: 8, Column: 9}, - EndPos: Position{Offset: 10, Line: 11, Column: 12}, - }, }, Block: &Block{ Statements: []Statement{}, - Range: Range{ - StartPos: Position{Offset: 13, Line: 14, Column: 15}, - EndPos: Position{Offset: 16, Line: 17, Column: 18}, - }, }, - StartPos: Position{Offset: 19, Line: 20, Column: 21}, } assert.Equal(t, @@ -664,6 +795,58 @@ func TestForStatement_Doc(t *testing.T) { }) } +func TestForStatement_String(t *testing.T) { + + t.Parallel() + + t.Run("without index", func(t *testing.T) { + + t.Parallel() + + stmt := &ForStatement{ + Identifier: Identifier{ + Identifier: "foobar", + }, + Value: &BoolExpression{ + Value: false, + }, + Block: &Block{ + Statements: []Statement{}, + }, + } + + assert.Equal(t, + "for foobar in false {}", + stmt.String(), + ) + }) + + t.Run("with index", func(t *testing.T) { + + t.Parallel() + + stmt := &ForStatement{ + Index: &Identifier{ + Identifier: "i", + }, + Identifier: Identifier{ + Identifier: "foobar", + }, + Value: &BoolExpression{ + Value: false, + }, + Block: &Block{ + Statements: []Statement{}, + }, + } + + assert.Equal(t, + "for i, foobar in false {}", + stmt.String(), + ) + }) +} + func TestAssignmentStatement_MarshalJSON(t *testing.T) { t.Parallel() @@ -761,6 +944,30 @@ func TestAssignmentStatement_Doc(t *testing.T) { ) } +func TestAssignmentStatement_String(t *testing.T) { + + t.Parallel() + + stmt := &AssignmentStatement{ + Target: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foobar", + }, + }, + Transfer: &Transfer{ + Operation: TransferOperationCopy, + }, + Value: &BoolExpression{ + Value: false, + }, + } + + require.Equal(t, + "foobar = false", + stmt.String(), + ) +} + func TestSwapStatement_MarshalJSON(t *testing.T) { t.Parallel() @@ -839,6 +1046,27 @@ func TestSwapStatement_Doc(t *testing.T) { ) } +func TestSwapStatement_String(t *testing.T) { + + t.Parallel() + + stmt := &SwapStatement{ + Left: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foobar", + }, + }, + Right: &BoolExpression{ + Value: false, + }, + } + + assert.Equal(t, + "foobar <-> false", + stmt.String(), + ) +} + func TestEmitStatement_MarshalJSON(t *testing.T) { t.Parallel() @@ -974,6 +1202,26 @@ func TestEmitStatement_Doc(t *testing.T) { ) } +func TestEmitStatement_String(t *testing.T) { + + t.Parallel() + + stmt := &EmitStatement{ + InvocationExpression: &InvocationExpression{ + InvokedExpression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foobar", + }, + }, + }, + } + + require.Equal(t, + "emit foobar()", + stmt.String(), + ) +} + func TestSwitchStatement_MarshalJSON(t *testing.T) { t.Parallel() @@ -1110,7 +1358,7 @@ func TestSwitchStatement_MarshalJSON(t *testing.T) { ) } -func TestSwitchStatement_Doc(t *testing.T) { +func TestSwitchStatement_String(t *testing.T) { t.Parallel() @@ -1196,3 +1444,53 @@ func TestSwitchStatement_Doc(t *testing.T) { stmt.Doc(), ) } + +func TestSwitchStatement_Doc(t *testing.T) { + + t.Parallel() + + stmt := &SwitchStatement{ + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "foo", + }, + }, + Cases: []*SwitchCase{ + { + Expression: &BoolExpression{ + Value: false, + }, + Statements: []Statement{ + &ExpressionStatement{ + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "bar", + }, + }, + }, + }, + }, + { + Statements: []Statement{ + &ExpressionStatement{ + Expression: &IdentifierExpression{ + Identifier: Identifier{ + Identifier: "baz", + }, + }, + }, + }, + }, + }, + } + + assert.Equal(t, + "switch foo {\n"+ + " case false:\n"+ + " bar\n"+ + " default:\n"+ + " baz\n"+ + "}", + stmt.String(), + ) +} diff --git a/runtime/ast/transaction_declaration.go b/runtime/ast/transaction_declaration.go index 0fb631caac..ec87edb467 100644 --- a/runtime/ast/transaction_declaration.go +++ b/runtime/ast/transaction_declaration.go @@ -21,6 +21,8 @@ package ast import ( "encoding/json" + "github.com/turbolent/prettier" + "github.com/onflow/cadence/runtime/common" ) @@ -119,3 +121,69 @@ func (d *TransactionDeclaration) MarshalJSON() ([]byte, error) { Alias: (*Alias)(d), }) } + +var transactionKeywordDoc = prettier.Text("transaction") + +func (d *TransactionDeclaration) Doc() prettier.Doc { + + var contents []prettier.Doc + + addContent := func(doc prettier.Doc) { + contents = append( + contents, + prettier.Concat{ + prettier.HardLine{}, + doc, + }, + ) + } + + for _, field := range d.Fields { + addContent(field.Doc()) + } + + if d.Prepare != nil { + addContent(d.Prepare.Doc()) + } + + if conditionsDoc := d.PreConditions.Doc(preConditionsKeywordDoc); conditionsDoc != nil { + addContent(conditionsDoc) + } + + if d.Execute != nil { + addContent(d.Execute.Doc()) + } + + if conditionsDoc := d.PostConditions.Doc(postConditionsKeywordDoc); conditionsDoc != nil { + addContent(conditionsDoc) + } + + doc := prettier.Concat{ + transactionKeywordDoc, + } + + if !d.ParameterList.IsEmpty() { + doc = append( + doc, + d.ParameterList.Doc(), + ) + } + + return append( + doc, + prettier.Space, + blockStartDoc, + prettier.Indent{ + Doc: prettier.Join( + prettier.HardLine{}, + contents..., + ), + }, + prettier.HardLine{}, + blockEndDoc, + ) +} + +func (d *TransactionDeclaration) String() string { + return Prettier(d) +} diff --git a/runtime/ast/transaction_declaration_test.go b/runtime/ast/transaction_declaration_test.go index 30ba51cbf1..8b6d8f9fa9 100644 --- a/runtime/ast/transaction_declaration_test.go +++ b/runtime/ast/transaction_declaration_test.go @@ -24,13 +24,16 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/turbolent/prettier" + + "github.com/onflow/cadence/runtime/common" ) func TestTransactionDeclaration_MarshalJSON(t *testing.T) { t.Parallel() - expr := &TransactionDeclaration{ + decl := &TransactionDeclaration{ ParameterList: &ParameterList{ Parameters: []*Parameter{}, Range: Range{ @@ -50,7 +53,7 @@ func TestTransactionDeclaration_MarshalJSON(t *testing.T) { }, } - actual, err := json.Marshal(expr) + actual, err := json.Marshal(decl) require.NoError(t, err) assert.JSONEq(t, @@ -75,3 +78,389 @@ func TestTransactionDeclaration_MarshalJSON(t *testing.T) { string(actual), ) } + +func TestTransactionDeclaration_Doc(t *testing.T) { + + t.Parallel() + + decl := &TransactionDeclaration{ + ParameterList: &ParameterList{ + Parameters: []*Parameter{ + { + Identifier: Identifier{ + Identifier: "x", + }, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "X", + }, + }, + }, + }, + }, + }, + Fields: []*FieldDeclaration{ + { + Access: AccessPublic, + VariableKind: VariableKindConstant, + Identifier: Identifier{ + Identifier: "f", + }, + TypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "F", + }, + }, + }, + }, + }, + Prepare: &SpecialFunctionDeclaration{ + Kind: common.DeclarationKindPrepare, + FunctionDeclaration: &FunctionDeclaration{ + ParameterList: &ParameterList{ + Parameters: []*Parameter{ + { + Identifier: Identifier{ + Identifier: "signer", + }, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AuthAccount", + }, + }, + }, + }, + }, + }, + }, + }, + PreConditions: &Conditions{ + { + Kind: ConditionKindPre, + Test: &BoolExpression{ + Value: true, + }, + Message: &StringExpression{ + Value: "pre", + }, + }, + }, + Execute: &SpecialFunctionDeclaration{ + Kind: common.DeclarationKindExecute, + FunctionDeclaration: &FunctionDeclaration{ + FunctionBlock: &FunctionBlock{ + Block: &Block{ + Statements: []Statement{ + &ExpressionStatement{ + &StringExpression{ + Value: "xyz", + }, + }, + }, + }, + }, + }, + }, + PostConditions: &Conditions{ + { + Kind: ConditionKindPre, + Test: &BoolExpression{ + Value: false, + }, + Message: &StringExpression{ + Value: "post", + }, + }, + }, + } + + require.Equal( + t, + prettier.Concat{ + prettier.Text("transaction"), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Concat{ + prettier.Text("x"), + prettier.Text(": "), + prettier.Text("X"), + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + prettier.Text(" "), + prettier.Text("{"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.Concat{ + prettier.HardLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("pub"), + prettier.Text(" "), + prettier.Text("let"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("f"), + prettier.Text(": "), + prettier.Concat{ + prettier.Text("@"), + prettier.Text("F"), + }, + }, + }, + }, + }, + }, + prettier.HardLine{}, + prettier.Concat{ + prettier.HardLine{}, + prettier.Concat{ + prettier.Text("prepare"), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("("), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.SoftLine{}, + prettier.Concat{ + prettier.Text("signer"), + prettier.Text(": "), + prettier.Text("AuthAccount"), + }, + }, + }, + prettier.SoftLine{}, + prettier.Text(")"), + }, + }, + }, + }, + prettier.Text(" {}"), + }, + }, + prettier.HardLine{}, + prettier.Concat{ + prettier.HardLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("pre"), + prettier.Text(" "), + prettier.Text("{"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("true"), + prettier.Text(":"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Text("\"pre\""), + }, + }, + }, + }, + }, + }, + prettier.HardLine{}, + prettier.Text("}"), + }, + }, + }, + prettier.HardLine{}, + prettier.Concat{ + prettier.HardLine{}, + prettier.Concat{ + prettier.Text("execute"), + prettier.Text(" "), + prettier.Concat{ + prettier.Text("{"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Text("\"xyz\""), + }, + }, + prettier.HardLine{}, + prettier.Text("}"), + }, + }, + }, + prettier.HardLine{}, + prettier.Concat{ + prettier.HardLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("post"), + prettier.Text(" "), + prettier.Text("{"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("false"), + prettier.Text(":"), + prettier.Indent{ + Doc: prettier.Concat{ + prettier.HardLine{}, + prettier.Text("\"post\""), + }, + }, + }, + }, + }, + }, + prettier.HardLine{}, + prettier.Text("}"), + }, + }, + }, + }, + }, + prettier.HardLine{}, + prettier.Text("}"), + }, + decl.Doc(), + ) +} + +func TestTransactionDeclaration_String(t *testing.T) { + + t.Parallel() + + decl := &TransactionDeclaration{ + ParameterList: &ParameterList{ + Parameters: []*Parameter{ + { + Identifier: Identifier{ + Identifier: "x", + }, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "X", + }, + }, + }, + }, + }, + }, + Fields: []*FieldDeclaration{ + { + Access: AccessPublic, + VariableKind: VariableKindConstant, + Identifier: Identifier{ + Identifier: "f", + }, + TypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "F", + }, + }, + }, + }, + }, + Prepare: &SpecialFunctionDeclaration{ + Kind: common.DeclarationKindPrepare, + FunctionDeclaration: &FunctionDeclaration{ + ParameterList: &ParameterList{ + Parameters: []*Parameter{ + { + Identifier: Identifier{ + Identifier: "signer", + }, + TypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AuthAccount", + }, + }, + }, + }, + }, + }, + }, + }, + PreConditions: &Conditions{ + { + Kind: ConditionKindPre, + Test: &BoolExpression{ + Value: true, + }, + Message: &StringExpression{ + Value: "pre", + }, + }, + }, + Execute: &SpecialFunctionDeclaration{ + Kind: common.DeclarationKindExecute, + FunctionDeclaration: &FunctionDeclaration{ + FunctionBlock: &FunctionBlock{ + Block: &Block{ + Statements: []Statement{ + &ExpressionStatement{ + &StringExpression{ + Value: "xyz", + }, + }, + }, + }, + }, + }, + }, + PostConditions: &Conditions{ + { + Kind: ConditionKindPre, + Test: &BoolExpression{ + Value: false, + }, + Message: &StringExpression{ + Value: "post", + }, + }, + }, + } + + require.Equal( + t, + "transaction(x: X) {\n"+ + " pub let f: @F\n"+ + " \n"+ + " prepare(signer: AuthAccount) {}\n"+ + " \n"+ + " pre {\n"+ + " true:\n"+ + " \"pre\"\n"+ + " }\n"+ + " \n"+ + " execute {\n"+ + " \"xyz\"\n"+ + " }\n"+ + " \n"+ + " post {\n"+ + " false:\n"+ + " \"post\"\n"+ + " }\n"+ + "}", + decl.String(), + ) +} diff --git a/runtime/ast/transfer_test.go b/runtime/ast/transfer_test.go index 68dcaf63fc..caf99a7081 100644 --- a/runtime/ast/transfer_test.go +++ b/runtime/ast/transfer_test.go @@ -30,12 +30,12 @@ func TestTransfer_MarshalJSON(t *testing.T) { t.Parallel() - expr := Transfer{ + transfer := Transfer{ Operation: TransferOperationMove, Pos: Position{Offset: 1, Line: 2, Column: 3}, } - actual, err := json.Marshal(expr) + actual, err := json.Marshal(transfer) require.NoError(t, err) assert.JSONEq(t, diff --git a/runtime/ast/type.go b/runtime/ast/type.go index ccbb1ca7f1..9ead2d7a71 100644 --- a/runtime/ast/type.go +++ b/runtime/ast/type.go @@ -21,13 +21,14 @@ package ast import ( "encoding/json" "fmt" - "strings" "github.com/turbolent/prettier" "github.com/onflow/cadence/runtime/common" ) +const typeSeparatorSpaceDoc = prettier.Text(": ") + // TypeAnnotation type TypeAnnotation struct { @@ -52,10 +53,7 @@ func NewTypeAnnotation( } func (t *TypeAnnotation) String() string { - if t.IsResource { - return fmt.Sprintf("@%s", t.Type) - } - return fmt.Sprint(t.Type) + return Prettier(t) } func (t *TypeAnnotation) StartPosition() Position { @@ -129,13 +127,7 @@ func NewNominalType( func (*NominalType) isType() {} func (t *NominalType) String() string { - var sb strings.Builder - sb.WriteString(t.Identifier.String()) - for _, identifier := range t.NestedIdentifiers { - sb.WriteRune('.') - sb.WriteString(identifier.String()) - } - return sb.String() + return Prettier(t) } func (t *NominalType) StartPosition() Position { @@ -151,8 +143,22 @@ func (t *NominalType) EndPosition(memoryGauge common.MemoryGauge) Position { return lastIdentifier.EndPosition(memoryGauge) } +var nominalTypeSeparatorDoc = prettier.Text(".") + func (t *NominalType) Doc() prettier.Doc { - return prettier.Text(t.String()) + var doc prettier.Doc = prettier.Text(t.Identifier.String()) + if len(t.NestedIdentifiers) > 0 { + concat := prettier.Concat{doc} + for _, identifier := range t.NestedIdentifiers { + concat = append( + concat, + nominalTypeSeparatorDoc, + prettier.Text(identifier.String()), + ) + } + doc = concat + } + return doc } func (t *NominalType) MarshalJSON() ([]byte, error) { @@ -200,7 +206,7 @@ func NewOptionalType( func (*OptionalType) isType() {} func (t *OptionalType) String() string { - return fmt.Sprintf("%s?", t.Type) + return Prettier(t) } func (t *OptionalType) StartPosition() Position { @@ -261,7 +267,7 @@ func NewVariableSizedType( func (*VariableSizedType) isType() {} func (t *VariableSizedType) String() string { - return fmt.Sprintf("[%s]", t.Type) + return Prettier(t) } const arrayTypeStartDoc = prettier.Text("[") @@ -323,7 +329,7 @@ func NewConstantSizedType( func (*ConstantSizedType) isType() {} func (t *ConstantSizedType) String() string { - return fmt.Sprintf("[%s; %s]", t.Type, t.Size) + return Prettier(t) } const constantSizedTypeSeparatorSpaceDoc = prettier.Text("; ") @@ -386,12 +392,11 @@ func NewDictionaryType( func (*DictionaryType) isType() {} func (t *DictionaryType) String() string { - return fmt.Sprintf("{%s: %s}", t.KeyType, t.ValueType) + return Prettier(t) } const dictionaryTypeStartDoc = prettier.Text("{") const dictionaryTypeEndDoc = prettier.Text("}") -const dictionaryTypeSeparatorSpaceDoc = prettier.Text(": ") func (t *DictionaryType) Doc() prettier.Doc { return prettier.Concat{ @@ -400,7 +405,7 @@ func (t *DictionaryType) Doc() prettier.Doc { Doc: prettier.Concat{ prettier.SoftLine{}, t.KeyType.Doc(), - dictionaryTypeSeparatorSpaceDoc, + typeSeparatorSpaceDoc, t.ValueType.Doc(), }, }, @@ -451,20 +456,11 @@ func NewFunctionType( func (*FunctionType) isType() {} func (t *FunctionType) String() string { - var parameters strings.Builder - for i, parameterTypeAnnotation := range t.ParameterTypeAnnotations { - if i > 0 { - parameters.WriteString(", ") - } - parameters.WriteString(parameterTypeAnnotation.String()) - } - - return fmt.Sprintf("((%s): %s)", parameters.String(), t.ReturnTypeAnnotation.String()) + return Prettier(t) } const functionTypeStartDoc = prettier.Text("(") const functionTypeEndDoc = prettier.Text(")") -const functionTypeTypeSeparatorSpaceDoc = prettier.Text(": ") const functionTypeParameterSeparatorDoc = prettier.Text(",") func (t *FunctionType) Doc() prettier.Doc { @@ -498,7 +494,7 @@ func (t *FunctionType) Doc() prettier.Doc { functionTypeEndDoc, }, }, - functionTypeTypeSeparatorSpaceDoc, + typeSeparatorSpaceDoc, t.ReturnTypeAnnotation.Doc(), functionTypeEndDoc, } @@ -546,13 +542,7 @@ func NewReferenceType( func (*ReferenceType) isType() {} func (t *ReferenceType) String() string { - var builder strings.Builder - if t.Authorized { - builder.WriteString("auth ") - } - builder.WriteRune('&') - builder.WriteString(t.Type.String()) - return builder.String() + return Prettier(t) } func (t *ReferenceType) StartPosition() Position { @@ -623,19 +613,7 @@ func NewRestrictedType( func (*RestrictedType) isType() {} func (t *RestrictedType) String() string { - var builder strings.Builder - if t.Type != nil { - builder.WriteString(t.Type.String()) - } - builder.WriteRune('{') - for i, restriction := range t.Restrictions { - if i > 0 { - builder.WriteString(", ") - } - builder.WriteString(restriction.String()) - } - builder.WriteRune('}') - return builder.String() + return Prettier(t) } const restrictedTypeStartDoc = prettier.Text("{") @@ -726,17 +704,7 @@ func NewInstantiationType( func (*InstantiationType) isType() {} func (t *InstantiationType) String() string { - var sb strings.Builder - sb.WriteString(t.Type.String()) - sb.WriteRune('<') - for i, typeArgument := range t.TypeArguments { - if i > 0 { - sb.WriteString(", ") - } - sb.WriteString(typeArgument.String()) - } - sb.WriteRune('>') - return sb.String() + return Prettier(t) } func (t *InstantiationType) StartPosition() Position { diff --git a/runtime/ast/type_test.go b/runtime/ast/type_test.go index 592edb6d18..1f0b96c46a 100644 --- a/runtime/ast/type_test.go +++ b/runtime/ast/type_test.go @@ -50,6 +50,25 @@ func TestTypeAnnotation_Doc(t *testing.T) { ) } +func TestTypeAnnotation_String(t *testing.T) { + + t.Parallel() + + ty := &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "R", + }, + }, + } + + assert.Equal(t, + "@R", + ty.String(), + ) +} + func TestTypeAnnotation_MarshalJSON(t *testing.T) { t.Parallel() @@ -94,16 +113,100 @@ func TestNominalType_Doc(t *testing.T) { t.Parallel() - ty := &NominalType{ - Identifier: Identifier{ - Identifier: "R", - }, - } + t.Run("simple", func(t *testing.T) { + + t.Parallel() + + ty := &NominalType{ + Identifier: Identifier{ + Identifier: "R", + }, + } + + assert.Equal(t, + prettier.Text("R"), + ty.Doc(), + ) + + }) + + t.Run("nested", func(t *testing.T) { + + t.Parallel() + + ty := &NominalType{ + Identifier: Identifier{ + Identifier: "R", + }, + NestedIdentifiers: []Identifier{ + { + Identifier: "S", + }, + { + Identifier: "T", + }, + }, + } + + assert.Equal(t, + prettier.Concat{ + prettier.Text("R"), + prettier.Text("."), + prettier.Text("S"), + prettier.Text("."), + prettier.Text("T"), + }, + ty.Doc(), + ) + + }) + +} + +func TestNominalType_String(t *testing.T) { + + t.Parallel() + + t.Run("simple", func(t *testing.T) { + + ty := &NominalType{ + Identifier: Identifier{ + Identifier: "R", + }, + } + + assert.Equal(t, + "R", + ty.String(), + ) + + }) + + t.Run("nested", func(t *testing.T) { + + t.Parallel() + + ty := &NominalType{ + Identifier: Identifier{ + Identifier: "R", + }, + NestedIdentifiers: []Identifier{ + { + Identifier: "S", + }, + { + Identifier: "T", + }, + }, + } + + assert.Equal(t, + "R.S.T", + ty.String(), + ) + + }) - assert.Equal(t, - prettier.Text("R"), - ty.Doc(), - ) } func TestNominalType_MarshalJSON(t *testing.T) { @@ -171,6 +274,24 @@ func TestOptionalType_Doc(t *testing.T) { ) } +func TestOptionalType_String(t *testing.T) { + + t.Parallel() + + ty := &OptionalType{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "R", + }, + }, + } + + assert.Equal(t, + "R?", + ty.String(), + ) +} + func TestOptionalType_MarshalJSON(t *testing.T) { t.Parallel() @@ -238,6 +359,24 @@ func TestVariableSizedType_Doc(t *testing.T) { ) } +func TestVariableSizedType_String(t *testing.T) { + + t.Parallel() + + ty := &VariableSizedType{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "T", + }, + }, + } + + assert.Equal(t, + "[T]", + ty.String(), + ) +} + func TestVariableSizedType_MarshalJSON(t *testing.T) { t.Parallel() @@ -315,6 +454,29 @@ func TestConstantSizedType_Doc(t *testing.T) { ) } +func TestConstantSizedType_String(t *testing.T) { + + t.Parallel() + + ty := &ConstantSizedType{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "T", + }, + }, + Size: &IntegerExpression{ + PositiveLiteral: "42", + Value: big.NewInt(42), + Base: 10, + }, + } + + assert.Equal(t, + "[T; 42]", + ty.String(), + ) +} + func TestConstantSizedType_MarshalJSON(t *testing.T) { t.Parallel() @@ -409,6 +571,29 @@ func TestDictionaryType_Doc(t *testing.T) { ) } +func TestDictionaryType_String(t *testing.T) { + + t.Parallel() + + ty := &DictionaryType{ + KeyType: &NominalType{ + Identifier: Identifier{ + Identifier: "AB", + }, + }, + ValueType: &NominalType{ + Identifier: Identifier{ + Identifier: "CD", + }, + }, + } + + assert.Equal(t, + "{AB: CD}", + ty.String(), + ) +} + func TestDictionaryType_MarshalJSON(t *testing.T) { t.Parallel() @@ -532,6 +717,44 @@ func TestFunctionType_Doc(t *testing.T) { ) } +func TestFunctionType_String(t *testing.T) { + + t.Parallel() + + ty := &FunctionType{ + ParameterTypeAnnotations: []*TypeAnnotation{ + { + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AB", + }, + }, + }, + { + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "CD", + }, + }, + }, + }, + ReturnTypeAnnotation: &TypeAnnotation{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "EF", + }, + }, + }, + } + + assert.Equal(t, + "((@AB, @CD): EF)", + ty.String(), + ) +} + func TestFunctionType_MarshalJSON(t *testing.T) { t.Parallel() @@ -659,6 +882,48 @@ func TestReferenceType_Doc(t *testing.T) { ty.Doc(), ) }) +} + +func TestReferenceType_String(t *testing.T) { + + t.Parallel() + + t.Run("auth", func(t *testing.T) { + + t.Parallel() + + ty := &ReferenceType{ + Authorized: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "T", + }, + }, + } + + assert.Equal(t, + "auth &T", + ty.String(), + ) + }) + + t.Run("un-auth", func(t *testing.T) { + + t.Parallel() + + ty := &ReferenceType{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "T", + }, + }, + } + + assert.Equal(t, + "&T", + ty.String(), + ) + }) } @@ -751,6 +1016,36 @@ func TestRestrictedType_Doc(t *testing.T) { ) } +func TestRestrictedType_String(t *testing.T) { + + t.Parallel() + + ty := &RestrictedType{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AB", + }, + }, + Restrictions: []*NominalType{ + { + Identifier: Identifier{ + Identifier: "CD", + }, + }, + { + Identifier: Identifier{ + Identifier: "EF", + }, + }, + }, + } + + assert.Equal(t, + "AB{CD, EF}", + ty.String(), + ) +} + func TestRestrictedType_MarshalJSON(t *testing.T) { t.Parallel() @@ -886,6 +1181,42 @@ func TestInstantiationType_Doc(t *testing.T) { ) } +func TestInstantiationType_String(t *testing.T) { + + t.Parallel() + + ty := &InstantiationType{ + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AB", + }, + }, + TypeArguments: []*TypeAnnotation{ + { + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "CD", + }, + }, + }, + { + IsResource: false, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "EF", + }, + }, + }, + }, + } + + assert.Equal(t, + "AB<@CD, EF>", + ty.String(), + ) +} + func TestInstantiationType_MarshalJSON(t *testing.T) { t.Parallel() diff --git a/runtime/ast/variable_declaration.go b/runtime/ast/variable_declaration.go index 93542184c8..e1a9c6fecd 100644 --- a/runtime/ast/variable_declaration.go +++ b/runtime/ast/variable_declaration.go @@ -78,6 +78,10 @@ func NewEmptyVariableDeclaration(gauge common.MemoryGauge) *VariableDeclaration return &VariableDeclaration{} } +func (*VariableDeclaration) isDeclaration() {} + +func (*VariableDeclaration) isStatement() {} + func (*VariableDeclaration) ElementType() ElementType { return ElementTypeVariableDeclaration } @@ -95,10 +99,6 @@ func (d *VariableDeclaration) EndPosition(memoryGauge common.MemoryGauge) Positi func (*VariableDeclaration) isIfStatementTest() {} -func (*VariableDeclaration) isDeclaration() {} - -func (*VariableDeclaration) isStatement() {} - func (d *VariableDeclaration) Accept(visitor Visitor) Repr { return visitor.VisitVariableDeclaration(d) } @@ -143,30 +143,88 @@ func (d *VariableDeclaration) Doc() prettier.Doc { keywordDoc = letKeywordDoc } - // TODO: second transfer and value (if any) + identifierTypeDoc := prettier.Concat{ + prettier.Text(d.Identifier.Identifier), + } + + if d.TypeAnnotation != nil { + identifierTypeDoc = append( + identifierTypeDoc, + typeSeparatorSpaceDoc, + d.TypeAnnotation.Doc(), + ) + } - // TODO: potentially parenthesize valueDoc := d.Value.Doc() - return prettier.Group{ - Doc: prettier.Concat{ - keywordDoc, + var valuesDoc prettier.Doc + + if d.SecondValue == nil { + // Put transfer before the break + + valuesDoc = prettier.Concat{ + prettier.Group{ + Doc: identifierTypeDoc, + }, prettier.Space, + d.Transfer.Doc(), + prettier.Group{ + Doc: prettier.Indent{ + Doc: prettier.Concat{ + prettier.Line{}, + valueDoc, + }, + }, + }, + } + } else { + secondValueDoc := d.SecondValue.Doc() + + // Put transfers at start of value lines, + // and break both values at once + + valuesDoc = prettier.Concat{ prettier.Group{ - Doc: prettier.Concat{ - prettier.Text(d.Identifier.Identifier), - prettier.Space, - // TODO: type annotation, if any - d.Transfer.Doc(), - prettier.Space, - prettier.Group{ - Doc: prettier.Indent{ - Doc: valueDoc, - }, + Doc: identifierTypeDoc, + }, + prettier.Group{ + Doc: prettier.Indent{ + Doc: prettier.Concat{ + prettier.Line{}, + d.Transfer.Doc(), + prettier.Space, + valueDoc, + prettier.Line{}, + d.SecondTransfer.Doc(), + prettier.Space, + secondValueDoc, }, }, }, + } + } + + var doc prettier.Concat + + if d.Access != AccessNotSpecified { + doc = append( + doc, + prettier.Text(d.Access.Keyword()), + prettier.Space, + ) + } + + doc = append( + doc, + keywordDoc, + prettier.Space, + prettier.Group{ + Doc: valuesDoc, }, + ) + + return prettier.Group{ + Doc: doc, } } @@ -182,3 +240,7 @@ func (d *VariableDeclaration) MarshalJSON() ([]byte, error) { Alias: (*Alias)(d), }) } + +func (d *VariableDeclaration) String() string { + return Prettier(d) +} diff --git a/runtime/ast/variable_declaration_test.go b/runtime/ast/variable_declaration_test.go index d5da2d0960..0599e93322 100644 --- a/runtime/ast/variable_declaration_test.go +++ b/runtime/ast/variable_declaration_test.go @@ -24,13 +24,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/turbolent/prettier" ) func TestVariableDeclaration_MarshalJSON(t *testing.T) { t.Parallel() - ty := &VariableDeclaration{ + decl := &VariableDeclaration{ Access: AccessPublic, IsConstant: true, Identifier: Identifier{ @@ -73,7 +74,7 @@ func TestVariableDeclaration_MarshalJSON(t *testing.T) { DocString: "test", } - actual, err := json.Marshal(ty) + actual, err := json.Marshal(decl) require.NoError(t, err) assert.JSONEq(t, @@ -134,3 +135,219 @@ func TestVariableDeclaration_MarshalJSON(t *testing.T) { string(actual), ) } + +func TestVariableDeclaration_Doc(t *testing.T) { + + t.Parallel() + + t.Run("with one value", func(t *testing.T) { + + t.Parallel() + + decl := &VariableDeclaration{ + Access: AccessPublic, + IsConstant: true, + Identifier: Identifier{ + Identifier: "foo", + }, + TypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AB", + }, + }, + }, + Value: &BoolExpression{ + Value: true, + }, + Transfer: &Transfer{ + Operation: TransferOperationMove, + }, + } + + require.Equal(t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("pub"), + prettier.Text(" "), + prettier.Text("let"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("foo"), + prettier.Text(": "), + prettier.Concat{ + prettier.Text("@"), + prettier.Text("AB"), + }, + }, + }, + prettier.Text(" "), + prettier.Text("<-"), + prettier.Group{ + Doc: prettier.Indent{ + Doc: prettier.Concat{ + prettier.Line{}, + prettier.Text("true"), + }, + }, + }, + }, + }, + }, + }, + decl.Doc(), + ) + }) + + t.Run("with second value", func(t *testing.T) { + + t.Parallel() + + decl := &VariableDeclaration{ + Access: AccessPublic, + IsConstant: true, + Identifier: Identifier{ + Identifier: "foo", + }, + TypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AB", + }, + }, + }, + Value: &BoolExpression{ + Value: true, + }, + Transfer: &Transfer{ + Operation: TransferOperationMove, + }, + SecondTransfer: &Transfer{ + Operation: TransferOperationMove, + }, + SecondValue: &BoolExpression{ + Value: false, + }, + } + + require.Equal(t, + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("pub"), + prettier.Text(" "), + prettier.Text("let"), + prettier.Text(" "), + prettier.Group{ + Doc: prettier.Concat{ + prettier.Group{ + Doc: prettier.Concat{ + prettier.Text("foo"), + prettier.Text(": "), + prettier.Concat{ + prettier.Text("@"), + prettier.Text("AB"), + }, + }, + }, + prettier.Group{ + Doc: prettier.Indent{ + Doc: prettier.Concat{ + prettier.Line{}, + prettier.Text("<-"), + prettier.Text(" "), + prettier.Text("true"), + prettier.Line{}, + prettier.Text("<-"), + prettier.Text(" "), + prettier.Text("false"), + }, + }, + }, + }, + }, + }, + }, + decl.Doc(), + ) + }) +} + +func TestVariableDeclaration_String(t *testing.T) { + + t.Parallel() + + t.Run("with one value", func(t *testing.T) { + + t.Parallel() + + decl := &VariableDeclaration{ + Access: AccessPublic, + IsConstant: true, + Identifier: Identifier{ + Identifier: "foo", + }, + TypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AB", + }, + }, + }, + Value: &BoolExpression{ + Value: true, + }, + Transfer: &Transfer{ + Operation: TransferOperationMove, + }, + } + + require.Equal(t, + "pub let foo: @AB <- true", + decl.String(), + ) + }) + + t.Run("with second value", func(t *testing.T) { + + t.Parallel() + + decl := &VariableDeclaration{ + Access: AccessPublic, + IsConstant: true, + Identifier: Identifier{ + Identifier: "foo", + }, + TypeAnnotation: &TypeAnnotation{ + IsResource: true, + Type: &NominalType{ + Identifier: Identifier{ + Identifier: "AB", + }, + }, + }, + Value: &BoolExpression{ + Value: true, + }, + Transfer: &Transfer{ + Operation: TransferOperationMove, + }, + SecondTransfer: &Transfer{ + Operation: TransferOperationMove, + }, + SecondValue: &BoolExpression{ + Value: false, + }, + } + + require.Equal(t, + "pub let foo: @AB <- true <- false", + decl.String(), + ) + }) +} diff --git a/runtime/parser2/declaration_test.go b/runtime/parser2/declaration_test.go index 7b66f12a22..849e8f69c8 100644 --- a/runtime/parser2/declaration_test.go +++ b/runtime/parser2/declaration_test.go @@ -2291,7 +2291,6 @@ func TestParseTransactionDeclaration(t *testing.T) { Identifier: "execute", Pos: ast.Position{Offset: 14, Line: 1, Column: 14}, }, - ParameterList: &ast.ParameterList{}, FunctionBlock: &ast.FunctionBlock{ Block: &ast.Block{ Range: ast.Range{ @@ -2468,7 +2467,6 @@ func TestParseTransactionDeclaration(t *testing.T) { Identifier: "execute", Pos: ast.Position{Offset: 104, Line: 10, Column: 6}, }, - ParameterList: &ast.ParameterList{}, ReturnTypeAnnotation: nil, FunctionBlock: &ast.FunctionBlock{ Block: &ast.Block{ @@ -2709,7 +2707,6 @@ func TestParseTransactionDeclaration(t *testing.T) { Identifier: "execute", Pos: ast.Position{Offset: 136, Line: 14, Column: 6}, }, - ParameterList: &ast.ParameterList{}, ReturnTypeAnnotation: nil, FunctionBlock: &ast.FunctionBlock{ Block: &ast.Block{ @@ -2950,7 +2947,6 @@ func TestParseTransactionDeclaration(t *testing.T) { Identifier: "execute", Pos: ast.Position{Offset: 179, Line: 18, Column: 6}, }, - ParameterList: &ast.ParameterList{}, ReturnTypeAnnotation: nil, FunctionBlock: &ast.FunctionBlock{ Block: &ast.Block{ diff --git a/runtime/parser2/expression.go b/runtime/parser2/expression.go index d50a7cdcde..75cfb67ee9 100644 --- a/runtime/parser2/expression.go +++ b/runtime/parser2/expression.go @@ -242,14 +242,12 @@ func init() { defineExpr(binaryExpr{ tokenType: lexer.TokenVerticalBarVerticalBar, leftBindingPower: exprLeftBindingPowerLogicalOr, - rightAssociative: true, operation: ast.OperationOr, }) defineExpr(binaryExpr{ tokenType: lexer.TokenAmpersandAmpersand, leftBindingPower: exprLeftBindingPowerLogicalAnd, - rightAssociative: true, operation: ast.OperationAnd, }) diff --git a/runtime/parser2/transaction.go b/runtime/parser2/transaction.go index 78206067ea..f4358817cc 100644 --- a/runtime/parser2/transaction.go +++ b/runtime/parser2/transaction.go @@ -224,11 +224,7 @@ func parseTransactionExecute(p *parser) *ast.SpecialFunctionDeclaration { p.memoryGauge, ast.AccessNotSpecified, identifier, - ast.NewParameterList( - p.memoryGauge, - nil, - ast.EmptyRange, - ), + nil, nil, ast.NewFunctionBlock( p.memoryGauge, diff --git a/runtime/tests/checker/conversion_test.go b/runtime/tests/checker/conversion_test.go index bd0d3e0982..03e24fe2a2 100644 --- a/runtime/tests/checker/conversion_test.go +++ b/runtime/tests/checker/conversion_test.go @@ -49,7 +49,7 @@ func TestCheckNumberConversionReplacementHint(t *testing.T) { require.IsType(t, &sema.ReplacementHint{}, hints[0]) require.Equal(t, - "consider replacing with: `(1.0 as Fix64)`", + "consider replacing with: `1.0 as Fix64`", hints[0].(*sema.ReplacementHint).Hint(), ) }) @@ -155,7 +155,7 @@ func TestCheckNumberConversionReplacementHint(t *testing.T) { require.IsType(t, &sema.ReplacementHint{}, hints[0]) require.Equal(t, - "consider replacing with: `(1 as UInt8)`", + "consider replacing with: `1 as UInt8`", hints[0].(*sema.ReplacementHint).Hint(), ) }) @@ -175,7 +175,7 @@ func TestCheckNumberConversionReplacementHint(t *testing.T) { require.IsType(t, &sema.ReplacementHint{}, hints[0]) require.Equal(t, - "consider replacing with: `(1 as Int8)`", + "consider replacing with: `1 as Int8`", hints[0].(*sema.ReplacementHint).Hint(), ) }) @@ -195,7 +195,7 @@ func TestCheckNumberConversionReplacementHint(t *testing.T) { require.IsType(t, &sema.ReplacementHint{}, hints[0]) require.Equal(t, - "consider replacing with: `(-1 as Int8)`", + "consider replacing with: `-1 as Int8`", hints[0].(*sema.ReplacementHint).Hint(), ) }) diff --git a/tools/pretty/main.go b/tools/pretty/main.go index bcee9b9d11..cdc440444f 100644 --- a/tools/pretty/main.go +++ b/tools/pretty/main.go @@ -38,22 +38,8 @@ func pretty(code string, maxLineWidth int) string { return err.Error() } - declarations := program.Declarations() - - docs := make([]prettier.Doc, 0, len(declarations)) - - for _, declaration := range declarations { - // TODO: replace once Declaration implements Doc - hasDoc, ok := declaration.(interface{ Doc() prettier.Doc }) - if !ok { - continue - } - - docs = append(docs, hasDoc.Doc()) - } - var b strings.Builder - prettier.Prettier(&b, prettier.Concat(docs), maxLineWidth, " ") + prettier.Prettier(&b, program.Doc(), maxLineWidth, " ") return b.String() } @@ -72,13 +58,11 @@ const page = ` padding: 0; font-family: monospace; height: 100vh; - overflow: hidden; } #panels { display: grid; - height: 100%; - grid-template-rows: 1fr; + grid-template-rows: 100vh; grid-template-columns: 50% 50%; grid-template-areas: "editor ast"; } @@ -96,6 +80,8 @@ const page = ` #output { white-space: pre; + height: 100%; + overflow: scroll; } #bar { @@ -107,6 +93,10 @@ const page = ` background-color: black; } + #stepper { + position: sticky; + top: 0 + }