granita 0.0.3-aarch64-linux → 0.0.4-aarch64-linux

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 73a04013e2c85fbfaba2f867b680f4ba5786315364f016eff3c15ca7bd8c8954
4
- data.tar.gz: 2549c3b46a84eff37b18aa03f378970832f3b2378b1cc7d2c6ec2ca492accda4
3
+ metadata.gz: b33f8759d6ec6a83591a4d88ee834c7be6d862990e081bc9579bb3aa4499868b
4
+ data.tar.gz: 782a66757a36cd6f8fc619bca524277065bbfea6562dcf78f61b8c44b4b49a90
5
5
  SHA512:
6
- metadata.gz: d4689fe88702b6f316da59eebc91189b4139e5ca61db25d7c49171f3e5d7150601459854f61fecd1e353af88ba8be4ee55034e3d407deabb1fd1f1f9810f4bd9
7
- data.tar.gz: d2b0857372abf5f793255e42d764664f13eddcc8710b3b8677a45ab3a16552cf3354975fe380e80713e99362d07948c0d90e47ccca2defe3fdb4b1022b8016ca
6
+ metadata.gz: 07c5437c70e3bc9434bafe3e1a1d8b26fb6072149e98b5d9f902f2bc350e7bab9f1939bf1b38669c83a3673ec87dbdbaa7e4e7e8d792fd39374311cb6a169ee5
7
+ data.tar.gz: da11cbc755206de9c2841871fafd68751f99e88c1957b7ce9ca1740476286031ed2244b29903af31d5272489aafc054c55e64347ef7dac60335e51a6c3337b07
data/README.md CHANGED
@@ -14,4 +14,37 @@ Granita is in beta and is meant as a drop-in replacement for `srb tc`.
14
14
  granita # typecheck the project described by sorbet/config
15
15
  granita --lsp # run the language server
16
16
 
17
+ ## Semantic graph API
18
+
19
+ The native graph API runs Granita's semantic pipeline and exposes an immutable query snapshot:
20
+
21
+ ```ruby
22
+ require "granita"
23
+
24
+ graph = Granita::Graph.index(Dir["lib/**/*.rb"])
25
+
26
+ graph.documents.each { |document| puts document.path }
27
+ graph.definitions.each { |definition| puts definition.full_name }
28
+ graph.declarations.each { |declaration| puts declaration.full_name }
29
+ graph.constant_references.each { |reference| puts reference.name }
30
+ graph.calls.each do |call|
31
+ puts "#{call.receiver_types.join(' | ')}##{call.name} -> #{call.target_ids.join(', ')}"
32
+ end
33
+
34
+ product = graph["Shop::Product"]
35
+ puts product.definition_ids
36
+
37
+ result = graph.cypher(<<~CYPHER)
38
+ MATCH (call:Call)-[:TARGETS]->(method:Method)
39
+ WHERE call.receiver_type CONTAINS 'Shop::Product'
40
+ RETURN call.name, method.name
41
+ CYPHER
42
+ result.rows.each { |row| puts row }
43
+ puts result.render(:table)
44
+ ```
45
+
46
+ For editor buffers, generated source, or non-UTF-8 Ruby, use `Granita::Graph.from_sources("path.rb" => source)`; source bytes are preserved. Calls are present for typed and untyped files. Inferred calls expose `receiver_types` and resolved `target_ids`; uninferred or unresolved calls leave both arrays empty.
47
+
48
+ Locations contain zero-based, end-exclusive byte offsets. `Definition#location` covers the full construct and `#declaration_location` identifies its header/name. Declarations from the bundled RBI payload that are referenced by workspace code have `external == true`. `Granita::Query.parse` allows a parsed read-only Cypher query to be reused across graph snapshots, and `Granita::Query.schema(:json)` describes the available labels, properties, and relationships.
49
+
17
50
  Set `SRB_SORBET_EXE` to run a different typechecker binary.
