rucoa 0.1.0 → 0.4.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -0
  3. data/Gemfile +1 -0
  4. data/Gemfile.lock +4 -1
  5. data/README.md +7 -4
  6. data/lib/rucoa/cli.rb +2 -2
  7. data/lib/rucoa/configuration.rb +71 -0
  8. data/lib/rucoa/handler_concerns/configuration_requestable.rb +35 -0
  9. data/lib/rucoa/handler_concerns/diagnostics_publishable.rb +172 -0
  10. data/lib/rucoa/handler_concerns.rb +8 -0
  11. data/lib/rucoa/handlers/base.rb +61 -0
  12. data/lib/rucoa/handlers/initialize_handler.rb +23 -0
  13. data/lib/rucoa/handlers/initialized_handler.rb +13 -0
  14. data/lib/rucoa/handlers/text_document_code_action_handler.rb +104 -0
  15. data/lib/rucoa/handlers/text_document_did_change_handler.rb +31 -0
  16. data/lib/rucoa/handlers/text_document_did_open_handler.rb +31 -0
  17. data/lib/rucoa/handlers/text_document_document_symbol_handler.rb +241 -0
  18. data/lib/rucoa/handlers/text_document_formatting_handler.rb +64 -0
  19. data/lib/rucoa/handlers/text_document_range_formatting_handler.rb +76 -0
  20. data/lib/rucoa/handlers/text_document_selection_range_handler.rb +141 -0
  21. data/lib/rucoa/handlers/workspace_did_change_configuration_handler.rb +13 -0
  22. data/lib/rucoa/handlers.rb +17 -0
  23. data/lib/rucoa/message_reader.rb +3 -1
  24. data/lib/rucoa/message_writer.rb +3 -0
  25. data/lib/rucoa/node_concerns/name_full_qualifiable.rb +20 -0
  26. data/lib/rucoa/node_concerns.rb +7 -0
  27. data/lib/rucoa/nodes/base.rb +13 -0
  28. data/lib/rucoa/nodes/casgn_node.rb +14 -0
  29. data/lib/rucoa/nodes/class_node.rb +8 -0
  30. data/lib/rucoa/nodes/const_node.rb +12 -0
  31. data/lib/rucoa/nodes/def_node.rb +12 -0
  32. data/lib/rucoa/nodes/defs_node.rb +12 -0
  33. data/lib/rucoa/nodes/module_node.rb +21 -0
  34. data/lib/rucoa/nodes/sclass_node.rb +8 -0
  35. data/lib/rucoa/nodes/send_node.rb +17 -0
  36. data/lib/rucoa/nodes/str_node.rb +4 -0
  37. data/lib/rucoa/nodes/sym_node.rb +12 -0
  38. data/lib/rucoa/nodes.rb +9 -0
  39. data/lib/rucoa/parser_builder.rb +19 -10
  40. data/lib/rucoa/range.rb +32 -15
  41. data/lib/rucoa/rubocop_autocorrector.rb +38 -0
  42. data/lib/rucoa/rubocop_configuration_checker.rb +42 -0
  43. data/lib/rucoa/{rubocop_runner.rb → rubocop_investigator.rb} +9 -8
  44. data/lib/rucoa/server.rb +80 -97
  45. data/lib/rucoa/source.rb +2 -2
  46. data/lib/rucoa/source_store.rb +7 -1
  47. data/lib/rucoa/version.rb +1 -1
  48. data/lib/rucoa.rb +8 -4
  49. metadata +32 -5
  50. data/lib/rucoa/diagnostic_provider.rb +0 -112
  51. data/lib/rucoa/selection_range_provider.rb +0 -97
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Nodes
5
+ class ModuleNode < Base
6
+ include NodeConcerns::NameFullQualifiable
7
+
8
+ # @return [String]
9
+ def name
10
+ const_node.name
11
+ end
12
+
13
+ private
14
+
15
+ # @return [Rucoa::Nodes::ConstNode]
16
+ def const_node
17
+ children[0]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Nodes
5
+ class SclassNode < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Nodes
5
+ class SendNode < Base
6
+ # @return [Array<Rucoa::Nodes::Base>]
7
+ def arguments
8
+ children[2..]
9
+ end
10
+
11
+ # @return [String]
12
+ def name
13
+ children[1].to_s
14
+ end
15
+ end
16
+ end
17
+ end
@@ -3,6 +3,10 @@
3
3
  module Rucoa
