flushHeredocTemplateParts modifies in-place the line-leading literal strings to apply the flush heredoc processing rule: find the line with the smallest number of whitespace characters as prefix and then trim that number of characters from all of the lines. This rule is applied to static tokens rat
(parts *templateParts)
| 697 | // so interpolating a string with leading whitespace cannot affect the chosen |
| 698 | // prefix length. |
| 699 | func flushHeredocTemplateParts(parts *templateParts) { |
| 700 | if len(parts.Tokens) == 0 { |
| 701 | // Nothing to do |
| 702 | return |
| 703 | } |
| 704 | |
| 705 | const maxInt = int((^uint(0)) >> 1) |
| 706 | |
| 707 | minSpaces := maxInt |
| 708 | newline := true |
| 709 | var adjust []*templateLiteralToken |
| 710 | for _, ttok := range parts.Tokens { |
| 711 | if newline { |
| 712 | newline = false |
| 713 | var spaces int |
| 714 | if lit, ok := ttok.(*templateLiteralToken); ok { |
| 715 | orig := lit.Val |
| 716 | trimmed := strings.TrimLeftFunc(orig, unicode.IsSpace) |
| 717 | // If a token is entirely spaces and ends with a newline |
| 718 | // then it's a "blank line" and thus not considered for |
| 719 | // space-prefix-counting purposes. |
| 720 | if len(trimmed) == 0 && strings.HasSuffix(orig, "\n") { |
| 721 | spaces = maxInt |
| 722 | } else { |
| 723 | spaceBytes := len(lit.Val) - len(trimmed) |
| 724 | spaces, _ = textseg.TokenCount([]byte(orig[:spaceBytes]), textseg.ScanGraphemeClusters) |
| 725 | adjust = append(adjust, lit) |
| 726 | } |
| 727 | } else if _, ok := ttok.(*templateEndToken); ok { |
| 728 | break // don't process the end token since it never has spaces before it |
| 729 | } |
| 730 | if spaces < minSpaces { |
| 731 | minSpaces = spaces |
| 732 | } |
| 733 | } |
| 734 | if lit, ok := ttok.(*templateLiteralToken); ok { |
| 735 | if strings.HasSuffix(lit.Val, "\n") { |
| 736 | newline = true // The following token, if any, begins a new line |
| 737 | } |
| 738 | } |
| 739 | } |
| 740 | |
| 741 | for _, lit := range adjust { |
| 742 | // Since we want to count space _characters_ rather than space _bytes_, |
| 743 | // we can't just do a straightforward slice operation here and instead |
| 744 | // need to hunt for the split point with a scanner. |
| 745 | valBytes := []byte(lit.Val) |
| 746 | spaceByteCount := 0 |
| 747 | for i := 0; i < minSpaces; i++ { |
| 748 | adv, _, _ := textseg.ScanGraphemeClusters(valBytes, true) |
| 749 | spaceByteCount += adv |
| 750 | valBytes = valBytes[adv:] |
| 751 | } |
| 752 | lit.Val = lit.Val[spaceByteCount:] |
| 753 | lit.SrcRange.Start.Column += minSpaces |
| 754 | lit.SrcRange.Start.Byte += spaceByteCount |
| 755 | } |
| 756 | } |