syntax_tree 2.3.0 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)