diff --git a/.github/workflows/release-rubygem.yml b/.github/workflows/release-rubygem.yml index b12f9bff..0314b2b2 100644 --- a/.github/workflows/release-rubygem.yml +++ b/.github/workflows/release-rubygem.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0.2' + ruby-version: '3.2' bundler-cache: true - uses: cucumber/action-publish-rubygem@v1.0.0 with: diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 41f5bfb2..a4f8070e 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -17,20 +17,20 @@ jobs: matrix: os: - ubuntu-latest - ruby: ['2.5', '2.6', '2.7', '3.0'] + ruby: ['2.5', '2.6', '2.7', '3.0', '3.1', '3.2'] include: - os: windows-latest - ruby: '3.0' + ruby: '3.2' - os: macos-latest - ruby: '3.0' + ruby: '3.2' steps: - uses: actions/checkout@v4 - - name: Set up Ruby - uses: ruby/setup-ruby@v1 + - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true + rubygems: '3.0.8' working-directory: ruby - run: bundle exec rspec working-directory: ruby diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b176eb9..6a5a14aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- [Ruby] Initial rubocop gems and basic compliance added (More work to come) ([#133](https://github.com/cucumber/tag-expressions/pull/133)) + +### Changed +- [Ruby] Minimum ruby version is now bumped from 1.9 to 2.3 ([#133](https://github.com/cucumber/tag-expressions/pull/133)) + ### Fixed - [Perl] Include README.md and LICENSE in the release tarball (by [ehuelsmann](https://github.com/ehuelsmann)) diff --git a/ruby/.gitignore b/ruby/.gitignore index 2d16bb2a..63759f3d 100644 --- a/ruby/.gitignore +++ b/ruby/.gitignore @@ -1,10 +1,5 @@ Gemfile.lock coverage/ acceptance/ -pkg/ -*.gem -.compared -.deps -.tested* *-go *.iml diff --git a/ruby/.rubocop.yml b/ruby/.rubocop.yml new file mode 100644 index 00000000..9889e7f5 --- /dev/null +++ b/ruby/.rubocop.yml @@ -0,0 +1,35 @@ +# TODO: Re-enable once tag-expressions > 7.0 +#require: +# - rubocop-performance +# - rubocop-rake +# - rubocop-rspec + +inherit_from: .rubocop_todo.yml + +inherit_mode: + merge: + - Exclude + +AllCops: + TargetRubyVersion: 2.3 +# TODO: Re-enable once rubocop > 1.10 +# NewCops: enable + +# Disabled on our repo's to enable polyglot-release +# TODO: Re-enable once rubocop > 1.40 +#Gemspec/RequireMFA: +# Enabled: false + +Layout/LineLength: + Max: 200 + +Style/Documentation: + Enabled: false + +Style/RegexpLiteral: + EnforcedStyle: slashes + AllowInnerSlashes: true + +# TODO: Re-enable once rubocop-rspec > 1.20 +#RSpec/MessageSpies: +# EnforcedStyle: receive diff --git a/ruby/.rubocop_todo.yml b/ruby/.rubocop_todo.yml new file mode 100644 index 00000000..e211320c --- /dev/null +++ b/ruby/.rubocop_todo.yml @@ -0,0 +1,244 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2023-10-06 16:28:14 +0100 using RuboCop version 0.79.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# TODO: Oct '23 -> 9 files inspected, 78 offenses detected + +# Offense count: 3 +# Cop supports --auto-correct. +Layout/EmptyLineAfterGuardClause: + Exclude: + - 'lib/cucumber/tag_expressions/parser.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Layout/EmptyLineAfterMagicComment: + Exclude: + - 'Gemfile' + +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. +Layout/ExtraSpacing: + Exclude: + - 'cucumber-tag-expressions.gemspec' + - 'lib/cucumber/tag_expressions/parser.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_braces +Layout/FirstHashElementIndentation: + Exclude: + - 'cucumber-tag-expressions.gemspec' + +# Offense count: 7 +# Cop supports --auto-correct. +# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. +# SupportedHashRocketStyles: key, separator, table +# SupportedColonStyles: key, separator, table +# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit +Layout/HashAlignment: + Exclude: + - 'cucumber-tag-expressions.gemspec' + - 'lib/cucumber/tag_expressions/parser.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: aligned, indented +Layout/MultilineOperationIndentation: + Exclude: + - 'lib/cucumber/tag_expressions/parser.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleInsidePipes. +# SupportedStylesInsidePipes: space, no_space +Layout/SpaceAroundBlockParameters: + Exclude: + - 'lib/cucumber/tag_expressions/parser.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator. +# SupportedStylesForExponentOperator: space, no_space +Layout/SpaceAroundOperators: + Exclude: + - 'cucumber-tag-expressions.gemspec' + - 'lib/cucumber/tag_expressions/expressions.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. +Lint/UnusedMethodArgument: + Exclude: + - 'lib/cucumber/tag_expressions/expressions.rb' + +# Offense count: 2 +Metrics/AbcSize: + Max: 27 + +# Offense count: 1 +# Configuration parameters: CountComments. +Metrics/ClassLength: + Max: 132 + +# Offense count: 1 +Metrics/CyclomaticComplexity: + Max: 13 + +# Offense count: 2 +# Configuration parameters: CountComments, ExcludedMethods. +Metrics/MethodLength: + Max: 29 + +# Offense count: 1 +Metrics/PerceivedComplexity: + Max: 15 + +# Offense count: 1 +# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. +# AllowedNames: io, id, to, by, on, in, at, ip, db, os +Naming/MethodParameterName: + Exclude: + - 'lib/cucumber/tag_expressions/parser.rb' + +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: percent_q, bare_percent +Style/BarePercentLiterals: + Exclude: + - 'lib/cucumber/tag_expressions/parser.rb' + - 'spec/errors_spec.rb' + - 'spec/parsing_spec.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. +# SupportedStyles: assign_to_condition, assign_inside_condition +Style/ConditionalAssignment: + Exclude: + - 'lib/cucumber/tag_expressions/parser.rb' + +# Offense count: 1 +Style/Documentation: + Exclude: + - 'spec/**/*' + - 'test/**/*' + - 'lib/cucumber/tag_expressions/expressions.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/Encoding: + Exclude: + - 'cucumber-tag-expressions.gemspec' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/ExpandPathArguments: + Exclude: + - 'Rakefile' + +# Offense count: 8 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: always, never +Style/FrozenStringLiteralComment: + Exclude: + - 'Rakefile' + - 'cucumber-tag-expressions.gemspec' + - 'lib/cucumber/tag_expressions.rb' + - 'lib/cucumber/tag_expressions/expressions.rb' + - 'lib/cucumber/tag_expressions/parser.rb' + - 'spec/errors_spec.rb' + - 'spec/evaluations_spec.rb' + - 'spec/parsing_spec.rb' + +# Offense count: 2 +# Configuration parameters: MinBodyLength. +Style/GuardClause: + Exclude: + - 'lib/cucumber/tag_expressions/parser.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/IfUnlessModifier: + Exclude: + - 'lib/cucumber/tag_expressions/parser.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: both, prefix, postfix +Style/NegatedIf: + Exclude: + - 'lib/cucumber/tag_expressions/parser.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods. +# SupportedStyles: predicate, comparison +Style/NumericPredicate: + Exclude: + - 'spec/**/*' + - 'lib/cucumber/tag_expressions/parser.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: PreferredDelimiters. +Style/PercentLiteralDelimiters: + Exclude: + - 'lib/cucumber/tag_expressions/parser.rb' + - 'spec/errors_spec.rb' + - 'spec/parsing_spec.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: . +# SupportedStyles: use_perl_names, use_english_names +Style/SpecialGlobalVars: + EnforcedStyle: use_perl_names + +# Offense count: 11 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiterals: + Exclude: + - 'Rakefile' + - 'cucumber-tag-expressions.gemspec' + - 'lib/cucumber/tag_expressions/expressions.rb' + - 'lib/cucumber/tag_expressions/parser.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInHashLiteral: + Exclude: + - 'cucumber-tag-expressions.gemspec' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/WhileUntilModifier: + Exclude: + - 'lib/cucumber/tag_expressions/parser.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/ZeroLengthPredicate: + Exclude: + - 'lib/cucumber/tag_expressions/parser.rb' + +# Offense count: 9 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https +Layout/LineLength: + Max: 140 diff --git a/ruby/Gemfile b/ruby/Gemfile index 9aa53998..7f4f5e95 100644 --- a/ruby/Gemfile +++ b/ruby/Gemfile @@ -1,3 +1,5 @@ # frozen_string_literal: true + source 'https://rubygems.org' + gemspec diff --git a/ruby/Rakefile b/ruby/Rakefile index 078ae345..fb697d68 100644 --- a/ruby/Rakefile +++ b/ruby/Rakefile @@ -1,8 +1,3 @@ -# encoding: utf-8 -require 'rubygems' -require 'bundler' -Bundler::GemHelper.install_tasks - $:.unshift File.expand_path("../lib", __FILE__) require "rspec/core/rake_task" diff --git a/ruby/cucumber-tag-expressions.gemspec b/ruby/cucumber-tag-expressions.gemspec index 5f084744..0fa0514c 100644 --- a/ruby/cucumber-tag-expressions.gemspec +++ b/ruby/cucumber-tag-expressions.gemspec @@ -1,5 +1,3 @@ -# -*- encoding: utf-8 -*- - version = File.read(File.expand_path("VERSION", __dir__)).strip Gem::Specification.new do |s| @@ -12,7 +10,8 @@ Gem::Specification.new do |s| s.homepage = 'https://cucumber.io/docs/cucumber/api/#tag-expressions' s.platform = Gem::Platform::RUBY s.license = 'MIT' - s.required_ruby_version = '>= 1.9.3' + s.required_ruby_version = '>= 2.3' + s.required_rubygems_version = '>= 3.0.8' s.metadata = { 'bug_tracker_uri' => 'https://github.com/cucumber/cucumber/issues', @@ -23,9 +22,9 @@ Gem::Specification.new do |s| } s.add_development_dependency 'rake', '~> 13.0', '>= 13.0.6' - s.add_development_dependency 'rspec', '~> 3.10', '>= 3.10.0' + s.add_development_dependency 'rspec', '~> 3.11' + s.add_development_dependency 'rubocop', '~> 0.79.0' - s.rubygems_version = '>= 1.6.1' s.files = Dir[ 'README.md', 'LICENSE', diff --git a/ruby/lib/cucumber/tag_expressions/parser.rb b/ruby/lib/cucumber/tag_expressions/parser.rb index 1c09450b..7476a47f 100644 --- a/ruby/lib/cucumber/tag_expressions/parser.rb +++ b/ruby/lib/cucumber/tag_expressions/parser.rb @@ -2,7 +2,6 @@ module Cucumber module TagExpressions - # Ruby tag expression parser class Parser def initialize @expressions = [] @@ -33,6 +32,7 @@ def parse(infix_expression) while @operators.any? raise %Q{Tag expression "#{infix_expression}" could not be parsed because of syntax error: Unmatched (.} if @operators.last == '(' + push_expression(pop(@operators)) end expression = pop(@expressions) @@ -41,9 +41,6 @@ def parse(infix_expression) private - ############################################################################ - # Helpers - # def assoc_of(token, value) @operator_types[token][:assoc] == value end @@ -109,9 +106,6 @@ def push_expression(token) end end - ############################################################################ - # Handlers - # def handle_unary_operator(infix_expression, token, expected_token_type) check(infix_expression, expected_token_type, :operand) @operators.push(token) @@ -140,6 +134,7 @@ def handle_close_paren(infix_expression, _token, expected_token_type) push_expression(pop(@operators)) end raise %Q{Tag expression "#{infix_expression}" could not be parsed because of syntax error: Unmatched ).} if @operators.empty? + pop(@operators) if @operators.last == '(' :operator end @@ -159,6 +154,7 @@ def check(infix_expression, expected_token_type, token_type) def pop(array, n = 1) result = array.pop(n) raise('Empty stack') if result.size != n + n == 1 ? result.first : result end end diff --git a/ruby/scripts/update-gemspec b/ruby/scripts/update-gemspec deleted file mode 100755 index d45401fd..00000000 --- a/ruby/scripts/update-gemspec +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash -# -# Updates the *.gemspec in the current directory to use the latest releases of gems -# -set -uf -o pipefail -IFS=$'\n' - -gemspec=$(find . -maxdepth 1 -type f -name "*.gemspec") -if [ "${gemspec}" = "" ]; then - exit 0 -fi -add_dependency_lines=$(cat ${gemspec} | grep "s.add_[a-z_]*dependency '[^']*'") -if [ $? -ne 0 ]; then - # No add_dependency_lines found - nothing to do - exit 0 -fi - -set -e - -gems=$(echo "${add_dependency_lines}" | tr -s ' ' | cut -d ' ' -f3 | cut -d"'" -f 2) -while read -r gem; do - echo "upgrading ${gem}" - if [ "${gem}" = "bundler" ]; then - cat "${gemspec}" | sed "s/\(s.add_[a-z_]*dependency\) '${gem}'.*/\1 '${gem}', '>= 1.16.2'/" > ${gemspec}.tmp - else - gem_line=$(gem list "${gem}" --remote --all --no-prerelease | grep "^${gem}\s") - latest_patch_version=$(echo "${gem_line}" | cut -d'(' -f2 | cut -d')' -f1 | cut -d',' -f1 | cut -d' ' -f1) - latest_minor_version=$(echo "${latest_patch_version}" | cut -d. -f1,2) - cat "${gemspec}" | sed "s/\(s.add_[a-z_]*dependency\) '${gem}'.*/\1 '${gem}', '~> ${latest_minor_version}', '>= ${latest_patch_version}'/" > ${gemspec}.tmp - fi - mv ${gemspec}.tmp ${gemspec} -done <<< "${gems}" diff --git a/ruby/spec/errors_spec.rb b/ruby/spec/errors_spec.rb index 3af908d3..1e5a6deb 100644 --- a/ruby/spec/errors_spec.rb +++ b/ruby/spec/errors_spec.rb @@ -5,8 +5,9 @@ describe 'Errors' do tests.each do |test| + let(:parser) { Cucumber::TagExpressions::Parser.new } + it %Q{fails to parse "#{test['expression']}" with "#{test['error']}"} do - parser = Cucumber::TagExpressions::Parser.new expect { parser.parse(test['expression']) }.to raise_error(test['error']) end end diff --git a/ruby/spec/evaluations_spec.rb b/ruby/spec/evaluations_spec.rb index a7f8a46f..0d0f6384 100644 --- a/ruby/spec/evaluations_spec.rb +++ b/ruby/spec/evaluations_spec.rb @@ -6,9 +6,10 @@ describe 'Evaluations' do evaluations.each do |evaluation| context evaluation['expression'] do + let(:parser) { Cucumber::TagExpressions::Parser.new } + evaluation['tests'].each do |test| it "evaluates [#{test['variables'].join(', ')}] to #{test['result']}" do - parser = Cucumber::TagExpressions::Parser.new expect(parser.parse(evaluation['expression']).evaluate(test['variables'])).to eq(test['result']) end end diff --git a/ruby/spec/parsing_spec.rb b/ruby/spec/parsing_spec.rb index 57329658..7b80bea7 100644 --- a/ruby/spec/parsing_spec.rb +++ b/ruby/spec/parsing_spec.rb @@ -4,11 +4,11 @@ tests = YAML.load_file('../testdata/parsing.yml') describe 'Parsing' do + let(:parser) { Cucumber::TagExpressions::Parser.new } + tests.each do |test| it %Q{parses "#{test['expression']}" into "#{test['formatted']}"} do - parser = Cucumber::TagExpressions::Parser.new - expression = parser.parse(test['expression']) - expect(expression.to_s).to eq(test['formatted']) + expect(parser.parse(test['expression']).to_s).to eq(test['formatted']) end end end