summaryrefslogtreecommitdiff
path: root/hclwrite
diff options
context:
space:
mode:
authorMartin Atkins <mart@degeneration.co.uk>2018-12-14 14:19:04 -0800
committerMartin Atkins <mart@degeneration.co.uk>2018-12-14 14:19:04 -0800
commit002296d7bb6a2f74d3eb2a3915409ae60a6a4ba3 (patch)
tree20734e275847882064471d9b297031266ea169b4 /hclwrite
parentbafa0c5ace186f6b3b1ed5c120ab5f11086b5bad (diff)
hclwrite: add space between "in" keyword and expr in for expression
Our normal ruleset thinks that the "in" keyword here is a variable reference and so writes it as "in[y]". Since there's never any reason for a variable to appear immediately after another variable, we can check for a preceding identifier as a heuristic to recognize whether in is probably being used as a keyword rather than as a variable. This is not exact, but the only time this should be a false positive is if there were a syntax error in the input, and we don't make any guarantees about the result in that case anyway. This fixes #52.
Diffstat (limited to 'hclwrite')
-rw-r--r--hclwrite/format.go17
-rw-r--r--hclwrite/format_test.go8
-rw-r--r--hclwrite/tokens.go18
3 files changed, 42 insertions, 1 deletions
diff --git a/hclwrite/format.go b/hclwrite/format.go
index a084935..c699195 100644
--- a/hclwrite/format.go
+++ b/hclwrite/format.go
@@ -4,6 +4,8 @@ import (
"github.com/hashicorp/hcl2/hcl/hclsyntax"
)
+var inKeyword = hclsyntax.Keyword([]byte{'i', 'n'})
+
// placeholder token used when we don't have a token but we don't want
// to pass a real "nil" and complicate things with nil pointer checks
var nilToken = &Token{
@@ -247,6 +249,15 @@ func spaceAfterToken(subject, before, after *Token) bool {
// No extra spaces within templates
return false
+ case inKeyword.TokenMatches(subject.asHCLSyntax()) && before.Type == hclsyntax.TokenIdent:
+ // This is a special case for inside for expressions where a user
+ // might want to use a literal tuple constructor:
+ // [for x in [foo]: x]
+ // ... in that case, we would normally produce in[foo] thinking that
+ // in is a reference, but we'll recognize it as a keyword here instead
+ // to make the result less confusing.
+ return true
+
case after.Type == hclsyntax.TokenOBrack && (subject.Type == hclsyntax.TokenIdent || subject.Type == hclsyntax.TokenNumberLit || tokenBracketChange(subject) < 0):
return false
@@ -283,7 +294,7 @@ func spaceAfterToken(subject, before, after *Token) bool {
return true
}
- case subject.Type == hclsyntax.TokenOBrace || (after != nil && after.Type == hclsyntax.TokenCBrace):
+ case subject.Type == hclsyntax.TokenOBrace || after.Type == hclsyntax.TokenCBrace:
// Unlike other bracket types, braces have spaces on both sides of them,
// both in single-line nested blocks foo { bar = baz } and in object
// constructor expressions foo = { bar = baz }.
@@ -294,6 +305,10 @@ func spaceAfterToken(subject, before, after *Token) bool {
}
return true
+ case after.Type == hclsyntax.TokenColon:
+ // Never spaces before colons
+ return false
+
case tokenBracketChange(subject) > 0:
// No spaces after open brackets
return false
diff --git a/hclwrite/format_test.go b/hclwrite/format_test.go
index 50fee30..1c06682 100644
--- a/hclwrite/format_test.go
+++ b/hclwrite/format_test.go
@@ -112,6 +112,14 @@ foo(
`[[]]`,
},
{
+ `[for x in y: x]`,
+ `[for x in y: x]`,
+ },
+ {
+ `[for x in [y]: x]`,
+ `[for x in [y]: x]`,
+ },
+ {
`
[
[
diff --git a/hclwrite/tokens.go b/hclwrite/tokens.go
index d37b1c6..d87f818 100644
--- a/hclwrite/tokens.go
+++ b/hclwrite/tokens.go
@@ -5,6 +5,7 @@ import (
"io"
"github.com/apparentlymart/go-textseg/textseg"
+ "github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hcl/hclsyntax"
)
@@ -22,6 +23,23 @@ type Token struct {
SpacesBefore int
}
+// asHCLSyntax returns the receiver expressed as an incomplete hclsyntax.Token.
+// A complete token is not possible since we don't have source location
+// information here, and so this method is unexported so we can be sure it will
+// only be used for internal purposes where we know the range isn't important.
+//
+// This is primarily intended to allow us to re-use certain functionality from
+// hclsyntax rather than re-implementing it against our own token type here.
+func (t *Token) asHCLSyntax() hclsyntax.Token {
+ return hclsyntax.Token{
+ Type: t.Type,
+ Bytes: t.Bytes,
+ Range: hcl.Range{
+ Filename: "<invalid>",
+ },
+ }
+}
+
// Tokens is a flat list of tokens.
type Tokens []*Token