diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index c8541da79d1..2d5ac780d6a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5153,6 +5153,7 @@ namespace ts { /** * True if node is of some token syntax kind. * For example, this is true for an IfKeyword but not for an IfStatement. + * Literals are considered tokens, except TemplateLiteral, but does include TemplateHead/Middle/Tail. */ export function isToken(n: Node): boolean { return n.kind >= SyntaxKind.FirstToken && n.kind <= SyntaxKind.LastToken; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 8b4bb1ad212..8df8a902734 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -761,23 +761,12 @@ namespace ts { Debug.assert(!(result && isWhiteSpaceOnlyJsxText(result))); return result; - function findRightmostToken(n: Node): Node { - if (isToken(n)) { + function find(n: Node): Node | undefined { + if (isNonWhitespaceToken(n)) { return n; } - const children = n.getChildren(); - const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length); - return candidate && findRightmostToken(candidate); - - } - - function find(n: Node): Node { - if (isToken(n)) { - return n; - } - - const children = n.getChildren(); + const children = n.getChildren(sourceFile); for (let i = 0; i < children.length; i++) { const child = children[i]; // Note that the span of a node's tokens is [node.getStart(...), node.end). @@ -795,7 +784,7 @@ namespace ts { if (lookInPreviousChild) { // actual start of the node is past the position - previous token should be at the end of previous child const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ i); - return candidate && findRightmostToken(candidate); + return candidate && findRightmostToken(candidate, sourceFile); } else { // candidate should be in this node @@ -812,23 +801,37 @@ namespace ts { // Namely we are skipping the check: 'position < node.end' if (children.length) { const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length); - return candidate && findRightmostToken(candidate); + return candidate && findRightmostToken(candidate, sourceFile); } } + } - /** - * Finds the rightmost child to the left of `children[exclusiveStartPosition]` which is a non-all-whitespace token or has constituent tokens. - */ - function findRightmostChildNodeWithTokens(children: Node[], exclusiveStartPosition: number): Node { - for (let i = exclusiveStartPosition - 1; i >= 0; i--) { - const child = children[i]; + function isNonWhitespaceToken(n: Node): boolean { + return isToken(n) && !isWhiteSpaceOnlyJsxText(n); + } - if (isWhiteSpaceOnlyJsxText(child)) { - Debug.assert(i > 0, "`JsxText` tokens should not be the first child of `JsxElement | JsxSelfClosingElement`"); - } - else if (nodeHasTokens(children[i])) { - return children[i]; - } + function findRightmostToken(n: Node, sourceFile: SourceFile): Node | undefined { + if (isNonWhitespaceToken(n)) { + return n; + } + + const children = n.getChildren(sourceFile); + const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length); + return candidate && findRightmostToken(candidate, sourceFile); + } + + /** + * Finds the rightmost child to the left of `children[exclusiveStartPosition]` which is a non-all-whitespace token or has constituent tokens. + */ + function findRightmostChildNodeWithTokens(children: Node[], exclusiveStartPosition: number): Node | undefined { + for (let i = exclusiveStartPosition - 1; i >= 0; i--) { + const child = children[i]; + + if (isWhiteSpaceOnlyJsxText(child)) { + Debug.assert(i > 0, "`JsxText` tokens should not be the first child of `JsxElement | JsxSelfClosingElement`"); + } + else if (nodeHasTokens(children[i])) { + return children[i]; } } } @@ -893,7 +896,7 @@ namespace ts { return false; } - export function isWhiteSpaceOnlyJsxText(node: Node): node is JsxText { + function isWhiteSpaceOnlyJsxText(node: Node): boolean { return isJsxText(node) && node.containsOnlyWhiteSpaces; } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 90a73a823eb..6ef91a9a057 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3211,6 +3211,7 @@ declare namespace ts { /** * True if node is of some token syntax kind. * For example, this is true for an IfKeyword but not for an IfStatement. + * Literals are considered tokens, except TemplateLiteral, but does include TemplateHead/Middle/Tail. */ function isToken(n: Node): boolean; function isLiteralExpression(node: Node): node is LiteralExpression; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 571e6aadcd3..b6877639e5c 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3266,6 +3266,7 @@ declare namespace ts { /** * True if node is of some token syntax kind. * For example, this is true for an IfKeyword but not for an IfStatement. + * Literals are considered tokens, except TemplateLiteral, but does include TemplateHead/Middle/Tail. */ function isToken(n: Node): boolean; function isLiteralExpression(node: Node): node is LiteralExpression;