rucoa 0.1.0 → 0.2.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: 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