33 */
44
55import { createRequire } from "node:module" ;
6- import { sourceText , initSourceText } from "./source_code.js" ;
6+ import { sourceText } from "./source_code.js" ;
77import { debugAssert , debugAssertIsNonNull } from "../utils/asserts.js" ;
88
99import type { Comment , Node , NodeOrToken } from "./types.ts" ;
@@ -1699,9 +1699,7 @@ export function getTokenByRangeStart(
16991699 return null ;
17001700}
17011701
1702- // Regex that tests for whitespace.
1703- // TODO: Is this too liberal? Should it be a more constrained set of whitespace characters?
1704- const WHITESPACE_REGEXP = / \s / ;
1702+ const JSX_WHITESPACE_REGEXP = / \s / u;
17051703
17061704/**
17071705 * Determine if two nodes or tokens have at least one whitespace character between them.
@@ -1712,23 +1710,25 @@ const WHITESPACE_REGEXP = /\s/;
17121710 * Checks for whitespace *between tokens*, not including whitespace *inside tokens*.
17131711 * e.g. Returns `false` for `isSpaceBetween(x, y)` in `x+" "+y`.
17141712 *
1715- * TODO: Implementation is not quite right at present.
1716- * We don't use tokens, so return `true` for `isSpaceBetween(x, y)` in `x+" "+y`, but should return `false`.
1717- * Note: `checkInsideOfJSXText === false` in ESLint's implementation of `sourceCode.isSpaceBetween`.
1718- * https://github.com/eslint/eslint/blob/523c076866400670fb2192a3f55dbf7ad3469247/lib/languages/js/source-code/source-code.js#L182-L230
1719- *
1720- * @param nodeOrToken1 - The first node or token to check between.
1721- * @param nodeOrToken2 - The second node or token to check between.
1713+ * @param first - The first node or token to check between.
1714+ * @param second - The second node or token to check between.
17221715 * @returns `true` if there is a whitespace character between
17231716 * any of the tokens found between the two given nodes or tokens.
17241717 */
1725- export function isSpaceBetween ( nodeOrToken1 : NodeOrToken , nodeOrToken2 : NodeOrToken ) : boolean {
1726- const range1 = nodeOrToken1 . range ,
1727- range2 = nodeOrToken2 . range ,
1728- start1 = range1 [ 0 ] ,
1729- start2 = range2 [ 0 ] ;
1718+ export function isSpaceBetween ( first : NodeOrToken , second : NodeOrToken ) : boolean {
1719+ if ( tokensWithComments === null ) {
1720+ if ( tokens === null ) initTokens ( ) ;
1721+ initTokensWithComments ( ) ;
1722+ }
1723+ debugAssertIsNonNull ( tokensWithComments ) ;
17301724
1731- // Find the gap between the two nodes/tokens.
1725+ const range1 = first . range ,
1726+ range2 = second . range ;
1727+
1728+ // Find the range between the two nodes/tokens.
1729+ //
1730+ // Unlike other methods which require the user to pass the nodes in order of appearance,
1731+ // `isSpaceBetween()` is invariant over the sequence of the two nodes.
17321732 //
17331733 // 1 node/token can completely enclose another, but they can't *partially* overlap.
17341734 // ```
@@ -1741,23 +1741,43 @@ export function isSpaceBetween(nodeOrToken1: NodeOrToken, nodeOrToken2: NodeOrTo
17411741 // |------------|
17421742 // ```
17431743 // We use that invariant to reduce this to a single branch.
1744- let gapStart , gapEnd ;
1745- if ( start1 < start2 ) {
1746- gapStart = range1 [ 1 ] ; // end1
1747- gapEnd = start2 ;
1744+ let rangeStart : number = range1 [ 0 ] ,
1745+ rangeEnd : number = range2 [ 0 ] ;
1746+ if ( rangeStart < rangeEnd ) {
1747+ rangeStart = range1 [ 1 ] ;
17481748 } else {
1749- gapStart = range2 [ 1 ] ; // end2;
1750- gapEnd = start1 ;
1749+ rangeEnd = rangeStart ;
1750+ rangeStart = range2 [ 1 ] ;
1751+ }
1752+
1753+ // Binary search for the first token past `rangeStart`.
1754+ // Unless `first` and `second` are adjacent or overlapping,
1755+ // the token will be the first token between the two nodes.
1756+ const tokensWithCommentsLength = tokensWithComments . length ;
1757+ let tokenBetweenIndex = tokensWithCommentsLength ;
1758+ for ( let lo = 0 ; lo < tokenBetweenIndex ; ) {
1759+ const mid = ( lo + tokenBetweenIndex ) >> 1 ;
1760+ if ( tokensWithComments [ mid ] . range [ 0 ] < rangeStart ) {
1761+ lo = mid + 1 ;
1762+ } else {
1763+ tokenBetweenIndex = mid ;
1764+ }
17511765 }
17521766
1753- // If `gapStart >= gapEnd`, one node encloses the other, or the two are directly adjacent
1754- if ( gapStart >= gapEnd ) return false ;
1755-
1756- // Check if there's any whitespace in the gap
1757- if ( sourceText === null ) initSourceText ( ) ;
1758- debugAssertIsNonNull ( sourceText ) ;
1767+ for (
1768+ let lastTokenEnd = rangeStart ;
1769+ tokenBetweenIndex < tokensWithCommentsLength ;
1770+ tokenBetweenIndex ++
1771+ ) {
1772+ const { range } = tokensWithComments [ tokenBetweenIndex ] ;
1773+ const tokenStart = range [ 0 ] ;
1774+ // The first token of the later node should undergo the check in the second branch
1775+ if ( tokenStart > rangeEnd ) break ;
1776+ if ( tokenStart !== lastTokenEnd ) return true ;
1777+ lastTokenEnd = range [ 1 ] ;
1778+ }
17591779
1760- return WHITESPACE_REGEXP . test ( sourceText . slice ( gapStart , gapEnd ) ) ;
1780+ return false ;
17611781}
17621782
17631783/**
@@ -1775,13 +1795,67 @@ export function isSpaceBetween(nodeOrToken1: NodeOrToken, nodeOrToken2: NodeOrTo
17751795 *
17761796 * @deprecated Use `sourceCode.isSpaceBetween` instead.
17771797 *
1778- * TODO: Implementation is not quite right at present, for same reasons as `SourceCode#isSpaceBetween`.
1779- *
1780- * @param nodeOrToken1 - The first node or token to check between.
1781- * @param nodeOrToken2 - The second node or token to check between.
1798+ * @param first - The first node or token to check between.
1799+ * @param second - The second node or token to check between.
17821800 * @returns `true` if there is a whitespace character between
17831801 * any of the tokens found between the two given nodes or tokens.
17841802 */
1785- export function isSpaceBetweenTokens ( token1 : NodeOrToken , token2 : NodeOrToken ) : boolean {
1786- return isSpaceBetween ( token1 , token2 ) ;
1803+ export function isSpaceBetweenTokens ( first : NodeOrToken , second : NodeOrToken ) : boolean {
1804+ if ( tokensWithComments === null ) {
1805+ if ( tokens === null ) initTokens ( ) ;
1806+ initTokensWithComments ( ) ;
1807+ }
1808+ debugAssertIsNonNull ( tokensWithComments ) ;
1809+
1810+ const range1 = first . range ,
1811+ range2 = second . range ;
1812+
1813+ // Find the range between the two nodes/tokens.
1814+ // Unlike other methods which require the user to pass the nodes in order of appearance,
1815+ // `isSpaceBetweenTokens()` is invariant over the sequence of the two nodes.
1816+ // See comment in `isSpaceBetween` about why this is a single branch.
1817+ let rangeStart : number = range1 [ 0 ] ,
1818+ rangeEnd : number = range2 [ 0 ] ;
1819+ if ( rangeStart < rangeEnd ) {
1820+ rangeStart = range1 [ 1 ] ;
1821+ } else {
1822+ rangeEnd = rangeStart ;
1823+ rangeStart = range2 [ 1 ] ;
1824+ }
1825+
1826+ // Binary search for the first token past `rangeStart`.
1827+ // Unless `first` and `second` are adjacent or overlapping,
1828+ // the token will be the first token between the two nodes.
1829+ const tokensWithCommentsLength = tokensWithComments . length ;
1830+ let tokenBetweenIndex = tokensWithCommentsLength ;
1831+ for ( let lo = 0 ; lo < tokenBetweenIndex ; ) {
1832+ const mid = ( lo + tokenBetweenIndex ) >> 1 ;
1833+ if ( tokensWithComments [ mid ] . range [ 0 ] < rangeStart ) {
1834+ lo = mid + 1 ;
1835+ } else {
1836+ tokenBetweenIndex = mid ;
1837+ }
1838+ }
1839+
1840+ for (
1841+ let lastTokenEnd = rangeStart ;
1842+ tokenBetweenIndex < tokensWithCommentsLength ;
1843+ tokenBetweenIndex ++
1844+ ) {
1845+ const token = tokensWithComments [ tokenBetweenIndex ] ,
1846+ { range } = token ,
1847+ tokenStart = range [ 0 ] ;
1848+
1849+ // The first token of the later node should undergo the check in the second branch
1850+ if ( tokenStart > rangeEnd ) break ;
1851+ if (
1852+ tokenStart !== lastTokenEnd ||
1853+ ( token . type === "JSXText" && JSX_WHITESPACE_REGEXP . test ( token . value ) )
1854+ ) {
1855+ return true ;
1856+ }
1857+ lastTokenEnd = range [ 1 ] ;
1858+ }
1859+
1860+ return false ;
17871861}
0 commit comments