rsense-core 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +1 -0
  5. data/README.md +35 -0
  6. data/Rakefile +84 -0
  7. data/TypeAnnotation.tokens +41 -0
  8. data/build.xml +84 -0
  9. data/build_lib/antlr-3.2.jar +0 -0
  10. data/lib/jars/ant-1.7.0.jar +0 -0
  11. data/lib/jars/ant-launcher-1.7.0.jar +0 -0
  12. data/lib/jars/antlr-runtime-3.2.jar +0 -0
  13. data/lib/jars/bsf-2.3.0.jar +0 -0
  14. data/lib/rsense-core.rb +28 -0
  15. data/lib/rsense.jar +0 -0
  16. data/lib/rsense/core.rb +5 -0
  17. data/lib/rsense/core/version.rb +5 -0
  18. data/lib/rsense/parser.rb +6 -0
  19. data/lib/rsense/ruby.rb +19 -0
  20. data/lib/rsense/typing.rb +13 -0
  21. data/lib/rsense/typing/annotation.rb +20 -0
  22. data/lib/rsense/typing/runtime.rb +23 -0
  23. data/lib/rsense/typing/vertex.rb +15 -0
  24. data/lib/rsense/util.rb +9 -0
  25. data/rsense-core.gemspec +30 -0
  26. data/src/org/cx4a/rsense/CodeAssist.java +744 -0
  27. data/src/org/cx4a/rsense/CodeAssistError.java +31 -0
  28. data/src/org/cx4a/rsense/CodeAssistResult.java +42 -0
  29. data/src/org/cx4a/rsense/CodeCompletionResult.java +65 -0
  30. data/src/org/cx4a/rsense/FindDefinitionResult.java +24 -0
  31. data/src/org/cx4a/rsense/LoadResult.java +19 -0
  32. data/src/org/cx4a/rsense/Main.java +916 -0
  33. data/src/org/cx4a/rsense/Options.java +353 -0
  34. data/src/org/cx4a/rsense/Project.java +103 -0
  35. data/src/org/cx4a/rsense/TypeInferenceResult.java +25 -0
  36. data/src/org/cx4a/rsense/WhereResult.java +19 -0
  37. data/src/org/cx4a/rsense/parser/TypeAnnotation.g +221 -0
  38. data/src/org/cx4a/rsense/parser/TypeAnnotationLexer.java +1759 -0
  39. data/src/org/cx4a/rsense/parser/TypeAnnotationParser.java +2025 -0
  40. data/src/org/cx4a/rsense/ruby/Block.java +10 -0
  41. data/src/org/cx4a/rsense/ruby/Context.java +75 -0
  42. data/src/org/cx4a/rsense/ruby/DynamicMethod.java +10 -0
  43. data/src/org/cx4a/rsense/ruby/DynamicScope.java +51 -0
  44. data/src/org/cx4a/rsense/ruby/Frame.java +95 -0
  45. data/src/org/cx4a/rsense/ruby/IRubyObject.java +17 -0
  46. data/src/org/cx4a/rsense/ruby/LocalScope.java +43 -0
  47. data/src/org/cx4a/rsense/ruby/MetaClass.java +50 -0
  48. data/src/org/cx4a/rsense/ruby/Ruby.java +242 -0
  49. data/src/org/cx4a/rsense/ruby/RubyClass.java +146 -0
  50. data/src/org/cx4a/rsense/ruby/RubyModule.java +255 -0
  51. data/src/org/cx4a/rsense/ruby/RubyObject.java +94 -0
  52. data/src/org/cx4a/rsense/ruby/Scope.java +7 -0
  53. data/src/org/cx4a/rsense/ruby/SpecialObject.java +15 -0
  54. data/src/org/cx4a/rsense/ruby/Visibility.java +17 -0
  55. data/src/org/cx4a/rsense/typing/Graph.java +1690 -0
  56. data/src/org/cx4a/rsense/typing/Propagation.java +73 -0
  57. data/src/org/cx4a/rsense/typing/Template.java +84 -0
  58. data/src/org/cx4a/rsense/typing/TemplateAttribute.java +158 -0
  59. data/src/org/cx4a/rsense/typing/TypeSet.java +48 -0
  60. data/src/org/cx4a/rsense/typing/annotation/ClassType.java +57 -0
  61. data/src/org/cx4a/rsense/typing/annotation/MethodType.java +79 -0
  62. data/src/org/cx4a/rsense/typing/annotation/TypeAnnotation.java +4 -0
  63. data/src/org/cx4a/rsense/typing/annotation/TypeAny.java +7 -0
  64. data/src/org/cx4a/rsense/typing/annotation/TypeApplication.java +37 -0
  65. data/src/org/cx4a/rsense/typing/annotation/TypeConstraint.java +29 -0
  66. data/src/org/cx4a/rsense/typing/annotation/TypeExpression.java +11 -0
  67. data/src/org/cx4a/rsense/typing/annotation/TypeIdentity.java +59 -0
  68. data/src/org/cx4a/rsense/typing/annotation/TypeOptional.java +22 -0
  69. data/src/org/cx4a/rsense/typing/annotation/TypePragma.java +22 -0
  70. data/src/org/cx4a/rsense/typing/annotation/TypeSplat.java +22 -0
  71. data/src/org/cx4a/rsense/typing/annotation/TypeTuple.java +35 -0
  72. data/src/org/cx4a/rsense/typing/annotation/TypeUnion.java +23 -0
  73. data/src/org/cx4a/rsense/typing/annotation/TypeVariable.java +44 -0
  74. data/src/org/cx4a/rsense/typing/runtime/AliasMethod.java +94 -0
  75. data/src/org/cx4a/rsense/typing/runtime/AnnotationHelper.java +69 -0
  76. data/src/org/cx4a/rsense/typing/runtime/AnnotationResolver.java +523 -0
  77. data/src/org/cx4a/rsense/typing/runtime/Array.java +84 -0
  78. data/src/org/cx4a/rsense/typing/runtime/ClassTag.java +27 -0
  79. data/src/org/cx4a/rsense/typing/runtime/DefaultMethod.java +115 -0
  80. data/src/org/cx4a/rsense/typing/runtime/Hash.java +131 -0
  81. data/src/org/cx4a/rsense/typing/runtime/LoopTag.java +21 -0
  82. data/src/org/cx4a/rsense/typing/runtime/Method.java +32 -0
  83. data/src/org/cx4a/rsense/typing/runtime/MonomorphicObject.java +77 -0
  84. data/src/org/cx4a/rsense/typing/runtime/ObjectAllocator.java +40 -0
  85. data/src/org/cx4a/rsense/typing/runtime/PolymorphicObject.java +90 -0
  86. data/src/org/cx4a/rsense/typing/runtime/Proc.java +100 -0
  87. data/src/org/cx4a/rsense/typing/runtime/RuntimeHelper.java +1339 -0
  88. data/src/org/cx4a/rsense/typing/runtime/SpecialMethod.java +119 -0
  89. data/src/org/cx4a/rsense/typing/runtime/TypeVarMap.java +112 -0
  90. data/src/org/cx4a/rsense/typing/runtime/VertexHolder.java +48 -0
  91. data/src/org/cx4a/rsense/typing/vertex/CallVertex.java +122 -0
  92. data/src/org/cx4a/rsense/typing/vertex/MultipleAsgnVertex.java +23 -0
  93. data/src/org/cx4a/rsense/typing/vertex/PassThroughVertex.java +20 -0
  94. data/src/org/cx4a/rsense/typing/vertex/SValueVertex.java +24 -0
  95. data/src/org/cx4a/rsense/typing/vertex/SplatVertex.java +24 -0
  96. data/src/org/cx4a/rsense/typing/vertex/ToAryVertex.java +24 -0
  97. data/src/org/cx4a/rsense/typing/vertex/TypeVarVertex.java +22 -0
  98. data/src/org/cx4a/rsense/typing/vertex/Vertex.java +221 -0
  99. data/src/org/cx4a/rsense/typing/vertex/YieldVertex.java +70 -0
  100. data/src/org/cx4a/rsense/util/HereDocReader.java +48 -0
  101. data/src/org/cx4a/rsense/util/Logger.java +111 -0
  102. data/src/org/cx4a/rsense/util/NodeUtil.java +198 -0
  103. data/src/org/cx4a/rsense/util/SourceLocation.java +70 -0
  104. data/src/org/cx4a/rsense/util/StringUtil.java +63 -0
  105. data/src/resources/org/cx4a/rsense/rsense.properties +1 -0
  106. data/stubs/1.8/_builtin.rb +3006 -0
  107. data/stubs/1.8/bigdecimal.rb +131 -0
  108. data/stubs/1.8/cgi.rb +257 -0
  109. data/stubs/1.8/date.rb +147 -0
  110. data/stubs/1.8/optparse.rb +113 -0
  111. data/stubs/1.8/rational.rb +47 -0
  112. data/stubs/1.8/set.rb +94 -0
  113. data/stubs/1.8/socket.rb +461 -0
  114. data/stubs/1.8/stringio.rb +129 -0
  115. data/test/data/a file.rb +1 -0
  116. data/test/data/benchmark.rb +12 -0
  117. data/test/data/crlf.rb +5 -0
  118. data/test/data/test.rb +19 -0
  119. data/test/script/all.rsense +2 -0
  120. data/test/script/array_dynamic.rsense +25 -0
  121. data/test/script/block_nested.rsense +7 -0
  122. data/test/script/builtin.rsense +785 -0
  123. data/test/script/class_method_partial_update.rsense +52 -0
  124. data/test/script/class_partial_update.rsense +17 -0
  125. data/test/script/find-definition.rsense +72 -0
  126. data/test/script/method_arg_onearg.rsense +6 -0
  127. data/test/script/method_arg_optional.rsense +7 -0
  128. data/test/script/method_partial_update.rsense +14 -0
  129. data/test/script/method_yield_arrayarg.rsense +8 -0
  130. data/test/script/method_yield_arrayarg_expand.rsense +8 -0
  131. data/test/script/method_yield_arrayarg_splat.rsense +17 -0
  132. data/test/script/misc.rsense +2 -0
  133. data/test/script/proc_higher_order.rsense +22 -0
  134. data/test/script/regression.rsense +95 -0
  135. data/test/script/stdlib.rsense +66 -0
  136. data/test/script/where.rsense +41 -0
  137. metadata +315 -0
