rucoa 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ceb1c198d4cc4deef7d9b122f4d50ca77c4c9e57713b96069278d7b827f9ba05
4
- data.tar.gz: 7d826934f96b44bfbde2f0b75670b4d6678c665c6651f5ece3b463150dbd985f
3
+ metadata.gz: 76603729adc41973a6692fa067867eefa24f1724afa9d972f224dc34338cf925
4
+ data.tar.gz: d5c4a1c8001121bafbb9a768f8c5d91d6ae66a737e8b415572756772450d9ba1
5
5
  SHA512:
6
- metadata.gz: 76179fc8d424e84b810ed46135a9a39f403e4799dece8743bf44c48adf478b0467c0774c6827312868c5e4f41baa98b161448e3442e24f9c04b5836d0b390982
7
- data.tar.gz: 9ffa4f84e54f7592cdb669934778b1a2d0e23cc6b3ea356a189e62daf935bb54dc69f10095bddd8a93f797ec7b5e308cecf9923d78154c2c1634798b344cd512
6
+ metadata.gz: de17db3774a60396fa40a307cf1d34c73db06b55afa9a3e2e2cae2085f1aeef07e459ace8149cf668e0cee47ca940a29066c963e599633e6c647dee31c36b1a0
7
+ data.tar.gz: c309425209a6146a1ea4031f57b752bcef0ce7fe25f965704d6a334194000e8aa96a94aaad992bed04ddc200397ee9790349e0d5e94998703eaeafa4fd8f3558
data/.rubocop.yml CHANGED
@@ -2,6 +2,7 @@ require:
2
2
  - rubocop-performance
3
3
  - rubocop-rake
4
4
  - rubocop-rspec
5
+ - sevencop
5
6
 
6
7
  AllCops:
7
8
  NewCops: enable
@@ -25,6 +26,9 @@ RSpec/MultipleMemoizedHelpers:
25
26
  RSpec/NamedSubject:
26
27
  Enabled: false
27
28
 
29
+ Sevencop/HashLiteralOrder:
30
+ Enabled: true
31
+
28
32
  Style/Documentation:
29
33
  Enabled: false
30
34
 
data/Gemfile CHANGED
@@ -10,3 +10,4 @@ gem 'rubocop'
10
10
  gem 'rubocop-performance'
11
11
  gem 'rubocop-rake'
12
12
  gem 'rubocop-rspec'
13
+ gem 'sevencop'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rucoa (0.1.0)
4
+ rucoa (0.2.0)
5
5
  parser
6
6
  rubocop
7
7
 
@@ -51,6 +51,8 @@ GEM
51
51
  rubocop-rspec (2.12.1)
52
52
  rubocop (~> 1.31)
53
53
  ruby-progressbar (1.11.0)
54
+ sevencop (0.9.3)
55
+ rubocop
54
56
  unicode-display_width (2.2.0)
55
57
 
56
58
  PLATFORMS
@@ -64,6 +66,7 @@ DEPENDENCIES
64
66
  rubocop-rake
65
67
  rubocop-rspec
66
68
  rucoa!
69
+ sevencop
67
70
 
68
71
  BUNDLED WITH
69
72
  2.3.21
data/README.md CHANGED
@@ -33,12 +33,13 @@ gem install rucoa
33
33
  ## Features
34
34
 
35
35
  - Diagnostics
36
+ - Formatting
37
+ - Quick Fix
36
38
  - Selection Ranges (experimental)
37
39
 
38
40
  ### Coming soon
39
41
 
40
42
  - Completion
41
43
  - Documentation
42
- - Formatting
43
44
  - Highlight
