From 1c2ce8d15d48572f9c1bbd8fef0102491f3cd6b0 Mon Sep 17 00:00:00 2001 From: Miguel Date: Mon, 1 Sep 2025 13:55:52 +0200 Subject: [PATCH 1/2] Update Sorbet and start fixing sorbet errors --- lib/herb/position.rb | 7 +++++-- lib/herb/range.rb | 7 +++++-- sorbet/config | 5 +---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/herb/position.rb b/lib/herb/position.rb index f7131d151..1213b0355 100644 --- a/lib/herb/position.rb +++ b/lib/herb/position.rb @@ -2,9 +2,12 @@ # typed: true module Herb + #: type serialized_position = { line: Integer, column: Integer } class Position - attr_reader :line #: Integer - attr_reader :column #: Integer + #: Integer + attr_reader :line + #: Integer + attr_reader :column #: (Integer, Integer) -> void def initialize(line, column) diff --git a/lib/herb/range.rb b/lib/herb/range.rb index 7adb16c10..edd15a8e9 100644 --- a/lib/herb/range.rb +++ b/lib/herb/range.rb @@ -2,9 +2,12 @@ # typed: true module Herb + #: type serialized_range = [Integer, Integer] class Range - attr_reader :from #: Integer - attr_reader :to #: Integer + #: Integer + attr_reader :from + #: Integer + attr_reader :to #: (Integer, Integer) -> void def initialize(from, to) diff --git a/sorbet/config b/sorbet/config index e76047cc3..a215ea13d 100644 --- a/sorbet/config +++ b/sorbet/config @@ -1,6 +1,3 @@ ---dir -lib/ --ignore=/tmp/ --ignore=/vendor/bundle ---enable-experimental-rbs-signatures ---enable-experimental-rbs-assertions +--enable-experimental-rbs-comments From 58d10cb66d5cf93491ee7253c6610346381c178e Mon Sep 17 00:00:00 2001 From: Miguel Date: Mon, 22 Sep 2025 15:57:31 +0200 Subject: [PATCH 2/2] Update dynamic values --- .github/workflows/build.yml | 4 ++-- lib/herb/ast/helpers.rb | 6 +++--- lib/herb/ast/node.rb | 20 ++++++++++++++------ lib/herb/engine.rb | 1 + lib/herb/engine/debug_visitor.rb | 4 ++-- lib/herb/lex_result.rb | 4 +++- lib/herb/location.rb | 16 +++++++++++----- lib/herb/parse_result.rb | 4 +++- lib/herb/result.rb | 9 ++++++--- lib/herb/token.rb | 24 +++++++++++++++++------- lib/herb/warnings.rb | 14 +++++++++++--- sig/herb/ast/helpers.rbs | 2 +- sig/herb/token.rbs | 4 ++-- sorbet/config | 2 ++ templates/lib/herb/ast/nodes.rb.erb | 16 ++++++++++++---- templates/lib/herb/errors.rb.erb | 27 ++++++++++++++++++++------- templates/lib/herb/visitor.rb.erb | 4 ++-- 17 files changed, 112 insertions(+), 49 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ecef0960d..49661d605 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,8 +50,8 @@ jobs: - name: RBS Inline run: bundle exec rake rbs_inline - # - name: Sorbet - # run: bundle exec srb tc + - name: Sorbet + run: bundle exec srb tc - name: Run C tests run: ./run_herb_tests diff --git a/lib/herb/ast/helpers.rb b/lib/herb/ast/helpers.rb index 2ddef5355..22fa03a4b 100644 --- a/lib/herb/ast/helpers.rb +++ b/lib/herb/ast/helpers.rb @@ -4,12 +4,12 @@ module Herb module AST module Helpers - #: (Herb::AST::Node) -> bool + #: (Herb::AST::Node?) -> bool def erb_outputs?(node) return false unless node.is_a?(Herb::AST::ERBContentNode) - opening = node.tag_opening&.value - opening&.include?("=") && !opening&.start_with?("<%#") + opening = node.tag_opening&.value || "" + opening.include?("=") && !opening.start_with?("<%#") end #: (String) -> bool diff --git a/lib/herb/ast/node.rb b/lib/herb/ast/node.rb index e1e5e4270..1c388caa4 100644 --- a/lib/herb/ast/node.rb +++ b/lib/herb/ast/node.rb @@ -2,13 +2,21 @@ # typed: true module Herb + #: type serialized_node = { + #| type: String, + #| location: serialized_location?, + #| errors: Array[serialized_error] + #| } module AST class Node - attr_reader :type #: String - attr_reader :location #: Location - attr_reader :errors #: Array[Herb::Errors::Error] - - #: (String, Location, Array[Herb::Errors::Error]) -> void + #: String + attr_reader :type + #: Location + attr_reader :location + #: Array[Herb::Errors::Error] + attr_reader :errors + + #: (String, Location, ?Array[Herb::Errors::Error]) -> void def initialize(type, location, errors = []) @type = type @location = location @@ -19,7 +27,7 @@ def initialize(type, location, errors = []) def to_hash { type: type, - location: location&.to_hash, + location: location.to_hash, errors: errors.map(&:to_hash), } end diff --git a/lib/herb/engine.rb b/lib/herb/engine.rb index fe4d1c983..a706f7aa9 100644 --- a/lib/herb/engine.rb +++ b/lib/herb/engine.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# typed: false require "json" require "time" diff --git a/lib/herb/engine/debug_visitor.rb b/lib/herb/engine/debug_visitor.rb index fdef692ad..b6e135a13 100644 --- a/lib/herb/engine/debug_visitor.rb +++ b/lib/herb/engine/debug_visitor.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# typed: false module Herb class Engine @@ -30,6 +31,7 @@ def initialize(file_path: nil, project_path: nil) @in_html_comment = false @in_html_doctype = false @erb_nodes_to_wrap = [] #: Array[Herb::AST::ERBContentNode] + @top_level_elements = [] #: Array[Herb::AST::HTMLElementNode] end def visit_document_node(node) @@ -142,8 +144,6 @@ def replace_erb_nodes_recursive(node) end def find_top_level_elements(document_node) - @top_level_elements = [] #: Array[Herb::AST::HTMLElementNode] - document_node.children.each do |child| @top_level_elements << child if child.is_a?(Herb::AST::HTMLElementNode) end diff --git a/lib/herb/lex_result.rb b/lib/herb/lex_result.rb index 5c32a76d1..e7b0d15cc 100644 --- a/lib/herb/lex_result.rb +++ b/lib/herb/lex_result.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true +# typed: true module Herb class LexResult < Result - attr_reader :value #: TokenList + #: TokenList + attr_reader :value #: (Array[Herb::Token], String, Array[Herb::Warnings::Warning], Array[Herb::Errors::Error]) -> void def initialize(value, source, warnings, errors) diff --git a/lib/herb/location.rb b/lib/herb/location.rb index e7c4a1a10..a81943c7f 100644 --- a/lib/herb/location.rb +++ b/lib/herb/location.rb @@ -2,9 +2,15 @@ # typed: true module Herb + #: type serialized_location = { + #| start: serialized_position, + #| end: serialized_position + #| } class Location - attr_reader :start #: Position - attr_reader :end #: Position + #: Position + attr_reader :start + #: Position + attr_reader :end #: (Position, Position) -> void def initialize(start_position, end_position) @@ -28,9 +34,9 @@ def self.[](start_line, start_column, end_line, end_column) #: () -> serialized_location def to_hash { - start: start, - end: self.end, - } #: Herb::serialized_location + start: start.to_hash, + end: self.end.to_hash, + } end #: (?untyped) -> String diff --git a/lib/herb/parse_result.rb b/lib/herb/parse_result.rb index 9b0bc1764..8849cbcfb 100644 --- a/lib/herb/parse_result.rb +++ b/lib/herb/parse_result.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true +# typed: true require "json" module Herb class ParseResult < Result - attr_reader :value #: Herb::AST::DocumentNode + #: Herb::AST::DocumentNode + attr_reader :value #: (Herb::AST::DocumentNode, String, Array[Herb::Warnings::Warning], Array[Herb::Errors::Error]) -> void def initialize(value, source, warnings, errors) diff --git a/lib/herb/result.rb b/lib/herb/result.rb index 9067e2843..1b0356b44 100644 --- a/lib/herb/result.rb +++ b/lib/herb/result.rb @@ -3,9 +3,12 @@ module Herb class Result - attr_reader :source #: String - attr_reader :warnings #: Array[Herb::Warnings::Warning] - attr_reader :errors #: Array[Herb::Errors::Error] + #: String + attr_reader :source + #: Array[Herb::Warnings::Warning] + attr_reader :warnings + #: Array[Herb::Errors::Error] + attr_reader :errors #: (String, Array[Herb::Warnings::Warning], Array[Herb::Errors::Error]) -> void def initialize(source, warnings, errors) diff --git a/lib/herb/token.rb b/lib/herb/token.rb index e12d3c4d4..99e8cb863 100644 --- a/lib/herb/token.rb +++ b/lib/herb/token.rb @@ -2,11 +2,21 @@ # typed: true module Herb + #: type serialized_token = { + #| value: String, + #| range: serialized_range?, + #| location: serialized_location?, + #| type: String + #| } class Token - attr_reader :value #: String - attr_reader :range #: Range - attr_reader :location #: Location - attr_reader :type #: String + #: String + attr_reader :value + #: Range? + attr_reader :range + #: Location? + attr_reader :location + #: String + attr_reader :type #: (String, Range, Location, String) -> void def initialize(value, range, location, type) @@ -23,7 +33,7 @@ def to_hash range: range&.to_a, location: location&.to_hash, type: type, - } #: Herb::serialized_token + } end #: (?untyped) -> String @@ -33,7 +43,7 @@ def to_json(state = nil) #: () -> String def tree_inspect - %("#{value.force_encoding("utf-8")}" #{location.tree_inspect}) + %("#{value.force_encoding("utf-8")}" #{location&.tree_inspect}) end #: () -> String @@ -47,7 +57,7 @@ def value_inspect #: () -> String def inspect - %(#) + %(#) end end end diff --git a/lib/herb/warnings.rb b/lib/herb/warnings.rb index 9020a3d7d..950effd54 100644 --- a/lib/herb/warnings.rb +++ b/lib/herb/warnings.rb @@ -3,10 +3,18 @@ module Herb module Warnings + #: type serialized_warning = { + #| type: String, + #| location: serialized_location?, + #| message: String + #| } class Warning - attr_reader :type #: String - attr_reader :location #: Location - attr_reader :message #: String + #: String + attr_reader :type + #: Location? + attr_reader :location + #: String + attr_reader :message #: (String, Location, String) -> void def initialize(type, location, message) diff --git a/sig/herb/ast/helpers.rbs b/sig/herb/ast/helpers.rbs index ca4d99ad7..69190bfb9 100644 --- a/sig/herb/ast/helpers.rbs +++ b/sig/herb/ast/helpers.rbs @@ -3,7 +3,7 @@ module Herb module AST module Helpers - # : (Herb::AST::Node) -> bool + # : (Herb::AST::Node?) -> bool def erb_outputs?: (Herb::AST::Node) -> bool # : (String) -> bool diff --git a/sig/herb/token.rbs b/sig/herb/token.rbs index d29c75c06..dacb0292c 100644 --- a/sig/herb/token.rbs +++ b/sig/herb/token.rbs @@ -4,9 +4,9 @@ module Herb class Token attr_reader value: String - attr_reader range: Range + attr_reader range: Range? - attr_reader location: Location + attr_reader location: Location? attr_reader type: String diff --git a/sorbet/config b/sorbet/config index a215ea13d..af3793756 100644 --- a/sorbet/config +++ b/sorbet/config @@ -1,3 +1,5 @@ +--dir +lib/ --ignore=/tmp/ --ignore=/vendor/bundle --enable-experimental-rbs-comments diff --git a/templates/lib/herb/ast/nodes.rb.erb b/templates/lib/herb/ast/nodes.rb.erb index cb04b29ab..cfe90a494 100644 --- a/templates/lib/herb/ast/nodes.rb.erb +++ b/templates/lib/herb/ast/nodes.rb.erb @@ -1,9 +1,17 @@ module Herb module AST <%- nodes.each do |node| -%> + #: type serialized_<%= node.human %> = { + <%- node.fields.each do |field| -%> + <%- is_nilable = !%w[Array[Herb::AST::Node] Array[Herb::AST::ERBWhenNode] Array[Herb::AST::ERBInNode] bool nil].include?(field.ruby_type) -%> + #| <%= field.name %>: <%= field.ruby_type %><%= if is_nilable then "?" end %>, + <%- end -%> + #| } class <%= node.name -%> < Node <%- node.fields.each do |field| -%> - attr_reader :<%= field.name %> #: <%= field.ruby_type %> + <%- is_nilable = !%w[Array[Herb::AST::Node] Array[Herb::AST::ERBWhenNode] Array[Herb::AST::ERBInNode] bool nil].include?(field.ruby_type) -%> + #: <%= field.ruby_type %><%= if is_nilable then "?" end %> + attr_reader :<%= field.name %> <%- end -%> #: (<%= ["String", "Location", "Array[Herb::Errors::Error]", *node.fields.map(&:ruby_type)].join(", ") %>) -> void @@ -24,7 +32,7 @@ module Herb <%- node.fields.each do |field| -%> <%= field.name %>: <%= field.name %>, <%- end -%> - }) #: Herb::serialized_<%= node.human %> + }) end #: (Visitor) -> void @@ -73,7 +81,7 @@ module Herb output += %(<%= name %>#{<%= field.name %>.inspect}\n) <%- when Herb::Template::TokenField -%> output += "<%= name %>" - output += <%= field.name %> ? <%= field.name %>.tree_inspect : "∅" + output += <%= field.name %>&.tree_inspect || "∅" output += "\n" <%- when Herb::Template::BooleanField -%> output += "<%= name %>" @@ -95,7 +103,7 @@ module Herb <%- else -%> output += "│ └── " <%- end -%> - output += <%= field.name %>.tree_inspect(indent).gsub(/^/, " " * (indent + 1)).lstrip.gsub(/^/, "<%= prefix %>").delete_prefix("<%= prefix %>") + output += <%= field.name %>&.tree_inspect(indent)&.gsub(/^/, " " * (indent + 1))&.lstrip&.gsub(/^/, "<%= prefix %>")&.delete_prefix("<%= prefix %>") || "" else output += "∅\n" end diff --git a/templates/lib/herb/errors.rb.erb b/templates/lib/herb/errors.rb.erb index cc750f2fa..4e483c12f 100644 --- a/templates/lib/herb/errors.rb.erb +++ b/templates/lib/herb/errors.rb.erb @@ -1,9 +1,15 @@ -<%- base_arguments = [["type", "String"], ["location", "Location"], ["message", "String"]] -%> +<%- base_arguments = [["type", "String"], ["location", "Location?"], ["message", "String"]] -%> module Herb + #: type serialized_error = { + #| type: String, + #| location: serialized_location?, + #| message: String + #| } module Errors class Error <%- base_arguments.each do |argument, type| -%> - attr_reader :<%= argument %> #: <%= type %> + #: <%= type %> + attr_reader :<%= argument %> <%- end -%> #: (<%= base_arguments.map { |_argument, type| type }.join(", ") %>) -> void @@ -45,8 +51,15 @@ module Herb <%- errors.each do |error| -%> class <%= error.name -%> < Error + #: type serialized_<%= error.human %> = { <%- error.fields.each do |field| -%> - attr_reader :<%= field.name %> #: <%= field.ruby_type %> + #| <%= "#{field.name}: #{field.ruby_type}?," %> + <%- end -%> + #| } + + <%- error.fields.each do |field| -%> + #: <%= field.ruby_type %>? + attr_reader :<%= field.name %> <%- end -%> #: (<%= [*base_arguments.map(&:last), *error.fields.map(&:ruby_type)].join(", ") %>) -> void @@ -69,14 +82,14 @@ module Herb <%- error.fields.each do |field| -%> <%= field.name %>: <%= field.name %>, <%- end -%> - }) #: Herb::serialized_<%= error.human %> + }) end #: (?Integer) -> String def tree_inspect(indent = 0) output = +"" - output += %(@ #{error_name} #{location.tree_inspect}\n) + output += %(@ #{error_name} #{location&.tree_inspect}\n) <%- symbol = error.fields.none? ? "└──" : "├──" -%> output += %(<%= symbol %> message: #{message.inspect}\n) <%- error.fields.each do |field| -%> @@ -84,9 +97,9 @@ module Herb <%- name = "#{symbol} #{field.name}: " -%> <%- case field -%> <%- when Herb::Template::PositionField -%> - output += %(<%= name %>#{<%= field.name %> ? <%= field.name %>.tree_inspect : "∅"}\n) + output += %(<%= name %>#{<%= field.name %>&.tree_inspect || "∅"}\n) <%- when Herb::Template::TokenField -%> - output += %(<%= name %>#{<%= field.name %> ? <%= field.name %>.tree_inspect : "∅"}\n) + output += %(<%= name %>#{<%= field.name %>&.tree_inspect || "∅"}\n) <%- when Herb::Template::TokenTypeField -%> output += %(<%= name %>#{<%= field.name %>.inspect}\n) <%- when Herb::Template::StringField -%> diff --git a/templates/lib/herb/visitor.rb.erb b/templates/lib/herb/visitor.rb.erb index 06bbe335a..e9e5d9ed5 100644 --- a/templates/lib/herb/visitor.rb.erb +++ b/templates/lib/herb/visitor.rb.erb @@ -2,12 +2,12 @@ module Herb class Visitor include AST::Helpers - #: (Herb::AST::Node) -> void + #: (Herb::AST::Node?) -> void def visit(node) node&.accept(self) end - #: (Array[Herb::AST::Node]) -> void + #: (Array[Herb::AST::Node?]) -> void def visit_all(nodes) nodes.each { |node| node&.accept(self) } end