Class: JsxRosetta::AST::Node

Inherits:
Object
  • Object
show all
Defined in:
lib/jsx_rosetta/ast/node.rb

Overview

Base class for every Babel-shaped AST node. Wraps the raw JSON hash and provides:

* Field access via `node[:opening_element]` (snake_case symbols or
  camelCase strings — both resolve to the same field).
* Source location accessors (`loc`, `range`, `start_pos`, `end_pos`).
* Tree traversal via `each_child` / `walk`.
* Pattern-matching support via `deconstruct_keys`.

Specific Babel node types may register subclasses that add named accessors (e.g. JSXElement#opening_element). Unknown types fall through to the generic Node class so the parser doesn’t crash on ESNext additions.

Constant Summary collapse

TYPE_REGISTRY =

rubocop:disable Style/MutableConstant

{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(raw) ⇒ Node

Returns a new instance of Node.



44
45
46
# File 'lib/jsx_rosetta/ast/node.rb', line 44

def initialize(raw)
  @raw = raw
end

Instance Attribute Details

#rawObject (readonly)

Returns the value of attribute raw.



22
23
24
# File 'lib/jsx_rosetta/ast/node.rb', line 22

def raw
  @raw
end

Class Method Details

.matches?(value, *types) ⇒ Boolean

Defensive type predicate. True iff ‘value` is an AST::Node whose type is one of `types`. False for nil, arrays, strings, hashes, or any non-Node — making it safe for raw hash field accesses where the contents may be anything.

Returns:

  • (Boolean)


101
102
103
# File 'lib/jsx_rosetta/ast/node.rb', line 101

def self.matches?(value, *types)
  value.is_a?(Node) && types.include?(value.type)
end

.register(*type_names) ⇒ Object



24
25
26
# File 'lib/jsx_rosetta/ast/node.rb', line 24

def self.register(*type_names)
  type_names.each { |name| TYPE_REGISTRY[name] = self }
end

.wrap(value) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/jsx_rosetta/ast/node.rb', line 28

def self.wrap(value)
  case value
  when Hash
    if value.key?("type")
      klass = TYPE_REGISTRY.fetch(value["type"], Node)
      klass.new(value)
    else
      value
    end
  when Array
    value.map { |element| wrap(element) }
  else
    value
  end
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



146
147
148
# File 'lib/jsx_rosetta/ast/node.rb', line 146

def ==(other)
  other.is_a?(Node) && other.raw == @raw
end

#[](key) ⇒ Object

Field access. Accepts snake_case symbols/strings (translated to camelCase) and camelCase strings (used verbatim). Returns whatever the raw hash contains at that key (Node, Array of Nodes, String, Hash, nil) — caller is responsible for type-checking. For typed access, prefer ‘#child` (returns Node | nil).



73
74
75
76
77
78
79
# File 'lib/jsx_rosetta/ast/node.rb', line 73

def [](key)
  raw_key = key.to_s
  return Node.wrap(@raw[raw_key]) if @raw.key?(raw_key)

  camel_key = Inflector.camelize(raw_key)
  Node.wrap(@raw[camel_key])
end

#child(key) ⇒ Object

Typed child access — returns the wrapped Node at ‘key`, or nil if absent or non-Node-shaped. Use this in lowering passes to avoid repetitive `is_a?(AST::Node)` defenses.



84
85
86
87
# File 'lib/jsx_rosetta/ast/node.rb', line 84

def child(key)
  value = self[key]
  value.is_a?(Node) ? value : nil
end

#childrenObject



119
120
121
# File 'lib/jsx_rosetta/ast/node.rb', line 119

def children
  each_child.to_a
end

#deconstruct_keys(keys) ⇒ Object

Pattern-matching support. Returns a hash with snake_case symbol keys; values are wrapped (Node instances or arrays of Node/raw values).



132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/jsx_rosetta/ast/node.rb', line 132

def deconstruct_keys(keys)
  if keys.nil?
    @raw.each_with_object({}) do |(k, v), out|
      out[Inflector.underscore(k).to_sym] = Node.wrap(v)
    end
  else
    keys.each_with_object({}) do |key, out|
      camel_key = Inflector.camelize(key.to_s)
      actual_key = @raw.key?(key.to_s) ? key.to_s : camel_key
      out[key] = Node.wrap(@raw[actual_key]) if @raw.key?(actual_key)
    end
  end
end

#dig(*keys) ⇒ Object



105
106
107
108
109
110
111
# File 'lib/jsx_rosetta/ast/node.rb', line 105

def dig(*keys)
  keys.reduce(self) do |current, key|
    break nil if current.nil?

    current[key]
  end
end

#each_child(&block) ⇒ Object



113
114
115
116
117
# File 'lib/jsx_rosetta/ast/node.rb', line 113

def each_child(&block)
  return enum_for(:each_child) unless block

  @raw.each_value { |value| yield_descendant_nodes(value, &block) }
end

#end_posObject



64
65
66
# File 'lib/jsx_rosetta/ast/node.rb', line 64

def end_pos
  @raw["end"]
end

#hashObject



151
152
153
# File 'lib/jsx_rosetta/ast/node.rb', line 151

def hash
  @raw.hash
end

#inspectObject



155
156
157
# File 'lib/jsx_rosetta/ast/node.rb', line 155

def inspect
  "#<#{self.class.name || "JsxRosetta::AST::Node"} type=#{type.inspect} loc=#{loc_summary}>"
end

#locObject



52
53
54
# File 'lib/jsx_rosetta/ast/node.rb', line 52

def loc
  @raw["loc"]
end

#of_type?(*types) ⇒ Boolean

Type predicate on a known Node. Use only when the receiver is guaranteed to be a Node — otherwise prefer ‘Node.matches?` (class method) which tolerates nil / non-Node values from hash lookups. Accepts multiple types: `node.of_type?(“StringLiteral”, “NumericLiteral”)`.

Returns:

  • (Boolean)


93
94
95
# File 'lib/jsx_rosetta/ast/node.rb', line 93

def of_type?(*types)
  types.include?(type)
end

#rangeObject



56
57
58
# File 'lib/jsx_rosetta/ast/node.rb', line 56

def range
  @raw["range"]
end

#start_posObject



60
61
62
# File 'lib/jsx_rosetta/ast/node.rb', line 60

def start_pos
  @raw["start"]
end

#typeObject



48
49
50
# File 'lib/jsx_rosetta/ast/node.rb', line 48

def type
  @raw["type"]
end

#walk {|_self| ... } ⇒ Object

Yields:

  • (_self)

Yield Parameters:



123
124
125
126
127
128
# File 'lib/jsx_rosetta/ast/node.rb', line 123

def walk(&block)
  return enum_for(:walk) unless block

  yield self
  each_child { |child| child.walk(&block) }
end