diff --git a/README.md b/README.md index c2db41e..a40c0e3 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ The goal is to create a Rubocop plugin which can check for covert some ugly code parts introduced by the automatic code conversion done by [YCP Killer](https://github.com/yast/ycp-killer) (conversion from YCP to Ruby). -Check [the test descriptions](spec/builtins_spec.md) to see the examples of offense -detection and code conversion. +Check [the RSpec tests](spec) and [the Cucumber features](features)to see +the examples of offense detection and code conversion. *The plugin is currently in early development, always manually check the chages done by the plugin! It can eat your code... ;-)* @@ -135,4 +135,4 @@ and then run: bundle exec rake release ``` -(Note: You need push permissions at Rubygems.org.) +(Note: You need push permissions at [rubygems.org](https://rubygems.org/gems/rubocop-yast).) diff --git a/Rakefile b/Rakefile index 6dfe755..ef15de7 100644 --- a/Rakefile +++ b/Rakefile @@ -10,34 +10,13 @@ rescue Bundler::BundlerError => e exit e.status_code end -require "redcarpet" -require_relative "spec/rspec_renderer" - -def render_markdown(renderer, task) - puts "Rendering file: #{task.name}" - markdown = Redcarpet::Markdown.new(renderer, fenced_code_blocks: true) - - string = markdown.render(File.read(task.prerequisites[0])) - File.write(task.name, string) -end - -renderer = "spec/rspec_renderer.rb" - -file "spec/builtins_spec.rb" => ["spec/builtins_spec.md", renderer] do |t| - render_markdown(RSpecRenderer.new("RuboCop::Cop::Yast::Builtins"), t) -end - -file "spec/builtins_spec.html" => ["spec/builtins_spec.md", renderer] do |t| - render_markdown(Redcarpet::Render::HTML, t) -end -desc "Render the specification to HTML locally" -task html: ["spec/builtins_spec.html"] +require "cucumber/rake/task" +Cucumber::Rake::Task.new(:features) require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) -task spec: ["spec/builtins_spec.rb"] require "rubocop/rake_task" RuboCop::RakeTask.new(:rubocop) -task default: [:spec, :rubocop] +task default: [:spec, :features, :rubocop] diff --git a/features/builtins_cop.feature b/features/builtins_cop.feature new file mode 100644 index 0000000..270b91c --- /dev/null +++ b/features/builtins_cop.feature @@ -0,0 +1,67 @@ +Feature: Generic Builtins detection and replacement + + Some Builtin calls are not detected as an offense and are kept unchanged. + That include the calls which do not have native ruby replacement, like lsort() + or crytp() functions, or the replacement would be too complex (the gettext + builtins). + + Only known builtins can be replaced, the rest needs to be kept untouched. + + + Scenario: y2milestone() builtin call is reported as an offense + Given the original code is + """ + Builtins.y2milestone("foo") + """ + When I check it using RuboCop::Cop::Yast::Builtins cop + Then offense "Builtin call `y2milestone` is obsolete" is found + + Scenario: Builtin with explicit Yast namespace is reported as an offense + Given the original code is + """ + Yast::Builtins.y2milestone("foo") + """ + When I check it using RuboCop::Cop::Yast::Builtins cop + Then offense "Builtin call `y2milestone` is obsolete" is found + + Scenario: Builtin with explicit ::Yast namespace is reported as an offense + Given the original code is + """ + ::Yast::Builtins.y2milestone("foo") + """ + When I check it using RuboCop::Cop::Yast::Builtins cop + Then offense "Builtin call `y2milestone` is obsolete" is found + + Scenario: Builtins with ::Builtins name space are ignored + Given the original code is + """ + ::Builtins.y2milestone("foo") + """ + When I check it using RuboCop::Cop::Yast::Builtins cop + Then the code is found correct + + Scenario: Builtins in non Yast name space are ignored + Given the original code is + """ + Foo::Builtins.y2milestone("foo") + """ + When I check it using RuboCop::Cop::Yast::Builtins cop + Then the code is found correct + + Scenario: lsort(), crypt and gettext builtins are allowed + Given the original code is + """ + Builtins.lsort(["foo"]) + Builtins.crypt("foo") + Builtins.dgettext("domain", "foo") + """ + When I check it using RuboCop::Cop::Yast::Builtins cop + Then the code is found correct + + Scenario: Unknown builtins are kept unchanged + Given the original code is + """ + Builtins.foo() + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is unchanged diff --git a/features/builtins_env.feature b/features/builtins_env.feature new file mode 100644 index 0000000..2101862 --- /dev/null +++ b/features/builtins_env.feature @@ -0,0 +1,39 @@ +Feature: Builtins.getenv() + + Builtins.getenv() call can be easily replaced by ENV hash access. + We just need to keep the original parameter. + + Scenario: With string literal parameter it is translated to ENV equivalent + Given the original code is + """ + Builtins.getenv("foo") + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + ENV["foo"] + """ + + Scenario: Variable as the parameter is preserved + Given the original code is + """ + foo = bar + Builtins.getenv(foo) + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + foo = bar + ENV[foo] + """ + + Scenario: Any other statement is preserved + Given the original code is + """ + Builtins.getenv(Ops.add(foo, bar)) + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + ENV[Ops.add(foo, bar)] + """ diff --git a/features/builtins_time.feature b/features/builtins_time.feature new file mode 100644 index 0000000..0ace69b --- /dev/null +++ b/features/builtins_time.feature @@ -0,0 +1,10 @@ +Feature: Builtins.time() + + Builtins.time() call can be easily replaced by native Time.now call. + It has no parameter therefore no extra handling is needed. + + + Scenario: Builtins.time() is replaced by ::Time.now.to_i + Given the original code is "Builtins.time()" + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to "::Time.now.to_i" diff --git a/features/builtins_y2log.feature b/features/builtins_y2log.feature new file mode 100644 index 0000000..a804d34 --- /dev/null +++ b/features/builtins_y2log.feature @@ -0,0 +1,383 @@ +Feature: Builtins.y2debug(), Builtins.y2milestone(), ... + + The logging builtins can be replaced by a logger call. + + But we need to replace the YCP sformat message by Ruby interpolation + (otherwise we would introduce a new Builtins.sformat() call and basically + just replaced one builtin by another). + + The new logger needs extra include call as it is defined in the + Yast::Logger module. This include is added at the parent class or module + definition. If there is no parent class or module then it is added at + the top level. + + Some modules already use (include) the logger, we need to check it's presence + and add it only when it is missing to avoid duplicates. + + Another possible problem is using a local variable named `log`. The problem + is that `log = "foo"` code would replace the logger object by a String + and all subsequent `log.*` calls would fail. + + In that case no logging call is replaced in whole class. + + + Scenario: `y2debug` call is changed to `log.debug` call + Given the original code is + """ + Builtins.y2debug("foo") + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.debug "foo" + """ + + Scenario: `y2milestone` call is changed to `log.info` + Given the original code is + """ + Builtins.y2milestone("foo") + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.info "foo" + """ + + Scenario: `y2warning` call is changed to `log.warn` + Given the original code is + """ + Builtins.y2warning("foo") + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.warn "foo" + """ + + Scenario: `y2error` call is changed to `log.error` + Given the original code is + """ + Builtins.y2error("foo") + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.error "foo" + """ + + Scenario: `y2security` call is changed to `log.error` + Given the original code is + """ + Builtins.y2security("foo") + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.error "foo" + """ + + Scenario: `y2internal` call is changed to `log.fatal` + Given the original code is + """ + Builtins.y2internal("foo") + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.fatal "foo" + """ + + Scenario: The include statement is added only once for multiple calls + Given the original code is + """ + Builtins.y2milestone("foo") + Builtins.y2milestone("foo") + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.info "foo" + log.info "foo" + """ + + Scenario: The YCP sformat message is converted to Ruby interpolation + Given the original code is + """ + Builtins.y2milestone("foo: %1", foo) + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.info "foo: #{foo}" + """ + + Scenario: The %% sequence in the format string is changed to simple % + Given the original code is + """ + Builtins.y2milestone("foo: %1%%", foo) + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.info "foo: #{foo}%" + """ + + Scenario: The repeated % placeholders are replaced in the format string + Given the original code is + """ + Builtins.y2warning("%1 %1", foo) + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.warn "#{foo} #{foo}" + """ + + Scenario: The % placeholders do not need to start from 1 + Given the original code is + """ + Builtins.y2warning("%2 %2", foo, bar) + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.warn "#{bar} #{bar}" + """ + + Scenario: The % placeholders do not need to be in ascending order + Given the original code is + """ + Builtins.y2warning("%2 %1", foo, bar) + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.warn "#{bar} #{foo}" + """ + + Scenario: The original statements in interpolated string are kept + Given the original code is + """ + Builtins.y2warning("%1", foo + bar) + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.warn "#{foo + bar}" + """ + + Scenario: A log message containing interpolattion is kept unchanged + Given the original code is + """ + Builtins.y2warning("foo: #{foo}") + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.warn "foo: #{foo}" + """ + + Scenario: Log message stored in a variable can be converted as well + Given the original code is + """ + msg = "message" + Builtins.y2warning(msg) + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + msg = "message" + log.warn msg + """ + + Scenario: Log message returned by function call is converted as well + Given the original code is + """ + Builtins.y2warning(msg) + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.warn msg + """ + + Scenario: Log call with variable message and extra parameters is kept unchaged + Given the original code is + """ + Builtins.y2warning(msg, arg1, arg2) + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is unchanged + + Scenario: Message with operator call is traslated + Given the original code is + """ + Builtins.y2warning(msg1 + msg2) + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.warn msg1 + msg2 + """ + + Scenario: The include is added to the class definition + Given the original code is + """ + class Foo + Builtins.y2error('foo') + end + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + class Foo + include Yast::Logger + + log.error "foo" + end + """ + + Scenario: The added include follows parent class indentation + Given the original code is + """ + class Foo + Builtins.y2error('foo') + end + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + class Foo + include Yast::Logger + + log.error "foo" + end + """ + + Scenario: The logger include is not added if already present + Given the original code is + """ + class Foo + include Yast::Logger + Builtins.y2error('foo') + end + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + class Foo + include Yast::Logger + log.error "foo" + end + """ + + Scenario: The logger include is added after the parent class name if present + Given the original code is + """ + class Foo < Bar + Builtins.y2error('foo') + end + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + class Foo < Bar + include Yast::Logger + + log.error "foo" + end + """ + + Scenario: The logger include is added to parent class when used in a method + Given the original code is + """ + module Yast + class TestClass < Module + def test + Builtins.y2error("foo") + Builtins.y2debug("foo") + end + end + end + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + module Yast + class TestClass < Module + include Yast::Logger + + def test + log.error "foo" + log.debug "foo" + end + end + end + """ + + Scenario: Single quoted format string is converted to double quoted + Given the original code is + """ + Builtins.y2milestone('foo: %1', foo) + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.info "foo: #{foo}" + """ + + Scenario: Double quotes and interpolations are escaped when converting a single quoted string + Given the original code is + """ + Builtins.y2milestone('"#{foo}"') + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is converted to + """ + include Yast::Logger + log.info "\"\#{foo}\"" + """ + + Scenario: Logging call with a backtrace is kept unchanged + Given the original code is + """ + Builtins.y2milestone(-1, "foo") + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is unchanged + + Scenario: Code with a local variable 'log' is kept unchanged + Given the original code is + """ + log = 1 + Builtins.y2milestone("foo") + """ + When I correct it using RuboCop::Cop::Yast::Builtins cop + Then the code is unchanged + + Scenario: Call with missing parenthesis around argument is also reported as an offense + Given the original code is + """ + Builtins.y2milestone "Executing hook '#{name}'" + """ + When I check it using RuboCop::Cop::Yast::Builtins cop + Then offense "Builtin call `y2milestone` is obsolete" is found + diff --git a/features/step_definitions/cop_steps.rb b/features/step_definitions/cop_steps.rb new file mode 100644 index 0000000..66b3e8e --- /dev/null +++ b/features/step_definitions/cop_steps.rb @@ -0,0 +1,42 @@ + +# inline code +Given(/^the original code is "(.*)"$/) do |original_code| + @original_code = original_code +end + +# multiline code passed via docstring +Given(/^the original code is$/) do |original_code| + @original_code = original_code +end + +When(/^I correct it using (.*) cop$/) do |cop| + @cop = Object.const_get(cop).new + @corrected = autocorrect_source(@cop, @original_code.split("\n")) +end + +# inline code +Then(/^the code is converted to "(.*)"$/) do |expected_code| + expect(@corrected).to eq(expected_code) +end + +# multiline code passed via docstring +Then(/^the code is converted to$/) do |expected_code| + expect(@corrected).to eq(expected_code) +end + +Then(/^the code is unchanged$/) do + expect(@corrected).to eq(@original_code) +end + +When(/^I check it using (.*) cop$/) do |cop| + @cop = Object.const_get(cop).new + inspect_source(@cop, @original_code.split("\n")) +end + +Then(/^the code is found correct$/) do + expect(@cop.offenses).to be_empty +end + +Then(/^offense "(.*)" is found$/) do |offense| + expect(@cop.offenses.first.message).to include(offense) +end diff --git a/features/support/env.rb b/features/support/env.rb new file mode 100644 index 0000000..b14d742 --- /dev/null +++ b/features/support/env.rb @@ -0,0 +1,36 @@ +# encoding: utf-8 + +require "simplecov" +require "rspec" + +# use coveralls for on-line code coverage reporting at Travis CI +if ENV["TRAVIS"] + require "coveralls" + + SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ + SimpleCov::Formatter::HTMLFormatter, + Coveralls::SimpleCov::Formatter + ] +end + +SimpleCov.start do + # don't check code coverage in these subdirectories + add_filter "/vendor/" + add_filter "/features/" +end + +# allow only the new "expect" RSpec syntax +RSpec.configure do |config| + config.expect_with :rspec do |c| + c.syntax = :expect + end +end + +# reuse the Rubocop helper, provides some nice methods used in tests +require File.join(Gem::Specification.find_by_name("rubocop").gem_dir, "spec", + "support", "cop_helper.rb") +include CopHelper + +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib")) + +require "rubocop-yast" diff --git a/rubocop-yast.gemspec b/rubocop-yast.gemspec index 104f9dc..7279e49 100644 --- a/rubocop-yast.gemspec +++ b/rubocop-yast.gemspec @@ -31,7 +31,7 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency("rubocop", "~> 0.27") spec.add_development_dependency("rake") - spec.add_development_dependency("redcarpet", "~> 3") spec.add_development_dependency("rspec", "~> 3.1.0") + spec.add_development_dependency("cucumber") spec.add_development_dependency("simplecov") end diff --git a/spec/builtins_spec.md b/spec/builtins_spec.md deleted file mode 100644 index a6ab0bd..0000000 --- a/spec/builtins_spec.md +++ /dev/null @@ -1,622 +0,0 @@ -Builtins Cop -============ - -Table Of Contents ------------------ - -1. [Description](#description) -1. [Builtins.time()](#builtinstime) -1. [Builtins.getenv()](#builtinsgetenv) -1. [Logging - Builtins.y2debug(),...](#logging---builtinsy2debug-) - -Description ------------ - -This Cop is designed to check for `Builtins` call presence. These calls were -added by YCP Killer to ensure 100% compatibily of the translated Ruby code -with the old YCP. - -But these calls should not be used in the new code, native Ruby methods should -be used instead of these wrappers. - -The Cop can autocorrect some trivial or easy translatable builtins. - -Generic Tests -------------- - -It reports y2milestone builtin as offense - -**Offense** - -```ruby -Builtins.y2milestone("foo") -``` - -It finds builtin in explicit Yast namespace - -**Offense** - -```ruby -Yast::Builtins.y2milestone("foo") -``` - -It finds builtin in explicit ::Yast namespace - -**Offense** - -```ruby -::Yast::Builtins.y2milestone("foo") -``` - -Builtins in the ::Builtins name space are ignored - -**Accepted** - -```ruby -::Builtins.y2milestone("foo") -``` - -Builtins in non Yast name space are ignored - -**Accepted** - -```ruby -Foo::Builtins.y2milestone("foo") -``` - -lsort(), crypt and gettext builtins are allowed - -**Accepted** - -```ruby -Builtins.lsort(["foo"]) -Builtins.crypt("foo") -Builtins.dgettext("domain", "foo") -``` - -It does not change unknown builtins - -**Unchanged** - -```ruby -Builtins.foo() -``` - - -Builtins.time() ---------------- - -Is trivially converted to `::Time.now.to_i` - -**Original** - -```ruby -Builtins.time() -``` - -**Translated** - -```ruby -::Time.now.to_i -``` - -Builtins.getenv() ------------------ - -With string literal parameter is translated to ENV equivalent - -**Original** - -```ruby -Builtins.getenv("foo") -``` - -**Translated** - -```ruby -ENV["foo"] -``` - -Variable as parameter is preserved. - -**Original** - -```ruby -foo = bar -Builtins.getenv(foo) -``` - -**Translated** - -```ruby -foo = bar -ENV[foo] -``` - -Any other statement is preserved. - -**Original** - -```ruby -Builtins.getenv(Ops.add(foo, bar)) -``` - -**Translated** - -```ruby -ENV[Ops.add(foo, bar)] -``` - -Logging - Builtins.y2debug(), ... ----------------------------------- - -It translates `y2debug` to `log.debug` - -**Original** - -```ruby -Builtins.y2debug("foo") -``` - -**Translated** - -```ruby -include Yast::Logger -log.debug "foo" -``` - -It translates `y2milestone` to `log.info` - -**Original** - -```ruby -Builtins.y2milestone("foo") -``` - -**Translated** - -```ruby -include Yast::Logger -log.info "foo" -``` - -It translates `y2warning` to `log.warn` - -**Original** - -```ruby -Builtins.y2warning("foo") -``` - -**Translated** - -```ruby -include Yast::Logger -log.warn "foo" -``` - -It translates `y2error` to `log.error` - -**Original** - -```ruby -Builtins.y2error("foo") -``` - -**Translated** - -```ruby -include Yast::Logger -log.error "foo" -``` - -It translates `y2security` to `log.error` - -**Original** - -```ruby -Builtins.y2security("foo") -``` - -**Translated** - -```ruby -include Yast::Logger -log.error "foo" -``` - -It translates `y2internal` to `log.fatal` - -**Original** - -```ruby -Builtins.y2internal("foo") -``` - -**Translated** - -```ruby -include Yast::Logger -log.fatal "foo" -``` - -It adds the include statement only once - -**Original** - -```ruby -Builtins.y2milestone("foo") -Builtins.y2milestone("foo") -``` - -**Translated** - -```ruby -include Yast::Logger -log.info \"foo\" -log.info \"foo\" -``` - -It converts YCP sformat to Ruby interpolation - -**Original** - -```ruby -Builtins.y2milestone("foo: %1", foo) -``` - -**Translated** - -```ruby -include Yast::Logger -log.info "foo: #{foo}" -``` - -It converts %% in the format string to simple %. - -**Original** - -```ruby -Builtins.y2milestone("foo: %1%%", foo) -``` - -**Translated** - -```ruby -include Yast::Logger -log.info "foo: #{foo}%" -``` - -It replaces repeated % placeholders in the format string - -**Original** - -```ruby -Builtins.y2warning("%1 %1", foo) -``` - -**Translated** - -```ruby -include Yast::Logger -log.warn "#{foo} #{foo}" -``` - -The % placeholders do not need to start from 1 - -**Original** - -```ruby -Builtins.y2warning("%2 %2", foo, bar) -``` - -**Translated** - -```ruby -include Yast::Logger -log.warn "#{bar} #{bar}" -``` - -The % placeholders do not need to be in ascending order - -**Original** - -```ruby -Builtins.y2warning("%2 %1", foo, bar) -``` - -**Translated** - -```ruby -include Yast::Logger -log.warn "#{bar} #{foo}" -``` - -It keeps the original statements in interpolated string - -**Original** - -```ruby -Builtins.y2warning("%1", foo + bar) -``` - -**Translated** - -```ruby -include Yast::Logger -log.warn "#{foo + bar}" -``` - -It converts a log with string interpolation - -**Original** - -```ruby -Builtins.y2warning("foo: #{foo}") -``` - -**Translated** - -```ruby -include Yast::Logger -log.warn "foo: #{foo}" -``` - -It converts a log with a message variable - -**Original** - -```ruby -msg = "message" -Builtins.y2warning(msg) -``` - -**Translated** - -```ruby -include Yast::Logger -msg = "message" -log.warn msg -``` - -It converts a log with function call - -**Original** - -```ruby -Builtins.y2warning(msg) -``` - -**Translated** - -```ruby -include Yast::Logger -log.warn msg -``` - -It doesn't convert a log with extra parameters - -**Unchanged** - -```ruby -Builtins.y2warning(msg, arg1, arg2) -``` - -It converts log with operator call - -**Original** - -```ruby -Builtins.y2warning(msg1 + msg2) -``` - -**Translated** - -```ruby -include Yast::Logger -log.warn msg1 + msg2 -``` - -It adds logger include to the class definition - -**Original** - -```ruby -class Foo - Builtins.y2error('foo') -end -``` - -**Translated** - -```ruby -class Foo - include Yast::Logger - - log.error "foo" -end -``` - -It adds logger include with correct indentation - -**Original** - -```ruby - class Foo - Builtins.y2error('foo') - end -``` - -**Translated** - -```ruby - class Foo - include Yast::Logger - - log.error "foo" - end -``` - -It does not add the logger include if already present - -**Original** - -```ruby -class Foo - include Yast::Logger - Builtins.y2error('foo') -end -``` - -**Translated** - -```ruby -class Foo - include Yast::Logger - log.error "foo" -end -``` - -It adds the logger include after the parent class name if present - -**Original** - -```ruby -class Foo < Bar - Builtins.y2error('foo') -end -``` - -**Translated** - -```ruby -class Foo < Bar - include Yast::Logger - - log.error "foo" -end -``` - -It adds logger include once to a derived class in a module - -**Original** - -```ruby -module Yast - class TestClass < Module - def test - Builtins.y2error("foo") - Builtins.y2debug("foo") - end - end -end -``` - -**Translated** - -```ruby -module Yast - class TestClass < Module - include Yast::Logger - - def test - log.error "foo" - log.debug "foo" - end - end -end -``` - -It converts the single quoted format string to double quoted - -**Original** - -```ruby -Builtins.y2milestone('foo: %1', foo) -``` - -**Translated** - -```ruby -include Yast::Logger -log.info "foo: #{foo}" -``` - -It escapes double quotes and interpolations - -**Original** - -```ruby -Builtins.y2milestone('"#{foo}"') -``` - -**Translated** - -```ruby -include Yast::Logger -log.info "\\"\\#{foo}\\"" -``` - -It does not convert logging with backtrace - -**Unchanged** - -```ruby -Builtins.y2milestone(-1, "foo") -``` - -It does not convert code with a local variable 'log' - -**Unchanged** - -```ruby -log = 1 -Builtins.y2milestone("foo") -``` - -It finds an offense with missing parenthesis around argument - -**Offense** - -```ruby -Builtins.y2milestone "Executing hook '#{name}'" -``` - - diff --git a/spec/rspec_code.rb b/spec/rspec_code.rb deleted file mode 100644 index b86f087..0000000 --- a/spec/rspec_code.rb +++ /dev/null @@ -1,46 +0,0 @@ - -# this module provides code block generators for RSpecRenderer -module RspecCode - # rubocop:disable Metrics/MethodLength - def generate_translation_code - [ - "original_code = code_cleanup(<<-EOT)", - Code.indent(@original_code), - "EOT", - "", - "translated_code = code_cleanup(<<-EOT)", - Code.indent(@translated_code), - "EOT", - "", - "cop = #{@tested_class}.new", - "expect(autocorrect_source(cop, original_code)).to eq(translated_code)" - ].join("\n") - end - - def generate_offense_code - [ - "code = code_cleanup(<<-EOT)", - Code.indent(@offense), - "EOT", - "", - "cop = #{@tested_class}.new", - "inspect_source(cop, [code])", - "", - "expect(cop.offenses.size).to eq(1)" - ].join("\n") - end - - def generate_accepted_code - [ - "code = code_cleanup(<<-EOT)", - Code.indent(@accepted_code), - "EOT", - "", - "cop = #{@tested_class}.new", - "inspect_source(cop, [code])", - "", - "expect(cop.offenses).to be_empty" - ].join("\n") - end - # rubocop:enable Metrics/MethodLength -end diff --git a/spec/rspec_renderer.rb b/spec/rspec_renderer.rb deleted file mode 100644 index 188d703..0000000 --- a/spec/rspec_renderer.rb +++ /dev/null @@ -1,208 +0,0 @@ -require "redcarpet" - -require_relative "rspec_code" - -# Utility functions for manipulating code. -module Code - INDENT_STEP = 2 - - class << self - def join(lines) - lines.map { |l| "#{l}\n" }.join("") - end - - def indent(s) - s.gsub(/^(?=.)/, " " * INDENT_STEP) - end - end -end - -# Represents RSpec's "it" block. -class It - def initialize(attrs) - @description = attrs[:description] - @code = attrs[:code] - @skip = attrs[:skip] - end - - def render - [ - "#{@skip ? "xit" : "it"} #{@description.inspect} do", - Code.indent(@code), - "end" - ].join("\n") - end -end - -# Represents RSpec's "describe" block. -class Describe - attr_reader :blocks - - def initialize(attrs) - @description = attrs[:description] - @blocks = attrs[:blocks] - end - - def render - parts = [] - parts << "describe #{@description.inspect} do" - parts << Code.indent(@blocks.map(&:render).join("\n\n")) if !blocks.empty? - parts << "end" - parts.join("\n") - end -end - -# Renders a Markdown file to an RSpec test script -class RSpecRenderer < Redcarpet::Render::Base - include RspecCode - - IGNORED_HEADERS = [ - "Table Of Contents", - "Description" - ] - - def initialize(tested_class, description = nil) - super() - - @next_block_type = :unknown - @tested_class = tested_class - @describe = Describe.new(description: description || tested_class, - blocks: []) - end - - # preprocess the MarkDown input - remove comments - def preprocess(fulldoc) - # use multiline regexp pattern - fulldoc.gsub(//m, "") - end - - def header(text, header_level) - return nil if header_level == 1 || IGNORED_HEADERS.include?(text) - - if header_level > describes_depth + 1 - raise "Missing higher level header: #{text}" - end - - describe_at_level(header_level - 1).blocks << Describe.new( - description: text + ":", - blocks: [] - ) - - nil - end - - def paragraph(text) - if text =~ /^\*\*(.*)\*\*$/ - @next_block_type = Regexp.last_match[1].downcase.to_sym - else - first_sentence = text.split(/\.(\s+|$)/).first - @description = first_sentence.sub(/^RuboCop-Yast /, "").sub(/\n/, " ") - end - - nil - end - - # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity: - def block_code(code, _language) - escaped_code = escape(code[0..-2]) - - case @next_block_type - when :original - @original_code = escaped_code - when :translated - @translated_code = escaped_code - when :unchanged - @original_code = @translated_code = escaped_code - when :offense - @offense = escaped_code - when :accepted - @accepted_code = escaped_code - else - raise "Invalid next code block type: #{@next_block_type}.\n#{code}" - end - - @next_block_type = :unknown - - add_translation_block if @original_code && @translated_code - add_offense_block if @offense - add_accepted_block if @accepted_code - - nil - end - # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity: - - # escape ruby interpolation - def escape(code) - code.gsub("\#{", "\\\#{") - end - - def doc_header - Code.join([ - "# Automatically generated -- DO NOT EDIT!", - "", - "require \"spec_helper\"", - "" - ]) - end - - def doc_footer - "#{@describe.render}\n" - end - - private - - def describes_depth - describe = @describe - depth = 1 - while describe.blocks.last.is_a?(Describe) - describe = describe.blocks.last - depth += 1 - end - depth - end - - def current_describe - describe = @describe - describe = describe.blocks.last while describe.blocks.last.is_a?(Describe) - describe - end - - def describe_at_level(level) - describe = @describe - 2.upto(level) do - describe = describe.blocks.last - end - describe - end - - def add_translation_block - current_describe.blocks << It.new( - description: @description, - code: generate_translation_code, - skip: @description =~ /XFAIL/ - ) - - @original_code = nil - @translated_code = nil - end - - def add_offense_block - current_describe.blocks << It.new( - description: @description, - code: generate_offense_code, - skip: @description =~ /XFAIL/ - ) - - @offense = nil - end - - def add_accepted_block - current_describe.blocks << It.new( - description: @description, - code: generate_accepted_code, - skip: @description =~ /XFAIL/ - ) - - @accepted_code = nil - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 39b00d8..b5bafe4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,18 +2,6 @@ require "simplecov" -# use coveralls for on-line code coverage reporting at Travis CI -if ENV["TRAVIS"] - require "coveralls" - - SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ - SimpleCov::Formatter::HTMLFormatter, - Coveralls::SimpleCov::Formatter - ] -end - -SimpleCov.minimum_coverage 95 - SimpleCov.start do # don't check code coverage in these subdirectories add_filter "/vendor/" @@ -38,10 +26,4 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib")) -def code_cleanup(s) - s.split("\n").reject { |l| l =~ /^\s*$/ }.first =~ /^(\s*)/ - return s.dup if Regexp.last_match.nil? - s.gsub(Regexp.new("^#{Regexp.last_match[1]}"), "")[0..-2] -end - require "rubocop-yast"