@suss/adapter-ruby 0.3.2-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.
- package/LICENSE +205 -0
- package/README.md +49 -0
- package/dist/index.cjs +927 -0
- package/dist/index.d.cts +256 -0
- package/dist/index.d.ts +256 -0
- package/dist/index.js +869 -0
- package/grammar/NOTICE +28 -0
- package/grammar/README.md +35 -0
- package/grammar/tree-sitter-ruby.wasm +0 -0
- package/package.json +64 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { Tree, Node } from 'web-tree-sitter';
|
|
2
|
+
export { Node as RbNode, Tree as RbTree } from 'web-tree-sitter';
|
|
3
|
+
import { RawCodeStructure } from '@suss/extractor';
|
|
4
|
+
import { TypeShape, BehavioralSummary } from '@suss/behavioral-ir';
|
|
5
|
+
import { Database } from '@suss/datalog';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Finds the file behind a constant.
|
|
9
|
+
*
|
|
10
|
+
* A Ruby codebase locates that file by a naming convention rather than through a
|
|
11
|
+
* load graph a static reader could follow, so we build one path from the
|
|
12
|
+
* constant's own name and check it once against the configured root. Nothing
|
|
13
|
+
* here searches several roots or chooses between candidates.
|
|
14
|
+
*/
|
|
15
|
+
/** A pack picks a convention by name, and the code for each one lives here. */
|
|
16
|
+
type ConstantPathConvention = "railsUnderscore";
|
|
17
|
+
/** Ported from ActiveSupport's own `String#underscore`, which is what Rails autoloading runs a constant path through. */
|
|
18
|
+
declare function underscoreConstantPath(qualifiedName: string): string;
|
|
19
|
+
/** Null when there is no file at that path. We do not fall back to another root or another spelling. */
|
|
20
|
+
declare function resolveConstantFile(root: string, qualifiedName: string, convention: ConstantPathConvention): string | null;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A new `Parser` for every call, so that parses running at the same time do
|
|
24
|
+
* not fight over one parser's mutable `language` field. The compiled `Language`
|
|
25
|
+
* is loaded once per process and shared.
|
|
26
|
+
*/
|
|
27
|
+
declare function parseRuby(source: string): Promise<Tree>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolves Ruby constant paths against lexical nesting.
|
|
31
|
+
*
|
|
32
|
+
* A compound path written out, like `Data::Record`, means the same thing
|
|
33
|
+
* wherever it appears, so its text is the qualified name. A bare name is
|
|
34
|
+
* relative to the nesting it is written inside. `require` is never resolved,
|
|
35
|
+
* because Rails autoloads by naming convention rather than through a load graph
|
|
36
|
+
* a static reader could follow, so a constant that nesting alone cannot qualify
|
|
37
|
+
* keeps whatever name the source wrote and callers treat it as unresolved.
|
|
38
|
+
*
|
|
39
|
+
* Ruby's own `Module.nesting` puts a newly opened class or module on the front
|
|
40
|
+
* of the chain already in effect, whether the name used to open it was bare or
|
|
41
|
+
* compound, because nesting follows the class and module keywords in the text
|
|
42
|
+
* and not the form of the name. So even when a class is opened with a compound
|
|
43
|
+
* name, as in `module Api; class Types::CampaignType; end; end`, Ruby still
|
|
44
|
+
* looks up through `Api` inside that body. That matters, because a bare
|
|
45
|
+
* reference in there is resolved by trying each level of the chain, and dropping
|
|
46
|
+
* `Api` would miss a class the file itself defines.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Qualifies a name against the innermost level of `nesting` and no further. It
|
|
51
|
+
* returns null for a computed expression or a variable, which callers treat as
|
|
52
|
+
* unresolved. `shadowingClassFor` is the one place that walks every level.
|
|
53
|
+
*/
|
|
54
|
+
declare function qualifyConstantRef(node: Node, nesting: readonly string[]): string | null;
|
|
55
|
+
interface ClassInfo {
|
|
56
|
+
node: Node;
|
|
57
|
+
/** Which keyword opened it. A module has no superclass and cannot be subclassed, but its methods are available to every class including it. */
|
|
58
|
+
kind: "class" | "module";
|
|
59
|
+
/** This class's own fully-qualified constant path. */
|
|
60
|
+
qualifiedName: string;
|
|
61
|
+
/** Null when it declares no superclass, or the expression is not a literal constant path. */
|
|
62
|
+
superclassQualifiedName: string | null;
|
|
63
|
+
/** `body_statement` node, or null for `class Foo; end`. */
|
|
64
|
+
bodyNode: Node | null;
|
|
65
|
+
/** `Module.nesting` in effect inside this class's own body, innermost first. */
|
|
66
|
+
bodyNesting: readonly string[];
|
|
67
|
+
}
|
|
68
|
+
/** Every `class` declaration reachable from `root` through direct module/class nesting, in source order. */
|
|
69
|
+
declare function walkClasses(root: Node, visit: (info: ClassInfo) => void): void;
|
|
70
|
+
/** The same walk including `module` declarations, for a lookup that has to reach a module's own methods. */
|
|
71
|
+
declare function walkDefinitions(root: Node, visit: (info: ClassInfo) => void): void;
|
|
72
|
+
/** A pack picks a derivation by name, and the code for each one lives here. */
|
|
73
|
+
type GraphqlTypeNameConvention = "stripTypeSuffix";
|
|
74
|
+
/** We never read a per-class name override, so this is always the name the convention derives. */
|
|
75
|
+
declare function graphqlTypeNameFromQualified(qualifiedName: string, convention: GraphqlTypeNameConvention): string;
|
|
76
|
+
|
|
77
|
+
/** One class or module body a walk reached, with the definitions its own file makes, since a bare constant is shadowed per file. */
|
|
78
|
+
interface ReachedBody {
|
|
79
|
+
info: ClassInfo;
|
|
80
|
+
knownClasses: ReadonlySet<string>;
|
|
81
|
+
}
|
|
82
|
+
/** One ancestor: every block reopening its name, or the name alone when nothing reached it. Blocks stay together because Ruby treats a reopened class as one place in the chain. */
|
|
83
|
+
type AncestorEntry = {
|
|
84
|
+
type: "bodies";
|
|
85
|
+
name: string;
|
|
86
|
+
blocks: ReachedBody[];
|
|
87
|
+
} | {
|
|
88
|
+
type: "unfollowed";
|
|
89
|
+
name: string;
|
|
90
|
+
};
|
|
91
|
+
/** A class and everything it inherits from, in Ruby's own method-lookup order. */
|
|
92
|
+
type Ancestry = readonly AncestorEntry[];
|
|
93
|
+
/** What a walk needs to reach a class it knows only by name. */
|
|
94
|
+
interface AncestorLookup {
|
|
95
|
+
/** Directory the constant-to-path convention resolves an ancestor's name against. */
|
|
96
|
+
root: string;
|
|
97
|
+
pathConvention: ConstantPathConvention;
|
|
98
|
+
/**
|
|
99
|
+
* The library's own classes a project's chain ends at. Reaching one
|
|
100
|
+
* ends a walk with nothing left unfollowed.
|
|
101
|
+
*/
|
|
102
|
+
ancestryRootClassNames: readonly string[];
|
|
103
|
+
parsedFile(absPath: string): Promise<Node | null>;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Every block a file defines under `qualifiedName`, or null when the
|
|
107
|
+
* constant-to-path convention points at no file, no file is there, or
|
|
108
|
+
* the file defines nothing by that name.
|
|
109
|
+
*/
|
|
110
|
+
declare function reachDefinition(qualifiedName: string, lookup: AncestorLookup): Promise<ReachedBody[] | null>;
|
|
111
|
+
declare function ancestryOf(name: string, blocks: readonly ReachedBody[], lookup: AncestorLookup): Promise<Ancestry>;
|
|
112
|
+
/** What searching an ancestry for one method name came to. */
|
|
113
|
+
type MethodLookup = {
|
|
114
|
+
type: "found";
|
|
115
|
+
method: Node;
|
|
116
|
+
}
|
|
117
|
+
/** `reason` completes "could be answered by a method ...". */
|
|
118
|
+
| {
|
|
119
|
+
type: "unsettled";
|
|
120
|
+
reason: string;
|
|
121
|
+
} | {
|
|
122
|
+
type: "none";
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* The method `name` resolves to. An ancestor nearer than any definition
|
|
126
|
+
* that nothing could read stops the search: whatever sits further along
|
|
127
|
+
* is not what Ruby would have called.
|
|
128
|
+
*/
|
|
129
|
+
declare function methodInAncestry(ancestry: Ancestry, name: string): MethodLookup;
|
|
130
|
+
/** Every statement of every body reached, most distant ancestor first, so a nearer declaration overwrites what it inherits. */
|
|
131
|
+
declare function inheritedStatements(ancestry: Ancestry): Array<{
|
|
132
|
+
block: ReachedBody;
|
|
133
|
+
statement: Node;
|
|
134
|
+
}>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The Ruby adapter's own pattern-pack contract.
|
|
138
|
+
*
|
|
139
|
+
* Match shapes stay per-language until a second implementation shows what is
|
|
140
|
+
* actually shared, so this is Ruby's own and not the TypeScript or Python
|
|
141
|
+
* adapter's. Everything a library defines, meaning its call names, keywords,
|
|
142
|
+
* scalars, and conventions, arrives as pack data and is never hardcoded here.
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
interface RubyPack {
|
|
146
|
+
name: string;
|
|
147
|
+
/** Wire protocol for the produced boundary bindings, e.g. "http-graphql". */
|
|
148
|
+
protocol: string;
|
|
149
|
+
discovery: RubyDiscoveryPattern[];
|
|
150
|
+
}
|
|
151
|
+
type RubyDiscoveryPattern = GraphqlObjectFields;
|
|
152
|
+
/** A class whose `< ...` superclass is one of `baseClassNames` declares GraphQL fields through DSL calls in its own body. */
|
|
153
|
+
interface GraphqlObjectFields {
|
|
154
|
+
type: "graphqlObjectFields";
|
|
155
|
+
/** The base class the library itself generates. A project's own intermediate base class is added through pack config. */
|
|
156
|
+
baseClassNames: string[];
|
|
157
|
+
/** The directory a wiring keyword's referenced class is looked up under. That is the project's own layout, not the library's. */
|
|
158
|
+
root: string;
|
|
159
|
+
pathConvention: ConstantPathConvention;
|
|
160
|
+
/** The DSL call declaring one schema field in an object type's body. */
|
|
161
|
+
fieldCallName: string;
|
|
162
|
+
/** The DSL call declaring a referenced class's own return type. */
|
|
163
|
+
typeCallName: string;
|
|
164
|
+
argumentCallName: string;
|
|
165
|
+
/** Keywords whose value gives a class to read the declared contract from, one hop away. They are tried in the order listed. */
|
|
166
|
+
wiringKeywords: string[];
|
|
167
|
+
/** The method a wired class defines to resolve the field it is wired to. Library-defined. */
|
|
168
|
+
resolverMethodName: string;
|
|
169
|
+
/** The library's own classes that a project's class chain ends at. Library-defined. */
|
|
170
|
+
ancestryRootClassNames: string[];
|
|
171
|
+
/** Keyword on an argument call saying whether the argument is required. Library-defined. */
|
|
172
|
+
requiredKeyword: string;
|
|
173
|
+
/** What an argument means when it does not write the required keyword at all. */
|
|
174
|
+
requiredDefault: boolean;
|
|
175
|
+
/** The keyword that overrides `camelizeDefault` for a single name. */
|
|
176
|
+
camelizeKeyword: string;
|
|
177
|
+
/** Whether a symbol's snake_case name is exposed camelCased when the call itself does not say. */
|
|
178
|
+
camelizeDefault: boolean;
|
|
179
|
+
/** Only the names the library itself accepts in a type position belong here. */
|
|
180
|
+
scalars: Record<string, TypeShape>;
|
|
181
|
+
/** Module prefixes the same scalars can also be written under when someone spells out the full path. */
|
|
182
|
+
scalarNamePrefixes: string[];
|
|
183
|
+
typeNameConvention: GraphqlTypeNameConvention;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Parsed files by absolute path, so a class that several fields refer to is only parsed once. */
|
|
187
|
+
interface FileCache {
|
|
188
|
+
get(absPath: string): Promise<Node | null>;
|
|
189
|
+
}
|
|
190
|
+
declare function createFileCache(parse: (source: string) => Promise<Node>, readFile: (absPath: string) => string | null): FileCache;
|
|
191
|
+
interface DiscoveryOptions {
|
|
192
|
+
packs: RubyPack[];
|
|
193
|
+
/** Repo-relative or absolute path recorded on each summary's `location.file`. */
|
|
194
|
+
filePath: string;
|
|
195
|
+
cache: FileCache;
|
|
196
|
+
}
|
|
197
|
+
declare function discoverUnits(root: Node, options: DiscoveryOptions): Promise<RawCodeStructure[]>;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The name is part of the key because the range is measured in lines, two
|
|
201
|
+
* units can start on the same line, and `entry` is a set, so keying on the range
|
|
202
|
+
* alone would drop one of them.
|
|
203
|
+
*/
|
|
204
|
+
declare function unitKey(filePath: string, range: {
|
|
205
|
+
start: number;
|
|
206
|
+
end: number;
|
|
207
|
+
}, name: string): string;
|
|
208
|
+
declare function emitEntryFact(db: Database, filePath: string, range: {
|
|
209
|
+
start: number;
|
|
210
|
+
end: number;
|
|
211
|
+
}, name: string): void;
|
|
212
|
+
|
|
213
|
+
interface ExtractRubyOptions {
|
|
214
|
+
/** Absolute paths of the files to parse and extract. */
|
|
215
|
+
files: string[];
|
|
216
|
+
packs: RubyPack[];
|
|
217
|
+
/** When set, `location.file` on each summary is relativized against this. */
|
|
218
|
+
workspaceRoot?: string;
|
|
219
|
+
}
|
|
220
|
+
interface ExtractRubyResult {
|
|
221
|
+
summaries: BehavioralSummary[];
|
|
222
|
+
facts: Database;
|
|
223
|
+
}
|
|
224
|
+
declare function extractRubyProject(options: ExtractRubyOptions): Promise<ExtractRubyResult>;
|
|
225
|
+
/** Every `.rb` file under `root`, depth-first, skipping the usual non-source directories. */
|
|
226
|
+
declare function findRubyFiles(root: string): string[];
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Turns a Ruby DSL call's type argument into an IR type shape.
|
|
230
|
+
*
|
|
231
|
+
* The argument is only read when it is a literal constant, or a one-element
|
|
232
|
+
* array wrapping one. A type written as a lambda or a method call abstains
|
|
233
|
+
* rather than guessing at what it evaluates to.
|
|
234
|
+
*/
|
|
235
|
+
|
|
236
|
+
interface TypeReadContext {
|
|
237
|
+
/** The `Module.nesting` chain in effect, innermost first. */
|
|
238
|
+
nesting: readonly string[];
|
|
239
|
+
/** Every class the surrounding file defines, by qualified name, so we can tell when one shadows a scalar. */
|
|
240
|
+
knownClasses: ReadonlySet<string>;
|
|
241
|
+
scalars: Readonly<Record<string, TypeShape>>;
|
|
242
|
+
scalarNamePrefixes: readonly string[];
|
|
243
|
+
typeNameConvention: GraphqlTypeNameConvention;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Null means the field abstains. Callers must not fall back to `unknown`, which
|
|
247
|
+
* would read as a contract nobody actually declared.
|
|
248
|
+
*
|
|
249
|
+
* A bare `constant` is checked against `ctx.nesting` before the scalar table,
|
|
250
|
+
* because a project class at some level of `Module.nesting` is what Ruby itself
|
|
251
|
+
* finds first. A compound `scope_resolution` path is absolute and cannot be
|
|
252
|
+
* shadowed, so it skips that check.
|
|
253
|
+
*/
|
|
254
|
+
declare function typeShapeFromNode(node: Node, ctx: TypeReadContext): TypeShape | null;
|
|
255
|
+
|
|
256
|
+
export { type AncestorEntry, type AncestorLookup, type Ancestry, type ClassInfo, type ConstantPathConvention, type DiscoveryOptions, type ExtractRubyOptions, type ExtractRubyResult, type FileCache, type GraphqlObjectFields, type GraphqlTypeNameConvention, type MethodLookup, type ReachedBody, type RubyDiscoveryPattern, type RubyPack, type TypeReadContext, ancestryOf, createFileCache, discoverUnits, emitEntryFact, extractRubyProject, findRubyFiles, graphqlTypeNameFromQualified, inheritedStatements, methodInAncestry, parseRuby, qualifyConstantRef, reachDefinition, resolveConstantFile, typeShapeFromNode, underscoreConstantPath, unitKey, walkClasses, walkDefinitions };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { Tree, Node } from 'web-tree-sitter';
|
|
2
|
+
export { Node as RbNode, Tree as RbTree } from 'web-tree-sitter';
|
|
3
|
+
import { RawCodeStructure } from '@suss/extractor';
|
|
4
|
+
import { TypeShape, BehavioralSummary } from '@suss/behavioral-ir';
|
|
5
|
+
import { Database } from '@suss/datalog';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Finds the file behind a constant.
|
|
9
|
+
*
|
|
10
|
+
* A Ruby codebase locates that file by a naming convention rather than through a
|
|
11
|
+
* load graph a static reader could follow, so we build one path from the
|
|
12
|
+
* constant's own name and check it once against the configured root. Nothing
|
|
13
|
+
* here searches several roots or chooses between candidates.
|
|
14
|
+
*/
|
|
15
|
+
/** A pack picks a convention by name, and the code for each one lives here. */
|
|
16
|
+
type ConstantPathConvention = "railsUnderscore";
|
|
17
|
+
/** Ported from ActiveSupport's own `String#underscore`, which is what Rails autoloading runs a constant path through. */
|
|
18
|
+
declare function underscoreConstantPath(qualifiedName: string): string;
|
|
19
|
+
/** Null when there is no file at that path. We do not fall back to another root or another spelling. */
|
|
20
|
+
declare function resolveConstantFile(root: string, qualifiedName: string, convention: ConstantPathConvention): string | null;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A new `Parser` for every call, so that parses running at the same time do
|
|
24
|
+
* not fight over one parser's mutable `language` field. The compiled `Language`
|
|
25
|
+
* is loaded once per process and shared.
|
|
26
|
+
*/
|
|
27
|
+
declare function parseRuby(source: string): Promise<Tree>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolves Ruby constant paths against lexical nesting.
|
|
31
|
+
*
|
|
32
|
+
* A compound path written out, like `Data::Record`, means the same thing
|
|
33
|
+
* wherever it appears, so its text is the qualified name. A bare name is
|
|
34
|
+
* relative to the nesting it is written inside. `require` is never resolved,
|
|
35
|
+
* because Rails autoloads by naming convention rather than through a load graph
|
|
36
|
+
* a static reader could follow, so a constant that nesting alone cannot qualify
|
|
37
|
+
* keeps whatever name the source wrote and callers treat it as unresolved.
|
|
38
|
+
*
|
|
39
|
+
* Ruby's own `Module.nesting` puts a newly opened class or module on the front
|
|
40
|
+
* of the chain already in effect, whether the name used to open it was bare or
|
|
41
|
+
* compound, because nesting follows the class and module keywords in the text
|
|
42
|
+
* and not the form of the name. So even when a class is opened with a compound
|
|
43
|
+
* name, as in `module Api; class Types::CampaignType; end; end`, Ruby still
|
|
44
|
+
* looks up through `Api` inside that body. That matters, because a bare
|
|
45
|
+
* reference in there is resolved by trying each level of the chain, and dropping
|
|
46
|
+
* `Api` would miss a class the file itself defines.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Qualifies a name against the innermost level of `nesting` and no further. It
|
|
51
|
+
* returns null for a computed expression or a variable, which callers treat as
|
|
52
|
+
* unresolved. `shadowingClassFor` is the one place that walks every level.
|
|
53
|
+
*/
|
|
54
|
+
declare function qualifyConstantRef(node: Node, nesting: readonly string[]): string | null;
|
|
55
|
+
interface ClassInfo {
|
|
56
|
+
node: Node;
|
|
57
|
+
/** Which keyword opened it. A module has no superclass and cannot be subclassed, but its methods are available to every class including it. */
|
|
58
|
+
kind: "class" | "module";
|
|
59
|
+
/** This class's own fully-qualified constant path. */
|
|
60
|
+
qualifiedName: string;
|
|
61
|
+
/** Null when it declares no superclass, or the expression is not a literal constant path. */
|
|
62
|
+
superclassQualifiedName: string | null;
|
|
63
|
+
/** `body_statement` node, or null for `class Foo; end`. */
|
|
64
|
+
bodyNode: Node | null;
|
|
65
|
+
/** `Module.nesting` in effect inside this class's own body, innermost first. */
|
|
66
|
+
bodyNesting: readonly string[];
|
|
67
|
+
}
|
|
68
|
+
/** Every `class` declaration reachable from `root` through direct module/class nesting, in source order. */
|
|
69
|
+
declare function walkClasses(root: Node, visit: (info: ClassInfo) => void): void;
|
|
70
|
+
/** The same walk including `module` declarations, for a lookup that has to reach a module's own methods. */
|
|
71
|
+
declare function walkDefinitions(root: Node, visit: (info: ClassInfo) => void): void;
|
|
72
|
+
/** A pack picks a derivation by name, and the code for each one lives here. */
|
|
73
|
+
type GraphqlTypeNameConvention = "stripTypeSuffix";
|
|
74
|
+
/** We never read a per-class name override, so this is always the name the convention derives. */
|
|
75
|
+
declare function graphqlTypeNameFromQualified(qualifiedName: string, convention: GraphqlTypeNameConvention): string;
|
|
76
|
+
|
|
77
|
+
/** One class or module body a walk reached, with the definitions its own file makes, since a bare constant is shadowed per file. */
|
|
78
|
+
interface ReachedBody {
|
|
79
|
+
info: ClassInfo;
|
|
80
|
+
knownClasses: ReadonlySet<string>;
|
|
81
|
+
}
|
|
82
|
+
/** One ancestor: every block reopening its name, or the name alone when nothing reached it. Blocks stay together because Ruby treats a reopened class as one place in the chain. */
|
|
83
|
+
type AncestorEntry = {
|
|
84
|
+
type: "bodies";
|
|
85
|
+
name: string;
|
|
86
|
+
blocks: ReachedBody[];
|
|
87
|
+
} | {
|
|
88
|
+
type: "unfollowed";
|
|
89
|
+
name: string;
|
|
90
|
+
};
|
|
91
|
+
/** A class and everything it inherits from, in Ruby's own method-lookup order. */
|
|
92
|
+
type Ancestry = readonly AncestorEntry[];
|
|
93
|
+
/** What a walk needs to reach a class it knows only by name. */
|
|
94
|
+
interface AncestorLookup {
|
|
95
|
+
/** Directory the constant-to-path convention resolves an ancestor's name against. */
|
|
96
|
+
root: string;
|
|
97
|
+
pathConvention: ConstantPathConvention;
|
|
98
|
+
/**
|
|
99
|
+
* The library's own classes a project's chain ends at. Reaching one
|
|
100
|
+
* ends a walk with nothing left unfollowed.
|
|
101
|
+
*/
|
|
102
|
+
ancestryRootClassNames: readonly string[];
|
|
103
|
+
parsedFile(absPath: string): Promise<Node | null>;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Every block a file defines under `qualifiedName`, or null when the
|
|
107
|
+
* constant-to-path convention points at no file, no file is there, or
|
|
108
|
+
* the file defines nothing by that name.
|
|
109
|
+
*/
|
|
110
|
+
declare function reachDefinition(qualifiedName: string, lookup: AncestorLookup): Promise<ReachedBody[] | null>;
|
|
111
|
+
declare function ancestryOf(name: string, blocks: readonly ReachedBody[], lookup: AncestorLookup): Promise<Ancestry>;
|
|
112
|
+
/** What searching an ancestry for one method name came to. */
|
|
113
|
+
type MethodLookup = {
|
|
114
|
+
type: "found";
|
|
115
|
+
method: Node;
|
|
116
|
+
}
|
|
117
|
+
/** `reason` completes "could be answered by a method ...". */
|
|
118
|
+
| {
|
|
119
|
+
type: "unsettled";
|
|
120
|
+
reason: string;
|
|
121
|
+
} | {
|
|
122
|
+
type: "none";
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* The method `name` resolves to. An ancestor nearer than any definition
|
|
126
|
+
* that nothing could read stops the search: whatever sits further along
|
|
127
|
+
* is not what Ruby would have called.
|
|
128
|
+
*/
|
|
129
|
+
declare function methodInAncestry(ancestry: Ancestry, name: string): MethodLookup;
|
|
130
|
+
/** Every statement of every body reached, most distant ancestor first, so a nearer declaration overwrites what it inherits. */
|
|
131
|
+
declare function inheritedStatements(ancestry: Ancestry): Array<{
|
|
132
|
+
block: ReachedBody;
|
|
133
|
+
statement: Node;
|
|
134
|
+
}>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The Ruby adapter's own pattern-pack contract.
|
|
138
|
+
*
|
|
139
|
+
* Match shapes stay per-language until a second implementation shows what is
|
|
140
|
+
* actually shared, so this is Ruby's own and not the TypeScript or Python
|
|
141
|
+
* adapter's. Everything a library defines, meaning its call names, keywords,
|
|
142
|
+
* scalars, and conventions, arrives as pack data and is never hardcoded here.
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
interface RubyPack {
|
|
146
|
+
name: string;
|
|
147
|
+
/** Wire protocol for the produced boundary bindings, e.g. "http-graphql". */
|
|
148
|
+
protocol: string;
|
|
149
|
+
discovery: RubyDiscoveryPattern[];
|
|
150
|
+
}
|
|
151
|
+
type RubyDiscoveryPattern = GraphqlObjectFields;
|
|
152
|
+
/** A class whose `< ...` superclass is one of `baseClassNames` declares GraphQL fields through DSL calls in its own body. */
|
|
153
|
+
interface GraphqlObjectFields {
|
|
154
|
+
type: "graphqlObjectFields";
|
|
155
|
+
/** The base class the library itself generates. A project's own intermediate base class is added through pack config. */
|
|
156
|
+
baseClassNames: string[];
|
|
157
|
+
/** The directory a wiring keyword's referenced class is looked up under. That is the project's own layout, not the library's. */
|
|
158
|
+
root: string;
|
|
159
|
+
pathConvention: ConstantPathConvention;
|
|
160
|
+
/** The DSL call declaring one schema field in an object type's body. */
|
|
161
|
+
fieldCallName: string;
|
|
162
|
+
/** The DSL call declaring a referenced class's own return type. */
|
|
163
|
+
typeCallName: string;
|
|
164
|
+
argumentCallName: string;
|
|
165
|
+
/** Keywords whose value gives a class to read the declared contract from, one hop away. They are tried in the order listed. */
|
|
166
|
+
wiringKeywords: string[];
|
|
167
|
+
/** The method a wired class defines to resolve the field it is wired to. Library-defined. */
|
|
168
|
+
resolverMethodName: string;
|
|
169
|
+
/** The library's own classes that a project's class chain ends at. Library-defined. */
|
|
170
|
+
ancestryRootClassNames: string[];
|
|
171
|
+
/** Keyword on an argument call saying whether the argument is required. Library-defined. */
|
|
172
|
+
requiredKeyword: string;
|
|
173
|
+
/** What an argument means when it does not write the required keyword at all. */
|
|
174
|
+
requiredDefault: boolean;
|
|
175
|
+
/** The keyword that overrides `camelizeDefault` for a single name. */
|
|
176
|
+
camelizeKeyword: string;
|
|
177
|
+
/** Whether a symbol's snake_case name is exposed camelCased when the call itself does not say. */
|
|
178
|
+
camelizeDefault: boolean;
|
|
179
|
+
/** Only the names the library itself accepts in a type position belong here. */
|
|
180
|
+
scalars: Record<string, TypeShape>;
|
|
181
|
+
/** Module prefixes the same scalars can also be written under when someone spells out the full path. */
|
|
182
|
+
scalarNamePrefixes: string[];
|
|
183
|
+
typeNameConvention: GraphqlTypeNameConvention;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Parsed files by absolute path, so a class that several fields refer to is only parsed once. */
|
|
187
|
+
interface FileCache {
|
|
188
|
+
get(absPath: string): Promise<Node | null>;
|
|
189
|
+
}
|
|
190
|
+
declare function createFileCache(parse: (source: string) => Promise<Node>, readFile: (absPath: string) => string | null): FileCache;
|
|
191
|
+
interface DiscoveryOptions {
|
|
192
|
+
packs: RubyPack[];
|
|
193
|
+
/** Repo-relative or absolute path recorded on each summary's `location.file`. */
|
|
194
|
+
filePath: string;
|
|
195
|
+
cache: FileCache;
|
|
196
|
+
}
|
|
197
|
+
declare function discoverUnits(root: Node, options: DiscoveryOptions): Promise<RawCodeStructure[]>;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The name is part of the key because the range is measured in lines, two
|
|
201
|
+
* units can start on the same line, and `entry` is a set, so keying on the range
|
|
202
|
+
* alone would drop one of them.
|
|
203
|
+
*/
|
|
204
|
+
declare function unitKey(filePath: string, range: {
|
|
205
|
+
start: number;
|
|
206
|
+
end: number;
|
|
207
|
+
}, name: string): string;
|
|
208
|
+
declare function emitEntryFact(db: Database, filePath: string, range: {
|
|
209
|
+
start: number;
|
|
210
|
+
end: number;
|
|
211
|
+
}, name: string): void;
|
|
212
|
+
|
|
213
|
+
interface ExtractRubyOptions {
|
|
214
|
+
/** Absolute paths of the files to parse and extract. */
|
|
215
|
+
files: string[];
|
|
216
|
+
packs: RubyPack[];
|
|
217
|
+
/** When set, `location.file` on each summary is relativized against this. */
|
|
218
|
+
workspaceRoot?: string;
|
|
219
|
+
}
|
|
220
|
+
interface ExtractRubyResult {
|
|
221
|
+
summaries: BehavioralSummary[];
|
|
222
|
+
facts: Database;
|
|
223
|
+
}
|
|
224
|
+
declare function extractRubyProject(options: ExtractRubyOptions): Promise<ExtractRubyResult>;
|
|
225
|
+
/** Every `.rb` file under `root`, depth-first, skipping the usual non-source directories. */
|
|
226
|
+
declare function findRubyFiles(root: string): string[];
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Turns a Ruby DSL call's type argument into an IR type shape.
|
|
230
|
+
*
|
|
231
|
+
* The argument is only read when it is a literal constant, or a one-element
|
|
232
|
+
* array wrapping one. A type written as a lambda or a method call abstains
|
|
233
|
+
* rather than guessing at what it evaluates to.
|
|
234
|
+
*/
|
|
235
|
+
|
|
236
|
+
interface TypeReadContext {
|
|
237
|
+
/** The `Module.nesting` chain in effect, innermost first. */
|
|
238
|
+
nesting: readonly string[];
|
|
239
|
+
/** Every class the surrounding file defines, by qualified name, so we can tell when one shadows a scalar. */
|
|
240
|
+
knownClasses: ReadonlySet<string>;
|
|
241
|
+
scalars: Readonly<Record<string, TypeShape>>;
|
|
242
|
+
scalarNamePrefixes: readonly string[];
|
|
243
|
+
typeNameConvention: GraphqlTypeNameConvention;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Null means the field abstains. Callers must not fall back to `unknown`, which
|
|
247
|
+
* would read as a contract nobody actually declared.
|
|
248
|
+
*
|
|
249
|
+
* A bare `constant` is checked against `ctx.nesting` before the scalar table,
|
|
250
|
+
* because a project class at some level of `Module.nesting` is what Ruby itself
|
|
251
|
+
* finds first. A compound `scope_resolution` path is absolute and cannot be
|
|
252
|
+
* shadowed, so it skips that check.
|
|
253
|
+
*/
|
|
254
|
+
declare function typeShapeFromNode(node: Node, ctx: TypeReadContext): TypeShape | null;
|
|
255
|
+
|
|
256
|
+
export { type AncestorEntry, type AncestorLookup, type Ancestry, type ClassInfo, type ConstantPathConvention, type DiscoveryOptions, type ExtractRubyOptions, type ExtractRubyResult, type FileCache, type GraphqlObjectFields, type GraphqlTypeNameConvention, type MethodLookup, type ReachedBody, type RubyDiscoveryPattern, type RubyPack, type TypeReadContext, ancestryOf, createFileCache, discoverUnits, emitEntryFact, extractRubyProject, findRubyFiles, graphqlTypeNameFromQualified, inheritedStatements, methodInAncestry, parseRuby, qualifyConstantRef, reachDefinition, resolveConstantFile, typeShapeFromNode, underscoreConstantPath, unitKey, walkClasses, walkDefinitions };
|