syntax_tree 2.3.1 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +20 -1
- data/.rubocop.yml +80 -0
- data/CHANGELOG.md +36 -1
- data/Gemfile.lock +24 -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 +7 -6
- 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 -387
- data/lib/syntax_tree/parser.rb +447 -112
- data/lib/syntax_tree/plugin/single_quotes.rb +4 -0
- 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 +12 -18
- data/syntax_tree.gemspec +26 -21
- metadata +38 -5
- data/lib/syntax_tree/prettyprint.rb +0 -1156
@@ -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
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module SyntaxTree
|
4
4
|
# A slightly enhanced PP that knows how to format recursively including
|
5
5
|
# comments.
|
6
|
-
class Formatter <
|
6
|
+
class Formatter < PrettierPrint
|
7
7
|
COMMENT_PRIORITY = 1
|
8
8
|
HEREDOC_PRIORITY = 2
|
9
9
|
|
@@ -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)
|