import { templateKeys } from "../../../common" ; import { ext } from "../../extensionVariables" ; import { assert } from '../../fixed_assert' ; import { AzureRMAssets , BuiltinFunctionMetadata } from "../../language/expressions/AzureRMAssets" ; import { isTleExpression } from "../../language/expressions/isTleExpression" ; import * as TLE from "../../language/expressions/TLE" ; import { INamedDefinition } from "../../language/INamedDefinition" ; import * as Json from "../../language/json/JSON" ; import { ReferenceList } from "../../language/ReferenceList" ; import { ContainsBehavior , Span } from "../../language/Span" ; import { InsertionContext } from "../../snippets/InsertionContext" ; import { KnownContexts } from "../../snippets/KnownContexts" ; import { CachedValue } from "../../util/CachedValue" ; import * as Completion from "../../vscodeIntegration/Completion" ; import { FormattedExpressionHoverInfo } from "../../vscodeIntegration/FormattedExpressionHoverInfo" ; import { FormattedStringHoverInfo } from "../../vscodeIntegration/FormattedStringHoverInfo" ; import { IHoverInfo } from "../../vscodeIntegration/IHoverInfo" ; import { DeploymentDocument } from "../DeploymentDocument" ; import { DeploymentParametersDoc } from "../parameters/DeploymentParametersDoc" ; import { IParameterDefinition } from "../parameters/IParameterDefinition" ; import { IParameterValuesSource } from "../parameters/IParameterValuesSource" ; import { findReferencesToDefinitionInParameterValues , getPropertyValueCompletionItems , getReferenceSiteInfoForParameterValue } from "../parameters/ParameterValues" ; import { DeploymentTemplateDoc } from "../templates/DeploymentTemplateDoc" ; import { getDependsOnCompletions } from "../templates/getDependsOnCompletions" ; import { getResourceIdCompletions } from "../templates/getResourceIdCompletions" ; import { IFunctionMetadata , IFunctionParameterMetadata } from "../templates/IFunctionMetadata" ; import { TemplateScope } from "../templates/scopes/TemplateScope" ; import { isDeploymentResource , TopLevelTemplateScope } from "../templates/scopes/templateScopes" ; import { UserFunctionDefinition } from "../templates/UserFunctionDefinition" ; import { UserFunctionMetadata } from "../templates/UserFunctionMetadata" ; import { UserFunctionNamespaceDefinition } from "../templates/UserFunctionNamespaceDefinition" ; import { IVariableDefinition } from "../templates/VariableDefinition" ; import { ICompletionItemsResult , IReferenceSite , PositionContext , ReferenceSiteKind } from "./PositionContext" ; /**
 * Information about the TLE expression (if position is at an expression string)
 */ class TleInfo implements ITleInfo { public constructor ( public readonly tleParseResult , /**
     * Index of the cursor from the start of the TLE string
     */ public readonly tleCharacterIndex , public readonly tleValue , public readonly scope ) { } } /**
 * Represents a position inside the snapshot of a deployment template, plus all related information
 * that can be parsed and analyzed about it
 */ export class TemplatePositionContext extends PositionContext { private _tleInfo : CachedValue < TleInfo | undefined > = new CachedValue < TleInfo | undefined > ( ) ; public static fromDocumentLineAndColumnIndexes ( deploymentTemplate , documentLineIndex , documentColumnIndex , associatedParameters , allowOutOfBounds = true ) { let context = new TemplatePositionContext ( deploymentTemplate , associatedParameters ) ; context . initFromDocumentLineAndColumnIndices ( documentLineIndex , documentColumnIndex , allowOutOfBounds ) ; return context ; } public static fromDocumentCharacterIndex ( deploymentTemplate , documentCharacterIndex , associatedParameters , allowOutOfBounds = true ) { let context = new TemplatePositionContext ( deploymentTemplate , associatedParameters ) ; context . initFromDocumentCharacterIndex ( documentCharacterIndex , allowOutOfBounds ) ; return context ; } private constructor ( deploymentTemplate , associatedParameters ) { super ( deploymentTemplate , associatedParameters ) ; } public get document ( ) : DeploymentTemplateDoc { return < DeploymentTemplateDoc > super . document ; } /**
     * Retrieves TleInfo for the current position if it's inside a string
     */ public get tleInfo ( ) : TleInfo | undefined { return this . _tleInfo . getOrCacheValue ( ( ) => { const jsonToken = this . jsonToken ; if ( jsonToken && jsonToken . type === Json . TokenType . QuotedString && this . jsonValue && this . jsonValue instanceof Json . StringValue ) { const tleParseResult = this . document . getTLEParseResultFromJsonStringValue ( this . jsonValue ) ; const tleCharacterIndex = this . documentCharacterIndex - this . jsonTokenStartIndex ; const tleValue = tleParseResult . parseResult . getValueAtCharacterIndex ( tleCharacterIndex ) ; return new TleInfo ( tleParseResult . parseResult , tleCharacterIndex , tleValue , tleParseResult . scope ) ; } return undefined ; } ) ; } /**
     * If this position is inside an expression, inside a reference to an interesting function/parameter/etc, then
     * return an object with information about this reference and the corresponding definition
     */ public getReferenceSiteInfo ( considerDefinitionItself ) { const tleInfo = this . tleInfo ; if ( tleInfo ) { const scope = tleInfo . scope ; const tleCharacterIndex = tleInfo . tleCharacterIndex ; const definitionDocument = this . document ; const referenceDocument = this . document ; const tleFuncCall = TLE . asFunctionCallValue ( tleInfo . tleValue ) ; if ( tleFuncCall ) { if ( tleFuncCall . namespaceToken && tleFuncCall . namespaceToken . span . contains ( tleCharacterIndex , ContainsBehavior . strict ) ) { const ns = tleFuncCall . namespaceToken . stringValue ; const nsDefinition = scope . getFunctionNamespaceDefinition ( ns ) ; if ( nsDefinition ) { const unquotedReferenceSpan = tleFuncCall . namespaceToken . span . translate ( this . jsonTokenStartIndex ) ; return { referenceKind : ReferenceSiteKind . reference , referenceDocument , definition : nsDefinition , definitionScope : scope , unquotedReferenceSpan , definitionDocument } ; } } else if ( tleFuncCall . nameToken ) { const unquotedReferenceSpan = tleFuncCall . nameToken . span . translate ( this . jsonTokenStartIndex ) ; const referenceKind = ReferenceSiteKind . reference ; if ( tleFuncCall . nameToken . span . contains ( tleCharacterIndex , ContainsBehavior . strict ) ) { if ( tleFuncCall . namespaceToken ) { const ns = tleFuncCall . namespaceToken . stringValue ; const name = tleFuncCall . nameToken . stringValue ; const nsDefinition = scope . getFunctionNamespaceDefinition ( ns ) ; const userFunctiondefinition = scope . getUserFunctionDefinition ( ns , name ) ; if ( nsDefinition && userFunctiondefinition ) { return { referenceKind , referenceDocument , definition : userFunctiondefinition , definitionScope : scope , unquotedReferenceSpan , definitionDocument } ; } } else { const builtInFuncName = tleFuncCall . nameToken . stringValue . toLowerCase ( ) ; if ( tleFuncCall . argumentExpressions . length === 1 ) { const arg = tleFuncCall . argumentExpressions [ 0 ] ; if ( arg instanceof TLE . StringValue ) { const argStringValue = tleFuncCall . argumentExpressions [ 0 ] ?. asStringValue ?. unquotedValue ; if ( argStringValue ) { if ( builtInFuncName === templateKeys . parameters ) { const ref = this . getParameterReference ( referenceDocument , definitionDocument , scope , referenceKind , arg ) ; if ( ref ) { return ref ; } } else if ( builtInFuncName === templateKeys . variables ) { const ref = this . getVariableReference ( referenceDocument , definitionDocument , scope , referenceKind , arg ) ; if ( ref ) { return ref ; } } } } } const functionMetadata = AzureRMAssets . getFunctionMetadataFromName ( tleFuncCall . nameToken . stringValue ) ; if ( functionMetadata ) { return { referenceKind , referenceDocument , definition : functionMetadata , definitionScope : undefined , unquotedReferenceSpan , definitionDocument } ; } } } } } const tleStringValue = TLE . asStringValue ( tleInfo . tleValue ) ; if ( tleStringValue ) { const referenceKind = ReferenceSiteKind . reference ; if ( tleStringValue . isParametersArgument ( ) ) { const ref = this . getParameterReference ( referenceDocument , definitionDocument , scope , referenceKind , tleStringValue ) ; if ( ref ) { return ref ; } } else if ( tleStringValue . isVariablesArgument ( ) ) { const ref = this . getVariableReference ( referenceDocument , definitionDocument , scope , referenceKind , tleStringValue ) ; if ( ref ) { return ref ; } } } for ( const childScope of scope . childScopes ) { if ( childScope . parameterValuesSource ) { const infoAsParamValue = getReferenceSiteInfoForParameterValue ( this . document , childScope . parameterDefinitionsSource , childScope , childScope . parameterValuesSource , this . documentCharacterIndex ) ; if ( infoAsParamValue ) { return infoAsParamValue ; } } } if ( considerDefinitionItself ) { const definition = this . getDefinitionAtSite ( ) ; if ( definition && definition . nameValue ) { return { referenceKind : ReferenceSiteKind . definition , definition : definition , referenceDocument : this . document , definitionDocument : this . document , definitionScope : tleInfo . scope , unquotedReferenceSpan : definition . nameValue ?. unquotedSpan } ; } } return undefined ; } } public getHoverInfo ( ) { const infos = super . getHoverInfo ( ) ; if ( this . jsonValue instanceof Json . StringValue && isTleExpression ( this . jsonValue . unquotedValue ) ) { const tleParseResult = this . tleInfo ?. tleParseResult ; if ( tleParseResult ?. errors . length === 0 ) { const expr = tleParseResult . expression ; if ( expr ) { infos . push ( new FormattedExpressionHoverInfo ( expr ) ) ; } } } else if ( this . jsonValue instanceof Json . StringValue && this . jsonValue . unquotedValue . match ( /\\r|\\n|\r|\n/ ) ) { const tleParseResult = this . tleInfo ?. tleParseResult ; if ( tleParseResult ?. errors . length === 0 ) { const expr = tleParseResult . expression ; if ( expr ) { infos . push ( new FormattedStringHoverInfo ( this . jsonValue ) ) ; } } } return infos ; } private getParameterReference ( referenceDocument , definitionDocument , scope , referenceKind , arg ) { const parameterDefinition = scope . getParameterDefinition ( arg . unquotedValue ) ; if ( parameterDefinition ) { const unquotedReferenceSpan = arg . unquotedSpan . translate ( this . jsonTokenStartIndex ) ; return { referenceKind , referenceDocument , definition : parameterDefinition , unquotedReferenceSpan , definitionDocument , definitionScope : scope } ; } } private getVariableReference ( referenceDocument , definitionDocument , scope , referenceKind , arg ) { const variableDefinition = scope . getVariableDefinition ( arg . unquotedValue ) ; if ( variableDefinition ) { const unquotedReferenceSpan = arg . unquotedSpan . translate ( this . jsonTokenStartIndex ) ; return { referenceKind , referenceDocument , definition : variableDefinition , definitionScope : scope , unquotedReferenceSpan , definitionDocument } ; } } private isInsideParameterDefinitions ( parents ) { if ( parents [ 0 ] instanceof Json . ObjectValue && parents [ 1 ] ?. isPropertyWithName ( templateKeys . parameters ) && parents [ 2 ] === this . document . topLevelValue ) { return true ; } if ( parents [ 0 ] instanceof Json . ObjectValue && parents [ 1 ] ?. isPropertyWithName ( templateKeys . parameters ) && parents [ 2 ] instanceof Json . ObjectValue && parents [ 3 ] ?. isPropertyWithName ( templateKeys . nestedDeploymentTemplateProperty ) ) { return true ; } return false ; } private isInsideParameterValues ( parents ) { if ( parents [ 0 ] instanceof Json . ObjectValue && parents [ 1 ] ?. isPropertyWithName ( templateKeys . parameters ) && parents [ 2 ] instanceof Json . ObjectValue && parents [ 3 ] ?. isPropertyWithName ( templateKeys . properties ) && isDeploymentResource ( parents [ 4 ] ) ) { return true ; } return false ; } private isInsideUserFunctionParameterDefinitions ( parents ) { if ( parents [ 0 ] instanceof Json . ObjectValue ) { parents = parents . slice ( 1 ) ; } if ( parents [ 0 ] instanceof Json . ArrayValue && parents [ 1 ] ?. isPropertyWithName ( templateKeys . parameters ) && parents [ 2 ] instanceof Json . ObjectValue && parents [ 3 ] instanceof Json . Property && parents [ 4 ] instanceof Json . ObjectValue && parents [ 5 ] ?. isPropertyWithName ( templateKeys . userFunctionMembers ) ) { return true ; } return false ; } private isInsideResourceBody ( parents ) { if ( parents [ 0 ] instanceof Json . ObjectValue && parents [ 0 ] ?. hasProperty ( templateKeys . resourceType ) && parents [ 0 ] ?. hasProperty ( templateKeys . resourceApiVersion ) ) { return true ; } return false ; } public getInsertionContext ( options ) { const insertionContext = super . getInsertionContext ( options ) ; const context = insertionContext . context ; const parents = insertionContext . parents ; if ( parents ) { if ( context === 'parameters' ) { if ( this . isInsideParameterDefinitions ( parents ) ) { insertionContext . context = KnownContexts . parameterDefinitions ; } else if ( this . isInsideParameterValues ( parents ) ) { insertionContext . context = KnownContexts . parameterValues ; } else if ( this . isInsideUserFunctionParameterDefinitions ( parents ) ) { insertionContext . context = KnownContexts . userFuncParameterDefinitions ; } } else if ( ( options . triggerCharacter === undefined || options . triggerCharacter === '"' ) && this . isInsideResourceBody ( parents ) ) { insertionContext . context = KnownContexts . resourceBody ; } } return insertionContext ; } public async getCompletionItems ( triggerCharacter , tabSize ) { const tleInfo = this . tleInfo ; const completions = [ ] ; for ( let uniqueScope of this . document . uniqueScopes ) { if ( uniqueScope . parameterValuesSource ) { completions . push ( ... getPropertyValueCompletionItems ( uniqueScope . parameterDefinitionsSource , uniqueScope . parameterValuesSource , uniqueScope . parentWithUniqueParamsVarsAndFunctions . parameterDefinitionsSource , tabSize , this . documentCharacterIndex , triggerCharacter ) ) ; } } if ( ! tleInfo ) { const snippets = await this . getSnippetCompletionItems ( triggerCharacter ) ; if ( snippets . triggerSuggest ) { return snippets ; } else { completions . push ( ... snippets . items ) ; } } else { const tleValue = tleInfo . tleValue ; const scope = tleInfo . scope ; if ( ! tleValue || ! tleValue . contains ( tleInfo . tleCharacterIndex ) ) { if ( TemplatePositionContext . isInsideSquareBrackets ( tleInfo . tleParseResult , tleInfo . tleCharacterIndex ) ) { const replaceSpan = this . emptySpanAtDocumentCharacterIndex ; const functionCompletions = TemplatePositionContext . getFunctionCompletions ( scope , undefined , replaceSpan ) ; const namespaceCompletions = TemplatePositionContext . getNamespaceCompletions ( scope , replaceSpan ) ; completions . push ( ... functionCompletions ) ; completions . push ( ... namespaceCompletions ) ; } } else if ( tleValue instanceof TLE . FunctionCallValue ) { assert ( this . jsonToken ) ; completions . push ( ... this . getFunctionCallCompletions ( tleValue , this . jsonToken ! , tleInfo . tleCharacterIndex , scope ) ) ; } else if ( tleValue instanceof TLE . StringValue ) { completions . push ( ... this . getStringLiteralCompletions ( tleValue , tleInfo . tleCharacterIndex , scope ) ) ; } else if ( tleValue instanceof TLE . PropertyAccess ) { completions . push ( ... this . getPropertyAccessCompletions ( tleValue , tleInfo . tleCharacterIndex , scope ) ) ; } } completions . push ( ... this . getDependsOnCompletionItems ( triggerCharacter ) ) ; return { items : completions } ; } /**
     * Gets the scope at the current position
     */ public getScope ( ) { if ( this . jsonValue && this . document . topLevelValue ) { const objectLineage = < ( Json . ObjectValue | Json . ArrayValue ) [ ] > this . document . topLevelValue ?. findLineage ( this . jsonValue ) ?. filter ( v => v instanceof Json . ObjectValue ) ; const scopes = this . document . allScopes ; for ( const parent of objectLineage . reverse ( ) ) { const innermostMachingScope = scopes . find ( s => s . rootObject === parent ) ; if ( innermostMachingScope ) { return innermostMachingScope ; } } } return this . document . topLevelScope ; } private getDependsOnCompletionItems ( triggerCharacter ) { const insertionContext = this . getInsertionContext ( { triggerCharacter , allowInsideJsonString : true } ) ; if ( insertionContext . context === 'dependson' ) { return getDependsOnCompletions ( this ) ; } return [ ] ; } private async getSnippetCompletionItems ( triggerCharacter ) { const insertionContext = this . getInsertionContext ( { triggerCharacter } ) ; if ( insertionContext . triggerSuggest ) { return { items : [ ] , triggerSuggest : true } ; } else if ( insertionContext . context ) { let replacementInfo = this . getCompletionReplacementSpanInfo ( ) ; const snippets = await ext . snippetManager . value . getSnippetsAsCompletionItems ( insertionContext , replacementInfo . span ?? this . emptySpanAtDocumentCharacterIndex ) ; return { items : snippets } ; } return { items : [ ] } ; } /**
     * Given position in expression is past the left square bracket and before the right square bracket,
     * *or* there is no square bracket yet
     */ private static isInsideSquareBrackets ( parseResult , characterIndex ) { const leftSquareBracketToken = parseResult . leftSquareBracketToken ; const rightSquareBracketToken = parseResult . rightSquareBracketToken ; if ( leftSquareBracketToken && leftSquareBracketToken . span . afterEndIndex <= characterIndex && ( ! rightSquareBracketToken || characterIndex <= rightSquareBracketToken . span . startIndex ) ) { return true ; } return false ; } /**
     * Get completions when we're anywhere inside a string literal
     */ private getStringLiteralCompletions ( tleStringValue , tleCharacterIndex , scope ) { if ( tleStringValue . isParametersArgument ( ) ) { return this . getParameterCompletions ( tleStringValue , tleCharacterIndex , scope ) ; } else if ( tleStringValue . isVariablesArgument ( ) ) { return this . getVariableCompletions ( tleStringValue , tleCharacterIndex , scope ) ; } const funcCall = tleStringValue . getFunctionCallParentOfArgument ( ) ; if ( funcCall ) { assert ( this . jsonToken && this . jsonToken . type === Json . TokenType . QuotedString ) ; const jsonParentStringToken = this . jsonToken ! ; return getResourceIdCompletions ( this , funcCall , jsonParentStringToken ) ; } return [ ] ; } /**
     * Get completions when we're anywhere inside a property access, e.g. "resourceGroup().prop1.prop2"
     */ private getPropertyAccessCompletions ( tleValue , tleCharacterIndex , scope ) { const functionSource = tleValue . functionSource ; if ( functionSource ) { let propertyPrefix = "" ; let replaceSpan = this . emptySpanAtDocumentCharacterIndex ; const propertyNameToken = tleValue . nameToken ; if ( propertyNameToken ) { replaceSpan = propertyNameToken . span . translate ( this . jsonTokenStartIndex ) ; propertyPrefix = propertyNameToken . stringValue . substring ( 0 , tleCharacterIndex - propertyNameToken . span . startIndex ) . toLowerCase ( ) ; } const variableProperty = scope . getVariableDefinitionFromFunctionCall ( functionSource ) ; const parameterProperty = scope . getParameterDefinitionFromFunctionCall ( functionSource ) ; const sourcesNameStack = tleValue . sourcesNameStack ; if ( variableProperty ) { const sourceVariableDefinition = Json . asObjectValue ( variableProperty . value ) ; if ( sourceVariableDefinition ) { return this . getDeepPropertyAccessCompletions ( propertyPrefix , sourceVariableDefinition , sourcesNameStack , replaceSpan ) ; } } else if ( parameterProperty ) { const parameterDefValue = parameterProperty . defaultValue ? Json . asObjectValue ( parameterProperty . defaultValue ) : undefined ; if ( parameterDefValue ) { const sourcePropertyDefinition = Json . asObjectValue ( parameterDefValue . getPropertyValueFromStack ( sourcesNameStack ) ) ; if ( sourcePropertyDefinition ) { return this . getDeepPropertyAccessCompletions ( propertyPrefix , sourcePropertyDefinition , sourcesNameStack , replaceSpan ) ; } } } else if ( sourcesNameStack . length === 0 ) { const functionName = functionSource . name ; if ( functionName && ! functionSource . namespaceToken ) { let functionMetadata = AzureRMAssets . getFunctionMetadataFromName ( functionName ) ; if ( functionMetadata ) { const result = [ ] ; for ( const returnValueMember of functionMetadata . returnValueMembers ) { if ( propertyPrefix === "" || returnValueMember . toLowerCase ( ) . startsWith ( propertyPrefix ) ) { result . push ( TemplatePositionContext . createPropertyCompletionItem ( returnValueMember , replaceSpan ) ) ; } } return result ; } } } } return [ ] ; } /**
     * Return completions when we're anywhere inside a function call expression
     */ private getFunctionCallCompletions ( tleValue , parentStringToken , tleCharacterIndex , scope ) { assert ( tleValue . getSpan ( ) . contains ( tleCharacterIndex , ContainsBehavior . extended ) , "Position should be inside the function call, or right after it" ) ; const completions = [ ] ; const namespaceName = tleValue . namespaceToken ? tleValue . namespaceToken . stringValue : undefined ; const namespace = ( namespaceName && scope . getFunctionNamespaceDefinition ( namespaceName ) ) || undefined ; let tleTokenToComplete ; let completeNamespaces ; let completeBuiltinFunctions ; let completeUserFunctions ; if ( tleValue . nameToken && tleValue . nameToken . span . contains ( tleCharacterIndex , ContainsBehavior . extended ) ) { tleTokenToComplete = tleValue . nameToken ; if ( namespace ) { completeUserFunctions = true ; completeNamespaces = false ; completeBuiltinFunctions = false ; } else { completeNamespaces = true ; completeBuiltinFunctions = true ; completeUserFunctions = false ; } } else if ( namespaceName && tleValue . periodToken && tleValue . periodToken . span . afterEndIndex === tleCharacterIndex ) { if ( ! namespace ) { return [ ] ; } tleTokenToComplete = tleValue . nameToken ; completeNamespaces = false ; completeBuiltinFunctions = false ; completeUserFunctions = true ; } else if ( tleValue . namespaceToken && tleValue . periodToken && tleValue . namespaceToken . span . contains ( tleCharacterIndex , ContainsBehavior . extended ) ) { tleTokenToComplete = tleValue . namespaceToken ; completeNamespaces = true ; completeBuiltinFunctions = true ; completeUserFunctions = false ; } else if ( tleValue . isCallToBuiltinWithName ( templateKeys . parameters ) && tleValue . argumentExpressions . length === 0 ) { return this . getParameterCompletions ( tleValue , tleCharacterIndex , scope ) ; } else if ( tleValue . isCallToBuiltinWithName ( templateKeys . variables ) && tleValue . argumentExpressions . length === 0 ) { return this . getVariableCompletions ( tleValue , tleCharacterIndex , scope ) ; } else { tleTokenToComplete = undefined ; completeNamespaces = true ; completeBuiltinFunctions = true ; completeUserFunctions = false ; } let replaceSpan ; if ( tleTokenToComplete ) { const tokenToCompleteStartIndex = tleTokenToComplete . span . startIndex ; const completionLength = tleCharacterIndex - tokenToCompleteStartIndex ; assert ( completionLength >= 0 ) ; replaceSpan = new Span ( this . jsonTokenStartIndex , completionLength ) . translate ( tleTokenToComplete . span . startIndex ) ; } else { replaceSpan = this . emptySpanAtDocumentCharacterIndex ; } assert ( completeBuiltinFunctions || completeUserFunctions || completeNamespaces , "Should be completing something" ) ; if ( completeBuiltinFunctions || completeUserFunctions ) { if ( completeUserFunctions && namespace ) { const userFunctionCompletions = TemplatePositionContext . getFunctionCompletions ( scope , namespace , replaceSpan ) ; completions . push ( ... userFunctionCompletions ) ; } if ( completeBuiltinFunctions ) { const builtinCompletions = TemplatePositionContext . getFunctionCompletions ( scope , undefined , replaceSpan ) ; completions . push ( ... builtinCompletions ) ; } } if ( completeNamespaces ) { const namespaceCompletions = TemplatePositionContext . getNamespaceCompletions ( scope , replaceSpan ) ; completions . push ( ... namespaceCompletions ) ; } completions . push ( ... getResourceIdCompletions ( this , tleValue , parentStringToken ) ) ; return completions ; } private getDeepPropertyAccessCompletions ( propertyPrefix , variableOrParameterDefinition , sourcesNameStack , replaceSpan ) { const result = [ ] ; const sourcePropertyDefinitionObject = Json . asObjectValue ( variableOrParameterDefinition . getPropertyValueFromStack ( sourcesNameStack ) ) ; if ( sourcePropertyDefinitionObject ) { let matchingPropertyNames ; if ( ! propertyPrefix ) { matchingPropertyNames = sourcePropertyDefinitionObject . propertyNames ; } else { const propertyPrefixLC = propertyPrefix . toLowerCase ( ) ; matchingPropertyNames = [ ] ; for ( const propertyName of sourcePropertyDefinitionObject . propertyNames ) { if ( propertyName . toLowerCase ( ) . startsWith ( propertyPrefixLC ) ) { matchingPropertyNames . push ( propertyName ) ; } } } for ( const matchingPropertyName of matchingPropertyNames ) { result . push ( TemplatePositionContext . createPropertyCompletionItem ( matchingPropertyName , replaceSpan ) ) ; } } return result ; } private static createPropertyCompletionItem ( propertyName , replaceSpan ) { return Completion . Item . fromPropertyName ( propertyName , replaceSpan ) ; } returns public getReferences ( ) { const tleInfo = this . tleInfo ; if ( tleInfo ) { const refInfo = this . getReferenceSiteInfo ( true ) ; if ( refInfo ) { const references = this . document . findReferencesToDefinition ( refInfo . definition ) ; let parameterValuesSource = refInfo . definitionScope && this . getParameterValuesSource ( refInfo . definitionScope ) ; if ( parameterValuesSource ) { const templateReferences = findReferencesToDefinitionInParameterValues ( parameterValuesSource , refInfo . definition ) ; references . addAll ( templateReferences ) ; } return references ; } } return undefined ; } protected getParameterValuesSource ( scope ) { if ( scope instanceof TopLevelTemplateScope ) { if ( this . _associatedDocument instanceof DeploymentParametersDoc ) { return this . _associatedDocument . topLevelParameterValuesSource ; } } return scope . parameterValuesSource ; } /**
     * Returns the definition at the current position, if the current position represents
     * a definition.
     */ private getDefinitionAtSite ( ) { const tleInfo = this . tleInfo ; if ( tleInfo ) { const jsonStringValue = Json . asStringValue ( this . jsonValue ) ; if ( jsonStringValue ) { const unquotedString = jsonStringValue . unquotedValue ; const scope = tleInfo . scope ; const parameterDefinition = scope . getParameterDefinition ( unquotedString ) ; if ( parameterDefinition && parameterDefinition . nameValue === jsonStringValue ) { return parameterDefinition ; } const variableDefinition = scope . getVariableDefinition ( unquotedString ) ; if ( variableDefinition && variableDefinition . nameValue === jsonStringValue ) { return variableDefinition ; } const namespaceDefinition = scope . getFunctionNamespaceDefinition ( unquotedString ) ; if ( namespaceDefinition && namespaceDefinition . nameValue === jsonStringValue ) { return namespaceDefinition ; } for ( let ns of scope . namespaceDefinitions ) { const userFunctionDefinition = scope . getUserFunctionDefinition ( ns . nameValue . unquotedValue , unquotedString ) ; if ( userFunctionDefinition && userFunctionDefinition . nameValue === jsonStringValue ) { return userFunctionDefinition ; } } } } } public getSignatureHelp ( ) { const tleValue = this . tleInfo && this . tleInfo . tleValue ; if ( this . tleInfo && tleValue ) { let functionToHelpWith = TLE . asFunctionCallValue ( tleValue ) ; if ( ! functionToHelpWith ) { functionToHelpWith = TLE . asFunctionCallValue ( tleValue . parent ) ; } if ( functionToHelpWith && functionToHelpWith . name ) { let functionMetadata ; if ( functionToHelpWith . namespaceToken ) { const namespace = functionToHelpWith . namespaceToken . stringValue ; const name = functionToHelpWith . name ; const udfDefinition = this . tleInfo . scope . getUserFunctionDefinition ( namespace , name ) ; functionMetadata = udfDefinition ? UserFunctionMetadata . fromDefinition ( udfDefinition ) : undefined ; } else { functionMetadata = AzureRMAssets . getFunctionMetadataFromName ( functionToHelpWith . name ) ; } if ( functionMetadata ) { let currentArgumentIndex = this . getFunctionCallArgumentIndex ( ) ; if ( typeof currentArgumentIndex === 'number' ) { const functionMetadataParameters = functionMetadata . parameters ; if ( functionMetadataParameters . length > 0 && functionMetadataParameters . length <= currentArgumentIndex && functionMetadataParameters [ functionMetadataParameters . length - 1 ] . name . endsWith ( "..." ) ) { currentArgumentIndex = functionMetadataParameters . length - 1 ; } return new TLE . FunctionSignatureHelp ( currentArgumentIndex , functionMetadata ) ; } } } } return undefined ; } /**
     * Get the index (0-based) of the argument in the given function call, at the current position.
     * Returns -1 if the position is inside the function call, but not inside any of the parameters
     */ public getFunctionCallArgumentIndex ( functionCall ? ) { const tleInfo = this . tleInfo ; const tleValue = tleInfo ?. tleValue ; if ( tleInfo && tleValue ) { if ( ! functionCall ) { functionCall = TLE . asFunctionCallValue ( tleValue ) ; if ( ! functionCall ) { functionCall = TLE . asFunctionCallValue ( tleValue . parent ) ; } } if ( functionCall ) { if ( ! functionCall . leftParenthesisToken || tleInfo . tleCharacterIndex <= functionCall . leftParenthesisToken ?. span . endIndex ) { return - 1 ; } let currentArgumentIndex = 0 ; for ( const commaToken of functionCall . commaTokens ) { if ( commaToken . span . startIndex < tleInfo . tleCharacterIndex ) { ++ currentArgumentIndex ; } } return currentArgumentIndex ; } } return undefined ; } /**
     * Return a list of completions for all functions for a given namespace (return built-in function completions if namespace
     * is undefined).
     */ private static getFunctionCompletions ( scope , namespace , replaceSpan ) { let matches ; if ( namespace ) { matches = namespace . members . map ( fd => UserFunctionMetadata . fromDefinition ( fd ) ) ; } else { matches = AzureRMAssets . getFunctionsMetadata ( ) . functionMetadata ; } return matches . map ( m => Completion . Item . fromFunctionMetadata ( m , replaceSpan ) ) ; } /**
     * Return a list of completions for all user-defined namespaces
     */ private static getNamespaceCompletions ( scope , replaceSpan ) { return scope . namespaceDefinitions . map ( m => Completion . Item . fromNamespaceDefinition ( m , replaceSpan ) ) ; } private getParameterCompletions ( tleValue , tleCharacterIndex , scope ) { const replaceSpanInfo = this . getParameterOrVariableNameReplaceInfo ( tleValue , tleCharacterIndex ) ; const parameterCompletions = [ ] ; if ( replaceSpanInfo ) { for ( const parameterDefinition of scope . parameterDefinitionsSource . parameterDefinitions ) { parameterCompletions . push ( Completion . Item . fromParameterDefinition ( parameterDefinition , replaceSpanInfo . replaceSpan , replaceSpanInfo . includeRightParenthesisInCompletion , replaceSpanInfo . includeSingleQuotesInCompletion ) ) ; } } return parameterCompletions ; } private getVariableCompletions ( tleValue , tleCharacterIndex , scope ) { const replaceSpanInfo = this . getParameterOrVariableNameReplaceInfo ( tleValue , tleCharacterIndex ) ; const variableCompletions = [ ] ; if ( replaceSpanInfo ) { for ( const variableDefinition of scope . variableDefinitions ) { variableCompletions . push ( Completion . Item . fromVariableDefinition ( variableDefinition , replaceSpanInfo . replaceSpan , replaceSpanInfo . includeRightParenthesisInCompletion , replaceSpanInfo . includeSingleQuotesInCompletion ) ) ; } } return variableCompletions ; } private getParameterOrVariableNameReplaceInfo ( tleValue , tleCharacterIndex ) { let includeSingleQuotesInCompletion ; let includeRightParenthesisInCompletion ; let replaceSpan ; if ( tleValue instanceof TLE . StringValue ) { const stringSpan = tleValue . getSpan ( ) ; const functionValue = TLE . asFunctionCallValue ( tleValue . parent ) ; const stringStartIndex = stringSpan . startIndex ; let tleReplaceSpan ; if ( tleCharacterIndex <= stringStartIndex ) { tleReplaceSpan = new Span ( tleCharacterIndex , 0 ) ; includeSingleQuotesInCompletion = true ; includeRightParenthesisInCompletion = false ; } else if ( tleCharacterIndex > tleValue . getSpan ( ) . endIndex ) { return undefined ; } else if ( tleCharacterIndex - stringStartIndex === 1 && tleValue . unquotedValue . startsWith ( `''` ) ) { tleReplaceSpan = new Span ( stringStartIndex , 2 ) ; includeSingleQuotesInCompletion = true ; includeRightParenthesisInCompletion = false ; } else { includeSingleQuotesInCompletion = true ; const rightParenthesisIndex = tleValue . toString ( ) . indexOf ( ")" ) ; const rightSquareBracketIndex = tleValue . toString ( ) . indexOf ( "]" ) ; if ( rightParenthesisIndex >= 0 ) { tleReplaceSpan = new Span ( stringStartIndex , rightParenthesisIndex + 1 ) ; includeRightParenthesisInCompletion = true ; } else if ( rightSquareBracketIndex >= 0 ) { tleReplaceSpan = new Span ( stringStartIndex , rightSquareBracketIndex ) ; includeRightParenthesisInCompletion = true ; } else if ( functionValue && functionValue . rightParenthesisToken && functionValue . argumentExpressions . length === 1 ) { tleReplaceSpan = new Span ( stringStartIndex , functionValue . rightParenthesisToken . span . afterEndIndex - stringStartIndex ) ; includeRightParenthesisInCompletion = true ; } else { includeRightParenthesisInCompletion = ! ! functionValue && functionValue . argumentExpressions . length <= 1 ; tleReplaceSpan = stringSpan ; } } replaceSpan = tleReplaceSpan . translate ( this . jsonTokenStartIndex ) ; } else { includeSingleQuotesInCompletion = true ; if ( tleValue . rightParenthesisToken ) { replaceSpan = new Span ( this . documentCharacterIndex , tleValue . rightParenthesisToken . span . startIndex - tleCharacterIndex + 1 ) ; includeRightParenthesisInCompletion = true ; } else { replaceSpan = this . emptySpanAtDocumentCharacterIndex ; includeRightParenthesisInCompletion = true ; } } if ( includeRightParenthesisInCompletion ) { assert ( includeSingleQuotesInCompletion , "includeSingleQuotesInCompletion required if includeRightParenthesisInCompletion" ) ; } return { includeRightParenthesisInCompletion , replaceSpan : replaceSpan , includeSingleQuotesInCompletion } ; } } interface ITleReplaceSpanInfo { /** If true, the completion should add single quotes around the completion (for a parameter or variable name). If single
     * quotes already exist, they should be included in the replace span.
     */ includeSingleQuotesInCompletion : boolean ; /** If true, the completion should add a closing parenthesis to the completion (if an existing closing
     * parenthesis exists, it should be included in the replacement span, which will have the effect of moving the
     * cursor to after the closing parenthesis upon completion)
     */ includeRightParenthesisInCompletion : boolean ; /**
     * Replacement span to use for completions
     */ replaceSpan : Span ; } interface ITleInfo { /**
     * The parse result of the enclosing string, if we're inside a string (it's with an expression or not)
     */ tleParseResult : TLE . TleParseResult ; /**
     * The index inside the enclosing string, if we're inside a string (whether it's an expression or not)
     */ tleCharacterIndex : number ; /**
     * The outermost TLE value enclosing the current position, if we're inside a string
     * (whether it's an expression or not). This can undefined if inside square brackets but before
     * an expression, etc.
     */ tleValue : TLE . Value | undefined ; }