standard 1.19.1 → 1.24.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf52e94f737a041479cf2e741613a0e07d54af0b41f32f12309a94d5a2f0416b
4
- data.tar.gz: 745dd891033fdc11271fd1331858a11c74d9e4188385eac5cc53580616aa7bbb
3
+ metadata.gz: cc7f5e2e4baad0d0471e6aba98df29bd5d8bb3db7c4a8fc3557581572a30865b
4
+ data.tar.gz: 76083326596e81461fc7cf9664421d4089555d9a3b1a2f8706e2884522379611
5
5
  SHA512:
6
- metadata.gz: d8eb3f8f27fbfc111270b6d2cbf44aed23de2c51eac64add89e3b8b89631adda6ca2348025c944a022cd51513868fb2f3668f1a099954bd8aea08d4acc12ef51
7
- data.tar.gz: 17a3f227ef4044914bf12151ee9dfcf822f9c197938caac644601bf5c839895970ab3e6f58ee0079083fc877fc56849bd4f7391744d7a95da0b917545e76fb3b
6
+ metadata.gz: 47a6281011618bc51cd198c5dad586608d97c64ed05a4146d8549739332d22d70669f24fd9a59e1508c6b1ec6d631387949d589ab97241adf296fd0c2ec5ca22
7
+ data.tar.gz: dbca4f6a5c7e0777e93a98f3c6f0f14b4a15210cadcad1c8489f16319e636fe92ee5de19a295389fb3cdec7b73c37bd86510aa7de0b3dd8537f8033766cbd45c
@@ -16,7 +16,7 @@ jobs:
16
16
  strategy:
17
17
  matrix:
18
18
  os: [ubuntu-latest]
19
- ruby-version: [2.6, 2.7, '3.0', 3.1]
19
+ ruby-version: [2.6, 2.7, '3.0', 3.1, 3.2]
20
20
 
21
21
  runs-on: ${{ matrix.os }}
22
22
 
@@ -3,7 +3,9 @@ name: Update
3
3
  on:
4
4
  workflow_dispatch:
5
5
  schedule:
6
- - cron: '5 7 * * 1'
6
+ - cron: '0 0 */100,1-7 * 1'
7
+ # https://blog.healthchecks.io/2022/09/schedule-cron-job-the-funky-way/
8
+ # In case you want to know how that cron job works to get the first Monday of each month.
7
9
 
8
10
  jobs:
