chutney 3.7.0 → 3.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +1 -2
  3. data/.rubocop.yml +4 -3
  4. data/Gemfile +9 -0
  5. data/README.md +49 -3
  6. data/chutney.gemspec +8 -17
  7. data/examples/emoji.feature +1 -2
  8. data/exe/chutney +2 -2
  9. data/exe/chutney-lsp +23 -0
  10. data/img/happy_chutney.png +0 -0
  11. data/lib/chutney/configuration.rb +4 -2
  12. data/lib/chutney/formatter/pie_formatter.rb +2 -2
  13. data/lib/chutney/linter/inconsistent_quoting.rb +1 -1
  14. data/lib/chutney/linter/invalid_file_name.rb +1 -1
  15. data/lib/chutney/linter/same_tag_different_case.rb +1 -1
  16. data/lib/chutney/linter/same_tag_for_all_scenarios.rb +2 -2
  17. data/lib/chutney/linter/unique_scenario_names.rb +1 -1
  18. data/lib/chutney/linter/unused_variable.rb +1 -1
  19. data/lib/chutney/linter/use_outline.rb +1 -1
  20. data/lib/chutney/linter.rb +1 -1
  21. data/lib/chutney/lsp/result.rb +16 -0
  22. data/lib/chutney/lsp/server.rb +177 -0
  23. data/lib/chutney/lsp.rb +7 -0
  24. data/lib/chutney/version.rb +1 -1
  25. data/lib/chutney.rb +0 -1
  26. data/usechutney.com/.gitignore +5 -0
  27. data/usechutney.com/Gemfile +37 -0
  28. data/usechutney.com/_config.yml +298 -0
  29. data/usechutney.com/_data/navigation.yml +112 -0
  30. data/usechutney.com/_data/ui-text.yml +2132 -0
  31. data/usechutney.com/_posts/2024-09-23-welcome-to-jekyll.markdown +28 -0
  32. data/usechutney.com/assets/images/mr_pickle.png +0 -0
  33. data/usechutney.com/assets/images/pug.png +0 -0
  34. data/usechutney.com/docs/configuration/index.md +104 -0
  35. data/usechutney.com/docs/disabling-rules/index.md +11 -0
  36. data/usechutney.com/docs/installing/index.md +30 -0
  37. data/usechutney.com/docs/language-server/index.md +24 -0
  38. data/usechutney.com/docs/rules/avoid-full-stops/index.md +23 -0
  39. data/usechutney.com/docs/rules/avoid-outline-for-single-example/index.md +28 -0
  40. data/usechutney.com/docs/rules/avoid-scripting/index.md +33 -0
  41. data/usechutney.com/docs/rules/avoid-splat-steps-in-background/index.md +25 -0
  42. data/usechutney.com/docs/rules/avoid-splat-steps-in-scenarios/index.md +29 -0
  43. data/usechutney.com/docs/rules/avoid-typographers-quotes/index.md +24 -0
  44. data/usechutney.com/docs/rules/background-does-more-than-setup/index.md +28 -0
  45. data/usechutney.com/docs/rules/background-requires-multiple-scenarios/index.md +26 -0
  46. data/usechutney.com/docs/rules/bad-scenario-name/index.md +28 -0
  47. data/usechutney.com/docs/rules/empty-feature-file/index.md +7 -0
  48. data/usechutney.com/docs/rules/file-name-differs-feature-name/index.md +37 -0
  49. data/usechutney.com/docs/rules/givens-after-background/index.md +36 -0
  50. data/usechutney.com/docs/rules/inconsistent-quoting/index.md +29 -0
  51. data/usechutney.com/docs/rules/invalid-step-flow/index.md +31 -0
  52. data/usechutney.com/docs/rules/invalid_file_name/index.md +19 -0
  53. data/usechutney.com/docs/rules/missing-example-name/index.md +49 -0
  54. data/usechutney.com/docs/rules/missing-example-table/index.md +34 -0
  55. data/usechutney.com/docs/rules/missing-feature-description/index.md +25 -0
  56. data/usechutney.com/docs/rules/missing-feature-name/index.md +19 -0
  57. data/usechutney.com/docs/rules/missing-scenario-name/index.md +19 -0
  58. data/usechutney.com/docs/rules/missing-scenario-outline/index.md +39 -0
  59. data/usechutney.com/docs/rules/missing-test-action/index.md +28 -0
  60. data/usechutney.com/docs/rules/missing-test-verification/index.md +28 -0
  61. data/usechutney.com/docs/rules/required-tag-starts-with/index.md +31 -0
  62. data/usechutney.com/docs/rules/same-tag-different-case/index.md +42 -0
  63. data/usechutney.com/docs/rules/same-tag-for-all-scenarios/index.md +42 -0
  64. data/usechutney.com/docs/rules/scenario-names-match/index.md +30 -0
  65. data/usechutney.com/docs/rules/tag-used-multiple-times/index.md +32 -0
  66. data/usechutney.com/docs/rules/too-clumsy/index.md +78 -0
  67. data/usechutney.com/docs/rules/too-long-step/index.md +30 -0
  68. data/usechutney.com/docs/rules/too-many-different-tags/index.md +32 -0
  69. data/usechutney.com/docs/rules/too-many-steps/index.md +79 -0
  70. data/usechutney.com/docs/rules/too-many-tags/index.md +32 -0
  71. data/usechutney.com/docs/rules/unique-scenario-names/index.md +39 -0
  72. data/usechutney.com/docs/rules/unknown-variable/index.md +38 -0
  73. data/usechutney.com/docs/rules/unused-variable/index.md +37 -0
  74. data/usechutney.com/docs/rules/use-background/index.md +40 -0
  75. data/usechutney.com/docs/rules/use-outline/index.md +39 -0
  76. data/usechutney.com/docs/running/index.md +33 -0
  77. data/usechutney.com/pages/404.html +25 -0
  78. data/usechutney.com/pages/about/index.md +22 -0
  79. data/usechutney.com/pages/index.markdown +40 -0
  80. metadata +76 -114
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07c1a6a4a0afe29926cdb6c12fd189f6d30fe10e2a5d728478144ff3707ddee2
4
- data.tar.gz: b6e39a2d58ed3a4f8a67c04e3f55522d068192b7d4d8975f9f03ad8d8385a1da
3
+ metadata.gz: 06e7bafb2c4fb8f14e168e16a2f83a5aaf958f6a4d3430678f9f0c9da954835e
4
+ data.tar.gz: 07c8004a2cbd18de1ed90ab634df369c9548b1b7e952f8d111890924ccb77b67
5
5
  SHA512:
