solargraph 0.1.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.
@@ -0,0 +1,31 @@
1
+ module Solargraph
2
+ class Mapper
3
+ def initialize
4
+ @default_api_map = Solargraph::ApiMap.new
5
+ stub = Parser::CurrentRuby.parse(Solargraph::LiveParser.parse(nil))
6
+ @default_api_map.merge(stub)
7
+ @default_api_map.freeze
8
+ @require_nodes = {}
9
+ end
10
+
11
+ def get filename, text
12
+ workspace = find_workspace(filename)
13
+ CodeMap.new(text, api_map: @default_api_map, workspace: workspace, require_nodes: @require_nodes)
14
+ end
15
+
16
+ def find_workspace filename
17
+ dirname = filename
18
+ lastname = nil
19
+ result = nil
20
+ until dirname == lastname
21
+ if File.file?("#{dirname}/Gemfile")
22
+ result = dirname
23
+ break
24
+ end
25
+ lastname = dirname
26
+ dirname = File.dirname(dirname)
27
+ end
28
+ result || File.dirname(filename)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,48 @@
1
+ module Solargraph
2
+ module NodeMethods
3
+ def unpack_name(node)
4
+ pack_name(node).join("::")
5
+ end
6
+
7
+ def pack_name(node)
8
+ parts = []
9
+ node.children.each { |n|
10
+ if n.kind_of?(AST::Node)
11
+ if n.type == :cbase
12
+ parts = pack_name(n)
13
+ else
14
+ parts += pack_name(n)
15
+ end
16
+ else
17
+ parts.push n unless n.nil?
18
+ end
19
+ }
20
+ parts
21
+ end
22
+
23
+ def infer node
24
+ if node.type == :str
25
+ return 'String'
26
+ elsif node.type == :array
27
+ return 'Array'
28
+ elsif node.type == :hash
29
+ return 'Hash'
30
+ elsif node.type == :int
31
+ return 'Integer'
32
+ elsif node.type == :float
33
+ return 'Float'
34
+ elsif node.type == :send
35
+ if node.children[0].nil?
36
+ # TODO Another local variable or method or something? sheesh
37
+ else
38
+ ns = unpack_name(node.children[0])
39
+ if node.children[1] == :new
40
+ return ns
41
+ end
42
+ end
43
+ elsif node.type == :cbase or node.type == :const
44
+ unpack_name node
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,21 @@
1
+ require 'sinatra/base'
2
+
3
+ module Solargraph
4
+ class Server < Sinatra::Base
5
+ set :port, 0
6
+
7
+ post '/suggest' do
8
+ content_type :json
9
+ begin
10
+ code_map = CodeMap.new(code: params['text'], filename: params['filename'])
11
+ offset = code_map.get_offset(params['line'].to_i, params['col'].to_i)
12
+ sugg = code_map.suggest_at(offset, with_snippets: true, filtered: true)
13
+ { "status" => "ok", "suggestions" => sugg }.to_json
14
+ rescue Exception => e
15
+ STDERR.puts e
16
+ STDERR.puts e.backtrace.join("\n")
17
+ { "status" => "err", "message" => e.message }.to_json
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,84 @@
1
+ require 'thor'
2
+ require 'json'
3
+ require 'fileutils'
4
+ require 'rubygems/package'
5
+ require 'zlib'
6
+
7
+ module Solargraph
8
+ class Shell < Thor
9
+ desc 'prepare', 'Cache YARD files for the current environment'
10
+ option :force, type: :boolean, aliases: :f, desc: 'Force download of YARDOC files if they already exist'
11
+ option :host, type: :string, aliases: :h, desc: 'The host that provides YARDOC files for download', default: 'yardoc.solargraph.org'
12
+ def prepare
13
+ cache_dir = File.join(Dir.home, '.solargraph', 'cache')
14
+ version_dir = File.join(cache_dir, '2.0.0')
15
+ unless File.exist?(version_dir) or options[:force]
16
+ FileUtils.mkdir_p cache_dir
17
+ require 'net/http'
18
+ puts 'Downloading 2.0.0...'
19
+ Net::HTTP.start(options[:host]) do |http|
20
+ resp = http.get("/2.0.0.tar.gz")
21
+ open(File.join(cache_dir, '2.0.0.tar.gz'), "wb") do |file|
22
+ file.write(resp.body)
23
+ end
24
+ puts 'Uncompressing archives...'
25
+ FileUtils.rm_rf version_dir if File.exist?(version_dir)
26
+ tar_extract = Gem::Package::TarReader.new(Zlib::GzipReader.open(File.join(cache_dir, '2.0.0.tar.gz')))
27
+ tar_extract.rewind
28
+ tar_extract.each do |entry|
29
+ if entry.directory?
30
+ FileUtils.mkdir_p File.join(cache_dir, entry.full_name)
31
+ else
32
+ FileUtils.mkdir_p File.join(cache_dir, File.dirname(entry.full_name))
33
+ File.open(File.join(cache_dir, entry.full_name), 'wb') do |f|
34
+ f << entry.read
35
+ end
36
+ end
37
+ end
38
+ tar_extract.close
39
+ FileUtils.rm File.join(cache_dir, '2.0.0.tar.gz')
40
+ puts 'Done.'
41
+ end
42
+ end
43
+ end
44
+
45
+ desc 'bundled', 'Get a list of bundled gems'
46
+ def bundled
47
+ Bundler.load.specs.each { |s|
48
+ puts s.name
49
+ }
50
+ end
51
+
52
+ desc 'server', 'Start a Solargraph server'
53
+ def server
54
+ Solargraph::Server.run!
55
+ end
56
+
57
+ desc 'suggest', 'Get code suggestions for the provided input'
58
+ long_desc <<-LONGDESC
59
+ Analyze a Ruby file and output a list of code suggestions in JSON format.
60
+ LONGDESC
61
+ option :line, type: :numeric, aliases: :l, desc: 'Zero-based line number', required: true
62
+ option :column, type: :numeric, aliases: [:c, :col], desc: 'Zero-based column number', required: true
63
+ option :filename, type: :string, aliases: :f, desc: 'File name', required: false
64
+ def suggest(*filenames)
65
+ # HACK: The ARGV array needs to be manipulated for ARGF.read to work
66
+ ARGV.clear
67
+ ARGV.concat filenames
68
+ text = ARGF.read
69
+ filename = options[:filename] || filenames[0]
70
+ begin
71
+ code_map = CodeMap.new(code: text, filename: filename)
72
+ offset = code_map.get_offset(options[:line], options[:column])
73
+ sugg = code_map.suggest_at(offset, with_snippets: true, filtered: true)
74
+ result = { "status" => "ok", "suggestions" => sugg }.to_json
75
+ STDOUT.puts result
76
+ rescue Exception => e
77
+ STDERR.puts e
78
+ STDERR.puts e.backtrace.join("\n")
79
+ result = { "status" => "err", "message" => e.message }.to_json
80
+ STDOUT.puts result
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,186 @@
1
+ require 'json'
2
+
3
+ module Solargraph
4
+ module Snippets
5
+ def self.definitions
6
+ @definitions ||= JSON.parse('{
7
+ "Exception block": {
8
+ "prefix": "begin",
9
+ "body": [
10
+ "begin",
11
+ "\t$1",
12
+ "rescue => exception",
13
+ "\t",
14
+ "end"
15
+ ]
16
+ },
17
+ "Exception block with ensure": {
18
+ "prefix": "begin ensure",
19
+ "body": [
20
+ "begin",
21
+ "\t$1",
22
+ "rescue => exception",
23
+ "\t",
24
+ "ensure",
25
+ "\t",
26
+ "end"
27
+ ]
28
+ },
29
+ "Exception block with else": {
30
+ "prefix": "begin else",
31
+ "body": [
32
+ "begin",
33
+ "\t$1",
34
+ "rescue => exception",
35
+ "\t",
36
+ "else",
37
+ "\t",
38
+ "end"
39
+ ]
40
+ },
41
+ "Exception block with else and ensure": {
42
+ "prefix": "begin else ensure",
43
+ "body": [
44
+ "begin",
45
+ "\t$1",
46
+ "rescue => exception",
47
+ "\t",
48
+ "else",
49
+ "\t",
50
+ "ensure",
51
+ "\t",
52
+ "end"
53
+ ]
54
+ },
55
+ "Class definition with initialize": {
56
+ "prefix": "class init",
57
+ "body": [
58
+ "class ${ClassName}",
59
+ "\tdef initialize",
60
+ "\t\t$0",
61
+ "\tend",
62
+ "end"
63
+ ]
64
+ },
65
+ "Class definition": {
66
+ "prefix": "class",
67
+ "body": [
68
+ "class ${ClassName}",
69
+ "\t$0",
70
+ "end"
71
+ ]
72
+ },
73
+ "for loop": {
74
+ "prefix": "for",
75
+ "body": [
76
+ "for ${value} in ${enumerable} do",
77
+ "\t$0",
78
+ "end"
79
+ ]
80
+ },
81
+ "if": {
82
+ "prefix": "if",
83
+ "body": [
84
+ "if ${test}",
85
+ "\t$0",
86
+ "end"
87
+ ]
88
+ },
89
+ "if else": {
90
+ "prefix": "if else",
91
+ "body": [
92
+ "if ${test}",
93
+ "\t$0",
94
+ "else",
95
+ "\t",
96
+ "end"
97
+ ]
98
+ },
99
+ "if elsif": {
100
+ "prefix": "if elsif",
101
+ "body": [
102
+ "if ${test}",
103
+ "\t$0",
104
+ "elsif ",
105
+ "\t",
106
+ "end"
107
+ ]
108
+ },
109
+ "if elsif else": {
110
+ "prefix": "if elsif else",
111
+ "body": [
112
+ "if ${test}",
113
+ "\t$0",
114
+ "elsif ",
115
+ "\t",
116
+ "else",
117
+ "\t",
118
+ "end"
119
+ ]
120
+ },
121
+ "forever loop": {
122
+ "prefix": "loop",
123
+ "body": [
124
+ "loop do",
125
+ "\t$0",
126
+ "end"
127
+ ]
128
+ },
129
+ "Module definition": {
130
+ "prefix": "module",
131
+ "body": [
132
+ "module ${ModuleName}",
133
+ "\t$0",
134
+ "end"
135
+ ]
136
+ },
137
+ "unless": {
138
+ "prefix": "unless",
139
+ "body": [
140
+ "unless ${test}",
141
+ "\t$0",
142
+ "end"
143
+ ]
144
+ },
145
+ "until loop": {
146
+ "prefix": "until",
147
+ "body": [
148
+ "until ${test}",
149
+ "\t$0",
150
+ "end"
151
+ ]
152
+ },
153
+ "while loop": {
154
+ "prefix": "while",
155
+ "body": [
156
+ "while ${test}",
157
+ "\t$0",
158
+ "end"
159
+ ]
160
+ },
161
+ "method definition": {
162
+ "prefix": "def",
163
+ "body": [
164
+ "def ${method_name}",
165
+ "\t$0",
166
+ "end"
167
+ ],
168
+ "description": "Snippet for method definitions"
169
+ },
170
+ "defined?": {
171
+ "prefix": "defined?",
172
+ "body": [
173
+ "defined?"
174
+ ],
175
+ "description": "Something silly"
176
+ }
177
+ }')
178
+ end
179
+
180
+ def self.keywords
181
+ @keywords ||= [
182
+ 'begin', 'class', 'for', 'if', 'module', 'unless', 'until', 'while', 'def', 'defined?'
183
+ ]
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,40 @@
1
+ require 'json'
2
+
3
+ module Solargraph
4
+
5
+ class Suggestion
6
+ CLASS = 'Class'
7
+ KEYWORD = 'Keyword'
8
+ MODULE = 'Module'
9
+ METHOD = 'Method'
10
+ VARIABLE = 'Variable'
11
+ SNIPPET = 'Snippet'
12
+
13
+ attr_reader :label, :kind, :insert, :detail, :documentation
14
+
15
+ def initialize label, kind: KEYWORD, insert: nil, detail: nil, documentation: nil, code_object: nil
16
+ @label = label.to_s
17
+ @kind = kind
18
+ @insert = insert || @label
19
+ @detail = detail
20
+ @code_object = code_object
21
+ @documentation = documentation
22
+ end
23
+
24
+ def to_s
25
+ label
26
+ end
27
+
28
+ def to_json args={}
29
+ {
30
+ label: @label,
31
+ kind: @kind,
32
+ insert: @insert,
33
+ detail: @detail,
34
+ #documentation: (@documentation.nil? ? nil : @documentation.all)
35
+ documentation: (@code_object.nil? ? nil : YARD::Templates::Engine.render(format: :text, object: @code_object))
36
+ }.to_json(args)
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,3 @@
1
+ module Solargraph
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,119 @@
1
+ require 'rubygems'
2
+ require 'parser/current'
3
+ require 'yard'
4
+
5
+ module Solargraph
6
+
7
+ class YardMap
8
+ def initialize required: [], workspace: nil
9
+ unless workspace.nil?
10
+ wsy = File.join(workspace, '.yardoc')
11
+ yardocs.push wsy if File.exist?(wsy)
12
+ end
13
+ required.each { |r|
14
+ gy = YARD::Registry.yardoc_file_for_gem(r)
15
+ yardocs.push gy unless gy.nil?
16
+ }
17
+ yardocs.push File.join(Dir.home, '.solargraph', 'cache', '2.0.0', 'yardoc')
18
+ #yardocs.push File.join(Dir.home, '.solargraph', 'cache', '2.0.0', 'yardoc-stdlib')
19
+ end
20
+
21
+ def yardocs
22
+ @yardocs ||= []
23
+ end
24
+
25
+ def get_constants namespace, scope = ''
26
+ consts = []
27
+ result = []
28
+ yardocs.each { |y|
29
+ yard = YARD::Registry.load! y
30
+ unless yard.nil?
31
+ ns = nil
32
+ if scope == ''
33
+ ns = yard.at(namespace)
34
+ else
35
+ ns = yard.resolve(P(scope), namespace)
36
+ end
37
+ consts += ns.children unless ns.nil?
38
+ end
39
+ }
40
+ consts.each { |c|
41
+ result.push Suggestion.new(c.to_s.split('::').last, kind: Suggestion::CLASS)
42
+ }
43
+ result
44
+ end
45
+
46
+ def at signature
47
+ yardocs.each { |y|
48
+ yard = YARD::Registry.load! y
49
+ unless yard.nil?
50
+ obj = yard.at(signature)
51
+ return obj unless obj.nil?
52
+ end
53
+ }
54
+ nil
55
+ end
56
+
57
+ def resolve signature, scope
58
+ yardocs.each { |y|
59
+ yard = YARD::Registry.load! y
60
+ unless yard.nil?
61
+ obj = yard.resolve(P(scope), signature)
62
+ return obj unless obj.nil?
63
+ end
64
+ }
65
+ nil
66
+ end
67
+
68
+ def get_methods namespace, scope = ''
69
+ meths = []
70
+ yardocs.each { |y|
71
+ yard = YARD::Registry.load! y
72
+ unless yard.nil?
73
+ ns = nil
74
+ if scope == ''
75
+ ns = yard.at(namespace)
76
+ else
77
+ ns = yard.resolve(P(scope), namespace)
78
+ end
79
+ unless ns.nil?
80
+ ns.meths(scope: :class, visibility: [:public]).each { |m|
81
+ n = m.to_s.split('.').last
82
+ meths.push Suggestion.new("#{n}", kind: Suggestion::METHOD) if n.to_s.match(/^[a-z]/i)
83
+ }
84
+ if ns.kind_of?(YARD::CodeObjects::ClassObject) and namespace != 'Class'
85
+ meths += get_instance_methods('Class')
86
+ end
87
+ end
88
+ end
89
+ }
90
+ meths
91
+ end
92
+
93
+ def get_instance_methods namespace, scope = ''
94
+ meths = []
95
+ yardocs.each { |y|
96
+ yard = YARD::Registry.load! y
97
+ unless yard.nil?
98
+ ns = nil
99
+ if scope == ''
100
+ ns = yard.at(namespace)
101
+ else
102
+ ns = yard.resolve(P(scope), namespace)
103
+ end
104
+ unless ns.nil?
105
+ ns.meths(scope: :instance, visibility: [:public]).each { |m|
106
+ n = m.to_s.split('#').last
107
+ meths.push Suggestion.new("#{n}", kind: Suggestion::METHOD, documentation: m.docstring, code_object: m) if n.to_s.match(/^[a-z]/i) and !m.to_s.start_with?('Kernel#') and !m.docstring.to_s.include?(':nodoc:')
108
+ }
109
+ if ns.kind_of?(YARD::CodeObjects::ClassObject) and namespace != 'Object'
110
+ meths += get_instance_methods('Object')
111
+ end
112
+ end
113
+ end
114
+ }
115
+ meths
116
+ end
117
+ end
118
+
119
+ end