Class: Minifyrb::Minifier

Inherits:
Object
  • Object
show all
Defined in:
lib/minifyrb/minifier.rb

Constant Summary collapse

AFTER_SPACE_REQUIRED_KEYWORDS =
i(
  KEYWORD_ALIAS KEYWORD_AND KEYWORD_BEGIN KEYWORD_BREAK KEYWORD_CASE KEYWORD_CLASS KEYWORD_DEF KEYWORD_DO KEYWORD_ELSE
  KEYWORD_ELSIF KEYWORD_ENSURE KEYWORD_FOR KEYWORD_IF KEYWORD_IF_MODIFIER KEYWORD_IN KEYWORD_MODULE KEYWORD_NEXT
  KEYWORD_NOT KEYWORD_OR KEYWORD_REDO KEYWORD_RESCUE KEYWORD_RESCUE_MODIFIER KEYWORD_RETURN KEYWORD_SUPER KEYWORD_THEN
  KEYWORD_UNDEF KEYWORD_UNLESS KEYWORD_UNLESS_MODIFIER KEYWORD_UNTIL KEYWORD_UNTIL_MODIFIER KEYWORD_WHEN KEYWORD_WHILE
  KEYWORD_WHILE_MODIFIER KEYWORD_YIELD
)
BEFORE_SPACE_REQUIRED_KEYWORDS =
i(
  KEYWORD_AND KEYWORD_DO KEYWORD_IF_MODIFIER KEYWORD_IN KEYWORD_OR KEYWORD_RESCUE_MODIFIER KEYWORD_THEN
  KEYWORD_UNLESS_MODIFIER KEYWORD_UNTIL_MODIFIER KEYWORD_WHILE_MODIFIER
)
NUMERIC_LITERAL_TYPES =
i(FLOAT FLOAT_RATIONAL INTEGER INTEGER_RATIONAL)
PERCENT_ARRAY_LITERAL_TYPES =
i(PERCENT_LOWER_W PERCENT_UPPER_W PERCENT_LOWER_I PERCENT_UPPER_I)
NO_DELIMITER_VALUE_TYPES =
i(CONSTANT IDENTIFIER) + NUMERIC_LITERAL_TYPES
REQUIRE_SPACE_AFTER_IDENTIFIER_TYPES =
i(KEYWORD_SELF KEYWORD_TRUE KEYWORD_FALSE KEYWORD_NIL METHOD_NAME) + NUMERIC_LITERAL_TYPES
CLOSING_DELIMITER_TYPES =
i(PARENTHESIS_RIGHT BRACKET_RIGHT BRACE_RIGHT)

Instance Method Summary collapse

Constructor Details

#initialize(source, filepath: nil) ⇒ Minifier

Returns a new instance of Minifier.

Raises:

  • (SyntaxError)


24
25
26
27
28
29
30
31
32
33
34
# File 'lib/minifyrb/minifier.rb', line 24

def initialize(source, filepath: nil)
  result = Prism.lex(source)
  raise SyntaxError, filepath || 'compile error' unless result.errors.empty?

  @tokens = result.value

  @in_heredoc = false
  @string_quote = nil
  @minified_values = []
  @heredoc_content_tokens = []
end

Instance Method Details

#minifyObject



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
156
157
158
159
160
# File 'lib/minifyrb/minifier.rb', line 36