6
- metadata.gz: 04d16c6f3be6ce96f57df7db2c6a2e7c3bc1cd3946f676400df989c0fb6afb07b02519248b2b22c873705d362b05af2cd214f849e234252116e215427ac067be
7
- data.tar.gz: c00827961589bd8ca1b47a4c7b0c229ba5fa7505830fc061891c461ceb28c4af9cfb98b660bbef47d30eac725c86b9ebc61f1eb7bf7ed6f7d4d7a024c478df84
6
+ metadata.gz: 1904c405fb2f33997023ee7628ad5371c9f5b621850c193dd09c7d057b5d5e9f7a8dabdad98ef3ee24b40cb71348d5fa8f51ee2e7ef8154e069319237f2bd430
7
+ data.tar.gz: 9add005095ab65e1ec8d858379ac231d8457b0ff99f7b55f5518772e8255dccfa0145d02325a9f3b52e5641caf7052cf48c722b591dc14798cba1eed44231ba2
data/.circleci/config.yml CHANGED
@@ -1,11 +1,10 @@
1
1
  # Use the latest 2.1 version of CircleCI pipeline processing engine, see https://circleci.com/docs/2.0/configuration-reference/
2
2
  version: 2.1
3
3
 
4
-
5
4
  jobs:
6
5
  build:
7
6
  docker:
8
- - image: circleci/ruby:2.6.3-stretch-node
7
+ - image: cimg/ruby:3.2
9
8
  steps:
10
9
  - checkout
11
10
  - run: bundle install
data/.rubocop.yml CHANGED
@@ -27,14 +27,13 @@ Layout/LineLength:
27
27
  Metrics/MethodLength:
28
28
  Max: 20
29
29
 
30
-
31
30
  # Offense count: 1
32
31
  # Cop supports --auto-correct.
33
32
  # Configuration parameters: EnforcedStyle, SupportedStyles.
34
33
  # SupportedStyles: predicate, comparison
35
34
  Style/NumericPredicate:
