Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
35b16ab
temp
aozgaa Jun 7, 2017
6b4cd0b
Merge branch 'isInMultiLineComment' of https://github.com/aozgaa/Type…
aozgaa Jun 7, 2017
a819e4e
isInMultiLineComment
aozgaa Jun 7, 2017
8f28a02
indent block comments according to first line
aozgaa Jun 9, 2017
b02963b
make indent work with trailing comments
aozgaa Jun 9, 2017
8fc3fd9
request returns span
aozgaa Jun 10, 2017
cc88091
fix offsetting and tests
aozgaa Jun 10, 2017
efdbeba
Merge branch 'master' into isInMultiLineComment
Aug 4, 2017
777bc57
implementation comment
Aug 4, 2017
091376f
supressFormatOnKeyInComments
aozgaa Aug 7, 2017
2f5b1d3
Merge branch 'master' into isInMultiLineComment
aozgaa Aug 7, 2017
7c402d5
Merge branch 'master' into isInMultiLineComment
aozgaa Aug 9, 2017
de92e98
fix end-of-file assert failure
aozgaa Aug 10, 2017
b2188ad
cleanup
aozgaa Aug 14, 2017
472ad9d
findPrevious changes
aozgaa Aug 14, 2017
f3e0cbb
`findPrecedingToken` handles EOF child more gracefully
aozgaa Aug 15, 2017
a209db7
dont compute preceding token twice
aozgaa Aug 15, 2017
a08d18a
consolidate isInComment and getRangeOfEnclosingComment
aozgaa Aug 15, 2017
ad9c29b
add test
aozgaa Aug 15, 2017
153b94a
`JsxText` has no leading comments
aozgaa Aug 16, 2017
70e4f34
update test
aozgaa Aug 17, 2017
4b9f5a0
rename tests
aozgaa Aug 17, 2017
62f16be
add tests
aozgaa Aug 17, 2017
23ca368
Use simpler indentation for comments
aozgaa Aug 17, 2017
b7bc7d8
clarify `JsxText` handling
aozgaa Aug 17, 2017
19e2fa6
Merge branch 'master' into isInMultiLineComment
aozgaa Aug 17, 2017
6029b5c
cleanup
aozgaa Aug 17, 2017
760ef44
test if `onlyMultiLine` flag changes answer
aozgaa Aug 17, 2017
e7d2af0
remove duplicate verify
aozgaa Aug 18, 2017
e4e969a
respond to comments
aozgaa Aug 18, 2017
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
14 changes: 9 additions & 5 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2500,13 +2500,17 @@ namespace FourSlash {
}
}

public verifyisInMultiLineCommentAtPosition(negative: boolean) {
public verifySpanOfEnclosingComment(negative: boolean, onlyMultiLine: boolean) {
Copy link
Member

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?

Copy link
Contributor Author

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 the TestState by passing in negative. I'm merely following that idiom.

const expected = !negative;
const position = this.currentCaretPosition;
const fileName = this.activeFile.fileName;
const actual = this.languageService.getisInMultiLineCommentAtPosition(fileName, position);
const actual = !!this.languageService.getSpanOfEnclosingComment(fileName, position, /*onlyMultiLine*/ onlyMultiLine);
if (expected !== actual) {
this.raiseError(`verifyIsInDocComment failed: at position '${position}' in '${fileName}', expected '${expected}'.`);
this.raiseError(`verifySpanOfEnclosingComment failed:
position: '${position}'
fileName: '${fileName}'
onlyMultiLine: '${onlyMultiLine}'
expected: '${expected}'.`);
}
}

Expand Down Expand Up @@ -3583,8 +3587,8 @@ namespace FourSlashInterface {
this.state.verifyBraceCompletionAtPosition(this.negative, openingBrace);
}

public isInMultiLineCommentAtPosition() {
this.state.verifyisInMultiLineCommentAtPosition(this.negative);
public isInCommentAtPosition(onlyMultiLine: boolean) {
this.state.verifySpanOfEnclosingComment(this.negative, onlyMultiLine);
}

public codeFixAvailable() {
Expand Down
4 changes: 2 additions & 2 deletions src/harness/harnessLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,8 +486,8 @@ namespace Harness.LanguageService {
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));
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.");
Expand Down
2 changes: 1 addition & 1 deletion src/server/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ namespace ts.server {
return notImplemented();
}

getisInMultiLineCommentAtPosition(_fileName: string, _position: number): boolean {
getSpanOfEnclosingComment(_fileName: string, _position: number, _onlyMultiLine: boolean): TextSpan {
return notImplemented();
}

Expand Down
16 changes: 12 additions & 4 deletions src/server/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace ts.server.protocol {
/* @internal */
BraceFull = "brace-full",
BraceCompletion = "braceCompletion",
isInMultiLineComment = "isInMultiLineComment",
GetSpanOfEnclosingComment = "getSpanOfEnclosingComment",
Change = "change",
Close = "close",
Completions = "completions",
Expand Down Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We only ever pass in onlyMultiLine: true right now with the proposed change in the VS extension.

Copy link
Member

Choose a reason for hiding this comment

The 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).

}

/**
Expand Down
9 changes: 5 additions & 4 deletions src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -961,11 +961,12 @@ namespace ts.server {
return project.getLanguageService(/*ensureSynchronized*/ false).getDocCommentTemplateAtPosition(file, position);
}

