require 'cucumber/messages' require_relative 'ast_node' module Gherkin class AstBuilder def initialize(id_generator) @id_generator = id_generator reset end def reset @stack = [AstNode.new(:None)] @comments = [] end def start_rule(rule_type) @stack.push AstNode.new(rule_type) end def end_rule(rule_type) node = @stack.pop current_node.add(node.rule_type, transform_node(node)) end def build(token) if token.matched_type == :Comment @comments.push( Cucumber::Messages::Comment.new( location: get_location(token, 0), text: token.matched_text ) ) else current_node.add(token.matched_type, token) end end def get_result current_node.get_single(:GherkinDocument) end def current_node @stack.last end def get_location(token, column) column = column == 0 ? token.location[:column] : column Cucumber::Messages::Location.new( line: token.location[:line], column: column ) end def get_tags(node) tags = [] tags_node = node.get_single(:Tags) return tags unless tags_node tags_node.get_tokens(:TagLine).each do |token| token.matched_items.each do |tag_item| tags.push( Cucumber::Messages::Tag.new( location: get_location(token, tag_item.column), name: tag_item.text, id: @id_generator.new_id ) ) end end tags end def get_table_rows(node) rows = node.get_tokens(:TableRow).map do |token| Cucumber::Messages::TableRow.new( id: @id_generator.new_id, location: get_location(token, 0), cells: get_cells(token) ) end ensure_cell_count(rows) rows end def ensure_cell_count(rows) return if rows.empty? cell_count = rows[0].cells.length rows.each do |row| raise AstBuilderException.new('inconsistent cell count within the table', row.location.to_h) if row.cells.length != cell_count end end def get_cells(table_row_token) table_row_token.matched_items.map do |cell_item| Cucumber::Messages::TableCell.new( location: get_location(table_row_token, cell_item.column), value: cell_item.text ) end end def get_description(node) node.get_single(:Description) || '' end def get_steps(node) node.get_items(:Step) end def transform_node(node) case node.rule_type when :Step step_line = node.get_token(:StepLine) data_table = node.get_single(:DataTable) doc_string = node.get_single(:DocString) step = Cucumber::Messages::Step.new( location: get_location(step_line, 0), keyword: step_line.matched_keyword, keyword_type: step_line.matched_keyword_type, text: step_line.matched_text, data_table: data_table, doc_string: doc_string, id: @id_generator.new_id ) when :DocString separator_token = node.get_tokens(:DocStringSeparator)[0] media_type = separator_token.matched_text == '' ? nil : separator_token.matched_text line_tokens = node.get_tokens(:Other) content = line_tokens.map { |t| t.matched_text }.join("\n") Cucumber::Messages::DocString.new( location: get_location(separator_token, 0), content: content, delimiter: separator_token.matched_keyword, media_type: media_type ) when :DataTable rows = get_table_rows(node) Cucumber::Messages::DataTable.new( location: rows[0].location, rows: rows ) when :Background background_line = node.get_token(:BackgroundLine) description = get_description(node) steps = get_steps(node) Cucumber::Messages::Background.new( id: @id_generator.new_id, location: get_location(background_line, 0), keyword: background_line.matched_keyword, name: background_line.matched_text, description: description, steps: steps ) when :ScenarioDefinition tags = get_tags(node) scenario_node = node.get_single(:Scenario) scenario_line = scenario_node.get_token(:ScenarioLine) description = get_description(scenario_node) steps = get_steps(scenario_node) examples = scenario_node.get_items(:ExamplesDefinition) Cucumber::Messages::Scenario.new( id: @id_generator.new_id, tags: tags, location: get_location(scenario_line, 0), keyword: scenario_line.matched_keyword, name: scenario_line.matched_text, description: description, steps: steps, examples: examples ) when :ExamplesDefinition tags = get_tags(node) examples_node = node.get_single(:Examples) examples_line = examples_node.get_token(:ExamplesLine) description = get_description(examples_node) rows = examples_node.get_single(:ExamplesTable) table_header = rows.nil? ? nil : rows.first table_body = rows.nil? ? [] : rows[1..-1] Cucumber::Messages::Examples.new( id: @id_generator.new_id, tags: tags, location: get_location(examples_line, 0), keyword: examples_line.matched_keyword, name: examples_line.matched_text, description: description, table_header: table_header, table_body: table_body ) when :ExamplesTable get_table_rows(node) when :Description line_tokens = node.get_tokens(:Other) # Trim trailing empty lines last_non_empty = line_tokens.rindex { |token| !token.line.trimmed_line_text.empty? } line_tokens[0..last_non_empty].map { |token| token.matched_text }.join("\n") when :Feature header = node.get_single(:FeatureHeader) return unless header tags = get_tags(header) feature_line = header.get_token(:FeatureLine) return unless feature_line children = [] background = node.get_single(:Background) children.push(Cucumber::Messages::FeatureChild.new(background: background)) if background node.get_items(:ScenarioDefinition).each do |scenario| children.push(Cucumber::Messages::FeatureChild.new(scenario: scenario)) end node.get_items(:Rule).each do |rule| children.push(Cucumber::Messages::FeatureChild.new(rule: rule)) end description = get_description(header) language = feature_line.matched_gherkin_dialect Cucumber::Messages::Feature.new( tags: tags, location: get_location(feature_line, 0), language: language, keyword: feature_line.matched_keyword, name: feature_line.matched_text, description: description, children: children, ) when :Rule header = node.get_single(:RuleHeader) return unless header rule_line = header.get_token(:RuleLine) return unless rule_line tags = get_tags(header) children = [] background = node.get_single(:Background) children.push(Cucumber::Messages::RuleChild.new(background: background)) if background node.get_items(:ScenarioDefinition).each do |scenario| children.push(Cucumber::Messages::RuleChild.new(scenario: scenario)) end description = get_description(header) Cucumber::Messages::Rule.new( id: @id_generator.new_id, tags: tags, location: get_location(rule_line, 0), keyword: rule_line.matched_keyword, name: rule_line.matched_text, description: description, children: children, ) when :GherkinDocument feature = node.get_single(:Feature) Cucumber::Messages::GherkinDocument.new( comments: @comments, feature: feature ) else node end end end end