import { assert } from '../../fixed_assert' ; import { FunctionSignatureHelp } from '../../language/expressions/TLE' ; import { INamedDefinition } from '../../language/INamedDefinition' ; import * as Json from "../../language/json/JSON" ; import { LineColPos } from '../../language/LineColPos' ; 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 { __debugMarkPositionInString } from "../../util/debugMarkStrings" ; import { InitializeBeforeUse } from "../../util/InitializeBeforeUse" ; import { nonNullValue } from "../../util/nonNull" ; import * as Completion from "../../vscodeIntegration/Completion" ; import { IHoverInfo } from '../../vscodeIntegration/IHoverInfo' ; import { UsageInfoHoverInfo } from "../../vscodeIntegration/UsageInfoHoverInfo" ; import { DeploymentDocument as DeploymentDocument } from "../DeploymentDocument" ; import { IJsonDocument } from '../templates/IJsonDocument' ; import { TemplateScope } from '../templates/scopes/TemplateScope' ; export enum ReferenceSiteKind { definition = "definition" , reference = "reference" } export interface ICompletionItemsResult { items : Completion . Item [ ] ; triggerSuggest ? : boolean ; } /**
 * Information about a reference site (function call, parameter reference, etc.), or to
 * a definition itself (function definition, parameter definition, etc.)
 */ export interface IReferenceSite { referenceKind : ReferenceSiteKind ; /**
     * Where the reference or definition occurs in the template, without any quotes
     */ unquotedReferenceSpan : Span ; /**
     * The document that contains the reference
     */ referenceDocument : IJsonDocument ; /**
     * The definition that the reference refers to
     */ definition : INamedDefinition ; definitionScope : TemplateScope | undefined ; /**
     * The document that contains the definition
     */ definitionDocument : IJsonDocument ; } /**
 * (Abstract base class) Represents a position inside the snapshot of a deployment file, plus all related information
 * that can be parsed and analyzed about it from that position.
 */ export abstract class PositionContext { private _documentPosition : InitializeBeforeUse < LineColPos > = new InitializeBeforeUse < LineColPos > ( "_documentPosition" ) ; private _documentCharacterIndex : InitializeBeforeUse < number > = new InitializeBeforeUse < number > ( "_documentCharacterIndex" ) ; private _jsonToken : CachedValue < Json . Token | undefined > = new CachedValue < Json . Token > ( ) ; private _jsonValue : CachedValue < Json . Value | undefined > = new CachedValue < Json . Value | undefined > ( ) ; protected constructor ( private _document , protected _associatedDocument ) { nonNullValue ( this . _document , "document" ) ; } protected initFromDocumentLineAndColumnIndices ( documentLineIndex , documentColumnIndex , allowOutOfBounds = true ) { nonNullValue ( documentLineIndex , "documentLineIndex" ) ; assert ( documentLineIndex >= 0 , "documentLineIndex cannot be negative" ) ; nonNullValue ( documentColumnIndex , "documentColumnIndex" ) ; assert ( documentColumnIndex >= 0 , "documentColumnIndex cannot be negative" ) ; if ( documentLineIndex >= this . _document . lineCount ) { assert ( allowOutOfBounds , `documentLineIndex cannot be greater than or equal to the deployment template's line count` ) ; documentLineIndex = this . _document . lineCount - 1 ; } if ( documentColumnIndex > this . _document . getMaxColumnIndex ( documentLineIndex ) ) { assert ( allowOutOfBounds , `documentColumnIndex cannot be greater than the line's maximum index` ) ; documentColumnIndex = this . _document . getMaxColumnIndex ( documentLineIndex ) ; } this . _documentPosition . value = new LineColPos ( documentLineIndex , documentColumnIndex ) ; this . _documentCharacterIndex . value = this . _document . getDocumentCharacterIndex ( documentLineIndex , documentColumnIndex , { allowOutOfBounds } ) ; } protected initFromDocumentCharacterIndex ( documentCharacterIndex , allowOutOfBounds = true ) { nonNullValue ( documentCharacterIndex , "documentCharacterIndex" ) ; assert ( documentCharacterIndex >= 0 , "documentCharacterIndex cannot be negative" ) ; if ( documentCharacterIndex > this . _document . maxCharacterIndex ) { assert ( allowOutOfBounds , `documentCharacterIndex cannot be greater than the maximum character index` ) ; documentCharacterIndex = this . _document . maxCharacterIndex ; } this . _documentCharacterIndex . value = documentCharacterIndex ; this . _documentPosition . value = this . _document . getDocumentPosition ( documentCharacterIndex ) ; } public get document ( ) : DeploymentDocument { return this . _document ; } protected get associatedDocument ( ) : DeploymentDocument | undefined { return this . _associatedDocument ; } /**
     * Convenient way of seeing what this object represents in the debugger, shouldn't be used for production code
     */ public get __debugDisplay ( ) : string { let docText = this . _document . documentText ; return __debugMarkPositionInString ( docText , this . documentCharacterIndex , "<CURSOR>" ) ; } /**
     * Convenient way of seeing what this object represents in the debugger, shouldn't be used for production code
     */ public get __debugFullDisplay ( ) : string { let docText = this . _document . documentText ; return __debugMarkPositionInString ( docText , this . documentCharacterIndex , "<CURSOR>" , Number . MAX_SAFE_INTEGER , Number . MAX_SAFE_INTEGER ) ; } public get documentPosition ( ) : LineColPos { return this . _documentPosition . value ; } public get documentLineIndex ( ) : number { return this . documentPosition . line ; } public get documentColumnIndex ( ) : number { return this . documentPosition . column ; } public get documentCharacterIndex ( ) : number { return this . _documentCharacterIndex . value ; } public get jsonToken ( ) : Json . Token | undefined { return this . _jsonToken . getOrCacheValue ( ( ) => { return this . _document . getJSONTokenAtDocumentCharacterIndex ( this . documentCharacterIndex ) ; } ) ; } public get jsonValue ( ) : Json . Value | undefined { return this . _jsonValue . getOrCacheValue ( ( ) => { return this . _document . getJSONValueAtDocumentCharacterIndex ( this . documentCharacterIndex , ContainsBehavior . extended ) ; } ) ; } public get jsonTokenStartIndex ( ) : number { assert ( ! ! this . jsonToken , "The jsonTokenStartIndex can only be requested when the PositionContext is inside a JSONToken." ) ; return this . jsonToken ! . span . startIndex ; } public get emptySpanAtDocumentCharacterIndex ( ) : Span { return new Span ( this . documentCharacterIndex , 0 ) ; } /**
     * Gets the "word" at the cursor or right after the cursor position (i.e.,
     * the token that the cursor is "touching"), to indicate the span that
     * an Intellisense completion should replace
     */ public getCompletionReplacementSpanInfo ( ) { const index = this . documentCharacterIndex ; let tokenAtCursor = this . document . getJSONTokenAtDocumentCharacterIndex ( index ) ; if ( ! tokenAtCursor && index > 0 ) { const tokenAfterCursor = this . document . getJSONTokenAtDocumentCharacterIndex ( index - 1 ) ; if ( tokenAfterCursor ) { const line = this . document . getDocumentPosition ( tokenAfterCursor . span . startIndex ) . line ; if ( line === this . documentPosition . line ) { tokenAtCursor = tokenAfterCursor ; } } } if ( tokenAtCursor && tokenAtCursor . type !== Json . TokenType . QuotedString ) { let start = tokenAtCursor . span . startIndex ; const documentText = this . document . documentText ; while ( start > 0 && documentText . charAt ( start - 1 ) . match ( /^[\w-!\$]/ ) ) { -- start ; } const match = this . document . documentText . slice ( start ) . match ( /^[\w-!\$]+/ ) ; return { span : match ? new Span ( start , match [ 0 ] . length ) : undefined , token : tokenAtCursor } ; } return { span : tokenAtCursor ?. span , token : tokenAtCursor } ; } /**
     * 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 abstract getReferenceSiteInfo ( includeDefinition ) ; returns public abstract getReferences ( ) ; public getHoverInfo ( ) { const infos = [ ] ; const reference = this . getReferenceSiteInfo ( false ) ; if ( reference ) { const span = reference . unquotedReferenceSpan ; const definition = reference . definition ; infos . push ( new UsageInfoHoverInfo ( definition . definitionKind , definition . usageInfo , span ) ) ; } return infos ; } public abstract getCompletionItems ( triggerCharacter , tabSize ) ; public abstract getSignatureHelp ( ) ; /**
     * Determines the snippet insertion context at this location (i.e., the type of snippets which are appropriate to insert here,
     * the span of insertion, etc.)
     */ public getInsertionContext ( options ) { const triggerCharacter = options . triggerCharacter ; const allowInsideJsonString = ! ! options . allowInsideJsonString ; if ( ! this . document . topLevelValue ) { return { context : KnownContexts . emptyDocument , parents : [ ] } ; } const insideJsonString = this . jsonToken ?. type === Json . TokenType . QuotedString ; let parents = [ ] ; let insertionParent = this . getInsertionParent ( ) ; if ( insideJsonString && ! insertionParent && allowInsideJsonString ) { const pcAtStartOfString = this . document . getContextFromDocumentCharacterIndex ( this . jsonTokenStartIndex , this . _associatedDocument ) ; insertionParent = pcAtStartOfString . getInsertionParent ( ) ; } if ( insertionParent ) { const lineage = this . document . topLevelValue . findLineage ( insertionParent ) ; assert ( lineage , `Couldn't find JSON value inside the top-level value: ${ insertionParent . toFullFriendlyString ( ) }` ) ; parents = lineage . reverse ( ) ; parents . unshift ( insertionParent ) ; if ( ( ! triggerCharacter || triggerCharacter === '"' ) && parents [ 0 ] instanceof Json . ObjectValue && parents [ 1 ] instanceof Json . Property ) { const parentPropertyName = parents [ 1 ] . propertyName ?. toLowerCase ( ) ; return { context : parentPropertyName , parents , insideJsonString } ; } if ( ( ( insideJsonString && allowInsideJsonString ) || ! insideJsonString ) && ( ! triggerCharacter || triggerCharacter === '"' ) && parents [ 0 ] instanceof Json . ArrayValue && parents [ 1 ] instanceof Json . Property ) { const parentPropertyName = parents [ 1 ] . propertyName ?. toLowerCase ( ) ; return { context : parentPropertyName , parents , insideJsonString } ; } if ( ! insideJsonString && parents [ 0 ] instanceof Json . ObjectValue && parents [ 1 ] instanceof Json . ArrayValue && parents [ 2 ] instanceof Json . Property && parents [ 0 ] . properties . length === 0 ) { if ( triggerCharacter === '{' ) { return { context : undefined , triggerSuggest : true , parents , insideJsonString } ; } else if ( ! triggerCharacter ) { const parentPropertyName = parents [ 2 ] . propertyName ?. toLowerCase ( ) ; return { context : parentPropertyName , curlyBraces : insertionParent . span , parents , insideJsonString } ; } } } return { context : undefined , parents , insideJsonString } ; } public get isInsideComment ( ) : boolean { return ! ! this . document . jsonParseResult . getCommentTokenAtDocumentIndex ( this . documentCharacterIndex , ContainsBehavior . enclosed ) ; } public getInsertionParent ( ) { const enclosingJsonValue = this . document . jsonParseResult . getValueAtCharacterIndex ( this . documentCharacterIndex , ContainsBehavior . enclosed ) ; if ( ! ( enclosingJsonValue instanceof Json . ObjectValue || enclosingJsonValue instanceof Json . ArrayValue ) ) { return undefined ; } if ( this . isInsideComment ) { return undefined ; } return enclosingJsonValue ; } /**
     * Gets the nearest enclosing parent (array or object) at the current position
     */ public getEnclosingParent ( ) { const enclosingJsonValue = this . document . jsonParseResult . getValueAtCharacterIndex ( this . documentCharacterIndex , ContainsBehavior . enclosed ) ; if ( enclosingJsonValue ) { const lineage = this . document . topLevelValue ?. findLineage ( enclosingJsonValue ) ?? [ ] ; const lineageWithoutProperties = < ( Json . ArrayValue | Json . ObjectValue ) [ ] > lineage . filter ( l => ! ( l instanceof Json . Property ) ) ; return lineageWithoutProperties [ lineage . length - 1 ] ; } return undefined ; } }