33
44using System . Collections . Concurrent ;
55using System . Collections . Immutable ;
6+ using System . DirectoryServices . Protocols ;
67using System . Text ;
78using System . Text . RegularExpressions ;
89using Bicep . Core ;
10+ using Bicep . Core . Parsing ;
11+ using Bicep . Core . PrettyPrint ;
12+ using Bicep . Core . PrettyPrintV2 ;
913using Bicep . Core . Resources ;
1014using Bicep . Core . Syntax ;
1115using Bicep . Core . TypeSystem ;
@@ -25,7 +29,7 @@ public class SnippetsProvider : ISnippetsProvider
2529 // The common properties should be authored consistently to provide for understandability and consumption of the code.
2630 // See https://github.com/Azure/azure-quickstart-templates/blob/master/1-CONTRIBUTION-GUIDE/best-practices.md#resources
2731 // for more information
28- private readonly ImmutableArray < string > propertiesSortPreferenceList = [ "scope" , "parent" , "name" , "location" , "zones" , "sku" , "kind" , "scale" , "plan" , "identity" , "tags" , "properties" , "dependsOn" ] ;
32+ private static readonly ImmutableArray < string > PropertiesSortPreferenceList = [ "scope" , "parent" , "name" , "location" , "zones" , "sku" , "kind" , "scale" , "plan" , "identity" , "tags" , "properties" , "dependsOn" ] ;
2933
3034 private static readonly SnippetCache snippetCache = SnippetCache . FromManifest ( ) ;
3135
@@ -119,96 +123,68 @@ private IEnumerable<Snippet> GetRequiredPropertiesSnippetsForDisciminatedObjectT
119123 }
120124 }
121125
122- private Snippet ? GetRequiredPropertiesSnippet ( ObjectType objectType , string label , string ? discriminatedObjectKey = null )
126+ private static ObjectSyntax GetObjectSnippetSyntax ( ObjectType objectType , ref int tabStopIndex , string ? discriminatedObjectKey )
123127 {
124- int index = 1 ;
125- StringBuilder sb = new ( ) ;
126-
127- var sortedProperties = objectType . Properties . OrderBy ( x =>
128+ var typeProperties = objectType . Properties . Values . OrderBy ( x =>
129+ PropertiesSortPreferenceList . IndexOf ( x . Name ) switch {
130+ - 1 => int . MaxValue ,
131+ int index => index ,
132+ } )
133+ . Where ( TypeHelper . IsRequired ) ;
134+
135+ var objectProperties = new List < ObjectPropertySyntax > ( ) ;
136+ foreach ( var typeProperty in typeProperties )
128137 {
129- var index = propertiesSortPreferenceList . IndexOf ( x . Key ) ;
138+ // Here we deliberately want to iterate in the correct order, and use a DFS approach, to ensure that the tab stops are correctly ordered.
139+ // For example, we want to ensure we output: {\n foo: $1\n nested: {\n bar: $2\n }\n baz: $3\n}
140+ // Instead of: {\n foo: $1\n nested: {\n bar: $3\n }\n baz: $2\n}
141+ objectProperties . Add ( GetObjectPropertySnippetSyntax ( typeProperty , ref tabStopIndex , discriminatedObjectKey ) ) ;
142+ }
130143
131- return ( index > - 1 ) ? index : ( propertiesSortPreferenceList . Length - 1 ) ;
132- } ) ;
144+ return SyntaxFactory . CreateObject ( objectProperties ) ;
145+ }
133146
134- foreach ( var ( key , value ) in sortedProperties )
147+ private static ObjectPropertySyntax GetObjectPropertySnippetSyntax ( TypeProperty typeProperty , ref int tabStopIndex , string ? discriminatedObjectKey )
148+ {
149+ var valueType = typeProperty . TypeReference . Type ;
150+ if ( valueType is ObjectType objectType )
135151 {
136- string ? snippetText = GetSnippetText ( value , indentLevel : 1 , ref index , discriminatedObjectKey ) ;
137-
138- if ( snippetText is not null )
139- {
140- sb . Append ( snippetText ) ;
141- }
152+ return SyntaxFactory . CreateObjectProperty (
153+ typeProperty . Name ,
154+ GetObjectSnippetSyntax ( objectType , ref tabStopIndex , null ) ) ;
142155 }
143-
144- if ( sb . Length > 0 )
156+ else if ( discriminatedObjectKey is { } &&
157+ valueType is StringLiteralType stringLiteralType &&
158+ stringLiteralType . Name == discriminatedObjectKey )
145159 {
146- // Insert open curly at the beginning
147- sb . Insert ( 0 , "{\n " ) ;
148-
149- // Insert final tab stop outside the top level object
150- sb . Append ( "}$0" ) ;
151-
152- return new Snippet ( sb . ToString ( ) , CompletionPriority . Medium , label , RequiredPropertiesDescription ) ;
160+ return SyntaxFactory . CreateObjectProperty (
161+ typeProperty . Name ,
162+ SyntaxFactory . CreateStringLiteral ( stringLiteralType . RawStringValue ) ) ;
163+ }
164+ else
165+ {
166+ var newTabStopIndex = tabStopIndex ++ ;
167+ return SyntaxFactory . CreateObjectProperty (
168+ typeProperty . Name ,
169+ SyntaxFactory . CreateFreeformToken ( TokenType . Unrecognized , GetTabStop ( newTabStopIndex ) ) ) ;
153170 }
154-
155- return null ;
156171 }
157172
158- private string ? GetSnippetText ( TypeProperty typeProperty , int indentLevel , ref int index , string ? discrimatedObjectKey = null )
173+ private static string GetTabStop ( int index )
174+ => $ "${ index } ";
175+
176+ private Snippet ? GetRequiredPropertiesSnippet ( ObjectType objectType , string label , string ? discriminatedObjectKey = null )
159177 {
160- if ( TypeHelper . IsRequired ( typeProperty ) )
178+ if ( ! objectType . Properties . Values . Any ( TypeHelper . IsRequired ) )
161179 {
162- StringBuilder sb = new ( ) ;
163-
164- if ( typeProperty . TypeReference . Type is ObjectType objectType )
165- {
166- sb . AppendLine ( GetIndentString ( indentLevel ) + typeProperty . Name + ": {" ) ;
167-
168- indentLevel ++ ;
169-
170- foreach ( KeyValuePair < string , TypeProperty > kvp in objectType . Properties . OrderBy ( x => x . Key ) )
171- {
172- string ? snippetText = GetSnippetText ( kvp . Value , indentLevel , ref index ) ;
173- if ( snippetText is not null )
174- {
175- sb . Append ( snippetText ) ;
176- }
177- }
178-
179- indentLevel -- ;
180- sb . AppendLine ( GetIndentString ( indentLevel ) + "}" ) ;
181- }
182- else
183- {
184- string value = ": $" + ( index ) . ToString ( ) ;
185- bool shouldIncrementIndent = true ;
186-
187- if ( discrimatedObjectKey is not null &&
188- typeProperty . TypeReference . Type is TypeSymbol typeSymbol &&
189- typeSymbol . Name == discrimatedObjectKey )
190- {
191- value = ": " + discrimatedObjectKey ;
192- shouldIncrementIndent = false ;
193- }
194-
195- sb . AppendLine ( GetIndentString ( indentLevel ) + typeProperty . Name + value ) ;
196-
197- if ( shouldIncrementIndent )
198- {
199- index ++ ;
200- }
201- }
202-
203- return sb . ToString ( ) ;
180+ return null ;
204181 }
205182
206- return null ;
207- }
183+ var tabStopIndex = 1 ;
184+ var syntax = GetObjectSnippetSyntax ( objectType , ref tabStopIndex , discriminatedObjectKey ) ;
208185
209- private string GetIndentString ( int indentLevel )
210- {
211- return new string ( '\t ' , indentLevel ) ;
186+ var output = PrettyPrinterV2 . PrintValid ( syntax , PrettyPrinterV2Options . Default with { IndentKind = IndentKind . Tab } ) + GetTabStop ( 0 ) ;
187+ return new Snippet ( output , CompletionPriority . Medium , label , RequiredPropertiesDescription ) ;
212188 }
213189
214190 private Snippet GetEmptySnippet ( )
0 commit comments