standard 1.21.0 → 1.24.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b81947d8fd0c0d0ac932f947406aa3ecaa893f26861d143895c4c17d5174e12
4
- data.tar.gz: 451ce5e6c758f9d12539e6ae7ee877ea3597c6cb876994e192d5ccc54bc3d453
3
+ metadata.gz: cc7f5e2e4baad0d0471e6aba98df29bd5d8bb3db7c4a8fc3557581572a30865b
4
+ data.tar.gz: 76083326596e81461fc7cf9664421d4089555d9a3b1a2f8706e2884522379611
5
5
  SHA512:
6
- metadata.gz: 4b25fcaa73ce0ec0ac525497f600a8d7c37c36e780c7d913d84e1837fa77d3551ed75890b069312950a81b00376725080b0272639be67ca8e1741f9f4cb9b91c
7
- data.tar.gz: cca1619ce8107cb4e8d2a5156846272759643dd1031e4fe53671ec4c8f7ae7e7f31d01ec49499ef407de6a11b89236534638890cdf93454fd6cd536dc290fe66
6
+ metadata.gz: 47a6281011618bc51cd198c5dad586608d97c64ed05a4146d8549739332d22d70669f24fd9a59e1508c6b1ec6d631387949d589ab97241adf296fd0c2ec5ca22
7
+ data.tar.gz: dbca4f6a5c7e0777e93a98f3c6f0f14b4a15210cadcad1c8489f16319e636fe92ee5de19a295389fb3cdec7b73c37bd86510aa7de0b3dd8537f8033766cbd45c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.24.3
4
+
5
+ * _Further_ _further_ _further_ improve `--lsp` server to ignore files correctly
6
+
7
+ ## 1.24.2
8
+
9
+ * _Further_ _further_ improve `--lsp` server to always respond to requests
10
+
11
+ ## 1.24.1
12
+
13
+ * _Further_ improve `--lsp` server to better support commands used by VS Code
14
+
15
+ ## 1.24.0
16
+
17
+ * Improve `--lsp` server to better support the commands used by VS Code
18
+
19
+ ## 1.23.0
20
+
21
+ * Update rubocop from 1.42.0 to [1.44.1](https://github.com/rubocop/rubocop/releases/tag/v1.44.1)
22
+
23
+ ## 1.22.1
24
+
25
+ * Improve the behavior of `extend_config` to more accurately reflect how Rubocop
26
+ extensions would behave when loaded via the `rubocop` CLI (by capturing any
27
+ mutations to RuboCop's default configuration)
28
+ [#512](https://github.com/testdouble/standard/pull/512)
29
+
30
+ ## 1.22.0
31
+
32
+ * Add `extend_config` option [#506](https://github.com/testdouble/standard/pull/506)
33
+
34
+ ## 1.21.1
35
+
36
+ * Fix standard comment directives [#498](https://github.com/testdouble/standard/pull/498)
37
+
3
38
  ## 1.21.0
4
39
 
5
40
  * Update rubocop-performance from 1.15.1 to [1.15.2](https://github.com/rubocop/rubocop-performance/releases/tag/v1.15.2)
data/Gemfile CHANGED
@@ -7,6 +7,7 @@ gem "minitest", "~> 5.0"
7
7
  gem "pry"
8
8
  gem "rake", "~> 13.0"
9
9
  gem "gimme"
10
+ gem "m"
10
11
 
11
12
  if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.5")
12
13
  gem "simplecov"
data/Gemfile.lock CHANGED
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- standard (1.21.0)
4
+ standard (1.24.3)
5
5
  language_server-protocol (~> 3.17.0.2)
6
- rubocop (= 1.42.0)
6
+ rubocop (= 1.44.1)
7
7
  rubocop-performance (= 1.15.2)
8
8
 
9
9
  GEM
@@ -14,31 +14,34 @@ GEM
14
14
  docile (1.4.0)
15
15
  gimme (0.5.0)
16
16
  json (2.6.3)
17
- language_server-protocol (3.17.0.2)
17
+ language_server-protocol (3.17.0.3)
18
+ m (1.6.1)
19
+ method_source (>= 0.6.7)
20
+ rake (>= 0.9.2.2)
18
21
  method_source (1.0.0)
19
22
  minitest (5.17.0)
20
23
  parallel (1.22.1)
21
- parser (3.2.0.0)
24
+ parser (3.2.1.0)
22
25
  ast (~> 2.4.1)
23
- pry (0.14.1)
26
+ pry (0.14.2)
24
27
  coderay (~> 1.1)
25
28
  method_source (~> 1.0)
26
29
  rainbow (3.1.1)
27
30
  rake (13.0.6)
28
- regexp_parser (2.6.1)
31
+ regexp_parser (2.7.0)
29
32
  rexml (3.2.5)
30
- rubocop (1.42.0)
33
+ rubocop (1.44.1)
31
34
  json (~> 2.3)
32
35
  parallel (~> 1.10)
33
- parser (>= 3.1.2.1)
36
+ parser (>= 3.2.0.0)
34
37
  rainbow (>= 2.2.2, < 4.0)
35
38
  regexp_parser (>= 1.8, < 3.0)
36
39
  rexml (>= 3.2.5, < 4.0)
37
40
  rubocop-ast (>= 1.24.1, < 2.0)
38
41
  ruby-progressbar (~> 1.7)
39
- unicode-display_width (>= 1.4.0, < 3.0)
40
- rubocop-ast (1.24.1)
41
- parser (>= 3.1.1.0)
42
+ unicode-display_width (>= 2.4.0, < 3.0)
43
+ rubocop-ast (1.26.0)
44
+ parser (>= 3.2.1.0)
42
45
  rubocop-performance (1.15.2)
43
46
  rubocop (>= 1.7.0, < 2.0)
44
47
  rubocop-ast (>= 0.4.0)
@@ -49,7 +52,7 @@ GEM
49
52
  simplecov_json_formatter (~> 0.1)
50
53
  simplecov-html (0.12.3)
51
54
  simplecov_json_formatter (0.1.4)
52
- unicode-display_width (2.4.0)
55
+ unicode-display_width (2.4.2)
53
56
 
54
57
  PLATFORMS
55
58
  ruby
@@ -58,6 +61,7 @@ PLATFORMS
58
61
  DEPENDENCIES
59
62
  bundler
60
63
  gimme
64
+ m
61
65
  minitest (~> 5.0)
62
66
  pry
63
67
  rake (~> 13.0)
data/README.md CHANGED
@@ -215,12 +215,12 @@ inherit_gem:
215
215
 
216
216
  ## Who uses Ruby Standard Style?
217
217
 
218
- (This section will not [look very
219
- familiar](https://github.com/standard/standard#who-uses-javascript-standard-style)
220
- if you've used StandardJS.)
218
+ Here are a few examples of Ruby Standard-compliant teams & projects:
221
219
 
222
220
  * [Test Double](https://testdouble.com/agency)
223
221
  * [Amazon Web Services](https://aws.amazon.com/)
222
+ * [Arrows](https://arrows.to/)
223
+ * [Avo Admin](https://avohq.io/)
224
224
  * [Babylist](https://www.babylist.com/)
225
225
  * [Brand New Box](https://brandnewbox.com)
226
226
  * [Brave Software](https://github.com/brave-intl/publishers)
@@ -244,9 +244,7 @@ if you've used StandardJS.)
244
244
  * [RubyCI](https://ruby.ci)
245
245
  * [thoughtbot](https://thoughtbot.com/)
246
246
 
247
- If your team starts using Standard, [send a pull
248
- request](https://github.com/testdouble/standard/edit/main/README.md) to let us
249
- know!
247
+ Does your team use Standard? [Add your name to the list](https://github.com/testdouble/standard/edit/main/README.md)!
250
248
 
251
249
  ## Is there a readme badge?
252
250
 
@@ -401,6 +399,87 @@ configurations](https://github.com/testdouble/standard/tree/main/config) and
401
399
  consider helping out by submitting a pull request if you find a rule that won't
402
400
  work for older Rubies.
403
401
 
402
+ ## How do I use Standard with RuboCop extensions or custom rules?
403
+
404
+ If you want to use Standard in conjunction with RuboCop extensions or custom
405
+ cops, you can specify them in your own [RuboCop configuration
406
+ YAML](https://docs.rubocop.org/rubocop/configuration.html) files and
407
+ `.standard.yml` using the "extend_config` setting.
408
+
409
+ For a simple example, you could include [rubocop-rails](https://github.com/rubocop/rubocop-rails)
410
+ when Standard runs by first specifying a file in `.standard.yml`:
411
+
412
+ ```yaml
413
+ # .standard.yml
414
+
415
+ extend_config:
416
+ - .standard_rubocop_extensions.yml
417
+ ```
418
+
419
+ And a minimal RuboCop configuration file:
420
+
421
+ ```yaml
422
+ # .standard_rubocop_extensions.yml
423
+
424
+ require:
425
+ - rubocop-rails
426
+ ```
427
+
428
+ That's it! Now, in addition to all of Standard's built-in rules, `standardrb`
429
+ and `rake standard` will also execute the default configuration of the
430
+ `rubocop-rails` gem without needing to invoke `rubocop` separately.
431
+
432
+ For a slightly more complex example, we could add the
433
+ [https://github.com/Betterment/betterlint] gem from our friends at
434
+ [Betterment](https://www.betterment.com), first by telling Standard where our
435
+ configuration file is:
436
+
437
+ ```yml
438
+ # .standard.yml
439
+
440
+ extend_config:
441
+ - .betterlint.yml
442
+ ```
443
+
444
+ But if we only wanted to enable a particular rule, we could configure it more
445
+ narrowly, like this:
446
+
447
+ ```yml
448
+ # .betterlint.yml
449
+
450
+ require:
451
+ - rubocop/cop/betterment.rb
452
+
453
+ AllCops:
454
+ DisabledByDefault: true
455
+
456
+ Betterment/UnscopedFind:
457
+ Enabled: true
458
+
459
+ unauthenticated_models:
460
+ - SystemConfiguration
461
+ ```
462
+
463
+ This same approach works for more than just gems! Just require a Ruby file that
464
+ defines or loads your [custom RuboCop
465
+ implementation](https://docs.rubocop.org/rubocop/development.html) and configure
466
+ it using `extend_config`.
467
+
468
+ When Standard encounters an `extend_config` property, it will merge your
469
+ configuration files with Standard's base ruleset. To prevent Standard's built-in
470
+ rules from being modified, any configuration of rules includued in the `rubocop`
471
+ or `rubocop-performance` gems will be ignored. Most settings under `AllCops:`
472
+ can be configured, however, unless they'd conflict with a setting used by
473
+ Standard (like `TargetRubyVersion`) or prevent Standard's own rules from running
474
+ (like `StyleGuideCopsOnly`). If you specify multiple YAML files under
475
+ `extend_config`, note that their resulting RuboCop configurations will be merged
476
+ in order (i.e. last-in-wins).
477
+
478
+ If you find that Standard's `extend_config` feature doesn't meet your needs,
479
+ Evil Martians also maintains [a regularly updated
480
+ guide](https://evilmartians.com/chronicles/rubocoping-with-legacy-bring-your-ruby-code-up-to-standard)
481
+ on how to configure RuboCop to load and execute Standard's ruleset.
482
+
404
483
  ## How do I change the output?
405
484
 
406
485
  Standard's built-in formatter is intentionally minimal, printing only unfixed
@@ -492,13 +571,6 @@ either:
492
571
  gem to your build, which will automatically ensure that the comment is
493
572
  prepended for every applicable file in your project
494
573
 
495
- ## How do I use Standard with RuboCop extensions?
496
-
497
- This is not officially supported by Standard. However, Evil Martians wrote up [a
498
- regularly updated
499
- guide](https://evilmartians.com/chronicles/rubocoping-with-legacy-bring-your-ruby-code-up-to-standard)
500
- on how to do so.
501
-
502
574
  ## How often is Standard updated?
503
575
 
504
576
  We aim to update Standard once a month, in the first week of the month. In between releases, we'll be considering RuboCop updates, RuboCop Performance updates, and community contributions.
data/config/base.yml CHANGED
@@ -38,6 +38,9 @@ Gemspec/DependencyVersion:
38
38
  Gemspec/DeprecatedAttributeAssignment:
39
39
  Enabled: true
40
40
 
41
+ Gemspec/DevelopmentDependencies:
42
+ Enabled: false
43
+
41
44
  Gemspec/DuplicatedAssignment:
42
45
  Enabled: true
43
46
  Include:
@@ -824,6 +827,9 @@ Lint/UselessElseWithoutRescue:
824
827
  Lint/UselessMethodDefinition:
825
828
  Enabled: false
826
829
 
830
+ Lint/UselessRescue:
831
+ Enabled: true
832
+
827
833
  Lint/UselessRuby2Keywords:
828
834
  Enabled: true
829
835
 
@@ -1213,6 +1219,9 @@ Style/CommentAnnotation:
1213
1219
  Style/CommentedKeyword:
1214
1220
  Enabled: false
1215
1221
 
1222
+ Style/ComparableClamp:
1223
+ Enabled: true
1224
+
1216
1225
  Style/ConcatArrayLiterals:
1217
1226
  Enabled: false
1218
1227
 
@@ -1269,6 +1278,7 @@ Style/EmptyCaseCondition:
1269
1278
 
1270
1279
  Style/EmptyElse:
1271
1280
  Enabled: true
1281
+ AllowComments: true
1272
1282
  EnforcedStyle: both
1273
1283
 
1274
1284
  Style/EmptyHeredoc:
@@ -1406,6 +1416,9 @@ Style/InlineComment:
1406
1416
  Style/InverseMethods:
1407
1417
  Enabled: false
1408
1418
 
1419
+ Style/InvertibleUnlessCondition:
1420
+ Enabled: false
1421
+
1409
1422
  Style/IpAddresses:
1410
1423
  Enabled: false
1411
1424
 
data/config/ruby-2.3.yml CHANGED
@@ -2,3 +2,6 @@ inherit_from: ./ruby-2.4.yml
2
2
 
3
3
  AllCops:
4
4
  TargetRubyVersion: 2.3
5
+
6
+ Style/ComparableClamp:
7
+ Enabled: false
@@ -0,0 +1,67 @@
1
+ require_relative "../file_finder"
2
+
3
+ class Standard::CreatesConfigStore
4
+ class MergesUserConfigExtensions
5
+ DISALLOWED_ALLCOPS_KEYS = [
6
+ "Include",
7
+ "Exclude",
8
+ "StyleGuideCopsOnly",
9
+ "TargetRubyVersion",
10
+
11
+ # The AllCops[Enabled] key is an unused artifact of #merge_with_default.
12
+ # See: https://github.com/rubocop/rubocop/blob/master/lib/rubocop/config_loader_resolver.rb#L81-L85
13
+ "Enabled"
14
+ ].freeze
15
+
16
+ def call(options_config, standard_config)
17
+ return unless standard_config[:extend_config]&.any?
18
+
19
+ extended_config = load_and_merge_extended_rubocop_configs(options_config, standard_config).to_h
20
+ merge_standard_and_user_all_cops!(options_config, extended_config)
21
+ merge_extended_rules_into_standard!(options_config, extended_config)
22
+ end
23
+
24
+ private
25
+
26
+ def load_and_merge_extended_rubocop_configs(options_config, standard_config)
27
+ fake_out_rubocop_default_configuration(options_config) do |fake_config|
28
+ standard_config[:extend_config].reduce(fake_config) do |config, path|
29
+ RuboCop::ConfigLoader.instance_variable_set(:@default_configuration, config)
30
+
31
+ user_rubocop_yaml_path = Standard::FileFinder.new.call(path, Dir.pwd)
32
+ extension = RuboCop::ConfigLoader.load_file(user_rubocop_yaml_path)
33
+ RuboCop::ConfigLoader.merge_with_default(extension, path)
34
+ end
35
+ end
36
+ end
37
+
38
+ def merge_standard_and_user_all_cops!(options_config, extended_config)
39
+ options_config["AllCops"].merge!(
40
+ except(extended_config["AllCops"], DISALLOWED_ALLCOPS_KEYS)
41
+ )
42
+ end
43
+
44
+ def merge_extended_rules_into_standard!(options_config, extended_config)
45
+ except(extended_config, options_config.keys).each do |key, value|
46
+ options_config[key] = value
47
+ end
48
+ end
49
+
50
+ def fake_out_rubocop_default_configuration(options_config)
51
+ og_default_config = RuboCop::ConfigLoader.instance_variable_get(:@default_configuration)
52
+ result = yield blank_rubocop_config(options_config)
53
+ RuboCop::ConfigLoader.instance_variable_set(:@default_configuration, og_default_config)
54
+ result
55
+ end
56
+
57
+ # Blank configuration object to merge extensions into, with all known
58
+ # AllCops keys set to avoid warnings about unknown properties
59
+ def blank_rubocop_config(example_config)
60
+ RuboCop::Config.new(example_config.to_h.slice("AllCops"), "")
61
+ end
62
+
63
+ def except(hash_or_config, keys)
64
+ hash_or_config.to_h.reject { |key, _| keys.include?(key) }.to_h
65
+ end
66
+ end
67
+ end
@@ -3,6 +3,7 @@ require "rubocop"
3
3
  require_relative "creates_config_store/assigns_rubocop_yaml"
4
4
  require_relative "creates_config_store/sets_target_ruby_version"
5
5
  require_relative "creates_config_store/configures_ignored_paths"
6
+ require_relative "creates_config_store/merges_user_config_extensions"
6
7
 
7
8
  module Standard
8
9
  class CreatesConfigStore
@@ -10,6 +11,7 @@ module Standard
10
11
  @assigns_rubocop_yaml = AssignsRubocopYaml.new
11
12
  @sets_target_ruby_version = SetsTargetRubyVersion.new
12
13
  @configures_ignored_paths = ConfiguresIgnoredPaths.new
14
+ @merges_user_config_extensions = MergesUserConfigExtensions.new
13
15
  end
14
16
 
15
17
  def call(standard_config)
@@ -17,6 +19,7 @@ module Standard
17
19
  options_config = @assigns_rubocop_yaml.call(config_store, standard_config)
18
20
  @sets_target_ruby_version.call(options_config, standard_config)
19
21
  @configures_ignored_paths.call(options_config, standard_config)
22
+ @merges_user_config_extensions.call(options_config, standard_config)
20
23
  end
21
24
  end
22
25
  end
@@ -1,9 +1,23 @@
1
+ require_relative "runners/rubocop"
2
+ require_relative "runners/version"
3
+ require_relative "runners/verbose_version"
4
+ require_relative "runners/lsp"
5
+ require_relative "runners/genignore"
6
+ require_relative "runners/help"
7
+
1
8
  module Standard
2
9
  class LoadsRunner
3
- # Warning: clever metaprogramming. 99% of the time this is Runners::Rubocop
10
+ RUNNERS = {
11
+ rubocop: ::Standard::Runners::Rubocop,
12
+ version: ::Standard::Runners::Version,
13
+ verbose_version: ::Standard::Runners::VerboseVersion,
14
+ lsp: ::Standard::Runners::Lsp,
15
+ genignore: ::Standard::Runners::Genignore,
16
+ help: ::Standard::Runners::Help
17
+ }.freeze
18
+
4
19
  def call(command)
5
- require_relative "runners/#{command}"
6
- ::Standard::Runners.const_get(command.to_s.capitalize).new
20
+ RUNNERS[command].new
7
21
  end
8
22
  end
9
23
  end
@@ -32,7 +32,8 @@ module Standard
32
32
  default_ignores: standard_yaml.key?("default_ignores") ? !!standard_yaml["default_ignores"] : true,
33
33
  config_root: yaml_path ? Pathname.new(yaml_path).dirname.to_s : nil,
34
34
  todo_file: todo_path,
35
- todo_ignore_files: (todo_yaml["ignore"] || []).map { |f| (Hash === f) ? f.keys.first : f }
35
+ todo_ignore_files: Array(todo_yaml["ignore"]).map { |f| (Hash === f) ? f.keys.first : f },
36
+ extend_config: Array(standard_yaml["extend_config"])
36
37
  }
37
38
  end
38
39
 
@@ -0,0 +1,10 @@
1
+ module Standard
2
+ module Lsp
3
+ class KillsServer
4
+ def call(&blk)
5
+ at_exit(&blk) unless blk.nil?
6
+ exit 0
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ module Standard
2
+ module Lsp
3
+ class Logger
4
+ def initialize
5
+ @puts_onces = []
6
+ end
7
+
8
+ def puts(message)
9
+ warn("[server] #{message}")
10
+ end
11
+
12
+ def puts_once(message)
13
+ return if @puts_onces.include?(message)
14
+ @puts_onces << message
15
+ puts(message)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,204 @@
1
+ require_relative "kills_server"
2
+
3
+ module Standard
4
+ module Lsp
5
+ class Routes
6
+ def initialize(writer, logger, standardizer)
7
+ @writer = writer
8
+ @logger = logger
9
+ @standardizer = standardizer
10
+
11
+ @text_cache = {}
12
+ @kills_server = KillsServer.new
13
+ end
14
+
15
+ def self.handle(name, &block)
16
+ define_method("handle_#{name}", &block)
17
+ end
18
+
19
+ def for(name)
20
+ name = "handle_#{name}"
21
+ if respond_to?(name)
22
+ method(name)
23
+ end
24
+ end
25
+
26
+ handle "initialize" do |request|
27
+ @writer.write(id: request[:id], result: Proto::Interface::InitializeResult.new(
28
+ capabilities: Proto::Interface::ServerCapabilities.new(
29
+ document_formatting_provider: true,
30
+ diagnostic_provider: true,
31
+ execute_command_provider: true,
32
+ text_document_sync: Proto::Interface::TextDocumentSyncOptions.new(
33
+ change: Proto::Constant::TextDocumentSyncKind::FULL
34
+ )
35
+ )
36
+ ))
37
+ end
38
+
39
+ handle "initialized" do |request|
40
+ @logger.puts "Standard Ruby v#{Standard::VERSION} LSP server initialized, pid #{Process.pid}"
41
+ end
42
+
43
+ handle "shutdown" do |request|
44
+ @logger.puts "Client asked to shutdown Standard LSP server."
45
+ @kills_server.call do
46
+ @writer.write(id: request[:id], result: nil)
47
+ @logger.puts "Exiting..."
48
+ end
49
+ end
50
+
51
+ handle "textDocument/diagnostic" do |request|
52
+ doc = request[:params][:textDocument]
53
+ result = diagnostic(doc[:uri], doc[:text])
54
+ @writer.write(result)
55
+ end
56
+
57
+ handle "textDocument/didChange" do |request|
58
+ params = request[:params]
59
+ result = diagnostic(params[:textDocument][:uri], params[:contentChanges][0][:text])
60
+ @writer.write(result)
61
+ end
62
+
63
+ handle "textDocument/didOpen" do |request|
64
+ doc = request[:params][:textDocument]
65
+ result = diagnostic(doc[:uri], doc[:text])
66
+ @writer.write(result)
67
+ end
68
+
69
+ handle "textDocument/didClose" do |request|
70
+ @text_cache.delete(request.dig(:params, :textDocument, :uri))
71
+ end
72
+
73
+ handle "textDocument/formatting" do |request|
74
+ uri = request[:params][:textDocument][:uri]
75
+ @writer.write({id: request[:id], result: format_file(uri)})
76
+ end
77
+
78
+ handle "workspace/didChangeWatchedFiles" do |request|
79
+ if request[:params][:changes].any? { |change| change[:uri].end_with?(".standard.yml") }
80
+ @logger.puts "Configuration file changed; restart required"
81
+ @kills_server.call
82
+ end
83
+ end
84
+
85
+ handle "workspace/executeCommand" do |request|
86
+ if request[:params][:command] == "standardRuby.formatAutoFixes"
87
+ uri = request[:params][:arguments][0][:uri]
88
+ @writer.write({
89
+ id: request[:id],
90
+ method: "workspace/applyEdit",
91
+ params: {
92
+ label: "Format with Standard Ruby auto-fixes",
93
+ edit: {
94
+ changes: {
95
+ uri => format_file(uri)
96
+ }
97
+ }
98
+ }
99
+ })
100
+ else
101
+ handle_unsupported_method(request, request[:params][:command])
102
+ end
103
+ end
104
+
105
+ handle "textDocument/didSave" do |_request|
106
+ # Nothing to do
107
+ end
108
+
109
+ handle "$/cancelRequest" do |_request|
110
+ # Can't cancel anything because single-threaded
111
+ end
112
+
113
+ handle "$/setTrace" do |_request|
114
+ # No-op, we log everything
115
+ end
116
+
117
+ def handle_unsupported_method(request, method = request[:method])
118
+ @writer.write({id: request[:id], error: Proto::Interface::ResponseError.new(
119
+ code: Proto::Constant::ErrorCodes::METHOD_NOT_FOUND,
120
+ message: "Unsupported Method: #{method}"
121
+ )})
122
+ @logger.puts "Unsupported Method: #{method}"
123
+ end
124
+
125
+ def handle_method_missing(request)
126
+ if request.key?(:id)
127
+ @writer.write({id: request[:id], result: nil})
128
+ end
129
+ end
130
+
131
+ private
132
+
133
+ def uri_to_path(uri)
134
+ uri.sub(%r{^file://}, "")
135
+ end
136
+
137
+ def format_file(file_uri)
138
+ text = @text_cache[file_uri]
139
+ if text.nil?
140
+ @logger.puts "Format request arrived before text synchonized; skipping: `#{file_uri}'"
141
+ []
142
+ else
143
+ new_text = @standardizer.format(uri_to_path(file_uri), text)
144
+ if new_text == text
145
+ []
146
+ else
147
+ [{
148
+ newText: new_text,
149
+ range: {
150
+ start: {line: 0, character: 0},
151
+ end: {line: text.count("\n") + 1, character: 0}
152
+ }
153
+ }]
154
+ end
155
+ end
156
+ end
157
+
158
+ def diagnostic(file_uri, text)
159
+ @text_cache[file_uri] = text
160
+ offenses = @standardizer.offenses(uri_to_path(file_uri), text)
161
+
162
+ lsp_diagnostics = offenses.map { |o|
163
+ code = o[:cop_name]
164
+
165
+ msg = o[:message].delete_prefix(code)
166
+ loc = o[:location]
167
+
168
+ severity = case o[:severity]
169
+ when "error", "fatal"
170
+ SEV::ERROR
171
+ when "warning"
172
+ SEV::WARNING
173
+ when "convention"
174
+ SEV::INFORMATION
175
+ when "refactor", "info"
176
+ SEV::HINT
177
+ else # the above cases fully cover what RuboCop sends at this time
178
+ logger.puts "Unknown severity: #{severity.inspect}"
179
+ SEV::HINT
180
+ end
181
+
182
+ {
183
+ code: code,
184
+ message: msg,
185
+ range: {
186
+ start: {character: loc[:start_column] - 1, line: loc[:start_line] - 1},
187
+ end: {character: loc[:last_column] - 1, line: loc[:last_line] - 1}
188
+ },
189
+ severity: severity,
190
+ source: "standard"
191
+ }
192
+ }
193
+
194
+ {
195
+ method: "textDocument/publishDiagnostics",
196
+ params: {
197
+ uri: file_uri,
198
+ diagnostics: lsp_diagnostics
199
+ }
200
+ }
201
+ end
202
+ end
203
+ end
204
+ end
@@ -1,144 +1,36 @@
1
1
  require "language_server-protocol"
2
2
  require_relative "standardizer"
3
+ require_relative "routes"
4
+ require_relative "logger"
3
5
 
4
6
  module Standard
5
- module LSP
6
- class Server
7
- Proto = LanguageServer::Protocol
8
- SEV = Proto::Constant::DiagnosticSeverity
9
-
10
- def self.start(standardizer)
11
- new(standardizer).start
12
- end
13
-
14
- attr_accessor :standardizer, :writer, :reader, :logger, :text_cache, :subscribers
15
-
16
- def initialize(standardizer)
17
- self.standardizer = standardizer
18
- self.writer = Proto::Transport::Io::Writer.new($stdout)
19
- self.reader = Proto::Transport::Io::Reader.new($stdin)
20
- self.logger = $stderr
21
- self.text_cache = {}
22
-
23
- self.subscribers = {
24
- "initialize" => ->(request) {
25
- init_result = Proto::Interface::InitializeResult.new(
26
- capabilities: Proto::Interface::ServerCapabilities.new(
27
- document_formatting_provider: true,
28
- diagnostic_provider: true,
29
- text_document_sync: Proto::Constant::TextDocumentSyncKind::FULL
30
- )
31
- )
32
- writer.write(id: request[:id], result: init_result)
33
- },
34
-
35
- "initialized" => ->(request) { logger.puts "standard v#{Standard::VERSION} initialized, pid #{Process.pid}" },
7
+ module Lsp
8
+ Proto = LanguageServer::Protocol
9
+ SEV = Proto::Constant::DiagnosticSeverity
36
10
 
37
- "shutdown" => ->(request) {
38
- logger.puts "asked to shutdown, exiting..."
39
- exit
40
- },
41
-
42
- "textDocument/didChange" => ->(request) {
43
- params = request[:params]
44
- result = diagnostic(params[:textDocument][:uri], params[:contentChanges][0][:text])
45
- writer.write(result)
46
- },
47
-
48
- "textDocument/didOpen" => ->(request) {
49
- td = request[:params][:textDocument]
50
- result = diagnostic(td[:uri], td[:text])
51
- writer.write(result)
52
- },
53
-
54
- "textDocument/didClose" => ->(request) {
55
- text_cache.delete(request.dig(:params, :textDocument, :uri))
56
- },
57
-
58
- "textDocument/formatting" => ->(request) {
59
- uri = request[:params][:textDocument][:uri]
60
- writer.write({id: request[:id], result: format_file(uri)})
61
- },
62
-
63
- "textDocument/didSave" => ->(request) {}
64
- }
11
+ class Server
12
+ def initialize(config)
13
+ @writer = Proto::Transport::Io::Writer.new($stdout)
14
+ @reader = Proto::Transport::Io::Reader.new($stdin)
15
+ @logger = Logger.new
16
+ @standardizer = Standard::Lsp::Standardizer.new(config, @logger)
17
+ @routes = Routes.new(@writer, @logger, @standardizer)
65
18
  end
66
19
 
67
20
  def start
68
- reader.read do |request|
69
- method = request[:method]
70
- if (subscriber = subscribers[method])
71
- subscriber.call(request)
21
+ @reader.read do |request|
22
+ if !request.key?(:method)
23
+ @routes.handle_method_missing(request)
24
+ elsif (route = @routes.for(request[:method]))
25
+ route.call(request)
72
26
  else
73
- logger.puts "unknown method: #{method}"
27
+ @routes.handle_unsupported_method(request)
74
28
  end
75
29
  rescue => e
76
- logger.puts "error #{e.class} #{e.message[0..100]}"
77
- logger.puts e.backtrace.inspect
30
+ @logger.puts "Error #{e.class} #{e.message[0..100]}"
31
+ @logger.puts e.backtrace.inspect
78
32
  end
79
33
  end
80
-
81
- def format_file(file_uri)
82
- text = text_cache[file_uri]
83
- new_text = standardizer.format(text)
84
-
85
- if new_text == text
86
- []
87
- else
88
- [{
89
- newText: new_text,
90
- range: {
91
- start: {line: 0, character: 0},
92
- end: {line: text.count("\n") + 1, character: 0}
93
- }
94
- }]
95
- end
96
- end
97
-
98
- def diagnostic(file_uri, text)
99
- text_cache[file_uri] = text
100
- offenses = standardizer.offenses(text)
101
-
102
- lsp_diagnostics = offenses.map { |o|
103
- code = o[:cop_name]
104
-
105
- msg = o[:message].delete_prefix(code)
106
- loc = o[:location]
107
-
108
- severity = case o[:severity]
109
- when "error", "fatal"
110
- SEV::ERROR
111
- when "warning"
112
- SEV::WARNING
113
- when "convention"
114
- SEV::INFORMATION
115
- when "refactor", "info"
116
- SEV::HINT
117
- else # the above cases fully cover what RuboCop sends at this time
118
- logger.puts "unknown severity: #{severity.inspect}"
119
- SEV::HINT
120
- end
121
-
122
- {
123
- code: code,
124
- message: msg,
125
- range: {
126
- start: {character: loc[:start_column] - 1, line: loc[:start_line] - 1},
127
- end: {character: loc[:last_column] - 1, line: loc[:last_line] - 1}
128
- },
129
- severity: severity,
130
- source: "standard"
131
- }
132
- }
133
-
134
- {
135
- method: "textDocument/publishDiagnostics",
136
- params: {
137
- diagnostics: lsp_diagnostics,
138
- uri: file_uri
139
- }
140
- }
141
- end
142
34
  end
143
35
  end
144
36
  end
@@ -1,49 +1,65 @@
1
1
  require_relative "../runners/rubocop"
2
- require "tempfile"
3
2
 
4
3
  module Standard
5
- module LSP
4
+ module Lsp
6
5
  class Standardizer
7
- def initialize(config)
8
- @template_options = config
9
- @runner = Standard::Runners::Rubocop.new
6
+ def initialize(config, logger)
7
+ @config = config
8
+ @logger = logger
9
+ @rubocop_runner = Standard::Runners::Rubocop.new
10
10
  end
11
11
 
12
- def format(text)
13
- run_standard(text, format: true)
12
+ # This abuses the --stdin option of rubocop and reads the formatted text
13
+ # from the options[:stdin] that rubocop mutates. This depends on
14
+ # parallel: false as well as the fact that rubocop doesn't otherwise dup
15
+ # or reassign that options object. Risky business!
16
+ #
17
+ # Reassigning options[:stdin] is done here:
18
+ # https://github.com/rubocop/rubocop/blob/master/lib/rubocop/cop/team.rb#L131
19
+ # Printing options[:stdin]
20
+ # https://github.com/rubocop/rubocop/blob/master/lib/rubocop/cli/command/execute_runner.rb#L95
21
+ # Setting `parallel: true` would break this here:
22
+ # https://github.com/rubocop/rubocop/blob/master/lib/rubocop/runner.rb#L72
23
+ def format(path, text)
24
+ ad_hoc_config = fork_config(path, text, format: true)
25
+ capture_rubocop_stdout(ad_hoc_config)
26
+ ad_hoc_config.rubocop_options[:stdin]
14
27
  end
15
28
 
16
- def offenses(text)
17
- results = run_standard(text, format: false)
18
- JSON.parse(results, symbolize_names: true).dig(:files, 0, :offenses)
29
+ def offenses(path, text)
30
+ results = JSON.parse(
31
+ capture_rubocop_stdout(fork_config(path, text, format: false)),
32
+ symbolize_names: true
33
+ )
34
+ if results[:files].empty?
35
+ @logger.puts_once "Ignoring file, per configuration: #{path}"
36
+ []
37
+ else
38
+ results.dig(:files, 0, :offenses)
39
+ end
19
40
  end
20
41
 
21
42
  private
22
43
 
23
- BASENAME = ["source", ".rb"].freeze
24
- def run_standard(text, format:)
25
- Tempfile.open(BASENAME) do |temp|
26
- temp.write(text)
27
- temp.flush
28
- stdout = capture_rubocop_stdout(make_config(temp.path, format))
29
- format ? File.read(temp.path) : stdout
30
- end
31
- end
32
-
33
- def make_config(file, format)
34
- # Can't make frozen versions of this hash because RuboCop mutates it
35
- o = if format
36
- {autocorrect: true, formatters: [["Standard::Formatter", nil]], parallel: true, todo_file: nil, todo_ignore_files: [], safe_autocorrect: true}
44
+ BASE_OPTIONS = {
45
+ force_exclusion: true,
46
+ parallel: false,
47
+ todo_file: nil,
48
+ todo_ignore_files: []
49
+ }
50
+ def fork_config(path, text, format:)
51
+ options = if format
52
+ {stdin: text, autocorrect: true, formatters: [], safe_autocorrect: true}
37
53
  else
38
- {autocorrect: false, formatters: [["json"]], parallel: true, todo_file: nil, todo_ignore_files: [], format: "json"}
54
+ {stdin: text, autocorrect: false, formatters: [["json"]], format: "json"}
39
55
  end
40
- Standard::Config.new(@template_options.runner, [file], o, @template_options.rubocop_config_store)
56
+ Standard::Config.new(@config.runner, [path], BASE_OPTIONS.merge(options), @config.rubocop_config_store)
41
57
  end
42
58
 
43
59
  def capture_rubocop_stdout(config)
44
60
  redir = StringIO.new
45
61
  $stdout = redir
46
- @runner.call(config)
62
+ @rubocop_runner.call(config)
47
63
  redir.string
48
64
  ensure
49
65
  $stdout = STDOUT
@@ -20,7 +20,7 @@ module Standard
20
20
 
21
21
  def separate_argv(argv)
22
22
  argv.partition do |flag|
23
- ["--generate-todo", "--fix", "--no-fix", "--version", "-v", "--help", "-h", "--lsp"].include?(flag)
23
+ ["--generate-todo", "--fix", "--no-fix", "--version", "-v", "--verbose-version", "-V", "--help", "-h", "--lsp"].include?(flag)
24
24
  end
25
25
  end
26
26
 
@@ -39,6 +39,8 @@ module Standard
39
39
  :help
40
40
  elsif (argv & ["--version", "-v"]).any?
41
41
  :version
42
+ elsif (argv & ["--verbose-version", "-V"]).any?
43
+ :verbose_version
42
44
  elsif (argv & ["--generate-todo"]).any?
43
45
  :genignore
44
46
  elsif (argv & ["--lsp"]).any?
@@ -13,4 +13,13 @@ module RuboCop
13
13
  .gsub(" ", '\s*')
14
14
  )
15
15
  end
16
+
17
+ class CommentConfig
18
+ alias_method :old_initialize, :initialize
19
+
20
+ def initialize(processed_source)
21
+ old_initialize(processed_source)
22
+ @no_directives &&= !processed_source.raw_source.include?("standard")
23
+ end
24
+ end
16
25
  end
@@ -15,6 +15,7 @@ module Standard
15
15
  --generate-todo Create a .standard_todo.yml that lists all the files that contain errors
16
16
  --lsp Start a LSP server listening on STDIN
17
17
  -v, --version Print the version of Standard
18
+ -V, --verbose-version Print the version of Standard and its dependencies.
18
19
  -h, --help Print this message
19
20
  FILE Files to lint [default: ./]
20
21
 
@@ -4,8 +4,7 @@ module Standard
4
4
  module Runners
5
5
  class Lsp
6
6
  def call(config)
7
- standardizer = Standard::LSP::Standardizer.new(config)
8
- Standard::LSP::Server.start(standardizer)
7
+ Standard::Lsp::Server.new(config).start
9
8
  end
10
9
  end
11
10
  end
@@ -0,0 +1,14 @@
1
+ require_relative "rubocop"
2
+
3
+ module Standard
4
+ module Runners
5
+ class VerboseVersion
6
+ def call(config)
7
+ puts <<-MSG.gsub(/^ {10}/, "")
8
+ Standard version: #{Standard::VERSION}
9
+ RuboCop version: #{RuboCop::Version.version(debug: true)}
10
+ MSG
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,3 @@
1
1
  module Standard
2
- VERSION = Gem::Version.new("1.21.0")
2
+ VERSION = Gem::Version.new("1.24.3")
3
3
  end
data/standard.gemspec CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency "rubocop", "1.42.0"
22
+ spec.add_dependency "rubocop", "1.44.1"
23
23
  spec.add_dependency "rubocop-performance", "1.15.2"
24
24
 
25
25
  # not semver: first three are lsp protocol version, last is patch
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.21.0
4
+ version: 1.24.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Searls
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-01-03 00:00:00.000000000 Z
11
+ date: 2023-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 1.42.0
19
+ version: 1.44.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 1.42.0
26
+ version: 1.44.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rubocop-performance
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -52,7 +52,7 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: 3.17.0.2
55
- description:
55
+ description:
56
56
  email:
57
57
  - searls@gmail.com
58
58
  executables:
@@ -95,12 +95,16 @@ files:
95
95
  - lib/standard/creates_config_store.rb
96
96
  - lib/standard/creates_config_store/assigns_rubocop_yaml.rb
97
97
  - lib/standard/creates_config_store/configures_ignored_paths.rb
98
+ - lib/standard/creates_config_store/merges_user_config_extensions.rb
98
99
  - lib/standard/creates_config_store/sets_target_ruby_version.rb
99
100
  - lib/standard/detects_fixability.rb
100
101
  - lib/standard/file_finder.rb
101
102
  - lib/standard/formatter.rb
102
103
  - lib/standard/loads_runner.rb
103
104
  - lib/standard/loads_yaml_config.rb
105
+ - lib/standard/lsp/kills_server.rb
106
+ - lib/standard/lsp/logger.rb
107
+ - lib/standard/lsp/routes.rb
104
108
  - lib/standard/lsp/server.rb
105
109
  - lib/standard/lsp/standardizer.rb
106
110
  - lib/standard/merges_settings.rb
@@ -112,13 +116,14 @@ files:
112
116
  - lib/standard/runners/help.rb
113
117
  - lib/standard/runners/lsp.rb
114
118
  - lib/standard/runners/rubocop.rb
119
+ - lib/standard/runners/verbose_version.rb
115
120
  - lib/standard/runners/version.rb
116
121
  - lib/standard/version.rb
117
122
  - standard.gemspec
118
123
  homepage: https://github.com/testdouble/standard
119
124
  licenses: []
120
125
  metadata: {}
121
- post_install_message:
126
+ post_install_message:
122
127
  rdoc_options: []
123
128
  require_paths:
124
129
  - lib
@@ -133,8 +138,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
138
  - !ruby/object:Gem::Version
134
139
  version: '0'
135
140
  requirements: []
136
- rubygems_version: 3.1.6
137
- signing_key:
141
+ rubygems_version: 3.3.26
142
+ signing_key:
138
143
  specification_version: 4
139
144
  summary: Ruby Style Guide, with linter & automatic code fixer
140
145
  test_files: []