cuke_linter 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (26) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -1
  3. data/README.md +8 -11
  4. data/Rakefile +2 -2
  5. data/lib/cuke_linter.rb +7 -3
  6. data/lib/cuke_linter/linters/example_without_name_linter.rb +9 -13
  7. data/lib/cuke_linter/linters/feature_without_scenarios_linter.rb +9 -13
  8. data/lib/cuke_linter/linters/linter.rb +35 -0
  9. data/lib/cuke_linter/linters/outline_with_single_example_row_linter.rb +11 -15
  10. data/lib/cuke_linter/linters/test_with_too_many_steps_linter.rb +12 -16
  11. data/lib/cuke_linter/version.rb +1 -1
  12. data/testing/cucumber/features/linters/custom_linters.feature +54 -0
  13. data/testing/cucumber/step_definitions/action_steps.rb +1 -1
  14. data/testing/cucumber/step_definitions/setup_steps.rb +27 -0
  15. data/testing/cucumber/step_definitions/verification_steps.rb +1 -1
  16. data/testing/linter_factory.rb +7 -3
  17. data/testing/model_factory.rb +9 -12
  18. data/testing/rspec/spec/integration/cuke_linter_integration_spec.rb +16 -5
  19. data/testing/rspec/spec/integration/linters/linter_integration_spec.rb +8 -0
  20. data/testing/rspec/spec/unit/linters/example_without_name_linter_unit_spec.rb +11 -11
  21. data/testing/rspec/spec/unit/linters/feature_without_scenarios_linter_unit_spec.rb +13 -13
  22. data/testing/rspec/spec/unit/linters/linter_unit_spec.rb +180 -0
  23. data/testing/rspec/spec/unit/linters/linter_unit_specs.rb +7 -7
  24. data/testing/rspec/spec/unit/linters/outline_with_single_example_row_linter_unit_spec.rb +17 -17
  25. data/testing/rspec/spec/unit/linters/test_with_too_many_steps_linter_unit_spec.rb +22 -22
  26. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07f2dd742bac2e9218abe46eed4c60d925c9735cb36e416b247f351f5318edd9
4
- data.tar.gz: cd3cbe140a04bb6fcb372ef4637ea567b83693f716808a3d157aad50c9212a1c
3
+ metadata.gz: 232cb6cefe917fc75ea2c3b21da060298a4b4f1d4c392dd0853ee2cef91bb80d
4
+ data.tar.gz: b8365e3d6b4b82bb494a9af552ee06db6467dac7e73d144a5c7dcc4df8d7b59c
5
5
  SHA512:
6
- metadata.gz: 5d6c601cebde0cf6adbb604368266b454060dc7bd1469f49a9504210f4197530455f08218ba5e0a95ea18aec3e2735dd41c9f41165ced06926081b861a1bc9eb
7
- data.tar.gz: b852cc1f217e681e5d0c23a2f05ca19d54027700d4b30db5845977d0c9c29aa8416128430d34f37ab77dc7d8d623838cee406fabe190c487a1b1cbe17197ebe2
6
+ metadata.gz: e2ad543a81bd093b5856e82fe7fb2bcf74447dcd96f1816adc47dcb220253d4a5e581e2e7fa7414cbb595b34815e217f431fb658f4ce70f4a779f8ff1bbc603c
7
+ data.tar.gz: b721820908884a39d786de81e8b87ff3c77daa4ff77037ad87384e301f3391f005328c0147aae9b093aea40552cf35a030db62c563c893838bef275f61dd44f9
data/CHANGELOG.md CHANGED
@@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
 
9
9
  Nothing yet...
10
10
 
11
+ ## [0.4.0] - 2019-05-11
12
+
13
+ ### Added
14
+ - A base linter class has been added that can be used to create custom linters more easily by providing common boilerplate code that every linter would need.
15
+
16
+ ### Changed
17
+ - Linters now return only a single problem instead of returning a collection of problems.
18
+
11
19
  ## [0.3.1] - 2019-04-13
12
20
 
13
21
  ### Added
@@ -33,7 +41,8 @@ Nothing yet...
33
41
  - Custom linters, formatters, and command line usability
34
42
 
35
43
 
36
- [Unreleased]: https://github.com/enkessler/cuke_linter/compare/v0.3.1...HEAD
44
+ [Unreleased]: https://github.com/enkessler/cuke_linter/compare/v0.4.0...HEAD
45
+ [0.4.0]: https://github.com/enkessler/cuke_linter/compare/v0.3.1...v0.4.0
37
46
  [0.3.1]: https://github.com/enkessler/cuke_linter/compare/v0.3.0...v0.3.1
38
47
  [0.3.0]: https://github.com/enkessler/cuke_linter/compare/v0.2.0...v0.3.0
39
48
  [0.2.0]: https://github.com/enkessler/cuke_linter/compare/v0.1.0...v0.2.0
data/README.md CHANGED
@@ -56,20 +56,17 @@ CukeLinter.lint
56
56
 
57
57
  The linting will happen against a tree of `CukeModeler` models that is generated based on the current directory. You can generate your own model tree and use that instead, if desired.
58
58
 
59
- Custom linters can be any object that responds to `#lint` and returns a collection of detected issues in the format of
59
+ Custom linters can be any object that responds to `#lint` and returns a detected issue (or `nil`) in the format of
60
60
 
61
61
  ```
62
- [
63
- { problem: 'some linting issue',
64
- location: 'path/to/file:line_number' },
65
- { problem: 'some linting issue',
66
- location: 'path/to/file:line_number' },
67
- # etc.
68
- ]
62
+ { problem: 'some linting issue',
63
+ location: 'path/to/file:line_number' }
69
64
  ```
70
65
 
71
66
  Note that a linter will receive, in turn, *every model* in the model tree in order for it to have the chance to detect problems with it. Checking the model's class before attempting to lint it is recommended.
72
67
 
