-
Notifications
You must be signed in to change notification settings - Fork 13k
multi-line comment formatting fix and handler #16385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
35b16ab
6b4cd0b
a819e4e
8f28a02
b02963b
8fc3fd9
cc88091
efdbeba
777bc57
091376f
2f5b1d3
7c402d5
de92e98
b2188ad
472ad9d
f3e0cbb
a209db7
a08d18a
ad9c29b
153b94a
70e4f34
4b9f5a0
62f16be
23ca368
b7bc7d8
19e2fa6
6029b5c
760ef44
e7d2af0
e4e969a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,7 @@ namespace ts.server.protocol { | |
/* @internal */ | ||
BraceFull = "brace-full", | ||
BraceCompletion = "braceCompletion", | ||
isInMultiLineComment = "isInMultiLineComment", | ||
GetSpanOfEnclosingComment = "getSpanOfEnclosingComment", | ||
Change = "change", | ||
Close = "close", | ||
Completions = "completions", | ||
|
@@ -240,10 +240,18 @@ namespace ts.server.protocol { | |
} | ||
|
||
/** | ||
* A request to determine if the caret is inside a multi-line comment. | ||
* A request to determine if the caret is inside a comment. | ||
*/ | ||
export interface IsInMultiLineCommentAtPositionRequest extends FileLocationRequest { | ||
command: CommandTypes.isInMultiLineComment; | ||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this just here for the generality of the API? We don't appear to actually use it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We only ever pass in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have mixed feeling about this but don't object as long as we have test coverage (which we do). |
||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1118,23 +1118,37 @@ namespace ts.formatting { | |
} | ||
|
||
/** | ||
* @returns -1 iff the position is not in a multi-line comment. | ||
* Gets the indentation level of the multi-line comment enclosing position, | ||
* and a negative value if 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) { | ||
export function getIndentationOfEnclosingMultiLineComment(sourceFile: SourceFile, position: number, options: EditorSettings): number { | ||
const range = getRangeOfEnclosingComment(sourceFile, position, /*onlyMultiLine*/ true); | ||
if (range) { | ||
|
||
const commentStart = range.pos; | ||
const commentLineStart = getLineStartPositionForPosition(commentStart, sourceFile); | ||
const { column, character } = SmartIndenter.findFirstNonWhitespaceCharacterAndColumn(commentLineStart, commentStart, sourceFile, options); | ||
return column + /*length after whitespace ends*/ range.pos - (commentLineStart + character); | ||
|
||
} | ||
return undefined; | ||
} | ||
|
||
export function getRangeOfEnclosingComment(sourceFile: SourceFile, position: number, onlyMultiLine: boolean): CommentRange | undefined { | ||
const precedingToken = findPrecedingToken(position, sourceFile); | ||
const trailingRangesOfPreviousToken = precedingToken && getTrailingCommentRanges(sourceFile.text, precedingToken.end); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably short-circuit if position is within |
||
const leadingCommentRangesOfNextToken = getLeadingCommentRangesOfNode(getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false), sourceFile); | ||
|
||
const commentRanges = trailingRangesOfPreviousToken && leadingCommentRangesOfNextToken ? | ||
trailingRangesOfPreviousToken.concat(leadingCommentRangesOfNextToken) : | ||
trailingRangesOfPreviousToken || leadingCommentRangesOfNextToken; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add some explanatory comments for the logic in here? It assumes the reader knows a lot of detail about how we keep comments in the trees attached to tokens. And I'm guessing there's some edge cases or patterns here this logic needs to handle that makes it non-trivial. That knowledge would be good for future readers/maintains not to have to figure out again for themselves. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added above. |
||
if (commentRanges) { | ||
for (const range of commentRanges) { | ||
// 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; | ||
if (range.pos < position && position < range.end || | ||
position === range.end && (range.kind === SyntaxKind.SingleLineCommentTrivia || position === sourceFile.getFullWidth())) { | ||
|
||
return onlyMultiLine && range.kind !== SyntaxKind.MultiLineCommentTrivia ? undefined : range; | ||
} | ||
} | ||
} | ||
return -1; | ||
return undefined; | ||
} | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,8 +44,8 @@ namespace ts.formatting { | |
|
||
const lineAtPosition = sourceFile.getLineAndCharacterOfPosition(position).line; | ||
|
||
const indentationOfEnclosingMultiLineComment = getIndentationOfEnclosingMultiLineComment(sourceFile, position); | ||
if (indentationOfEnclosingMultiLineComment !== -1) { | ||
const indentationOfEnclosingMultiLineComment = getIndentationOfEnclosingMultiLineComment(sourceFile, position, options); | ||
|
||
if (indentationOfEnclosingMultiLineComment >= 0) { | ||
return indentationOfEnclosingMultiLineComment; | ||
} | ||
|
||
|
@@ -410,13 +410,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; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,47 @@ | ||
/// <reference path="fourslash.ts" /> | ||
|
||
//// /* x */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be interesting to test a comment that spans multiple lines? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added |
||
//// /** x */ | ||
//// /** | ||
//// * @param this doesn't make sense here. | ||
//// */ | ||
//// // x | ||
//// let x = 1; | ||
|
||
|
||
for (let i = 1; i < 7; ++i) { | ||
goTo.position(i); | ||
verify.isInMultiLineCommentAtPosition(); | ||
} | ||
|
||
for (let i = 0; i < 2; ++i) { | ||
goTo.position(i * 7); | ||
verify.not.isInMultiLineCommentAtPosition(); | ||
} | ||
|
||
const jsDocStart = 8; | ||
|
||
for (let i = 1; i < 8; ++i) { | ||
goTo.position(jsDocStart + i); | ||
verify.isInMultiLineCommentAtPosition(); | ||
} | ||
|
||
for (let i = 0; i < 2; ++i) { | ||
goTo.position(jsDocStart + i * 8); | ||
verify.not.isInMultiLineCommentAtPosition(); | ||
} | ||
|
||
const singleLineCommentStart = 17; | ||
|
||
for (let i = 0; i < 5; ++i) { | ||
goTo.position(singleLineCommentStart + i); | ||
verify.not.isInMultiLineCommentAtPosition(); | ||
} | ||
//// let x = 1; /* | ||
//// * | ||
|
||
const firstCommentStart = 0; | ||
const firstCommentEnd = 7; | ||
goTo.position(firstCommentStart); | ||
verify.not.isInCommentAtPosition(/*onlyMultiLine*/ true); | ||
|
||
goTo.position(firstCommentStart + 1); | ||
verify.isInCommentAtPosition(/*onlyMultiLine*/ true); | ||
goTo.position(firstCommentEnd - 1); | ||
verify.isInCommentAtPosition(/*onlyMultiLine*/ true); | ||
|
||
goTo.position(firstCommentEnd); | ||
verify.not.isInCommentAtPosition(/*onlyMultiLine*/ true); | ||
|
||
const multilineJsDocStart = firstCommentEnd + 1; | ||
const multilineJsDocEnd = multilineJsDocStart + 49; | ||
|
||
goTo.position(multilineJsDocStart); | ||
verify.not.isInCommentAtPosition(/*onlyMultiLine*/ true); | ||
goTo.position(multilineJsDocStart + 1); | ||
verify.isInCommentAtPosition(/*onlyMultiLine*/ true); | ||
goTo.position(multilineJsDocEnd - 1); | ||
verify.isInCommentAtPosition(/*onlyMultiLine*/ true); | ||
goTo.position(multilineJsDocEnd); | ||
verify.not.isInCommentAtPosition(/*onlyMultiLine*/ true); | ||
|
||
const singleLineCommentStart = multilineJsDocEnd + 1; | ||
|
||
goTo.position(singleLineCommentStart + 1); | ||
verify.not.isInCommentAtPosition(/*onlyMultiLine*/ true); | ||
verify.isInCommentAtPosition(/*onlyMultiLine*/ false); | ||
|
||
|
||
const postNodeCommentStart = singleLineCommentStart + 16; | ||
|
||
goTo.position(postNodeCommentStart); | ||
verify.not.isInCommentAtPosition(/*onlyMultiLine*/ true); | ||
goTo.position(postNodeCommentStart + 1); | ||
verify.isInCommentAtPosition(/*onlyMultiLine*/ true); |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just pass
expected
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Methods in
VerifyNegatable
often call into theTestState
by passing innegative
. I'm merely following that idiom.