44
45
  - Go to Definition
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ class CodeActionProvider
5
+ class << self
6
+ # @param diagnostics [Array<Hash>]
7
+ # @return [Array<Hash>]
8
+ def call(diagnostics:)
9
+ new(diagnostics: diagnostics).call
10
+ end
11
+ end
12
+
13
+ # @param diagnostics [Array<Hash>]
14
+ def initialize(diagnostics:)
15
+ @diagnostics = diagnostics
16
+ end
17
+
18
+ # @return [Array<Hash>]
19
+ def call
20
+ correctable_diagnostics.map do |diagnostic|
21
+ DiagnosticToCodeActionMapper.call(diagnostic)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ # @return [Array<Hash>]
28
+ def correctable_diagnostics
29
+ @diagnostics.select do |diagnostic|
30
+ diagnostic.dig('data', 'edits')
31
+ end
32
+ end
33
+
34
+ class DiagnosticToCodeActionMapper
35
+ class << self
36
+ # @param diagnostic [Hash]
37
+ # @return [Hash]
38
+ def call(diagnostic)
39
+ new(diagnostic).call
40
+ end
41
+ end
42
+
43
+ # @param diagnostic [Hash]
44
+ def initialize(diagnostic)
45
+ @diagnostic = diagnostic
46
+ end
47
+
48
+ # @return [Hash]
49
+ def call
50
+ {
51
+ diagnostics: diagnostics,
52
+ edit: edit,
53
+ isPreferred: preferred?,
54
+ kind: kind,
55
+ title: title
56
+ }
57
+ end
58
+
59
+ private
60
+
61
+ # @return [Hash]
62
+ def edit
63
+ {
64
+ documentChanges: [
65
+ {
66
+ edits: @diagnostic.dig('data', 'edits'),
67
+ textDocument: {
68
+ uri: @diagnostic.dig('data', 'uri'),
69
+ version: nil
70
+ }
71
+ }
72
+ ]
73
+ }
74
+ end
75
+
76
+ # @return [String]
77
+ def cop_name
78
+ @diagnostic.dig('data', 'cop_name')
79
+ end
80
+
81
+ # @return [Array]
82
+ def diagnostics
83
+ [@diagnostic]
84
+ end
85
+
86
+ # @return [Boolean]
87
+ def preferred?
88
+ true
89
+ end
90
+
91
+ # @return [String]
92
+ def kind
93
+ 'quickfix'
94
+ end
95
+
96
+ # @return [String]
97
+ def title
98
+ "Autocorrect #{cop_name}"
99
+ end
100
+ end
101
+ end
102
+ end
@@ -1,22 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pathname'
4
+
3
5
  module Rucoa
4
6
  class DiagnosticProvider
5
7
  # @param source [Rucoa::Source]
8
+ # @param uri [String]
6
9
  # @return [Array<Hash>]
7
- def self.call(source:)
8
- new(source: source).call
10
+ def self.call(source:, uri:)
11
+ new(
12
+ source: source,
13
+ uri: uri
14
+ ).call
9
15
  end
10
16
 
11
17
  # @param source [Rucoa::Source]
12
- def initialize(source:)
18
+ # @param uri [String]
19
+ def initialize(source:, uri:)
13
20
  @source = source
21
+ @uri = uri
14
22
  end
15
23
 
16
24
  # @return [Array<Hash>]
17
25
  def call
26
+ return [] unless rubocop_configured?
27
+
18
28
  offenses.map do |offense|
19
- OffenseToDiagnosticMapper.call(offense: offense)
29
+ OffenseToDiagnosticMapper.call(
30
+ offense,
31
+ uri: @uri
32
+ )
20
33
  end
21
34
  end
22
35
 
@@ -24,7 +37,27 @@ module Rucoa
24
37
 
25
38
  # @return [Array<RuboCop::Cop::Offense>]
26
39
  def offenses
27
- RubocopRunner.call(path: @source.path)
40
+ RubocopInvestigator.call(source: @source)
41
+ end
42
+
43
+ # @return [Boolean]
44
+ def rubocop_configured?
45
+ each_ancestor_pathname.any? do |pathname|
46
+ pathname.join('.rubocop.yml').exist?
47
+ end
48
+ end
49
+
50
+ # @return [Enumerable<Pathname>]
51
+ def each_ancestor_pathname
52
+ return to_enum(__method__) unless block_given?
53
+
54
+ pathname = ::Pathname.new(@source.path)
55
+ loop do
56
+ pathname = pathname.parent
57
+ yield pathname
58
+ break if pathname.root?
59
+ end
60
+ self
28
61
  end
29
62
 