4
4
  module Nodes
5
5
  class StrNode < Base
6
+ # @return [String]
7
+ def value
8
+ children[0]
9
+ end
6
10
  end
7
11
  end
8
12
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Nodes
5
+ class SymNode < Base
6
+ # @return [String]
7
+ def value
8
+ children[0]
9
+ end
10
+ end
11
+ end
12
+ end
data/lib/rucoa/nodes.rb CHANGED
@@ -3,6 +3,15 @@
3
3
  module Rucoa
4
4
  module Nodes
5
5
  autoload :Base, 'rucoa/nodes/base'
6
+ autoload :ClassNode, 'rucoa/nodes/class_node'
7
+ autoload :CasgnNode, 'rucoa/nodes/casgn_node'
8
+ autoload :ConstNode, 'rucoa/nodes/const_node'
9
+ autoload :DefNode, 'rucoa/nodes/def_node'
10
+ autoload :DefsNode, 'rucoa/nodes/defs_node'
11
+ autoload :ModuleNode, 'rucoa/nodes/module_node'
12
+ autoload :SclassNode, 'rucoa/nodes/sclass_node'
13
+ autoload :SendNode, 'rucoa/nodes/send_node'
6
14
  autoload :StrNode, 'rucoa/nodes/str_node'
15
+ autoload :SymNode, 'rucoa/nodes/sym_node'
7
16
  end
8
17
  end
@@ -5,24 +5,33 @@ require 'parser/current'
5
5
  module Rucoa
6
6
  class ParserBuilder < ::Parser::Builders::Default
7
7
  NODE_CLASS_BY_TYPE = {
8
- str: Nodes::StrNode
8
+ casgn: Nodes::CasgnNode,
9
+ class: Nodes::ClassNode,
10
+ const: Nodes::ConstNode,
11
+ def: Nodes::DefNode,
12
+ defs: Nodes::DefsNode,
13
+ module: Nodes::ModuleNode,
14
+ sclass: Nodes::SclassNode,
15
+ send: Nodes::SendNode,
16
+ str: Nodes::StrNode,
17
+ sym: Nodes::SymNode
9
18
  }.freeze
10
19
 
20
+ class << self
21
+ # @param type [Symbol]
22
+ # @return [Class]
23
+ def node_class_for(type)
24
+ NODE_CLASS_BY_TYPE.fetch(type, Nodes::Base)
25
+ end
26
+ end
27
+
11
28
  # @note Override.
12
29
  def n(type, children, source_map)
13
- node_class_for(type).new(
30
+ self.class.node_class_for(type).new(
14
31
  type,
15
32
  children,
16
33
  location: source_map
17
34
  )
18
35
  end
19
-
20
- private
21
-
22
- # @param type [Symbol]
23
- # @return [Class]
24
- def node_class_for(type)
25
- NODE_CLASS_BY_TYPE.fetch(type, Nodes::Base)
26
- end
27
36
  end
28
37
  end
data/lib/rucoa/range.rb CHANGED
@@ -7,32 +7,53 @@ 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)
10
+ Position.from_parser_range_beginning(range),
11
+ Position.from_parser_range_ending(range)
12
12
  )
13
13
  end
14
14
 
15
- # @param offense [RuboCop::Cop::Offense]
15
+ # @param hash [Hash]
16
16
  # @return [Rucoa::Range]
17
- def from_rubocop_offense(offense)
17
+ def from_vscode_range(hash)
18
18
  new(
19
- Position.from_parser_range_beginning(offense.location),
20
- Position.from_parser_range_ending(offense.location)
19
+ Position.from_vscode_position(hash['start']),
20
+ Position.from_vscode_position(hash['end'])
21
21
  )
22
22
  end
23
23
  end
24
24
 
25
+ # @return [Rucoa::Position]
26
+ attr_reader :beginning
27
+
28
+ # @return [Rucoa::Position]
29
+ attr_reader :ending
30
+
25
31
  # @param beginning [Rucoa::Position]
26
32
  # @param ending [Ruoca::Position]
