@suss/adapter-ruby 0.29.0 → 0.30.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/dist/index.cjs +761 -223
- package/dist/index.d.cts +164 -13
- package/dist/index.d.ts +164 -13
- package/dist/index.js +745 -207
- package/package.json +7 -7
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { Database } from '@suss/datalog';
|
|
1
2
|
import { Tree, Node } from 'web-tree-sitter';
|
|
2
3
|
export { Node as RbNode, Tree as RbTree } from 'web-tree-sitter';
|
|
3
|
-
import { Database } from '@suss/datalog';
|
|
4
4
|
import { TypeShape, BehavioralSummary } from '@suss/behavioral-ir';
|
|
5
5
|
import { RawCodeStructure, TimingReport, ExtractionReport, CacheDiagnostic, ExtractorOptions } from '@suss/extractor';
|
|
6
6
|
import { WhyExplained } from '@suss/resolution';
|
|
@@ -38,6 +38,19 @@ declare function bodyStatements(body: Node): Node[];
|
|
|
38
38
|
* included; the caller descends into that one itself.
|
|
39
39
|
*/
|
|
40
40
|
declare function nestedStatements(stmt: Node): Node[];
|
|
41
|
+
/** What a pack says about one call whose block runs as part of the body around it, with the fields it left out settled. */
|
|
42
|
+
interface BodyBlockKind {
|
|
43
|
+
/** Whether the library only gives the call to a module, so a class writing the same name means something else. */
|
|
44
|
+
readonly moduleOnly: boolean;
|
|
45
|
+
/** Whether a `def` in the block declares a method on the class itself rather than on an instance. */
|
|
46
|
+
readonly definesClassMethods: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The calls a run's packs say run their block as part of the class or
|
|
50
|
+
* module body it is written in, by the name a project writes. Ruby
|
|
51
|
+
* defines no such call, so this is empty until a pack declares one.
|
|
52
|
+
*/
|
|
53
|
+
type BodyBlocks = ReadonlyMap<string, BodyBlockKind>;
|
|
41
54
|
/** A call whose block is the thing being configured rather than a place statements run. */
|
|
42
55
|
type BlockConfigures = (call: Node) => boolean;
|
|
43
56
|
/**
|
|
@@ -92,6 +105,33 @@ declare function underscoreConstantPath(qualifiedName: string, acronyms?: readon
|
|
|
92
105
|
*/
|
|
93
106
|
declare function resolveConstantFile(root: string, qualifiedName: string, convention: ConstantPathConvention, acronyms?: readonly string[]): string | null;
|
|
94
107
|
|
|
108
|
+
/**
|
|
109
|
+
* The method names a class gets under a name the source computes.
|
|
110
|
+
*
|
|
111
|
+
* `KEYS.each { |key| define_method(key) { ... } }` defines one method
|
|
112
|
+
* per element of `KEYS`, and a reader of `def` nodes sees none of them.
|
|
113
|
+
* Reading the names lets a lookup for a name none of them defines carry
|
|
114
|
+
* on up the ancestry instead of stopping at the class.
|
|
115
|
+
*
|
|
116
|
+
* Which calls those are, and what each name is written as, arrive as
|
|
117
|
+
* `definesMethodFrom` and `nameTurnsOn`, so this reads no source of its
|
|
118
|
+
* own. Each name goes through the shared value evaluator, which follows
|
|
119
|
+
* names and constants across the whole run. A name read only in part
|
|
120
|
+
* becomes a pattern the whole name has to match.
|
|
121
|
+
*/
|
|
122
|
+
|
|
123
|
+
/** What the dynamic definitions in one class define. */
|
|
124
|
+
interface DefinedNames {
|
|
125
|
+
/** Every name this reader read one as defining. */
|
|
126
|
+
readonly names: ReadonlySet<string>;
|
|
127
|
+
/** What a name read only in part has to match for the class to be defining it. */
|
|
128
|
+
readonly patterns: readonly RegExp[];
|
|
129
|
+
/** Whether one of them was given a name this reader could not read. */
|
|
130
|
+
readonly unreadable: boolean;
|
|
131
|
+
}
|
|
132
|
+
/** Every class's reading, by the key the value facts give the class node. */
|
|
133
|
+
type DynamicNames = ReadonlyMap<string, DefinedNames>;
|
|
134
|
+
|
|
95
135
|
/**
|
|
96
136
|
* Resolves Ruby constant paths against lexical nesting.
|
|
97
137
|
*
|
|
@@ -174,6 +214,13 @@ type AncestorEntry = {
|
|
|
174
214
|
};
|
|
175
215
|
/** A class and everything it inherits from, in Ruby's own method-lookup order. */
|
|
176
216
|
type Ancestry = readonly AncestorEntry[];
|
|
217
|
+
/** What reading a class body takes beyond the body: the run's facts, and what its packs declare about a block written in one. */
|
|
218
|
+
interface BodyReading {
|
|
219
|
+
readonly facts?: Database | undefined;
|
|
220
|
+
readonly bodyBlocks?: BodyBlocks | undefined;
|
|
221
|
+
/** What each class defines under a name the source computes, by class key. */
|
|
222
|
+
readonly dynamicNames?: DynamicNames | undefined;
|
|
223
|
+
}
|
|
177
224
|
/** What a walk needs to reach a class it knows only by name. */
|
|
178
225
|
interface AncestorLookup {
|
|
179
226
|
/** Directory the constant-to-path convention resolves an ancestor's name against. */
|
|
@@ -228,7 +275,7 @@ type MethodLookup =
|
|
|
228
275
|
* The method `name` resolves to. An unread ancestor nearer than any
|
|
229
276
|
* definition stops the search, since Ruby would have called that one first.
|
|
230
277
|
*/
|
|
231
|
-
declare function methodInAncestry(ancestry: Ancestry, name: string): MethodLookup;
|
|
278
|
+
declare function methodInAncestry(ancestry: Ancestry, name: string, read?: BodyReading): MethodLookup;
|
|
232
279
|
/** Every statement of every body reached, most distant ancestor first, so a nearer declaration overwrites what it inherits. */
|
|
233
280
|
declare function inheritedStatements(ancestry: Ancestry, blockConfigures?: BlockConfigures): Array<{
|
|
234
281
|
block: ReachedBody;
|
|
@@ -271,6 +318,40 @@ interface RubyPack {
|
|
|
271
318
|
storage?: RbStoragePattern[];
|
|
272
319
|
/** Calls the library gives a project for reading a model through a batching loader, rather than on the model itself. */
|
|
273
320
|
loaders?: RbLoaderPattern[];
|
|
321
|
+
/** Calls the library gives a class or module body whose block runs as part of that body. */
|
|
322
|
+
bodyBlocks?: RbBodyBlock[];
|
|
323
|
+
/** What the project taught the library's inflector, which comes ahead of the adapter's own defaults. */
|
|
324
|
+
inflections?: RbInflections;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* The words and rules a project registered with its library's
|
|
328
|
+
* inflector. The library says where a project writes them and how; what
|
|
329
|
+
* arrives here is the reading, so the adapter applies them without
|
|
330
|
+
* knowing any of that.
|
|
331
|
+
*/
|
|
332
|
+
interface RbInflections {
|
|
333
|
+
/** Words a constant name keeps whole: `API` gives `APIToken` rather than `ApiToken`. */
|
|
334
|
+
acronyms?: string[];
|
|
335
|
+
/** A plural and the singular it comes from: `["people", "person"]`. */
|
|
336
|
+
irregular?: Array<[plural: string, singular: string]>;
|
|
337
|
+
/** Words spelled the same in both numbers. */
|
|
338
|
+
uncountable?: string[];
|
|
339
|
+
/** A rule turning a word singular, and what the part it matched becomes. A rule written `/body/flags` is a pattern; anything else is the text to replace. */
|
|
340
|
+
singular?: Array<[rule: string, replacement: string]>;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* A receiverless call a library gives a class or module body, whose
|
|
344
|
+
* block runs as part of that body rather than keeping what it declares
|
|
345
|
+
* to itself. Ruby has no such call of its own, so a run whose packs
|
|
346
|
+
* declare nothing here never opens a block out into the body.
|
|
347
|
+
*/
|
|
348
|
+
interface RbBodyBlock {
|
|
349
|
+
/** The call as a project writes it, `included`. */
|
|
350
|
+
name: string;
|
|
351
|
+
/** Set when the library only gives the call to a module, as a concern's own `included` is. */
|
|
352
|
+
moduleOnly?: boolean;
|
|
353
|
+
/** Set when a `def` in the block declares a method on the class itself rather than on an instance. */
|
|
354
|
+
definesClassMethods?: boolean;
|
|
274
355
|
}
|
|
275
356
|
/**
|
|
276
357
|
* The calls a library gives a project for making a request. A method
|
|
@@ -322,17 +403,50 @@ interface RbClientResponse {
|
|
|
322
403
|
* Ruby writes no return type, so a call is matched by what its receiver
|
|
323
404
|
* inherits from. A model is a class that reaches one of these base classes,
|
|
324
405
|
* whether the project declares an intermediate one or not.
|
|
406
|
+
*
|
|
407
|
+
* `reads` and `writes` are the whole of what the library does to the
|
|
408
|
+
* database. A method in neither list is the project's own or the
|
|
409
|
+
* language's, and the reach walk follows it instead.
|
|
325
410
|
*/
|
|
326
411
|
interface RbStoragePattern {
|
|
327
412
|
/** Base classes the library gives a model, `ActiveRecord::Base` for Rails. */
|
|
328
413
|
baseClasses: string[];
|
|
329
|
-
/** Methods that change what is stored.
|
|
414
|
+
/** Methods the library defines that change what is stored. */
|
|
330
415
|
writes: string[];
|
|
416
|
+
/** Methods the library defines that run a query. */
|
|
417
|
+
reads: string[];
|
|
331
418
|
/** Methods whose result is the model again: one record, or a relation a later read narrows to one. */
|
|
332
419
|
givesBack: string[];
|
|
420
|
+
/** Methods that pick rows by the primary key when they are given a positional argument, and the column that is. */
|
|
421
|
+
byPrimaryKey?: RbPrimaryKeyLookup;
|
|
422
|
+
/** Read methods whose symbol arguments are the columns they ask for. */
|
|
423
|
+
columnArguments?: string[];
|
|
424
|
+
/** How the library declares that one model reaches another, when it has such a thing. */
|
|
425
|
+
associations?: RbAssociationCalls;
|
|
333
426
|
/** Which database is behind the connection, which the project settles. */
|
|
334
427
|
storageSystem: "postgresql" | "mysql" | "sqlite";
|
|
335
428
|
}
|
|
429
|
+
/** Which methods take the primary key positionally, and what that column is called. */
|
|
430
|
+
interface RbPrimaryKeyLookup {
|
|
431
|
+
methods: string[];
|
|
432
|
+
/** The column the library uses unless a model says another. */
|
|
433
|
+
column: string;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* The class-body calls a library gives a model for declaring an
|
|
437
|
+
* association, and where one says which class it reaches. The two lists
|
|
438
|
+
* are apart because the target's name is the association's singularised
|
|
439
|
+
* for a plural call and the association's as written for a singular one,
|
|
440
|
+
* and only the library knows which of its calls is which.
|
|
441
|
+
*/
|
|
442
|
+
interface RbAssociationCalls {
|
|
443
|
+
/** Calls written in the singular, `belongs_to :account` and `has_one :profile`. */
|
|
444
|
+
singular: string[];
|
|
445
|
+
/** Calls written in the plural, `has_many :statuses`. */
|
|
446
|
+
plural: string[];
|
|
447
|
+
/** The keyword whose value is the target class's name instead, `class_name`. */
|
|
448
|
+
classNameKeyword: string;
|
|
449
|
+
}
|
|
336
450
|
/**
|
|
337
451
|
* A loader that batches reads of a model on the caller's behalf, graphql-ruby's
|
|
338
452
|
* dataloader for one. The call that picks what to load is given the model as
|
|
@@ -489,14 +603,16 @@ interface GraphqlObjectFields {
|
|
|
489
603
|
/**
|
|
490
604
|
* storage.ts: which calls in a body talk to the database, for Ruby.
|
|
491
605
|
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
*
|
|
606
|
+
* One rule covers both ways a call is written. It is database work when the
|
|
607
|
+
* pack lists its method as a read or a write, the class behind its receiver
|
|
608
|
+
* reaches a base class the pack lists, and the project does not declare that
|
|
609
|
+
* method itself. A chain counts once, at the outermost call the library
|
|
610
|
+
* defines, and every other call is left to the reach walk. The README says
|
|
611
|
+
* why ancestry.
|
|
496
612
|
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
*
|
|
613
|
+
* The class behind the receiver comes from the constant bindings for a chain
|
|
614
|
+
* written from a constant and from the resolution rules otherwise. A loader
|
|
615
|
+
* pattern adds calls given the model as an argument, tested the same way.
|
|
500
616
|
*/
|
|
501
617
|
|
|
502
618
|
interface RbStorageOptions {
|
|
@@ -523,6 +639,10 @@ interface BodyReadOptions {
|
|
|
523
639
|
readonly storage?: RbStorageOptions | undefined;
|
|
524
640
|
/** The methods every pack in the run said its own library defines, which are left off an effect list. */
|
|
525
641
|
readonly inheritedMethods?: InheritedMethods | undefined;
|
|
642
|
+
/** The calls every pack in the run said run their block as part of the body around it. */
|
|
643
|
+
readonly bodyBlocks?: BodyBlocks | undefined;
|
|
644
|
+
/** What each class defines under a name the source computes, by class key. */
|
|
645
|
+
readonly dynamicNames?: DynamicNames | undefined;
|
|
526
646
|
}
|
|
527
647
|
interface DiscoveryOptions extends BodyReadOptions {
|
|
528
648
|
packs: RubyPack[];
|
|
@@ -547,6 +667,29 @@ interface ReachSeed {
|
|
|
547
667
|
}
|
|
548
668
|
declare function discoverUnits(root: Node, options: DiscoveryOptions): Promise<RawCodeStructure[]>;
|
|
549
669
|
|
|
670
|
+
/**
|
|
671
|
+
* associations.ts: reading `has_many :statuses` out of a model's body.
|
|
672
|
+
*
|
|
673
|
+
* A pack says which calls declare an association. The target is the
|
|
674
|
+
* class the `class_name:` keyword gives, or the association's own name
|
|
675
|
+
* put through ActiveSupport's default inflections when the declaration
|
|
676
|
+
* gives none. That inflected name is written nowhere in the source, so
|
|
677
|
+
* the declaration comes with a constant reference of its own and the
|
|
678
|
+
* constant bindings settle it from the nesting the model is written in:
|
|
679
|
+
* a `has_many :statuses` inside `Admin::Account` reaches `Admin::Status`
|
|
680
|
+
* before it reaches `Status`.
|
|
681
|
+
*/
|
|
682
|
+
|
|
683
|
+
/** One association a class or module body declares. */
|
|
684
|
+
interface AssociationDeclaration {
|
|
685
|
+
/** The class or module whose body declares it, keyed the way a class is. */
|
|
686
|
+
readonly classKey: string;
|
|
687
|
+
/** The association's name, as the call writes it. */
|
|
688
|
+
readonly name: string;
|
|
689
|
+
/** The class it reaches, as a reference the constant bindings settle. */
|
|
690
|
+
readonly target: ConstantReference;
|
|
691
|
+
}
|
|
692
|
+
|
|
550
693
|
/** A definition every file in the run can see, under the name it is written as. */
|
|
551
694
|
interface ConstantDefinition {
|
|
552
695
|
readonly qualifiedName: string;
|
|
@@ -566,12 +709,16 @@ interface FileConstants {
|
|
|
566
709
|
readonly filePath: string;
|
|
567
710
|
readonly definitions: readonly ConstantDefinition[];
|
|
568
711
|
readonly references: readonly ConstantReference[];
|
|
712
|
+
/** Empty unless a pack in the run said what an association declaration looks like. */
|
|
713
|
+
readonly associations: readonly AssociationDeclaration[];
|
|
569
714
|
}
|
|
570
715
|
/**
|
|
571
716
|
* Every constant this file defines and every one it reads. One walk, because
|
|
572
717
|
* the nesting a reference is written inside is what the walk already knows.
|
|
718
|
+
* An association's target is read here too: the name it inflects to has no
|
|
719
|
+
* node in the source, and the nesting is what settles which class it means.
|
|
573
720
|
*/
|
|
574
|
-
declare function collectFileConstants(filePath: string, root: Node): FileConstants;
|
|
721
|
+
declare function collectFileConstants(filePath: string, root: Node, associationCalls?: readonly RbAssociationCalls[], inflections?: RbInflections): FileConstants;
|
|
575
722
|
/**
|
|
576
723
|
* Link every constant read anywhere in the run to the definition behind it,
|
|
577
724
|
* looking outwards from the nesting it was read inside the way Ruby does.
|
|
@@ -579,6 +726,10 @@ declare function collectFileConstants(filePath: string, root: Node): FileConstan
|
|
|
579
726
|
* `binds(refKey, defKey)` is the link itself. `rbConstantFrom(from, to)`
|
|
580
727
|
* is the same link at file level, which is the closest thing to an
|
|
581
728
|
* import graph a language without imports has.
|
|
729
|
+
*
|
|
730
|
+
* `declaresAssociation` is emitted here as well, because an association's
|
|
731
|
+
* target is one of those references and the shared rules read the pair
|
|
732
|
+
* together.
|
|
582
733
|
*/
|
|
583
734
|
declare function emitConstantBindings(db: Database, perFile: Iterable<FileConstants>): void;
|
|
584
735
|
|
|
@@ -597,7 +748,7 @@ declare function readKey(filePath: string, written: Node, enclosing: Node | null
|
|
|
597
748
|
* Walk a file and emit the value facts. A method is walked in its own right,
|
|
598
749
|
* so a body's returns and calls belong to the method that wrote them.
|
|
599
750
|
*/
|
|
600
|
-
declare function emitValueFacts(db: Database, filePath: string, root: Node): void;
|
|
751
|
+
declare function emitValueFacts(db: Database, filePath: string, root: Node, bodyBlocks?: BodyBlocks): void;
|
|
601
752
|
|
|
602
753
|
/**
|
|
603
754
|
* The session behind `suss ask why` on a Ruby project: a tree-sitter
|
|
@@ -810,4 +961,4 @@ declare function methodDefinitionsIn(file: string, root: Node): Map<string, Node
|
|
|
810
961
|
*/
|
|
811
962
|
declare const ADAPTER_VERSION = "0.1.0";
|
|
812
963
|
|
|
813
|
-
export { ADAPTER_VERSION, type AncestorEntry, type AncestorLookup, type Ancestry, type CallArgs, type ClassInfo, type ConstantPathConvention, type ControllerActions, type DiscoveryOptions, type EvaluatedFile, type ExtractRubyOptions, type ExtractRubyResult, type FileCache, type FileConstants, type GraphqlObjectFields, type GraphqlTypeNameConvention, type MethodLookup, type ParameterBindings, type ProjectNodes, type Range, type RbStatusCall, type RbStoragePattern, type ReachedBody, type RubyDiscoveryPattern, type RubyExtendsSite, type RubyPack, type RubyRequireSite, type RubyStubEvidence, type RubyStubEvidenceOptions, type RubyValueHandle, RubyWhySession, type RubyWhySessionOptions, type TypeReadContext, ancestryOf, bindEvaluator, bodyStatements, booleanLiteralValue, collectFileConstants, createFileCache, discoverUnits, emitConstantBindings, emitEntryFact, emitValueFacts, evaluatedValue, extractRubyProject, field, findRubyFiles, graphqlTypeNameFromQualified, hashKeySymbolName, inheritedStatements, methodDefinitionsIn, methodInAncestry, nestedStatements, nodeId, parseRuby, parseRubySync, preloadRubyGrammar, qualifyConstantRef, reachDefinition, readCallArgs, readKey, resolveConstantFile, rubyStubEvidence, runStatements, stringLiteralValue, stringValueOf, symbolValue, typeShapeFromNode, underscoreConstantPath, unitKey, walkClasses, walkDefinitions };
|
|
964
|
+
export { ADAPTER_VERSION, type AncestorEntry, type AncestorLookup, type Ancestry, type CallArgs, type ClassInfo, type ConstantPathConvention, type ControllerActions, type DiscoveryOptions, type EvaluatedFile, type ExtractRubyOptions, type ExtractRubyResult, type FileCache, type FileConstants, type GraphqlObjectFields, type GraphqlTypeNameConvention, type MethodLookup, type ParameterBindings, type ProjectNodes, type Range, type RbBodyBlock, type RbInflections, type RbStatusCall, type RbStoragePattern, type ReachedBody, type RubyDiscoveryPattern, type RubyExtendsSite, type RubyPack, type RubyRequireSite, type RubyStubEvidence, type RubyStubEvidenceOptions, type RubyValueHandle, RubyWhySession, type RubyWhySessionOptions, type TypeReadContext, ancestryOf, bindEvaluator, bodyStatements, booleanLiteralValue, collectFileConstants, createFileCache, discoverUnits, emitConstantBindings, emitEntryFact, emitValueFacts, evaluatedValue, extractRubyProject, field, findRubyFiles, graphqlTypeNameFromQualified, hashKeySymbolName, inheritedStatements, methodDefinitionsIn, methodInAncestry, nestedStatements, nodeId, parseRuby, parseRubySync, preloadRubyGrammar, qualifyConstantRef, reachDefinition, readCallArgs, readKey, resolveConstantFile, rubyStubEvidence, runStatements, stringLiteralValue, stringValueOf, symbolValue, typeShapeFromNode, underscoreConstantPath, unitKey, walkClasses, walkDefinitions };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { Database } from '@suss/datalog';
|
|
1
2
|
import { Tree, Node } from 'web-tree-sitter';
|
|
2
3
|
export { Node as RbNode, Tree as RbTree } from 'web-tree-sitter';
|
|
3
|
-
import { Database } from '@suss/datalog';
|
|
4
4
|
import { TypeShape, BehavioralSummary } from '@suss/behavioral-ir';
|
|
5
5
|
import { RawCodeStructure, TimingReport, ExtractionReport, CacheDiagnostic, ExtractorOptions } from '@suss/extractor';
|
|
6
6
|
import { WhyExplained } from '@suss/resolution';
|
|
@@ -38,6 +38,19 @@ declare function bodyStatements(body: Node): Node[];
|
|
|
38
38
|
* included; the caller descends into that one itself.
|
|
39
39
|
*/
|
|
40
40
|
declare function nestedStatements(stmt: Node): Node[];
|
|
41
|
+
/** What a pack says about one call whose block runs as part of the body around it, with the fields it left out settled. */
|
|
42
|
+
interface BodyBlockKind {
|
|
43
|
+
/** Whether the library only gives the call to a module, so a class writing the same name means something else. */
|
|
44
|
+
readonly moduleOnly: boolean;
|
|
45
|
+
/** Whether a `def` in the block declares a method on the class itself rather than on an instance. */
|
|
46
|
+
readonly definesClassMethods: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The calls a run's packs say run their block as part of the class or
|
|
50
|
+
* module body it is written in, by the name a project writes. Ruby
|
|
51
|
+
* defines no such call, so this is empty until a pack declares one.
|
|
52
|
+
*/
|
|
53
|
+
type BodyBlocks = ReadonlyMap<string, BodyBlockKind>;
|
|
41
54
|
/** A call whose block is the thing being configured rather than a place statements run. */
|
|
42
55
|
type BlockConfigures = (call: Node) => boolean;
|
|
43
56
|
/**
|
|
@@ -92,6 +105,33 @@ declare function underscoreConstantPath(qualifiedName: string, acronyms?: readon
|
|
|
92
105
|
*/
|
|
93
106
|
declare function resolveConstantFile(root: string, qualifiedName: string, convention: ConstantPathConvention, acronyms?: readonly string[]): string | null;
|
|
94
107
|
|
|
108
|
+
/**
|
|
109
|
+
* The method names a class gets under a name the source computes.
|
|
110
|
+
*
|
|
111
|
+
* `KEYS.each { |key| define_method(key) { ... } }` defines one method
|
|
112
|
+
* per element of `KEYS`, and a reader of `def` nodes sees none of them.
|
|
113
|
+
* Reading the names lets a lookup for a name none of them defines carry
|
|
114
|
+
* on up the ancestry instead of stopping at the class.
|
|
115
|
+
*
|
|
116
|
+
* Which calls those are, and what each name is written as, arrive as
|
|
117
|
+
* `definesMethodFrom` and `nameTurnsOn`, so this reads no source of its
|
|
118
|
+
* own. Each name goes through the shared value evaluator, which follows
|
|
119
|
+
* names and constants across the whole run. A name read only in part
|
|
120
|
+
* becomes a pattern the whole name has to match.
|
|
121
|
+
*/
|
|
122
|
+
|
|
123
|
+
/** What the dynamic definitions in one class define. */
|
|
124
|
+
interface DefinedNames {
|
|
125
|
+
/** Every name this reader read one as defining. */
|
|
126
|
+
readonly names: ReadonlySet<string>;
|
|
127
|
+
/** What a name read only in part has to match for the class to be defining it. */
|
|
128
|
+
readonly patterns: readonly RegExp[];
|
|
129
|
+
/** Whether one of them was given a name this reader could not read. */
|
|
130
|
+
readonly unreadable: boolean;
|
|
131
|
+
}
|
|
132
|
+
/** Every class's reading, by the key the value facts give the class node. */
|
|
133
|
+
type DynamicNames = ReadonlyMap<string, DefinedNames>;
|
|
134
|
+
|
|
95
135
|
/**
|
|
96
136
|
* Resolves Ruby constant paths against lexical nesting.
|
|
97
137
|
*
|
|
@@ -174,6 +214,13 @@ type AncestorEntry = {
|
|
|
174
214
|
};
|
|
175
215
|
/** A class and everything it inherits from, in Ruby's own method-lookup order. */
|
|
176
216
|
type Ancestry = readonly AncestorEntry[];
|
|
217
|
+
/** What reading a class body takes beyond the body: the run's facts, and what its packs declare about a block written in one. */
|
|
218
|
+
interface BodyReading {
|
|
219
|
+
readonly facts?: Database | undefined;
|
|
220
|
+
readonly bodyBlocks?: BodyBlocks | undefined;
|
|
221
|
+
/** What each class defines under a name the source computes, by class key. */
|
|
222
|
+
readonly dynamicNames?: DynamicNames | undefined;
|
|
223
|
+
}
|
|
177
224
|
/** What a walk needs to reach a class it knows only by name. */
|
|
178
225
|
interface AncestorLookup {
|
|
179
226
|
/** Directory the constant-to-path convention resolves an ancestor's name against. */
|
|
@@ -228,7 +275,7 @@ type MethodLookup =
|
|
|
228
275
|
* The method `name` resolves to. An unread ancestor nearer than any
|
|
229
276
|
* definition stops the search, since Ruby would have called that one first.
|
|
230
277
|
*/
|
|
231
|
-
declare function methodInAncestry(ancestry: Ancestry, name: string): MethodLookup;
|
|
278
|
+
declare function methodInAncestry(ancestry: Ancestry, name: string, read?: BodyReading): MethodLookup;
|
|
232
279
|
/** Every statement of every body reached, most distant ancestor first, so a nearer declaration overwrites what it inherits. */
|
|
233
280
|
declare function inheritedStatements(ancestry: Ancestry, blockConfigures?: BlockConfigures): Array<{
|
|
234
281
|
block: ReachedBody;
|
|
@@ -271,6 +318,40 @@ interface RubyPack {
|
|
|
271
318
|
storage?: RbStoragePattern[];
|
|
272
319
|
/** Calls the library gives a project for reading a model through a batching loader, rather than on the model itself. */
|
|
273
320
|
loaders?: RbLoaderPattern[];
|
|
321
|
+
/** Calls the library gives a class or module body whose block runs as part of that body. */
|
|
322
|
+
bodyBlocks?: RbBodyBlock[];
|
|
323
|
+
/** What the project taught the library's inflector, which comes ahead of the adapter's own defaults. */
|
|
324
|
+
inflections?: RbInflections;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* The words and rules a project registered with its library's
|
|
328
|
+
* inflector. The library says where a project writes them and how; what
|
|
329
|
+
* arrives here is the reading, so the adapter applies them without
|
|
330
|
+
* knowing any of that.
|
|
331
|
+
*/
|
|
332
|
+
interface RbInflections {
|
|
333
|
+
/** Words a constant name keeps whole: `API` gives `APIToken` rather than `ApiToken`. */
|
|
334
|
+
acronyms?: string[];
|
|
335
|
+
/** A plural and the singular it comes from: `["people", "person"]`. */
|
|
336
|
+
irregular?: Array<[plural: string, singular: string]>;
|
|
337
|
+
/** Words spelled the same in both numbers. */
|
|
338
|
+
uncountable?: string[];
|
|
339
|
+
/** A rule turning a word singular, and what the part it matched becomes. A rule written `/body/flags` is a pattern; anything else is the text to replace. */
|
|
340
|
+
singular?: Array<[rule: string, replacement: string]>;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* A receiverless call a library gives a class or module body, whose
|
|
344
|
+
* block runs as part of that body rather than keeping what it declares
|
|
345
|
+
* to itself. Ruby has no such call of its own, so a run whose packs
|
|
346
|
+
* declare nothing here never opens a block out into the body.
|
|
347
|
+
*/
|
|
348
|
+
interface RbBodyBlock {
|
|
349
|
+
/** The call as a project writes it, `included`. */
|
|
350
|
+
name: string;
|
|
351
|
+
/** Set when the library only gives the call to a module, as a concern's own `included` is. */
|
|
352
|
+
moduleOnly?: boolean;
|
|
353
|
+
/** Set when a `def` in the block declares a method on the class itself rather than on an instance. */
|
|
354
|
+
definesClassMethods?: boolean;
|
|
274
355
|
}
|
|
275
356
|
/**
|
|
276
357
|
* The calls a library gives a project for making a request. A method
|
|
@@ -322,17 +403,50 @@ interface RbClientResponse {
|
|
|
322
403
|
* Ruby writes no return type, so a call is matched by what its receiver
|
|
323
404
|
* inherits from. A model is a class that reaches one of these base classes,
|
|
324
405
|
* whether the project declares an intermediate one or not.
|
|
406
|
+
*
|
|
407
|
+
* `reads` and `writes` are the whole of what the library does to the
|
|
408
|
+
* database. A method in neither list is the project's own or the
|
|
409
|
+
* language's, and the reach walk follows it instead.
|
|
325
410
|
*/
|
|
326
411
|
interface RbStoragePattern {
|
|
327
412
|
/** Base classes the library gives a model, `ActiveRecord::Base` for Rails. */
|
|
328
413
|
baseClasses: string[];
|
|
329
|
-
/** Methods that change what is stored.
|
|
414
|
+
/** Methods the library defines that change what is stored. */
|
|
330
415
|
writes: string[];
|
|
416
|
+
/** Methods the library defines that run a query. */
|
|
417
|
+
reads: string[];
|
|
331
418
|
/** Methods whose result is the model again: one record, or a relation a later read narrows to one. */
|
|
332
419
|
givesBack: string[];
|
|
420
|
+
/** Methods that pick rows by the primary key when they are given a positional argument, and the column that is. */
|
|
421
|
+
byPrimaryKey?: RbPrimaryKeyLookup;
|
|
422
|
+
/** Read methods whose symbol arguments are the columns they ask for. */
|
|
423
|
+
columnArguments?: string[];
|
|
424
|
+
/** How the library declares that one model reaches another, when it has such a thing. */
|
|
425
|
+
associations?: RbAssociationCalls;
|
|
333
426
|
/** Which database is behind the connection, which the project settles. */
|
|
334
427
|
storageSystem: "postgresql" | "mysql" | "sqlite";
|
|
335
428
|
}
|
|
429
|
+
/** Which methods take the primary key positionally, and what that column is called. */
|
|
430
|
+
interface RbPrimaryKeyLookup {
|
|
431
|
+
methods: string[];
|
|
432
|
+
/** The column the library uses unless a model says another. */
|
|
433
|
+
column: string;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* The class-body calls a library gives a model for declaring an
|
|
437
|
+
* association, and where one says which class it reaches. The two lists
|
|
438
|
+
* are apart because the target's name is the association's singularised
|
|
439
|
+
* for a plural call and the association's as written for a singular one,
|
|
440
|
+
* and only the library knows which of its calls is which.
|
|
441
|
+
*/
|
|
442
|
+
interface RbAssociationCalls {
|
|
443
|
+
/** Calls written in the singular, `belongs_to :account` and `has_one :profile`. */
|
|
444
|
+
singular: string[];
|
|
445
|
+
/** Calls written in the plural, `has_many :statuses`. */
|
|
446
|
+
plural: string[];
|
|
447
|
+
/** The keyword whose value is the target class's name instead, `class_name`. */
|
|
448
|
+
classNameKeyword: string;
|
|
449
|
+
}
|
|
336
450
|
/**
|
|
337
451
|
* A loader that batches reads of a model on the caller's behalf, graphql-ruby's
|
|
338
452
|
* dataloader for one. The call that picks what to load is given the model as
|
|
@@ -489,14 +603,16 @@ interface GraphqlObjectFields {
|
|
|
489
603
|
/**
|
|
490
604
|
* storage.ts: which calls in a body talk to the database, for Ruby.
|
|
491
605
|
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
*
|
|
606
|
+
* One rule covers both ways a call is written. It is database work when the
|
|
607
|
+
* pack lists its method as a read or a write, the class behind its receiver
|
|
608
|
+
* reaches a base class the pack lists, and the project does not declare that
|
|
609
|
+
* method itself. A chain counts once, at the outermost call the library
|
|
610
|
+
* defines, and every other call is left to the reach walk. The README says
|
|
611
|
+
* why ancestry.
|
|
496
612
|
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
*
|
|
613
|
+
* The class behind the receiver comes from the constant bindings for a chain
|
|
614
|
+
* written from a constant and from the resolution rules otherwise. A loader
|
|
615
|
+
* pattern adds calls given the model as an argument, tested the same way.
|
|
500
616
|
*/
|
|
501
617
|
|
|
502
618
|
interface RbStorageOptions {
|
|
@@ -523,6 +639,10 @@ interface BodyReadOptions {
|
|
|
523
639
|
readonly storage?: RbStorageOptions | undefined;
|
|
524
640
|
/** The methods every pack in the run said its own library defines, which are left off an effect list. */
|
|
525
641
|
readonly inheritedMethods?: InheritedMethods | undefined;
|
|
642
|
+
/** The calls every pack in the run said run their block as part of the body around it. */
|
|
643
|
+
readonly bodyBlocks?: BodyBlocks | undefined;
|
|
644
|
+
/** What each class defines under a name the source computes, by class key. */
|
|
645
|
+
readonly dynamicNames?: DynamicNames | undefined;
|
|
526
646
|
}
|
|
527
647
|
interface DiscoveryOptions extends BodyReadOptions {
|
|
528
648
|
packs: RubyPack[];
|
|
@@ -547,6 +667,29 @@ interface ReachSeed {
|
|
|
547
667
|
}
|
|
548
668
|
declare function discoverUnits(root: Node, options: DiscoveryOptions): Promise<RawCodeStructure[]>;
|
|
549
669
|
|
|
670
|
+
/**
|
|
671
|
+
* associations.ts: reading `has_many :statuses` out of a model's body.
|
|
672
|
+
*
|
|
673
|
+
* A pack says which calls declare an association. The target is the
|
|
674
|
+
* class the `class_name:` keyword gives, or the association's own name
|
|
675
|
+
* put through ActiveSupport's default inflections when the declaration
|
|
676
|
+
* gives none. That inflected name is written nowhere in the source, so
|
|
677
|
+
* the declaration comes with a constant reference of its own and the
|
|
678
|
+
* constant bindings settle it from the nesting the model is written in:
|
|
679
|
+
* a `has_many :statuses` inside `Admin::Account` reaches `Admin::Status`
|
|
680
|
+
* before it reaches `Status`.
|
|
681
|
+
*/
|
|
682
|
+
|
|
683
|
+
/** One association a class or module body declares. */
|
|
684
|
+
interface AssociationDeclaration {
|
|
685
|
+
/** The class or module whose body declares it, keyed the way a class is. */
|
|
686
|
+
readonly classKey: string;
|
|
687
|
+
/** The association's name, as the call writes it. */
|
|
688
|
+
readonly name: string;
|
|
689
|
+
/** The class it reaches, as a reference the constant bindings settle. */
|
|
690
|
+
readonly target: ConstantReference;
|
|
691
|
+
}
|
|
692
|
+
|
|
550
693
|
/** A definition every file in the run can see, under the name it is written as. */
|
|
551
694
|
interface ConstantDefinition {
|
|
552
695
|
readonly qualifiedName: string;
|
|
@@ -566,12 +709,16 @@ interface FileConstants {
|
|
|
566
709
|
readonly filePath: string;
|
|
567
710
|
readonly definitions: readonly ConstantDefinition[];
|
|
568
711
|
readonly references: readonly ConstantReference[];
|
|
712
|
+
/** Empty unless a pack in the run said what an association declaration looks like. */
|
|
713
|
+
readonly associations: readonly AssociationDeclaration[];
|
|
569
714
|
}
|
|
570
715
|
/**
|
|
571
716
|
* Every constant this file defines and every one it reads. One walk, because
|
|
572
717
|
* the nesting a reference is written inside is what the walk already knows.
|
|
718
|
+
* An association's target is read here too: the name it inflects to has no
|
|
719
|
+
* node in the source, and the nesting is what settles which class it means.
|
|
573
720
|
*/
|
|
574
|
-
declare function collectFileConstants(filePath: string, root: Node): FileConstants;
|
|
721
|
+
declare function collectFileConstants(filePath: string, root: Node, associationCalls?: readonly RbAssociationCalls[], inflections?: RbInflections): FileConstants;
|
|
575
722
|
/**
|
|
576
723
|
* Link every constant read anywhere in the run to the definition behind it,
|
|
577
724
|
* looking outwards from the nesting it was read inside the way Ruby does.
|
|
@@ -579,6 +726,10 @@ declare function collectFileConstants(filePath: string, root: Node): FileConstan
|
|
|
579
726
|
* `binds(refKey, defKey)` is the link itself. `rbConstantFrom(from, to)`
|
|
580
727
|
* is the same link at file level, which is the closest thing to an
|
|
581
728
|
* import graph a language without imports has.
|
|
729
|
+
*
|
|
730
|
+
* `declaresAssociation` is emitted here as well, because an association's
|
|
731
|
+
* target is one of those references and the shared rules read the pair
|
|
732
|
+
* together.
|
|
582
733
|
*/
|
|
583
734
|
declare function emitConstantBindings(db: Database, perFile: Iterable<FileConstants>): void;
|
|
584
735
|
|
|
@@ -597,7 +748,7 @@ declare function readKey(filePath: string, written: Node, enclosing: Node | null
|
|
|
597
748
|
* Walk a file and emit the value facts. A method is walked in its own right,
|
|
598
749
|
* so a body's returns and calls belong to the method that wrote them.
|
|
599
750
|
*/
|
|
600
|
-
declare function emitValueFacts(db: Database, filePath: string, root: Node): void;
|
|
751
|
+
declare function emitValueFacts(db: Database, filePath: string, root: Node, bodyBlocks?: BodyBlocks): void;
|
|
601
752
|
|
|
602
753
|
/**
|
|
603
754
|
* The session behind `suss ask why` on a Ruby project: a tree-sitter
|
|
@@ -810,4 +961,4 @@ declare function methodDefinitionsIn(file: string, root: Node): Map<string, Node
|
|
|
810
961
|
*/
|
|
811
962
|
declare const ADAPTER_VERSION = "0.1.0";
|
|
812
963
|
|
|
813
|
-
export { ADAPTER_VERSION, type AncestorEntry, type AncestorLookup, type Ancestry, type CallArgs, type ClassInfo, type ConstantPathConvention, type ControllerActions, type DiscoveryOptions, type EvaluatedFile, type ExtractRubyOptions, type ExtractRubyResult, type FileCache, type FileConstants, type GraphqlObjectFields, type GraphqlTypeNameConvention, type MethodLookup, type ParameterBindings, type ProjectNodes, type Range, type RbStatusCall, type RbStoragePattern, type ReachedBody, type RubyDiscoveryPattern, type RubyExtendsSite, type RubyPack, type RubyRequireSite, type RubyStubEvidence, type RubyStubEvidenceOptions, type RubyValueHandle, RubyWhySession, type RubyWhySessionOptions, type TypeReadContext, ancestryOf, bindEvaluator, bodyStatements, booleanLiteralValue, collectFileConstants, createFileCache, discoverUnits, emitConstantBindings, emitEntryFact, emitValueFacts, evaluatedValue, extractRubyProject, field, findRubyFiles, graphqlTypeNameFromQualified, hashKeySymbolName, inheritedStatements, methodDefinitionsIn, methodInAncestry, nestedStatements, nodeId, parseRuby, parseRubySync, preloadRubyGrammar, qualifyConstantRef, reachDefinition, readCallArgs, readKey, resolveConstantFile, rubyStubEvidence, runStatements, stringLiteralValue, stringValueOf, symbolValue, typeShapeFromNode, underscoreConstantPath, unitKey, walkClasses, walkDefinitions };
|
|
964
|
+
export { ADAPTER_VERSION, type AncestorEntry, type AncestorLookup, type Ancestry, type CallArgs, type ClassInfo, type ConstantPathConvention, type ControllerActions, type DiscoveryOptions, type EvaluatedFile, type ExtractRubyOptions, type ExtractRubyResult, type FileCache, type FileConstants, type GraphqlObjectFields, type GraphqlTypeNameConvention, type MethodLookup, type ParameterBindings, type ProjectNodes, type Range, type RbBodyBlock, type RbInflections, type RbStatusCall, type RbStoragePattern, type ReachedBody, type RubyDiscoveryPattern, type RubyExtendsSite, type RubyPack, type RubyRequireSite, type RubyStubEvidence, type RubyStubEvidenceOptions, type RubyValueHandle, RubyWhySession, type RubyWhySessionOptions, type TypeReadContext, ancestryOf, bindEvaluator, bodyStatements, booleanLiteralValue, collectFileConstants, createFileCache, discoverUnits, emitConstantBindings, emitEntryFact, emitValueFacts, evaluatedValue, extractRubyProject, field, findRubyFiles, graphqlTypeNameFromQualified, hashKeySymbolName, inheritedStatements, methodDefinitionsIn, methodInAncestry, nestedStatements, nodeId, parseRuby, parseRubySync, preloadRubyGrammar, qualifyConstantRef, reachDefinition, readCallArgs, readKey, resolveConstantFile, rubyStubEvidence, runStatements, stringLiteralValue, stringValueOf, symbolValue, typeShapeFromNode, underscoreConstantPath, unitKey, walkClasses, walkDefinitions };
|