diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index aa3097debad..54b5b48da2b 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2427,16 +2427,6 @@ namespace FourSlash { } } - public verifyIsInMultiLineComment(negative: boolean) { - const expected = !negative; - const position = this.currentCaretPosition; - const fileName = this.activeFile.fileName; - const actual = this.languageService.getIsInMultiLineComment(fileName, position); - if (expected !== actual) { - this.raiseError(`verifyIsInDocComment failed: at position '${position}' in '${fileName}', expected '${expected}'.`); - } - } - private clarifyNewlines(str: string) { return str.replace(/\r?\n/g, lineEnding => { const representation = lineEnding === "\r\n" ? "CRLF" : "LF"; @@ -2510,6 +2500,16 @@ namespace FourSlash { } } + public verifyisInMultiLineCommentAtPosition(negative: boolean) { + const expected = !negative; + const position = this.currentCaretPosition; + const fileName = this.activeFile.fileName; + const actual = this.languageService.getisInMultiLineCommentAtPosition(fileName, position); + if (expected !== actual) { + this.raiseError(`verifyIsInDocComment failed: at position '${position}' in '${fileName}', expected '${expected}'.`); + } + } + /* Check number of navigationItems which match both searchValue and matchKind, if a filename is passed in, limit the results to that file. @@ -3583,12 +3583,12 @@ namespace FourSlashInterface { this.state.verifyBraceCompletionAtPosition(this.negative, openingBrace); } - public codeFixAvailable() { - this.state.verifyCodeFixAvailable(this.negative); + public isInMultiLineCommentAtPosition() { + this.state.verifyisInMultiLineCommentAtPosition(this.negative); } - public isInMultiLineComment() { - this.state.verifyIsInMultiLineComment(this.negative); + public codeFixAvailable() { + this.state.verifyCodeFixAvailable(this.negative); } public applicableRefactorAvailableAtMarker(markerName: string) { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index c67dde5f67b..c3a09b3926f 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -483,12 +483,12 @@ namespace Harness.LanguageService { getDocCommentTemplateAtPosition(fileName: string, position: number): ts.TextInsertion { return unwrapJSONCallResult(this.shim.getDocCommentTemplateAtPosition(fileName, position)); } - getIsInMultiLineComment(fileName: string, position: number): boolean { - return unwrapJSONCallResult(this.shim.getIsInMultiLineComment(fileName, position)); - } isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { return unwrapJSONCallResult(this.shim.isValidBraceCompletionAtPosition(fileName, position, openingBrace)); } + getisInMultiLineCommentAtPosition(fileName: string, position: number): boolean { + return unwrapJSONCallResult(this.shim.getisInMultiLineCommentAtPosition(fileName, position)); + } getCodeFixesAtPosition(): ts.CodeAction[] { throw new Error("Not supported on the shim."); } diff --git a/src/server/client.ts b/src/server/client.ts index 69727a18e60..5a162c1041d 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -672,11 +672,11 @@ namespace ts.server { return notImplemented(); } - getIsInMultiLineComment(_fileName: string, _position: number): boolean { + isValidBraceCompletionAtPosition(_fileName: string, _position: number, _openingBrace: number): boolean { return notImplemented(); } - isValidBraceCompletionAtPosition(_fileName: string, _position: number, _openingBrace: number): boolean { + getisInMultiLineCommentAtPosition(_fileName: string, _position: number): boolean { return notImplemented(); } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 1cfe617ca72..299ba4fceed 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -8,6 +8,7 @@ namespace ts.server.protocol { /* @internal */ BraceFull = "brace-full", BraceCompletion = "braceCompletion", + isInMultiLineComment = "isInMultiLineComment", Change = "change", Close = "close", Completions = "completions", @@ -85,7 +86,6 @@ namespace ts.server.protocol { TodoComments = "todoComments", Indentation = "indentation", DocCommentTemplate = "docCommentTemplate", - IsInMultiLineComment = "isInMultiLineComment", /* @internal */ CompilerOptionsDiagnosticsFull = "compilerOptionsDiagnostics-full", /* @internal */ @@ -242,15 +242,8 @@ namespace ts.server.protocol { /** * A request to determine if the caret is inside a multi-line comment. */ - export interface IsInMultiLineCommentRequest extends FileLocationRequest { - command: CommandTypes.IsInMultiLineComment; - } - - /** - * Response for TodoCommentRequest request. - */ - export interface IsInMultiLineCommentResponse extends Response { - body?: { isInMultiLineComment: boolean }; + export interface IsInMultiLineCommentAtPositionRequest extends FileLocationRequest { + command: CommandTypes.isInMultiLineComment; } /** diff --git a/src/server/session.ts b/src/server/session.ts index abebdb5fae9..e4e900ddce7 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -961,11 +961,11 @@ namespace ts.server { return project.getLanguageService(/*ensureSynchronized*/ false).getDocCommentTemplateAtPosition(file, position); } - private getIsInMultiLineComment(args: protocol.FileLocationRequestArgs) { + private getisInMultiLineCommentAtPosition(args: protocol.FileLocationRequestArgs) { const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); const scriptInfo = project.getScriptInfoForNormalizedPath(file); const position = this.getPosition(args, scriptInfo); - return project.getLanguageService(/*ensureSynchronized*/ false).getIsInMultiLineComment(file, position); + return project.getLanguageService(/*ensureSynchronized*/ false).getisInMultiLineCommentAtPosition(file, position); } private getIndentation(args: protocol.IndentationRequestArgs) { @@ -1701,8 +1701,8 @@ namespace ts.server { [CommandNames.DocCommentTemplate]: (request: protocol.DocCommentTemplateRequest) => { return this.requiredResponse(this.getDocCommentTemplate(request.arguments)); }, - [CommandNames.IsInMultiLineComment]: (request: protocol.IsInMultiLineCommentRequest) => { - return this.requiredResponse(this.getIsInMultiLineComment(request.arguments)); + [CommandNames.isInMultiLineComment]: (request: protocol.IsInMultiLineCommentAtPositionRequest) => { + return this.requiredResponse(this.getisInMultiLineCommentAtPosition(request.arguments)); }, [CommandNames.Format]: (request: protocol.FormatRequest) => { return this.requiredResponse(this.getFormattingEditsForRange(request.arguments)); diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index 531b768f6d8..fa49ee852b1 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -1117,6 +1117,27 @@ namespace ts.formatting { } } + /** + * @returns -1 iff the position is not in a multi-line comment. + */ + export function getIndentationOfEnclosingMultiLineComment(sourceFile: SourceFile, position: number): number { + const token = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); + const leadingCommentRanges = getLeadingCommentRangesOfNode(token, sourceFile); + if (leadingCommentRanges) { + loop: for (const range of leadingCommentRanges) { + // We need to extend the range when in an unclosed multi-line comment. + if (range.pos < position && (position < range.end || position === range.end && position === sourceFile.getFullWidth())) { + if (range.kind === SyntaxKind.MultiLineCommentTrivia) { + return range.pos - getLineStartPositionForPosition(range.pos, sourceFile); + } + break loop; + } + } + } + return -1; + } + + function getOpenTokenForList(node: Node, list: Node[]) { switch (node.kind) { case SyntaxKind.Constructor: diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts index 18b0479c85a..8acab0e6603 100644 --- a/src/services/formatting/smartIndenter.ts +++ b/src/services/formatting/smartIndenter.ts @@ -38,12 +38,17 @@ namespace ts.formatting { // no indentation in string \regex\template literals const precedingTokenIsLiteral = isStringOrRegularExpressionOrTemplateLiteral(precedingToken.kind); - if (precedingTokenIsLiteral && precedingToken.getStart(sourceFile) <= position && precedingToken.end > position) { + if (precedingTokenIsLiteral && precedingToken.getStart(sourceFile) <= position && position < precedingToken.end) { return 0; } const lineAtPosition = sourceFile.getLineAndCharacterOfPosition(position).line; + const indentationOfEnclosingMultiLineComment = getIndentationOfEnclosingMultiLineComment(sourceFile, position); + if (indentationOfEnclosingMultiLineComment !== -1) { + return indentationOfEnclosingMultiLineComment; + } + // indentation is first non-whitespace character in a previous line // for block indentation, we should look for a line which contains something that's not // whitespace. diff --git a/src/services/services.ts b/src/services/services.ts index 1f85ad427f5..ab398db7c35 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1789,21 +1789,6 @@ namespace ts { return JsDoc.getDocCommentTemplateAtPosition(getNewLineOrDefaultFromHost(host), syntaxTreeCache.getCurrentSourceFile(fileName), position); } - function getIsInMultiLineComment(_fileName: string, position: number): boolean { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(_fileName); - const token = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); - const leadingCommentRanges = getLeadingCommentRangesOfNode(token, sourceFile); - - for (const range of leadingCommentRanges) { - // We need to extend the range when in an unclosed multi-line comment. - if (range.pos < position && (position < range.end || position === range.end && position === sourceFile.getFullWidth())) { - return range.kind === SyntaxKind.MultiLineCommentTrivia; - } - } - - return false; - } - function isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { // '<' is currently not supported, figuring out if we're in a Generic Type vs. a comparison is too // expensive to do during typing scenarios @@ -1833,6 +1818,11 @@ namespace ts { return true; } + function getisInMultiLineCommentAtPosition(fileName: string, position: number): boolean { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + return ts.formatting.getIndentationOfEnclosingMultiLineComment(sourceFile, position) !== -1; + } + function getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] { // Note: while getting todo comments seems like a syntactic operation, we actually // treat it as a semantic operation here. This is because we expect our host to call @@ -2054,8 +2044,8 @@ namespace ts { getFormattingEditsForDocument, getFormattingEditsAfterKeystroke, getDocCommentTemplateAtPosition, - getIsInMultiLineComment, isValidBraceCompletionAtPosition, + getisInMultiLineCommentAtPosition, getCodeFixesAtPosition, getEmitOutput, getNonBoundSourceFile, diff --git a/src/services/shims.ts b/src/services/shims.ts index 3ce553d552e..75c5e8445c1 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -247,11 +247,6 @@ namespace ts { */ getDocCommentTemplateAtPosition(fileName: string, position: number): string; - /** - * Returns JSON-encoded value of the type TextInsertion. - */ - getIsInMultiLineComment(fileName: string, position: number): string; - /** * Returns JSON-encoded boolean to indicate whether we should support brace location * at the current position. @@ -259,6 +254,11 @@ namespace ts { */ isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): string; + /** + * Returns JSON-encoded boolean to indicate whether the caret at the current position is in a multi-line comment. + */ + getisInMultiLineCommentAtPosition(fileName: string, position: number): string; + getEmitOutput(fileName: string): string; getEmitOutputObject(fileName: string): EmitOutput; } @@ -840,6 +840,14 @@ namespace ts { ); } + /// GET IS IN MULTI-LINE COMMENT + public getisInMultiLineCommentAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getisInMultiLineCommentAtPosition('${fileName}', ${position})`, + () => this.languageService.getisInMultiLineCommentAtPosition(fileName, position) + ); + } + /// GET SMART INDENT public getIndentationAtPosition(fileName: string, position: number, options: string /*Services.EditorOptions*/): string { return this.forwardJSONCall( @@ -940,13 +948,6 @@ namespace ts { ); } - public getIsInMultiLineComment(fileName: string, position: number): string { - return this.forwardJSONCall( - `getIsInMultiLineComment('${fileName}', ${position})`, - () => this.languageService.getIsInMultiLineComment(fileName, position) - ); - } - /// NAVIGATE TO /** Return a list of symbols that are interesting to navigate to */ diff --git a/src/services/types.ts b/src/services/types.ts index 62d2db1b65c..3a7a14d3dbc 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -257,10 +257,11 @@ namespace ts { getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; - getIsInMultiLineComment(fileName: string, position: number): boolean; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; + getisInMultiLineCommentAtPosition(fileName: string, position: number): boolean; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[]; getRefactorCodeActions(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string): CodeAction[] | undefined; diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 3984a074fd9..96cc152f0ff 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -149,8 +149,8 @@ declare namespace FourSlashInterface { typeDefinitionCountIs(expectedCount: number): void; implementationListIsEmpty(): void; isValidBraceCompletionAtPosition(openingBrace?: string): void; + isInMultiLineCommentAtPosition(): void; codeFixAvailable(): void; - isInMultiLineComment(): void; applicableRefactorAvailableAtMarker(markerName: string): void; codeFixDiagnosticsAvailableAtMarkers(markerNames: string[], diagnosticCode?: number): void; applicableRefactorAvailableForRange(): void; diff --git a/tests/cases/fourslash/isInMultiLineComment.ts b/tests/cases/fourslash/isInMultiLineComment.ts index ebcac7215f9..39aac71a8bd 100644 --- a/tests/cases/fourslash/isInMultiLineComment.ts +++ b/tests/cases/fourslash/isInMultiLineComment.ts @@ -8,29 +8,29 @@ for (let i = 1; i < 7; ++i) { goTo.position(i); - verify.isInMultiLineComment(); + verify.isInMultiLineCommentAtPosition(); } for (let i = 0; i < 2; ++i) { goTo.position(i * 7); - verify.not.isInMultiLineComment(); + verify.not.isInMultiLineCommentAtPosition(); } const jsDocStart = 8; for (let i = 1; i < 8; ++i) { goTo.position(jsDocStart + i); - verify.isInMultiLineComment(); + verify.isInMultiLineCommentAtPosition(); } for (let i = 0; i < 2; ++i) { goTo.position(jsDocStart + i * 8); - verify.not.isInMultiLineComment(); + verify.not.isInMultiLineCommentAtPosition(); } const singleLineCommentStart = 17; for (let i = 0; i < 5; ++i) { goTo.position(singleLineCommentStart + i); - verify.not.isInMultiLineComment(); + verify.not.isInMultiLineCommentAtPosition(); } diff --git a/tests/cases/fourslash/isInMultiLineCommentEOF.ts b/tests/cases/fourslash/isInMultiLineCommentEOF.ts index 2e34264b1f6..2b65a958340 100644 --- a/tests/cases/fourslash/isInMultiLineCommentEOF.ts +++ b/tests/cases/fourslash/isInMultiLineCommentEOF.ts @@ -8,5 +8,5 @@ for (let i = 0; i < 4; ++i) { goTo.marker(`${i}`); - verify.isInMultiLineComment(); + verify.isInMultiLineCommentAtPosition(); } \ No newline at end of file