private getisInMultiLineCommentAtPosition(args: protocol.FileLocationRequestArgs) {
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).getisInMultiLineCommentAtPosition(file, position);
return project.getLanguageService(/*ensureSynchronized*/ false).getSpanOfEnclosingComment(file, position, onlyMultiLine);
}

private getIndentation(args: protocol.IndentationRequestArgs) {
Expand Down Expand Up @@ -1701,8 +1702,8 @@ namespace ts.server {
[CommandNames.DocCommentTemplate]: (request: protocol.DocCommentTemplateRequest) => {
return this.requiredResponse(this.getDocCommentTemplate(request.arguments));
},
[CommandNames.isInMultiLineComment]: (request: protocol.IsInMultiLineCommentAtPositionRequest) => {
return this.requiredResponse(this.getisInMultiLineCommentAtPosition(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));
Expand Down
38 changes: 26 additions & 12 deletions src/services/formatting/formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: if you return immediately after determining that range is undefined, the rest of the method reads as straight-line code.

Copy link
Contributor Author

@aozgaa aozgaa Aug 17, 2017

Choose a reason for hiding this comment

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

Just in-lined the helper since it's only called once. Additionally, the logic has changed because I discovered an issue with determining the columns when we have text which is non-whitespace (because determining the number of graphemes in a string is non-trivial).

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);
Copy link
Member

Choose a reason for hiding this comment

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

Can you clarify what the above logic is doing? Its a lot more involved than I would have expected, and I can't figure out the reason for some of it.

}
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);
Copy link
Member

Choose a reason for hiding this comment

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

We should probably short-circuit if position is within tokenAtPosition.

const leadingCommentRangesOfNextToken = getLeadingCommentRangesOfNode(getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false), sourceFile);
Copy link
Member

Choose a reason for hiding this comment

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

What is the impact of /*includeJsDocComment*/ false? Is there a test that depends on the value?

const commentRanges = trailingRangesOfPreviousToken && leadingCommentRangesOfNextToken ?
trailingRangesOfPreviousToken.concat(leadingCommentRangesOfNextToken) :
trailingRangesOfPreviousToken || leadingCommentRangesOfNextToken;
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
Copy link
Member

Choose a reason for hiding this comment

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

How does the check below detect unclosed multi-line comments?

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())) {
Copy link
Member

Choose a reason for hiding this comment

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

What is sourceFile.getFullWidth()?

return onlyMultiLine && range.kind !== SyntaxKind.MultiLineCommentTrivia ? undefined : range;
}
}
}
return -1;
return undefined;
}


Expand Down
18 changes: 9 additions & 9 deletions src/services/formatting/smartIndenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

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