27
- def initialize(beginning, ending)
33
+ # @param exclude_end [Boolean]
34
+ def initialize(beginning, ending, exclude_end: true)
28
35
  @beginning = beginning
29
36
  @ending = ending
37
+ @exclude_end = exclude_end
38
+ end
39
+
40
+ # @param range [Rucoa::Range]
41
+ # @return [Boolean]
42
+ def contains?(range)
43
+ copy = with_including_end
44
+ copy.include?(range.beginning) && copy.include?(range.ending)
30
45
  end
31
46
 
32
47
  # @param position [Rucoa::Position]
33
48
  # @return [Boolean]
34
49
  def include?(position)
35
- !exclude?(position)
50
+ return false if position.line > @ending.line
51
+ return false if position.line < @beginning.line
52
+ return false if position.column < @beginning.column
53
+ return false if position.column > @ending.column
54
+ return false if position.column == @ending.column && @exclude_end
55
+
56
+ true
36
57
  end
37
58
 
38
59
  # @return [Hash]
@@ -45,13 +66,9 @@ module Rucoa
45
66
 
46
67
  private
47
68
 
48
- # @param position [Rucoa::Position]
49
- # @return [Boolean]
50
- def exclude?(position)
51
- position.line > @ending.line ||
52
- position.line < @beginning.line ||
53
- (position.line == @beginning.line && position.column < @beginning.column) ||
54
- (position.line == @ending.line && position.column >= @ending.column)
69
+ # @return [Rucoa::Range]
70
+ def with_including_end
71
+ self.class.new(@beginning, @ending, exclude_end: false)
55
72
  end
56
73
  end
57
74
  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
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+
5
+ module Rucoa
6
+ class RubocopConfigurationChecker
7
+ class << self
8
+ # @return [Boolean]
9
+ def call
10
+ new.call
11
+ end
12
+ end
13
+
14
+ # @return [Boolean]
15
+ def call
16
+ rubocop_configured_for_current_directory?
17
+ end
18
+
19
+ private
20
+
21
+ # @return [Boolean]
22
+ def rubocop_configured_for_current_directory?
23
+ each_current_and_ancestor_pathname.any? do |pathname|
24
+ pathname.join('.rubocop.yml').exist?
25
+ end
26
+ end
27
+
28
+ # @return [Enumerable<Pathname>]
29
+ def each_current_and_ancestor_pathname
30
+ return to_enum(__method__) unless block_given?
31
+
32
+ pathname = ::Pathname.pwd
33
+ loop do
34
+ yield pathname
35
+ break if pathname.root?
36
+
37
+ pathname = pathname.parent
38
+ end
39
+ self
40
+ end
41
+ end
42
+ 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
@@ -2,131 +2,114 @@
2
2
 
3
3
  module Rucoa
4
4
  class Server
5
- # @param reader [IO]
6
- # @param writer [IO]
7
- def initialize(reader:, writer:)
8
- @reader = MessageReader.new(reader)
9
- @writer = MessageWriter.new(writer)
5
+ # @return [Hash{String => Class}]
6
+ METHOD_TO_HANDLER_MAP = {
7
+ 'initialize' => Handlers::InitializeHandler,
8
+ 'initialized' => Handlers::InitializedHandler,
9
+ 'textDocument/codeAction' => Handlers::TextDocumentCodeActionHandler,
10
+ 'textDocument/didChange' => Handlers::TextDocumentDidChangeHandler,
11
+ 'textDocument/didOpen' => Handlers::TextDocumentDidOpenHandler,
12
+ 'textDocument/documentSymbol' => Handlers::TextDocumentDocumentSymbolHandler,
13
+ 'textDocument/formatting' => Handlers::TextDocumentFormattingHandler,
14
+ 'textDocument/rangeFormatting' => Handlers::TextDocumentRangeFormattingHandler,
15
+ 'textDocument/selectionRange' => Handlers::TextDocumentSelectionRangeHandler,
16
+ 'workspace/didChangeConfiguration' => Handlers::WorkspaceDidChangeConfigurationHandler
17
+ }.freeze
18
+ private_constant :METHOD_TO_HANDLER_MAP
19
+
20
+ # @return [Rucoa::Configuration]
21
+ attr_reader :configuration
22
+
23
+ # @return [Rucoa::SourceStore]
24
+ attr_reader :source_store
25
+
26
+ # @param input [IO]
27
+ # @param output [IO]
28
+ def initialize(input:, output:)
29
+ @reader = MessageReader.new(input)
30
+ @writer = MessageWriter.new(output)
31
+
32
+ @configuration = Configuration.new
33
+ @server_request_id = 0
34
+ @client_response_handlers = {}
10
35
  @source_store = SourceStore.new