@@ -0,0 +1,9 @@
1
+ module Rsense
2
+ module Util
3
+ import Java::org.cx4a.rsense.util::HereDocReader
4
+ import Java::org.cx4a.rsense.util::Logger
5
+ import Java::org.cx4a.rsense.util::NodeUtil
6
+ import Java::org.cx4a.rsense.util::SourceLocation
7
+ import Java::org.cx4a.rsense.util::StringUtil
8
+ end
9
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rsense/core/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rsense-core"
8
+ spec.version = Rsense::Core::VERSION
9
+ spec.authors = ["Eric West", "Tomohiro Matsuyama"]
10
+ spec.email = ["esw9999@gmail.com", "tomo@cx4a.org"]
11
+ spec.summary = %q{RSense knows your code.}
12
+ spec.description = %q{RSense is a tool for doing static analysis of Ruby source code. Rsense-core contains the "library" portion of rsense, as well as the stubs and other artefacts it needs to run.}
13
+ spec.homepage = ""
14
+ spec.license = "GPL"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "spoon", "~> 0.0.4"
22
+ spec.add_dependency "jruby-jars", "~> 1.7.4"
23
+ spec.add_dependency "jruby-parser", "~> 0.5.2"
24
+ spec.add_dependency "thor", "~> 0.18.1"
25
+
26
+ spec.add_development_dependency 'guard'
27
+ spec.add_development_dependency 'guard-minitest'
28
+ spec.add_development_dependency "bundler", "~> 1.6"
29
+ spec.add_development_dependency "rake"
30
+ end
@@ -0,0 +1,744 @@
1
+ package org.cx4a.rsense;
2
+
3
+ import java.io.File;
4
+ import java.io.FileInputStream;
5
+ import java.io.IOException;
6
+ import java.io.InputStream;
7
+ import java.io.InputStreamReader;
8
+ import java.io.LineNumberReader;
9
+ import java.io.Reader;
10
+ import java.io.StringReader;
11
+ import java.util.ArrayList;
12
+ import java.util.Collection;
13
+ import java.util.HashMap;
14
+ import java.util.HashSet;
15
+ import java.util.Iterator;
16
+ import java.util.List;
17
+ import java.util.Map;
18
+ import java.util.Set;
19
+ import java.util.regex.Matcher;
20
+ import java.util.regex.Pattern;
21
+
22
+ import org.jrubyparser.ast.INameNode;
23
+ import org.jrubyparser.ast.Node;
24
+ import org.jrubyparser.ast.NodeType;
25
+ import org.jrubyparser.parser.ParserConfiguration;
26
+ import org.jrubyparser.CompatVersion;
27
+
28
+ import org.cx4a.rsense.CodeCompletionResult.CompletionCandidate;
29
+ import org.cx4a.rsense.ruby.Block;
30
+ import org.cx4a.rsense.ruby.DynamicMethod;
31
+ import org.cx4a.rsense.ruby.IRubyObject;
32
+ import org.cx4a.rsense.ruby.MetaClass;
33
+ import org.cx4a.rsense.ruby.Ruby;
34
+ import org.cx4a.rsense.ruby.RubyClass;
35
+ import org.cx4a.rsense.ruby.RubyModule;
36
+ import org.cx4a.rsense.typing.Graph;
37
+ import org.cx4a.rsense.typing.TypeSet;
38
+ import org.cx4a.rsense.typing.runtime.Method;
39
+ import org.cx4a.rsense.typing.runtime.SpecialMethod;
40
+ import org.cx4a.rsense.typing.runtime.VertexHolder;
41
+ import org.cx4a.rsense.typing.vertex.CallVertex;
42
+ import org.cx4a.rsense.typing.vertex.Vertex;
43
+ import org.cx4a.rsense.util.Logger;
44
+ import org.cx4a.rsense.util.SourceLocation;
45
+
46
+ public class CodeAssist {
47
+ public static final String TYPE_INFERENCE_METHOD_NAME = "__rsense_type_inference__";
48
+ public static final String FIND_DEFINITION_METHOD_NAME_PREFIX = "__rsense_find_definition__";
49
+ public static final String PROJECT_CONFIG_NAME = ".project.rsense";
50
+
51
+ public static class Location {
52
+ // 3 ways to specify location
53
+
54
+ private int offset;
55
+ private int line, col;
56
+ private char[] mark;
57
+
58
+ private Location(int offset, int line, int col, String mark) {
59
+ this.offset = offset;
60
+ this.line = line;
61
+ this.col = col;
62
+ this.mark = mark != null ? mark.toCharArray() : null;
63
+ }
64
+
65
+ public int getOffset() {
66
+ return offset;
67
+ }
68
+
69
+ public int getLine() {
70
+ return line;
71
+ }
72
+
73
+ public int getColumn() {
74
+ return col;
75
+ }
76
+
77
+ public char[] getMark() {
78
+ return mark;
79
+ }
80
+
81
+ public int findOffset(int offset, int line, char[] buf, int length) {
82
+ if (this.offset != -1) {
83
+ return this.offset;
84
+ } else if (this.line != -1) {
85
+ int col = 0;
86
+ int count = 0;
87
+ for (int i = 0; i < length; i++) {
88
+ char c = buf[i];
89
+ if (Character.isHighSurrogate(c) || c == '\r') {
90
+ } else if (c == '\n') {
91
+ line++;
92
+ col = 0;
93
+ count++;
94
+ } else {
95
+ col++;
96
+ count++;
97
+ if (line == this.line && col == this.col) {
98
+ return offset + count;
99
+ }
100
+ }
101
+ }
102
+ return -1;
103
+ } else if (this.mark != null) {
104
+ int count = 0;
105
+ for (int i = 0; i <= length - mark.length; i++) {
106
+ int j = 0;
107
+ if (Character.isHighSurrogate(buf[i]) || buf[i] == '\r') {
108
+ } else {
109
+ count++;
110
+ }
111
+ for (; j < mark.length && buf[i + j] == mark[j]; j++);
112
+ if (j == mark.length) {
113
+ return offset + count - 1;
114
+ }
115
+ }
116
+ return -1;
117
+ }
118
+ return -1;
119
+ }
120
+
121
+ public int getSkip() {
122
+ return mark != null ? mark.length : 0;
123
+ }
124
+
125
+ public static Location offsetLocation(int offset) {
126
+ return new Location(offset, -1, -1, null);
127
+ }
128
+
129
+ public static Location logicalLocation(int line, int col) {
130
+ return new Location(-1, line, col, null);
131
+ }
132
+
133
+ public static Location markLocation(String mark) {
134
+ return new Location(-1, -1, -1, mark);
135
+ }
136
+ }
137
+
138
+ private static class Context {
139
+ public Project project;
140
+ public TypeSet typeSet;
141
+ public boolean main;
142
+ public String feature;
143
+ public int loadPathLevel;
144
+
145
+ public void clear() {
146
+ this.project = null;
147
+ this.typeSet = null;
148
+ this.main = false;
149
+ this.feature = null;
150
+ this.loadPathLevel = 0;
151
+ }
152
+ }
153
+
154
+ private class WhereEventListener implements Project.EventListener {
155
+ private int line;
156
+ private int closest;
157
+ private String name;
158
+
159
+ public void prepare(int line) {
160
+ this.line = line;
161
+ }
162
+
163
+ public String getName() {
164
+ return name;
165
+ }
166
+
167
+ public void update(Event event) {
168
+ if (context.main
169
+ && (event.type == EventType.DEFINE
170
+ || event.type == EventType.CLASS
171
+ || event.type == EventType.MODULE)
172
+ && event.name != null
173
+ && event.node != null) {
174
+ SourceLocation loc = SourceLocation.of(event.node);
175
+ if (loc != null
176
+ && line >= loc.getLine()
177
+ && line - closest > line - loc.getLine()) {
178
+ closest = loc.getLine();
179
+ name = event.name;
180
+ }
181
+ }
182
+ }
183
+ }
184
+
185
+ private class FindDefinitionEventListener implements Project.EventListener {
186
+ private int counter = 0;
187
+ private String prefix;
188
+ private Set<SourceLocation> locations = new HashSet<SourceLocation>();
189
+
190
+ public String setup() {
191
+ locations.clear();
192
+
193
+ // Make unique prefix to distinguish older find-definition command results.
194
+ return prefix = FIND_DEFINITION_METHOD_NAME_PREFIX + counter++;
195
+ }
196
+
197
+ public Collection<SourceLocation> getLocations() {
198
+ return locations;
199
+ }
200
+
201
+ public void update(Event event) {
202
+ CallVertex vertex;
203
+ if (context.main
204
+ && event.type == EventType.METHOD_MISSING
205
+ && (vertex = (CallVertex) event.vertex) != null
206
+ && vertex.getName().startsWith(prefix)
207
+ && vertex.getReceiverVertex() != null) {
208
+ String realName = vertex.getName().substring(prefix.length());
209
+ for (IRubyObject receiver : vertex.getReceiverVertex().getTypeSet()) {
210
+ RubyClass receiverType = receiver.getMetaClass();
211
+ if (receiverType != null) {
212
+ SourceLocation location = null;
213
+
214
+ // Try to find method
215
+ // TODO callSuper
216
+ Method method = (Method) receiverType.searchMethod(realName);
217
+ if (method != null) {
218
+ if (method.getLocation() != null)
219
+ locations.add(method.getLocation());
220
+ } else {
221
+ // Try to find constant
222
+ RubyModule klass = null;
223
+ if (receiverType instanceof MetaClass) {
224
+ MetaClass metaClass = (MetaClass) receiverType;
225
+ if (metaClass.getAttached() instanceof RubyModule)
226
+ klass = (RubyModule) metaClass.getAttached();
227
+ } else
228
+ klass = context.project.getGraph().getRuntime().getContext().getCurrentScope().getModule();
229
+ if (klass != null) {
230
+ IRubyObject constant = klass.getConstant(realName);
231
+ if (constant instanceof VertexHolder)
232
+ location = SourceLocation.of(((VertexHolder) constant).getVertex().getNode());
233
+ else if (constant instanceof RubyModule)
234
+ location = ((RubyModule) constant).getLocation();
235
+ }
236
+ }
237
+
238
+ if (location != null)
239
+ locations.add(location);
240
+ }
241
+ }
242
+ vertex.cutout();
243
+ }
244
+ }
245
+ }
246
+
247
+ private org.jrubyparser.Parser rubyParser;
248
+ private final Options options;
249
+ private final Context context;
250
+ private Map<String, Project> projects;
251
+ private Project sandbox;
252
+ private FindDefinitionEventListener definitionFinder;
253
+ private WhereEventListener whereListener;
254
+
255
+ private SpecialMethod typeInferenceMethod = new SpecialMethod() {
256
+ public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block blcck, Result result) {
257
+ for (IRubyObject receiver : receivers) {
258
+ context.typeSet.add(receiver);
259
+ }
260
+ result.setResultTypeSet(receivers);
261
+ result.setNeverCallAgain(true); // cutout vertex
262
+ }
263
+ };
264
+
265
+ private SpecialMethod requireMethod = new SpecialMethod() {
266
+ public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block blcck, Result result) {
267
+ if (args != null && args.length > 0) {
268
+ String feature = Vertex.getString(args[0]);
269
+ if (feature != null) {
270
+ require(context.project, feature, "UTF-8");
271
+ }
272
+ }
273
+ }
274
+ };
275
+
276
+ private SpecialMethod requireNextMethod = new SpecialMethod() {
277
+ public void call(Ruby runtime, TypeSet receivers, Vertex[] args, Block blcck, Result result) {
278
+ if (context.feature != null) {
279
+ require(context.project, context.feature, "UTF-8", context.loadPathLevel + 1);
280
+ }
281
+ }
282
+ };
283
+
284
+ public CodeAssist(Options options) {
285
+ this.context = new Context();
286
+ this.options = options;
287
+ clear();
288
+ }
289
+
290
+ public void openProject(String path, Options options) {
291
+ File dir = new File(path);
292
+ if (dir.isDirectory()) {
293
+ openProject(newProject(dir, options));
294
+ } else {
295
+ Logger.message("failed to open project: %s", path);
296
+ }
297
+ }
298
+
299
+ public void openProject(Project project) {
300
+ projects.put(project.getName(), project);
301
+ Logger.message("project opened: %s", project.getName());
302
+ }
303
+
304
+ public void closeProject(String name) {
305
+ Project project = projects.remove(name);
306
+ if (project != null) {
307
+ Logger.message("project closed: %s", project.getName());
308
+ }
309
+ }
310
+
311
+ public Map<String, Project> getProjects() {
312
+ return projects;
313
+ }
314
+
315
+ public Project getProject(Options options) {
316
+ Project project = projects.get(options.getProject());
317
+ if (project == null && options.isDetectProject()) {
318
+ File file = options.getDetectProject();
319
+ if (file == null && !options.isFileStdin()) {
320
+ file = options.getFile();
321
+ }
322
+ if (file != null) {
323
+ File parent = file;
324
+ while ((parent = parent.getParentFile()) != null) {
325
+ for (String[] list : new String[][] {new String[] {PROJECT_CONFIG_NAME},
326
+ new String[] {"Rakefile.rb"},
327
+ new String[] {"Rakefile"},
328
+ new String[] {"setup.rb"},
329
+ new String[] {"Makefile", "lib"}}) {
330
+ boolean found = true;
331
+ for (String name : list) {
332
+ if (!new File(parent, name).exists()) {
333
+ found = false;
334
+ break;
335
+ }
336
+ }
337
+ if (found) {
338
+ project = getProjectByPath(parent);
339
+ if (project == null) {
340
+ project = newProject(parent, options);
341
+ }
342
+ return project;
343
+ }
344
+ }
345
+ }
346
+ }
347
+ }
348
+
349
+ return project != null ? project : sandbox;
350
+ }
351
+
352
+ public Project getProjectByPath(File path) {
353
+ for (Project project : projects.values()) {
354
+ if (project.getPath().equals(path)) {
355
+ return project;
356
+ }
357
+ }
358
+ return null;
359
+ }
360
+
361
+ private Project newProject(File path, Options options) {
362
+ File config = new File(path, PROJECT_CONFIG_NAME);
363
+ if (config.isFile()) {
364
+ options.loadConfig(config);
365
+ } else {
366
+ // Guess config
367
+ options.addOption("load-path", "lib");
368
+ }
369
+
370
+ String name = options.getName();
371
+ if (name == null) {
372
+ name = path.getName();
373
+ }
374
+ Project project = new Project(name, path);
375
+ project.setLoadPath(options.getLoadPath());
376
+ project.setGemPath(options.getGemPath());
377
+ openProject(project);
378
+
379
+ return project;
380
+ }
381
+
382
+ public LoadResult load(Project project, File file, String encoding) {
383
+ return load(project, file, encoding, true);
384
+ }
385
+
386
+ private LoadResult load(Project project, File file, String encoding, boolean prepare) {
387
+ if (project.isLoaded(file.getPath())) {
388
+ return LoadResult.alreadyLoaded();
389
+ }
390
+ project.setLoaded(file.getPath());
391
+
392
+ try {
393
+ InputStream in = new FileInputStream(file);
394
+ try {
395
+ return load(project, file, new InputStreamReader(in, encoding), prepare);
396
+ } finally {
397
+ in.close();
398
+ }
399
+ } catch (IOException e) {
400
+ return LoadResult.failWithException("Cannot open file", e);
401
+ }
402
+ }
403
+
404
+ public LoadResult load(Project project, File file, Reader reader) {
405
+ return load(project, file, reader, true);
406
+ }
407
+
408
+ private LoadResult load(Project project, File file, Reader reader, boolean prepare) {
409
+ boolean oldMain = context.main;
410
+ try {
411
+ if (prepare)
412
+ prepare(project);
413
+ else
414
+ context.main = false;
415
+ Node ast = parseFileContents(file, readAll(reader));
416
+ project.getGraph().load(ast);
417
+
418
+ LoadResult result = new LoadResult();
419
+ result.setAST(ast);
420
+ return result;
421
+ } catch (IOException e) {
422
+ return LoadResult.failWithException("Cannot load file", e);
423
+ } finally {
424
+ context.main = oldMain;
425
+ }
426
+ }
427
+
428
+ public LoadResult require(Project project, String feature, String encoding) {
429
+ return require(project, feature, encoding, 0);
430
+ }
431
+
432
+ private LoadResult require(Project project, String feature, String encoding, int loadPathLevel) {
433
+ if (project.isLoaded(feature)) {
434
+ return LoadResult.alreadyLoaded();
435
+ }
436
+ project.setLoaded(feature);
437
+ Logger.info("feature required: %s", feature);
438
+
439
+ if (File.pathSeparator.equals(";")) { // Windows?
440
+ feature = feature.replace('/', '\\');
441
+ }
442
+
443
+ List<File> loadPath = project.getLoadPath();
444
+ int loadPathLen = loadPath.size();
445
+ for (int i = loadPathLevel; i < loadPathLen; i++) {
446
+ File pathElement = loadPath.get(i);
447
+ int oldLoadPathLevel = context.loadPathLevel;
448
+ context.loadPathLevel = i;
449
+ String oldFeature = context.feature;
450
+ context.feature = feature;
451
+ try {
452
+ File file = new File(pathElement, feature + ".rb");
453
+ if (file.exists()) {
454
+ return load(project, file, encoding, false);
455
+ }
456
+ } finally {
457
+ context.feature = oldFeature;
458
+ context.loadPathLevel = oldLoadPathLevel;
459
+ }
460
+ }
461
+
462
+ List<File> gemPath = project.getGemPath();
463
+ int gemPathLen = gemPath.size();
464
+ String sep = File.separator;
465
+ for (int i = 0; i < gemPathLen; i++) {
466
+ File pathElement = gemPath.get(i);
467
+ int oldLoadPathLevel = context.loadPathLevel;
468
+ context.loadPathLevel = i + loadPathLen;
469
+ String oldFeature = context.feature;
470
+ context.feature = feature;
471
+ try {
472
+ File gemsDir = new File(pathElement, "gems");
473
+ String[] gems = gemsDir.list();
474
+ if (gems != null) {
475
+ for (String gem : gems) {
476
+ File file = new File(gemsDir + sep + gem + sep + "lib" + sep + feature + ".rb");
477
+ if (file.exists()) {
478
+ return load(project, file, encoding, false);
479
+ }
480
+ }
481
+ }
482
+ } finally {
483
+ context.feature = oldFeature;
484
+ context.loadPathLevel = oldLoadPathLevel;
485
+ }
486
+ }
487
+
488
+ Logger.warn("cannot require: %s", feature);
489
+ return LoadResult.failWithNotFound();
490
+ }
491
+
492
+ public TypeInferenceResult typeInference(Project project, File file, String encoding, Location loc) {
493
+ try {
494
+ InputStream in = new FileInputStream(file);
495
+ try {
496
+ return typeInference(project, file, new InputStreamReader(in, encoding), loc);
497
+ } finally {
498
+ in.close();
499
+ }
500
+ } catch (IOException e) {
501
+ return TypeInferenceResult.failWithException("Cannot open file", e);
502
+ }
503
+ }
504
+
505
+ public TypeInferenceResult typeInference(Project project, File file, Reader reader, Location loc) {
506
+ try {
507
+ prepare(project);
508
+ Node ast = parseFileContents(file, readAndInjectCode(reader, loc, TYPE_INFERENCE_METHOD_NAME, "(?:\\.|::)", "."));
509
+ project.getGraph().load(ast);
510
+
511
+ TypeInferenceResult result = new TypeInferenceResult();
512
+ result.setAST(ast);
513
+ TypeSet ts = new TypeSet();
514
+ for (IRubyObject receiver : context.typeSet) {
515
+ ts.add(receiver.getMetaClass());
516
+ }
517
+ result.setTypeSet(ts);
518
+ return result;
519
+ } catch (IOException e) {
520
+ return TypeInferenceResult.failWithException("Cannot read file", e);
521
+ }
522
+ }
523
+
524
+ public CodeCompletionResult codeCompletion(Project project, File file, String encoding, Location loc) {
525
+ try {
526
+ InputStream in = new FileInputStream(file);
527
+ try {
528
+ return codeCompletion(project, file, new InputStreamReader(in, encoding), loc);
529
+ } finally {
530
+ in.close();
531
+ }
532
+ } catch (IOException e) {
533
+ return CodeCompletionResult.failWithException("Cannot open file", e);
534
+ }
535
+ }
536
+
537
+ public CodeCompletionResult codeCompletion(Project project, File file, Reader reader, Location loc) {
538
+ try {
539
+ prepare(project);
540
+ Node ast = parseFileContents(file, readAndInjectCode(reader, loc, TYPE_INFERENCE_METHOD_NAME, "(?:\\.|::)", "."));
541
+ project.getGraph().load(ast);
542
+
543
+ CodeCompletionResult result = new CodeCompletionResult();
544
+ result.setAST(ast);
545
+
546
+ List<CompletionCandidate> candidates = new ArrayList<CompletionCandidate>();
547
+ for (IRubyObject receiver : context.typeSet) {
548
+ RubyClass rubyClass = receiver.getMetaClass();
549
+ for (String name : rubyClass.getMethods(true)) {
550
+ DynamicMethod method = rubyClass.searchMethod(name);
551
+ candidates.add(new CompletionCandidate(name,
552
+ method.toString(),
553
+ method.getModule().getMethodPath(null),
554
+ CompletionCandidate.Kind.METHOD));
555
+ }
556
+ if (receiver instanceof RubyModule) {
557
+ RubyModule module = ((RubyModule) receiver);
558
+ for (String name : module.getConstants(true)) {
559
+ RubyModule directModule = module.getConstantModule(name);
560
+ IRubyObject constant = directModule.getConstant(name);
561
+ String baseName = directModule.toString();
562
+ String qname = baseName + "::" + name;
563
+ CompletionCandidate.Kind kind
564
+ = (constant instanceof RubyClass)
565
+ ? CompletionCandidate.Kind.CLASS
566
+ : (constant instanceof RubyModule)
567
+ ? CompletionCandidate.Kind.MODULE
568
+ : CompletionCandidate.Kind.CONSTANT;
569
+ candidates.add(new CompletionCandidate(name, qname, baseName, kind));
570
+ }
571
+ }
572
+ }
573
+
574
+ result.setCandidates(candidates);
575
+ return result;
576
+ } catch (IOException e) {
577
+ return CodeCompletionResult.failWithException("Cannot read file", e);
578
+ }
579
+ }
580
+
581
+ public WhereResult where(Project project, File file, String encoding, int line) {
582
+ try {
583
+ InputStream in = new FileInputStream(file);
584
+ try {
585
+ return where(project, file, new InputStreamReader(in, encoding), line);
586
+ } finally {
587
+ in.close();
588
+ }
589
+ } catch (IOException e) {
590
+ return WhereResult.failWithException("Cannot open file", e);
591
+ }
592
+ }
593
+
594
+ public WhereResult where(Project project, File file, Reader reader, int line) {
595
+ try {
596
+ prepare(project);
597
+ Node ast = parseFileContents(file, readAll(reader));
598
+
599
+ whereListener.prepare(line);
600
+ project.addEventListener(whereListener);
601
+ try {
602
+ project.getGraph().load(ast);
603
+ } finally {
604
+ project.removeEventListener(whereListener);
605
+ }
606
+
607
+ WhereResult result = new WhereResult();
608
+ result.setAST(ast);
609
+ result.setName(whereListener.getName());
610
+
611
+ return result;
612
+ } catch (IOException e) {
613
+ return WhereResult.failWithException("Cannot read file", e);
614
+ }
615
+ }
616
+
617
+ public FindDefinitionResult findDefinition(Project project, File file, String encoding, Location loc) {
618
+ try {
619
+ InputStream in = new FileInputStream(file);
620
+ try {
621
+ return findDefinition(project, file, new InputStreamReader(in, encoding), loc);
622
+ } finally {
623
+ in.close();
624
+ }
625
+ } catch (IOException e) {
626
+ return FindDefinitionResult.failWithException("Cannot open file", e);
627
+ }
628
+ }
629
+
630
+ public FindDefinitionResult findDefinition(Project project, File file, Reader reader, Location loc) {
631
+ try {
632
+ prepare(project);
633
+ Node ast = parseFileContents(file, readAndInjectCode(reader, loc, definitionFinder.setup(), "(?:\\.|::|\\s)(\\w+?[!?]?)", null));
634
+
635
+ project.addEventListener(definitionFinder);
636
+ try {
637
+ project.getGraph().load(ast);
638
+ } finally {
639
+ project.removeEventListener(definitionFinder);
640
+ }
641
+
642
+ FindDefinitionResult result = new FindDefinitionResult();
643
+ result.setAST(ast);
644
+
645
+ result.setLocations(definitionFinder.getLocations());
646
+
647
+ return result;
648
+ } catch (IOException e) {
649
+ return FindDefinitionResult.failWithException("Cannot read file", e);
650
+ }
651
+ }
652
+
653
+ public void clear() {
654
+ this.rubyParser = new org.jrubyparser.Parser(); // for parse
655
+ this.context.clear();
656
+ this.projects = new HashMap<String, Project>();
657
+ this.sandbox = new Project("(sandbox)", new File("."));
658
+ this.sandbox.setLoadPath(options.getLoadPath());
659
+ this.sandbox.setGemPath(options.getGemPath());
660
+ this.definitionFinder = new FindDefinitionEventListener();
661
+ this.whereListener = new WhereEventListener();
662
+ openProject(this.sandbox);
663
+ }
664
+
665
+ private void prepare(Project project) {
666
+ context.project = project;
667
+ context.typeSet = new TypeSet();
668
+ context.main = true;
669
+
670
+ Graph graph = project.getGraph();
671
+ graph.addSpecialMethod(TYPE_INFERENCE_METHOD_NAME, typeInferenceMethod);
672
+ graph.addSpecialMethod("require", requireMethod);
673
+ graph.addSpecialMethod("require_next", requireNextMethod);
674
+
675
+ require(project, "_builtin", "UTF-8");
676
+ }
677
+
678
+ private String readAll(Reader reader) throws IOException {
679
+ return readAndInjectCode(reader, null, null, null, null);
680
+ }
681
+
682
+ private String readAndInjectCode(Reader _reader, Location loc, String injection, String prefixPattern, String defaultPrefix) throws IOException {
683
+ LineNumberReader reader = new LineNumberReader(_reader);
684
+ int line = reader.getLineNumber() + 1;
685
+ int offset = -1;
686
+ char[] buf = new char[4096];
687
+ int read;
688
+ int len = 0;
689
+ StringBuilder buffer = new StringBuilder();
690
+ while ((read = reader.read(buf)) != -1) {
691
+ int index = 0;
692
+ if (loc != null) {
693
+ if (offset == -1) {
694
+ offset = loc.findOffset(len, line, buf, read);
695
+ }
696
+ for (int i = 0; i < read; i++) {
697
+ if (Character.isHighSurrogate(buf[i]) || buf[i] == '\r') {
698
+ } else {
699
+ len++;
700
+ if (len == offset) {
701
+ index = i + 1;
702
+
703
+ int pstart = Math.max(0, index - 128);
704
+ String pbuf = new String(buf, pstart, index - pstart);
705
+ Matcher matcher = Pattern.compile(".*" + prefixPattern, Pattern.DOTALL).matcher(pbuf);
706
+ boolean match = matcher.matches();
707
+ if (match) {
708
+ if (matcher.groupCount() > 0) {
709
+ int end = index - (pbuf.length() - matcher.start(1));
710
+ buffer.append(buf, 0, end);
711
+ buffer.append(injection);
712
+ buffer.append(buf, end, index - end);
713
+ } else {
714
+ buffer.append(buf, 0, index);
715
+ buffer.append(injection);
716
+ }
717
+ } else {
718
+ buffer.append(buf, 0, index);
719
+ if (defaultPrefix != null)
720
+ buffer.append(defaultPrefix);
721
+ buffer.append(injection);
722
+ }
723
+
724
+ index += loc.getSkip();
725
+ break;
726
+ }
727
+ }
728
+ }
729
+ }
730
+ buffer.append(buf, index, read - index);
731
+ }
732
+ return buffer.toString();
733
+ }
734
+
735
+ public Node parseFileContents(File file, String string) {
736
+ StringReader in = new StringReader(string);
737
+ CompatVersion version = CompatVersion.RUBY1_8;
738
+ //CompatVersion versionTwo = CompatVersion.RUBY1_9;
739
+ CompatVersion versionTwo = CompatVersion.RUBY2_0;
740
+ ParserConfiguration config = new ParserConfiguration(0, versionTwo);
741
+ config.setSyntax(ParserConfiguration.SyntaxGathering.COMMENTS);
742
+ return rubyParser.parse(file.getPath(), in, config);
743
+ }
744
+ }