30
63
  class OffenseToDiagnosticMapper
@@ -48,15 +81,18 @@ module Rucoa
48
81
 
49
82
  class << self
50
83
  # @param offense [RuboCop::Cop::Offense]
84
+ # @param uri [String]
51
85
  # @return [Hash]
52
- def call(offense:)
53
- new(offense: offense).call
86
+ def call(offense, uri:)
87
+ new(offense, uri: uri).call
54
88
  end
55
89
  end
56
90
 
57
91
  # @param offense [RuboCop::Cop::Offense]
58
- def initialize(offense:)
92
+ # @param uri [String]
93
+ def initialize(offense, uri:)
59
94
  @offense = offense
95
+ @uri = uri
60
96
  end
61
97
 
62
98
  # @return [Hash]
@@ -82,12 +118,23 @@ module Rucoa
82
118
  def data
83
119
  {
84
120
  cop_name: @offense.cop_name,
85
- correctable: @offense.correctable?,
121
+ edits: edits,
86
122
  path: @offense.location.source_buffer.name,
87
- range: range
123
+ range: range,
124
+ uri: @uri
88
125
  }
89
126
  end
90
127
 
128
+ # @return [Array<Hash>, nil]
129
+ def edits
130
+ @offense.corrector&.as_replacements&.map do |range, replacement|
131
+ {
132
+ newText: replacement,
133
+ range: Range.from_parser_range(range).to_vscode_range
134
+ }
135
+ end
136
+ end
137
+
91
138
  # @return [String]
92
139
  def message
93
140
  @offense.message.delete_prefix("#{@offense.cop_name}: ")
@@ -95,7 +142,7 @@ module Rucoa
95
142
 
96
143
  # @return [Hash]
97
144
  def range
98
- Range.from_rubocop_offense(@offense).to_vscode_range
145
+ Range.from_parser_range(@offense.location).to_vscode_range
99
146
  end
100
147
 
101
148
  # @return [Integer]
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ class FormattingProvider
5
+ class << self
6
+ # @param source [Rucoa::Source]
7
+ # @return [Array<Hash>]
8
+ def call(source:)
9
+ new(source: source).call
10
+ end
11
+ end
12
+
13
+ # @param source [Rucoa::Source]
14
+ def initialize(source:)
15
+ @source = source
16
+ end
17
+
18
+ # @return [Array<Hash>]
19
+ def call
20
+ [text_edit]
21
+ end
22
+
23
+ private
24
+
25
+ # @return [Hash]
26
+ def text_edit
27
+ {
28
+ newText: new_text,
29
+ range: range
30
+ }
31
+ end
32
+
33
+ # @return [String]
34
+ def new_text
35
+ RubocopAutocorrector.call(source: @source)
36
+ end
37
+
38
+ # @return [Hash]
39
+ def range
40
+ Range.new(
41
+ Position.new(
42
+ column: 0,
43
+ line: 1
44
+ ),
45
+ Position.new(
46
+ column: @source.content.lines.last.length,
47
+ line: @source.content.lines.count + 1
48
+ )
49
+ ).to_vscode_range
50
+ end
51
+ end
52
+ end
data/lib/rucoa/range.rb CHANGED
@@ -7,17 +7,8 @@ module Rucoa
7
7
  # @return [Rucoa::Range]
8
8
  def from_parser_range(range)
9
9
  new(
10
- Position.from_parser_range_beginning(range.begin),
11
- Position.from_parser_range_beginning(range.end)
12
- )
13
- end
14
-
15
- # @param offense [RuboCop::Cop::Offense]
16
- # @return [Rucoa::Range]
17
- def from_rubocop_offense(offense)
18
- new(
19
- Position.from_parser_range_beginning(offense.location),
20
- Position.from_parser_range_ending(offense.location)
10
+ Position.from_parser_range_beginning(range),
11
+ Position.from_parser_range_ending(range)
21
12
  )
22
13
  end