11
36
  end
12
37
 
13
38
  # @return [void]
14
39
  def start
15
- read do |request|
16
- result = handle(request)
17
- if result
18
- write(
19
- request: request,
20
- result: result
21
- )
22
- end
40
+ @reader.read do |request|
41
+ handle(request)
23
42
  end
24
43
  end
25
44
 
26
- private
27
-
28
- # @param request [Hash]
29
- # @return [Object]
30
- def handle(request)
31
- case request['method']
32
- when 'initialize'
33
- on_initialize(request)
34
- when 'textDocument/didChange'
35
- on_text_document_did_change(request)
36
- when 'textDocument/didOpen'
37
- on_text_document_did_open(request)
38
- when 'textDocument/selectionRange'
39
- on_text_document_selection_range(request)
45
+ # @yieldparam response [Hash]
46
+ # @param message [Hash]
47
+ # @return [void]
48
+ def write(message, &block)
49
+ if block
50
+ write_server_request(message, &block)
51
+ else
52
+ write_server_response(message)
40
53
  end
41
54
  end
42
55
 
43
- # @yieldparam request [Hash]
44
- # @return [void]
45
- def read(&block)
46
- @reader.read(&block)
56
+ # @note This method is for testing.
57
+ # @return [Array<Hash>]
58
+ def responses
59
+ io = @writer.io
60
+ io.rewind
61
+ MessageReader.new(io).read.to_a
47
62
  end
48
63
 
64
+ private
65
+
49
66
  # @param request [Hash]
50
- # @param result [Object]
51
67
  # @return [void]
52
- def write(request:, result:)
53
- @writer.write(
54
- {
55
- id: request['id'],
56
- result: result
57
- }
58
- )
68
+ def handle(request)
69
+ if request['method']
70
+ handle_client_request(request)
71
+ elsif request['id']
72
+ handle_client_response(request)
73
+ end
59
74
  end
60
75
 
61
- # @param uri [String]
76
+ # @param request [Hash]
62
77
  # @return [void]
