import { assert } from "../../fixed_assert" ; import { AzureRMAssets } from "../../language/expressions/AzureRMAssets" ; import * as TLE from "../../language/expressions/TLE" ; import * as Json from "../../language/json/JSON" ; import { ContainsBehavior , Span } from "../../language/Span" ; import * as Completion from "../../vscodeIntegration/Completion" ; import { PositionContext } from "../positionContexts/PositionContext" ; import { TemplatePositionContext } from "../positionContexts/TemplatePositionContext" ; import { getResourcesInfo , IResourceInfo } from "./getResourcesInfo" ; import { FunctionBehaviors } from "./IFunctionMetadata" ; import { TemplateScope } from "./scopes/TemplateScope" ; export function getResourceIdCompletions ( pc , funcCall , parentStringToken ) { assert ( parentStringToken . type === Json . TokenType . QuotedString , "parentStringToken should be a string token" ) ; const scope = pc . getScope ( ) ; if ( ! funcCall . isUserDefinedFunction && funcCall . name ) { const functionMetadata = AzureRMAssets . getFunctionMetadataFromName ( funcCall . name ) ; if ( functionMetadata ?. hasBehavior ( FunctionBehaviors . usesResourceIdCompletions ) ) { const argumentIndex = pc . getFunctionCallArgumentIndex ( funcCall ) ; if ( typeof argumentIndex === 'number' && argumentIndex >= 0 ) { const segmentIndex = argumentIndex ; return getCompletions ( pc , scope , funcCall , parentStringToken , segmentIndex , { maxOptionalParameters : 2 } ) ; } } } return [ ] ; } function getCompletions ( pc , scope , funcCall , parentStringToken , argIndexAtCursor , resourceIdCompletions ) { if ( argIndexAtCursor === 0 ) { return getResourceTypeCompletions ( funcCall , pc , scope , resourceIdCompletions , argIndexAtCursor , parentStringToken ) ; } const allResources = getResourcesInfo ( { scope , recognizeDecoupledChildren : false } ) ; let argWithResourceType = findFunctionCallArgumentWithResourceType ( allResources , pc , funcCall , parentStringToken , argIndexAtCursor - 1 ) ; if ( ! argWithResourceType ) { return getResourceTypeCompletions ( funcCall , pc , scope , resourceIdCompletions , argIndexAtCursor , parentStringToken ) ; } let filteredResources = filterResourceInfosByType ( allResources , argWithResourceType . typeExpression ) ; let argIndex = argWithResourceType . argIndex + 1 ; let nameSegmentIndex = 0 ; while ( true ) { assert ( argIndex <= argIndexAtCursor ) ; if ( argIndex === argIndexAtCursor ) { break ; } let argExpression = getArgumentExpressionText ( pc , funcCall , parentStringToken , argIndex ) ; if ( ! argExpression ) { return [ ] ; } filteredResources = filterResourceInfosByNameSegment ( filteredResources , argExpression , nameSegmentIndex ) ; ++ argIndex ; ++ nameSegmentIndex ; } const results = [ ] ; for ( let info of filteredResources ) { const insertText = info . nameSegmentExpressions [ nameSegmentIndex ] ; if ( insertText ) { const label = insertText ; let nameSegmentArgument = funcCall . argumentExpressions [ argIndex ] ; let span = getReplacementSpan ( pc , nameSegmentArgument , parentStringToken ) ; results . push ( new Completion . Item ( { label , insertText , span , kind : Completion . CompletionKind . tleResourceIdResNameParameter , priority : Completion . CompletionPriority . high , preselect : true , commitCharacters : [ ')' , ',' ] , telemetryProperties : { segment : String ( argIndex ) , arg : String ( argIndexAtCursor ) , function : funcCall . fullName } } ) ) ; } } return Completion . Item . dedupeByLabel ( results ) ; } /**
 * Finds the first argument in the function that matches a known type (could be
 * an expression), or looks like a resource type as a string literal, e.g.
 *
 *   resourceId(subscription().id, 'Microsoft.Network/vnet', 'vnet1')
 *     ->
 * returns the second argument (index=0)
 */ function findFunctionCallArgumentWithResourceType ( resources , pc , funcCall , parentStringToken , maxIndex ) { for ( let argIndex = 0 ; argIndex <= maxIndex ; ++ argIndex ) { let argText = getArgumentExpressionText ( pc , funcCall , parentStringToken , argIndex ) ; if ( argText ) { if ( looksLikeResourceTypeStringLiteral ( argText ) ) { return { argIndex , typeExpression : argText } ; } let argTextLC = argText ?. toLowerCase ( ) ; if ( argTextLC ) { for ( let info of resources ) { if ( info . getFullTypeExpression ( ) ?. toLowerCase ( ) === argTextLC ) { return { argIndex , typeExpression : argText } ; } } } } } return undefined ; } function filterResourceInfosByType ( infos , typeExpression ) { if ( ! typeExpression ) { return [ ] ; } const typeExpressionLC = typeExpression . toLowerCase ( ) ; return infos . filter ( info => info . getFullTypeExpression ( ) ?. toLowerCase ( ) === typeExpressionLC ) ; } function filterResourceInfosByNameSegment ( infos , segmentExpression , segmentIndex ) { if ( ! segmentExpression ) { return [ ] ; } const segmentExpressionLC = lowerCaseAndNoWhitespace ( segmentExpression ) ; return infos . filter ( info => lowerCaseAndNoWhitespace ( info . nameSegmentExpressions [ segmentIndex ] ) === segmentExpressionLC ) ; } function lowerCaseAndNoWhitespace ( s ) { return s ?. toLowerCase ( ) . replace ( /\s/g , '' ) ; } /**
 * Get completions for all resource types available (all those used in the template)
 * at the given document index
 */ function getResourceTypeCompletions ( funcCall , pc , scope , resourceIdCompletions , argumentIndex , parentStringToken ) { if ( argumentIndex > resourceIdCompletions . maxOptionalParameters ) { return [ ] ; } const results = [ ] ; const infos = getResourcesInfo ( { scope , recognizeDecoupledChildren : false } ) ; for ( const info of infos ) { const insertText = info . getFullTypeExpression ( ) ; if ( insertText ) { const label = insertText ; let typeArgument = funcCall . argumentExpressions [ argumentIndex ] ; let span = getReplacementSpan ( pc , typeArgument , parentStringToken ) ; const item = new Completion . Item ( { label , insertText , span , kind : Completion . CompletionKind . tleResourceIdResTypeParameter , priority : Completion . CompletionPriority . high , preselect : true , commitCharacters : [ ',' ] , telemetryProperties : { segment : String ( 0 ) , arg : String ( argumentIndex ) , function : funcCall . fullName } } ) ; if ( item . span . contains ( pc . documentCharacterIndex , ContainsBehavior . strict ) ) { results . push ( item ) ; } else { } } } return Completion . Item . dedupeByLabel ( results ) ; } function getReplacementSpan ( pc , argument , parentStringToken ) { let span = argument ?. getSpan ( ) ?. translate ( parentStringToken . span . startIndex ) ?? pc . emptySpanAtDocumentCharacterIndex ; return span ; } /**
 * Retrieves the document text for the given function call argument
 */ function getArgumentExpressionText ( pc , funcCall , parentStringToken , argumentIndex ) { const argExpressionValue = funcCall . argumentExpressions [ argumentIndex ] ; if ( argExpressionValue ) { return pc . document . getTextAtTleValue ( argExpressionValue , parentStringToken ) ?. toLowerCase ( ) ; } return undefined ; } export function looksLikeResourceTypeStringLiteral ( text ) { return ! ! text . match ( /^'[^'.]+\.[^'.]+\/([^'.]+)(\.[^'.]+)*'$/ ) ; }