23
14
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+
5
+ module Rucoa
6
+ class RubocopAutocorrector < ::RuboCop::Runner
7
+ class << self
8
+ # @param source [Rucoa::Source]
9
+ # @return [String]
10
+ def call(source:)
11
+ new(source: source).call
12
+ end
13
+ end
14
+
15
+ # @param source [Rucoa::Source]
16
+ def initialize(source:)
17
+ @source = source
18
+ super(
19
+ ::RuboCop::Options.new.parse(
20
+ %w[
21
+ --stderr
22
+ --force-exclusion
23
+ --format RuboCop::Formatter::BaseFormatter
24
+ -A
25
+ ]
26
+ ).first,
27
+ ::RuboCop::ConfigStore.new
28
+ )
29
+ end
30
+
31
+ # @return [String]
32
+ def call
33
+ @options[:stdin] = @source.content
34
+ run([@source.path])
35
+ @options[:stdin]
36
+ end
37
+ end
38
+ end
@@ -3,18 +3,18 @@
3
3
  require 'rubocop'
4
4
 
5
5
  module Rucoa
6
- class RubocopRunner < ::RuboCop::Runner
6
+ class RubocopInvestigator < ::RuboCop::Runner
7
7
  class << self
8
- # @param path [String]
8
+ # @param source [Rucoa::Source]
9
9
  # @return [Array<RuboCop::Cop::Offense>]
10
- def call(path:)
11
- new(path: path).call
10
+ def call(source:)
11
+ new(source: source).call
12
12
  end
13
13
  end
14
14
 
15
- # @param path [String]
16
- def initialize(path:)
17
- @path = path
15
+ # @param source [Rucoa::Source]
16
+ def initialize(source:)
17
+ @source = source
18
18
  @offenses = []
