22package align
33
44import (
5+ "fmt"
56 "sort"
67 "strings"
78
9+ "github.com/hashicorp/hcl/v2/hclsyntax"
810 "github.com/hashicorp/hcl/v2/hclwrite"
11+ ihcl "github.com/oferchen/hclalign/internal/hcl"
912)
1013
1114type terraformStrategy struct {}
@@ -16,10 +19,9 @@ func (terraformStrategy) Name() string { return "terraform" }
1619// are sorted alphabetically. Nested blocks are sorted by type and labels while
1720// preserving the ordering of their contents. If a required_providers block is
1821// present, the provider entries inside it are also sorted alphabetically.
19- func (terraformStrategy ) Align (block * hclwrite.Block , _ * Options ) error {
22+ func (terraformStrategy ) Align (block * hclwrite.Block , opts * Options ) error {
2023 body := block .Body ()
2124
22- // Sort provider entries within required_providers blocks
2325 for _ , nb := range body .Blocks () {
2426 if nb .Type () == "required_providers" {
2527 attrs := nb .Body ().Attributes ()
@@ -34,31 +36,154 @@ func (terraformStrategy) Align(block *hclwrite.Block, _ *Options) error {
3436 }
3537 }
3638
37- // Order top-level blocks by type then labels
39+ attrs := body . Attributes ()
3840 blocks := body .Blocks ()
39- sort .SliceStable (blocks , func (i , j int ) bool {
40- bi , bj := blocks [i ], blocks [j ]
41+
42+ canonical := []string {"required_version" , "required_providers" , "experiments" , "cloud" , "backend" }
43+ canonSet := make (map [string ]struct {}, len (canonical ))
44+ for _ , n := range canonical {
45+ canonSet [n ] = struct {}{}
46+ }
47+
48+ if opts != nil && opts .Strict {
49+ var missing []string
50+ for _ , n := range canonical {
51+ if _ , ok := attrs [n ]; ok {
52+ continue
53+ }
54+ found := false
55+ for _ , b := range blocks {
56+ if b .Type () == n {
57+ found = true
58+ break
59+ }
60+ }
61+ if ! found {
62+ missing = append (missing , n )
63+ }
64+ }
65+ if len (missing ) > 0 {
66+ sort .Strings (missing )
67+ return fmt .Errorf ("terraform: missing attributes or blocks: %s" , strings .Join (missing , ", " ))
68+ }
69+ var unknown []string
70+ for name := range attrs {
71+ if _ , ok := canonSet [name ]; ! ok {
72+ unknown = append (unknown , name )
73+ }
74+ }
75+ for _ , b := range blocks {
76+ if _ , ok := canonSet [b .Type ()]; ! ok {
77+ unknown = append (unknown , b .Type ())
78+ }
79+ }
80+ if len (unknown ) > 0 {
81+ sort .Strings (unknown )
82+ return fmt .Errorf ("terraform: unknown attributes or blocks: %s" , strings .Join (unknown , ", " ))
83+ }
84+ }
85+
86+ tokens := body .BuildTokens (nil )
87+ newline := ihcl .DetectLineEnding (tokens )
88+ trailingComma := ihcl .HasTrailingComma (tokens )
89+
90+ attrTokens := map [string ]ihcl.AttrTokens {}
91+ for name , attr := range attrs {
92+ attrTokens [name ] = ihcl .ExtractAttrTokens (attr )
93+ body .RemoveAttribute (name )
94+ }
95+
96+ var reqProviders , cloudBlock , backendBlock * hclwrite.Block
97+ otherBlocks := make ([]* hclwrite.Block , 0 , len (blocks ))
98+ for _ , b := range blocks {
99+ body .RemoveBlock (b )
100+ switch b .Type () {
101+ case "required_providers" :
102+ reqProviders = b
103+ case "cloud" :
104+ cloudBlock = b
105+ case "backend" :
106+ backendBlock = b
107+ default :
108+ otherBlocks = append (otherBlocks , b )
109+ }
110+ }
111+
112+ sort .SliceStable (otherBlocks , func (i , j int ) bool {
113+ bi , bj := otherBlocks [i ], otherBlocks [j ]
41114 if bi .Type () != bj .Type () {
42115 return bi .Type () < bj .Type ()
43116 }
44117 return strings .Join (bi .Labels (), "\x00 " ) < strings .Join (bj .Labels (), "\x00 " )
45118 })
46- for _ , b := range body .Blocks () {
47- body .RemoveBlock (b )
119+
120+ otherAttrNames := make ([]string , 0 , len (attrTokens ))
121+ for name := range attrTokens {
122+ if name != "required_version" && name != "experiments" {
123+ otherAttrNames = append (otherAttrNames , name )
124+ }
48125 }
49- for _ , b := range blocks {
50- body .AppendBlock (b )
126+ sort .Strings (otherAttrNames )
127+
128+ type item struct {
129+ name string
130+ block * hclwrite.Block
131+ isAttr bool
51132 }
52133
53- // Gather and order attributes
54- attrs := body .Attributes ()
55- names := make ([]string , 0 , len (attrs ))
56- for name := range attrs {
57- names = append (names , name )
134+ var items []item
135+ if _ , ok := attrTokens ["required_version" ]; ok {
136+ items = append (items , item {name : "required_version" , isAttr : true })
137+ }
138+ if reqProviders != nil {
139+ items = append (items , item {block : reqProviders })
140+ }
141+ if _ , ok := attrTokens ["experiments" ]; ok {
142+ items = append (items , item {name : "experiments" , isAttr : true })
143+ }
144+ if cloudBlock != nil {
145+ items = append (items , item {block : cloudBlock })
146+ }
147+ if backendBlock != nil {
148+ items = append (items , item {block : backendBlock })
149+ }
150+ for _ , name := range otherAttrNames {
151+ items = append (items , item {name : name , isAttr : true })
152+ }
153+ for _ , b := range otherBlocks {
154+ items = append (items , item {block : b })
58155 }
59- sort .Strings (names )
60156
61- return reorderBlock (block , names )
157+ body .Clear ()
158+ if len (items ) > 0 {
159+ body .AppendUnstructuredTokens (hclwrite.Tokens {
160+ & hclwrite.Token {Type : hclsyntax .TokenNewline , Bytes : newline },
161+ })
162+ }
163+ for _ , it := range items {
164+ if it .isAttr {
165+ tok := attrTokens [it .name ]
166+ body .AppendUnstructuredTokens (tok .LeadTokens )
167+ body .SetAttributeRaw (it .name , tok .ExprTokens )
168+ } else {
169+ body .AppendUnstructuredTokens (hclwrite.Tokens {
170+ & hclwrite.Token {Type : hclsyntax .TokenNewline , Bytes : newline },
171+ })
172+ body .AppendBlock (it .block )
173+ }
174+ }
175+ if trailingComma && len (items ) > 0 {
176+ body .AppendUnstructuredTokens (hclwrite.Tokens {
177+ & hclwrite.Token {Type : hclsyntax .TokenComma , Bytes : []byte ("," )},
178+ })
179+ }
180+ toks := body .BuildTokens (nil )
181+ if len (toks ) > 0 && toks [len (toks )- 1 ].Type != hclsyntax .TokenNewline {
182+ body .AppendUnstructuredTokens (hclwrite.Tokens {
183+ & hclwrite.Token {Type : hclsyntax .TokenNewline , Bytes : newline },
184+ })
185+ }
186+ return nil
62187}
63188
64189func init () { Register (terraformStrategy {}) }
0 commit comments