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.
This commit is contained in:
parent
1de72e146e
commit
5477fecfad
@ -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()
|
||||
}
|
||||
|
@ -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},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -10,7 +10,8 @@ import (
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
tests := []string{
|
||||
``,
|
||||
`foo = 1`,
|
||||
`foo = 1
|
||||
`,
|
||||
`
|
||||
foobar = 1
|
||||
baz = 1
|
||||
|
Loading…
Reference in New Issue
Block a user