19
19
  super(
20
20
  ::RuboCop::Options.new.parse(
@@ -30,7 +30,8 @@ module Rucoa
30
30
 
31
31
  # @return [Array<RuboCop::Cop::Offense>]
32
32
  def call
33
- run([@path])
33
+ @options[:stdin] = @source.content
34
+ run([@source.path])
34
35
  @offenses
35
36
  end
36
37
 
data/lib/rucoa/server.rb CHANGED
@@ -31,10 +31,14 @@ module Rucoa
31
31
  case request['method']
32
32
  when 'initialize'
33
33
  on_initialize(request)
34
+ when 'textDocument/codeAction'
35
+ on_text_document_code_action(request)
34
36
  when 'textDocument/didChange'
35
37
  on_text_document_did_change(request)
36
38
  when 'textDocument/didOpen'
37
39
  on_text_document_did_open(request)
40
+ when 'textDocument/formatting'
41
+ on_text_document_formatting(request)
38
42
  when 'textDocument/selectionRange'
39
43
  on_text_document_selection_range(request)
40
44
  end
@@ -61,15 +65,13 @@ module Rucoa
61
65
  # @param uri [String]
62
66
  # @return [void]
63
67
  def investigate_diagnostics(uri:)
64
- diagnostics = DiagnosticProvider.call(
65
- source: @source_store.get(uri)
66
- )
67
- return if diagnostics.empty?
68
-
69
68
  @writer.write(
70
69
  method: 'textDocument/publishDiagnostics',
71
70
  params: {
72
- diagnostics: diagnostics,
71
+ diagnostics: DiagnosticProvider.call(
72
+ source: @source_store.get(uri),
73
+ uri: uri
74
+ ),
73
75
  uri: uri
74
76
  }
75
77
  )
@@ -80,17 +82,30 @@ module Rucoa
80
82
  def on_initialize(_request)
81
83
  {
82
84
  capabilities: {
85
+ codeActionProvider: true,
86
+ documentFormattingProvider: true,
87
+ selectionRangeProvider: true,
83
88
  textDocumentSync: {
84
89
  change: 1, # Full
85
90
  openClose: true
86
- },
87
- selectionRangeProvider: true
91
+ }
88
92
  }
89
93
  }
90
94
  end
91
95
 
92
96
  # @param request [Hash]
93
- # @return [Array<Hash>]
97
+ # @return [Array<Hash>, nil]
98
+ def on_text_document_code_action(request)
99
+ diagnostics = request.dig('params', 'context', 'diagnostics')
100
+ return unless diagnostics
101
+
102
+ CodeActionProvider.call(
103
+ diagnostics: diagnostics
104
+ )
105
+ end
106
+
107
+ # @param request [Hash]
108
+ # @return [nil]
94
109
  def on_text_document_did_change(request)
95
110
  uri = request.dig('params', 'textDocument', 'uri')
96
111
  @source_store.set(
@@ -102,7 +117,7 @@ module Rucoa
102
117
  end
103
118
 
104
119
  # @param request [Hash]
105
- # @return [Array<Hash>]
120
+ # @return [nil]
106
121
  def on_text_document_did_open(request)
107
122
  uri = request.dig('params', 'textDocument', 'uri')
108
123
  @source_store.set(
@@ -113,6 +128,18 @@ module Rucoa
113
128
  nil
114
129
  end
115
130
 
131
+ # @param request [Hash]
132
+ # @return [Array<Hash>, nil]
133
+ def on_text_document_formatting(request)
134
+ uri = request.dig('params', 'textDocument', 'uri')
135
+ source = @source_store.get(uri)
136
+ return unless source
137
+
138
+ FormattingProvider.call(
139
+ source: source
140
+ )
141
+ end
142
+
116
143
  # @param request [Hash]
117
144
  # @return [Array<Hash>, nil]
118
145
  def on_text_document_selection_range(request)
data/lib/rucoa/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rucoa
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/rucoa.rb CHANGED
@@ -4,16 +4,19 @@ require_relative 'rucoa/version'
4
4
 
5
5
  module Rucoa
6
6
  autoload :Cli, 'rucoa/cli'
7
- autoload :Errors, 'rucoa/errors'
7
+ autoload :CodeActionProvider, 'rucoa/code_action_provider'
8
8
  autoload :DiagnosticProvider, 'rucoa/diagnostic_provider'
9
+ autoload :Errors, 'rucoa/errors'
10
+ autoload :FormattingProvider, 'rucoa/formatting_provider'
9
11
  autoload :MessageReader, 'rucoa/message_reader'
10
12
  autoload :MessageWriter, 'rucoa/message_writer'
11
13
  autoload :Nodes, 'rucoa/nodes'
12
- autoload :ParserBuilder, 'rucoa/parser_builder'
13
14
  autoload :Parser, 'rucoa/parser'
15
+ autoload :ParserBuilder, 'rucoa/parser_builder'
14
16
  autoload :Position, 'rucoa/position'
15
17
  autoload :Range, 'rucoa/range'
16
- autoload :RubocopRunner, 'rucoa/rubocop_runner'
18
+ autoload :RubocopAutocorrector, 'rucoa/rubocop_autocorrector'
19
+ autoload :RubocopInvestigator, 'rucoa/rubocop_investigator'
17
20
  autoload :SelectionRangeProvider, 'rucoa/selection_range_provider'
18
21
  autoload :Server, 'rucoa/server'
19
22
  autoload :Source, 'rucoa/source'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rucoa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryo Nakamura
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-03 00:00:00.000000000 Z
11
+ date: 2022-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parser
@@ -57,8 +57,10 @@ files:
57
57
  - exe/rucoa
58
58
  - lib/rucoa.rb
59
59
  - lib/rucoa/cli.rb
60
+ - lib/rucoa/code_action_provider.rb
60
61
  - lib/rucoa/diagnostic_provider.rb
61
62
  - lib/rucoa/errors.rb
63
+ - lib/rucoa/formatting_provider.rb
62
64
  - lib/rucoa/message_reader.rb
63
65
  - lib/rucoa/message_writer.rb
64
66
  - lib/rucoa/nodes.rb
@@ -68,7 +70,8 @@ files:
68
70
  - lib/rucoa/parser_builder.rb
69
71
  - lib/rucoa/position.rb
70
72
  - lib/rucoa/range.rb
71
- - lib/rucoa/rubocop_runner.rb
73
+ - lib/rucoa/rubocop_autocorrector.rb
74
+ - lib/rucoa/rubocop_investigator.rb
72
75
  - lib/rucoa/selection_range_provider.rb
73
76
  - lib/rucoa/server.rb
74
77
  - lib/rucoa/source.rb