36
35
  Exclude:
37
- - 'lib/chutney/linter/file_name_differs_feature_name.rb'
36
+ - "lib/chutney/linter/file_name_differs_feature_name.rb"
38
37
 
39
38
  Metrics/ModuleLength:
40
39
  Exclude:
@@ -63,4 +62,6 @@ Style/StringConcatenation:
63
62
 
64
63
  AllCops:
65
64
  NewCops: enable
66
- SuggestExtensions: false
65
+ SuggestExtensions: false
66
+ Exclude:
67
+ - "usechutney.com/**"
data/Gemfile CHANGED
@@ -3,3 +3,12 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
+
7
+ gem 'coveralls', '~> 0.8'
8
+ gem 'cucumber', '>= 9.0'
9
+ gem 'pry-byebug', '~> 3.0'
10
+ gem 'rake', '~> 13.0'
11
+ gem 'rerun', '~> 0.13'
12
+ gem 'rspec', '~> 3.13'
13
+ gem 'rspec-expectations', '~> 3.0'
14
+ gem 'rubocop', '~> 1.66'
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <h1 align="center">
2
- <img src="https://raw.githubusercontent.com/BillyRuffian/chutney/master/img/chutney.svg?sanitize=true" alt="Chutney" height="200">
2
+ <img src="https://raw.githubusercontent.com/BillyRuffian/chutney/master/img/happy_chutney.png?sanitize=true" alt="Chutney" height="200">
3
3
  <br>
4
4
  Chutney
5
5
  <br>
@@ -19,10 +19,56 @@
19
19
 
20
20
  </div>
21
21
 
