@@ -720,8 +720,14 @@ namespace ts {
720720 }
721721 }
722722
723- export function findPrecedingToken ( position : number , sourceFile : SourceFile , startNode ?: Node , includeJsDoc ?: boolean ) : Node {
724- return find ( startNode || sourceFile ) ;
723+ /**
724+ * Finds the rightmost token satisfying `token.end <= position`,
725+ * excluding `JsxText` tokens containing only whitespace.
726+ */
727+ export function findPrecedingToken ( position : number , sourceFile : SourceFile , startNode ?: Node , includeJsDoc ?: boolean ) : Node | undefined {
728+ const result = find ( startNode || sourceFile ) ;
729+ Debug . assert ( ! ( result && isWhiteSpaceOnlyJsxText ( result ) ) ) ;
730+ return result ;
725731
726732 function findRightmostToken ( n : Node ) : Node {
727733 if ( isToken ( n ) ) {
@@ -743,18 +749,16 @@ namespace ts {
743749 for ( let i = 0 ; i < children . length ; i ++ ) {
744750 const child = children [ i ] ;
745751 // Note that the span of a node's tokens is [node.getStart(...), node.end).
746- // Given that `position < child.end` and child has constiutent tokens* , we distinguish these cases:
752+ // Given that `position < child.end` and child has constituent tokens, we distinguish these cases:
747753 // 1) `position` precedes `child`'s tokens or `child` has no tokens (ie: in a comment or whitespace preceding `child`):
748754 // we need to find the last token in a previous child.
749755 // 2) `position` is within the same span: we recurse on `child`.
750- // * JsxText is exceptional in that its tokens are (non-trivia) whitespace, which we do not want to return.
751- // TODO(arozga): shouldn't `findRightmost...` need to handle JsxText?
752756 if ( position < child . end ) {
753757 const start = child . getStart ( sourceFile , includeJsDoc ) ;
754758 const lookInPreviousChild =
755759 ( start >= position ) || // cursor in the leading trivia
756760 ! nodeHasTokens ( child ) ||
757- ( child . kind === SyntaxKind . JsxText && start === child . end ) ; // whitespace only JsxText
761+ isWhiteSpaceOnlyJsxText ( child ) ;
758762
759763 if ( lookInPreviousChild ) {
760764 // actual start of the node is past the position - previous token should be at the end of previous child
@@ -781,11 +785,16 @@ namespace ts {
781785 }
782786
783787 /**
784- * Finds the rightmost child to the left of `children[exclusiveStartPosition]` which has constituent tokens.
788+ * Finds the rightmost child to the left of `children[exclusiveStartPosition]` which is a non-all-whitespace token or has constituent tokens.
785789 */
786790 function findRightmostChildNodeWithTokens ( children : Node [ ] , exclusiveStartPosition : number ) : Node {
787791 for ( let i = exclusiveStartPosition - 1 ; i >= 0 ; i -- ) {
788- if ( nodeHasTokens ( children [ i ] ) ) {
792+ const child = children [ i ] ;
793+
794+ if ( isWhiteSpaceOnlyJsxText ( child ) ) {
795+ Debug . assert ( i > 0 , "`JsxText` tokens should not be the first child of `JsxElement | JsxSelfClosingElement`" ) ;
796+ }
797+ else if ( nodeHasTokens ( children [ i ] ) ) {
789798 return children [ i ] ;
790799 }
791800 }
@@ -853,6 +862,11 @@ namespace ts {
853862 return false ;
854863 }
855864
865+ export function isWhiteSpaceOnlyJsxText ( node : Node ) : node is JsxText {
866+ return isJsxText ( node ) && node . containsOnlyWhiteSpaces ;
867+ }
868+
869+
856870 export function isInTemplateString ( sourceFile : SourceFile , position : number ) {
857871 const token = getTokenAtPosition ( sourceFile , position , /*includeJsDocComment*/ false ) ;
858872 return isTemplateLiteralKind ( token . kind ) && position > token . getStart ( sourceFile ) ;
@@ -888,7 +902,7 @@ namespace ts {
888902
889903 function nodeHasTokens ( n : Node ) : boolean {
890904 // If we have a token or node that has a non-zero width, it must have tokens.
891- // Note, that getWidth() does not take trivia into account.
905+ // Note: getWidth() does not take trivia into account.
892906 return n . getWidth ( ) !== 0 ;
893907 }
894908
0 commit comments