@@ -0,0 +1,56 @@
1
+ #ifndef GRANITA_GRAPH_H
2
+ #define GRANITA_GRAPH_H
3
+
4
+ #include <stddef.h>
5
+
6
+ #ifdef __cplusplus
7
+ extern "C" {
8
+ #endif
9
+
10
+ /** Opaque, immutable semantic graph. */
11
+ typedef struct GranitaGraph GranitaGraph;
12
+ /** Opaque, reusable parsed Cypher query. */
13
+ typedef struct GranitaQuery GranitaQuery;
14
+
15
+ /**
16
+ * Builds a graph from UTF-8 JSON. Accepted inputs are:
17
+ * {"paths":["lib/a.rb", "lib/b.rb"]}
18
+ * {"sources":[{"path":"a.rb", "source":[99,108,97,115,115,32,65,59,32,101,110,100]}]}
19
+ *
20
+ * Returns NULL on failure and stores an allocated error in *error_out when error_out is non-NULL.
21
+ */
22
+ GranitaGraph *granita_graph_new_json(const char *input, char **error_out);
23
+ void granita_graph_free(GranitaGraph *graph);
24
+
25
+ /**
26
+ * Returns an allocated UTF-8 JSON string for one collection. `collection` is one of
27
+ * "documents", "definitions", "declarations", "constant_references", "calls", or "all".
28
+ * Returns NULL on failure and stores an allocated error in *error_out when non-NULL.
29
+ */
30
+ char *granita_graph_query_json(const GranitaGraph *graph, const char *collection, char **error_out);
31
+ /** Parses a read-only Cypher query. */
32
+ GranitaQuery *granita_query_parse(const char *query, char **error_out);
33
+ void granita_query_free(GranitaQuery *query);
34
+
35
+ /**
36
+ * Executes a parsed Cypher query and returns a JSON array of row objects. Returned node values
37
+ * include stable `id`, `label`, and `name` fields.
38
+ */
39
+ char *granita_query_execute_json(
40
+ const GranitaGraph *graph,
41
+ const GranitaQuery *query,
42
+ char **error_out
43
+ );
44
+
45
+ /** Returns the queryable schema. `format` is "json" or "table". */
46
+ char *granita_query_schema(const char *format, char **error_out);
47
+
48
+
49
+ /** Frees strings returned by this API, including errors. */
50
+ void granita_string_free(char *string);
51
+
52
+ #ifdef __cplusplus
53
+ }
54
+ #endif
55
+
56
+ #endif
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fiddle/import"
4
+ require "json"
5
+ require "rbconfig"
6
+
7
+ module Granita
8
+ class Graph
9
+ Document = Struct.new(:id, :path, :definition_ids, :constant_reference_ids, :call_ids, keyword_init: true)
10
+ Definition = Struct.new(:id, :declaration_id, :kind, :name, :full_name, :location, :declaration_location, :line, keyword_init: true)
11
+ Declaration = Struct.new(:id, :kind, :name, :full_name, :owner_id, :definition_ids, :constant_reference_ids, :visibility, :external, keyword_init: true)
12
+ ConstantReference = Struct.new(:id, :name, :target_id, :location, keyword_init: true)
13
+ Call = Struct.new(:id, :name, :receiver_types, :target_ids, :location, keyword_init: true)
14
+ Location = Struct.new(:document_id, :start, :end, keyword_init: true)
15
+
16
+ module Native
17
+ extend Fiddle::Importer
18
+
19
+ extension = RbConfig::CONFIG.fetch("DLEXT")
20
+ default = File.expand_path("../../libexec/granita_graph.#{extension}", __dir__)
21
+ dlload ENV.fetch("GRANITA_GRAPH_LIBRARY", default)
22
+
23
+ extern "void *granita_graph_new_json(const char *, void *)"
24
+ extern "void granita_graph_free(void *)"
25
+ extern "char *granita_graph_query_json(void *, const char *, void *)"
26
+ extern "void *granita_query_parse(const char *, void *)"
27
+ extern "void granita_query_free(void *)"
28
+ extern "char *granita_query_execute_json(void *, void *, void *)"
29
+ extern "char *granita_query_schema(const char *, void *)"
30
+ extern "void granita_string_free(void *)"
31
+ end
32
+
33
+ class << self
34
+ def index(paths)
35
+ new(paths: Array(paths).map { |path| File.expand_path(path) })
36
+ end
37
+
38
+ def from_sources(sources)
39
+ entries = sources.map { |path, source| {path: path.to_s, source: source.to_s.b.bytes} }
40
+ new(sources: entries)
41
+ end
42
+
43
+ private :new
44
+ end
45
+
46
+ def initialize(input)
47
+ error = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP)
48
+ error[0, Fiddle::SIZEOF_VOIDP] = [0].pack(pointer_pack)
49
+ address = Native.granita_graph_new_json(JSON.generate(input), error)
50
+ raise_native_error(error) if address.to_i.zero?
51
+
52
+ @pointer = Fiddle::Pointer.new(address.to_i, 0, Native["granita_graph_free"])
53
+ end
54
+
55
+ def documents
56
+ query("documents").map { |row| Document.new(**symbolize(row)) }
57
+ end
58
+
59
+ def definitions
60
+ query("definitions").map do |row|
61
+ values = record(row)
62
+ values[:declaration_location] = Location.new(**symbolize(values.fetch(:declaration_location)))
63
+ Definition.new(**values)
64
+ end
65
+ end
66
+
67
+ def declarations
68
+ query("declarations").map { |row| Declaration.new(**symbolize(row)) }
69
+ end
70
+
71
+ def constant_references
72
+ query("constant_references").map { |row| ConstantReference.new(**record(row)) }
73
+ end
74
+
75
+ def calls
76
+ query("calls").map { |row| Call.new(**record(row)) }
77
+ end
78
+
79
+ def declaration(name)
80
+ declarations.find { |declaration| declaration.full_name == name || declaration.full_name == name.delete_prefix("::") }
81
+ end
82
+ alias_method :[], :declaration
83
+ def cypher(query)
84
+ Granita::Query.parse(query).run(self)
85
+ end
86
+
87
+
88
+ private
89
+
90
+ def native_handle
91
+ @pointer
92
+ end
93
+
94
+ def resolve_query_node(encoded)
95
+ kind, id = encoded.split(":", 2)
96
+ id = Integer(id, exception: false)
97
+ return unless id
98
+
99
+ @query_node_indexes ||= {}
100
+ records = case kind
101
+ when "doc"
102
+ @query_node_indexes[:documents] ||= documents.to_h { |record| [record.id, record] }
103
+ when "def"
104
+ @query_node_indexes[:definitions] ||= definitions.to_h { |record| [record.id, record] }
105
+ when "decl"
106
+ @query_node_indexes[:declarations] ||= declarations.to_h { |record| [record.id, record] }
107
+ when "call"
108
+ @query_node_indexes[:calls] ||= calls.to_h { |record| [record.id, record] }
109
+ else
110
+ return
111
+ end
112
+ records[id]
113
+ end
114
+
115
+ def query(collection)
116
+ error = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP)
117
+ error[0, Fiddle::SIZEOF_VOIDP] = [0].pack(pointer_pack)
118
+ result = Native.granita_graph_query_json(@pointer, collection, error)
119
+ raise_native_error(error) if result.to_i.zero?
120
+
121
+ pointer = Fiddle::Pointer.new(result.to_i)
122
+ json = pointer.to_s
123
+ Native.granita_string_free(pointer)
124
+ JSON.parse(json)
125
+ end
126
+
127
+ def record(row)
128
+ values = symbolize(row)
129
+ values[:location] = Location.new(**symbolize(values.fetch(:location)))
130
+ values
131
+ end
132
+
133
+ def symbolize(hash)
134
+ hash.transform_keys!(&:to_sym)
135
+ end
136
+
137
+ def raise_native_error(error)
138
+ address = error[0, Fiddle::SIZEOF_VOIDP].unpack1(pointer_pack)
139
+ message = "Granita graph operation failed"
140
+ unless address.zero?
141
+ pointer = Fiddle::Pointer.new(address)
142
+ message = pointer.to_s
143
+ Native.granita_string_free(pointer)
144
+ end
145
+ raise ArgumentError, message
146
+ end
147
+
148
+ def pointer_pack
149
+ "J"
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "graph"
4
+
5
+ module Granita
6
+ class Query
7
+ class SyntaxError < ArgumentError; end
8
+ class ExecutionError < StandardError; end
9
+
10
+ class Result
11
+ attr_reader :rows
12
+
13
+ def initialize(rows, raw_rows)
14
+ @rows = rows.freeze
15
+ @raw_rows = raw_rows.freeze
16
+ end
17
+
18
+ def render(format = :table)
19
+ case format.to_sym
20
+ when :json
21
+ JSON.generate(@raw_rows)
22
+ when :table
23
+ render_table
24
+ else
25
+ raise ArgumentError, "unknown query output format `#{format}`"
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def render_table
32
+ return "" if @raw_rows.empty?
33
+
34
+ columns = @raw_rows.flat_map(&:keys).uniq
35
+ values = @raw_rows.map do |row|
36
+ columns.map { |column| display(row[column]) }
37
+ end
38
+ widths = columns.each_index.map do |index|
39
+ ([columns[index].length] + values.map { |row| row[index].length }).max
40
+ end
41
+ lines = []
42
+ lines << columns.each_index.map { |index| columns[index].ljust(widths[index]) }.join(" | ")
43
+ lines << widths.map { |width| "-" * width }.join("-+-")
44
+ values.each do |row|
45
+ lines << row.each_index.map { |index| row[index].ljust(widths[index]) }.join(" | ")
46
+ end
47
+ lines << ""
48
+ lines << "#{values.length} #{values.length == 1 ? "row" : "rows"}"
49
+ "#{lines.join("\n")}\n"
50
+ end
51
+
52
+ def display(value)
53
+ case value
54
+ when Hash
55
+ value["name"] || JSON.generate(value)
56
+ when Array
57
+ "[#{value.map { |item| display(item) }.join(", ")}]"
58
+ when nil
59
+ ""
60
+ else
61
+ value.to_s
62
+ end
63
+ end
64
+ end
65
+
66
+ class << self
67
+ def parse(source)
68
+ address = call_native(SyntaxError) do |error|
69
+ Graph::Native.granita_query_parse(source.to_s, error)
70
+ end
71
+ new(address)
72
+ end
73
+
74
+ def schema(format = :table)
75
+ address = call_native(ArgumentError) do |error|
76
+ Graph::Native.granita_query_schema(format.to_s, error)
77
+ end
78
+ take_string(address)
79
+ end
80
+
81
+ private
82
+
83
+ def call_native(error_class)
84
+ error = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP)
85
+ error[0, Fiddle::SIZEOF_VOIDP] = [0].pack("J")
86
+ address = yield(error)
87
+ return address unless address.to_i.zero?
88
+
89
+ pointer_address = error[0, Fiddle::SIZEOF_VOIDP].unpack1("J")
90
+ message = "Granita query operation failed"
91
+ unless pointer_address.zero?
92
+ pointer = Fiddle::Pointer.new(pointer_address)
93
+ message = pointer.to_s
94
+ Graph::Native.granita_string_free(pointer)
95
+ end
96
+ raise error_class, message
97
+ end
98
+
99
+ def take_string(address)
100
+ pointer = Fiddle::Pointer.new(address.to_i)
101
+ value = pointer.to_s
102
+ Graph::Native.granita_string_free(pointer)
103
+ value
104
+ end
105
+
106
+ private :new
107
+ end
108
+
109
+ def initialize(address)
110
+ @pointer = Fiddle::Pointer.new(address.to_i, 0, Graph::Native["granita_query_free"])
111
+ end
112
+
113
+ def run(graph)
114
+ address = self.class.__send__(:call_native, ExecutionError) do |error|
115
+ Graph::Native.granita_query_execute_json(
116
+ graph.__send__(:native_handle),
117
+ @pointer,
118
+ error,
119
+ )
120
+ end
121
+ raw_rows = JSON.parse(self.class.__send__(:take_string, address))
122
+ rows = raw_rows.map do |row|
123
+ row.transform_values { |value| convert_value(graph, value) }
124
+ end
125
+ Result.new(rows, raw_rows)
126
+ end
127
+
128
+ private
129
+
130
+ def convert_value(graph, value)
131
+ case value
132
+ when Array
133
+ value.map { |item| convert_value(graph, item) }
134
+ when Hash
135
+ if value["id"].is_a?(String) && value["id"].match?(/\A(?:doc|def|decl|call):\d+\z/)
136
+ graph.__send__(:resolve_query_node, value["id"]) || value
137
+ else
138
+ value.transform_values { |item| convert_value(graph, item) }
139
+ end
140
+ else
141
+ value
142
+ end
143
+ end
144
+ end
145
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Granita
4
- VERSION = "0.0.3"
4
+ VERSION = "0.0.4"
5
5
  end