22
- Read the documentation [here](https://billyruffian.github.io/chutney/).
22
+ Read the documentation [here](https://www.usechutney.com/).
23
+
24
+ Your documentation is precious and should be treated as such. Chutney is a tool to help you keep your gherkin files in good shape. It will help you to write better gherkin and keep your feature files consistent through an opinionated, but optional, set of rules.
25
+
26
+ ## Installation
27
+
28
+ ### Ruby
29
+
30
+ Chutney is a ruby gem, so relies on you having ruby installed. It requires ruby 3.2 or later.
31
+
32
+ For macOS, Linux or other Unix-like systems, I recommend using a version manager like [rvm](https://rvm.io), [asdf](https://asdf-vm.com) or [rbenv](https://github.com/rbenv/rbenv).
33
+
34
+ For Windows, you can use [RubyInstaller](https://rubyinstaller.org/).
35
+
36
+ ### Chutney
37
+
38
+ To install chutney system-wide, run:
39
+
40
+ ```bash
41
+ gem install chutney
42
+ ```
43
+
44
+ To install chutney for a specific project, add it to your Gemfile:
45
+
46
+ ```ruby
47
+ gem 'chutney'
48
+ ```
49
+
50
+ ## Usage
51
+
52
+ To run chutney, simply run:
53
+
54
+ ```bash
55
+ chutney
56
+ ```
57
+
58
+ It will search for any `.feature` files beneath the current directory and give you an opinion. It comes with a default set of rules and will give you a little nudge if you haven't got your own chutney configuration file.
59
+
60
+ To create a configuration file, run:
61
+
62
+ ```bash
63
+ chutney --init
64
+ ```
65
+
66
+ (Configuration files can in either `.chutney.yml` or `.chutney.yml` and reside in the top-level of the project or in a `/config` directory.)
67
+
23
68
 
24
69
  See [this page](https://billyruffian.github.io/chutney/usage/rules.html) for a full list of the rules chutney encourages.
25
70
 
71
+
26
72
  ## Notes
27
73
 
28
- Chutney 3+ has replaced its direct dependency on Cucumber and instead uses the excellent [cuke_modeller](https://github.com/enkessler/cuke_modeler) to parse your feature files.
74
+ Chutney 3+ has replaced its direct dependency on Cucumber and instead uses the excellent [cuke_modeller](https://github.com/enkessler/cuke_modeler) to parse your feature files.
data/chutney.gemspec CHANGED
@@ -43,26 +43,17 @@ Gem::Specification.new do |spec|
43
43
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
44
44
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|s|features)/}) }
45
45
  end
46
-
46
+
47
47
  spec.bindir = 'exe'
48
48
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
49
49
  spec.require_paths = ['lib']
50
-
51
- spec.add_runtime_dependency 'amatch', '~> 0.4.0'
52
- spec.add_runtime_dependency 'cuke_modeler', '~> 3.3'
53
- spec.add_runtime_dependency 'i18n', '>= 1.8.2', '< 1.15.0'
54
- spec.add_runtime_dependency 'pastel', '~> 0.7'
55
- spec.add_runtime_dependency 'tty-pie', '~> 0.3'
56
50
 
51
+ spec.add_dependency 'amatch', '~> 0.4.0'
52
+ spec.add_dependency 'cuke_modeler', '~> 3.21'
53
+ spec.add_dependency 'i18n', '>= 1.8.2', '< 1.15.0'
54
+ spec.add_dependency 'language_server-protocol', '~> 3.17'
55
+ spec.add_dependency 'pastel', '~> 0.7'
56
+ spec.add_dependency 'tty-pie', '~> 0.3'
57
57
 
58
- spec.add_development_dependency 'coveralls', '~> 0.8'
59
- spec.add_development_dependency 'cucumber', '>= 7.0'
60
- spec.add_development_dependency 'pry-byebug', '~> 3.0'
61
- spec.add_development_dependency 'rake', '~> 13.0'
62
- spec.add_development_dependency 'rerun', '~> 0.13'
63
- spec.add_development_dependency 'rspec-expectations', '~> 3.0'
64
- spec.add_development_dependency 'rubocop', '~> 1.50.2'
65
- spec.add_development_dependency 'rspec', '~> 3.8'
66
-
67
- spec.required_ruby_version = '>= 2.6'
58
+ spec.required_ruby_version = '>= 3.2'
68
59
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  📚: 🤯
4
4
 
5
- As a 👹
5
+ As a 👹
6
6
  I want to 🏃‍♂️ 🥒
7
7
  So that 👁 don't 👀 a 🍆
8
8
 
@@ -11,4 +11,3 @@
11
11
  🎬 I am 😘 by a 👸
12
12
  🎬 I am 😘 by a 👸
13
13
  🙏 I will ⏎ into a 🤴
14
-
data/exe/chutney CHANGED
@@ -24,7 +24,7 @@ OptionParser.new do |opts|
24
24
 
25
25
  opts.on('-q',
26
26
  '--quiet',
27
- 'Disable chutney usage warnings. Does not affect the output of the formatters.') do
27
+ 'Disable chutney usage warnings. Does not affect the output of the formatters.') do
28
28
  quiet = true
29
29
  end
30
30
 
@@ -90,7 +90,7 @@ basic_formatter = Chutney::Formatter.new
90
90
  basic_formatter.results = report
91
91
 
92
92
  if basic_formatter.files_with_issues.empty?
93
- exit(true)
93
+ exit
94
94
  else
95
95
  exit(false)
96
96
  end
data/exe/chutney-lsp ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'language_server-protocol'
5
+ require 'logger'
6
+ require 'optparse'
7
+ require 'uri'
8
+
9
+ require 'chutney'
10
+ require 'chutney/lsp'
11
+ require 'chutney/lsp/result'
12
+ require 'chutney/lsp/server'
13
+
14
+ OptionParser.new do |opts|
15
+ opts.banner = 'Usage: chutney-lsp'
16
+
17
+ opts.on('-v', '--version', 'Display the version.') do
18
+ puts Chutney::VERSION
19
+ exit
20
+ end
21
+ end.parse!
22
+
23
+ Chutney::LSP::Server.new.start
Binary file
@@ -20,8 +20,10 @@ module Chutney
20
20
 
21
21
  def load_user_configuration
22
22
  config_files = ['chutney.yml', '.chutney.yml'].map do |fname|
23
- Dir.glob(File.join(Dir.pwd, '**', fname))
24
- end.flatten
23
+ ['.', 'config'].map do |dir|
24
+ Dir["#{dir}#{File::SEPARATOR}#{fname}"]
25
+ end
26
+ end.flatten.compact
25
27
 
26
28
  self.user_configuration_path = config_files.first
27
29
  return unless !user_configuration_path.nil? && File.exist?(user_configuration_path)
@@ -21,13 +21,13 @@ module Chutney
21
21
  def print_report(data)
22
22
  return if data.empty?
23
23
 
24
- print TTY::Pie.new(data: data, radius: 8, legend: { format: '%<label>s %<name>s %<value>i' })
24
+ print TTY::Pie.new(data:, radius: 8, legend: { format: '%<label>s %<name>s %<value>i' })
25
25
  puts
26
26
  end
27
27
 
28
28
  def top_offences
29
29
  offence = Hash.new(0)
30
- files_with_issues.each do |_file, linter|
30
+ files_with_issues.each_value do |linter|
31
31
  linter.each do |lint|
32
32
  offence[lint[:linter]] += lint[:issues].count
33
33
  end
@@ -7,7 +7,7 @@ module Chutney
7
7
  # matching group 1: opening quote; 2: quoted text; 3: closing quote
8
8
  # opening and closing quote must match (via backrefs)
9
9
  # apostrophes, both singular and plural posessives, are accounted for
10
- QUOTED_STRING = /(?!\b\b)(['"])(.*(?:\b'\b[^\1]*)*(?!\b[\1]\b))(\1)/.freeze
10
+ QUOTED_STRING = /(?!\b\b)(['"])(.*(?:\b'\b[^\1]*)*(?!\b[\1]\b))(\1)/
11
11
  Parameter = Struct.new('Parameter', :quotation_mark, :name)
12
12
 
13
13
  def lint
@@ -15,7 +15,7 @@ module Chutney
15
15
  end
16
16
 
17
17
  def recommend(filename)
18
- File.basename(filename, '.*').gsub(/::/, '/')
18
+ File.basename(filename, '.*').gsub('::', '/')
19
19
  .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
20
20
  .gsub(/([a-z\d])([A-Z])/, '\1_\2')
21
21
  .tr('-', '_')
@@ -19,7 +19,7 @@ module Chutney
19
19
  collision_with = case_collision(tag)
20
20
  if collision_with
21
21
  add_issue(I18n.t('linters.same_tag_different_case',
22
- existing_tag: collision_with, tag: tag),
22
+ existing_tag: collision_with, tag:),
23
23
  feature, scenario)
24
24
  else
25
25
  @@all_known_tags << tag
@@ -20,7 +20,7 @@ module Chutney
20
20
 
21
21
  add_issue(
22
22
  I18n.t('linters.same_tag_for_all_scenarios.feature_level',
23
- tag: tag),
23
+ tag:),
24
24
  feature
25
25
  )