utilities.ts already exports a brief isInComment function. It it worth enclosing this block in an if check on that to make it clearer?

if (indentationOfEnclosingMultiLineComment >= 0) {
return indentationOfEnclosingMultiLineComment;
}

Expand Down Expand Up @@ -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;
Expand Down
7 changes: 4 additions & 3 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1818,9 +1818,10 @@ namespace ts {
return true;
}

function getisInMultiLineCommentAtPosition(fileName: string, position: number): boolean {
function getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean) {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
return ts.formatting.getIndentationOfEnclosingMultiLineComment(sourceFile, position) !== -1;
const range = ts.formatting.getRangeOfEnclosingComment(sourceFile, position, onlyMultiLine);
return range && createTextSpanFromRange(range);
}

function getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] {
Expand Down Expand Up @@ -2045,7 +2046,7 @@ namespace ts {
getFormattingEditsAfterKeystroke,
getDocCommentTemplateAtPosition,
isValidBraceCompletionAtPosition,
getisInMultiLineCommentAtPosition,
getSpanOfEnclosingComment,
getCodeFixesAtPosition,
getEmitOutput,
getNonBoundSourceFile,
Expand Down
11 changes: 5 additions & 6 deletions src/services/shims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,9 @@ 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.
* Returns a JSON-encoded TextSpan | undefined indicating the range of the enclosing comment, if it exists.
*/
getisInMultiLineCommentAtPosition(fileName: string, position: number): string;
getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string;

getEmitOutput(fileName: string): string;
getEmitOutputObject(fileName: string): EmitOutput;
Expand Down Expand Up @@ -840,11 +840,10 @@ namespace ts {
);
}

/// GET IS IN MULTI-LINE COMMENT
public getisInMultiLineCommentAtPosition(fileName: string, position: number): string {
public getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string {
return this.forwardJSONCall(
`getisInMultiLineCommentAtPosition('${fileName}', ${position})`,
() => this.languageService.getisInMultiLineCommentAtPosition(fileName, position)
`getSpanOfEnclosingComment('${fileName}', ${position})`,
() => this.languageService.getSpanOfEnclosingComment(fileName, position, onlyMultiLine)
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ namespace ts {

isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean;

getisInMultiLineCommentAtPosition(fileName: string, position: 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[];
Expand Down
2 changes: 1 addition & 1 deletion tests/cases/fourslash/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ declare namespace FourSlashInterface {
typeDefinitionCountIs(expectedCount: number): void;
implementationListIsEmpty(): void;
isValidBraceCompletionAtPosition(openingBrace?: string): void;
isInMultiLineCommentAtPosition(): void;
isInCommentAtPosition(onlyMultiLine: boolean): void;
codeFixAvailable(): void;
applicableRefactorAvailableAtMarker(markerName: string): void;
codeFixDiagnosticsAvailableAtMarkers(markerNames: string[], diagnosticCode?: number): void;
Expand Down
75 changes: 43 additions & 32 deletions tests/cases/fourslash/isInMultiLineComment.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,47 @@
/// <reference path="fourslash.ts" />

//// /* x */
Copy link
Member

Choose a reason for hiding this comment

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

Would it be interesting to test a comment that spans multiple lines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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);
Copy link
Member

Choose a reason for hiding this comment

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

Presumably, the result for false matches the result for true in every case but this one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. I thought it felt a bit heavy-handed to query each case in the test that is checked in, but I'm open to checking more thoroughly if it seems worthwhile.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe isInCommentAtPosition could check both unless either true or false is specified?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added.


const postNodeCommentStart = singleLineCommentStart + 16;

goTo.position(postNodeCommentStart);
verify.not.isInCommentAtPosition(/*onlyMultiLine*/ true);
goTo.position(postNodeCommentStart + 1);
verify.isInCommentAtPosition(/*onlyMultiLine*/ true);
12 changes: 0 additions & 12 deletions tests/cases/fourslash/isInMultiLineCommentEOF.ts

This file was deleted.