Skip to content

Commit 7aac8d5

Browse files
committed
Merge pull request #689 from Microsoft/todoComments
Migrate getTodoComments to use the new tree
2 parents 3ca2a7d + 831d26f commit 7aac8d5

File tree

1 file changed

+110
-93
lines changed

1 file changed

+110
-93
lines changed

src/services/services.ts

Lines changed: 110 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -2066,6 +2066,7 @@ module ts {
20662066
}
20672067
}
20682068

2069+
/** Get the token whose text contains the position, or the containing node. */
20692070
function getNodeAtPosition(sourceFile: SourceFile, position: number) {
20702071
var current: Node = sourceFile;
20712072
outer: while (true) {
@@ -2076,9 +2077,24 @@ module ts {
20762077
current = child;
20772078
continue outer;
20782079
}
2079-
if (child.end > position) {
2080-
break;
2081-
}
2080+
}
2081+
return current;
2082+
}
2083+
}
2084+
2085+
/** Get a token that contains the position. This is guaranteed to return a token, the position can be in the
2086+
* leading trivia or within the token text.
2087+
*/
2088+
function getTokenAtPosition(sourceFile: SourceFile, position: number) {
2089+
var current: Node = sourceFile;
2090+
outer: while (true) {
2091+
// find the child that has this
2092+
for (var i = 0, n = current.getChildCount(); i < n; i++) {
2093+
var child = current.getChildAt(i);
2094+
if (child.getFullStart() <= position && position < child.getEnd()) {
2095+
current = child;
2096+
continue outer;
2097+
}
20822098
}
20832099
return current;
20842100
}
@@ -3793,83 +3809,21 @@ module ts {
37933809
return [];
37943810
}
37953811

3796-
function escapeRegExp(str: string): string {
3797-
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
3798-
}
3799-
3800-
function getTodoCommentsRegExp(descriptors: TodoCommentDescriptor[]): RegExp {
3801-
// NOTE: ?: means 'non-capture group'. It allows us to have groups without having to
3802-
// filter them out later in the final result array.
3803-
3804-
// TODO comments can appear in one of the following forms:
3805-
//
3806-
// 1) // TODO or /////////// TODO
3807-
//
3808-
// 2) /* TODO or /********** TODO
3809-
//
3810-
// 3) /*
3811-
// * TODO
3812-
// */
3813-
//
3814-
// The following three regexps are used to match the start of the text up to the TODO
3815-
// comment portion.
3816-
var singleLineCommentStart = /(?:\/\/+\s*)/.source;
3817-
var multiLineCommentStart = /(?:\/\*+\s*)/.source;
3818-
var anyNumberOfSpacesAndAsterixesAtStartOfLine = /(?:^(?:\s|\*)*)/.source;
3819-
3820-
// Match any of the above three TODO comment start regexps.
3821-
// Note that the outermost group *is* a capture group. We want to capture the preamble
3822-
// so that we can determine the starting position of the TODO comment match.
3823-
var preamble = "(" + anyNumberOfSpacesAndAsterixesAtStartOfLine + "|" + singleLineCommentStart + "|" + multiLineCommentStart + ")";
3824-
3825-
// Takes the descriptors and forms a regexp that matches them as if they were literals.
3826-
// For example, if the descriptors are "TODO(jason)" and "HACK", then this will be:
3827-
//
3828-
// (?:(TODO\(jason\))|(HACK))
3829-
//
3830-
// Note that the outermost group is *not* a capture group, but the innermost groups
3831-
// *are* capture groups. By capturing the inner literals we can determine after
3832-
// matching which descriptor we are dealing with.
3833-
var literals = "(?:" + descriptors.map(d => "(" + escapeRegExp(d.text) + ")").join("|") + ")";
3834-
3835-
// After matching a descriptor literal, the following regexp matches the rest of the
3836-
// text up to the end of the line (or */).
3837-
var endOfLineOrEndOfComment = /(?:$|\*\/)/.source
3838-
var messageRemainder = /(?:.*?)/.source
3839-
3840-
// This is the portion of the match we'll return as part of the TODO comment result. We
3841-
// match the literal portion up to the end of the line or end of comment.
3842-
var messagePortion = "(" + literals + messageRemainder + ")";
3843-
var regExpString = preamble + messagePortion + endOfLineOrEndOfComment;
3844-
3845-
// The final regexp will look like this:
3846-
// /((?:\/\/+\s*)|(?:\/\*+\s*)|(?:^(?:\s|\*)*))((?:(TODO\(jason\))|(HACK))(?:.*?))(?:$|\*\/)/gim
3847-
3848-
// The flags of the regexp are important here.
3849-
// 'g' is so that we are doing a global search and can find matches several times
3850-
// in the input.
3851-
//
3852-
// 'i' is for case insensitivity (We do this to match C# TODO comment code).
3853-
//
3854-
// 'm' is so we can find matches in a multiline input.
3855-
return new RegExp(regExpString, "gim");
3856-
}
3812+
function getTodoComments(filename: string, descriptors: TodoCommentDescriptor[]): TodoComment[] {
3813+
filename = TypeScript.switchToForwardSlashes(filename);
38573814

3858-
function getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] {
3859-
fileName = TypeScript.switchToForwardSlashes(fileName);
3815+
var sourceFile = getCurrentSourceFile(filename);
38603816

3861-
var sourceFile = getCurrentSourceFile(fileName);
3862-
var syntaxTree = sourceFile.getSyntaxTree();
38633817
cancellationToken.throwIfCancellationRequested();
38643818

3865-
var text = syntaxTree.text;
3866-
var fileContents = text.substr(0, text.length());
3819+
var fileContents = sourceFile.text;
3820+
38673821
cancellationToken.throwIfCancellationRequested();
38683822

38693823
var result: TodoComment[] = [];
38703824

38713825
if (descriptors.length > 0) {
3872-
var regExp = getTodoCommentsRegExp(descriptors);
3826+
var regExp = getTodoCommentsRegExp();
38733827

38743828
var matchArray: RegExpExecArray;
38753829
while (matchArray = regExp.exec(fileContents)) {
@@ -3884,7 +3838,7 @@ module ts {
38843838
// ["// hack 1", "// ", "hack 1", undefined, "hack"]
38853839
//
38863840
// Here are the relevant capture groups:
3887-
// 0) The full match for the entire regex.
3841+
// 0) The full match for the entire regexp.
38883842
// 1) The preamble to the message portion.
38893843
// 2) The message portion.
38903844
// 3...N) The descriptor that was matched - by index. 'undefined' for each
@@ -3898,20 +3852,19 @@ module ts {
38983852
var preamble = matchArray[1];
38993853
var matchPosition = matchArray.index + preamble.length;
39003854

3901-
// Ok, we have found a match in the file. This is only an acceptable match if
3855+
// OK, we have found a match in the file. This is only an acceptable match if
39023856
// it is contained within a comment.
3903-
var token = TypeScript.findToken(syntaxTree.sourceUnit(), matchPosition);
3857+
var token = getTokenAtPosition(sourceFile, matchPosition);
39043858

3905-
if (matchPosition >= TypeScript.start(token) && matchPosition < TypeScript.end(token)) {
3859+
if (token.getStart() <= matchPosition && matchPosition < token.getEnd()) {
39063860
// match was within the token itself. Not in the comment. Keep searching
39073861
// descriptor.
39083862
continue;
39093863
}
39103864

3911-
// Looks to be within the trivia. See if we can find the comment containing it.
3912-
var triviaList = matchPosition < TypeScript.start(token) ? token.leadingTrivia(syntaxTree.text) : token.trailingTrivia(syntaxTree.text);
3913-
var trivia = findContainingComment(triviaList, matchPosition);
3914-
if (trivia === null) {
3865+
// Looks to be within the trivia. See if we can find the comment containing it.
3866+
if (!getContainingComment(getTrailingComments(fileContents, token.getFullStart()), matchPosition) &&
3867+
!getContainingComment(getLeadingComments(fileContents, token.getFullStart()), matchPosition)) {
39153868
continue;
39163869
}
39173870

@@ -3935,25 +3888,89 @@ module ts {
39353888
}
39363889

39373890
return result;
3938-
}
39393891

3940-
function isLetterOrDigit(char: number): boolean {
3941-
return (char >= TypeScript.CharacterCodes.a && char <= TypeScript.CharacterCodes.z) ||
3942-
(char >= TypeScript.CharacterCodes.A && char <= TypeScript.CharacterCodes.Z) ||
3943-
(char >= TypeScript.CharacterCodes._0 && char <= TypeScript.CharacterCodes._9);
3944-
}
3945-
3946-
function findContainingComment(triviaList: TypeScript.ISyntaxTriviaList, position: number): TypeScript.ISyntaxTrivia {
3947-
for (var i = 0, n = triviaList.count(); i < n; i++) {
3948-
var trivia = triviaList.syntaxTriviaAt(i);
3949-
var fullEnd = trivia.fullStart() + trivia.fullWidth();
3950-
if (trivia.isComment() && trivia.fullStart() <= position && position < fullEnd) {
3951-
return trivia;
3892+
function escapeRegExp(str: string): string {
3893+
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
3894+
}
3895+
3896+
function getTodoCommentsRegExp(): RegExp {
3897+
// NOTE: ?: means 'non-capture group'. It allows us to have groups without having to
3898+
// filter them out later in the final result array.
3899+
3900+
// TODO comments can appear in one of the following forms:
3901+
//
3902+
// 1) // TODO or /////////// TODO
3903+
//
3904+
// 2) /* TODO or /********** TODO
3905+
//
3906+
// 3) /*
3907+
// * TODO
3908+
// */
3909+
//
3910+
// The following three regexps are used to match the start of the text up to the TODO
3911+
// comment portion.
3912+
var singleLineCommentStart = /(?:\/\/+\s*)/.source;
3913+
var multiLineCommentStart = /(?:\/\*+\s*)/.source;
3914+
var anyNumberOfSpacesAndAsterixesAtStartOfLine = /(?:^(?:\s|\*)*)/.source;
3915+
3916+
// Match any of the above three TODO comment start regexps.
3917+
// Note that the outermost group *is* a capture group. We want to capture the preamble
3918+
// so that we can determine the starting position of the TODO comment match.
3919+
var preamble = "(" + anyNumberOfSpacesAndAsterixesAtStartOfLine + "|" + singleLineCommentStart + "|" + multiLineCommentStart + ")";
3920+
3921+
// Takes the descriptors and forms a regexp that matches them as if they were literals.
3922+
// For example, if the descriptors are "TODO(jason)" and "HACK", then this will be:
3923+
//
3924+
// (?:(TODO\(jason\))|(HACK))
3925+
//
3926+
// Note that the outermost group is *not* a capture group, but the innermost groups
3927+
// *are* capture groups. By capturing the inner literals we can determine after
3928+
// matching which descriptor we are dealing with.
3929+
var literals = "(?:" + map(descriptors, d => "(" + escapeRegExp(d.text) + ")").join("|") + ")";
3930+
3931+
// After matching a descriptor literal, the following regexp matches the rest of the
3932+
// text up to the end of the line (or */).
3933+
var endOfLineOrEndOfComment = /(?:$|\*\/)/.source
3934+
var messageRemainder = /(?:.*?)/.source
3935+
3936+
// This is the portion of the match we'll return as part of the TODO comment result. We
3937+
// match the literal portion up to the end of the line or end of comment.
3938+
var messagePortion = "(" + literals + messageRemainder + ")";
3939+
var regExpString = preamble + messagePortion + endOfLineOrEndOfComment;
3940+
3941+
// The final regexp will look like this:
3942+
// /((?:\/\/+\s*)|(?:\/\*+\s*)|(?:^(?:\s|\*)*))((?:(TODO\(jason\))|(HACK))(?:.*?))(?:$|\*\/)/gim
3943+
3944+
// The flags of the regexp are important here.
3945+
// 'g' is so that we are doing a global search and can find matches several times
3946+
// in the input.
3947+
//
3948+
// 'i' is for case insensitivity (We do this to match C# TODO comment code).
3949+
//
3950+
// 'm' is so we can find matches in a multi-line input.
3951+
return new RegExp(regExpString, "gim");
3952+
}
3953+
3954+
function getContainingComment(comments: Comment[], position: number): Comment {
3955+
if (comments) {
3956+
for (var i = 0, n = comments.length; i < n; i++) {
3957+
var comment = comments[i];
3958+
if (comment.pos <= position && position < comment.end) {
3959+
return comment;
3960+
}
3961+
}
39523962
}
3963+
3964+
return undefined;
39533965
}
39543966

3955-
return null;
3967+
function isLetterOrDigit(char: number): boolean {
3968+
return (char >= TypeScript.CharacterCodes.a && char <= TypeScript.CharacterCodes.z) ||
3969+
(char >= TypeScript.CharacterCodes.A && char <= TypeScript.CharacterCodes.Z) ||
3970+
(char >= TypeScript.CharacterCodes._0 && char <= TypeScript.CharacterCodes._9);
3971+
}
39563972
}
3973+
39573974

39583975
function getRenameInfo(fileName: string, position: number): RenameInfo {
39593976
synchronizeHostData();

0 commit comments

Comments
 (0)