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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +20 -1
- data/.rubocop.yml +80 -0
- data/CHANGELOG.md +30 -1
- data/Gemfile +2 -0
- data/Gemfile.lock +22 -1
- data/README.md +126 -5
- data/Rakefile +27 -5
- data/config/rubocop.yml +64 -0
- data/lib/syntax_tree/cli.rb +63 -27
- data/lib/syntax_tree/formatter/single_quotes.rb +13 -0
- data/lib/syntax_tree/formatter.rb +6 -5
- data/lib/syntax_tree/language_server/inlay_hints.rb +87 -38
- data/lib/syntax_tree/language_server.rb +50 -14
- data/lib/syntax_tree/node.rb +499 -306
- data/lib/syntax_tree/parser.rb +447 -112
- data/lib/syntax_tree/plugin/single_quotes.rb +4 -0
- data/lib/syntax_tree/prettyprint.rb +28 -25
- data/lib/syntax_tree/version.rb +1 -1
- data/lib/syntax_tree/visitor/field_visitor.rb +1115 -0
- data/lib/syntax_tree/visitor/json_visitor.rb +25 -1305
- data/lib/syntax_tree/visitor/match_visitor.rb +122 -0
- data/lib/syntax_tree/visitor/pretty_print_visitor.rb +35 -1163
- data/lib/syntax_tree/visitor.rb +6 -1
- data/lib/syntax_tree.rb +19 -1
- data/syntax_tree.gemspec +21 -19
- metadata +10 -4
@@ -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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
6
|
-
|
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
|
27
|
-
|
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
|
31
|
-
#
|
97
|
+
# Adds parentheses around unary statements using the - operator that are
|
98
|
+
# contained within Binary nodes. For example,
|
32
99
|
#
|
33
|
-
# a + b
|
100
|
+
# -a + b
|
34
101
|
#
|
35
102
|
# becomes
|
36
103
|
#
|
37
|
-
# a +
|
104
|
+
# ₍-a₎ + b
|
38
105
|
#
|
39
|
-
def
|
40
|
-
|
41
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
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
|
-
|
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:
|
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
|
37
|
+
in method: "initialized"
|
33
38
|
# ignored
|
34
|
-
in
|
39
|
+
in method: "shutdown"
|
35
40
|
store.clear
|
36
41
|
return
|
37
|
-
in {
|
42
|
+
in {
|
43
|
+
method: "textDocument/didChange",
|
44
|
+
params: { textDocument: { uri: }, contentChanges: [{ text: }, *] }
|
45
|
+
}
|
38
46
|
store[uri] = text
|
39
|
-
in {
|
47
|
+
in {
|
48
|
+
method: "textDocument/didOpen",
|
49
|
+
params: { textDocument: { uri:, text: } }
|
50
|
+
}
|
40
51
|
store[uri] = text
|
41
|
-
in {
|
52
|
+
in {
|
53
|
+
method: "textDocument/didClose", params: { textDocument: { uri: } }
|
54
|
+
}
|
42
55
|
store.delete(uri)
|
43
|
-
in {
|
56
|
+
in {
|
57
|
+
method: "textDocument/formatting",
|
58
|
+
id:,
|
59
|
+
params: { textDocument: { uri: } }
|
60
|
+
}
|
44
61
|
write(id: id, result: [format(store[uri])])
|
45
|
-
in {
|
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 {
|
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
|
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: {
|
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: {
|
72
|
-
|
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)
|