standard 1.18.1 → 1.19.0

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: 18c18f211f0cdcadb84cc05bbf54f6ea8fc40de394978df75e90ee46b7e5f378
4
- data.tar.gz: aa65d3f56f7d7cb51abe64e9473ceea973316ce1bbfd6f3af8654136b669ffbd
3
+ metadata.gz: 6db13f12aa4f1f0b9e7a2243b59de1902c323835a0a98bc80f261154ede30342
4
+ data.tar.gz: ff4117589900989c5e6d80b01845ce0572a3351fb0be0a2d5cc4801d744db971
5
5
  SHA512:
6
- metadata.gz: 27903c69ab28846399bff4716b3bd0894ced1870fe9ebf8ded2178f5b5092f2c260b7a21b835bec214ac318dc3e278d1fbc003777f78788428311a40f9ae9b47
7
- data.tar.gz: 36011dac9b213b11ffd7a924482aef7d12c34171a0e5527cbc0bbac71098fb027fd01a48b7b87089a384d19a950eff9f182c7bb77ac7261cad83e608c73d0600
6
+ metadata.gz: a02ed1dcdbc8ae45c450e886b5c1a7869ef5cb905e4f2f606b3a93022284031850ef6405022898dcc552c707e75759cf79cceaccf6da0846d14bd5a4cfb55ca0
7
+ data.tar.gz: e04163746057abfac81ef9dfdb219b21f6a95ecc02e903fe98fc2f7ed537a6319e75bb9b740ce930c4b57762b2ab65e80a930e5e2358fc56ab383538a0a272b8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.19.0
4
+
5
+ * Add a language server protocol (LSP) server via the new `standardrb --lsp`
6
+ command line mode. All credit to [@will](https://github.com/)!
7
+ [#475](https://github.com/testdouble/standard/pull/475)
8
+
3
9
  ## 1.18.1
4
10
 
5
11
  * Update rubocop-performance from 1.15.0 to [1.15.1](https://github.com/rubocop/rubocop-performance/releases/tag/v1.15.1)
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- standard (1.18.1)
4
+ standard (1.19.0)
5
+ language_server-protocol (= 3.17.0.1)
5
6
  rubocop (= 1.39.0)
6
7
  rubocop-performance (= 1.15.1)
7
8
 
@@ -13,10 +14,11 @@ GEM
13
14
  docile (1.4.0)
14
15
  gimme (0.5.0)
15
16
  json (2.6.2)
17
+ language_server-protocol (3.17.0.1)
16
18
  method_source (1.0.0)
17
19
  minitest (5.16.3)
18
20
  parallel (1.22.1)
19
- parser (3.1.2.1)
21
+ parser (3.1.3.0)
20
22
  ast (~> 2.4.1)
21
23
  pry (0.14.1)
22
24
  coderay (~> 1.1)
@@ -35,7 +37,7 @@ GEM
35
37
  rubocop-ast (>= 1.23.0, < 2.0)
36
38
  ruby-progressbar (~> 1.7)
37
39
  unicode-display_width (>= 1.4.0, < 3.0)
38
- rubocop-ast (1.23.0)
40
+ rubocop-ast (1.24.0)
39
41
  parser (>= 3.1.1.0)
40
42
  rubocop-performance (1.15.1)
41
43
  rubocop (>= 1.7.0, < 2.0)
data/README.md CHANGED
@@ -432,12 +432,32 @@ information.
432
432
  ## How do I run Standard in my editor?
433
433
 
434
434
  It can be very handy to know about failures while editing to shorten the
435
- feedback loop. Some editors support asynchronously running linters.
435
+ feedback loop.
436
+
437
+ ### Language Server Protocol support
438
+
439
+ To provide immediate feedback of Standard violations and support autofixing
440
+ of your code while avoiding the performance cost of starting and stopping the
441
+ `standardrb` binary repeatedly, Standard Ruby ships with a built-in [Language
442
+ Server Protocol](https://microsoft.github.io/language-server-protocol/) server,
443
+ which is powered by the [language_server-protocol
444
+ gem](https://github.com/mtsmfm/language_server-protocol-ruby) and can be
445
+ activated from the command line with the `--lsp` flag.
446
+
447
+ Most likely, you'd instantiate this server indirectly in your editor's
448
+ configuration, as can be demonstrated easily with
449
+ [neovim](https://github.com/testdouble/standard/wiki/IDE:-neovim).
450
+ Theoretically, this feature could be leveraged by a purpose-built editor plugin
451
+ to performantly format and fix your code. (If you're looking for a project, we'd
452
+ love to see one created for VS Code!)
453
+
454
+ ### Editor-specific guides
436
455
 
437
456
  - [Atom](https://github.com/testdouble/standard/wiki/IDE:-Atom)
438
457
  - [emacs (via flycheck)](https://github.com/julianrubisch/flycheck-standardrb)
439
458
  - [RubyMine](https://www.jetbrains.com/help/ruby/rubocop.html#disable_rubocop)
440
459
  - [vim (via ALE)](https://github.com/testdouble/standard/wiki/IDE:-vim)
460
+ - [neovim (via LSP)](https://github.com/testdouble/standard/wiki/IDE:-neovim)
441
461
  - [VS Code](https://github.com/testdouble/standard/wiki/IDE:-vscode)
442
462
 
443
463
  ## Why aren't `frozen_string_literal: true` magic comments enforced?
@@ -32,7 +32,7 @@ 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: (todo_yaml["ignore"] || []).map { |f| (Hash === f) ? f.keys.first : f }
36
36
  }
37
37
  end
38
38
 
@@ -0,0 +1,144 @@
1
+ require "language_server-protocol"
2
+ require_relative "standardizer"
3
+
4
+ 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}" },
36
+
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
+ }
65
+ end
66
+
67
+ def start
68
+ reader.read do |request|
69
+ method = request[:method]
70
+ if (subscriber = subscribers[method])
71
+ subscriber.call(request)
72
+ else
73
+ logger.puts "unknown method: #{method}"
74
+ end
75
+ rescue => e
76
+ logger.puts "error #{e.class} #{e.message[0..100]}"
77
+ logger.puts e.backtrace.inspect
78
+ end
79
+ 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
+ end
143
+ end
144
+ end
@@ -0,0 +1,53 @@
1
+ require_relative "../runners/rubocop"
2
+ require "tempfile"
3
+
4
+ module Standard
5
+ module LSP
6
+ class Standardizer
7
+ def initialize(config)
8
+ @template_options = config
9
+ @runner = Standard::Runners::Rubocop.new
10
+ end
11
+
12
+ def format(text)
13
+ run_standard(text, format: true)
14
+ end
15
+
16
+ def offenses(text)
17
+ results = run_standard(text, format: false)
18
+ JSON.parse(results, symbolize_names: true).dig(:files, 0, :offenses)
19
+ end
20
+
21
+ private
22
+
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}
37
+ else
38
+ {autocorrect: false, formatters: [["json"]], parallel: true, todo_file: nil, todo_ignore_files: [], format: "json"}
39
+ end
40
+ Standard::Config.new(@template_options.runner, [file], o, @template_options.rubocop_config_store)
41
+ end
42
+
43
+ def capture_rubocop_stdout(config)
44
+ redir = StringIO.new
45
+ $stdout = redir
46
+ @runner.call(config)
47
+ redir.string
48
+ ensure
49
+ $stdout = STDOUT
50
+ end
51
+ end
52
+ end
53
+ end
@@ -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"].include?(flag)
23
+ ["--generate-todo", "--fix", "--no-fix", "--version", "-v", "--help", "-h", "--lsp"].include?(flag)
24
24
  end
25
25
  end
26
26
 
@@ -41,6 +41,8 @@ module Standard
41
41
  :version
42
42
  elsif (argv & ["--generate-todo"]).any?
43
43
  :genignore
44
+ elsif (argv & ["--lsp"]).any?
45
+ :lsp
44
46
  else
45
47
  :rubocop
46
48
  end
@@ -4,8 +4,8 @@ module Standard
4
4
  module Runners
5
5
  class Help
6
6
  def call(config)
7
- puts <<-MESSAGE.gsub(/^ {10}/, "")
8
- Usage: standardrb [--fix] [-vh] [--format <name>] [--] [FILE]...
7
+ puts <<~MESSAGE
8
+ Usage: standardrb [--fix] [--lsp] [-vh] [--format <name>] [--] [FILE]...
9
9
 
10
10
  Options:
11
11
 
@@ -13,6 +13,7 @@ module Standard
13
13
  --no-fix Do not automatically fix failures
14
14
  --format <name> Format output with any RuboCop formatter (e.g. "json")
15
15
  --generate-todo Create a .standard_todo.yml that lists all the files that contain errors
16
+ --lsp Start a LSP server listening on STDIN
16
17
  -v, --version Print the version of Standard
17
18
  -h, --help Print this message
18
19
  FILE Files to lint [default: ./]
@@ -0,0 +1,12 @@
1
+ require_relative "../lsp/server"
2
+
3
+ module Standard
4
+ module Runners
5
+ class Lsp
6
+ def call(config)
7
+ standardizer = Standard::LSP::Standardizer.new(config)
8
+ Standard::LSP::Server.start(standardizer)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,3 +1,3 @@
1
1
  module Standard
2
- VERSION = Gem::Version.new("1.18.1")
2
+ VERSION = Gem::Version.new("1.19.0")
3
3
  end
data/standard.gemspec CHANGED
@@ -21,4 +21,7 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.add_dependency "rubocop", "1.39.0"
23
23
  spec.add_dependency "rubocop-performance", "1.15.1"
24
+
25
+ # not semver: first three are lsp protocol version, last is patch
26
+ spec.add_dependency "language_server-protocol", "3.17.0.1"
24
27
  end
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.18.1
4
+ version: 1.19.0
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: 2022-11-21 00:00:00.000000000 Z
11
+ date: 2022-12-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -38,7 +38,21 @@ dependencies:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
40
  version: 1.15.1
41
- description:
41
+ - !ruby/object:Gem::Dependency
42
+ name: language_server-protocol
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 3.17.0.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 3.17.0.1
55
+ description:
42
56
  email:
43
57
  - searls@gmail.com
44
58
  executables:
@@ -87,6 +101,8 @@ files:
87
101
  - lib/standard/formatter.rb
88
102
  - lib/standard/loads_runner.rb
89
103
  - lib/standard/loads_yaml_config.rb
104
+ - lib/standard/lsp/server.rb
105
+ - lib/standard/lsp/standardizer.rb
90
106
  - lib/standard/merges_settings.rb
91
107
  - lib/standard/parses_cli_option.rb
92
108
  - lib/standard/railtie.rb
@@ -94,6 +110,7 @@ files:
94
110
  - lib/standard/rubocop/ext.rb
95
111
  - lib/standard/runners/genignore.rb
96
112
  - lib/standard/runners/help.rb
113
+ - lib/standard/runners/lsp.rb
97
114
  - lib/standard/runners/rubocop.rb
98
115
  - lib/standard/runners/version.rb
99
116
  - lib/standard/version.rb
@@ -101,7 +118,7 @@ files:
101
118
  homepage: https://github.com/testdouble/standard
102
119
  licenses: []
103
120
  metadata: {}
104
- post_install_message:
121
+ post_install_message:
105
122
  rdoc_options: []
106
123
  require_paths:
107
124
  - lib
@@ -116,8 +133,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
133
  - !ruby/object:Gem::Version
117
134
  version: '0'
118
135
  requirements: []
119
- rubygems_version: 3.1.6
120
- signing_key:
136
+ rubygems_version: 3.3.7
137
+ signing_key:
121
138
  specification_version: 4
122
139
  summary: Ruby Style Guide, with linter & automatic code fixer
123
140
  test_files: []