File: children_compiler.rb

package info (click to toggle)
ruby-haml 6.3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,004 kB
  • sloc: ruby: 9,908; sh: 23; makefile: 11
file content (155 lines) | stat: -rw-r--r-- 4,278 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# frozen_string_literal: true
require 'haml/temple_line_counter'

module Haml
  class Compiler
    class ChildrenCompiler
      def initialize
        @lineno = 1
        @multi_flattener = Temple::Filters::MultiFlattener.new
      end

      def compile(node, &block)
        temple = [:multi]
        return temple if node.children.empty?

        temple << [:whitespace] if prepend_whitespace?(node)
        node.children.each do |n|
          rstrip_whitespace!(temple) if nuke_prev_whitespace?(n)
          insert_newlines!(temple, n)
          temple << moving_lineno(n) { block.call(n) }
          temple << [:whitespace] if insert_whitespace?(n)
        end
        rstrip_whitespace!(temple) if nuke_inner_whitespace?(node)
        temple
      end

      private

      def insert_newlines!(temple, node)
        (node.line - @lineno).times do
          temple << [:newline]
        end

        @lineno = node.line
      end

      def moving_lineno(node, &block)
        # before: As they may have children, we need to increment lineno before compilation.
        case node.type
        when :script, :silent_script
          @lineno += 1
        when :tag
          [node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_hash|
            @lineno += attribute_hash.count("\n")
          end
          @lineno += 1 if node.children.empty? && node.value[:parse]
        end

        temple = block.call # compile

        # after: filter may not have children, and for some dynamic filters we can't predict the number of lines.
        case node.type
        when :filter
          @lineno += TempleLineCounter.count_lines(temple)
        end

        temple
      end

      def prepend_whitespace?(node)
        return false unless %i[comment tag].include?(node.type)
        !nuke_inner_whitespace?(node)
      end

      def nuke_inner_whitespace?(node)
        case
        when node.type == :tag
          node.value[:nuke_inner_whitespace]
        when node.parent.nil?
          false
        else
          nuke_inner_whitespace?(node.parent)
        end
      end

      def nuke_prev_whitespace?(node)
        case node.type
        when :tag
          node.value[:nuke_outer_whitespace]
        when :silent_script
          !node.children.empty? && nuke_prev_whitespace?(node.children.first)
        else
          false
        end
      end

      def nuke_outer_whitespace?(node)
        return false if node.type != :tag
        node.value[:nuke_outer_whitespace]
      end

      def rstrip_whitespace!(temple)
        return if temple.size == 1

        case temple[0]
        when :multi
          case temple[-1][0]
          when :whitespace
            temple.delete_at(-1)
          when :multi, :block
            rstrip_whitespace!(temple[-1])
          end
        when :block
          _block, code, exp = temple
          if code.start_with?(/\s*if\s/)
            # Remove [:whitespace] before `end`
            exp.replace(@multi_flattener.call(exp))
            rstrip_whitespace!(exp)

            # Remove [:whitespace] before `else` if exists
            else_index = find_else_index(exp)
            if else_index
              whitespace_index = else_index - 1
              while exp[whitespace_index] == [:newline]
                whitespace_index -= 1
              end
              if exp[whitespace_index] == [:whitespace]
                exp.delete_at(whitespace_index)
              end
            end
          end
        end
      end

      def find_else_index(temple)
        multi, *args = temple
        return nil if multi != :multi

        args.each_with_index do |arg, index|
          if arg[0] == :code && arg[1].match?(/\A\s*else\s*\z/)
            return index + 1
          end
        end
        nil
      end

      def insert_whitespace?(node)
        return false if nuke_outer_whitespace?(node)

        case node.type
        when :doctype
          node.value[:type] != 'xml'
        when :comment, :plain, :tag
          true
        when :script
          node.children.empty? && !nuke_inner_whitespace?(node)
        when :filter
          !%w[ruby].include?(node.value[:name])
        else
          false
        end
      end
    end
  end
end