data/lib/granita.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require_relative "granita/version"
4
4
 
5
5
  module Granita
6
+ autoload :Graph, File.expand_path("granita/graph", __dir__)
7
+ autoload :Query, File.expand_path("granita/query", __dir__)
6
8
  # Absolute path to the bundled typechecker binary.
7
9
  def self.binary_path
8
10
  File.expand_path("../libexec/sorbet", __dir__)
@@ -0,0 +1,4 @@
1
+ [diffend] Oversized file quarantined before diffing.
2
+ name: data/libexec/granita_graph.so
3
+ size: 46990064 bytes
4
+ sha256: 4e77b34ec247642c8d05af2130fe5402d1c0a86c96429e1eb50b48146f722b13
data/libexec/sorbet CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: data/libexec/sorbet
3
- size: 60919864 bytes
4
- sha256: 5bf02070ca52a26ddc2579e454143d040df9a7db2d633105ef8d49a2ac63bc06
3
+ size: 60922008 bytes
4
+ sha256: ac6d6a3025048011b933edfeead667a7b2a7656d25edfedd95f9bee015c1dc6c
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: granita
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Alexandre Terrasa
@@ -18,8 +18,12 @@ files:
18
18
  - NOTICE
19
19
  - README.md
20
20
  - exe/granita
21
+ - include/granita_graph.h
21
22
  - lib/granita.rb
23
+ - lib/granita/graph.rb
24
+ - lib/granita/query.rb
22
25
  - lib/granita/version.rb
26
+ - libexec/granita_graph.so
23
27
  - libexec/sorbet
24
28
  homepage: https://github.com/Morriar/granita
25
29
  licenses: