@suss/adapter-ruby 0.28.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 +3523 -2605
- package/dist/index.d.cts +169 -11
- package/dist/index.d.ts +169 -11
- package/dist/index.js +3556 -2634
- 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. */
|
|
@@ -214,7 +261,8 @@ type MethodLookup =
|
|
|
214
261
|
* `cause` says why the search stopped, since a caller deciding
|
|
215
262
|
* whether to look further needs more than the sentence: `unreadAncestor`
|
|
216
263
|
* is a base whose file this run could not open, and `dynamicDefine`
|
|
217
|
-
* is a
|
|
264
|
+
* is a `define_method` call that defines this name, or one whose own
|
|
265
|
+
* name this reader could not read.
|
|
218
266
|
*/
|
|
219
267
|
| {
|
|
220
268
|
type: "unsettled";
|
|
@@ -227,7 +275,7 @@ type MethodLookup =
|
|
|
227
275
|
* The method `name` resolves to. An unread ancestor nearer than any
|
|
228
276
|
* definition stops the search, since Ruby would have called that one first.
|
|
229
277
|
*/
|
|
230
|
-
declare function methodInAncestry(ancestry: Ancestry, name: string): MethodLookup;
|
|
278
|
+
declare function methodInAncestry(ancestry: Ancestry, name: string, read?: BodyReading): MethodLookup;
|
|
231
279
|
/** Every statement of every body reached, most distant ancestor first, so a nearer declaration overwrites what it inherits. */
|
|
232
280
|
declare function inheritedStatements(ancestry: Ancestry, blockConfigures?: BlockConfigures): Array<{
|
|
233
281
|
block: ReachedBody;
|
|
@@ -270,6 +318,40 @@ interface RubyPack {
|
|
|
270
318
|
storage?: RbStoragePattern[];
|
|
271
319
|
/** Calls the library gives a project for reading a model through a batching loader, rather than on the model itself. */
|
|
272
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;
|
|
273
355
|
}
|
|
274
356
|
/**
|
|
275
357
|
* The calls a library gives a project for making a request. A method
|
|
@@ -321,15 +403,50 @@ interface RbClientResponse {
|
|
|
321
403
|
* Ruby writes no return type, so a call is matched by what its receiver
|
|
322
404
|
* inherits from. A model is a class that reaches one of these base classes,
|
|
323
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.
|
|
324
410
|
*/
|
|
325
411
|
interface RbStoragePattern {
|
|
326
412
|
/** Base classes the library gives a model, `ActiveRecord::Base` for Rails. */
|
|
327
413
|
baseClasses: string[];
|
|
328
|
-
/** Methods that change what is stored.
|
|
414
|
+
/** Methods the library defines that change what is stored. */
|
|
329
415
|
writes: string[];
|
|
416
|
+
/** Methods the library defines that run a query. */
|
|
417
|
+
reads: string[];
|
|
418
|
+
/** Methods whose result is the model again: one record, or a relation a later read narrows to one. */
|
|
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;
|
|
330
426
|
/** Which database is behind the connection, which the project settles. */
|
|
331
427
|
storageSystem: "postgresql" | "mysql" | "sqlite";
|
|
332
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
|
+
}
|
|
333
450
|
/**
|
|
334
451
|
* A loader that batches reads of a model on the caller's behalf, graphql-ruby's
|
|
335
452
|
* dataloader for one. The call that picks what to load is given the model as
|
|
@@ -486,10 +603,16 @@ interface GraphqlObjectFields {
|
|
|
486
603
|
/**
|
|
487
604
|
* storage.ts: which calls in a body talk to the database, for Ruby.
|
|
488
605
|
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
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.
|
|
612
|
+
*
|
|
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.
|
|
493
616
|
*/
|
|
494
617
|
|
|
495
618
|
interface RbStorageOptions {
|
|
@@ -516,6 +639,10 @@ interface BodyReadOptions {
|
|
|
516
639
|
readonly storage?: RbStorageOptions | undefined;
|
|
517
640
|
/** The methods every pack in the run said its own library defines, which are left off an effect list. */
|
|
518
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;
|
|
519
646
|
}
|
|
520
647
|
interface DiscoveryOptions extends BodyReadOptions {
|
|
521
648
|
packs: RubyPack[];
|
|
@@ -540,6 +667,29 @@ interface ReachSeed {
|
|
|
540
667
|
}
|
|
541
668
|
declare function discoverUnits(root: Node, options: DiscoveryOptions): Promise<RawCodeStructure[]>;
|
|
542
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
|
+
|
|
543
693
|
/** A definition every file in the run can see, under the name it is written as. */
|
|
544
694
|
interface ConstantDefinition {
|
|
545
695
|
readonly qualifiedName: string;
|
|
@@ -559,12 +709,16 @@ interface FileConstants {
|
|
|
559
709
|
readonly filePath: string;
|
|
560
710
|
readonly definitions: readonly ConstantDefinition[];
|
|
561
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[];
|
|
562
714
|
}
|
|
563
715
|
/**
|
|
564
716
|
* Every constant this file defines and every one it reads. One walk, because
|
|
565
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.
|
|
566
720
|
*/
|
|
567
|
-
declare function collectFileConstants(filePath: string, root: Node): FileConstants;
|
|
721
|
+
declare function collectFileConstants(filePath: string, root: Node, associationCalls?: readonly RbAssociationCalls[], inflections?: RbInflections): FileConstants;
|
|
568
722
|
/**
|
|
569
723
|
* Link every constant read anywhere in the run to the definition behind it,
|
|
570
724
|
* looking outwards from the nesting it was read inside the way Ruby does.
|
|
@@ -572,6 +726,10 @@ declare function collectFileConstants(filePath: string, root: Node): FileConstan
|
|
|
572
726
|
* `binds(refKey, defKey)` is the link itself. `rbConstantFrom(from, to)`
|
|
573
727
|
* is the same link at file level, which is the closest thing to an
|
|
574
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.
|
|
575
733
|
*/
|
|
576
734
|
declare function emitConstantBindings(db: Database, perFile: Iterable<FileConstants>): void;
|
|
577
735
|
|
|
@@ -590,7 +748,7 @@ declare function readKey(filePath: string, written: Node, enclosing: Node | null
|
|
|
590
748
|
* Walk a file and emit the value facts. A method is walked in its own right,
|
|
591
749
|
* so a body's returns and calls belong to the method that wrote them.
|
|
592
750
|
*/
|
|
593
|
-
declare function emitValueFacts(db: Database, filePath: string, root: Node): void;
|
|
751
|
+
declare function emitValueFacts(db: Database, filePath: string, root: Node, bodyBlocks?: BodyBlocks): void;
|
|
594
752
|
|
|
595
753
|
/**
|
|
596
754
|
* The session behind `suss ask why` on a Ruby project: a tree-sitter
|
|
@@ -803,4 +961,4 @@ declare function methodDefinitionsIn(file: string, root: Node): Map<string, Node
|
|
|
803
961
|
*/
|
|
804
962
|
declare const ADAPTER_VERSION = "0.1.0";
|
|
805
963
|
|
|
806
|
-
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. */
|
|
@@ -214,7 +261,8 @@ type MethodLookup =
|
|
|
214
261
|
* `cause` says why the search stopped, since a caller deciding
|
|
215
262
|
* whether to look further needs more than the sentence: `unreadAncestor`
|
|
216
263
|
* is a base whose file this run could not open, and `dynamicDefine`
|
|
217
|
-
* is a
|
|
264
|
+
* is a `define_method` call that defines this name, or one whose own
|
|
265
|
+
* name this reader could not read.
|
|
218
266
|
*/
|
|
219
267
|
| {
|
|
220
268
|
type: "unsettled";
|
|
@@ -227,7 +275,7 @@ type MethodLookup =
|
|
|
227
275
|
* The method `name` resolves to. An unread ancestor nearer than any
|
|
228
276
|
* definition stops the search, since Ruby would have called that one first.
|
|
229
277
|
*/
|
|
230
|
-
declare function methodInAncestry(ancestry: Ancestry, name: string): MethodLookup;
|
|
278
|
+
declare function methodInAncestry(ancestry: Ancestry, name: string, read?: BodyReading): MethodLookup;
|
|
231
279
|
/** Every statement of every body reached, most distant ancestor first, so a nearer declaration overwrites what it inherits. */
|
|
232
280
|
declare function inheritedStatements(ancestry: Ancestry, blockConfigures?: BlockConfigures): Array<{
|
|
233
281
|
block: ReachedBody;
|
|
@@ -270,6 +318,40 @@ interface RubyPack {
|
|
|
270
318
|
storage?: RbStoragePattern[];
|
|
271
319
|
/** Calls the library gives a project for reading a model through a batching loader, rather than on the model itself. */
|
|
272
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;
|
|
273
355
|
}
|
|
274
356
|
/**
|
|
275
357
|
* The calls a library gives a project for making a request. A method
|
|
@@ -321,15 +403,50 @@ interface RbClientResponse {
|
|
|
321
403
|
* Ruby writes no return type, so a call is matched by what its receiver
|
|
322
404
|
* inherits from. A model is a class that reaches one of these base classes,
|
|
323
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.
|
|
324
410
|
*/
|
|
325
411
|
interface RbStoragePattern {
|
|
326
412
|
/** Base classes the library gives a model, `ActiveRecord::Base` for Rails. */
|
|
327
413
|
baseClasses: string[];
|
|
328
|
-
/** Methods that change what is stored.
|
|
414
|
+
/** Methods the library defines that change what is stored. */
|
|
329
415
|
writes: string[];
|
|
416
|
+
/** Methods the library defines that run a query. */
|
|
417
|
+
reads: string[];
|
|
418
|
+
/** Methods whose result is the model again: one record, or a relation a later read narrows to one. */
|
|
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;
|
|
330
426
|
/** Which database is behind the connection, which the project settles. */
|
|
331
427
|
storageSystem: "postgresql" | "mysql" | "sqlite";
|
|
332
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
|
+
}
|
|
333
450
|
/**
|
|
334
451
|
* A loader that batches reads of a model on the caller's behalf, graphql-ruby's
|
|
335
452
|
* dataloader for one. The call that picks what to load is given the model as
|
|
@@ -486,10 +603,16 @@ interface GraphqlObjectFields {
|
|
|
486
603
|
/**
|
|
487
604
|
* storage.ts: which calls in a body talk to the database, for Ruby.
|
|
488
605
|
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
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.
|
|
612
|
+
*
|
|
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.
|
|
493
616
|
*/
|
|
494
617
|
|
|
495
618
|
interface RbStorageOptions {
|
|
@@ -516,6 +639,10 @@ interface BodyReadOptions {
|
|
|
516
639
|
readonly storage?: RbStorageOptions | undefined;
|
|
517
640
|
/** The methods every pack in the run said its own library defines, which are left off an effect list. */
|
|
518
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;
|
|
519
646
|
}
|
|
520
647
|
interface DiscoveryOptions extends BodyReadOptions {
|
|
521
648
|
packs: RubyPack[];
|
|
@@ -540,6 +667,29 @@ interface ReachSeed {
|
|
|
540
667
|
}
|
|
541
668
|
declare function discoverUnits(root: Node, options: DiscoveryOptions): Promise<RawCodeStructure[]>;
|
|
542
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
|
+
|
|
543
693
|
/** A definition every file in the run can see, under the name it is written as. */
|
|
544
694
|
interface ConstantDefinition {
|
|
545
695
|
readonly qualifiedName: string;
|
|
@@ -559,12 +709,16 @@ interface FileConstants {
|
|
|
559
709
|
readonly filePath: string;
|
|
560
710
|
readonly definitions: readonly ConstantDefinition[];
|
|
561
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[];
|
|
562
714
|
}
|
|
563
715
|
/**
|
|
564
716
|
* Every constant this file defines and every one it reads. One walk, because
|
|
565
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.
|
|
566
720
|
*/
|
|
567
|
-
declare function collectFileConstants(filePath: string, root: Node): FileConstants;
|
|
721
|
+
declare function collectFileConstants(filePath: string, root: Node, associationCalls?: readonly RbAssociationCalls[], inflections?: RbInflections): FileConstants;
|
|
568
722
|
/**
|
|
569
723
|
* Link every constant read anywhere in the run to the definition behind it,
|
|
570
724
|
* looking outwards from the nesting it was read inside the way Ruby does.
|
|
@@ -572,6 +726,10 @@ declare function collectFileConstants(filePath: string, root: Node): FileConstan
|
|
|
572
726
|
* `binds(refKey, defKey)` is the link itself. `rbConstantFrom(from, to)`
|
|
573
727
|
* is the same link at file level, which is the closest thing to an
|
|
574
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.
|
|
575
733
|
*/
|
|
576
734
|
declare function emitConstantBindings(db: Database, perFile: Iterable<FileConstants>): void;
|
|
577
735
|
|
|
@@ -590,7 +748,7 @@ declare function readKey(filePath: string, written: Node, enclosing: Node | null
|
|
|
590
748
|
* Walk a file and emit the value facts. A method is walked in its own right,
|
|
591
749
|
* so a body's returns and calls belong to the method that wrote them.
|
|
592
750
|
*/
|
|
593
|
-
declare function emitValueFacts(db: Database, filePath: string, root: Node): void;
|
|
751
|
+
declare function emitValueFacts(db: Database, filePath: string, root: Node, bodyBlocks?: BodyBlocks): void;
|
|
594
752
|
|
|
595
753
|
/**
|
|
596
754
|
* The session behind `suss ask why` on a Ruby project: a tree-sitter
|
|
@@ -803,4 +961,4 @@ declare function methodDefinitionsIn(file: string, root: Node): Map<string, Node
|
|
|
803
961
|
*/
|
|
804
962
|
declare const ADAPTER_VERSION = "0.1.0";
|
|
805
963
|
|
|
806
|
-
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 };
|