private IEnumerable <CompletionItem> GetResourceOrModuleBodyCompletions(BicepCompletionContext context) { if (context.Kind.HasFlag(BicepCompletionContextKind.ResourceBody) || context.Kind.HasFlag(BicepCompletionContextKind.ModuleBody)) { yield return(CreateObjectBodyCompletion(context.ReplacementRange)); } }
private IEnumerable <CompletionItem> GetResourceOrModuleBodyCompletions(BicepCompletionContext context) { if (context.Kind.HasFlag(BicepCompletionContextKind.ResourceBody) || context.Kind.HasFlag(BicepCompletionContextKind.ModuleBody)) { yield return(CreateObjectBodyCompletion(context.ReplacementRange)); // loops are always allowed in a resource/module yield return(CreateLoopCompletion(context.ReplacementRange, LanguageConstants.Object)); } }
private IEnumerable <CompletionItem> GetDeclarationTypeCompletions(BicepCompletionContext context) { // local function IEnumerable <CompletionItem> GetPrimitiveTypeCompletions() => LanguageConstants.DeclarationTypes.Values.Select(type => CreateTypeCompletion(type, context.ReplacementRange)); if (context.Kind.HasFlag(BicepCompletionContextKind.ParameterType)) { return(GetPrimitiveTypeCompletions().Concat(GetParameterTypeSnippets(context.ReplacementRange))); } if (context.Kind.HasFlag(BicepCompletionContextKind.OutputType)) { return(GetPrimitiveTypeCompletions()); } return(Enumerable.Empty <CompletionItem>()); }
private IEnumerable <CompletionItem> GetPrimitiveTypeCompletions(BicepCompletionContext completionContext) => completionContext.Kind.HasFlag(BicepCompletionContextKind.Declaration) == false ? LanguageConstants.DeclarationTypes.Values.Select(CreateTypeCompletion) : Enumerable.Empty <CompletionItem>();
private IEnumerable <CompletionItem> GetTargetScopeCompletions(SemanticModel model, BicepCompletionContext context) { return(context.Kind.HasFlag(BicepCompletionContextKind.TargetScope) && context.TargetScope is {} targetScope ? GetValueCompletionsForType(model.GetDeclaredType(targetScope), context.ReplacementRange) : Enumerable.Empty <CompletionItem>()); }
private IEnumerable <CompletionItem> GetSymbolCompletions(SemanticModel model, BicepCompletionContext context) { if (!context.Kind.HasFlag(BicepCompletionContextKind.Expression)) { return(Enumerable.Empty <CompletionItem>()); } if (context.Property != null && model.GetDeclaredTypeAssignment(context.Property)?.Flags == DeclaredTypeFlags.Constant) { // the enclosing property's declared type is supposed to be a constant value // the constant flag comes from TypeProperty constant flag, so nothing else can really alter it except for another property // (in other words constant flag inherits down into the expression tree of the property value) return(Enumerable.Empty <CompletionItem>()); } // when we're inside an expression that is inside a property that expects a compile-time constant value, // we should not be emitting accessible symbol completions return(GetAccessibleSymbolCompletions(model, context)); }
private IEnumerable <CompletionItem> GetDeclarationCompletions(BicepCompletionContext context) { if (context.Kind.HasFlag(BicepCompletionContextKind.TopLevelDeclarationStart)) { yield return(CreateKeywordCompletion(LanguageConstants.ParameterKeyword, "Parameter keyword", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ParameterKeyword, "Parameter declaration", "param ${1:Identifier} ${2:Type}", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ParameterKeyword, "Parameter declaration with default value", "param ${1:Identifier} ${2:Type} = ${3:DefaultValue}", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ParameterKeyword, "Parameter declaration with default and allowed values", @"param ${1:Identifier} ${2:Type} { default: $3 allowed: [ $4 ] }", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ParameterKeyword, "Parameter declaration with options", @"param ${1:Identifier} ${2:Type} { $0 }", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ParameterKeyword, "Secure string parameter", @"param ${1:Identifier} string { secure: true }", context.ReplacementRange)); yield return(CreateKeywordCompletion(LanguageConstants.VariableKeyword, "Variable keyword", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.VariableKeyword, "Variable declaration", "var ${1:Identifier} = $0", context.ReplacementRange)); yield return(CreateKeywordCompletion(LanguageConstants.ResourceKeyword, "Resource keyword", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ResourceKeyword, "Resource with defaults", @"resource ${1:Identifier} 'Microsoft.${2:Provider}/${3:Type}@${4:Version}' = { name: $5 location: $6 properties: { $0 } }", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ResourceKeyword, "Child Resource with defaults", @"resource ${1:Identifier} 'Microsoft.${2:Provider}/${3:ParentType}/${4:ChildType}@${5:Version}' = { name: $6 properties: { $0 } }", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ResourceKeyword, "Resource without defaults", @"resource ${1:Identifier} 'Microsoft.${2:Provider}/${3:Type}@${4:Version}' = { name: $5 $0 } ", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ResourceKeyword, "Child Resource without defaults", @"resource ${1:Identifier} 'Microsoft.${2:Provider}/${3:ParentType}/${4:ChildType}@${5:Version}' = { name: $6 $0 }", context.ReplacementRange)); yield return(CreateKeywordCompletion(LanguageConstants.OutputKeyword, "Output keyword", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.OutputKeyword, "Output declaration", "output ${1:Identifier} ${2:Type} = $0", context.ReplacementRange)); yield return(CreateKeywordCompletion(LanguageConstants.ModuleKeyword, "Module keyword", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ModuleKeyword, "Module declaration", @"module ${1:Identifier} '${2:Path}' = { name: $3 $0 }", context.ReplacementRange)); yield return(CreateKeywordCompletion(LanguageConstants.TargetScopeKeyword, "Target Scope keyword", context.ReplacementRange)); } if (context.Kind.HasFlag(BicepCompletionContextKind.NestedResourceDeclarationStart)) { yield return(CreateKeywordCompletion(LanguageConstants.ResourceKeyword, "Resource keyword", context.ReplacementRange)); // leaving out the API version on this, because we expect its more common to inherit from the containing resource. yield return(CreateContextualSnippetCompletion(LanguageConstants.ResourceKeyword, "Nested resource with defaults", @"resource ${1:Identifier} '${2:Type}' = { name: $3 properties: { $0 } }", context.ReplacementRange, insertTextMode: InsertTextMode.AdjustIndentation)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ResourceKeyword, "Nested resource without defaults", @"resource ${1:Identifier} '${2:Type}' = { name: $3 $0 } ", context.ReplacementRange, insertTextMode: InsertTextMode.AdjustIndentation)); } }
private IEnumerable <CompletionItem> GetSymbolCompletions(SemanticModel model, BicepCompletionContext context) { if (context.Kind != BicepCompletionContextKind.None) { return(Enumerable.Empty <CompletionItem>()); } // when we're inside an expression that is inside a property that expects a compile-time constant value, // we should not be emitting accessible symbol completions return(GetAccessibleSymbolCompletions(model, context)); }
private IEnumerable <CompletionItem> GetArrayIndexCompletions(Compilation compilation, BicepCompletionContext context) { if (!context.Kind.HasFlag(BicepCompletionContextKind.ArrayIndex) || context.ArrayAccess == null) { return(Enumerable.Empty <CompletionItem>()); } var declaredType = compilation.GetEntrypointSemanticModel().GetDeclaredType(context.ArrayAccess.BaseExpression); return(GetProperties(declaredType) .Where(p => !p.Flags.HasFlag(TypePropertyFlags.WriteOnly)) .Select(p => CreatePropertyIndexCompletion(p, context.ReplacementRange, CompletionPriority.High))); }
public IEnumerable <CompletionItem> GetFilteredCompletions(Compilation compilation, BicepCompletionContext context) { var model = compilation.GetEntrypointSemanticModel(); return(GetDeclarationCompletions(context) .Concat(GetSymbolCompletions(model, context)) .Concat(GetDeclarationTypeCompletions(context)) .Concat(GetObjectPropertyNameCompletions(model, context)) .Concat(GetMemberAccessCompletions(compilation, context)) .Concat(GetArrayIndexCompletions(compilation, context)) .Concat(GetPropertyValueCompletions(model, context)) .Concat(GetArrayItemCompletions(model, context)) .Concat(GetResourceTypeCompletions(model, context)) .Concat(GetResourceOrModuleBodyCompletions(context)) .Concat(GetTargetScopeCompletions(model, context))); }
private IEnumerable <CompletionItem> GetResourceTypeCompletions(SemanticModel model, BicepCompletionContext context) { if (!context.Kind.HasFlag(BicepCompletionContextKind.ResourceType)) { return(Enumerable.Empty <CompletionItem>()); } // we need to ensure that Microsoft.Compute/virtualMachines@whatever comes before Microsoft.Compute/virtualMachines/extensions@whatever // similarly, newest api versions should be shown first return(model.Compilation.ResourceTypeProvider.GetAvailableTypes() .OrderBy(rt => rt.FullyQualifiedType, StringComparer.OrdinalIgnoreCase) .ThenByDescending(rt => rt.ApiVersion) .Select((reference, index) => CreateResourceTypeCompletion(reference, index, context.ReplacementRange)) .ToList()); }
private static IEnumerable <CompletionItem> GetAccessibleSymbolCompletions(SemanticModel model, BicepCompletionContext context) { // maps insert text to the completion item var completions = new Dictionary <string, CompletionItem>(); var enclosingDeclarationSymbol = context.EnclosingDeclaration == null ? null : model.GetSymbolInfo(context.EnclosingDeclaration); // local function void AddSymbolCompletions(IDictionary <string, CompletionItem> result, IEnumerable <Symbol> symbols) { foreach (var symbol in symbols) { if (!result.ContainsKey(symbol.Name) && !ReferenceEquals(symbol, enclosingDeclarationSymbol) && !string.Equals(symbol.Name, enclosingDeclarationSymbol?.Name, LanguageConstants.IdentifierComparison)) { // the symbol satisfies the following conditions: // - we have not added a symbol with the same name (avoids duplicate completions) // - the symbol is different than the enclosing declaration (avoids suggesting cycles) // - the symbol name is different than the name of the enclosing declaration (avoids suggesting a duplicate identifier) result.Add(symbol.Name, CreateSymbolCompletion(symbol, context.ReplacementRange)); } } } // add namespaces first AddSymbolCompletions(completions, model.Root.ImportedNamespaces.Values); // add the non-output declarations with valid identifiers AddSymbolCompletions(completions, model.Root.AllDeclarations.Where(decl => decl.NameSyntax.IsValid && !(decl is OutputSymbol))); // get names of functions that always require to be fully qualified due to clashes between namespaces var alwaysFullyQualifiedNames = model.Root.ImportedNamespaces .SelectMany(pair => pair.Value.Type.MethodResolver.GetKnownFunctions().Values) .GroupBy(func => func.Name, (name, functionSymbols) => (name, count: functionSymbols.Count()), LanguageConstants.IdentifierComparer) .Where(tuple => tuple.count > 1) .Select(tuple => tuple.name) .ToHashSet(LanguageConstants.IdentifierComparer); foreach (var @namespace in model.Root.ImportedNamespaces.Values) { foreach (var function in @namespace.Type.MethodResolver.GetKnownFunctions().Values) { if (function.FunctionFlags.HasFlag(FunctionFlags.ParamDefaultsOnly) && !(enclosingDeclarationSymbol is ParameterSymbol)) { // this function is only allowed in param defaults but the enclosing declaration is not bound to a parameter symbol // therefore we should not suggesting this function as a viable completion continue; } if (completions.ContainsKey(function.Name) || alwaysFullyQualifiedNames.Contains(function.Name)) { // either there is a declaration with the same name as the function or the function is ambiguous between the imported namespaces // either way the function must be fully qualified in the completion var fullyQualifiedFunctionName = $"{@namespace.Name}.{function.Name}"; completions.Add(fullyQualifiedFunctionName, CreateSymbolCompletion(function, context.ReplacementRange, insertText: fullyQualifiedFunctionName)); } else { // function does not have to be fully qualified completions.Add(function.Name, CreateSymbolCompletion(function, context.ReplacementRange)); } } } return(completions.Values); }
private IEnumerable <CompletionItem> GetSymbolCompletions(SemanticModel model, BicepCompletionContext completionContext) => completionContext.Kind.HasFlag(BicepCompletionContextKind.Declaration) == false ? GetAccessibleSymbols(model).Select(sym => sym.ToCompletionItem()) : Enumerable.Empty <CompletionItem>();
public IEnumerable <CompletionItem> GetFilteredCompletions(SemanticModel model, BicepCompletionContext context) { return(GetDeclarationCompletions(context) .Concat(GetSymbolCompletions(model, context)) .Concat(GetPrimitiveTypeCompletions(context))); }
private IEnumerable <CompletionItem> GetModulePathCompletions(SemanticModel model, BicepCompletionContext context) { if (!context.Kind.HasFlag(BicepCompletionContextKind.ModulePath)) { return(Enumerable.Empty <CompletionItem>()); } // To provide intellisense before the quotes are typed if (context.EnclosingDeclaration is not ModuleDeclarationSyntax declarationSyntax || declarationSyntax.Path is not StringSyntax stringSyntax || stringSyntax.TryGetLiteralValue() is not string entered) { entered = ""; } // These should only fail if we're not able to resolve cwd path or the entered string if (FileResolver.TryResolveModulePath(model.SyntaxTree.FileUri, ".") is not { } cwdUri || FileResolver.TryResolveModulePath(cwdUri, entered) is not { } query) { return(Enumerable.Empty <CompletionItem>()); } var files = Enumerable.Empty <Uri>(); var dirs = Enumerable.Empty <Uri>(); // technically bicep files do not have to follow the bicep extension, so // we are not enforcing *.bicep get files command if (FileResolver.TryDirExists(query)) { files = FileResolver.GetFiles(query, string.Empty); dirs = FileResolver.GetDirectories(query, string.Empty); } else if (FileResolver.TryResolveModulePath(query, ".") is {} queryParent) { files = FileResolver.GetFiles(queryParent, ""); dirs = FileResolver.GetDirectories(queryParent, ""); } // "./" will not be preserved when making relative Uris. We have to go and manually add it. // Prioritize .bicep files higher than other files. var fileItems = files .Where(file => file != model.SyntaxTree.FileUri) .Where(file => file.Segments.Last().EndsWith(LanguageServerConstants.LanguageFileExtension)) .Select(file => CreateModulePathCompletion( file.Segments.Last(), (entered.StartsWith("./") ? "./" : "") + cwdUri.MakeRelativeUri(file).ToString(), context.ReplacementRange, CompletionItemKind.File, file.Segments.Last().EndsWith(LanguageServerConstants.LanguageId) ? CompletionPriority.High : CompletionPriority.Medium)) .ToList(); var dirItems = dirs .Select(dir => CreateModulePathCompletion( dir.Segments.Last(), (entered.StartsWith("./") ? "./" : "") + cwdUri.MakeRelativeUri(dir).ToString(), context.ReplacementRange, CompletionItemKind.Folder, CompletionPriority.Medium) .WithCommand(new Command { Name = EditorCommands.RequestCompletions })) .ToList(); return(fileItems.Concat(dirItems)); }
private IEnumerable <CompletionItem> GetSymbolCompletions(SemanticModel model, BicepCompletionContext completionContext) => completionContext.Kind == BicepCompletionContextKind.None ? GetAccessibleSymbols(model).Select(sym => sym.ToCompletionItem()) : Enumerable.Empty <CompletionItem>();
private static IEnumerable <CompletionItem> GetAccessibleSymbolCompletions(SemanticModel model, BicepCompletionContext context) { // maps insert text to the completion item var completions = new Dictionary <string, CompletionItem>(); var declaredNames = new HashSet <string>(); var accessibleDecoratorFunctionsCache = new Dictionary <NamespaceType, IEnumerable <FunctionSymbol> >(); var enclosingDeclarationSymbol = context.EnclosingDeclaration == null ? null : model.GetSymbolInfo(context.EnclosingDeclaration); // local function void AddSymbolCompletions(IDictionary <string, CompletionItem> result, IEnumerable <Symbol> symbols) { foreach (var symbol in symbols) { if (!result.ContainsKey(symbol.Name) && !ReferenceEquals(symbol, enclosingDeclarationSymbol)) { // the symbol satisfies the following conditions: // - we have not added a symbol with the same name (avoids duplicate completions) // - the symbol is different than the enclosing declaration (avoids suggesting cycles) // - the symbol name is different than the name of the enclosing declaration (avoids suggesting a duplicate identifier) result.Add(symbol.Name, CreateSymbolCompletion(symbol, context.ReplacementRange)); } } } // local function IEnumerable <FunctionSymbol> GetAccessibleDecoratorFunctionsWithCache(NamespaceType namespaceType) { if (accessibleDecoratorFunctionsCache.TryGetValue(namespaceType, out var result)) { return(result); } result = GetAccessibleDecoratorFunctions(namespaceType, enclosingDeclarationSymbol); accessibleDecoratorFunctionsCache.Add(namespaceType, result); return(result); } if (!context.Kind.HasFlag(BicepCompletionContextKind.DecoratorName)) { // add namespaces first AddSymbolCompletions(completions, model.Root.ImportedNamespaces.Values); // add accessible symbols from innermost scope and then move to outer scopes // reverse loop iteration for (int depth = context.ActiveScopes.Length - 1; depth >= 0; depth--) { // add the non-output declarations with valid identifiers at current scope var currentScope = context.ActiveScopes[depth]; AddSymbolCompletions(completions, currentScope.AllDeclarations.Where(decl => decl.NameSyntax.IsValid && !(decl is OutputSymbol))); } } else { // Only add the namespaces that contain accessible decorator function symbols. AddSymbolCompletions(completions, model.Root.ImportedNamespaces.Values.Where( @namespace => GetAccessibleDecoratorFunctionsWithCache(@namespace.Type).Any())); // Record the names of the non-output declarations which will be used to check name clashes later. declaredNames.UnionWith(model.Root.AllDeclarations.Where(decl => decl.NameSyntax.IsValid && decl is not OutputSymbol).Select(decl => decl.Name)); } // get names of functions that always require to be fully qualified due to clashes between namespaces var alwaysFullyQualifiedNames = model.Root.ImportedNamespaces .SelectMany(pair => context.Kind.HasFlag(BicepCompletionContextKind.DecoratorName) ? GetAccessibleDecoratorFunctionsWithCache(pair.Value.Type) : pair.Value.Type.MethodResolver.GetKnownFunctions().Values) .GroupBy(func => func.Name, (name, functionSymbols) => (name, count: functionSymbols.Count()), LanguageConstants.IdentifierComparer) .Where(tuple => tuple.count > 1) .Select(tuple => tuple.name) .ToHashSet(LanguageConstants.IdentifierComparer); foreach (var @namespace in model.Root.ImportedNamespaces.Values) { var functionSymbols = context.Kind.HasFlag(BicepCompletionContextKind.DecoratorName) ? GetAccessibleDecoratorFunctionsWithCache(@namespace.Type) : @namespace.Type.MethodResolver.GetKnownFunctions().Values; foreach (var function in functionSymbols) { if (function.FunctionFlags.HasFlag(FunctionFlags.ParamDefaultsOnly) && !(enclosingDeclarationSymbol is ParameterSymbol)) { // this function is only allowed in param defaults but the enclosing declaration is not bound to a parameter symbol // therefore we should not suggesting this function as a viable completion continue; } if (completions.ContainsKey(function.Name) || alwaysFullyQualifiedNames.Contains(function.Name) || declaredNames.Contains(function.Name)) { // either there is a declaration with the same name as the function or the function is ambiguous between the imported namespaces // either way the function must be fully qualified in the completion var fullyQualifiedFunctionName = $"{@namespace.Name}.{function.Name}"; completions.Add(fullyQualifiedFunctionName, CreateSymbolCompletion(function, context.ReplacementRange, insertText: fullyQualifiedFunctionName)); } else { // function does not have to be fully qualified completions.Add(function.Name, CreateSymbolCompletion(function, context.ReplacementRange)); } } } return(completions.Values); }
public IEnumerable <CompletionItem> GetFilteredCompletions(SemanticModel model, BicepCompletionContext context) { return(GetDeclarationCompletions(context) .Concat(GetSymbolCompletions(model, context)) .Concat(GetDeclarationTypeCompletions(context)) .Concat(GetObjectPropertyNameCompletions(model, context)) .Concat(GetPropertyValueCompletions(model, context)) .Concat(GetArrayItemCompletions(model, context))); }
private IEnumerable <CompletionItem> GetPropertyValueCompletions(SemanticModel model, BicepCompletionContext context) { if (!context.Kind.HasFlag(BicepCompletionContextKind.PropertyValue)) { return(Enumerable.Empty <CompletionItem>()); } var declaredTypeAssignment = GetDeclaredTypeAssignment(model, context.Property); if (declaredTypeAssignment == null) { return(Enumerable.Empty <CompletionItem>()); } return(GetValueCompletions(model, context, declaredTypeAssignment.Reference.Type, declaredTypeAssignment.Flags)); }
private IEnumerable <CompletionItem> GetMemberAccessCompletions(Compilation compilation, BicepCompletionContext context) { if (!context.Kind.HasFlag(BicepCompletionContextKind.MemberAccess) || context.PropertyAccess == null) { return(Enumerable.Empty <CompletionItem>()); } var declaredType = compilation.GetEntrypointSemanticModel().GetDeclaredType(context.PropertyAccess.BaseExpression); return(GetProperties(declaredType) .Where(p => !p.Flags.HasFlag(TypePropertyFlags.WriteOnly)) .Select(p => CreatePropertyAccessCompletion(p, compilation.SyntaxTreeGrouping.EntryPoint, context.PropertyAccess, context.ReplacementRange)) .Concat(GetMethods(declaredType) .Select(m => CreateSymbolCompletion(m, context.ReplacementRange)))); }
private IEnumerable <CompletionItem> GetArrayItemCompletions(SemanticModel model, BicepCompletionContext context) { if (!context.Kind.HasFlag(BicepCompletionContextKind.ArrayItem)) { return(Enumerable.Empty <CompletionItem>()); } var declaredTypeAssignment = GetDeclaredTypeAssignment(model, context.Array); if (declaredTypeAssignment == null || !(declaredTypeAssignment.Reference.Type is ArrayType arrayType)) { return(Enumerable.Empty <CompletionItem>()); } return(GetValueCompletions(model, context, arrayType.Item.Type, declaredTypeAssignment.Flags)); }
private IEnumerable <CompletionItem> GetObjectPropertyNameCompletions(SemanticModel model, BicepCompletionContext context) { if (context.Kind.HasFlag(BicepCompletionContextKind.ObjectPropertyName) == false || context.Object == null) { return(Enumerable.Empty <CompletionItem>()); } // in order to provide completions for property names, // we need to establish the type of the object first var declaredType = model.GetDeclaredType(context.Object); if (declaredType == null) { return(Enumerable.Empty <CompletionItem>()); } var specifiedPropertyNames = context.Object.ToKnownPropertyNames(); // exclude read-only properties as they can't be set // exclude properties whose name has been specified in the object already return(GetProperties(declaredType) .Where(p => !p.Flags.HasFlag(TypePropertyFlags.ReadOnly) && specifiedPropertyNames.Contains(p.Name) == false) .Select(p => CreatePropertyNameCompletion(p, context.ReplacementRange))); }
private static IEnumerable <CompletionItem> GetValueCompletions(SemanticModel model, BicepCompletionContext context, TypeSymbol type, DeclaredTypeFlags flags) { var completions = GetValueCompletionsForType(type); if (flags != DeclaredTypeFlags.Constant) { completions = completions.Concat(GetAccessibleSymbolCompletions(model, context)); } return(completions); }
private IEnumerable <CompletionItem> GetDeclarationCompletions(BicepCompletionContext context) { if (context.Kind.HasFlag(BicepCompletionContextKind.DeclarationStart)) { yield return(CreateKeywordCompletion(LanguageConstants.ParameterKeyword, "Parameter keyword", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ParameterKeyword, "Parameter declaration", "param ${1:Identifier} ${2:Type}", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ParameterKeyword, "Parameter declaration with default value", "param ${1:Identifier} ${2:Type} = ${3:DefaultValue}", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ParameterKeyword, "Parameter declaration with default and allowed values", @"param ${1:Identifier} ${2:Type} { default: $3 allowed: [ $4 ] }", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ParameterKeyword, "Parameter declaration with options", @"param ${1:Identifier} ${2:Type} { $0 }", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ParameterKeyword, "Secure string parameter", @"param ${1:Identifier} string { secure: true }", context.ReplacementRange)); yield return(CreateKeywordCompletion(LanguageConstants.VariableKeyword, "Variable keyword", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.VariableKeyword, "Variable declaration", "var ${1:Identifier} = $0", context.ReplacementRange)); yield return(CreateKeywordCompletion(LanguageConstants.ResourceKeyword, "Resource keyword", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ResourceKeyword, "Resource with defaults", @"resource ${1:Identifier} 'Microsoft.${2:Provider}/${3:Type}@${4:Version}' = { name: $5 location: $6 properties: { $0 } }", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ResourceKeyword, "Child Resource with defaults", @"resource ${1:Identifier} 'Microsoft.${2:Provider}/${3:ParentType}/${4:ChildType}@${5:Version}' = { name: $6 properties: { $0 } }", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ResourceKeyword, "Resource without defaults", @"resource ${1:Identifier} 'Microsoft.${2:Provider}/${3:Type}@${4:Version}' = { name: $5 $0 } ", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ResourceKeyword, "Child Resource without defaults", @"resource ${1:Identifier} 'Microsoft.${2:Provider}/${3:ParentType}/${4:ChildType}@${5:Version}' = { name: $6 $0 }", context.ReplacementRange)); yield return(CreateKeywordCompletion(LanguageConstants.OutputKeyword, "Output keyword", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.OutputKeyword, "Output declaration", "output ${1:Identifier} ${2:Type} = $0", context.ReplacementRange)); yield return(CreateKeywordCompletion(LanguageConstants.ModuleKeyword, "Module keyword", context.ReplacementRange)); yield return(CreateContextualSnippetCompletion(LanguageConstants.ModuleKeyword, "Module declaration", @"module ${1:Identifier} '${2:Path}' = { name: $3 $0 }", context.ReplacementRange)); yield return(CreateKeywordCompletion(LanguageConstants.TargetScopeKeyword, "Target Scope keyword", context.ReplacementRange)); } }
private IEnumerable <CompletionItem> GetResourceTypeCompletions(SemanticModel model, BicepCompletionContext context) { if (!context.Kind.HasFlag(BicepCompletionContextKind.ResourceType)) { return(Enumerable.Empty <CompletionItem>()); } // For a nested resource, we want to filter the set of types. // // The strategy when *can't* filter - due to errors - to fallback to the main path and offer full completions // then once the user corrects whatever's cause the error, they will be told to simplify the type. if (context.EnclosingDeclaration is SyntaxBase && model.Binder.GetNearestAncestor <ResourceDeclarationSyntax>(context.EnclosingDeclaration) is ResourceDeclarationSyntax parentSyntax && model.GetSymbolInfo(parentSyntax) is ResourceSymbol parentSymbol && parentSymbol.TryGetResourceTypeReference() is ResourceTypeReference parentTypeReference) { // This is more complex because we allow the API version to be omitted, so we want to make a list of unique values // for the FQT, and then create a "no version" completion + a completion for each version. var filtered = model.Compilation.ResourceTypeProvider.GetAvailableTypes() .Where(rt => parentTypeReference.IsParentOf(rt)) .ToLookup(rt => rt.FullyQualifiedType); var index = 0; var items = new List <CompletionItem>(); foreach (var group in filtered.OrderBy(group => group.Key, StringComparer.OrdinalIgnoreCase)) { // Doesn't matter which one of the group we take, we're leaving out the version. items.Add(CreateResourceTypeSegmentCompletion(group.First(), index++, context.ReplacementRange, includeApiVersion: false, displayApiVersion: parentTypeReference.ApiVersion)); foreach (var resourceType in group.OrderByDescending(rt => rt.ApiVersion, ApiVersionComparer.Instance)) { items.Add(CreateResourceTypeSegmentCompletion(resourceType, index++, context.ReplacementRange, includeApiVersion: true, displayApiVersion: resourceType.ApiVersion)); } } return(items); } // we need to ensure that Microsoft.Compute/virtualMachines@whatever comes before Microsoft.Compute/virtualMachines/extensions@whatever // similarly, newest api versions should be shown first return(model.Compilation.ResourceTypeProvider.GetAvailableTypes() .OrderBy(rt => rt.FullyQualifiedType, StringComparer.OrdinalIgnoreCase) .ThenByDescending(rt => rt.ApiVersion, ApiVersionComparer.Instance) .Select((reference, index) => CreateResourceTypeCompletion(reference, index, context.ReplacementRange)) .ToList()); }