diff --git a/CHANGES.md b/CHANGES.md index 7975bf4f..90f3ed54 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,7 @@ ## Unreleased -* ErbAst-parser now only used for `*.html.erb`-files, not e.g. `*.js.erb` +* ERB AST-scanner now uses regex instead of better_html to allow + parsing files other than HTML. ## v1.0.13 diff --git a/i18n-tasks.gemspec b/i18n-tasks.gemspec index 875c89c9..114793a7 100644 --- a/i18n-tasks.gemspec +++ b/i18n-tasks.gemspec @@ -39,7 +39,6 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', '>= 4.0.2' s.add_dependency 'ast', '>= 2.1.0' - s.add_dependency 'better_html', '>= 1.0', '< 3.0' s.add_dependency 'erubi' s.add_dependency 'highline', '>= 2.0.0' s.add_dependency 'i18n' diff --git a/lib/i18n/tasks/scanners/erb_ast_processor.rb b/lib/i18n/tasks/scanners/erb_ast_processor.rb deleted file mode 100644 index 1ccd2a5b..00000000 --- a/lib/i18n/tasks/scanners/erb_ast_processor.rb +++ /dev/null @@ -1,74 +0,0 @@ -# frozen_string_literal: true - -require 'ast' -require 'set' -require 'i18n/tasks/scanners/local_ruby_parser' - -module I18n::Tasks::Scanners - class ErbAstProcessor - include AST::Processor::Mixin - def initialize - super() - @ruby_parser = LocalRubyParser.new(ignore_blocks: true) - @comments = [] - end - - def process_and_extract_comments(ast) - result = process(ast) - [result, @comments] - end - - def on_code(node) - parsed, comments = @ruby_parser.parse( - node.children[0], - location: node.location - ) - @comments.concat(comments) - - unless parsed.nil? - parsed = parsed.updated( - nil, - parsed.children.map { |child| node?(child) ? process(child) : child } - ) - node = node.updated(:send, parsed) - end - node - end - - # @param node [::Parser::AST::Node] - # @return [::Parser::AST::Node] - def handler_missing(node) - node = handle_comment(node) - return if node.nil? - - node.updated( - nil, - node.children.map { |child| node?(child) ? process(child) : child } - ) - end - - private - - # Convert ERB-comments to ::Parser::Source::Comment and skip processing node - # - # @param node Parser::AST::Node Potential comment node - # @return Parser::AST::Node or nil - def handle_comment(node) - if node.type == :erb && node.children.size == 4 && - node.children[0]&.type == :indicator && node.children[0].children[0] == '#' && - node.children[2]&.type == :code - - # Do not continue parsing this node - comment = node.children[2] - @comments << ::Parser::Source::Comment.new(comment.location.expression) - return - end - - node - end - - def node?(node) - node.is_a?(::Parser::AST::Node) - end - end -end diff --git a/lib/i18n/tasks/scanners/erb_ast_scanner.rb b/lib/i18n/tasks/scanners/erb_ast_scanner.rb index 5d7a096d..c6d5120f 100644 --- a/lib/i18n/tasks/scanners/erb_ast_scanner.rb +++ b/lib/i18n/tasks/scanners/erb_ast_scanner.rb @@ -1,16 +1,16 @@ # frozen_string_literal: true require 'i18n/tasks/scanners/ruby_ast_scanner' -require 'i18n/tasks/scanners/erb_ast_processor' -require 'better_html/errors' -require 'better_html/parser' +require 'i18n/tasks/scanners/local_ruby_parser' module I18n::Tasks::Scanners # Scan for I18n.translate calls in ERB-file better-html and ASTs class ErbAstScanner < RubyAstScanner + DEFAULT_REGEXP = /<%(={1,2}|-|\#|%)?(.*?)([-=])?%>/m.freeze + def initialize(**args) super(**args) - @erb_ast_processor = ErbAstProcessor.new + @ruby_parser = LocalRubyParser.new(ignore_blocks: true) end private @@ -20,29 +20,59 @@ def initialize(**args) # @param path Path to file to parse # @return [{Parser::AST::Node}, [Parser::Source::Comment]] def path_to_ast_and_comments(path) - parser = BetterHtml::Parser.new(make_buffer(path)) - ast = convert_better_html(parser.ast) - @erb_ast_processor.process_and_extract_comments(ast) + comments = [] + buffer = make_buffer(path) + + children = [] + buffer + .source + .scan(DEFAULT_REGEXP) do |indicator, code, tailch, _rspace| + match = Regexp.last_match + character = indicator ? indicator[0] : nil + + start = match.begin(0) + 2 + (character&.size || 0) + stop = match.end(0) - 2 - (tailch&.size || 0) + + case character + when '=', nil, '-' + parsed, parsed_comments = handle_code(buffer, code, start, stop) + comments.concat(parsed_comments) + children << parsed unless parsed.nil? + when '#', '#-' + comments << handle_comment(buffer, start, stop) + end + end + + [root_node(children, buffer), comments] end - # Convert BetterHtml nodes to Parser::AST::Node - # - # @param node BetterHtml::Parser::AST::Node - # @return Parser::AST::Node - def convert_better_html(node) - definition = Parser::Source::Map::Definition.new( - node.location.begin, - node.location.begin, - node.location.begin, - node.location.end - ) - Parser::AST::Node.new( - node.type, - node.children.map { |child| child.is_a?(BetterHtml::AST::Node) ? convert_better_html(child) : child }, - { - location: definition - } - ) + def handle_code(buffer, code, start, stop) + range = ::Parser::Source::Range.new(buffer, start, stop) + location = + Parser::Source::Map::Definition.new( + range.begin, + range.begin, + range.begin, + range.end + ) + @ruby_parser.parse(code, location: location) + end + + def handle_comment(buffer, start, stop) + range = ::Parser::Source::Range.new(buffer, start, stop) + ::Parser::Source::Comment.new(range) + end + + def root_node(children, buffer) + range = ::Parser::Source::Range.new(buffer, 0, buffer.source.size) + location = + Parser::Source::Map::Definition.new( + range.begin, + range.begin, + range.begin, + range.end + ) + ::Parser::AST::Node.new(:erb, children, location: location) end end end diff --git a/spec/used_keys_erb_spec.rb b/spec/used_keys_erb_spec.rb index 2f881f46..7a4c959a 100644 --- a/spec/used_keys_erb_spec.rb +++ b/spec/used_keys_erb_spec.rb @@ -33,8 +33,8 @@ [ { path: 'app/views/application/show.html.erb', - pos: 17, - line_num: 1, line_pos: 17, + pos: 18, + line_num: 1, line_pos: 18, line: "
<%= t('a') %>
", raw_key: 'a' }, @@ -56,8 +56,8 @@ [ { path: 'app/views/application/show.html.erb', - pos: 184, - line_num: 7, line_pos: 5, + pos: 185, + line_num: 7, line_pos: 6, line: ' <%= MeetingNote.model_name.human(count: 1) %>', raw_key: 'activerecord.models.meeting_note' } @@ -72,8 +72,8 @@ [ { path: 'app/views/application/show.html.erb', - pos: 232, - line_num: 8, line_pos: 5, + pos: 233, + line_num: 8, line_pos: 6, line: ' <%= AgendaItem.human_attribute_name(:title) %>', raw_key: 'activerecord.attributes.agenda_item.title' } @@ -88,8 +88,8 @@ [ { path: 'app/views/application/show.html.erb', - pos: 292, - line_num: 11, line_pos: 5, + pos: 293, + line_num: 11, line_pos: 6, line: " <%= t('with_parameter', parameter: \"erb is the best\") %>", raw_key: 'with_parameter' } @@ -104,8 +104,8 @@ [ { path: 'app/views/application/show.html.erb', - pos: 351, - line_num: 12, line_pos: 5, + pos: 352, + line_num: 12, line_pos: 6, line: " <%= t 'with_scope', scope: \"scope_a.scope_b\", default: t(\".nested_call\") %>", raw_key: 'scope_a.scope_b.with_scope' } @@ -168,9 +168,9 @@ [ { path: 'app/views/application/show.html.erb', - pos: 88, - line_num: 5, line_pos: 4, - line: " <% # i18n-tasks-use t('comment.absolute.attribute') %>", + pos: 147, + line_num: 6, line_pos: 6, + line: ' <%= Translate.absolute.attribute %>', raw_key: 'comment.absolute.attribute' } ] @@ -196,9 +196,9 @@ [ { path: 'app/views/application/comments.html.erb', - pos: 90, - line_num: 4, line_pos: 2, - line: "<% # i18n-tasks-use t('ruby.comment.works') %>", + pos: 139, + line_num: 5, line_pos: 4, + line: '<%= Translate.ruby_comment_works %>', raw_key: 'ruby.comment.works' } ] @@ -212,9 +212,9 @@ [ { path: 'app/views/application/comments.html.erb', - pos: 174, - line_num: 7, line_pos: 4, - line: "<%# i18n-tasks-use t('erb.comment.works') %>", + pos: 221, + line_num: 8, line_pos: 4, + line: '<%= Translate.erb_comment_works %>', raw_key: 'erb.comment.works' } ] @@ -228,9 +228,9 @@ [ { path: 'app/views/application/comments.html.erb', - pos: 255, - line_num: 10, line_pos: 2, - line: "<%# i18n-tasks-use t('erb_multi.comment.line1')", + pos: 352, + line_num: 12, line_pos: 4, + line: '<%= t("erb_multi.comment.#{type}") %>', # rubocop:disable Lint/InterpolationCheck raw_key: 'erb_multi.comment.line1' } ] @@ -245,9 +245,9 @@ [ { path: 'app/views/application/comments.html.erb', - pos: 255, - line_num: 10, line_pos: 2, - line: "<%# i18n-tasks-use t('erb_multi.comment.line1')", + pos: 352, + line_num: 12, line_pos: 4, + line: '<%= t("erb_multi.comment.#{type}") %>', # rubocop:disable Lint/InterpolationCheck raw_key: 'erb_multi.comment.line2' } ] @@ -261,9 +261,9 @@ [ { path: 'app/views/application/comments.html.erb', - pos: 389, - line_num: 14, line_pos: 2, - line: '<%#-', + pos: 498, + line_num: 17, line_pos: 4, + line: '<%= t("erb_multi_dash.comment.#{type}") %>', # rubocop:disable Lint/InterpolationCheck raw_key: 'erb_multi_dash.comment.line1' } ] @@ -277,9 +277,9 @@ [ { path: 'app/views/application/comments.html.erb', - pos: 389, - line_num: 14, line_pos: 2, - line: '<%#-', + pos: 498, + line_num: 17, line_pos: 4, + line: '<%= t("erb_multi_dash.comment.#{type}") %>', # rubocop:disable Lint/InterpolationCheck raw_key: 'erb_multi_dash.comment.line2' } ] @@ -293,9 +293,9 @@ [ { path: 'app/views/application/comments.html.erb', - pos: 540, - line_num: 19, line_pos: 2, - line: '<%', + pos: 642, + line_num: 22, line_pos: 4, + line: '<%= t("ruby_multi.comment.#{type}") %>', # rubocop:disable Lint/InterpolationCheck raw_key: 'ruby_multi.comment.line1' } ]