( P: ParseState, start: number, end: number, )
| 1927 | } |
| 1928 | |
| 1929 | function parseHeredocBodyContent( |
| 1930 | P: ParseState, |
| 1931 | start: number, |
| 1932 | end: number, |
| 1933 | ): TsNode[] { |
| 1934 | // Parse expansions inside an unquoted heredoc body. |
| 1935 | const saved = saveLex(P.L) |
| 1936 | // Position lexer at body start |
| 1937 | restoreLexToByte(P, start) |
| 1938 | const out: TsNode[] = [] |
| 1939 | let contentStart = P.L.b |
| 1940 | // tree-sitter-bash's heredoc_body rule hides the initial text segment |
| 1941 | // (_heredoc_body_beginning) — only content AFTER the first expansion is |
| 1942 | // emitted as heredoc_content. Track whether we've seen an expansion yet. |
| 1943 | let sawExpansion = false |
| 1944 | while (P.L.b < end) { |
| 1945 | const c = peek(P.L) |
| 1946 | // Backslash escapes suppress expansion: \$ \` stay literal in heredoc. |
| 1947 | if (c === '\\') { |
| 1948 | const nxt = peek(P.L, 1) |
| 1949 | if (nxt === '$' || nxt === '`' || nxt === '\\') { |
| 1950 | advance(P.L) |
| 1951 | advance(P.L) |
| 1952 | continue |
| 1953 | } |
| 1954 | advance(P.L) |
| 1955 | continue |
| 1956 | } |
| 1957 | if (c === '$' || c === '`') { |
| 1958 | const preB = P.L.b |
| 1959 | const exp = parseDollarLike(P) |
| 1960 | // Bare `$` followed by non-name (e.g. `$'` in a regex) returns a lone |
| 1961 | // '$' leaf, not an expansion — treat as literal content, don't split. |
| 1962 | if ( |
| 1963 | exp && |
| 1964 | (exp.type === 'simple_expansion' || |
| 1965 | exp.type === 'expansion' || |
| 1966 | exp.type === 'command_substitution' || |
| 1967 | exp.type === 'arithmetic_expansion') |
| 1968 | ) { |
| 1969 | if (sawExpansion && preB > contentStart) { |
| 1970 | out.push(mk(P, 'heredoc_content', contentStart, preB, [])) |
| 1971 | } |
| 1972 | out.push(exp) |
| 1973 | contentStart = P.L.b |
| 1974 | sawExpansion = true |
| 1975 | } |
| 1976 | continue |
| 1977 | } |
| 1978 | advance(P.L) |
| 1979 | } |
| 1980 | // Only emit heredoc_content children if there were expansions — otherwise |
| 1981 | // the heredoc_body is a leaf node (tree-sitter convention). |
| 1982 | if (sawExpansion) { |
| 1983 | out.push(mk(P, 'heredoc_content', contentStart, end, [])) |
| 1984 | } |
| 1985 | restoreLex(P.L, saved) |
| 1986 | return out |
no test coverage detected