File: operation.rb

package info (click to toggle)
ruby-sass 3.7.4-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,396 kB
  • sloc: ruby: 32,443; sh: 26; makefile: 25
file content (149 lines) | stat: -rw-r--r-- 5,469 bytes parent folder | download | duplicates (3)
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
module Sass::Script::Tree
  # A SassScript parse node representing a binary operation,
  # such as `$a + $b` or `"foo" + 1`.
  class Operation < Node
    @@color_arithmetic_deprecation = Sass::Deprecation.new
    @@unitless_equals_deprecation = Sass::Deprecation.new

    attr_reader :operand1
    attr_reader :operand2
    attr_reader :operator

    # @param operand1 [Sass::Script::Tree::Node] The parse-tree node
    #   for the right-hand side of the operator
    # @param operand2 [Sass::Script::Tree::Node] The parse-tree node
    #   for the left-hand side of the operator
    # @param operator [Symbol] The operator to perform.
    #   This should be one of the binary operator names in {Sass::Script::Lexer::OPERATORS}
    def initialize(operand1, operand2, operator)
      @operand1 = operand1
      @operand2 = operand2
      @operator = operator
      super()
    end

    # @return [String] A human-readable s-expression representation of the operation
    def inspect
      "(#{@operator.inspect} #{@operand1.inspect} #{@operand2.inspect})"
    end

    # @see Node#to_sass
    def to_sass(opts = {})
      o1 = operand_to_sass @operand1, :left, opts
      o2 = operand_to_sass @operand2, :right, opts
      sep =
        case @operator
        when :comma; ", "
        when :space; " "
        else; " #{Sass::Script::Lexer::OPERATORS_REVERSE[@operator]} "
        end
      "#{o1}#{sep}#{o2}"
    end

    # Returns the operands for this operation.
    #
    # @return [Array<Node>]
    # @see Node#children
    def children
      [@operand1, @operand2]
    end

    # @see Node#deep_copy
    def deep_copy
      node = dup
      node.instance_variable_set('@operand1', @operand1.deep_copy)
      node.instance_variable_set('@operand2', @operand2.deep_copy)
      node
    end

    protected

    # Evaluates the operation.
    #
    # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
    # @return [Sass::Script::Value] The SassScript object that is the value of the operation
    # @raise [Sass::SyntaxError] if the operation is undefined for the operands
    def _perform(environment)
      value1 = @operand1.perform(environment)

      # Special-case :and and :or to support short-circuiting.
      if @operator == :and
        return value1.to_bool ? @operand2.perform(environment) : value1
      elsif @operator == :or
        return value1.to_bool ? value1 : @operand2.perform(environment)
      end

      value2 = @operand2.perform(environment)

      if (value1.is_a?(Sass::Script::Value::Null) || value2.is_a?(Sass::Script::Value::Null)) &&
          @operator != :eq && @operator != :neq
        raise Sass::SyntaxError.new(
          "Invalid null operation: \"#{value1.inspect} #{@operator} #{value2.inspect}\".")
      end

      begin
        result = opts(value1.send(@operator, value2))
      rescue NoMethodError => e
        raise e unless e.name.to_s == @operator.to_s
        raise Sass::SyntaxError.new("Undefined operation: \"#{value1} #{@operator} #{value2}\".")
      end

      warn_for_color_arithmetic(value1, value2)
      warn_for_unitless_equals(value1, value2, result)

      result
    end

    private

    def warn_for_color_arithmetic(value1, value2)
      return unless @operator == :plus || @operator == :times || @operator == :minus ||
                    @operator == :div || @operator == :mod

      if value1.is_a?(Sass::Script::Value::Number)
        return unless value2.is_a?(Sass::Script::Value::Color)
      elsif value1.is_a?(Sass::Script::Value::Color)
        return unless value2.is_a?(Sass::Script::Value::Color) || value2.is_a?(Sass::Script::Value::Number)
      else
        return
      end

      @@color_arithmetic_deprecation.warn(filename, line, <<WARNING)
The operation `#{value1} #{@operator} #{value2}` is deprecated and will be an error in future versions.
Consider using Sass's color functions instead.
https://sass-lang.com/documentation/Sass/Script/Functions.html#other_color_functions
WARNING
    end

    def warn_for_unitless_equals(value1, value2, result)
      return unless @operator == :eq || @operator == :neq
      return unless value1.is_a?(Sass::Script::Value::Number)
      return unless value2.is_a?(Sass::Script::Value::Number)
      return unless value1.unitless? != value2.unitless?
      return unless result == (if @operator == :eq
                                 Sass::Script::Value::Bool::TRUE
                               else
                                 Sass::Script::Value::Bool::FALSE
                               end)

      operation = "#{value1.to_sass} #{@operator == :eq ? '==' : '!='} #{value2.to_sass}"
      future_value = @operator == :neq
      @@unitless_equals_deprecation.warn(filename, line, <<WARNING)
The result of `#{operation}` will be `#{future_value}` in future releases of Sass.
Unitless numbers will no longer be equal to the same numbers with units.
WARNING
    end

    def operand_to_sass(op, side, opts)
      return "(#{op.to_sass(opts)})" if op.is_a?(Sass::Script::Tree::ListLiteral)
      return op.to_sass(opts) unless op.is_a?(Operation)

      pred = Sass::Script::Parser.precedence_of(@operator)
      sub_pred = Sass::Script::Parser.precedence_of(op.operator)
      assoc = Sass::Script::Parser.associative?(@operator)
      return "(#{op.to_sass(opts)})" if sub_pred < pred ||
        (side == :right && sub_pred == pred && !assoc)
      op.to_sass(opts)
    end
  end
end