9
11
  update:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
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
+
38
+ ## 1.21.0
39
+
40
+ * Update rubocop-performance from 1.15.1 to [1.15.2](https://github.com/rubocop/rubocop-performance/releases/tag/v1.15.2)
41
+ * Update rubocop from 1.40.0 to [1.42.0](https://github.com/rubocop/rubocop/releases/tag/v1.42.0)
42
+
43
+ ## 1.20.0
44
+
45
+ * Update rubocop from 1.39.0 to [1.40.0](https://github.com/rubocop/rubocop/releases/tag/v1.40.0)
46
+
3
47
  ## 1.19.1
4
48
 
5
49
  * Loosen dependency on `language_server-protocol`
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,10 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- standard (1.19.1)
4
+ standard (1.24.3)
5
5
  language_server-protocol (~> 3.17.0.2)
6
- rubocop (= 1.39.0)
7
- rubocop-performance (= 1.15.1)
6
+ rubocop (= 1.44.1)
7
+ rubocop-performance (= 1.15.2)
8
8
 
9
9
  GEM
10
10
  remote: https://rubygems.org/
@@ -14,42 +14,45 @@ 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
- minitest (5.16.3)
22
+ minitest (5.17.0)
20
23
  parallel (1.22.1)
21
- parser (3.1.3.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.39.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
- rubocop-ast (>= 1.23.0, < 2.0)
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.0)
41
- parser (>= 3.1.1.0)
42
- rubocop-performance (1.15.1)
42
+ unicode-display_width (>= 2.4.0, < 3.0)
43
+ rubocop-ast (1.26.0)
44
+ parser (>= 3.2.1.0)
45
+ rubocop-performance (1.15.2)
43
46
  rubocop (>= 1.7.0, < 2.0)
44
47
  rubocop-ast (>= 0.4.0)
45
48
  ruby-progressbar (1.11.0)
46
- simplecov (0.21.2)
49
+ simplecov (0.22.0)
47
50
  docile (~> 1.1)
48
51
  simplecov-html (~> 0.11)
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.3.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,12 +571,9 @@ 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?
574
+ ## How often is Standard updated?
496
575
 
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.
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.
501
577
 
502
578
  ## Does Standard work with [Insert other tool name here]?
503
579
 
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:
@@ -182,7 +185,7 @@ Layout/EndOfLine:
182
185
  Layout/ExtraSpacing:
183
186
  Enabled: true
184
187
  AllowForAlignment: false
185
- AllowBeforeTrailingComments: false
188
+ AllowBeforeTrailingComments: true
186
189
  ForceEqualSignAlignment: false
187
190
 
188
191
  Layout/FirstArgumentIndentation:
@@ -444,6 +447,7 @@ Lint/AmbiguousRegexpLiteral:
444
447
  Lint/AssignmentInCondition:
445
448
  Enabled: true
446
449
  AllowSafeAssignment: true
450
+ AutoCorrect: false
447
451
 
448
452
  Lint/BigDecimalNew:
449
453
  Enabled: true
@@ -823,6 +827,9 @@ Lint/UselessElseWithoutRescue:
823
827
  Lint/UselessMethodDefinition:
824
828
  Enabled: false
825
829
 
830
+ Lint/UselessRescue:
831
+ Enabled: true
832
+
826
833
  Lint/UselessRuby2Keywords:
827
834
  Enabled: true
828
835
 
@@ -1127,6 +1134,9 @@ Style/ArgumentsForwarding:
1127
1134
  Style/ArrayCoercion:
1128
1135
  Enabled: false
1129
1136
 
1137
+ Style/ArrayIntersect:
1138
+ Enabled: false
1139
+
1130
1140
  Style/ArrayJoin:
1131
1141
  Enabled: true
1132
1142
 
@@ -1209,6 +1219,12 @@ Style/CommentAnnotation:
1209
1219
  Style/CommentedKeyword:
1210
1220
  Enabled: false
1211
1221
 
1222
+ Style/ComparableClamp:
1223
+ Enabled: true
1224
+
1225
+ Style/ConcatArrayLiterals:
1226
+ Enabled: false
1227
+
1212
1228
  Style/ConditionalAssignment:
1213
1229
  Enabled: true
1214
1230
  EnforcedStyle: assign_to_condition
@@ -1262,6 +1278,7 @@ Style/EmptyCaseCondition:
1262
1278
 
1263
1279
  Style/EmptyElse:
1264
1280
  Enabled: true
1281
+ AllowComments: true
1265
1282
  EnforcedStyle: both
1266
1283
 
1267
1284
  Style/EmptyHeredoc:
@@ -1399,6 +1416,9 @@ Style/InlineComment:
1399
1416
  Style/InverseMethods:
1400
1417
  Enabled: false
1401
1418
 
1419
+ Style/InvertibleUnlessCondition:
1420
+ Enabled: false
1421
+
1402
1422
  Style/IpAddresses:
1403
1423
  Enabled: false
1404
1424
 
@@ -1425,6 +1445,9 @@ Style/MapCompactWithConditionalBlock:
1425
1445
  Style/MapToHash:
1426
1446
  Enabled: false
1427
1447
 
1448
+ Style/MapToSet:
1449
+ Enabled: false
1450
+
1428
1451
  Style/MethodCallWithArgsParentheses:
1429
1452
  Enabled: false
1430
1453
 
@@ -1441,6 +1464,9 @@ Style/MethodDefParentheses:
1441
1464
  Style/MinMax:
1442
1465
  Enabled: false
1443
1466
 
1467
+ Style/MinMaxComparison:
1468
+ Enabled: false
1469
+
1444
1470
  Style/MissingElse:
1445
1471
  Enabled: false
1446
1472
 
@@ -1645,6 +1671,12 @@ Style/RedundantCondition:
1645
1671
  Style/RedundantConditional:
1646
1672
  Enabled: true
1647
1673
 
1674
+ Style/RedundantConstantBase:
1675
+ Enabled: false
1676
+
1677
+ Style/RedundantDoubleSplatHashBraces:
1678
+ Enabled: true
1679
+
1648
1680
  Style/RedundantEach:
1649
1681
  Enabled: false
1650
1682
 
@@ -1703,6 +1735,9 @@ Style/RedundantStringEscape:
1703
1735
  Style/RegexpLiteral:
1704
1736
  Enabled: false
1705
1737
 
1738
+ Style/RequireOrder:
1739
+ Enabled: false
1740
+
1706
1741
  Style/RescueModifier:
1707
1742
  Enabled: true
1708
1743
 
@@ -1901,5 +1936,8 @@ Style/YodaCondition:
1901
1936
  Enabled: true
1902
1937
  EnforcedStyle: forbid_for_all_comparison_operators
1903
1938
 
1939
+ Style/YodaExpression:
1940
+ Enabled: false
1941
+
1904
1942
  Style/ZeroLengthPredicate:
1905
1943
  Enabled: false
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.19.1")
2
+ VERSION = Gem::Version.new("1.24.3")
3
3
  end
data/standard.gemspec CHANGED
@@ -19,8 +19,8 @@ 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.39.0"
23
- spec.add_dependency "rubocop-performance", "1.15.1"
22
+ spec.add_dependency "rubocop", "1.44.1"
23
+ spec.add_dependency "rubocop-performance", "1.15.2"
24
24
 
25
25
  # not semver: first three are lsp protocol version, last is patch
26
26
  spec.add_dependency "language_server-protocol", "~> 3.17.0.2"
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.19.1
4
+ version: 1.24.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Searls
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-05 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,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 1.39.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.39.0
26
+ version: 1.44.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rubocop-performance
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 1.15.1
33
+ version: 1.15.2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 1.15.1
40
+ version: 1.15.2
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: language_server-protocol
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -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,6 +116,7 @@ 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
@@ -133,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
138
  - !ruby/object:Gem::Version
134
139
  version: '0'
135
140
  requirements: []
136
- rubygems_version: 3.3.20
141
+ rubygems_version: 3.3.26
137
142
  signing_key:
138
143
  specification_version: 4
139
144
  summary: Ruby Style Guide, with linter & automatic code fixer