diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index c77d267471a06..f1a8dfd676b70 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -647,7 +647,7 @@ namespace ts { } export function getLeadingCommentRangesOfNode(node: Node, sourceFileOfNode: SourceFile) { - return getLeadingCommentRanges(sourceFileOfNode.text, node.pos); + return node.kind !== SyntaxKind.JsxText ? getLeadingCommentRanges(sourceFileOfNode.text, node.pos) : undefined; } export function getJSDocCommentRanges(node: Node, text: string) { diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 2b28ecbbfec21..32c9fb67da0d0 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2508,6 +2508,23 @@ namespace FourSlash { } } + public verifySpanOfEnclosingComment(negative: boolean, onlyMultiLineDiverges?: boolean) { + const expected = !negative; + const position = this.currentCaretPosition; + const fileName = this.activeFile.fileName; + const actual = !!this.languageService.getSpanOfEnclosingComment(fileName, position, /*onlyMultiLine*/ false); + const actualOnlyMultiLine = !!this.languageService.getSpanOfEnclosingComment(fileName, position, /*onlyMultiLine*/ true); + if (expected !== actual || onlyMultiLineDiverges === (actual === actualOnlyMultiLine)) { + this.raiseError(`verifySpanOfEnclosingComment failed: + position: '${position}' + fileName: '${fileName}' + onlyMultiLineDiverges: '${onlyMultiLineDiverges}' + actual: '${actual}' + actualOnlyMultiLine: '${actualOnlyMultiLine}' + expected: '${expected}'.`); + } + } + /* Check number of navigationItems which match both searchValue and matchKind, if a filename is passed in, limit the results to that file. @@ -3648,6 +3665,10 @@ namespace FourSlashInterface { this.state.verifyBraceCompletionAtPosition(this.negative, openingBrace); } + public isInCommentAtPosition(onlyMultiLineDiverges?: boolean) { + this.state.verifySpanOfEnclosingComment(this.negative, onlyMultiLineDiverges); + } + public codeFixAvailable() { this.state.verifyCodeFixAvailable(this.negative); } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 5e8d6e76716e8..e7d1c99deed3a 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -487,6 +487,9 @@ namespace Harness.LanguageService { isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { return unwrapJSONCallResult(this.shim.isValidBraceCompletionAtPosition(fileName, position, openingBrace)); } + getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): ts.TextSpan { + return unwrapJSONCallResult(this.shim.getSpanOfEnclosingComment(fileName, position, onlyMultiLine)); + } getCodeFixesAtPosition(): ts.CodeAction[] { throw new Error("Not supported on the shim."); } diff --git a/src/server/client.ts b/src/server/client.ts index a7e0615dce866..4acd862a89a24 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -527,6 +527,10 @@ namespace ts.server { return notImplemented(); } + getSpanOfEnclosingComment(_fileName: string, _position: number, _onlyMultiLine: boolean): TextSpan { + return notImplemented(); + } + getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: number[]): CodeAction[] { const args: protocol.CodeFixRequestArgs = { ...this.createFileRangeRequestArgs(file, start, end), errorCodes }; diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 0b7405c7b6986..f50c873112671 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", + GetSpanOfEnclosingComment = "getSpanOfEnclosingComment", Change = "change", Close = "close", Completions = "completions", @@ -241,6 +242,21 @@ namespace ts.server.protocol { body?: TodoComment[]; } + /** + * A request to determine if the caret is inside a comment. + */ + export interface SpanOfEnclosingCommentRequest extends FileLocationRequest { + command: CommandTypes.GetSpanOfEnclosingComment; + arguments: SpanOfEnclosingCommentRequestArgs; + } + + export interface SpanOfEnclosingCommentRequestArgs extends FileLocationRequestArgs { + /** + * Requires that the enclosing span be a multi-line comment, or else the request returns undefined. + */ + onlyMultiLine: boolean; + } + /** * Request to obtain outlining spans in file. */ diff --git a/src/server/session.ts b/src/server/session.ts index 4122408cfa6bb..6bfa1ed7e52df 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1025,6 +1025,14 @@ namespace ts.server { return project.getLanguageService(/*ensureSynchronized*/ false).getDocCommentTemplateAtPosition(file, position); } + private getSpanOfEnclosingComment(args: protocol.SpanOfEnclosingCommentRequestArgs) { + const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); + const scriptInfo = project.getScriptInfoForNormalizedPath(file); + const onlyMultiLine = args.onlyMultiLine; + const position = this.getPosition(args, scriptInfo); + return project.getLanguageService(/*ensureSynchronized*/ false).getSpanOfEnclosingComment(file, position, onlyMultiLine); + } + private getIndentation(args: protocol.IndentationRequestArgs) { const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); const position = this.getPosition(args, project.getScriptInfoForNormalizedPath(file)); @@ -1765,6 +1773,9 @@ namespace ts.server { [CommandNames.DocCommentTemplate]: (request: protocol.DocCommentTemplateRequest) => { return this.requiredResponse(this.getDocCommentTemplate(request.arguments)); }, + [CommandNames.GetSpanOfEnclosingComment]: (request: protocol.SpanOfEnclosingCommentRequest) => { + return this.requiredResponse(this.getSpanOfEnclosingComment(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 ad9b2f179aac6..85cab12655953 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -1152,6 +1152,56 @@ namespace ts.formatting { } } + /** + * @param precedingToken pass `null` if preceding token was already computed and result was `undefined`. + */ + export function getRangeOfEnclosingComment( + sourceFile: SourceFile, + position: number, + onlyMultiLine: boolean, + precedingToken?: Node | null, // tslint:disable-line:no-null-keyword + tokenAtPosition = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false), + predicate?: (c: CommentRange) => boolean): CommentRange | undefined { + const tokenStart = tokenAtPosition.getStart(sourceFile); + if (tokenStart <= position && position < tokenAtPosition.getEnd()) { + return undefined; + } + + if (precedingToken === undefined) { + precedingToken = findPrecedingToken(position, sourceFile); + } + + // Between two consecutive tokens, all comments are either trailing on the former + // or leading on the latter (and none are in both lists). + const trailingRangesOfPreviousToken = precedingToken && getTrailingCommentRanges(sourceFile.text, precedingToken.end); + const leadingCommentRangesOfNextToken = getLeadingCommentRangesOfNode(tokenAtPosition, sourceFile); + const commentRanges = trailingRangesOfPreviousToken && leadingCommentRangesOfNextToken ? + trailingRangesOfPreviousToken.concat(leadingCommentRangesOfNextToken) : + trailingRangesOfPreviousToken || leadingCommentRangesOfNextToken; + if (commentRanges) { + for (const range of commentRanges) { + // The end marker of a single-line comment does not include the newline character. + // With caret at `^`, in the following case, we are inside a comment (^ denotes the cursor position): + // + // // asdf ^\n + // + // But for closed multi-line comments, we don't want to be inside the comment in the following case: + // + // /* asdf */^ + // + // However, unterminated multi-line comments *do* contain their end. + // + // Internally, we represent the end of the comment at the newline and closing '/', respectively. + // + if ((range.pos < position && position < range.end || + position === range.end && (range.kind === SyntaxKind.SingleLineCommentTrivia || position === sourceFile.getFullWidth()))) { + return (range.kind === SyntaxKind.MultiLineCommentTrivia || !onlyMultiLine) && (!predicate || predicate(range)) ? range : undefined; + } + } + } + return undefined; + } + function getOpenTokenForList(node: Node, list: ReadonlyArray) { switch (node.kind) { case SyntaxKind.Constructor: diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts index a98986472f372..4de2e5765e51f 100644 --- a/src/services/formatting/smartIndenter.ts +++ b/src/services/formatting/smartIndenter.ts @@ -32,13 +32,36 @@ namespace ts.formatting { } const precedingToken = findPrecedingToken(position, sourceFile); + + const enclosingCommentRange = getRangeOfEnclosingComment(sourceFile, position, /*onlyMultiLine*/ true, precedingToken || null); // tslint:disable-line:no-null-keyword + if (enclosingCommentRange) { + const previousLine = getLineAndCharacterOfPosition(sourceFile, position).line - 1; + const commentStartLine = getLineAndCharacterOfPosition(sourceFile, enclosingCommentRange.pos).line; + + Debug.assert(commentStartLine >= 0); + + if (previousLine <= commentStartLine) { + return findFirstNonWhitespaceColumn(getStartPositionOfLine(commentStartLine, sourceFile), position, sourceFile, options); + } + + const startPostionOfLine = getStartPositionOfLine(previousLine, sourceFile); + const { column, character } = findFirstNonWhitespaceCharacterAndColumn(startPostionOfLine, position, sourceFile, options); + + if (column === 0) { + return column; + } + + const firstNonWhitespaceCharacterCode = sourceFile.text.charCodeAt(startPostionOfLine + character); + return firstNonWhitespaceCharacterCode === CharacterCodes.asterisk ? column - 1 : column; + } + if (!precedingToken) { return getBaseIndentation(options); } // 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; } @@ -405,13 +428,13 @@ namespace ts.formatting { return findFirstNonWhitespaceColumn(lineStart, lineStart + lineAndCharacter.character, sourceFile, options); } - /* - Character is the actual index of the character since the beginning of the line. - Column - position of the character after expanding tabs to spaces - "0\t2$" - value of 'character' for '$' is 3 - value of 'column' for '$' is 6 (assuming that tab size is 4) - */ + /** + * Character is the actual index of the character since the beginning of the line. + * Column - position of the character after expanding tabs to spaces. + * "0\t2$" + * value of 'character' for '$' is 3 + * value of 'column' for '$' is 6 (assuming that tab size is 4) + */ export function findFirstNonWhitespaceCharacterAndColumn(startPos: number, endPos: number, sourceFile: SourceFileLike, options: EditorSettings) { let character = 0; let column = 0; diff --git a/src/services/services.ts b/src/services/services.ts index ed9732cc9cf5c..7354e706cc946 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -107,12 +107,14 @@ namespace ts { scanner.setTextPos(pos); while (pos < end) { const token = scanner.scan(); - Debug.assert(token !== SyntaxKind.EndOfFileToken); // Else it would infinitely loop const textPos = scanner.getTextPos(); if (textPos <= end) { nodes.push(createNode(token, pos, textPos, this)); } pos = textPos; + if (token === SyntaxKind.EndOfFileToken) { + break; + } } return pos; } @@ -1757,17 +1759,20 @@ namespace ts { function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); const settings = toEditorSettings(options); - if (key === "{") { - return formatting.formatOnOpeningCurly(position, sourceFile, getRuleProvider(settings), settings); - } - else if (key === "}") { - return formatting.formatOnClosingCurly(position, sourceFile, getRuleProvider(settings), settings); - } - else if (key === ";") { - return formatting.formatOnSemicolon(position, sourceFile, getRuleProvider(settings), settings); - } - else if (key === "\n") { - return formatting.formatOnEnter(position, sourceFile, getRuleProvider(settings), settings); + + if (!isInComment(sourceFile, position)) { + if (key === "{") { + return formatting.formatOnOpeningCurly(position, sourceFile, getRuleProvider(settings), settings); + } + else if (key === "}") { + return formatting.formatOnClosingCurly(position, sourceFile, getRuleProvider(settings), settings); + } + else if (key === ";") { + return formatting.formatOnSemicolon(position, sourceFile, getRuleProvider(settings), settings); + } + else if (key === "\n") { + return formatting.formatOnEnter(position, sourceFile, getRuleProvider(settings), settings); + } } return []; @@ -1826,6 +1831,12 @@ namespace ts { return true; } + function getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean) { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const range = ts.formatting.getRangeOfEnclosingComment(sourceFile, position, onlyMultiLine); + return range && createTextSpanFromRange(range); + } + 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 @@ -2050,6 +2061,7 @@ namespace ts { getFormattingEditsAfterKeystroke, getDocCommentTemplateAtPosition, isValidBraceCompletionAtPosition, + getSpanOfEnclosingComment, getCodeFixesAtPosition, getEmitOutput, getNonBoundSourceFile, diff --git a/src/services/shims.ts b/src/services/shims.ts index b7c85d2ce82d4..0b4dde42d5182 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -254,6 +254,11 @@ namespace ts { */ isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): string; + /** + * Returns a JSON-encoded TextSpan | undefined indicating the range of the enclosing comment, if it exists. + */ + getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string; + getEmitOutput(fileName: string): string; getEmitOutputObject(fileName: string): EmitOutput; } @@ -815,6 +820,13 @@ namespace ts { ); } + public getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string { + return this.forwardJSONCall( + `getSpanOfEnclosingComment('${fileName}', ${position})`, + () => this.languageService.getSpanOfEnclosingComment(fileName, position, onlyMultiLine) + ); + } + /// GET SMART INDENT public getIndentationAtPosition(fileName: string, position: number, options: string /*Services.EditorOptions*/): string { return this.forwardJSONCall( diff --git a/src/services/types.ts b/src/services/types.ts index b034e2813c6e1..f4f323b8e9f71 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -270,6 +270,8 @@ namespace ts { isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; + getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[]; getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 27215190db3fd..e8f01bb078549 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -720,8 +720,14 @@ namespace ts { } } - export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node, includeJsDoc?: boolean): Node { - return find(startNode || sourceFile); + /** + * Finds the rightmost token satisfying `token.end <= position`, + * excluding `JsxText` tokens containing only whitespace. + */ + export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node, includeJsDoc?: boolean): Node | undefined { + const result = find(startNode || sourceFile); + Debug.assert(!(result && isWhiteSpaceOnlyJsxText(result))); + return result; function findRightmostToken(n: Node): Node { if (isToken(n)) { @@ -742,19 +748,17 @@ namespace ts { const children = n.getChildren(); for (let i = 0; i < children.length; i++) { const child = children[i]; - // condition 'position < child.end' checks if child node end after the position - // in the example below this condition will be false for 'aaaa' and 'bbbb' and true for 'ccc' - // aaaa___bbbb___$__ccc - // after we found child node with end after the position we check if start of the node is after the position. - // if yes - then position is in the trivia and we need to look into the previous child to find the token in question. - // if no - position is in the node itself so we should recurse in it. - // NOTE: JsxText is a weird kind of node that can contain only whitespaces (since they are not counted as trivia). - // if this is the case - then we should assume that token in question is located in previous child. - if (position < child.end && (nodeHasTokens(child) || child.kind === SyntaxKind.JsxText)) { + // Note that the span of a node's tokens is [node.getStart(...), node.end). + // Given that `position < child.end` and child has constituent tokens, we distinguish these cases: + // 1) `position` precedes `child`'s tokens or `child` has no tokens (ie: in a comment or whitespace preceding `child`): + // we need to find the last token in a previous child. + // 2) `position` is within the same span: we recurse on `child`. + if (position < child.end) { const start = child.getStart(sourceFile, includeJsDoc); const lookInPreviousChild = (start >= position) || // cursor in the leading trivia - (child.kind === SyntaxKind.JsxText && start === child.end); // whitespace only JsxText + !nodeHasTokens(child) || + isWhiteSpaceOnlyJsxText(child); if (lookInPreviousChild) { // actual start of the node is past the position - previous token should be at the end of previous child @@ -780,10 +784,17 @@ namespace ts { } } - /// finds last node that is considered as candidate for search (isCandidate(node) === true) starting from 'exclusiveStartPosition' + /** + * Finds the rightmost child to the left of `children[exclusiveStartPosition]` which is a non-all-whitespace token or has constituent tokens. + */ function findRightmostChildNodeWithTokens(children: Node[], exclusiveStartPosition: number): Node { for (let i = exclusiveStartPosition - 1; i >= 0; i--) { - if (nodeHasTokens(children[i])) { + const child = children[i]; + + if (isWhiteSpaceOnlyJsxText(child)) { + Debug.assert(i > 0, "`JsxText` tokens should not be the first child of `JsxElement | JsxSelfClosingElement`"); + } + else if (nodeHasTokens(children[i])) { return children[i]; } } @@ -851,6 +862,10 @@ namespace ts { return false; } + export function isWhiteSpaceOnlyJsxText(node: Node): node is JsxText { + return isJsxText(node) && node.containsOnlyWhiteSpaces; + } + export function isInTemplateString(sourceFile: SourceFile, position: number) { const token = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); return isTemplateLiteralKind(token.kind) && position > token.getStart(sourceFile); @@ -863,41 +878,11 @@ namespace ts { * @param predicate Additional predicate to test on the comment range. */ export function isInComment( - sourceFile: SourceFile, - position: number, - tokenAtPosition = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false), - predicate?: (c: CommentRange) => boolean): boolean { - return position <= tokenAtPosition.getStart(sourceFile) && - (isInCommentRange(getLeadingCommentRanges(sourceFile.text, tokenAtPosition.pos)) || - isInCommentRange(getTrailingCommentRanges(sourceFile.text, tokenAtPosition.pos))); - - function isInCommentRange(commentRanges: CommentRange[]): boolean { - return forEach(commentRanges, c => isPositionInCommentRange(c, position, sourceFile.text) && (!predicate || predicate(c))); - } - } - - function isPositionInCommentRange({ pos, end, kind }: ts.CommentRange, position: number, text: string): boolean { - if (pos < position && position < end) { - return true; - } - else if (position === end) { - // The end marker of a single-line comment does not include the newline character. - // In the following case, we are inside a comment (^ denotes the cursor position): - // - // // asdf ^\n - // - // But for multi-line comments, we don't want to be inside the comment in the following case: - // - // /* asdf */^ - // - // Internally, we represent the end of the comment at the newline and closing '/', respectively. - return kind === SyntaxKind.SingleLineCommentTrivia || - // true for unterminated multi-line comment - !(text.charCodeAt(end - 1) === CharacterCodes.slash && text.charCodeAt(end - 2) === CharacterCodes.asterisk); - } - else { - return false; - } + sourceFile: SourceFile, + position: number, + tokenAtPosition?: Node, + predicate?: (c: CommentRange) => boolean): boolean { + return !!formatting.getRangeOfEnclosingComment(sourceFile, position, /*onlyMultiLine*/ false, /*precedingToken*/ undefined, tokenAtPosition, predicate); } export function hasDocComment(sourceFile: SourceFile, position: number) { @@ -916,7 +901,7 @@ namespace ts { function nodeHasTokens(n: Node): boolean { // If we have a token or node that has a non-zero width, it must have tokens. - // Note, that getWidth() does not take trivia into account. + // Note: getWidth() does not take trivia into account. return n.getWidth() !== 0; } diff --git a/tests/cases/fourslash/completionListAndMemberListOnCommentedDot.ts b/tests/cases/fourslash/completionListAndMemberListOnCommentedDot.ts index d0517d87279b9..b57e7b84b5354 100644 --- a/tests/cases/fourslash/completionListAndMemberListOnCommentedDot.ts +++ b/tests/cases/fourslash/completionListAndMemberListOnCommentedDot.ts @@ -14,5 +14,4 @@ //////c./**/ goTo.marker(); -verify.completionListIsEmpty(); verify.completionListIsEmpty(); \ No newline at end of file diff --git a/tests/cases/fourslash/formatOnEnterInComment.ts b/tests/cases/fourslash/formatOnEnterInComment.ts new file mode 100644 index 0000000000000..489eb6ac6ce87 --- /dev/null +++ b/tests/cases/fourslash/formatOnEnterInComment.ts @@ -0,0 +1,14 @@ +/// + +//// /** +//// * /*1*/ +//// */ + +goTo.marker("1"); +edit.insertLine(""); +verify.currentFileContentIs( +` /** + * + + */` +); \ No newline at end of file diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 5175b52df6c9f..87e11ae4262d8 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -153,6 +153,7 @@ declare namespace FourSlashInterface { typeDefinitionCountIs(expectedCount: number): void; implementationListIsEmpty(): void; isValidBraceCompletionAtPosition(openingBrace?: string): void; + isInCommentAtPosition(onlyMultiLineDiverges?: boolean): void; codeFixAvailable(): void; applicableRefactorAvailableAtMarker(markerName: string): void; codeFixDiagnosticsAvailableAtMarkers(markerNames: string[], diagnosticCode?: number): void; diff --git a/tests/cases/fourslash/indentionsOfCommentBlockAfterFormatting.ts b/tests/cases/fourslash/indentationInBlockCommentAfterFormatting.ts similarity index 90% rename from tests/cases/fourslash/indentionsOfCommentBlockAfterFormatting.ts rename to tests/cases/fourslash/indentationInBlockCommentAfterFormatting.ts index 3b686285b9990..b965b314234fe 100644 --- a/tests/cases/fourslash/indentionsOfCommentBlockAfterFormatting.ts +++ b/tests/cases/fourslash/indentationInBlockCommentAfterFormatting.ts @@ -27,13 +27,13 @@ verify.indentationIs(4); goTo.marker("2"); verify.indentationIs(4); goTo.marker("3"); -verify.indentationIs(4); +verify.indentationIs(3); goTo.marker("4"); -verify.indentationIs(4); +verify.indentationIs(3); // Putting a marker in line "*" would bring some error when parsing code in automation. // So move right by 1 offset from marker 4 to locate the caret in this line. edit.moveRight(1); -verify.indentationIs(4); +verify.indentationIs(3); // Putting a marker in line " */" would bring some error when parsing code in automation. // So move left by 1 offset from marker 5 to locate the caret in this line. goTo.marker("5"); diff --git a/tests/cases/fourslash/indentationInComments.ts b/tests/cases/fourslash/indentationInComments.ts new file mode 100644 index 0000000000000..d54ec9daacb93 --- /dev/null +++ b/tests/cases/fourslash/indentationInComments.ts @@ -0,0 +1,32 @@ +/// + +//// // /*0_0*/ +//// /* /*0_1*/ +//// some text /*0_2*/ +//// some text /*1_0*/ +//// * some text /*0_3*/ +//// /*0_4*/ +//// */ +//// function foo() { +//// // /*4_0*/ +//// /** /*4_1*/ +//// * /*4_2*/ +//// * /*4_3*/ +//// /*7_0*/ +//// */ +//// /* /*4_4*/ */ +//// } + +for (let i = 0; i < 5; ++i) { + goTo.marker(`0_${i}`); + verify.indentationIs(0); + + goTo.marker(`4_${i}`); + verify.indentationIs(4); +} + +goTo.marker(`1_0`); +verify.indentationIs(1); + +goTo.marker(`7_0`); +verify.indentationIs(7); diff --git a/tests/cases/fourslash/isInMultiLineComment.ts b/tests/cases/fourslash/isInMultiLineComment.ts new file mode 100644 index 0000000000000..96da4f0021f8f --- /dev/null +++ b/tests/cases/fourslash/isInMultiLineComment.ts @@ -0,0 +1,46 @@ +/// + +//// /* x */ +//// /** +//// * @param this doesn't make sense here. +//// */ +//// // x +//// let x = 1; /* +//// * + +const firstCommentStart = 0; +const firstCommentEnd = 7; +goTo.position(firstCommentStart); +verify.not.isInCommentAtPosition(); + +goTo.position(firstCommentStart + 1); +verify.isInCommentAtPosition(); +goTo.position(firstCommentEnd - 1); +verify.isInCommentAtPosition(); + +goTo.position(firstCommentEnd); +verify.not.isInCommentAtPosition(); + +const multilineJsDocStart = firstCommentEnd + 1; +const multilineJsDocEnd = multilineJsDocStart + 49; + +goTo.position(multilineJsDocStart); +verify.not.isInCommentAtPosition(); +goTo.position(multilineJsDocStart + 1); +verify.isInCommentAtPosition(); +goTo.position(multilineJsDocEnd - 1); +verify.isInCommentAtPosition(); +goTo.position(multilineJsDocEnd); +verify.not.isInCommentAtPosition(); + +const singleLineCommentStart = multilineJsDocEnd + 1; + +goTo.position(singleLineCommentStart + 1); +verify.isInCommentAtPosition(/*onlyMultiLineDiverges*/ true); + +const postNodeCommentStart = singleLineCommentStart + 16; + +goTo.position(postNodeCommentStart); +verify.not.isInCommentAtPosition(); +goTo.position(postNodeCommentStart + 1); +verify.isInCommentAtPosition(); diff --git a/tests/cases/fourslash/isInMultiLineCommentInJsxText.ts b/tests/cases/fourslash/isInMultiLineCommentInJsxText.ts new file mode 100644 index 0000000000000..1664ca0cd1bcd --- /dev/null +++ b/tests/cases/fourslash/isInMultiLineCommentInJsxText.ts @@ -0,0 +1,27 @@ +/// + +// @Filename: file.jsx +////
+//// // /*0*/ +//// /* /*1*/ */ +//// /** +//// * /*2*/ +//// */ +//// foo() /* /*3*/ */ +//// // /*4*/ +//// /* /*5*/ */ +//// /** +//// * /*6*/ +//// */ +////
+////
+//// // /*7*/ +//// /* /*8*/ */ +//// /** +//// * /*9*/ +//// */ + +for (let i = 0; i < 10; ++i) { + goTo.marker(i.toString()); + verify.not.isInCommentAtPosition(); +} diff --git a/tests/cases/fourslash/isInMultiLineCommentInTemplateLiteral.ts b/tests/cases/fourslash/isInMultiLineCommentInTemplateLiteral.ts new file mode 100644 index 0000000000000..c3e2ae92e8ace --- /dev/null +++ b/tests/cases/fourslash/isInMultiLineCommentInTemplateLiteral.ts @@ -0,0 +1,27 @@ +/// + +// @Filename: file.jsx +//// ` +//// // /*0*/ +//// /* /*1*/ */ +//// /** +//// * /*2*/ +//// */ +//// foo() +//// // /*3*/ +//// /* /*4*/ */ +//// /** +//// * /*5*/ +//// */ +//// ` +//// ` +//// // /*6*/ +//// /* /*7*/ */ +//// /** +//// * /*8*/ +//// */ + +for (let i = 0; i < 9; ++i) { + goTo.marker(i.toString()); + verify.not.isInCommentAtPosition(); +} \ No newline at end of file diff --git a/tests/cases/fourslash/isInMultiLineCommentOnlyTrivia.ts b/tests/cases/fourslash/isInMultiLineCommentOnlyTrivia.ts new file mode 100644 index 0000000000000..41c9bbadfd8eb --- /dev/null +++ b/tests/cases/fourslash/isInMultiLineCommentOnlyTrivia.ts @@ -0,0 +1,37 @@ +/// + +//// /* x */ +//// /** +//// * @param this doesn't make sense here. +//// */ +//// // x + +const firstCommentStart = 0; +const firstCommentEnd = 7; +goTo.position(firstCommentStart); +verify.not.isInCommentAtPosition(); + +goTo.position(firstCommentStart + 1); +verify.isInCommentAtPosition(); +goTo.position(firstCommentEnd - 1); +verify.isInCommentAtPosition(); + +goTo.position(firstCommentEnd); +verify.not.isInCommentAtPosition(); + +const multilineJsDocStart = firstCommentEnd + 1; +const multilineJsDocEnd = multilineJsDocStart + 49; + +goTo.position(multilineJsDocStart); +verify.not.isInCommentAtPosition(); +goTo.position(multilineJsDocStart + 1); +verify.isInCommentAtPosition(); +goTo.position(multilineJsDocEnd - 1); +verify.isInCommentAtPosition(); +goTo.position(multilineJsDocEnd); +verify.not.isInCommentAtPosition(); + +const singleLineCommentStart = multilineJsDocEnd + 1; + +goTo.position(singleLineCommentStart + 1); +verify.isInCommentAtPosition(/*onlyMultiLineDiverges*/ true); \ No newline at end of file