cuke_linter 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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