syntax_tree 2.3.0 → 2.4.1

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.
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ class Formatter
5
+ # This module overrides the quote method on the formatter to use single
6
+ # quotes for everything instead of double quotes.
7
+ module SingleQuotes
8
+ def quote
9
+ "'"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -41,11 +41,12 @@ module SyntaxTree
41
41
 
42
42
  # If the node has a stree-ignore comment right before it, then we're
43
43
  # going to just print out the node as it was seen in the source.
44
- if leading.last&.ignore?
45
- doc = text(source[node.location.start_char...node.location.end_char])
46
- else
47
- doc = node.format(self)
48
- end
44
+ doc =
45
+ if leading.last&.ignore?
46
+ text(source[node.location.start_char...node.location.end_char])
47
+ else
48
+ node.format(self)
49
+ end
49
50
 
50
51
  # Print all comments that were found after the node.
51
52
  trailing.each do |comment|
@@ -2,14 +2,79 @@
2
2
 
3
3
  module SyntaxTree
4
4
  class LanguageServer
5
- class InlayHints
6
- attr_reader :before, :after
5
+ # This class provides inlay hints for the language server. It is loosely
6
+ # designed around the LSP spec, but existed before the spec was finalized so
7
+ # is a little different for now.
8
+ #
9
+ # For more information, see the spec here:
10
+ # https://github.com/microsoft/language-server-protocol/issues/956.
11
+ #
12
+ class InlayHints < Visitor
13
+ attr_reader :stack, :before, :after
7
14
 
8
15
  def initialize
16
+ @stack = []
9
17
  @before = Hash.new { |hash, key| hash[key] = +"" }
10
18
  @after = Hash.new { |hash, key| hash[key] = +"" }
11
19
  end
12
20
 
21
+ def visit(node)
22
+ stack << node
23
+ result = super
24
+ stack.pop
25
+ result
26
+ end
27
+
28
+ # Adds parentheses around assignments contained within the default values
29
+ # of parameters. For example,
30
+ #
31
+ # def foo(a = b = c)
32
+ # end
33
+ #
34
+ # becomes
35
+ #
36
+ # def foo(a = ₍b = c₎)
37
+ # end
38
+ #
39
+ def visit_assign(node)
40
+ parentheses(node.location) if stack[-2].is_a?(Params)
41
+ end
42
+
43
+ # Adds parentheses around binary expressions to make it clear which
44
+ # subexpression will be evaluated first. For example,
45
+ #
46
+ # a + b * c
47
+ #
48
+ # becomes
49
+ #
50
+ # a + ₍b * c₎
51
+ #
52
+ def visit_binary(node)
53
+ case stack[-2]
54
+ in Assign | OpAssign
55
+ parentheses(node.location)
56
+ in Binary[operator: operator] if operator != node.operator
57
+ parentheses(node.location)
58
+ else
59
+ end
60
+ end
61
+
62
+ # Adds parentheses around ternary operators contained within certain
63
+ # expressions where it could be confusing which subexpression will get
64
+ # evaluated first. For example,
65
+ #
66
+ # a ? b : c ? d : e
67
+ #
68
+ # becomes
69
+ #
70
+ # a ? b : ₍c ? d : e₎
71
+ #
72
+ def visit_if_op(node)
73
+ if stack[-2] in Assign | Binary | IfOp | OpAssign
74
+ parentheses(node.location)
75
+ end
76
+ end
77
+
13
78
  # Adds the implicitly rescued StandardError into a bare rescue clause. For
14
79
  # example,
15
80
  #
@@ -23,54 +88,38 @@ module SyntaxTree
23
88
  # rescue StandardError
24
89
  # end
25
90
  #
26
- def bare_rescue(location)
27
- after[location.start_char + "rescue".length] << " StandardError"
91
+ def visit_rescue(node)
92
+ if node.exception.nil?
93
+ after[node.location.start_char + "rescue".length] << " StandardError"
94
+ end
28
95
  end
29
96
 
30
- # Adds implicit parentheses around certain expressions to make it clear
31
- # which subexpression will be evaluated first. For example,
97
+ # Adds parentheses around unary statements using the - operator that are
98
+ # contained within Binary nodes. For example,
32
99
  #
33
- # a + b * c
100
+ # -a + b
34
101
  #
35
102
  # becomes
36
103
  #
37
- # a + b * c₎
104
+ # ₍-a + b
38
105
  #
39
- def precedence_parentheses(location)
40
- before[location.start_char] << ""
41
- after[location.end_char] << "₎"
106
+ def visit_unary(node)
107
+ if stack[-2].is_a?(Binary) && (node.operator == "-")
108
+ parentheses(node.location)
109
+ end
42
110
  end
43
111
 
44
112
  def self.find(program)
45
- inlay_hints = new
46
- queue = [[nil, program]]
47
-
48
- until queue.empty?
49
- parent_node, child_node = queue.shift
50
-
51
- child_node.child_nodes.each do |grand_child_node|
52
- queue << [child_node, grand_child_node] if grand_child_node
53
- end
113
+ visitor = new
114
+ visitor.visit(program)
115
+ visitor
116
+ end
54
117
 