26
26
  end
@@ -37,7 +37,7 @@ module Chutney
37
37
  next if tag == 'skip'
38
38
 
39
39
  add_issue(I18n.t('linters.same_tag_for_all_scenarios.example_level',
40
- tag: tag), feature, scenario)
40
+ tag:), feature, scenario)
41
41
  end
42
42
  end
43
43
  end
@@ -18,7 +18,7 @@ module Chutney
18
18
  def issue(name, first_location, scenario)
19
19
  add_issue(
20
20
  I18n.t('linters.unique_scenario_names',
21
- name: name,
21
+ name:,
22
22
  line: first_location[:line],
23
23
  column: first_location[:column]),
24
24
  feature, scenario
@@ -11,7 +11,7 @@ module Chutney
11
11
  example.rows.first.cells.map(&:value).each do |variable|
12
12
  next if used?(variable, scenario)
13
13
 
14
- add_issue(I18n.t('linters.unused_variable', variable: variable), feature, scenario, example)
14
+ add_issue(I18n.t('linters.unused_variable', variable:), feature, scenario, example)
15
15
  end
16
16
  end
17
17
  end
@@ -23,7 +23,7 @@ module Chutney
23
23
 
24
24
  def add_issue(lhs, rhs, pct)
25
25
  super(I18n.t('linters.use_outline',
26
- pct: pct,
26
+ pct:,
27
27
  lhs_name: lhs[:name],
28
28
  lhs_line: lhs[:reference][:line],
29
29
  rhs_name: rhs[:name],
@@ -82,7 +82,7 @@ module Chutney
82
82
 
83
83
  def add_issue(message, feature = nil, scenario = nil, item = nil)
84
84
  issues << Lint.new(
85
- message: message,
85
+ message:,
86
86
  gherkin_type: type(feature, scenario, item),
87
87
  location: location(feature, scenario, item),
88
88
  feature: feature&.name,
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chutney
4
+ module LSP
5
+ # A wrapper that holds a response to the query message
6
+ # from the LSP client
7
+ class Result
8
+ attr_reader :id, :response
9
+
10
+ def initialize(response:, id: nil)
11
+ @id = id
12
+ @response = response
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chutney
4
+ module LSP
5
+ # A minimalistic language server which will lint gherkin
6
+ # files on open and save
7
+ class Server
8
+ LSP_CONST = LanguageServer::Protocol::Constant
9
+ LSP_IO = LanguageServer::Protocol::Transport::Stdio
10
+ LSP_IF = LanguageServer::Protocol::Interface
11
+
12
+ def initialize
13
+ @writer = LSP_IO::Writer.new
14
+ @reader = LSP_IO::Reader.new
15
+ @mutex = Mutex.new
16
+ @incoming_queue = Thread::Queue.new
17
+ @outgoing_queue = Thread::Queue.new
18
+
19
+ @dispatcher = Thread.new do
20
+ while (message = @outgoing_queue.pop)
21
+ if message.is_a? Result
22
+ @mutex.synchronize { @writer.write(id: message.id, result: message.response) }
23
+ else
24
+ @mutex.synchronize { @writer.write(message.to_hash) }
25
+ end
26
+ end
27
+ end
28
+
29
+ @worker = Thread.new do
30
+ while (message = @incoming_queue.pop)
31
+ process_message(message)
32
+ end
33
+ end
34
+
35
+ Thread.main.priority = 1
36
+ end
37
+
38
+ def process_message(message)
39
+ case message[:method]
40
+ when 'initialize'
41
+ run_initialize(message)
42
+ when 'initialized'
43
+ run_initialized
44
+ when 'textDocument/didOpen', 'textDocument/didSave'
45
+ run_did_change(message)
46
+ when 'textDocument/didClose'
47
+ # no-op
48
+ end
49
+ end
50
+
51
+ def send_message(message)
52
+ return if outgoing_queue.closed?
53
+
54
+ outgoing_queue << message
55
+ end
56
+
57
+ def send_log(message, method: 'window/logMessage', error: false)
58
+ type = error ? LSP_CONST::MessageType::ERROR : LSP_CONST::MessageType::INFO
59
+ notification = LSP_IF::NotificationMessage.new(
60
+ method:,
61
+ jsonrpc: '2.0',
62
+ params: LSP_IF::ShowMessageParams.new(
63
+ type:,
64
+ message: "Chutney LSP [#{VERSION}]: #{message}"
65
+ )
66
+ )
67
+ send_message(notification)
68
+ end
69
+
70
+ def send_notification(message, error: false)
71
+ send_log(message, method: 'window/showMessage', error:)
72
+ end
73
+
74
+ def run_initialize(message)
75
+ initialize_result = LSP_IF::InitializeResult.new(
76
+ capabilities: LSP_IF::ServerCapabilities.new(
77
+ document_formatting_provider: true,
78
+ text_document_sync: LSP_IF::TextDocumentSyncOptions.new(
79
+ change: LSP_CONST::TextDocumentSyncKind::FULL,
80
+ open_close: true,
81
+ save: true
82
+ )
83
+ ),
84
+ server_info: {
85
+ name: 'chutney-lsp',
86
+ version: VERSION
87
+ }
88
+ )
89
+ send_message(Result.new(id: message[:id], response: initialize_result))
90
+ send_log('Initializing')
91
+ end
92
+
93
+ def run_initialized
94
+ RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
95
+ send_notification('Chutney LSP Server up and running')
96
+ send_log('Initialized')
97
+ end
98
+
99
+ def run_did_change(message)
100
+ document = message.dig(:params, :textDocument)
101
+ filename = document[:uri].delete_prefix('file://')
102
+ send_log("Evaluating #{filename}")
103
+ linter = Chutney::ChutneyLint.new(*filename)
104
+ linter.configuration.quiet!
105
+ begin
106
+ offenses = linter.analyse.values.first.filter { |r| r[:issues].any? }
107
+ rescue StandardError => e
108
+ send_log("Could not parse #{filename} as Gherkin. Received: #{e.full_message}", error: true)
109
+ send_notification("Could not parse #{filename} as Gherkin.", error: true)
110
+ return
111
+ end
112
+ send_log("Found #{offenses.count} offenses")
113
+ diagnostics = offenses
114
+ .flat_map { |group| group[:issues].each { |issue| issue[:linter] = group[:linter] } }
115
+ .map { |offense| to_diagnostic(offense) }
116
+ send_message(diagnostic_message(document[:uri], diagnostics))
117
+ end
118
+
119
+ def diagnostic_message(file_uri, diagnostics)
120
+ {
121
+ method: 'textDocument/publishDiagnostics',
122
+ params: {
123
+ uri: file_uri,
124
+ diagnostics:
125
+ }
126
+ }
127
+ end
128
+
129
+ def to_diagnostic(offense)
130
+ code = offense[:linter]
131
+ message = offense[:message]
132
+ source = 'chutney'
133
+ { code:, message:, source:, severity: 1, range: to_range(offense[:location]) }
134
+ end
135
+
136
+ def to_range(location)
137
+ {
138
+ start: { character: location.fetch(:column, 1) - 1, line: location.fetch(:line, 1) - 1 },
139
+ end: { character: 0, line: location.fetch(:line, 1) }
140
+ }
141
+ end
142
+
143
+ def shutdown
144
+ incoming_queue.clear
145
+ outgoing_queue.clear
146
+ incoming_queue.close
147
+ outgoing_queue.close
148
+ worker.join
149
+ dispatcher.join
150
+ send_log('Shutdown complete')
151
+ end
152
+
153
+ def start
154
+ reader.read do |message|
155
+ method = message[:method]
156
+ send_log("Received #{method}")
157
+
158
+ case method
159
+ when 'initialize', 'initialized', 'textDocument/didOpen', 'textDocument/didClose', 'textDocument/didSave'
160
+ incoming_queue.push(message)
161
+ when 'shutdown'
162
+ shutdown
163
+ when 'exit'
164
+ mutex.synchronize do
165
+ status = incoming_queue.closed? ? 0 : 1
166
+ exit(status)
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ private
173
+
174
+ attr_reader :reader, :writer, :mutex, :incoming_queue, :outgoing_queue, :worker, :dispatcher
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chutney
4
+ # The Chutney Language Server module wrapper
5
+ module LSP
6
+ end
7
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chutney
4
- VERSION = '3.7.0'
4
+ VERSION = '3.8.1'
5
5
  end
data/lib/chutney.rb CHANGED
@@ -48,7 +48,6 @@ require 'chutney/version'
48
48
  require 'cuke_modeler'
49
49
  require 'forwardable'
50
50
  require 'i18n'
51
- require 'set'
52
51
  require 'yaml'
53
52
 
54
53
  module Chutney
@@ -0,0 +1,5 @@
1
+ _site
2
+ .sass-cache
3
+ .jekyll-cache
4
+ .jekyll-metadata
5
+ vendor
@@ -0,0 +1,37 @@
1
+ source "https://rubygems.org"
2
+ # Hello! This is where you manage which Jekyll version is used to run.
3
+ # When you want to use a different version, change it below, save the
4
+ # file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
5
+ #
6
+ # bundle exec jekyll serve
7
+ #
8
+ # This will help ensure the proper Jekyll version is running.
9
+ # Happy Jekylling!
10
+ gem "jekyll", "~> 4.3.4"
11
+ # This is the default theme for new Jekyll sites. You may change this to anything you like.
12
+ gem "minima", "~> 2.5"
13
+ # If you want to use GitHub Pages, remove the "gem "jekyll"" above and
14
+ # uncomment the line below. To upgrade, run `bundle update github-pages`.
15
+ # gem "github-pages", group: :jekyll_plugins
16
+ # If you have any plugins, put them here!
17
+ group :jekyll_plugins do
18
+ gem "jekyll-feed", "~> 0.12"
19
+ gem "jekyll-data"
20
+ end
21
+
22
+ # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem
23
+ # and associated library.
24
+ platforms :mingw, :x64_mingw, :mswin, :jruby do
25
+ gem "tzinfo", ">= 1", "< 3"
26
+ gem "tzinfo-data"
27
+ end
28
+
29
+ # Performance-booster for watching directories on Windows
30
+ gem "wdm", "~> 0.1", :platforms => [:mingw, :x64_mingw, :mswin]
31
+
32
+ # Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem
33
+ # do not have a Java counterpart.
34
+ gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby]
35
+ gem "minimal-mistakes-jekyll"
36
+ gem 'base64'
37
+ gem 'csv'