(P: ParseState)
| 2337 | } |
| 2338 | |
| 2339 | function parseDoubleQuoted(P: ParseState): TsNode { |
| 2340 | const qStart = P.L.b |
| 2341 | advance(P.L) |
| 2342 | const qEnd = P.L.b |
| 2343 | const openQ = mk(P, '"', qStart, qEnd, []) |
| 2344 | const parts: TsNode[] = [openQ] |
| 2345 | let contentStart = P.L.b |
| 2346 | let contentStartI = P.L.i |
| 2347 | const flushContent = (): void => { |
| 2348 | if (P.L.b > contentStart) { |
| 2349 | // Tree-sitter's extras rule /\s/ has higher precedence than |
| 2350 | // string_content (prec -1), so whitespace-only segments are elided. |
| 2351 | // `" ${x} "` → (string (expansion)) not (string (string_content)(expansion)(string_content)). |
| 2352 | // Note: this intentionally diverges from preserving all content — cc |
| 2353 | // tests relying on whitespace-only string_content need updating |
| 2354 | // (CCReconcile). |
| 2355 | const txt = P.src.slice(contentStartI, P.L.i) |
| 2356 | if (!/^[ \t]+$/.test(txt)) { |
| 2357 | parts.push(mk(P, 'string_content', contentStart, P.L.b, [])) |
| 2358 | } |
| 2359 | } |
| 2360 | } |
| 2361 | while (P.L.i < P.L.len) { |
| 2362 | const c = peek(P.L) |
| 2363 | if (c === '"') break |
| 2364 | if (c === '\\' && P.L.i + 1 < P.L.len) { |
| 2365 | advance(P.L) |
| 2366 | advance(P.L) |
| 2367 | continue |
| 2368 | } |
| 2369 | if (c === '\n') { |
| 2370 | // Split string_content at newline |
| 2371 | flushContent() |
| 2372 | advance(P.L) |
| 2373 | contentStart = P.L.b |
| 2374 | contentStartI = P.L.i |
| 2375 | continue |
| 2376 | } |
| 2377 | if (c === '$') { |
| 2378 | const c1 = peek(P.L, 1) |
| 2379 | if ( |
| 2380 | c1 === '(' || |
| 2381 | c1 === '{' || |
| 2382 | isIdentStart(c1) || |
| 2383 | SPECIAL_VARS.has(c1) || |
| 2384 | isDigit(c1) |
| 2385 | ) { |
| 2386 | flushContent() |
| 2387 | const exp = parseDollarLike(P) |
| 2388 | if (exp) parts.push(exp) |
| 2389 | contentStart = P.L.b |
| 2390 | contentStartI = P.L.i |
| 2391 | continue |
| 2392 | } |
| 2393 | // Bare $ not at end-of-string: tree-sitter emits it as an anonymous |
| 2394 | // '$' token, which splits string_content. $ immediately before the |
| 2395 | // closing " is absorbed into the preceding string_content. |
| 2396 | if (c1 !== '"' && c1 !== '') { |
no test coverage detected