55
- case [parent_node, child_node]
56
- in _, Rescue[exception: nil, location:]
57
- inlay_hints.bare_rescue(location)
58
- in Assign | Binary | IfOp | OpAssign, IfOp[location:]
59
- inlay_hints.precedence_parentheses(location)
60
- in Assign | OpAssign, Binary[location:]
61
- inlay_hints.precedence_parentheses(location)
62
- in Binary[operator: parent_oper], Binary[operator: child_oper, location:] if parent_oper != child_oper
63
- inlay_hints.precedence_parentheses(location)
64
- in Binary, Unary[operator: "-", location:]
65
- inlay_hints.precedence_parentheses(location)
66
- in Params, Assign[location:]
67
- inlay_hints.precedence_parentheses(location)
68
- else
69
- # do nothing
70
- end
71
- end
118
+ private
72
119
 
73
- inlay_hints
120
+ def parentheses(location)
121
+ before[location.start_char] << "₍"
122
+ after[location.end_char] << "₎"
74
123
  end
75
124
  end
76
125
  end
@@ -7,10 +7,15 @@ require "uri"
7
7
  require_relative "language_server/inlay_hints"
8
8
 
9
9
  module SyntaxTree
10
+ # Syntax Tree additionally ships with a language server conforming to the
11
+ # language server protocol. It can be invoked through the CLI by running:
12
+ #
13
+ # stree lsp
14
+ #
10
15
  class LanguageServer
11
16
  attr_reader :input, :output
12
17
 
13
- def initialize(input: STDIN, output: STDOUT)
18
+ def initialize(input: $stdin, output: $stdout)
14
19
  @input = input.binmode
15
20
  @output = output.binmode
16
21
  end
@@ -21,7 +26,7 @@ module SyntaxTree
21
26
  hash[uri] = File.binread(CGI.unescape(URI.parse(uri).path))
22
27
  end
23
28
 
24
- while headers = input.gets("\r\n\r\n")
29
+ while (headers = input.gets("\r\n\r\n"))
25
30
  source = input.read(headers[/Content-Length: (\d+)/i, 1].to_i)
26
31
  request = JSON.parse(source, symbolize_names: true)
27
32
 
@@ -29,26 +34,46 @@ module SyntaxTree
29
34
  in { method: "initialize", id: }
30
35
  store.clear
31
36
  write(id: id, result: { capabilities: capabilities })
32
- in { method: "initialized" }
37
+ in method: "initialized"
33
38
  # ignored
34
- in { method: "shutdown" }
39
+ in method: "shutdown"
35
40
  store.clear
36
41
  return
37
- in { method: "textDocument/didChange", params: { textDocument: { uri: }, contentChanges: [{ text: }, *] } }
42
+ in {
43
+ method: "textDocument/didChange",
44
+ params: { textDocument: { uri: }, contentChanges: [{ text: }, *] }
45
+ }
38
46
  store[uri] = text
39
- in { method: "textDocument/didOpen", params: { textDocument: { uri:, text: } } }
47
+ in {
48
+ method: "textDocument/didOpen",
49
+ params: { textDocument: { uri:, text: } }
50
+ }
40
51
  store[uri] = text
41
- in { method: "textDocument/didClose", params: { textDocument: { uri: } } }
52
+ in {
53
+ method: "textDocument/didClose", params: { textDocument: { uri: } }
54
+ }
42
55
  store.delete(uri)
43
- in { method: "textDocument/formatting", id:, params: { textDocument: { uri: } } }
56
+ in {
57
+ method: "textDocument/formatting",
58
+ id:,
59
+ params: { textDocument: { uri: } }
60
+ }
44
61
  write(id: id, result: [format(store[uri])])
45
- in { method: "textDocument/inlayHints", id:, params: { textDocument: { uri: } } }
62
+ in {
63
+ method: "textDocument/inlayHints",
64
+ id:,
65
+ params: { textDocument: { uri: } }
66
+ }
46
67
  write(id: id, result: inlay_hints(store[uri]))
47
- in { method: "syntaxTree/visualizing", id:, params: { textDocument: { uri: } } }
68
+ in {
69
+ method: "syntaxTree/visualizing",
70
+ id:,
71
+ params: { textDocument: { uri: } }
72
+ }
48
73
  output = []
49
74
  PP.pp(SyntaxTree.parse(store[uri]), output)
50
75
  write(id: id, result: output.join)
51
- in { method: %r{\$/.+} }
76
+ in method: %r{\$/.+}
52
77
  # ignored
53
78
  else
54
79
  raise "Unhandled: #{request}"
@@ -61,15 +86,24 @@ module SyntaxTree
61
86
  def capabilities
62
87
  {
63
88
  documentFormattingProvider: true,
64
- textDocumentSync: { change: 1, openClose: true }
89
+ textDocumentSync: {
90
+ change: 1,
91
+ openClose: true
92
+ }
65
93
  }
66
94
  end
67
95
 
68
96
  def format(source)
69
97
  {
70
98
  range: {
71
- start: { line: 0, character: 0 },
72
- end: { line: source.lines.size + 1, character: 0 }
99
+ start: {
100
+ line: 0,
101
+ character: 0
102
+ },
103
+ end: {
104
+ line: source.lines.size + 1,
105
+ character: 0
106
+ }
73
107
  },
74
108
  newText: SyntaxTree.format(source)
75
109
  }
@@ -88,6 +122,8 @@ module SyntaxTree
88
122
  after: inlay_hints.after.map(&serialize)
89
123
  }
90
124
  rescue Parser::ParseError
125
+ # If there is a parse error, then we're not going to return any inlay
126
+ # hints for this source.
91
127
  end
92
128
 
93
129
  def write(value)