@@ -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