63
- def investigate_diagnostics(uri:)
64
- diagnostics = DiagnosticProvider.call(
65
- source: @source_store.get(uri)
66
- )
67
- return if diagnostics.empty?
68
-
69
- @writer.write(
70
- method: 'textDocument/publishDiagnostics',
71
- params: {
72
- diagnostics: diagnostics,
73
- uri: uri
74
- }
78
+ def handle_client_request(request)
79
+ find_client_request_handler(request['method'])&.call(
80
+ request: request,
81
+ server: self
75
82
  )
76
83
  end
77
84
 
78
- # @param _request [Hash]
79
- # @return [Hash]
80
- def on_initialize(_request)
81
- {
82
- capabilities: {
83
- textDocumentSync: {
84
- change: 1, # Full
85
- openClose: true
86
- },
87
- selectionRangeProvider: true
88
- }
89
- }
85
+ # @param response [Hash]
86
+ # @return [void]
87
+ def handle_client_response(response)
88
+ @client_response_handlers.delete(response['id'])&.call(response)
90
89
  end
91
90
 
92
- # @param request [Hash]
93
- # @return [Array<Hash>]
94
- def on_text_document_did_change(request)
95
- uri = request.dig('params', 'textDocument', 'uri')
96
- @source_store.set(
97
- uri,
98
- request.dig('params', 'contentChanges')[0]['text']
99
- )
100
- investigate_diagnostics(uri: uri)
101
- nil
91
+ # @param request_method [String]
92
+ # @return [Class, nil]
93
+ def find_client_request_handler(request_method)
94
+ METHOD_TO_HANDLER_MAP[request_method]
102
95
  end
103
96
 
104
- # @param request [Hash]
105
- # @return [Array<Hash>]
106
- def on_text_document_did_open(request)
107
- uri = request.dig('params', 'textDocument', 'uri')
108
- @source_store.set(
109
- uri,
110
- request.dig('params', 'textDocument', 'text')
97
+ # @param message [Hash]
98
+ # @return [void]
99
+ def write_server_request(message, &block)
100
+ @writer.write(
101
+ message.merge(
102
+ id: @server_request_id
103
+ )
111
104
  )
112
- investigate_diagnostics(uri: uri)
113
- nil
105
+ @client_response_handlers[@server_request_id] = block
106
+ @server_request_id += 1
114
107
  end
115
108
 
116
- # @param request [Hash]
117
- # @return [Array<Hash>, nil]
118
- def on_text_document_selection_range(request)
119
- source = @source_store.get(
120
- request.dig('params', 'textDocument', 'uri')
121
- )
122
- return unless source
123
-
124
- request.dig('params', 'positions').filter_map do |position|
125
- SelectionRangeProvider.call(
126
- position: Position.from_vscode_position(position),
127
- source: source
128
- )
129
- end
109
+ # @param message [Hash]
110
+ # @return [void]
111
+ def write_server_response(message)
112
+ @writer.write(message)
130
113
  end
131
114
  end
132
115
  end
data/lib/rucoa/source.rb CHANGED
@@ -23,13 +23,13 @@ module Rucoa
23
23
  end
24
24
  end
25
25
 
26
- private
27
-
28
26
  # @return [Rucoa::Nodes::Base, nil]
29
27
  def root_node
30
28
  @root_node ||= Parser.call(@content)
31
29
  end
32
30
 
31
+ private
32
+
33
33
  # @return [Array<Rucoa::Nodes::Base>]
34
34
  def root_and_descendant_nodes
35
35
  return [] unless root_node
@@ -25,13 +25,19 @@ module Rucoa
25
25
  )
26
26
  end
27
27
 
28
+ # @yieldparam uri [String]
29
+ # @return [Enumerable<String>]
30
+ def each_uri(&block)
31
+ @data.each_key(&block)
32
+ end
33
+
28
34
  private
29
35
 
30
36
  # @param uri [String]
31
37
  # @return [String]
32
38
  def path_from_uri(uri)
33
39
  ::CGI.unescape(
34
- ::URI.parse(uri).path
40
+ ::URI.parse(uri).path || 'untitled'
35
41
  )
36
42
  end
37
43
  end
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.4.0'
5
5
  end
data/lib/rucoa.rb CHANGED
@@ -4,17 +4,21 @@ require_relative 'rucoa/version'
4
4
 
5
5
  module Rucoa
6
6
  autoload :Cli, 'rucoa/cli'
7
+ autoload :Configuration, 'rucoa/configuration'
7
8
  autoload :Errors, 'rucoa/errors'
8
- autoload :DiagnosticProvider, 'rucoa/diagnostic_provider'
9
+ autoload :HandlerConcerns, 'rucoa/handler_concerns'
10
+ autoload :Handlers, 'rucoa/handlers'
9
11
  autoload :MessageReader, 'rucoa/message_reader'
10
12
  autoload :MessageWriter, 'rucoa/message_writer'
13
+ autoload :NodeConcerns, 'rucoa/node_concerns'
11
14
  autoload :Nodes, 'rucoa/nodes'
12
- autoload :ParserBuilder, 'rucoa/parser_builder'
13
15
  autoload :Parser, 'rucoa/parser'
16
+ autoload :ParserBuilder, 'rucoa/parser_builder'
14
17
  autoload :Position, 'rucoa/position'
15
18
  autoload :Range, 'rucoa/range'
16
- autoload :RubocopRunner, 'rucoa/rubocop_runner'
17
- autoload :SelectionRangeProvider, 'rucoa/selection_range_provider'
19
+ autoload :RubocopAutocorrector, 'rucoa/rubocop_autocorrector'
20
+ autoload :RubocopConfigurationChecker, 'rucoa/rubocop_configuration_checker'
21
+ autoload :RubocopInvestigator, 'rucoa/rubocop_investigator'
18
22
  autoload :Server, 'rucoa/server'
19
23
  autoload :Source, 'rucoa/source'
20
24
  autoload :SourceStore, 'rucoa/source_store'