Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 (<Identifier>lhs).escapedText === (<Identifier>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 (<PropertyAccessExpression>lhs).name.escapedText === (<PropertyAccessExpression>rhs).name.escapedText &&
tagNamesAreEquivalent((<PropertyAccessExpression>lhs).expression as JsxTagNameExpression, (<PropertyAccessExpression>rhs).expression as JsxTagNameExpression);
}


function parseJsxElementOrSelfClosingElementOrFragment(inExpressionContext: boolean): JsxElement | JsxSelfClosingElement | JsxFragment {
const opening = parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext);
let result: JsxElement | JsxSelfClosingElement | JsxFragment;
Expand Down Expand Up @@ -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 (<Identifier>lhs).escapedText === (<Identifier>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 (<PropertyAccessExpression>lhs).name.escapedText === (<PropertyAccessExpression>rhs).name.escapedText &&
tagNamesAreEquivalent((<PropertyAccessExpression>lhs).expression as JsxTagNameExpression, (<PropertyAccessExpression>rhs).expression as JsxTagNameExpression);
}
}
12 changes: 12 additions & 0 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);
}
Expand Down
3 changes: 3 additions & 0 deletions src/harness/harnessLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
1 change: 1 addition & 0 deletions src/harness/unittests/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ namespace ts.server {

describe("onMessage", () => {
const allCommandNames: CommandNames[] = [
CommandNames.AutoCloseTag,
CommandNames.Brace,
CommandNames.BraceFull,
CommandNames.BraceCompletion,
Expand Down
4 changes: 4 additions & 0 deletions src/server/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
12 changes: 12 additions & 0 deletions src/server/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<protocol.DocumentHighlightsItem> | ReadonlyArray<DocumentHighlights> {
const { file, project } = this.getFileAndProject(args);
const position = this.getPositionInFile(args, file);
Expand Down Expand Up @@ -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));
},
Expand Down
13 changes: 13 additions & 0 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2051,6 +2051,18 @@ namespace ts {
return true;
}

function getAutoCloseTagAtPosition(fileName: string, position: number): string | undefined {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would make the return type an object with a newText: string property to allow us to add more info as we need in the future.

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 `</${element.openingElement.tagName.getText(sourceFile)}>`;
}
}

function getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
const range = formatting.getRangeOfEnclosingComment(sourceFile, position, onlyMultiLine);
Expand Down Expand Up @@ -2283,6 +2295,7 @@ namespace ts {
getFormattingEditsAfterKeystroke,
getDocCommentTemplateAtPosition,
isValidBraceCompletionAtPosition,
getAutoCloseTagAtPosition,
getSpanOfEnclosingComment,
getCodeFixesAtPosition,
getCombinedCodeFix,
Expand Down
1 change: 1 addition & 0 deletions src/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would rather this had JSX in the name.. like getJSXClosingTagAtPosition also we need to add a comment on it when it should be called.


getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined;

Expand Down
12 changes: 12 additions & 0 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>, formatOptions: FormatCodeSettings, preferences: UserPreferences): ReadonlyArray<CodeFixAction>;
Expand Down Expand Up @@ -5506,6 +5507,7 @@ declare namespace ts.server {
*/
declare namespace ts.server.protocol {
enum CommandTypes {
AutoCloseTag = "autoCloseTag",
Brace = "brace",
BraceCompletion = "braceCompletion",
GetSpanOfEnclosingComment = "getSpanOfEnclosingComment",
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -8463,6 +8474,7 @@ declare namespace ts.server {
private getSyntacticDiagnosticsSync;
private getSemanticDiagnosticsSync;
private getSuggestionDiagnosticsSync;
private getAutoCloseTag;
private getDocumentHighlights;
private setCompilerOptionsForInferredProjects;
private getProjectInfo;
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>, formatOptions: FormatCodeSettings, preferences: UserPreferences): ReadonlyArray<CodeFixAction>;
Expand Down
20 changes: 20 additions & 0 deletions tests/cases/fourslash/autoCloseTag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/// <reference path='fourslash.ts' />

// @Filename: /a.tsx
////const x = <div>/*0*/;
////const x = <div> foo/*1*/ </div>;
////const x = <div></div>/*2*/;
////const x = <div/>/*3*/;
////const x = <div>
//// <p>/*4*/
//// </div>
////</p>;
////const x = <div> text /*5*/;

verify.autoCloseTag({
0: "</div>",
1: undefined,
2: undefined,
3: undefined,
4: "</p>",
});
1 change: 1 addition & 0 deletions tests/cases/fourslash/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down