From f76f10bd4e561f950fbd1ca5ed35c196b78d0de0 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 31 May 2018 15:34:01 -0700 Subject: [PATCH 1/2] Add autoCloseTag language service --- src/compiler/parser.ts | 42 +++++++++---------- src/harness/fourslash.ts | 12 ++++++ src/harness/harnessLanguageService.ts | 3 ++ src/harness/unittests/session.ts | 1 + src/server/client.ts | 4 ++ src/server/protocol.ts | 12 ++++++ src/server/session.ts | 10 +++++ src/services/services.ts | 13 ++++++ src/services/types.ts | 1 + .../reference/api/tsserverlibrary.d.ts | 12 ++++++ tests/baselines/reference/api/typescript.d.ts | 1 + tests/cases/fourslash/autoCloseTag.ts | 20 +++++++++ tests/cases/fourslash/fourslash.ts | 1 + 13 files changed, 111 insertions(+), 21 deletions(-) create mode 100644 tests/cases/fourslash/autoCloseTag.ts diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index ea5a2a339372a..6374dbb5179e7 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4147,27 +4147,6 @@ namespace ts { return finishNode(node); } - function tagNamesAreEquivalent(lhs: JsxTagNameExpression, rhs: JsxTagNameExpression): boolean { - if (lhs.kind !== rhs.kind) { - return false; - } - - if (lhs.kind === SyntaxKind.Identifier) { - return (lhs).escapedText === (rhs).escapedText; - } - - if (lhs.kind === SyntaxKind.ThisKeyword) { - return true; - } - - // If we are at this statement then we must have PropertyAccessExpression and because tag name in Jsx element can only - // take forms of JsxTagNameExpression which includes an identifier, "this" expression, or another propertyAccessExpression - // it is safe to case the expression property as such. See parseJsxElementName for how we parse tag name in Jsx element - return (lhs).name.escapedText === (rhs).name.escapedText && - tagNamesAreEquivalent((lhs).expression as JsxTagNameExpression, (rhs).expression as JsxTagNameExpression); - } - - function parseJsxElementOrSelfClosingElementOrFragment(inExpressionContext: boolean): JsxElement | JsxSelfClosingElement | JsxFragment { const opening = parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext); let result: JsxElement | JsxSelfClosingElement | JsxFragment; @@ -7906,4 +7885,25 @@ namespace ts { } return argMap; } + + /** @internal */ + export function tagNamesAreEquivalent(lhs: JsxTagNameExpression, rhs: JsxTagNameExpression): boolean { + if (lhs.kind !== rhs.kind) { + return false; + } + + if (lhs.kind === SyntaxKind.Identifier) { + return (lhs).escapedText === (rhs).escapedText; + } + + if (lhs.kind === SyntaxKind.ThisKeyword) { + return true; + } + + // If we are at this statement then we must have PropertyAccessExpression and because tag name in Jsx element can only + // take forms of JsxTagNameExpression which includes an identifier, "this" expression, or another propertyAccessExpression + // it is safe to case the expression property as such. See parseJsxElementName for how we parse tag name in Jsx element + return (lhs).name.escapedText === (rhs).name.escapedText && + tagNamesAreEquivalent((lhs).expression as JsxTagNameExpression, (rhs).expression as JsxTagNameExpression); + } } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index f6214d4c71967..6c3163253ed46 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2743,6 +2743,14 @@ Actual: ${stringify(fullActual)}`); } } + public verifyAutoCloseTag(map: { [markerName: string]: string | undefined }): void { + for (const markerName in map) { + this.goToMarker(markerName); + const actual = this.languageService.getAutoCloseTagAtPosition(this.activeFile.fileName, this.currentCaretPosition); + assert.equal(actual, map[markerName]); + } + } + public verifyMatchingBracePosition(bracePosition: number, expectedMatchPosition: number) { const actual = this.languageService.getBraceMatchingAtPosition(this.activeFile.fileName, bracePosition); @@ -4079,6 +4087,10 @@ namespace FourSlashInterface { this.state.verifyBraceCompletionAtPosition(this.negative, openingBrace); } + public autoCloseTag(map: { [markerName: string]: string | undefined }): void { + this.state.verifyAutoCloseTag(map); + } + public isInCommentAtPosition(onlyMultiLineDiverges?: boolean) { this.state.verifySpanOfEnclosingComment(this.negative, onlyMultiLineDiverges); } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 8d1322abfd7c3..4d2c85e4301e2 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -509,6 +509,9 @@ namespace Harness.LanguageService { isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { return unwrapJSONCallResult(this.shim.isValidBraceCompletionAtPosition(fileName, position, openingBrace)); } + getAutoCloseTagAtPosition(): string | undefined { + throw new Error("Not supported on the shim."); + } getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): ts.TextSpan { return unwrapJSONCallResult(this.shim.getSpanOfEnclosingComment(fileName, position, onlyMultiLine)); } diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index 5e0b44fb5f0c7..b990658643ed0 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -188,6 +188,7 @@ namespace ts.server { describe("onMessage", () => { const allCommandNames: CommandNames[] = [ + CommandNames.AutoCloseTag, CommandNames.Brace, CommandNames.BraceFull, CommandNames.BraceCompletion, diff --git a/src/server/client.ts b/src/server/client.ts index a7e76fb06a897..ae4ecf3378128 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -552,6 +552,10 @@ namespace ts.server { return notImplemented(); } + getAutoCloseTagAtPosition(_fileName: string, _position: number): string | undefined { + return notImplemented(); + } + getSpanOfEnclosingComment(_fileName: string, _position: number, _onlyMultiLine: boolean): TextSpan { return notImplemented(); } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 9c34130bdef86..032319607f323 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -6,6 +6,7 @@ namespace ts.server.protocol { // NOTE: If updating this, be sure to also update `allCommandNames` in `harness/unittests/session.ts`. export const enum CommandTypes { + AutoCloseTag = "autoCloseTag", Brace = "brace", /* @internal */ BraceFull = "brace-full", @@ -890,6 +891,17 @@ namespace ts.server.protocol { openingBrace: string; } + export interface AutoCloseTagRequest extends FileLocationRequest { + readonly command: CommandTypes.AutoCloseTag; + readonly arguments: AutoCloseTagRequestArgs; + } + + export interface AutoCloseTagRequestArgs extends FileLocationRequestArgs {} + + export interface AutoCloseTagResponse extends Response { + readonly body: TextInsertion; + } + /** * @deprecated * Get occurrences request; value of command field is diff --git a/src/server/session.ts b/src/server/session.ts index 191982a7cd444..bd4fe4fd6bf76 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -818,6 +818,13 @@ namespace ts.server { return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSuggestionDiagnostics(file), !!args.includeLinePosition); } + private getAutoCloseTag(args: protocol.AutoCloseTagRequestArgs): TextInsertion | undefined { + const { file, project } = this.getFileAndProject(args); + const position = this.getPositionInFile(args, file); + const tag = project.getLanguageService().getAutoCloseTagAtPosition(file, position); + return tag === undefined ? undefined : { newText: tag, caretOffset: 0 }; + } + private getDocumentHighlights(args: protocol.DocumentHighlightsRequestArgs, simplifiedResult: boolean): ReadonlyArray | ReadonlyArray { const { file, project } = this.getFileAndProject(args); const position = this.getPositionInFile(args, file); @@ -2130,6 +2137,9 @@ namespace ts.server { this.projectService.reloadProjects(); return this.notRequired(); }, + [CommandNames.AutoCloseTag]: (request: protocol.AutoCloseTagRequest) => { + return this.requiredResponse(this.getAutoCloseTag(request.arguments)); + }, [CommandNames.GetCodeFixes]: (request: protocol.CodeFixRequest) => { return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ true)); }, diff --git a/src/services/services.ts b/src/services/services.ts index 7d5e4ec0a870f..4a7716739fde9 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2051,6 +2051,18 @@ namespace ts { return true; } + function getAutoCloseTagAtPosition(fileName: string, position: number): string | undefined { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const token = findPrecedingToken(position, sourceFile); + if (!token) return undefined; + const element = token.kind === SyntaxKind.GreaterThanToken + ? isJsxOpeningElement(token.parent) ? token.parent.parent : undefined + : isJsxText(token) ? token.parent : undefined; + if (element && !tagNamesAreEquivalent(element.openingElement.tagName, element.closingElement.tagName)) { + return ``; + } + } + function getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined { const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); const range = formatting.getRangeOfEnclosingComment(sourceFile, position, onlyMultiLine); @@ -2283,6 +2295,7 @@ namespace ts { getFormattingEditsAfterKeystroke, getDocCommentTemplateAtPosition, isValidBraceCompletionAtPosition, + getAutoCloseTagAtPosition, getSpanOfEnclosingComment, getCodeFixesAtPosition, getCombinedCodeFix, diff --git a/src/services/types.ts b/src/services/types.ts index 4dcd59d22b554..7550c1069b38f 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -323,6 +323,7 @@ namespace ts { getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion | undefined; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; + getAutoCloseTagAtPosition(fileName: string, position: number): string | undefined; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index a6657b4d3410f..b9fd695eb4a16 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4556,6 +4556,7 @@ declare namespace ts { getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion | undefined; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; + getAutoCloseTagAtPosition(fileName: string, position: number): string | undefined; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined; toLineColumnOffset?(fileName: string, position: number): LineAndCharacter; getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings, preferences: UserPreferences): ReadonlyArray; @@ -5506,6 +5507,7 @@ declare namespace ts.server { */ declare namespace ts.server.protocol { enum CommandTypes { + AutoCloseTag = "autoCloseTag", Brace = "brace", BraceCompletion = "braceCompletion", GetSpanOfEnclosingComment = "getSpanOfEnclosingComment", @@ -6175,6 +6177,15 @@ declare namespace ts.server.protocol { */ openingBrace: string; } + interface AutoCloseTagRequest extends FileLocationRequest { + readonly command: CommandTypes.AutoCloseTag; + readonly arguments: AutoCloseTagRequestArgs; + } + interface AutoCloseTagRequestArgs extends FileLocationRequestArgs { + } + interface AutoCloseTagResponse extends Response { + readonly body: TextInsertion; + } /** * @deprecated * Get occurrences request; value of command field is @@ -8463,6 +8474,7 @@ declare namespace ts.server { private getSyntacticDiagnosticsSync; private getSemanticDiagnosticsSync; private getSuggestionDiagnosticsSync; + private getAutoCloseTag; private getDocumentHighlights; private setCompilerOptionsForInferredProjects; private getProjectInfo; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index d71bb5117baef..fe6b4304e170c 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4556,6 +4556,7 @@ declare namespace ts { getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion | undefined; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; + getAutoCloseTagAtPosition(fileName: string, position: number): string | undefined; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined; toLineColumnOffset?(fileName: string, position: number): LineAndCharacter; getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings, preferences: UserPreferences): ReadonlyArray; diff --git a/tests/cases/fourslash/autoCloseTag.ts b/tests/cases/fourslash/autoCloseTag.ts new file mode 100644 index 0000000000000..40a5de028e68e --- /dev/null +++ b/tests/cases/fourslash/autoCloseTag.ts @@ -0,0 +1,20 @@ +/// + +// @Filename: /a.tsx +////const x =
/*0*/; +////const x =
foo/*1*/
; +////const x =
/*2*/; +////const x =
/*3*/; +////const x =
+////

/*4*/ +////

+////

; +////const x =
text /*5*/; + +verify.autoCloseTag({ + 0: "
", + 1: undefined, + 2: undefined, + 3: undefined, + 4: "

", +}); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index de1ca0afd8cee..0db5b6da087a9 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -174,6 +174,7 @@ declare namespace FourSlashInterface { typeDefinitionCountIs(expectedCount: number): void; implementationListIsEmpty(): void; isValidBraceCompletionAtPosition(openingBrace?: string): void; + autoCloseTag(map: { [markerName: string]: string | undefined }): void; isInCommentAtPosition(onlyMultiLineDiverges?: boolean): void; codeFix(options: { description: string, From f7247f76767d9844715664954b68701030c7ee4b Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 1 Jun 2018 09:43:44 -0700 Subject: [PATCH 2/2] Change name to getJsxClosingTagAtPosition and return an object --- src/harness/fourslash.ts | 10 ++++---- src/harness/harnessLanguageService.ts | 2 +- src/harness/unittests/session.ts | 2 +- src/server/client.ts | 2 +- src/server/protocol.ts | 12 +++++----- src/server/session.ts | 10 ++++---- src/services/services.ts | 9 ++++---- src/services/types.ts | 10 +++++++- .../reference/api/tsserverlibrary.d.ts | 23 ++++++++++++------- tests/baselines/reference/api/typescript.d.ts | 9 +++++++- tests/cases/fourslash/autoCloseTag.ts | 6 ++--- tests/cases/fourslash/fourslash.ts | 2 +- 12 files changed, 59 insertions(+), 38 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 6c3163253ed46..4bb6fee3bd290 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2743,11 +2743,11 @@ Actual: ${stringify(fullActual)}`); } } - public verifyAutoCloseTag(map: { [markerName: string]: string | undefined }): void { + public verifyJsxClosingTag(map: { [markerName: string]: ts.JsxClosingTagInfo | undefined }): void { for (const markerName in map) { this.goToMarker(markerName); - const actual = this.languageService.getAutoCloseTagAtPosition(this.activeFile.fileName, this.currentCaretPosition); - assert.equal(actual, map[markerName]); + const actual = this.languageService.getJsxClosingTagAtPosition(this.activeFile.fileName, this.currentCaretPosition); + assert.deepEqual(actual, map[markerName]); } } @@ -4087,8 +4087,8 @@ namespace FourSlashInterface { this.state.verifyBraceCompletionAtPosition(this.negative, openingBrace); } - public autoCloseTag(map: { [markerName: string]: string | undefined }): void { - this.state.verifyAutoCloseTag(map); + public jsxClosingTag(map: { [markerName: string]: ts.JsxClosingTagInfo | undefined }): void { + this.state.verifyJsxClosingTag(map); } public isInCommentAtPosition(onlyMultiLineDiverges?: boolean) { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 4d2c85e4301e2..b0a0f1f249eca 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -509,7 +509,7 @@ namespace Harness.LanguageService { isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { return unwrapJSONCallResult(this.shim.isValidBraceCompletionAtPosition(fileName, position, openingBrace)); } - getAutoCloseTagAtPosition(): string | undefined { + getJsxClosingTagAtPosition(): never { throw new Error("Not supported on the shim."); } getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): ts.TextSpan { diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index b990658643ed0..cdb6e818d7b26 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -188,7 +188,6 @@ namespace ts.server { describe("onMessage", () => { const allCommandNames: CommandNames[] = [ - CommandNames.AutoCloseTag, CommandNames.Brace, CommandNames.BraceFull, CommandNames.BraceCompletion, @@ -225,6 +224,7 @@ namespace ts.server { CommandNames.Occurrences, CommandNames.DocumentHighlights, CommandNames.DocumentHighlightsFull, + CommandNames.JsxClosingTag, CommandNames.Open, CommandNames.Quickinfo, CommandNames.QuickinfoFull, diff --git a/src/server/client.ts b/src/server/client.ts index ae4ecf3378128..e797cadb8ca01 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -552,7 +552,7 @@ namespace ts.server { return notImplemented(); } - getAutoCloseTagAtPosition(_fileName: string, _position: number): string | undefined { + getJsxClosingTagAtPosition(_fileName: string, _position: number): never { return notImplemented(); } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 032319607f323..be1a6247fb0bc 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -6,7 +6,7 @@ namespace ts.server.protocol { // NOTE: If updating this, be sure to also update `allCommandNames` in `harness/unittests/session.ts`. export const enum CommandTypes { - AutoCloseTag = "autoCloseTag", + JsxClosingTag = "jsxClosingTag", Brace = "brace", /* @internal */ BraceFull = "brace-full", @@ -891,14 +891,14 @@ namespace ts.server.protocol { openingBrace: string; } - export interface AutoCloseTagRequest extends FileLocationRequest { - readonly command: CommandTypes.AutoCloseTag; - readonly arguments: AutoCloseTagRequestArgs; + export interface JsxClosingTagRequest extends FileLocationRequest { + readonly command: CommandTypes.JsxClosingTag; + readonly arguments: JsxClosingTagRequestArgs; } - export interface AutoCloseTagRequestArgs extends FileLocationRequestArgs {} + export interface JsxClosingTagRequestArgs extends FileLocationRequestArgs {} - export interface AutoCloseTagResponse extends Response { + export interface JsxClosingTagResponse extends Response { readonly body: TextInsertion; } diff --git a/src/server/session.ts b/src/server/session.ts index bd4fe4fd6bf76..c728bd2a850b9 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -818,11 +818,11 @@ namespace ts.server { return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSuggestionDiagnostics(file), !!args.includeLinePosition); } - private getAutoCloseTag(args: protocol.AutoCloseTagRequestArgs): TextInsertion | undefined { + private getJsxClosingTag(args: protocol.JsxClosingTagRequestArgs): TextInsertion | undefined { const { file, project } = this.getFileAndProject(args); const position = this.getPositionInFile(args, file); - const tag = project.getLanguageService().getAutoCloseTagAtPosition(file, position); - return tag === undefined ? undefined : { newText: tag, caretOffset: 0 }; + const tag = project.getLanguageService().getJsxClosingTagAtPosition(file, position); + return tag === undefined ? undefined : { newText: tag.newText, caretOffset: 0 }; } private getDocumentHighlights(args: protocol.DocumentHighlightsRequestArgs, simplifiedResult: boolean): ReadonlyArray | ReadonlyArray { @@ -2137,8 +2137,8 @@ namespace ts.server { this.projectService.reloadProjects(); return this.notRequired(); }, - [CommandNames.AutoCloseTag]: (request: protocol.AutoCloseTagRequest) => { - return this.requiredResponse(this.getAutoCloseTag(request.arguments)); + [CommandNames.JsxClosingTag]: (request: protocol.JsxClosingTagRequest) => { + return this.requiredResponse(this.getJsxClosingTag(request.arguments)); }, [CommandNames.GetCodeFixes]: (request: protocol.CodeFixRequest) => { return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ true)); diff --git a/src/services/services.ts b/src/services/services.ts index 4a7716739fde9..257ffb1c26296 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2051,15 +2051,14 @@ namespace ts { return true; } - function getAutoCloseTagAtPosition(fileName: string, position: number): string | undefined { + function getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined { const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); const token = findPrecedingToken(position, sourceFile); if (!token) return undefined; - const element = token.kind === SyntaxKind.GreaterThanToken - ? isJsxOpeningElement(token.parent) ? token.parent.parent : undefined + const element = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningElement(token.parent) ? token.parent.parent : isJsxText(token) ? token.parent : undefined; if (element && !tagNamesAreEquivalent(element.openingElement.tagName, element.closingElement.tagName)) { - return ``; + return { newText: `` }; } } @@ -2295,7 +2294,7 @@ namespace ts { getFormattingEditsAfterKeystroke, getDocCommentTemplateAtPosition, isValidBraceCompletionAtPosition, - getAutoCloseTagAtPosition, + getJsxClosingTagAtPosition, getSpanOfEnclosingComment, getCodeFixesAtPosition, getCombinedCodeFix, diff --git a/src/services/types.ts b/src/services/types.ts index 7550c1069b38f..1f65bc67b9737 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -323,7 +323,11 @@ namespace ts { getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion | undefined; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; - getAutoCloseTagAtPosition(fileName: string, position: number): string | undefined; + /** + * This will return a defined result if the position is after the `>` of the opening tag, or somewhere in the text, of a JSXElement with no closing tag. + * Editors should call this after `>` is typed. + */ + getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined; @@ -360,6 +364,10 @@ namespace ts { dispose(): void; } + export interface JsxClosingTagInfo { + readonly newText: string; + } + export interface CombinedCodeFixScope { type: "file"; fileName: string; } export type OrganizeImportsScope = CombinedCodeFixScope; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index b9fd695eb4a16..faabb3caa8c5f 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4556,7 +4556,11 @@ declare namespace ts { getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion | undefined; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; - getAutoCloseTagAtPosition(fileName: string, position: number): string | undefined; + /** + * This will return a defined result if the position is after the `>` of the opening tag, or somewhere in the text, of a JSXElement with no closing tag. + * Editors should call this after `>` is typed. + */ + getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined; toLineColumnOffset?(fileName: string, position: number): LineAndCharacter; getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings, preferences: UserPreferences): ReadonlyArray; @@ -4578,6 +4582,9 @@ declare namespace ts { getProgram(): Program | undefined; dispose(): void; } + interface JsxClosingTagInfo { + readonly newText: string; + } interface CombinedCodeFixScope { type: "file"; fileName: string; @@ -5507,7 +5514,7 @@ declare namespace ts.server { */ declare namespace ts.server.protocol { enum CommandTypes { - AutoCloseTag = "autoCloseTag", + JsxClosingTag = "jsxClosingTag", Brace = "brace", BraceCompletion = "braceCompletion", GetSpanOfEnclosingComment = "getSpanOfEnclosingComment", @@ -6177,13 +6184,13 @@ declare namespace ts.server.protocol { */ openingBrace: string; } - interface AutoCloseTagRequest extends FileLocationRequest { - readonly command: CommandTypes.AutoCloseTag; - readonly arguments: AutoCloseTagRequestArgs; + interface JsxClosingTagRequest extends FileLocationRequest { + readonly command: CommandTypes.JsxClosingTag; + readonly arguments: JsxClosingTagRequestArgs; } - interface AutoCloseTagRequestArgs extends FileLocationRequestArgs { + interface JsxClosingTagRequestArgs extends FileLocationRequestArgs { } - interface AutoCloseTagResponse extends Response { + interface JsxClosingTagResponse extends Response { readonly body: TextInsertion; } /** @@ -8474,7 +8481,7 @@ declare namespace ts.server { private getSyntacticDiagnosticsSync; private getSemanticDiagnosticsSync; private getSuggestionDiagnosticsSync; - private getAutoCloseTag; + private getJsxClosingTag; private getDocumentHighlights; private setCompilerOptionsForInferredProjects; private getProjectInfo; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index fe6b4304e170c..4e8e30a5a63cb 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4556,7 +4556,11 @@ declare namespace ts { getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion | undefined; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; - getAutoCloseTagAtPosition(fileName: string, position: number): string | undefined; + /** + * This will return a defined result if the position is after the `>` of the opening tag, or somewhere in the text, of a JSXElement with no closing tag. + * Editors should call this after `>` is typed. + */ + getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined; toLineColumnOffset?(fileName: string, position: number): LineAndCharacter; getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings, preferences: UserPreferences): ReadonlyArray; @@ -4578,6 +4582,9 @@ declare namespace ts { getProgram(): Program | undefined; dispose(): void; } + interface JsxClosingTagInfo { + readonly newText: string; + } interface CombinedCodeFixScope { type: "file"; fileName: string; diff --git a/tests/cases/fourslash/autoCloseTag.ts b/tests/cases/fourslash/autoCloseTag.ts index 40a5de028e68e..ba828f207a24d 100644 --- a/tests/cases/fourslash/autoCloseTag.ts +++ b/tests/cases/fourslash/autoCloseTag.ts @@ -11,10 +11,10 @@ ////

; ////const x =
text /*5*/; -verify.autoCloseTag({ - 0: "
", +verify.jsxClosingTag({ + 0: { newText: "
" }, 1: undefined, 2: undefined, 3: undefined, - 4: "

", + 4: { newText: "

" }, }); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 0db5b6da087a9..a104e2fde0fdc 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -174,7 +174,7 @@ declare namespace FourSlashInterface { typeDefinitionCountIs(expectedCount: number): void; implementationListIsEmpty(): void; isValidBraceCompletionAtPosition(openingBrace?: string): void; - autoCloseTag(map: { [markerName: string]: string | undefined }): void; + jsxClosingTag(map: { [markerName: string]: { readonly newText: string } | undefined }): void; isInCommentAtPosition(onlyMultiLineDiverges?: boolean): void; codeFix(options: { description: string,