(command: string)
| 519 | * Used by the pre-split gate to strip safe heredocs and re-check the remainder. |
| 520 | */ |
| 521 | export function stripSafeHeredocSubstitutions(command: string): string | null { |
| 522 | if (!HEREDOC_IN_SUBSTITUTION.test(command)) return null |
| 523 | |
| 524 | const heredocPattern = |
| 525 | /\$\(cat[ \t]*<<(-?)[ \t]*(?:'+([A-Za-z_]\w*)'+|\\([A-Za-z_]\w*))/g |
| 526 | let result = command |
| 527 | let found = false |
| 528 | let match |
| 529 | const ranges: Array<{ start: number; end: number }> = [] |
| 530 | while ((match = heredocPattern.exec(command)) !== null) { |
| 531 | if (match.index > 0 && command[match.index - 1] === '\\') continue |
| 532 | const delimiter = match[2] || match[3] |
| 533 | if (!delimiter) continue |
| 534 | const isDash = match[1] === '-' |
| 535 | const operatorEnd = match.index + match[0].length |
| 536 | |
| 537 | const afterOperator = command.slice(operatorEnd) |
| 538 | const openLineEnd = afterOperator.indexOf('\n') |
| 539 | if (openLineEnd === -1) continue |
| 540 | if (!/^[ \t]*$/.test(afterOperator.slice(0, openLineEnd))) continue |
| 541 | |
| 542 | const bodyStart = operatorEnd + openLineEnd + 1 |
| 543 | const bodyLines = command.slice(bodyStart).split('\n') |
| 544 | for (let i = 0; i < bodyLines.length; i++) { |
| 545 | const rawLine = bodyLines[i]! |
| 546 | const line = isDash ? rawLine.replace(/^\t*/, '') : rawLine |
| 547 | if (line.startsWith(delimiter)) { |
| 548 | const after = line.slice(delimiter.length) |
| 549 | let closePos = -1 |
| 550 | if (/^[ \t]*\)/.test(after)) { |
| 551 | const lineStart = |
| 552 | bodyStart + |
| 553 | bodyLines.slice(0, i).join('\n').length + |
| 554 | (i > 0 ? 1 : 0) |
| 555 | closePos = command.indexOf(')', lineStart) |
| 556 | } else if (after === '') { |
| 557 | const nextLine = bodyLines[i + 1] |
| 558 | if (nextLine !== undefined && /^[ \t]*\)/.test(nextLine)) { |
| 559 | const nextLineStart = |
| 560 | bodyStart + bodyLines.slice(0, i + 1).join('\n').length + 1 |
| 561 | closePos = command.indexOf(')', nextLineStart) |
| 562 | } |
| 563 | } |
| 564 | if (closePos !== -1) { |
| 565 | ranges.push({ start: match.index, end: closePos + 1 }) |
| 566 | found = true |
| 567 | } |
| 568 | break |
| 569 | } |
| 570 | } |
| 571 | } |
| 572 | if (!found) return null |
| 573 | for (let i = ranges.length - 1; i >= 0; i--) { |
| 574 | const r = ranges[i]! |
| 575 | result = result.slice(0, r.start) + result.slice(r.end) |
| 576 | } |
| 577 | return result |
| 578 | } |
no test coverage detected