private getParameterOrVariableNameReplaceInfo()

in src/documents/positionContexts/TemplatePositionContext.ts [1039:1161]


    private getParameterOrVariableNameReplaceInfo(tleValue: TLE.StringValue | TLE.FunctionCallValue, tleCharacterIndex: number): ITleReplaceSpanInfo | undefined {
        // Note: We icnclude closing parenthesis and single quote in the replacement span and the insertion text,
        // so that the cursor ends up after them once the replacement happens. This way the user can immediately start
        // typing the rest of the expression after the parameters call.

        // Also note that, unlike with function name replacements, we replace the entire string argument if the cursor is anywhere inside
        // the existing string, not just the part of it before the cursor.

        let includeSingleQuotesInCompletion: boolean;
        let includeRightParenthesisInCompletion: boolean;
        let replaceSpan: Span;

        if (tleValue instanceof TLE.StringValue) {
            // parameters(<CURSOR> 'xxyy') or parameters('xx<CURSOR>yy')  - tleValue is the string literal

            const stringSpan: Span = tleValue.getSpan();
            const functionValue: TLE.FunctionCallValue | undefined = TLE.asFunctionCallValue(tleValue.parent);
            const stringStartIndex = stringSpan.startIndex;
            let tleReplaceSpan: Span;

            if (tleCharacterIndex <= stringStartIndex) {
                // The cursor is before the beginning of the string (or right at the open quote, which means it's not yet
                //   inside the string) - just insert the completion text, don't replace anything existing (the user may be
                //   trying to add to the expression before the cursor if this is an existing expression or string)
                //
                // Example:  "['Microsoft.web/sites']" -> "[concat(parameters(<CURSOR>'Microsoft.web/sites']"
                // Desired replacement should *not* be to replace 'Microsoft.web/sites' with the parameter name, but rather to
                //   insert it: "[concat(parameters('parameter1''Microsoft.web/sites']"
                ///  ... because the user intends to keep typing to finish the expression
                //   this way: "[concat(parameters('parameter1'), 'Microsoft.web/sites')]"

                tleReplaceSpan = new Span(tleCharacterIndex, 0);
                includeSingleQuotesInCompletion = true;
                includeRightParenthesisInCompletion = false;
            } else if (tleCharacterIndex > tleValue.getSpan().endIndex) {
                // Cursor is after the string - no replacements
                return undefined;
            } else if (tleCharacterIndex - stringStartIndex === 1 && tleValue.unquotedValue.startsWith(`''`)) {
                // Special case - image user has
                //
                //   "[resourceId(<CURSOR>'Microsoft.Network/virtualNetworks', parameters('subnet2Name'))]"
                //
                // At the cursor they want to add "parameters('sku')" as a new first argument to resourceId, without
                // deleting the existing 'Microsoft.Network/virtualNetworks' string, which will become the second argument
                // after inserting.
                //
                // They type "parameters('" (note the initial single quote).  VS Code immediately adds the closing single
                // quote, so they have:
                //
                //   "[resourceId('<CURSOR>''Microsoft.Network/virtualNetworks', parameters('subnet2Name'))]"
                //
                // when the completion request comes through here.  Since two single quotes inside a string are an escape
                // for a single quote, the entire "'''Microsoft.Network/virtualNetworks'" string is tokenized as a single
                // string literal, which means we would normally replace the entire string.  Instead, special case this as
                // if it were two strings and only replace the first two single quotes, leaving the existing string alone.

                tleReplaceSpan = new Span(stringStartIndex, 2);
                includeSingleQuotesInCompletion = true;
                includeRightParenthesisInCompletion = false;
            } else {
                // The cursor is inside the string - replace the entire parameter, including the closing single
                //   quote and parenthesis, so that the cursor will end up after the parameters call

                includeSingleQuotesInCompletion = true;

                // If the string is not properly closed with an ending single quote, the parser may pick up the
                // closing parenthesis and bracket in the string.  It's very unlikely these were meant to be part of
                // a parameter/variable name, so cut off our replacement at this point, in order to prevent data in
                // the string that the user probably wants to keep around after making the completion.
                // This can happen for instance if they're turning an existing string into an expression or adding
                // to the front of an existing expression.
                //
                // Example:   "[concat(parameters('p1<CURSOR>)Microsoft.web/sites']"
                const rightParenthesisIndex: number = tleValue.toString().indexOf(")");
                const rightSquareBracketIndex: number = tleValue.toString().indexOf("]");
                if (rightParenthesisIndex >= 0) {
                    // Cut off before the ending parenthesis
                    tleReplaceSpan = new Span(stringStartIndex, rightParenthesisIndex + 1);
                    includeRightParenthesisInCompletion = true;
                } else if (rightSquareBracketIndex >= 0) {
                    // Cut off before the ending square bracket
                    tleReplaceSpan = new Span(stringStartIndex, rightSquareBracketIndex);
                    includeRightParenthesisInCompletion = true;
                } else if (functionValue && functionValue.rightParenthesisToken && functionValue.argumentExpressions.length === 1) {
                    // The parameters or variables function includes a right parenthesis already

                    tleReplaceSpan = new Span(stringStartIndex, functionValue.rightParenthesisToken.span.afterEndIndex - stringStartIndex);
                    includeRightParenthesisInCompletion = true;
                } else {
                    // The parameters or variables function call does not yet include a right parenthesis, just replace the string

                    includeRightParenthesisInCompletion = !!functionValue && functionValue.argumentExpressions.length <= 1;
                    tleReplaceSpan = stringSpan;
                }
            }

            replaceSpan = tleReplaceSpan.translate(this.jsonTokenStartIndex);
        } else {
            // parameters(<CURSOR>)        - tleValue is the parameters/variables call

            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
        };
    }