File: check_nesting.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 (173 lines) | stat: -rw-r--r-- 5,619 bytes parent folder | download | duplicates (4)
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# A visitor for checking that all nodes are properly nested.
class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
  protected

  def initialize
    @parents = []
    @parent = nil
    @current_mixin_def = nil
  end

  def visit(node)
    if (error = @parent && (
        try_send(@parent.class.invalid_child_method_name, @parent, node) ||
        try_send(node.class.invalid_parent_method_name, @parent, node)))
      raise Sass::SyntaxError.new(error)
    end
    super
  rescue Sass::SyntaxError => e
    e.modify_backtrace(:filename => node.filename, :line => node.line)
    raise e
  end

  CONTROL_NODES = [Sass::Tree::EachNode, Sass::Tree::ForNode, Sass::Tree::IfNode,
                   Sass::Tree::WhileNode, Sass::Tree::TraceNode]
  SCRIPT_NODES = [Sass::Tree::ImportNode] + CONTROL_NODES
  def visit_children(parent)
    old_parent = @parent

    # When checking a static tree, resolve at-roots to be sure they won't send
    # nodes where they don't belong.
    if parent.is_a?(Sass::Tree::AtRootNode) && parent.resolved_value
      old_parents = @parents
      @parents = @parents.reject {|p| parent.exclude_node?(p)}
      @parent = @parents.reverse.each_with_index.
        find {|p, i| !transparent_parent?(p, @parents[-i - 2])}.first

      begin
        return super
      ensure
        @parents = old_parents
        @parent = old_parent
      end
    end

    unless transparent_parent?(parent, old_parent)
      @parent = parent
    end

    @parents.push parent
    begin
      super
    ensure
      @parent = old_parent
      @parents.pop
    end
  end

  def visit_root(node)
    yield
  rescue Sass::SyntaxError => e
    e.sass_template ||= node.template
    raise e
  end

  def visit_import(node)
    yield
  rescue Sass::SyntaxError => e
    e.modify_backtrace(:filename => node.children.first.filename)
    e.add_backtrace(:filename => node.filename, :line => node.line)
    raise e
  end

  def visit_mixindef(node)
    @current_mixin_def, old_mixin_def = node, @current_mixin_def
    yield
  ensure
    @current_mixin_def = old_mixin_def
  end

  def invalid_content_parent?(parent, child)
    if @current_mixin_def
      @current_mixin_def.has_content = true
      nil
    else
      "@content may only be used within a mixin."
    end
  end

  def invalid_charset_parent?(parent, child)
    "@charset may only be used at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
  end

  VALID_EXTEND_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::MixinDefNode, Sass::Tree::MixinNode]
  def invalid_extend_parent?(parent, child)
    return if is_any_of?(parent, VALID_EXTEND_PARENTS)
    "Extend directives may only be used within rules."
  end

  INVALID_IMPORT_PARENTS = CONTROL_NODES +
    [Sass::Tree::MixinDefNode, Sass::Tree::MixinNode]
  def invalid_import_parent?(parent, child)
    unless (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty?
      return "Import directives may not be used within control directives or mixins."
    end
    return if parent.is_a?(Sass::Tree::RootNode)
    return "CSS import directives may only be used at the root of a document." if child.css_import?
  rescue Sass::SyntaxError => e
    e.modify_backtrace(:filename => child.imported_file.options[:filename])
    e.add_backtrace(:filename => child.filename, :line => child.line)
    raise e
  end

  def invalid_mixindef_parent?(parent, child)
    return if (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty?
    "Mixins may not be defined within control directives or other mixins."
  end

  def invalid_function_parent?(parent, child)
    return if (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty?
    "Functions may not be defined within control directives or other mixins."
  end

  VALID_FUNCTION_CHILDREN = [
    Sass::Tree::CommentNode,  Sass::Tree::DebugNode, Sass::Tree::ReturnNode,
    Sass::Tree::VariableNode, Sass::Tree::WarnNode, Sass::Tree::ErrorNode
  ] + CONTROL_NODES
  def invalid_function_child?(parent, child)
    return if is_any_of?(child, VALID_FUNCTION_CHILDREN)
    "Functions can only contain variable declarations and control directives."
  end

  VALID_PROP_CHILDREN = CONTROL_NODES + [Sass::Tree::CommentNode,
                                         Sass::Tree::PropNode,
                                         Sass::Tree::MixinNode]
  def invalid_prop_child?(parent, child)
    return if is_any_of?(child, VALID_PROP_CHILDREN)
    "Illegal nesting: Only properties may be nested beneath properties."
  end

  VALID_PROP_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::KeyframeRuleNode, Sass::Tree::PropNode,
                        Sass::Tree::MixinDefNode, Sass::Tree::DirectiveNode, Sass::Tree::MixinNode]
  def invalid_prop_parent?(parent, child)
    return if is_any_of?(parent, VALID_PROP_PARENTS)
    "Properties are only allowed within rules, directives, mixin includes, or other properties." +
      child.pseudo_class_selector_message
  end

  def invalid_return_parent?(parent, child)
    "@return may only be used within a function." unless parent.is_a?(Sass::Tree::FunctionNode)
  end

  private

  # Whether `parent` should be assigned to `@parent`.
  def transparent_parent?(parent, grandparent)
    is_any_of?(parent, SCRIPT_NODES) ||
      (parent.bubbles? &&
       !grandparent.is_a?(Sass::Tree::RootNode) &&
       !grandparent.is_a?(Sass::Tree::AtRootNode))
  end

  def is_any_of?(val, classes)
    classes.each do |c|
      return true if val.is_a?(c)
    end
    false
  end

  def try_send(method, *args)
    return unless respond_to?(method, true)
    send(method, *args)
  end
end