From 5477fecfadc2dac16f4e86cd8dfed81fb87a6796 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 9 Jun 2017 08:19:47 -0700 Subject: [PATCH] zclsyntax: require newlines after block items Previously we tolerated EOF as an alias for newline, but a file without a newline at the end is a edge case primarily limited to contrived examples in unit tests, and requiring it simplifies tasks such as code generation in zclwrite, since we can always assume that every block item comes with a built-in line terminator. --- zcl/zclsyntax/parser.go | 52 +++++++++++++++++++++---------- zcl/zclsyntax/parser_test.go | 50 +++++++++++++++--------------- zcldec/public_test.go | 20 ++++++------ zcldec/variables_test.go | 9 +++--- zclwrite/parser_test.go | 60 ++++++++++++++++++++++++++++++++++-- zclwrite/round_trip_test.go | 3 +- 6 files changed, 137 insertions(+), 57 deletions(-) diff --git a/zcl/zclsyntax/parser.go b/zcl/zclsyntax/parser.go index a3133b7..8ee5d0b 100644 --- a/zcl/zclsyntax/parser.go +++ b/zcl/zclsyntax/parser.go @@ -168,15 +168,25 @@ func (p *parser) finishParsingBodyAttribute(ident Token) (Node, zcl.Diagnostics) p.recoverAfterBodyItem() } else { end := p.Peek() - if end.Type != TokenNewline && end.Type != TokenEOF { + if end.Type != TokenNewline { if !p.recovery { - diags = append(diags, &zcl.Diagnostic{ - Severity: zcl.DiagError, - Summary: "Missing newline after attribute definition", - Detail: "An attribute definition must end with a newline.", - Subject: &end.Range, - Context: zcl.RangeBetween(ident.Range, end.Range).Ptr(), - }) + if end.Type == TokenEOF { + diags = append(diags, &zcl.Diagnostic{ + Severity: zcl.DiagError, + Summary: "Missing newline after attribute definition", + Detail: "A newline is required after an attribute definition at the end of a file.", + Subject: &end.Range, + Context: zcl.RangeBetween(ident.Range, end.Range).Ptr(), + }) + } else { + diags = append(diags, &zcl.Diagnostic{ + Severity: zcl.DiagError, + Summary: "Missing newline after attribute definition", + Detail: "An attribute definition must end with a newline.", + Subject: &end.Range, + Context: zcl.RangeBetween(ident.Range, end.Range).Ptr(), + }) + } } endRange = p.PrevRange() p.recoverAfterBodyItem() @@ -285,17 +295,27 @@ Token: cBraceRange := p.PrevRange() eol := p.Peek() - if eol.Type == TokenNewline || eol.Type == TokenEOF { + if eol.Type == TokenNewline { p.Read() // eat newline } else { if !p.recovery { - diags = append(diags, &zcl.Diagnostic{ - Severity: zcl.DiagError, - Summary: "Missing newline after block definition", - Detail: "A block definition must end with a newline.", - Subject: &eol.Range, - Context: zcl.RangeBetween(ident.Range, eol.Range).Ptr(), - }) + if eol.Type == TokenEOF { + diags = append(diags, &zcl.Diagnostic{ + Severity: zcl.DiagError, + Summary: "Missing newline after block definition", + Detail: "A newline is required after a block definition at the end of a file.", + Subject: &eol.Range, + Context: zcl.RangeBetween(ident.Range, eol.Range).Ptr(), + }) + } else { + diags = append(diags, &zcl.Diagnostic{ + Severity: zcl.DiagError, + Summary: "Missing newline after block definition", + Detail: "A block definition must end with a newline.", + Subject: &eol.Range, + Context: zcl.RangeBetween(ident.Range, eol.Range).Ptr(), + }) + } } p.recoverAfterBodyItem() } diff --git a/zcl/zclsyntax/parser_test.go b/zcl/zclsyntax/parser_test.go index bec9a98..24c33de 100644 --- a/zcl/zclsyntax/parser_test.go +++ b/zcl/zclsyntax/parser_test.go @@ -33,7 +33,7 @@ func TestParseConfig(t *testing.T) { }, { - `block {}`, + "block {}\n", 0, &Body{ Attributes: Attributes{}, @@ -72,16 +72,16 @@ func TestParseConfig(t *testing.T) { }, SrcRange: zcl.Range{ Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, - End: zcl.Pos{Line: 1, Column: 9, Byte: 8}, + End: zcl.Pos{Line: 2, Column: 1, Byte: 9}, }, EndRange: zcl.Range{ - Start: zcl.Pos{Line: 1, Column: 9, Byte: 8}, - End: zcl.Pos{Line: 1, Column: 9, Byte: 8}, + Start: zcl.Pos{Line: 2, Column: 1, Byte: 9}, + End: zcl.Pos{Line: 2, Column: 1, Byte: 9}, }, }, }, { - `block {}block {}`, + "block {}block {}\n", 1, // missing newline after block definition &Body{ Attributes: Attributes{}, @@ -120,16 +120,16 @@ func TestParseConfig(t *testing.T) { }, SrcRange: zcl.Range{ Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, - End: zcl.Pos{Line: 1, Column: 17, Byte: 16}, + End: zcl.Pos{Line: 2, Column: 1, Byte: 17}, }, EndRange: zcl.Range{ - Start: zcl.Pos{Line: 1, Column: 17, Byte: 16}, - End: zcl.Pos{Line: 1, Column: 17, Byte: 16}, + Start: zcl.Pos{Line: 2, Column: 1, Byte: 17}, + End: zcl.Pos{Line: 2, Column: 1, Byte: 17}, }, }, }, { - `block "foo" {}`, + "block \"foo\" {}\n", 0, &Body{ Attributes: Attributes{}, @@ -173,11 +173,11 @@ func TestParseConfig(t *testing.T) { }, SrcRange: zcl.Range{ Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, - End: zcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: zcl.Pos{Line: 2, Column: 1, Byte: 15}, }, EndRange: zcl.Range{ - Start: zcl.Pos{Line: 1, Column: 15, Byte: 14}, - End: zcl.Pos{Line: 1, Column: 15, Byte: 14}, + Start: zcl.Pos{Line: 2, Column: 1, Byte: 15}, + End: zcl.Pos{Line: 2, Column: 1, Byte: 15}, }, }, }, @@ -269,7 +269,8 @@ block "valid" {} }, }, { - `block "f\o" {}`, + `block "f\o" {} +`, 1, // "\o" is not a valid escape sequence &Body{ Attributes: Attributes{}, @@ -301,16 +302,17 @@ block "valid" {} }, SrcRange: zcl.Range{ Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, - End: zcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: zcl.Pos{Line: 2, Column: 1, Byte: 15}, }, EndRange: zcl.Range{ - Start: zcl.Pos{Line: 1, Column: 15, Byte: 14}, - End: zcl.Pos{Line: 1, Column: 15, Byte: 14}, + Start: zcl.Pos{Line: 2, Column: 1, Byte: 15}, + End: zcl.Pos{Line: 2, Column: 1, Byte: 15}, }, }, }, { - `block "f\n" {}`, + `block "f\n" {} +`, 0, &Body{ Attributes: Attributes{}, @@ -354,11 +356,11 @@ block "valid" {} }, SrcRange: zcl.Range{ Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, - End: zcl.Pos{Line: 1, Column: 15, Byte: 14}, + End: zcl.Pos{Line: 2, Column: 1, Byte: 15}, }, EndRange: zcl.Range{ - Start: zcl.Pos{Line: 1, Column: 15, Byte: 14}, - End: zcl.Pos{Line: 1, Column: 15, Byte: 14}, + Start: zcl.Pos{Line: 2, Column: 1, Byte: 15}, + End: zcl.Pos{Line: 2, Column: 1, Byte: 15}, }, }, }, @@ -405,7 +407,7 @@ block "valid" {} }, }, { - `a = foo.bar`, + "a = foo.bar\n", 0, &Body{ Attributes: Attributes{ @@ -454,11 +456,11 @@ block "valid" {} Blocks: Blocks{}, SrcRange: zcl.Range{ Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, - End: zcl.Pos{Line: 1, Column: 12, Byte: 11}, + End: zcl.Pos{Line: 2, Column: 1, Byte: 12}, }, EndRange: zcl.Range{ - Start: zcl.Pos{Line: 1, Column: 12, Byte: 11}, - End: zcl.Pos{Line: 1, Column: 12, Byte: 11}, + Start: zcl.Pos{Line: 2, Column: 1, Byte: 12}, + End: zcl.Pos{Line: 2, Column: 1, Byte: 12}, }, }, }, diff --git a/zcldec/public_test.go b/zcldec/public_test.go index aa47999..3a545c2 100644 --- a/zcldec/public_test.go +++ b/zcldec/public_test.go @@ -26,14 +26,14 @@ func TestDecode(t *testing.T) { 0, }, { - `a = 1`, + "a = 1\n", &ObjectSpec{}, nil, cty.EmptyObjectVal, 1, // attribute named "a" is not expected here }, { - `a = 1`, + "a = 1\n", &ObjectSpec{ "a": &AttrSpec{ Name: "a", @@ -47,7 +47,7 @@ func TestDecode(t *testing.T) { 0, }, { - `a = 1`, + "a = 1\n", &AttrSpec{ Name: "a", Type: cty.Number, @@ -57,7 +57,7 @@ func TestDecode(t *testing.T) { 0, }, { - `a = "1"`, + "a = \"1\"\n", &AttrSpec{ Name: "a", Type: cty.Number, @@ -67,7 +67,7 @@ func TestDecode(t *testing.T) { 0, }, { - `a = true`, + "a = true\n", &AttrSpec{ Name: "a", Type: cty.Number, @@ -112,7 +112,7 @@ b { 0, }, { - `a {}`, + "a {}\n", &BlockSpec{ TypeName: "b", Nested: ObjectSpec{}, @@ -179,7 +179,7 @@ func TestSourceRange(t *testing.T) { want zcl.Range }{ { - `a = 1`, + "a = 1\n", &AttrSpec{ Name: "a", }, @@ -192,7 +192,8 @@ func TestSourceRange(t *testing.T) { ` b { a = 1 -}`, +} +`, &BlockSpec{ TypeName: "b", Nested: &AttrSpec{ @@ -210,7 +211,8 @@ b { c { a = 1 } -}`, +} +`, &BlockSpec{ TypeName: "b", Nested: &BlockSpec{ diff --git a/zcldec/variables_test.go b/zcldec/variables_test.go index e758da7..dc5ed2e 100644 --- a/zcldec/variables_test.go +++ b/zcldec/variables_test.go @@ -21,12 +21,12 @@ func TestVariables(t *testing.T) { nil, }, { - `a = foo`, + "a = foo\n", &ObjectSpec{}, nil, // "a" is not actually used, so "foo" is not required }, { - `a = foo`, + "a = foo\n", &AttrSpec{ Name: "a", }, @@ -43,7 +43,7 @@ func TestVariables(t *testing.T) { }, }, { - `a = foo`, + "a = foo\n", &ObjectSpec{ "a": &AttrSpec{ Name: "a", @@ -65,7 +65,8 @@ func TestVariables(t *testing.T) { ` b { a = foo -}`, +} +`, &BlockSpec{ TypeName: "b", Nested: &AttrSpec{ diff --git a/zclwrite/parser_test.go b/zclwrite/parser_test.go index f56a0ba..3b44495 100644 --- a/zclwrite/parser_test.go +++ b/zclwrite/parser_test.go @@ -24,7 +24,7 @@ func TestParse(t *testing.T) { }, }, { - "a = 1", + "a = 1\n", &Body{ Items: []Node{ &Attribute{ @@ -56,6 +56,15 @@ func TestParse(t *testing.T) { }, }, }, + &TokenSeq{ + Tokens{ + { + Type: zclsyntax.TokenNewline, + Bytes: []byte{'\n'}, + SpacesBefore: 0, + }, + }, + }, }, NameTokens: &TokenSeq{Tokens{ { @@ -111,12 +120,21 @@ func TestParse(t *testing.T) { }, }, }, + &TokenSeq{ + Tokens{ + { + Type: zclsyntax.TokenNewline, + Bytes: []byte{'\n'}, + SpacesBefore: 0, + }, + }, + }, }, }, }, }, { - "# aye aye aye\na = 1", + "# aye aye aye\na = 1\n", &Body{ Items: []Node{ &Attribute{ @@ -157,6 +175,15 @@ func TestParse(t *testing.T) { }, }, }, + &TokenSeq{ + Tokens{ + { + Type: zclsyntax.TokenNewline, + Bytes: []byte{'\n'}, + SpacesBefore: 0, + }, + }, + }, }, LeadCommentTokens: &TokenSeq{Tokens{ { @@ -228,6 +255,15 @@ func TestParse(t *testing.T) { }, }, }, + &TokenSeq{ + Tokens{ + { + Type: zclsyntax.TokenNewline, + Bytes: []byte{'\n'}, + SpacesBefore: 0, + }, + }, + }, }, }, }, @@ -350,7 +386,7 @@ func TestParse(t *testing.T) { }, }, { - "# bee bee bee\n\nb = 1", // two newlines separate the comment from the attribute + "# bee bee bee\n\nb = 1\n", // two newlines separate the comment from the attribute &Body{ Items: []Node{ &Attribute{ @@ -382,6 +418,15 @@ func TestParse(t *testing.T) { }, }, }, + &TokenSeq{ + Tokens{ + { + Type: zclsyntax.TokenNewline, + Bytes: []byte{'\n'}, + SpacesBefore: 0, + }, + }, + }, }, NameTokens: &TokenSeq{Tokens{ { @@ -451,6 +496,15 @@ func TestParse(t *testing.T) { }, }, }, + &TokenSeq{ + Tokens{ + { + Type: zclsyntax.TokenNewline, + Bytes: []byte{'\n'}, + SpacesBefore: 0, + }, + }, + }, }, }, }, diff --git a/zclwrite/round_trip_test.go b/zclwrite/round_trip_test.go index c8adda3..9ac0f6f 100644 --- a/zclwrite/round_trip_test.go +++ b/zclwrite/round_trip_test.go @@ -10,7 +10,8 @@ import ( func TestRoundTrip(t *testing.T) { tests := []string{ ``, - `foo = 1`, + `foo = 1 +`, ` foobar = 1 baz = 1