def minify
  squiggly_heredoc = false
  prev_token = nil

  @tokens.each_cons(2) do |(token, _lex_state), (next_token, _next_lex_state)|
    case token.type
    when :COMMENT
      if prev_token && prev_token.location.start_line < next_token.location.start_line && prev_token.type == :IDENTIFIER && next_token.type == :IDENTIFIER
        append_token_value_to_minified_values(';')
      elsif prev_token && prev_token.location.start_line == token.location.start_line && token.location.start_line < next_token.location.start_line || next_token.type == :EOF
        append_token_value_to_minified_values("\n")
      end
    when :IDENTIFIER
      append_token_to_minified_values(token)

      if REQUIRE_SPACE_AFTER_IDENTIFIER_TYPES.include?(next_token.type)
        append_token_value_to_minified_values(' ')
      end
    when :IGNORED_NEWLINE, :EMBDOC_BEGIN, :EMBDOC_LINE, :EMBDOC_END
      # noop
    when :NEWLINE
      token_value = if next_token.type == :EOF
        token.value
      elsif CLOSING_DELIMITER_TYPES.include?(next_token.type)
        # NOTE: This noop is required due to https://github.com/ruby/prism/issues/2967.
      else
        ';'
      end

      append_token_value_to_minified_values(token_value)
    when :KEYWORD_END
      if NO_DELIMITER_VALUE_TYPES.include?(prev_token.type) && prev_token.location.start_line == token.location.start_line
        append_token_value_to_minified_values(' ')
      end
      append_token_to_minified_values(token)
    when :HEREDOC_START
      @in_heredoc = true
      squiggly_heredoc = token.value.start_with?('<<~')
      @string_quote = if token.value.end_with?("'")
        "'"
      elsif token.value.end_with?('`')
        '`'
      else
        '"'
      end

      @minified_values << (@string_quote == '`' ? '`' : '"')
    when :HEREDOC_END
      heredoc_contents = @heredoc_content_tokens.join

      if squiggly_heredoc
        lines = heredoc_contents.lines

        minimum_indentation_length = lines.map { |line|
          line.match(/\A(?<indentation_spaces> *)/)[:indentation_spaces].length
        }.min

        indentation = ' ' * minimum_indentation_length

        heredoc_contents = lines.map { |line| line.delete_prefix(indentation) }
      end

      @minified_values << heredoc_contents
      @minified_values << (@string_quote == '`' ? '`' : '"')

      @heredoc_content_tokens.clear
      @in_heredoc = false
    when :QUESTION_MARK
      # NOTE: Prevent syntax errors by converting `cond? ? x : y` to `cond??x:y`.
      token_value = if prev_token.value.end_with?('?')
        "#{token.value} "
      else
        # NOTE: Require both spaces to prevent syntax error of `foo==bar?x:y`.
        " #{token.value} "
      end

      append_token_value_to_minified_values(token_value)
    when :COLON
      # NOTE: Prevent syntax errors by converting `cond(arg) ? x : y` to `cond(arg)?x:y`.
      append_token_value_to_minified_values("#{token.value} ")
    when :UCOLON_COLON
      append_token_value_to_minified_values(' ') if prev_token.type == :IDENTIFIER || prev_token.type == :METHOD_NAME

      append_token_to_minified_values(token)
    when :BANG_TILDE
      append_token_value_to_minified_values(' ') if prev_token.type == :IDENTIFIER

      append_token_to_minified_values(token)
    when :LABEL
      append_token_value_to_minified_values(' ') if prev_token.type == :IDENTIFIER

      append_token_to_minified_values(token)

      append_token_value_to_minified_values(' ') if next_token.type == :SYMBOL_BEGIN
    when :EQUAL, :EQUAL_EQUAL, :EQUAL_EQUAL_EQUAL, :EQUAL_GREATER
      token_value = (prev_token.value.end_with?('*', '<', '=', '>', '?') ? " #{token.value}" : token.value)

      append_token_value_to_minified_values(token_value)
    when :STRING_BEGIN, :REGEXP_BEGIN, :PERCENT_LOWER_X, *PERCENT_ARRAY_LITERAL_TYPES
      append_token_value_to_minified_values(' ') if token.value.start_with?('%')

      append_token_to_minified_values(token)
    when :BRACKET_LEFT_ARRAY, :KEYWORD_DEF
      append_token_value_to_minified_values(' ') if prev_token&.type == :IDENTIFIER

      append_token_to_minified_values(token)
    when :WORDS_SEP
      if !PERCENT_ARRAY_LITERAL_TYPES.include?(prev_token.type) && next_token.type != :STRING_END
        append_token_value_to_minified_values(' ')
      end
    else
      append_token_to_minified_values(token)
    end

    if padding_required?(token, next_token)
      append_token_value_to_minified_values(' ')
    end

    prev_token = token
  end

  minified_code = @minified_values.join

  minified_code.match?(/\A\r?\n+\z/) ? '' : minified_code
end