68
+ **In order to simplify the process of creating custom linters a base class is provided (see [documentation](#documentation)).**
69
+
73
70
  Custom formatters can be any object that responds to `#format` and takes input data in the following format:
74
71
 
75
72
  ```
@@ -98,12 +95,12 @@ class MyCustomLinter
98
95
  end
99
96
 
100
97
  def lint(model)
101
- return [] unless model.is_a?(CukeModeler::Scenario)
98
+ return nil unless model.is_a?(CukeModeler::Scenario)
102
99
 
103
100
  if model.name.empty?
104
- [{ problem: 'Scenario has no name', location: "#{model.get_ancestor(:feature_file).path}:#{model.source_line}" }]
101
+ { problem: 'Scenario has no name', location: "#{model.get_ancestor(:feature_file).path}:#{model.source_line}" }
105
102
  else
106
- []
103
+ nil
107
104
  end
108
105
  end
109
106
 
data/Rakefile CHANGED
@@ -50,11 +50,11 @@ namespace 'cuke_linter' do
50
50
  Rake::Task['cuke_linter:test_everything'].invoke
51
51
  Rake::Task['cuke_linter:check_documentation'].invoke
52
52
  rescue => e
53
- puts Rainbow("Something isn't right!").red
53
+ puts Rainbow("-----------------------\nSomething isn't right!").red
54
54
  raise e
55
55
  end
56
56
 
57
- puts Rainbow('All is well. :)').green
57
+ puts Rainbow("-----------------------\nAll is well. :)").green
58
58
  end
59
59
 
60
60
  end
data/lib/cuke_linter.rb CHANGED
@@ -2,6 +2,7 @@ require 'cuke_modeler'
2
2
 
3
3
  require "cuke_linter/version"
4
4
  require 'cuke_linter/formatters/pretty_formatter'
5
+ require 'cuke_linter/linters/linter'
5
6
  require 'cuke_linter/linters/example_without_name_linter'
6
7
  require 'cuke_linter/linters/feature_without_scenarios_linter'
7
8
  require 'cuke_linter/linters/outline_with_single_example_row_linter'
@@ -73,9 +74,12 @@ module CukeLinter
73
74
  # TODO: have linters lint only certain types of models
74
75
  # linting_data.concat(linter.lint(model)) if relevant_model?(linter, model)
75
76
 
76
- linted_data = linter.lint(model)
77
- linted_data.each { |data_point| data_point[:linter] = linter.name }
78
- linting_data.concat(linted_data)
77
+ result = linter.lint(model)
78
+
79
+ if result
80
+ result[:linter] = linter.name
81
+ linting_data << result
82
+ end
79
83
  end
80
84
  end
81
85
 
@@ -2,22 +2,18 @@ module CukeLinter
2
2
 
3
3
  # A linter that detects unnamed example groups
4
4
 
5
- class ExampleWithoutNameLinter
5
+ class ExampleWithoutNameLinter < Linter
6
6
 
7
- # Returns the name of the linter
8
- def name
9
- 'ExampleWithoutNameLinter'
10
- end
7
+ # The rule used to determine if a model has a problem
8
+ def rule(model)
9
+ return false unless model.is_a?(CukeModeler::Example)
11
10
 
12
- # Lints the given model and returns linting data about said model
13
- def lint(model)
14
- return [] unless model.is_a?(CukeModeler::Example)
11
+ model.name.nil? || model.name.empty?
12
+ end
15
13
 
16
- if model.name.nil? || model.name.empty?
17
- [{ problem: 'Example has no name', location: "#{model.get_ancestor(:feature_file).path}:#{model.source_line}" }]
18
- else
19
- []
20
- end
14
+ # The message used to describe the problem that has been found
15
+ def message
16
+ 'Example has no name'
21
17
  end
22
18
 
23
19
  end
@@ -2,22 +2,18 @@ module CukeLinter
2
2
 
3
3
  # A linter that detects empty features
4
4
 
5
- class FeatureWithoutScenariosLinter
5
+ class FeatureWithoutScenariosLinter < Linter
6
6
 
7
- # Returns the name of the linter
8
- def name
9
- 'FeatureWithoutScenariosLinter'
10
- end
7
+ # The rule used to determine if a model has a problem
8
+ def rule(model)
9
+ return false unless model.is_a?(CukeModeler::Feature)
11
10
 
12
- # Lints the given model and returns linting data about said model
13
- def lint(model)
14
- return [] unless model.is_a?(CukeModeler::Feature)
11
+ model.tests.nil? || model.tests.empty?
12
+ end
15
13
 
16
- if model.tests.nil? || model.tests.empty?
17
- [{ problem: 'Feature has no scenarios', location: "#{model.parent_model.path}:#{model.source_line}" }]
18
- else
19
- []
20
- end
14
+ # The message used to describe the problem that has been found
15
+ def message
16
+ 'Feature has no scenarios'
21
17
  end
22
18
 
23
19
  end
@@ -0,0 +1,35 @@
1
+ module CukeLinter
2
+
3
+ # A generic linter that can be used to make arbitrary linting rules
4
+
5
+ class Linter
6
+
7
+ # Creates a new linter object
8
+ def initialize(name: nil, message: nil, rule: nil)
9
+ @name = name || self.class.name.split('::').last
10
+ @message = message || "#{self.name} problem detected"
11
+ @rule = rule
12
+ end
13
+
14
+ # Returns the name of the linter
15
+ def name
16
+ @name
17
+ end
18
+
19
+ # Lints the given model and returns linting data about said model
20
+ def lint(model)
21
+ raise 'No linting rule provided!' unless @rule || respond_to?(:rule)
22
+
23
+ problem_found = respond_to?(:rule) ? rule(model) : @rule.call(model)
24
+
25
+ if problem_found
26
+ problem_message = respond_to?(:message) ? message : @message
27
+
28
+ { problem: problem_message, location: "#{model.get_ancestor(:feature_file).path}:#{model.source_line}" }
29
+ else
30
+ nil
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -2,25 +2,21 @@ module CukeLinter
2
2
 
3
3
  # A linter that detects outlines that don't have multiple example rows
4
4
 
5
- class OutlineWithSingleExampleRowLinter
5
+ class OutlineWithSingleExampleRowLinter < Linter
6
6
 
7
- # Returns the name of the linter
8
- def name
9
- 'OutlineWithSingleExampleRowLinter'
10
- end
11
-
12
- # Lints the given model and returns linting data about said model
13
- def lint(model)
14
- return [] unless model.is_a?(CukeModeler::Outline)
15
- return [] if model.examples.nil?
7
+ # The rule used to determine if a model has a problem
8
+ def rule(model)
9
+ return false unless model.is_a?(CukeModeler::Outline)
10
+ return false if model.examples.nil?
16
11
 
17
12
  examples_rows = model.examples.collect(&:argument_rows).flatten
18
13
 
19
- if examples_rows.count == 1
20
- [{ problem: 'Outline has only one example row', location: "#{model.get_ancestor(:feature_file).path}:#{model.source_line}" }]
21
- else
22
- []
23
- end
14
+ examples_rows.count == 1
15
+ end
16
+
17
+ # The message used to describe the problem that has been found
18
+ def message
19
+ 'Outline has only one example row'
24
20
  end
25
21
 
26
22
  end
@@ -2,30 +2,26 @@ module CukeLinter
2
2
 
3
3
  # A linter that detects scenarios and outlines that have too many steps
4
4
 
5
- class TestWithTooManyStepsLinter
6
-
7
- # Returns the name of the linter
8
- def name
9
- 'TestWithTooManyStepsLinter'
10
- end
5
+ class TestWithTooManyStepsLinter < Linter
11
6
 
12
7
  # Changes the linting settings on the linter using the provided configuration
13
8
  def configure(options)
14
9
  @step_threshold = options['StepThreshold'] if options['StepThreshold']
15
10
  end
16
11
 
17
- # Lints the given model and returns linting data about said model
18
- def lint(model)
19
- return [] unless model.is_a?(CukeModeler::Scenario) || model.is_a?(CukeModeler::Outline)
12
+ # The rule used to determine if a model has a problem
13
+ def rule(model)
14
+ return false unless model.is_a?(CukeModeler::Scenario) || model.is_a?(CukeModeler::Outline)
20
15
 
21
- step_count = model.steps.nil? ? 0 : model.steps.count
22
- step_threshold = @step_threshold || 10
16
+ @linted_step_count = model.steps.nil? ? 0 : model.steps.count
17
+ @linted_step_threshold = @step_threshold || 10
18
+
19
+ @linted_step_count > @linted_step_threshold
20
+ end
23
21
 
24
- if step_count > step_threshold
25
- [{ problem: "Test has too many steps. #{step_count} steps found (max #{step_threshold})", location: "#{model.get_ancestor(:feature_file).path}:#{model.source_line}" }]
26
- else
27
- []
28
- end
22
+ # The message used to describe the problem that has been found
23
+ def message
24
+ "Test has too many steps. #{@linted_step_count} steps found (max #{@linted_step_threshold})"
29
25
  end
30
26
 
31
27
  end
@@ -1,4 +1,4 @@
1
1
  module CukeLinter
2
2
  # The release version of this gem
3
- VERSION = "0.3.1"
3
+ VERSION = "0.4.0"
4
4
  end
@@ -0,0 +1,54 @@
1
+ Feature: Custom linters
2
+
3
+ In addition to the linters provided by CukeSlicer, custom linters can be used. A linter is essentially any object that provides a few needed methods. In order to simplify the creation of custom linters, a base linter class is available that provides these needed methods.
4
+
5
+
6
+ Scenario: Creating a custom linter object
7
+ Given the following custom linter object:
8
+ """
9
+ custom_name = 'MyCustomLinter'
10
+ custom_message = 'My custom message'
11
+ custom_rule = lambda do |model|
12
+ # Your logic here, return true for a problem and false for not problem
13
+ true
14
+ end
15
+
16
+ @linter = CukeLinter::Linter.new(name: custom_name,
17
+ message: custom_message,
18
+ rule: custom_rule)
19
+ """
20
+ And a model to lint
21
+ When the model is linted
22
+ Then an error is reported
23
+ | linter | problem | location |
24
+ | MyCustomLinter | My custom message | <path_to_file>:<model_line_number> |
25
+
26
+ Scenario: Creating a custom linter class
27
+ Given the following custom linter class:
28
+ """
29
+ class MyCustomLinter < CukeLinter::Linter
30
+
31
+ def name
32
+ 'MyCustomLinter'
33
+ end
34
+
35
+ def message
36
+ 'My custom message'
37
+ end
38
+
39
+ def rule(model)
40
+ # Your logic here, return true for a problem and false for not problem
41
+ true
42
+ end
43
+
44
+ end
45
+ """
46
+ And the following code is used:
47
+ """
48
+ @linter = MyCustomLinter.new
49
+ """
50
+ And a model to lint
51
+ When the model is linted
52
+ Then an error is reported
53
+ | linter | problem | location |
54
+ | MyCustomLinter | My custom message | <path_to_file>:<model_line_number> |
@@ -8,7 +8,7 @@ When(/^it is formatted by the "([^"]*)" formatter$/) do |linter_name|
8
8
  @results = CukeLinter.const_get("#{linter_name.capitalize}Formatter").new.format(@linter_data)
9
9
  end
10
10
 
11
- When(/^(?:the feature|it) is linted$/) do
11
+ When(/^(?:the feature|the model|it) is linted$/) do
12
12
  options = { model_tree: @model,
13
13
  formatters: [[CukeLinter::FormatterFactory.generate_fake_formatter, "#{CukeLinter::FileHelper::create_directory}/junk_output_file.txt"]] }
14
14
  options[:linters] = [@linter] if @linter
@@ -66,3 +66,30 @@ end
66
66
  Given(/^no linters are currently registered$/) do
67
67
  CukeLinter.clear_registered_linters
68
68
  end
69
+
70
+ Given(/^the following custom linter object:$/) do |code|
71
+ code.sub!('<path_to>', @root_test_directory)
72
+ code.sub!('<code_to_generate_a_new_linter_instance>', 'CukeLinter::LinterFactory.generate_fake_linter')
73
+
74
+ if @working_directory
75
+ Dir.chdir(@working_directory) do
76
+ eval(code)
77
+ end
78
+ else
79
+ eval(code)
80
+ end
81
+ end
82
+
83
+ And(/^a model to lint$/) do
84
+ # Any old model should be fine
85
+ @model = CukeModeler::Feature.new
86
+
87
+ fake_file_model = CukeModeler::FeatureFile.new
88
+ fake_file_model.path = 'path_to_file'
89
+
90
+ @model.parent_model = fake_file_model
91
+ end
92
+
93
+ Given(/^the following custom linter class:$/) do |code|
94
+ eval(code)
95
+ end
@@ -10,7 +10,7 @@ Then(/^an error is reported$/) do |table|
10
10
  table.hashes.each do |error_record|
11
11
  expect(@results).to include({ linter: error_record['linter'],
12
12
  problem: error_record['problem'],
13
- location: error_record['location'].sub('<path_to_file>', @model.get_ancestor(:feature_file).path) })
13
+ location: error_record['location'].sub('<path_to_file>', @model.get_ancestor(:feature_file).path).sub('<model_line_number>', @model.source_line.to_s) })
14
14
  end
15
15
  end
16
16
 
@@ -1,15 +1,19 @@
1
1
  module CukeLinter
2
2
  module LinterFactory
3
3
 
4
- def self.generate_fake_linter(name: 'FakeLinter')
4
+ def self.generate_fake_linter(name: 'FakeLinter', finds_problems: true)
5
5
  linter = Object.new
6
6
 
7
7
  linter.define_singleton_method('lint') do |model|
8
8
  location = model.respond_to?(:source_line) ? "#{model.get_ancestor(:feature_file).path}:#{model.source_line}" : model.path
9
9
  problem = @problem || "#{name} problem"
10
10
 
11
- [{ problem: problem,
12
- location: location }]
11
+ if finds_problems
12
+ { problem: problem,
13
+ location: location }
14
+ else
15
+ nil
16
+ end
13
17
  end
14
18
 
15
19
  linter.define_singleton_method('name') do
@@ -17,41 +17,38 @@ module CukeLinter
17
17
  module ModelFactory
18
18
 
19
19
  def self.generate_feature_model(source_text: 'Feature:', parent_file_path: 'path_to_file')
20
- fake_file_model = CukeModeler::FeatureFile.new
21
- fake_file_model.path = parent_file_path
20
+ fake_parent_model = CukeModeler::FeatureFile.new
21
+ fake_parent_model.path = parent_file_path
22
22
 
23
23
  model = CukeModeler::Feature.new(source_text)
24
- model.parent_model = fake_file_model
24
+ model.parent_model = fake_parent_model
25
25
 
26
26
  model
27
27
  end
28
28
 
29
29
  def self.generate_example_model(source_text: 'Examples:', parent_file_path: 'path_to_file')
30
- fake_file_model = CukeModeler::FeatureFile.new
31
- fake_file_model.path = parent_file_path
30
+ fake_parent_model = generate_outline_model(parent_file_path: parent_file_path)
32
31
 
33
32
  model = CukeModeler::Example.new(source_text)
34
- model.parent_model = fake_file_model
33
+ model.parent_model = fake_parent_model
35
34
 
36
35
  model
37
36
  end
38
37
 
39
38
  def self.generate_outline_model(source_text: "Scenario Outline:\n*a step\nExamples:\n|param|", parent_file_path: 'path_to_file')
40
- fake_file_model = CukeModeler::FeatureFile.new
41
- fake_file_model.path = parent_file_path
39
+ fake_parent_model = generate_feature_model(parent_file_path: parent_file_path)
42
40
 
43
41
  model = CukeModeler::Outline.new(source_text)
44
- model.parent_model = fake_file_model
42
+ model.parent_model = fake_parent_model
45
43
 
46
44
  model
47
45
  end
48
46
 
49
47
  def self.generate_scenario_model(source_text: 'Scenario:', parent_file_path: 'path_to_file')
50
- fake_file_model = CukeModeler::FeatureFile.new
51
- fake_file_model.path = parent_file_path
48
+ fake_parent_model = generate_feature_model(parent_file_path: parent_file_path)
52
49
 
53
50
  model = CukeModeler::Scenario.new(source_text)
54
- model.parent_model = fake_file_model
51
+ model.parent_model = fake_parent_model
55
52
 
56
53
  model
57
54
  end
@@ -82,16 +82,21 @@ RSpec.describe CukeLinter do
82
82
  end
83
83
 
84
84
  it 'uses all registered linters if none are provided', :linter_registration do
85
+ CukeLinter.clear_registered_linters
85
86
  CukeLinter.register_linter(linter: CukeLinter::LinterFactory.generate_fake_linter(name: 'FakeLinter1'), name: 'FakeLinter1')
86
87
  CukeLinter.register_linter(linter: CukeLinter::LinterFactory.generate_fake_linter(name: 'FakeLinter2'), name: 'FakeLinter2')
87
88
  CukeLinter.register_linter(linter: CukeLinter::LinterFactory.generate_fake_linter(name: 'FakeLinter3'), name: 'FakeLinter3')
88
- linting_options.delete(:linters)
89
89
 
90
- results = subject.lint(linting_options)
90
+ begin
91
+ linting_options.delete(:linters)
92
+ results = subject.lint(linting_options)
91
93
 
92
- expect(results).to match_array([{ linter: 'FakeLinter1', location: 'path_to_file:1', problem: 'FakeLinter1 problem' },
93
- { linter: 'FakeLinter2', location: 'path_to_file:1', problem: 'FakeLinter2 problem' },
94
- { linter: 'FakeLinter3', location: 'path_to_file:1', problem: 'FakeLinter3 problem' }])
94
+ expect(results).to match_array([{ linter: 'FakeLinter1', location: 'path_to_file:1', problem: 'FakeLinter1 problem' },
95
+ { linter: 'FakeLinter2', location: 'path_to_file:1', problem: 'FakeLinter2 problem' },
96
+ { linter: 'FakeLinter3', location: 'path_to_file:1', problem: 'FakeLinter3 problem' }])
97
+ ensure
98
+ CukeLinter.reset_linters
99
+ end
95
100
  end
96
101
 
97
102
  it 'includes the name of the linter in the linting data' do
@@ -136,6 +141,12 @@ RSpec.describe CukeLinter do
136
141
  expect(CukeLinter.registered_linters.values.map(&:object_id)).to_not match_array(original_linter_ids)
137
142
  end
138
143
 
144
+ it 'can handle a mixture of problematic and non-problematic models' do
145
+ linting_options[:linters] = [CukeLinter::LinterFactory.generate_fake_linter(finds_problems: true),
146
+ CukeLinter::LinterFactory.generate_fake_linter(finds_problems: false)]
147
+
148
+ expect { subject.lint(linting_options) }.to_not raise_error
149
+ end
139
150
 
140
151
  describe 'configuration' do
141
152
 
@@ -0,0 +1,8 @@
1
+ require_relative '../../../../../environments/rspec_env'
2
+
3
+
4
+ RSpec.describe CukeLinter::Linter do
5
+
6
+ it_should_behave_like 'a linter at the integration level'
7
+
8
+ end
@@ -44,13 +44,13 @@ RSpec.describe CukeLinter::ExampleWithoutNameLinter do
44
44
  end
45
45
 
46
46
  it 'records a problem' do
47
- results = subject.lint(test_model_with_nil_name)
47
+ result = subject.lint(test_model_with_nil_name)
48
48
 
49
- expect(results.first[:problem]).to eq('Example has no name')
49
+ expect(result[:problem]).to eq('Example has no name')
50
50
 
51
- results = subject.lint(test_model_with_blank_name)
51
+ result = subject.lint(test_model_with_blank_name)
52
52
 
53
- expect(results.first[:problem]).to eq('Example has no name')
53
+ expect(result[:problem]).to eq('Example has no name')
54
54
  end
55
55
 
56
56
  it 'records the location of the problem' do
@@ -61,21 +61,21 @@ RSpec.describe CukeLinter::ExampleWithoutNameLinter do
61
61
  model_2.name = nil
62
62
  model_2.source_line = 3
63
63
 
64
- results = subject.lint(model_1)
65
- expect(results.first[:location]).to eq('path_to_file:1')
64
+ result = subject.lint(model_1)
65
+ expect(result[:location]).to eq('path_to_file:1')
66
66
 
67
- results = subject.lint(model_2)
68
- expect(results.first[:location]).to eq('path_to_file:3')
67
+ result = subject.lint(model_2)
68
+ expect(result[:location]).to eq('path_to_file:3')
69
69
  end
70
70
 
71
71
  end
72
72
 
73
73
  context 'a non-example model' do
74
74
 
75
- it 'returns an empty set of results' do
76
- results = subject.lint(CukeModeler::Model.new)
75
+ it 'returns no result' do
76
+ result = subject.lint(CukeModeler::Model.new)
77
77
 
78
- expect(results).to eq([])
78
+ expect(result).to eq(nil)
79
79
  end
80
80
 
81
81
  end
@@ -44,13 +44,13 @@ RSpec.describe CukeLinter::FeatureWithoutScenariosLinter do
44
44
  end
45
45
 
46
46
  it 'records a problem' do
47
- results = subject.lint(test_model_with_empty_scenarios)
47
+ result = subject.lint(test_model_with_empty_scenarios)
48
48
 
49
- expect(results.first[:problem]).to eq('Feature has no scenarios')
49
+ expect(result[:problem]).to eq('Feature has no scenarios')
50
50
 
51
- results = subject.lint(test_model_with_nil_scenarios)
51
+ result = subject.lint(test_model_with_nil_scenarios)
52
52
 
53
- expect(results.first[:problem]).to eq('Feature has no scenarios')
53
+ expect(result[:problem]).to eq('Feature has no scenarios')
54
54
  end
55
55
 
56
56
  it 'records the location of the problem' do
@@ -61,11 +61,11 @@ RSpec.describe CukeLinter::FeatureWithoutScenariosLinter do
61
61
  model_2.tests = []
62
62
  model_2.source_line = 3
63
63
 
64
- results = subject.lint(model_1)
65
- expect(results.first[:location]).to eq('path_to_file:1')
64
+ result = subject.lint(model_1)
65
+ expect(result[:location]).to eq('path_to_file:1')
66
66
 
67
- results = subject.lint(model_2)
68
- expect(results.first[:location]).to eq('path_to_file:3')
67
+ result = subject.lint(model_2)
68
+ expect(result[:location]).to eq('path_to_file:3')
69
69
  end
70
70
 
71
71
  end
@@ -83,7 +83,7 @@ RSpec.describe CukeLinter::FeatureWithoutScenariosLinter do
83
83
  end
84
84
 
85
85
  it 'does not record a problem' do
86
- expect(subject.lint(test_model)).to eq([])
86
+ expect(subject.lint(test_model)).to eq(nil)
87
87
  end
88
88
 
89
89
  end
@@ -102,7 +102,7 @@ RSpec.describe CukeLinter::FeatureWithoutScenariosLinter do
102
102
  end
103
103
 
104
104
  it 'does not record a problem' do
105
- expect(subject.lint(test_model)).to eq([])
105
+ expect(subject.lint(test_model)).to eq(nil)
106
106
  end
107
107
 
108
108
  end
@@ -111,10 +111,10 @@ RSpec.describe CukeLinter::FeatureWithoutScenariosLinter do
111
111
 
112
112
  context 'a non-feature model' do
113
113
 
114
- it 'returns an empty set of results' do
115
- results = subject.lint(CukeModeler::Model.new)
114
+ it 'returns no result' do
115
+ result = subject.lint(CukeModeler::Model.new)
116
116
 
117
- expect(results).to eq([])
117
+ expect(result).to eq(nil)
118
118
  end
119
119
 
120
120
  end
@@ -0,0 +1,180 @@
1
+ require_relative '../../../../../environments/rspec_env'
2
+
3
+
4
+ RSpec.describe CukeLinter::Linter do
5
+
6
+ let(:linter_name) { 'FooLinter' }
7
+ let(:linter_message) { 'Foo!' }
8
+ let(:linter_rule) { lambda { |model| !model.is_a?(CukeModeler::Example) } }
9
+ let(:linter_options) { { name: linter_name, message: linter_message, rule: linter_rule } }
10
+
11
+ let(:good_data) do
12
+ CukeLinter::ModelFactory.generate_example_model
13
+ end
14
+
15
+ let(:bad_data) do
16
+ CukeLinter::ModelFactory.generate_outline_model
17
+ end
18
+
19
+
20
+ context 'with custom values' do
21
+
22
+ subject { CukeLinter::Linter.new(linter_options) }
23
+
24
+
25
+ it_should_behave_like 'a linter at the unit level'
26
+
27
+ it 'uses the provided name' do
28
+ expect(subject.name).to eq(linter_name)
29
+ end
30
+
31
+ it 'uses the provided rule' do
32
+ expect(subject.lint(good_data)).to be_nil
33
+ expect(subject.lint(bad_data)).to_not be_nil
34
+ end
35
+
36
+ it 'uses the provided message' do
37
+ result = subject.lint(bad_data)
38
+
39
+ expect(result[:problem]).to eq(linter_message)
40
+ end
41
+
42
+ end
43
+
44
+ context 'with custom methods' do
45
+
46
+ subject { linter = CukeLinter::Linter.new
47
+
48
+ linter.define_singleton_method('rule') do |model|
49
+ !model.is_a?(CukeModeler::Example)
50
+ end
51
+
52
+ linter.define_singleton_method('name') do
53
+ 'FooLinter'
54
+ end
55
+
56
+ linter.define_singleton_method('message') do
57
+ 'Foo!'
58
+ end
59
+
60
+
61
+ linter }
62
+
63
+
64
+ it_should_behave_like 'a linter at the unit level'
65
+
66
+ it 'uses the provided #name' do
67
+ expect(subject.name).to eq(linter_name)
68
+ end
69
+
70
+ it 'uses the provided #rule' do
71
+ expect(subject.lint(good_data)).to be_nil
72
+ expect(subject.lint(bad_data)).to_not be_nil
73
+ end
74
+
75
+ it 'uses the provided #message' do
76
+ result = subject.lint(bad_data)
77
+
78
+ expect(result[:problem]).to eq(linter_message)
79
+ end
80
+
81
+ end
82
+
83
+ context 'with both custom values and methods' do
84
+
85
+ let(:good_data) do
86
+ CukeLinter::ModelFactory.generate_outline_model
87
+ end
88
+
89
+ let(:bad_data) do
90
+ CukeLinter::ModelFactory.generate_example_model
91
+ end
92
+
93
+ subject { linter = CukeLinter::Linter.new(linter_options)
94
+
95
+ linter.define_singleton_method('rule') do |model|
96
+ model.is_a?(CukeModeler::Example)
97
+ end
98
+
99
+ linter.define_singleton_method('name') do
100
+ 'Method Linter'
101
+ end
102
+
103
+ linter.define_singleton_method('message') do
104
+ 'Method Foo!'
105
+ end
106
+
107
+
108
+ linter }
109
+
110
+
111
+ it 'uses #name instead of the provided name' do
112
+ expect(subject.name).to eq('Method Linter')
113
+ end
114
+
115
+ it 'uses #rule instead of the provided rule' do
116
+ expect(subject.lint(good_data)).to be_nil
117
+ expect(subject.lint(bad_data)).to_not be_nil
118
+ end
119
+
120
+ it 'uses #message instead of the provided message' do
121
+ result = subject.lint(bad_data)
122
+
123
+ expect(result[:problem]).to eq('Method Foo!')
124
+ end
125
+
126
+ end
127
+
128
+ context 'with neither custom values nor methods' do
129
+
130
+ subject { CukeLinter::Linter.new }
131
+
132
+
133
+ it 'complains if not provided with a rule' do
134
+ expect { subject.lint('Anything') }.to raise_error('No linting rule provided!')
135
+ end
136
+
137
+
138
+ it 'has a default name based on its class' do
139
+ expect(subject.name).to eq('Linter')
140
+
141
+ class CustomLinter < CukeLinter::Linter;
142
+ end
143
+
144
+ expect(CustomLinter.new.name).to eq('CustomLinter')
145
+ end
146
+
147
+ it 'has a default message based on its name' do
148
+ linter_options[:message] = nil
149
+
150
+ # Default name
151
+ linter_options[:name] = nil
152
+ linter = CukeLinter::Linter.new(linter_options)
153
+ result = linter.lint(bad_data)
154
+
155
+ expect(result[:problem]).to eq('Linter problem detected')
156
+
157
+ # Value name
158
+ linter_options[:name] = 'Value name'
159
+ linter = CukeLinter::Linter.new(linter_options)
160
+ result = linter.lint(bad_data)
161
+
162
+ expect(result[:problem]).to eq('Value name problem detected')
163
+
164
+ # Method name
165
+ class CustomLinter < CukeLinter::Linter;
166
+ def name
167
+ 'Method name'
168
+ end
169
+ end
170
+
171
+ linter_options[:name] = nil
172
+ linter = CustomLinter.new(linter_options)
173
+ result = linter.lint(bad_data)
174
+
175
+ expect(result[:problem]).to eq('Method name problem detected')
176
+ end
177
+
178
+ end
179
+
180
+ end
@@ -16,22 +16,22 @@ shared_examples_for 'a linter at the unit level' do
16
16
 
17
17
  context 'with good data' do
18
18
 
19
- it 'returns an empty set of results' do
20
- expect(subject.lint(good_data)).to eq([])
19
+ it 'returns no problem' do
20
+ expect(subject.lint(good_data)).to be_nil
21
21
  end
22
22
 
23
23
  end
24
24
 
25
25
  context 'with bad data' do
26
26
 
27
- it 'returns a set of detected problems' do
28
- expect(subject.lint(bad_data)).to_not be_empty
27
+ it 'returns a detected problems' do
28
+ expect(subject.lint(bad_data)).to_not be_nil
29
29
  end
30
30
 
31
- it 'includes the problems and their locations in its results' do
32
- results = subject.lint(bad_data)
31
+ it 'includes the problem and its locations in its result' do
32
+ result = subject.lint(bad_data)
33
33
 
34
- results.each { |result| expect(result.keys).to match_array([:problem, :location]) }
34
+ expect(result.keys).to match_array([:problem, :location])
35
35
  end
36
36
 
37
37
  end
@@ -49,9 +49,9 @@ RSpec.describe CukeLinter::OutlineWithSingleExampleRowLinter do
49
49
  end
50
50
 
51
51
  it 'records a problem' do
52
- results = subject.lint(test_model)
52
+ result = subject.lint(test_model)
53
53
 
54
- expect(results.first[:problem]).to eq('Outline has only one example row')
54
+ expect(result[:problem]).to eq('Outline has only one example row')
55
55
  end
56
56
 
57
57
  end
@@ -71,9 +71,9 @@ RSpec.describe CukeLinter::OutlineWithSingleExampleRowLinter do
71
71
  end
72
72
 
73
73
  it 'records a problem' do
74
- results = subject.lint(test_model)
74
+ result = subject.lint(test_model)
75
75
 
76
- expect(results.first[:problem]).to eq('Outline has only one example row')
76
+ expect(result[:problem]).to eq('Outline has only one example row')
77
77
  end
78
78
 
79
79
  end
@@ -92,11 +92,11 @@ RSpec.describe CukeLinter::OutlineWithSingleExampleRowLinter do
92
92
  model_1.source_line = 1
93
93
  model_2.source_line = 3
94
94
 
95
- results = subject.lint(model_1)
96
- expect(results.first[:location]).to eq('path_to_file:1')
95
+ result = subject.lint(model_1)
96
+ expect(result[:location]).to eq('path_to_file:1')
97
97
 
98
- results = subject.lint(model_2)
99
- expect(results.first[:location]).to eq('path_to_file:3')
98
+ result = subject.lint(model_2)
99
+ expect(result[:location]).to eq('path_to_file:3')
100
100
  end
101
101
 
102
102
  end
@@ -117,7 +117,7 @@ RSpec.describe CukeLinter::OutlineWithSingleExampleRowLinter do
117
117
  end
118
118
 
119
119
  it 'does not record a problem' do
120
- expect(subject.lint(test_model)).to eq([])
120
+ expect(subject.lint(test_model)).to eq(nil)
121
121
  end
122
122
 
123
123
  end
@@ -138,7 +138,7 @@ RSpec.describe CukeLinter::OutlineWithSingleExampleRowLinter do
138
138
  end
139
139
 
140
140
  it 'does not record a problem' do
141
- expect(subject.lint(test_model)).to eq([])
141
+ expect(subject.lint(test_model)).to eq(nil)
142
142
  end
143
143
 
144
144
  end
@@ -159,7 +159,7 @@ RSpec.describe CukeLinter::OutlineWithSingleExampleRowLinter do
159
159
  end
160
160
 
161
161
  it 'does not record a problem' do
162
- expect(subject.lint(test_model)).to eq([])
162
+ expect(subject.lint(test_model)).to eq(nil)
163
163
  end
164
164
 
165
165
  end
@@ -173,7 +173,7 @@ RSpec.describe CukeLinter::OutlineWithSingleExampleRowLinter do
173
173
  end
174
174
 
175
175
  it 'does not record a problem' do
176
- expect(subject.lint(test_model)).to eq([])
176
+ expect(subject.lint(test_model)).to eq(nil)
177
177
  end
178
178
 
179
179
  end
@@ -192,7 +192,7 @@ RSpec.describe CukeLinter::OutlineWithSingleExampleRowLinter do
192
192
  end
193
193
 
194
194
  it 'does not record a problem' do
195
- expect(subject.lint(test_model)).to eq([])
195
+ expect(subject.lint(test_model)).to eq(nil)
196
196
  end
197
197
 
198
198
  end
@@ -211,7 +211,7 @@ RSpec.describe CukeLinter::OutlineWithSingleExampleRowLinter do
211
211
  end
212
212
 
213
213
  it 'does not record a problem' do
214
- expect(subject.lint(test_model)).to eq([])
214
+ expect(subject.lint(test_model)).to eq(nil)
215
215
  end
216
216
 
217
217
  end
@@ -220,10 +220,10 @@ RSpec.describe CukeLinter::OutlineWithSingleExampleRowLinter do
220
220
 
221
221
  context 'a non-outline model' do
222
222
 
223
- it 'returns an empty set of results' do
224
- results = subject.lint(CukeModeler::Model.new)
223
+ it 'returns no result' do
224
+ result = subject.lint(CukeModeler::Model.new)
225
225
 
226
- expect(results).to eq([])
226
+ expect(result).to eq(nil)
227
227
  end
228
228
 
229
229
  end
@@ -60,29 +60,29 @@ RSpec.describe CukeLinter::TestWithTooManyStepsLinter do
60
60
  end
61
61
 
62
62
  it 'records a problem' do
63
- results = subject.lint(test_model)
63
+ result = subject.lint(test_model)
64
64
 
65
- expect(results.first[:problem]).to match(/^Test has too many steps. \d+ steps found \(max 10\)/)
65
+ expect(result[:problem]).to match(/^Test has too many steps. \d+ steps found \(max 10\)/)
66
66
  end
67
67
 
68
68
  it 'records the location of the problem' do
69
69
  test_model.source_line = 1
70
- results = subject.lint(test_model)
71
- expect(results.first[:location]).to eq('path_to_file:1')
70
+ result = subject.lint(test_model)
71
+ expect(result[:location]).to eq('path_to_file:1')
72
72
 
73
73
  test_model.source_line = 3
74
- results = subject.lint(test_model)
75
- expect(results.first[:location]).to eq('path_to_file:3')
74
+ result = subject.lint(test_model)
75
+ expect(result[:location]).to eq('path_to_file:3')
76
76
  end
77
77
 
78
78
  it 'includes the number of steps found in the problem record' do
79
79
  step_count = test_model.steps.count
80
- results = subject.lint(test_model)
81
- expect(results.first[:problem]).to eq("Test has too many steps. #{step_count} steps found (max 10)")
80
+ result = subject.lint(test_model)
81
+ expect(result[:problem]).to eq("Test has too many steps. #{step_count} steps found (max 10)")
82
82
 
83
83
  test_model.steps << :another_step
84
- results = subject.lint(test_model)
85
- expect(results.first[:problem]).to eq("Test has too many steps. #{step_count + 1} steps found (max 10)")
84
+ result = subject.lint(test_model)
85
+ expect(result[:problem]).to eq("Test has too many steps. #{step_count + 1} steps found (max 10)")
86
86
  end
87
87
 
88
88
  end
@@ -99,7 +99,7 @@ RSpec.describe CukeLinter::TestWithTooManyStepsLinter do
99
99
  end
100
100
 
101
101
  it 'does not record a problem' do
102
- expect(subject.lint(test_model)).to eq([])
102
+ expect(subject.lint(test_model)).to eq(nil)
103
103
  end
104
104
 
105
105
  end
@@ -116,7 +116,7 @@ RSpec.describe CukeLinter::TestWithTooManyStepsLinter do
116
116
  end
117
117
 
118
118
  it 'does not record a problem' do
119
- expect(subject.lint(test_model)).to eq([])
119
+ expect(subject.lint(test_model)).to eq(nil)
120
120
  end
121
121
 
122
122
  end
@@ -131,7 +131,7 @@ RSpec.describe CukeLinter::TestWithTooManyStepsLinter do
131
131
  end
132
132
 
133
133
  it 'does not record a problem' do
134
- expect(subject.lint(test_model)).to eq([])
134
+ expect(subject.lint(test_model)).to eq(nil)
135
135
  end
136
136
 
137
137
  end
@@ -158,9 +158,9 @@ RSpec.describe CukeLinter::TestWithTooManyStepsLinter do
158
158
  end
159
159
 
160
160
  it 'defaults to a step threshold of 10 steps' do
161
- results = subject.lint(unconfigured_test_model)
161
+ result = subject.lint(unconfigured_test_model)
162
162
 
163
- expect(results.first[:problem]).to match(/^Test has too many steps. #{unconfigured_test_model.steps.count} steps found \(max 10\)/)
163
+ expect(result[:problem]).to match(/^Test has too many steps. #{unconfigured_test_model.steps.count} steps found \(max 10\)/)
164
164
  end
165
165
 
166
166
  end
@@ -178,9 +178,9 @@ RSpec.describe CukeLinter::TestWithTooManyStepsLinter do
178
178
  end
179
179
 
180
180
  it 'defaults to a step threshold of 10 steps' do
181
- results = subject.lint(configured_test_model)
181
+ result = subject.lint(configured_test_model)
182
182
 
183
- expect(results.first[:problem]).to match(/^Test has too many steps. #{configured_test_model.steps.count} steps found \(max 10\)/)
183
+ expect(result[:problem]).to match(/^Test has too many steps. #{configured_test_model.steps.count} steps found \(max 10\)/)
184
184
  end
185
185
 
186
186
  end
@@ -203,9 +203,9 @@ RSpec.describe CukeLinter::TestWithTooManyStepsLinter do
203
203
  end
204
204
 
205
205
  it 'the step threshold used is the configured value' do
206
- results = subject.lint(configured_test_model)
206
+ result = subject.lint(configured_test_model)
207
207
 
208
- expect(results.first[:problem]).to match(/^Test has too many steps. #{configured_test_model.steps.count} steps found \(max #{step_threshhold}\)/)
208
+ expect(result[:problem]).to match(/^Test has too many steps. #{configured_test_model.steps.count} steps found \(max #{step_threshhold}\)/)
209
209
  end
210
210
 
211
211
  end
@@ -218,10 +218,10 @@ RSpec.describe CukeLinter::TestWithTooManyStepsLinter do
218
218
 
219
219
  let(:test_model) { CukeModeler::Model.new }
220
220
 
221
- it 'returns an empty set of results' do
222
- results = subject.lint(test_model)
221
+ it 'returns no result' do
222
+ result = subject.lint(test_model)
223
223
 
224
- expect(results).to eq([])
224
+ expect(result).to eq(nil)
225
225
  end
226
226
 
227
227
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cuke_linter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Kessler
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-13 00:00:00.000000000 Z
11
+ date: 2019-05-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cuke_modeler
@@ -178,6 +178,7 @@ files:
178
178
  - lib/cuke_linter/formatters/pretty_formatter.rb
179
179
  - lib/cuke_linter/linters/example_without_name_linter.rb
180
180
  - lib/cuke_linter/linters/feature_without_scenarios_linter.rb
181
+ - lib/cuke_linter/linters/linter.rb
181
182
  - lib/cuke_linter/linters/outline_with_single_example_row_linter.rb
182
183
  - lib/cuke_linter/linters/test_with_too_many_steps_linter.rb
183
184
  - lib/cuke_linter/version.rb
@@ -185,6 +186,7 @@ files:
185
186
  - testing/cucumber/features/configuration/configuring_linters.feature
186
187
  - testing/cucumber/features/configuration/using_configurations.feature
187
188
  - testing/cucumber/features/formatters/pretty_formatter.feature
189
+ - testing/cucumber/features/linters/custom_linters.feature
188
190
  - testing/cucumber/features/linters/default_linters.feature
189
191
  - testing/cucumber/features/linters/example_without_name.feature
190
192
  - testing/cucumber/features/linters/feature_without_scenarios.feature
@@ -202,6 +204,7 @@ files:
202
204
  - testing/rspec/spec/integration/formatters/pretty_formatter_integration_spec.rb
203
205
  - testing/rspec/spec/integration/linters/example_without_name_linter_integration_spec.rb
204
206
  - testing/rspec/spec/integration/linters/feature_without_scenarios_linter_integration_spec.rb
207
+ - testing/rspec/spec/integration/linters/linter_integration_spec.rb
205
208
  - testing/rspec/spec/integration/linters/linter_integration_specs.rb
206
209
  - testing/rspec/spec/integration/linters/outline_with_single_example_row_linter_integration_spec.rb
207
210
  - testing/rspec/spec/integration/linters/test_with_too_many_steps_linter_integration_spec.rb
@@ -211,6 +214,7 @@ files:
211
214
  - testing/rspec/spec/unit/linters/configurable_linter_unit_specs.rb
212
215
  - testing/rspec/spec/unit/linters/example_without_name_linter_unit_spec.rb
213
216
  - testing/rspec/spec/unit/linters/feature_without_scenarios_linter_unit_spec.rb
217
+ - testing/rspec/spec/unit/linters/linter_unit_spec.rb
214
218
  - testing/rspec/spec/unit/linters/linter_unit_specs.rb
215
219
  - testing/rspec/spec/unit/linters/outline_with_single_example_row_linter_unit_spec.rb
216
220
  - testing/rspec/spec/unit/linters/test_with_too_many_steps_linter_unit_spec.rb