@@ -1204,47 +1204,34 @@ export class VariableResolver {
12041204 }
12051205
12061206 /**
1207- * Whether the `(` at this index opens a control-flow head rather than a value.
1207+ * Whether a `(` opens a control-flow head rather than a value.
12081208 *
12091209 * What follows the matching `)` differs entirely between the two — a statement, where a regex
12101210 * literal may begin, versus an operator, where a `/` divides — and the closing parenthesis
1211- * carries no trace of which it was. Reading the keyword in front of the opening one is what
1212- * lets `if (a) /re/.test(b)` and `(a + b) / 2` both scan correctly.
1211+ * carries no trace of which it was. The keyword in front of the opening one is what tells
1212+ * them apart, so `if (a) /re/.test(b)` and `(a + b) / 2` both scan correctly.
1213+ *
1214+ * Both inputs come from the forward scan rather than a walk back through the source: the
1215+ * scan already knows which characters were code and which sat inside a comment, and reading
1216+ * backwards cannot recover that — the opener of `/* a /* b *\/` is its first delimiter, not
1217+ * its last, and only the scan that passed through knows the difference.
12131218 */
1214- private opensControlFlowHead ( template : string , index : number ) : boolean {
1215- let end = index
1216- while ( end > 0 && WHITESPACE_CHAR . test ( template [ end - 1 ] ) ) {
1217- end --
1218- }
1219- let start = end
1220- while ( start > 0 && this . isJavaScriptIdentifierChar ( template [ start - 1 ] ) ) {
1221- start --
1222- }
1223- if ( ! CONTROL_FLOW_HEAD_KEYWORDS . has ( template . slice ( start , end ) ) ) {
1219+ private opensControlFlowHead (
1220+ template : string ,
1221+ previousSignificantIndex : number ,
1222+ precededByPropertyAccess : boolean
1223+ ) : boolean {
1224+ if ( precededByPropertyAccess || previousSignificantIndex < 0 ) {
12241225 return false
12251226 }
1226-
1227- // `p.catch(fn)` is a method call whose name happens to be a keyword, and what follows its
1228- // `)` is an operator, not a statement. A control-flow head can never be a property access,
1229- // and a comment can stand between the two (`p./* c */catch(fn)`), so a comment is stepped
1230- // over rather than treated as an answer — `/* c */ if (x)` is still a head.
1231- let before = this . skipWhitespaceBackward ( template , start )
1232- while ( template [ before - 1 ] === '/' && template [ before - 2 ] === '*' ) {
1233- const opening = template . lastIndexOf ( '/*' , before - 2 )
1234- if ( opening < 0 ) {
1235- return true
1236- }
1237- before = this . skipWhitespaceBackward ( template , opening )
1227+ if ( ! this . isJavaScriptIdentifierChar ( template [ previousSignificantIndex ] ) ) {
1228+ return false
12381229 }
1239- return template [ before - 1 ] !== '.'
1240- }
1241-
1242- private skipWhitespaceBackward ( template : string , index : number ) : number {
1243- let cursor = index
1244- while ( cursor > 0 && WHITESPACE_CHAR . test ( template [ cursor - 1 ] ) ) {
1245- cursor --
1230+ let start = previousSignificantIndex
1231+ while ( start > 0 && this . isJavaScriptIdentifierChar ( template [ start - 1 ] ) ) {
1232+ start --
12461233 }
1247- return cursor
1234+ return CONTROL_FLOW_HEAD_KEYWORDS . has ( template . slice ( start , previousSignificantIndex + 1 ) )
12481235 }
12491236
12501237 private matchesKeywordAt ( template : string , index : number , keyword : string ) : boolean {
@@ -1376,6 +1363,7 @@ export class VariableResolver {
13761363 const modes : CodeScanMode [ ] = [ { type : 'normal' } ]
13771364 let lastSignificantIndex = - 1
13781365 const openParenIsControlHead : boolean [ ] = [ ]
1366+ let identifierFollowsPropertyAccess = false
13791367 const controlHeadParenCloses = new Set < number > ( )
13801368 const regexCloseIndices = new Set < number > ( )
13811369
@@ -1483,8 +1471,21 @@ export class VariableResolver {
14831471 if ( ! WHITESPACE_CHAR . test ( char ) ) {
14841472 lastSignificantIndex = i
14851473 }
1486- if ( char === '(' ) {
1487- openParenIsControlHead . push ( this . opensControlFlowHead ( template , i ) )
1474+ if ( this . isJavaScriptIdentifierChar ( char ) ) {
1475+ if (
1476+ previousSignificantIndex < 0 ||
1477+ ! this . isJavaScriptIdentifierChar ( template [ previousSignificantIndex ] )
1478+ ) {
1479+ identifierFollowsPropertyAccess = template [ previousSignificantIndex ] === '.'
1480+ }
1481+ } else if ( char === '(' ) {
1482+ openParenIsControlHead . push (
1483+ this . opensControlFlowHead (
1484+ template ,
1485+ previousSignificantIndex ,
1486+ identifierFollowsPropertyAccess
1487+ )
1488+ )
14881489 } else if ( char === ')' ) {
14891490 if ( openParenIsControlHead . pop ( ) ) controlHeadParenCloses . add ( i )
14901491 }
@@ -1552,8 +1553,21 @@ export class VariableResolver {
15521553 if ( ! WHITESPACE_CHAR . test ( char ) ) {
15531554 lastSignificantIndex = i
15541555 }
1555- if ( char === '(' ) {
1556- openParenIsControlHead . push ( this . opensControlFlowHead ( template , i ) )
1556+ if ( this . isJavaScriptIdentifierChar ( char ) ) {
1557+ if (
1558+ previousSignificantIndex < 0 ||
1559+ ! this . isJavaScriptIdentifierChar ( template [ previousSignificantIndex ] )
1560+ ) {
1561+ identifierFollowsPropertyAccess = template [ previousSignificantIndex ] === '.'
1562+ }
1563+ } else if ( char === '(' ) {
1564+ openParenIsControlHead . push (
1565+ this . opensControlFlowHead (
1566+ template ,
1567+ previousSignificantIndex ,
1568+ identifierFollowsPropertyAccess
1569+ )
1570+ )
15571571 } else if ( char === ')' ) {
15581572 if ( openParenIsControlHead . pop ( ) ) controlHeadParenCloses . add ( i )
15591573 }
0 commit comments