diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 05d36e5c196ec..fda1ddd67f159 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -570,8 +570,8 @@ namespace ts { getFullyQualifiedName, getResolvedSignature: (node, candidatesOutArray, argumentCount) => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal), - getResolvedSignatureForStringLiteralCompletions: (node, candidatesOutArray, argumentCount, argumentIndex) => - getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal, argumentIndex), + getResolvedSignatureForStringLiteralCompletions: (node, candidatesOutArray, editingArgument) => + getResolvedSignatureWorker(node, candidatesOutArray, /*argumentCount*/ undefined, CheckMode.Normal, editingArgument), getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp), getExpandedParameters, @@ -741,29 +741,37 @@ namespace ts { getMemberOverrideModifierStatus, }; - function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode, argumentIndex?: number): Signature | undefined { + function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode, editingArgument?: Node): Signature | undefined { let node = getParseTreeNode(nodeIn, isCallLikeExpression); - if (node && argumentIndex !== undefined) { + if (node && editingArgument !== undefined) { const replacementArg = setParentRecursive(factory.createAsExpression(factory.createStringLiteral(""), factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword)), /*incremental*/ false); switch (node.kind) { case SyntaxKind.CallExpression: - node = factory.updateCallExpression(node, node.expression, node.typeArguments, [ - ...node.arguments.slice(0, argumentIndex), - replacementArg, - ...node.arguments.slice(argumentIndex + 1), - ]); + node = factory.updateCallExpression(node, node.expression, node.typeArguments, node.arguments.map(arg => { + return arg === editingArgument ? replacementArg : arg; + })); + setParent(replacementArg, node); break; case SyntaxKind.NewExpression: - node = factory.updateNewExpression(node, node.expression, node.typeArguments, [ - ...node.arguments?.slice(0, argumentIndex) || emptyArray, - replacementArg, - ...node.arguments?.slice(argumentIndex + 1) || emptyArray, - ]); + node = factory.updateNewExpression(node, node.expression, node.typeArguments, map(node.arguments, arg => { + return arg === editingArgument ? replacementArg : arg; + })); + setParent(replacementArg, node); + break; + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxSelfClosingElement: + const replacementExpr = factory.createJsxExpression(/*dotDotDotToken*/ undefined, replacementArg); + node = factory.updateJsxOpeningLikeElement(node, node.tagName, node.typeArguments, factory.updateJsxAttributes(node.attributes, map(node.attributes.properties, attr => { + return tryCast(attr, isJsxAttribute)?.initializer === editingArgument + ? setParent(factory.updateJsxAttribute(attr as JsxAttribute, (attr as JsxAttribute).name, setParent(replacementExpr, attr)), attr.parent) + : attr; + }))); + setParent(replacementArg, replacementExpr); + setParent(node.attributes, node); break; default: Debug.failBadSyntaxKind(node); } - setParent(replacementArg, node); setParent(node, nodeIn.parent); } apparentArgumentCount = argumentCount; diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index 633b4c2c5cd3e..c22b803af7e86 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -398,6 +398,7 @@ namespace ts { updateJsxSelfClosingElement, createJsxOpeningElement, updateJsxOpeningElement, + updateJsxOpeningLikeElement, createJsxClosingElement, updateJsxClosingElement, createJsxFragment, @@ -4833,6 +4834,16 @@ namespace ts { : node; } + // @api + function updateJsxOpeningLikeElement(node: JsxOpeningLikeElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes): JsxOpeningLikeElement { + if (node.kind === SyntaxKind.JsxOpeningElement) { + return updateJsxOpeningElement(node, tagName, typeArguments, attributes); + } + else { + return updateJsxSelfClosingElement(node, tagName, typeArguments, attributes); + } + } + // @api function createJsxClosingElement(tagName: JsxTagNameExpression) { const node = createBaseNode(SyntaxKind.JsxClosingElement); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 55c715e0249ff..4c59aaf657890 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4278,7 +4278,7 @@ namespace ts { * @param argumentCount Apparent number of arguments, passed in case of a possibly incomplete call. This should come from an ArgumentListInfo. See `signatureHelp.ts`. */ getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined; - /* @internal */ getResolvedSignatureForStringLiteralCompletions(node: CallExpression | NewExpression, candidatesOutArray: Signature[], argumentCount: number, argumentIndex: number): Signature | undefined; + /* @internal */ getResolvedSignatureForStringLiteralCompletions(node: CallLikeExpression, candidatesOutArray: Signature[], editingArgument: Node): Signature | undefined; /* @internal */ getResolvedSignatureForSignatureHelp(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined; /* @internal */ getExpandedParameters(sig: Signature): readonly (readonly Symbol[])[]; /* @internal */ hasEffectiveRestParameter(sig: Signature): boolean; @@ -7626,6 +7626,7 @@ namespace ts { updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes): JsxSelfClosingElement; createJsxOpeningElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes): JsxOpeningElement; updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes): JsxOpeningElement; + updateJsxOpeningLikeElement(node: JsxOpeningLikeElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes): JsxOpeningLikeElement; createJsxClosingElement(tagName: JsxTagNameExpression): JsxClosingElement; updateJsxClosingElement(node: JsxClosingElement, tagName: JsxTagNameExpression): JsxClosingElement; createJsxFragment(openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment): JsxFragment; diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index d5afce79187b2..12e2f31491d79 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -210,12 +210,13 @@ namespace ts.Completions.StringCompletions { case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: + case SyntaxKind.JsxAttribute: if (!isRequireCallArgument(node) && !isImportCall(parent)) { - const argumentInfo = SignatureHelp.getArgumentInfoForCompletions(node, position, sourceFile); + const argumentInfo = SignatureHelp.getArgumentInfoForCompletions(parent.kind === SyntaxKind.JsxAttribute ? parent.parent : node, position, sourceFile); // Get string literal completions from specialized signatures of the target // i.e. declare function f(a: 'A'); // f("/*completion position*/") - return argumentInfo ? getStringLiteralCompletionsFromSignature(parent as CallExpression | NewExpression, argumentInfo, typeChecker) : fromContextualType(); + return argumentInfo ? getStringLiteralCompletionsFromSignature(argumentInfo, node, typeChecker) : fromContextualType(); } // falls through (is `require("")` or `require(""` or `import("")`) @@ -257,12 +258,12 @@ namespace ts.Completions.StringCompletions { type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined); } - function getStringLiteralCompletionsFromSignature(node: CallExpression | NewExpression, argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes { + function getStringLiteralCompletionsFromSignature(argumentInfo: SignatureHelp.ArgumentInfoForCompletions, arg: StringLiteralLike, checker: TypeChecker): StringLiteralCompletionsFromTypes { let isNewIdentifier = false; const uniques = new Map(); const candidates: Signature[] = []; - checker.getResolvedSignatureForStringLiteralCompletions(node, candidates, argumentInfo.argumentCount, argumentInfo.argumentIndex); + checker.getResolvedSignatureForStringLiteralCompletions(argumentInfo.invocation, candidates, arg); const types = flatMap(candidates, candidate => { if (!signatureHasRestParameter(candidate) && argumentInfo.argumentCount > candidate.parameters.length) return; const type = candidate.getTypeParameterAtPosition(argumentInfo.argumentIndex);