diff --git a/METAMODEL.md b/METAMODEL.md index 6e9952c..97096fa 100644 --- a/METAMODEL.md +++ b/METAMODEL.md @@ -384,7 +384,7 @@ implemented* (robustness test of the schema). | `kinds` | `Record` | the licit trait compositions per entity kind | | `edges` | EdgeKind[] | edge kinds this language can emit | | `notes` | string[] | documented static-analysis blind spots (reflection, `Class.forName`, dynamic `require`, macros pre-expansion, …) | -| `lexicon` | `{keywords, conventional, technical?, fileKinds?, interfacePrefix?}` | the language's words, for the vocabulary (§5.3): reserved words, names the language dictates, plumbing words, kinds whose name is a file path, C#'s interface marker | +| `lexicon` | `{keywords, conventional, technical?, fileKinds?, qualified?, interfacePrefix?}` | the language's words, for the vocabulary (§5.3): reserved words, names the language dictates, plumbing words, kinds whose name is a file path or a qualified path, C#'s interface marker | **Validation** of an entity against its profile: 1. `kind` exists in the profile; @@ -437,14 +437,16 @@ lexicon, optional on a profile and present on every shipped one: | `conventional` | whole identifiers the language or its runtime dictates — overrides, protocol members, operators (`toString`, `__init__`, `<=>`). Their words say nothing about the domain, so such a name contributes none | | `technical` | plumbing words of the language beyond the vocabulary's shared list, spelled as the splitter yields them (lowercase letters and digits) | | `fileKinds` | kinds whose name is a file path (TypeScript's `module`, Ruby's `file`): the extension is not a word | +| `qualified` | kinds the language names by a qualified path, each with the separator that ends the qualifier: a Java `package` by `.`, a Ruby `class` by `::`, every file kind by `/`. The words before the last separator name a container (`AcmeOrder::` of `AcmeOrder::Order`), so they weigh as incidental words — still counted, never dropped | | `interfacePrefix` | `true` in C# alone: `IOrderService` names an interface `OrderService` | `validateProfile` rejects an entry that could never do what its list says: listed twice (`lexicon-duplicate`); a keyword with no letter or a name holding whitespace, a technical entry the splitter cannot yield (`lexicon-not-a-word`); a conventional name or technical word a keyword already claims -(`lexicon-keyword-overlap`); a file kind the profile does not define -(`lexicon-unknown-kind`). The lexicon is not on the wire: `schemas/` is +(`lexicon-keyword-overlap`); a file or qualified kind the profile does not +define (`lexicon-unknown-kind`); a separator that is empty or holds a letter, a +mark or a digit (`lexicon-not-a-separator`). The lexicon is not on the wire: `schemas/` is unchanged by it. --- diff --git a/PLAN.md b/PLAN.md index 720b7ab..fc4c45a 100644 --- a/PLAN.md +++ b/PLAN.md @@ -4914,6 +4914,10 @@ lexicon?: { // splitter yields it: bean, jdbc (Java); concern (Ruby); live, web (Elixir) fileKinds?: string[]; // kinds whose name is a file path: TS/JS `module`, Ruby and Elixir // `file`, PHP `codeFile` — the extension is not a word + qualified?: Record; + // kinds the language names by a qualified path, with the separator + // that ends the qualifier: Java `package` ".", Ruby `class` "::", + // every file kind "/" — the qualifier's words weigh as incidental interfacePrefix?: true; // C#: `IOrderService` → order, service (I + Upper + lower) }; ``` @@ -4927,6 +4931,19 @@ character that is not a letter, a mark or a digit anyway — or `<=>`, a path, its `module` the dotted `AcmeOrder.Registry`, whose `.Registry` a profile-wide flag would drop as an extension), so it became `fileKinds`. +`qualified` was added when the aggregation was measured (M19a, below): a +qualified name speaks its container's words again, on every entity the +container holds. Full names made `acme` weigh 468 in the Ruby fixture and 392 +in the Elixir one, against about 50 for the next domain word, and `packages` +168 in the TypeScript one. A kind is listed when the LANGUAGE names it by a +path — Java's `package`, C#'s `namespace`, Clojure's `namespace` and Python's +`module` by `.`, Elixir's `module` and `protocol` by `.`, Ruby's `class`, +`module` and `constant` by `::`, PHP's `namespace` by `\`, every file kind by +`/`; Go's package (`package order`) and Rust's module (`mod line`) are named by +one identifier and are not listed. `validateProfile` gains +`lexicon-not-a-separator` (empty, or holding a letter, a mark or a digit), and +an undeclared qualified kind is `lexicon-unknown-kind`. + `ProfileSchema` mirrors it (a test parses every shipped profile through it unchanged); `codegraph profiles --lang X` prints it in full, between the kinds and the notes (the command's completeness rule); `schemas/` is unchanged — a @@ -4941,12 +4958,16 @@ the five file kinds above. TypeScript reserves exactly ECMAScript's words, so the two profiles read one `ECMASCRIPT_LEXICON`. **The split** (`split.ts`): one function, `splitIdentifier(name, kind, -lexicon)`, in six steps, each pinned by a table of hand cases per idiom: +lexicon)` → `{tokens, qualifier, numeric, conventional}`, in six steps, each +pinned by a table of hand cases per idiom: 1. for a kind in `fileKinds`, drop the extension of the last path segment - (`legacy/acme-report.ts` → `legacy/acme-report`); then the interface - prefix when the flag is set and the name starts `I` + Upper + lower - (`IOrderService`, not `IO`); + (`legacy/acme-report.ts` → `legacy/acme-report`); for a kind in + `qualified`, cut the name at the LAST occurrence of its separator — the + words before it are the qualifier, counted in `qualifier` + (`AcmeOrder::Order::Line` → 3 of `acme`, `order`, `order`, `line`); then + the interface prefix on the own name when the flag is set and it starts + `I` + Upper + lower (`IOrderService`, not `IO`; `Acme.IOrder` → `order`); 2. split on every character that is not a Unicode letter, mark or digit — universal, no data: `@total` → `total`, `valid?` → `valid`, `save!`, `name=`, `Order::Line`, `max_lines`, `acme-report`, `Repository\`1` → @@ -4974,7 +4995,10 @@ a non-empty run of letters, marks and digits, lowercase, never digits only, and a slice of the lowered name; the same input gives the same tokens; `split(a + sep + b) = split(a) ++ split(b)` for any character that is not a letter, a mark or a digit — without a lexicon, since the file kinds, the -interface prefix and a conventional name read the WHOLE name. A Σ is left +interface prefix and a conventional name read the WHOLE name. Qualification +moves no word: the tokens are the same with the qualified kinds removed, and +for a qualified kind `qualifier` is the number of words of `a` in +`a + separator + b`. A Σ is left out of the generator: its lower case depends on what follows it, so a token's lower case and its slice of the lowered name may differ (`ΟΔΟΣ` → `οδος`, pinned by hand). @@ -5021,32 +5045,50 @@ recorded in the artifact and overridable with `--weights role=n,…`: | `operation` | 3 | something it does; the prefix is usually technical (`get`, `find`, `build`) | | `parameter`, `local` | 1 | incidental: the "variable buried deep in a method" of the issue | | `other` | 1 | no trait says what it is | +| `qualifier` | 1 | not a role: the words before the last separator of a qualified name, whatever its role — a container's words, spoken again by every name it qualifies | An occurrence is one token in one name; a name carrying a word twice counts -twice. Both the weighted sum and the raw occurrence count are kept — one is -a judgement, the other a fact. - -**Scopes and aggregation** (`aggregate.ts`). Scopes are the kept -(view-filtered, non-stub) modules and types, plus the corpus root; an -operation's table is computed on demand (`--scope ID`), not stored (§20.5). -A scope's table is the union of the tables of every kept entity whose fold -container it is: `term → { weight, occurrences, carriers }` where `carriers` -is the number of distinct entities carrying the word. Per scope: `distinct` -and `distinctDomain` (the cognitive-load proxy), total `weight` and -`occurrences`, `skipped` by reason. Per term at the root: `spread.modules` -and `spread.types` (how many scopes speak it) and `exclusive` (the modules -whose table alone has it) — the overlap and the drift inputs. - -**The artifact** `vocabulary.json`, kind `codegraph.vocabulary/1`: a header -(generator, view descriptor, `weights`, `classes`, the technical list's -digest, `langs`), `terms[]` interned and sorted with their class, `scopes[]` -(root first, then modules then types in id order: `id`, `level`, `name`, -`kind`, `parent` scope index, `terms: [termIndex, weight, occurrences, -carriers][]` by weight descending then term) and `diagnostics` (skipped -counts, entities the fold could not place). Every scope id is an entity the -model declares and every index is in range — the closure property, tested. -Size is measured on BroadleafCommerce (3 312 types, 502 modules) before any -cap is considered; none is planned. +twice, and is one carrier. Both the weighted sum and the raw occurrence count +are kept — one is a judgement, the other a fact; a word weighing 0 still +occurs. + +**Scopes and aggregation** (`aggregate.ts`, `buildVocabulary(graph, +{view?, weights?, technical?, name?})`). Kept is the view's verdict on a +non-stub entity; a stub never speaks, whatever the view — its name was chosen +by another corpus (`String`). Scopes are the kept modules and types (by trait: +TModule first), plus the corpus root; an operation's table is computed on +demand (`--scope ID`), not stored (§20.5). A scope's table is the union of the +tables of every kept entity whose fold container it is — the NEAREST: a nested +type is its own scope, and its members are not its outer type's — +`term → { weight, occurrences, carriers }` where `carriers` is the number of +distinct entities carrying the word. Per scope: `entities` (each one unnamed, +conventional, or counted in `roles` by its role), `skipped` by reason, +`distinct` and `distinctDomain` (the cognitive-load proxy), total `weight` and +`occurrences`. Per term: `modules` and `types` (how many scopes speak it) and +`exclusive` (the one module scope whose table has it, when exactly one does) — +the overlap and the drift inputs. A class is decided per language (Go +reserves `type`, Java does not), so a word is interned once per class it +takes; an entity's language is its model's, read from the union, never from +its id. A kept entity no module contains counts at the root alone and is +listed (`unplaced`); a language without a lexicon is split by the universal +rules alone and listed (`withoutLexicon`). Every scope's parent is the +nearest scope up the containment chain the model writes, else the root. + +**The artifact** `vocabulary.json`, kind `codegraph.vocabulary/1` +(`artifact.ts`): a header (generator, view descriptor, `corpus`, `langs`, +`weights`, `classes`, `technical: {words, sha256}` — the shared list's size +and a sha256 over its sorted words, one per line), `terms[]` interned and +sorted by text in code-unit order then class, `scopes[]` (root first, then +modules then types in id order: `level`, `id`, `name`, `kind`, `parent` scope +index, the counts above, `terms: [termIndex, weight, occurrences, +carriers][]` by weight descending then term) and `diagnostics` (`unplaced`, +`withoutLexicon`). Every scope id is a kept entity of its level, every index +is in range, and the counts add up — the closure property, tested on the +fixtures and on generated corpora, with conservation (every kept entity in one +module scope or unplaced) and order-independence (the same bytes whatever the +order entities arrive in). Compact by default; `pretty` writes one term and one +scope per line, the snapshots' form. Size is measured on BroadleafCommerce +(3 312 types, 502 modules) before any cap is considered; none is planned. **The command.** `codegraph vocabulary [--level corpus|module|type] [--scope ID] [--top N] [--class domain|technical|keyword|short|all] @@ -5283,15 +5325,21 @@ never looks like a fact. `weight.ts`; the hand tables per idiom and the properties of §20.1; the unicode fixture through the splitter; the shared words out of the profiles. -- [ ] `aggregate.ts` on `containingType`/`containingModule`, `artifact.ts` - (`codegraph.vocabulary/1`), `drift.ts`. -- [ ] `codegraph vocabulary` (§20.1): the report, `--level`, `--scope`, - `--class`, `--weights`, `--technical`, `--drift`, `--out`, `--json`, - the view flags; usage errors for an unknown scope, role or class. -- [ ] `fixtures//expected/vocabulary.json` for the five fixtures, - byte-identical on every run; hand counts in each fixture's README - (`order` in the Java fixture: which names, which weights); the closure - and determinism properties over every fixture. +- [x] `qualified` in the lexicon (the kinds named by a qualified path and + their separators), its validation code, `profiles --lang` printing it; + the splitter's `qualifier` count and the `qualifier` weight. +- [x] `aggregate.ts` on `containingType`/`containingModule`, `artifact.ts` + (`codegraph.vocabulary/1`); the package now depends on the analyzer. +- [ ] `drift.ts` and `codegraph vocabulary` (§20.1): the report, `--level`, + `--scope`, `--class`, `--weights`, `--technical`, `--drift`, `--out`, + `--json`, the view flags; usage errors for an unknown scope, role or + class. +- [x] `fixtures//expected/vocabulary.json` for the five fixtures and + `fixtures/unicode`, byte-identical on every run + (`CODEGRAPH_UPDATE_SNAPSHOTS=1` rewrites them); hand counts in each + fixture's README (`order` in the Java fixture: which names, which + weights), pinned by a test; the closure and determinism properties over + every fixture. - [ ] The coverage table of §20.1 filled on the fixtures, Broadleaf, codegraph itself, Mastodon and Plausible; the Broadleaf top ten reviewed; the technical list corrected as data where it leaked. diff --git a/docs/cli.md b/docs/cli.md index ff288c7..e1d4fdc 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -316,7 +316,7 @@ facts. Prints the language profiles core ships — the per-kind required and optional trait sets an extractor must respect, the edge kinds, the lexicon the vocabulary reads (reserved words, conventional names, technical words, file -kinds), and the notes. `--lang L` prints one profile in full; `--json` prints +kinds, qualified kinds and their separators), and the notes. `--lang L` prints one profile in full; `--json` prints the data as `{kind: "codegraph.profiles/1", profiles[]}`. ## The Java extractor diff --git a/fixtures/csharp/README.md b/fixtures/csharp/README.md new file mode 100644 index 0000000..ac46823 --- /dev/null +++ b/fixtures/csharp/README.md @@ -0,0 +1,27 @@ +# `fixtures/csharp` — the reference corpus for the C# extractor + +What this corpus pins, file by file, and how to reproduce its snapshot is in +[`docs/csharp-extractor.md`](../../docs/csharp-extractor.md). + +## The vocabulary: `order` and `priceable`, counted by hand + +`expected/vocabulary.json` is the words of this model's names +(`@codegraph/vocabulary`, PLAN.md §20), rebuilt byte for byte by +`pnpm --filter @codegraph/vocabulary test` (`CODEGRAPH_UPDATE_SNAPSHOTS=1` +rewrites it). A word weighs the role of the entity whose name carries it — +module and type 8, attribute 4, operation 3, parameter and local 1 — except a +word of the name's qualifier, before the last separator of a qualified kind, +which weighs 1. One word, counted by hand from the model and pinned by the +vocabulary's tests: + +| name | kind | role | weight | +|---|---|---|---| +| `Acme.Order` | namespace | module | 8 | +| `AbstractOrder`, `Order`, `OrderService` | class | type | 3 × 8 | +| `order` × 6 | parameter | parameter | 6 × 1 | +| `order` | localVariable | local | 1 | +| `Acme.Order.Adapter`, `Acme.Order.Extensions`, `Acme.Order.Legacy` | namespace | qualifier | 3 × 1 | + +**`order`: weight 42, 14 occurrences, 14 carriers.** The interface prefix is a +marker, not a word: `IPriceable` (type, 8) and the parameter `priceable` (1) +make **`priceable`: weight 9, 2 occurrences**, and no word `i`. diff --git a/fixtures/csharp/expected/vocabulary.json b/fixtures/csharp/expected/vocabulary.json new file mode 100644 index 0000000..78a6cb4 --- /dev/null +++ b/fixtures/csharp/expected/vocabulary.json @@ -0,0 +1,159 @@ +{ + "kind": "codegraph.vocabulary/1", + "generatedBy": "@codegraph/vocabulary", + "view": {"name":"all","filters":[]}, + "corpus": {"name":"src","roots":["fixtures/csharp/src"]}, + "langs": ["csharp"], + "weights": {"module":8,"type":8,"operation":3,"attribute":4,"parameter":1,"local":1,"other":1,"qualifier":1}, + "classes": ["domain","technical","keyword","short"], + "technical": {"words":125,"sha256":"c16bd05be59a0c3281696359e4b4f7b3efe7860512b4ab37519f8c3a063c95f7"}, + "terms": [ + {"text":"abstract","class":"keyword","modules":1,"types":1,"exclusive":1}, + {"text":"acme","class":"domain","modules":4,"types":0}, + {"text":"adapter","class":"technical","modules":1,"types":1,"exclusive":2}, + {"text":"add","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"allocator","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"amount","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"amounts","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"anything","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"apply","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"archive","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"attribute","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"audited","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"basket","class":"domain","modules":1,"types":3,"exclusive":1}, + {"text":"batch","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"best","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"bill","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"both","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"builder","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"cents","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"chain","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"channel","class":"domain","modules":1,"types":3,"exclusive":1}, + {"text":"check","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"converter","class":"domain","modules":1,"types":1,"exclusive":2}, + {"text":"count","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"counts","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"cursor","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"describe","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"discount","class":"domain","modules":1,"types":4,"exclusive":1}, + {"text":"discountable","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"document","class":"domain","modules":1,"types":1,"exclusive":2}, + {"text":"doubled","class":"domain","modules":1,"types":1,"exclusive":3}, + {"text":"e","class":"short","modules":1,"types":1,"exclusive":1}, + {"text":"empty","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"ensure","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"exception","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"extensions","class":"technical","modules":1,"types":1,"exclusive":3}, + {"text":"factor","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"find","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"first","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"free","class":"domain","modules":2,"types":3}, + {"text":"get","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"guard","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"halved","class":"domain","modules":1,"types":1,"exclusive":3}, + {"text":"handler","class":"technical","modules":1,"types":2,"exclusive":1}, + {"text":"head","class":"domain","modules":1,"types":1,"exclusive":4}, + {"text":"index","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"invoice","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"is","class":"keyword","modules":1,"types":1,"exclusive":3}, + {"text":"item","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"items","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"join","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"largest","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"ledger","class":"domain","modules":2,"types":2}, + {"text":"left","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"legacy","class":"domain","modules":1,"types":0,"exclusive":4}, + {"text":"length","class":"domain","modules":1,"types":1,"exclusive":4}, + {"text":"line","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"lines","class":"domain","modules":1,"types":3,"exclusive":1}, + {"text":"list","class":"domain","modules":1,"types":1,"exclusive":4}, + {"text":"log","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"max","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"message","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"money","class":"domain","modules":2,"types":3}, + {"text":"naïve","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"next","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"notifications","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"notify","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"of","class":"technical","modules":1,"types":2,"exclusive":1}, + {"text":"on","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"order","class":"domain","modules":4,"types":5}, + {"text":"orders","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"part","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"parts","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"percent","class":"domain","modules":1,"types":3,"exclusive":1}, + {"text":"phone","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"plain","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"post","class":"domain","modules":1,"types":1,"exclusive":2}, + {"text":"predicate","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"price","class":"domain","modules":1,"types":4,"exclusive":1}, + {"text":"priceable","class":"domain","modules":2,"types":2}, + {"text":"priced","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"prices","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"quantity","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"reference","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"reporting","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"repository","class":"technical","modules":1,"types":2,"exclusive":1}, + {"text":"right","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"rounded","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"rows","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"résumé","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"save","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"second","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"serialize","class":"domain","modules":1,"types":1,"exclusive":2}, + {"text":"service","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"shipment","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"shipped","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"size","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"stock","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"store","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"subscribe","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"tag","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"tail","class":"domain","modules":1,"types":1,"exclusive":4}, + {"text":"times","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"to","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"today","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"total","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"trail","class":"domain","modules":2,"types":3}, + {"text":"typed","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"value","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"values","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"verbose","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"web","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"zero","class":"domain","modules":2,"types":3} + ], + "scopes": [ + {"level":"corpus","name":"src","entities":210,"roles":{"module":4,"type":26,"operation":45,"attribute":38,"parameter":54,"local":9,"other":0},"skipped":{"unnamed":32,"conventional":2,"numeric":0},"distinct":113,"distinctDomain":95,"weight":707,"occurrences":202,"terms":[[69,42,14,14],[12,21,4,4],[20,18,5,5],[62,18,4,4],[27,17,4,4],[78,17,6,6],[2,16,2,2],[35,16,2,2],[85,16,2,2],[106,16,3,3],[39,15,4,4],[52,13,3,3],[105,12,3,3],[25,11,2,2],[57,11,3,3],[83,11,5,5],[95,11,3,3],[56,10,3,3],[112,10,3,3],[43,9,2,2],[44,9,3,3],[79,9,2,2],[0,8,1,1],[10,8,1,1],[11,8,1,1],[13,8,1,1],[22,8,1,1],[23,8,2,2],[28,8,1,1],[32,8,1,1],[34,8,1,1],[41,8,1,1],[45,8,2,2],[49,8,2,2],[54,8,1,1],[58,8,1,1],[65,8,1,1],[84,8,1,1],[89,8,1,1],[93,8,1,1],[94,8,1,1],[97,8,1,1],[47,7,2,2],[60,7,2,2],[5,6,3,3],[9,6,2,2],[26,6,2,2],[67,6,2,2],[18,5,2,2],[24,5,2,2],[81,5,2,2],[100,5,2,2],[101,5,2,2],[1,4,4,4],[38,4,2,2],[63,4,1,1],[68,4,1,1],[74,4,1,1],[75,4,1,1],[96,4,2,2],[98,4,1,1],[107,4,1,1],[110,4,1,1],[111,4,1,1],[3,3,1,1],[4,3,1,1],[8,3,1,1],[15,3,1,1],[16,3,1,1],[19,3,1,1],[21,3,1,1],[30,3,1,1],[33,3,1,1],[37,3,1,1],[40,3,1,1],[42,3,1,1],[50,3,1,1],[51,3,1,1],[55,3,1,1],[59,3,1,1],[64,3,1,1],[66,3,1,1],[73,3,3,3],[76,3,1,1],[80,3,1,1],[87,3,1,1],[90,3,1,1],[92,3,1,1],[99,3,1,1],[102,3,1,1],[103,3,1,1],[104,3,1,1],[29,2,2,2],[48,2,2,2],[61,2,2,2],[70,2,2,2],[82,2,2,2],[108,2,2,2],[109,2,2,2],[6,1,1,1],[7,1,1,1],[14,1,1,1],[17,1,1,1],[31,1,1,1],[36,1,1,1],[46,1,1,1],[53,1,1,1],[71,1,1,1],[72,1,1,1],[77,1,1,1],[86,1,1,1],[88,1,1,1],[91,1,1,1]]}, + {"level":"module","id":"csharp:Acme.Order","name":"Acme.Order","kind":"namespace","parent":0,"entities":183,"roles":{"module":1,"type":22,"operation":39,"attribute":33,"parameter":48,"local":9,"other":0},"skipped":{"unnamed":29,"conventional":2,"numeric":0},"distinct":99,"distinctDomain":84,"weight":570,"occurrences":167,"terms":[[69,39,11,11],[12,21,4,4],[20,18,5,5],[27,17,4,4],[78,17,6,6],[85,16,2,2],[39,12,3,3],[105,12,3,3],[25,11,2,2],[57,11,3,3],[83,11,5,5],[95,11,3,3],[56,10,3,3],[43,9,2,2],[62,9,2,2],[0,8,1,1],[10,8,1,1],[11,8,1,1],[13,8,1,1],[23,8,2,2],[28,8,1,1],[32,8,1,1],[34,8,1,1],[41,8,1,1],[45,8,2,2],[49,8,2,2],[65,8,1,1],[79,8,1,1],[84,8,1,1],[89,8,1,1],[93,8,1,1],[94,8,1,1],[97,8,1,1],[60,7,2,2],[5,6,3,3],[9,6,2,2],[26,6,2,2],[67,6,2,2],[112,6,2,2],[18,5,2,2],[24,5,2,2],[52,5,2,2],[81,5,2,2],[100,5,2,2],[38,4,2,2],[63,4,1,1],[68,4,1,1],[74,4,1,1],[75,4,1,1],[96,4,2,2],[98,4,1,1],[106,4,1,1],[107,4,1,1],[110,4,1,1],[111,4,1,1],[3,3,1,1],[4,3,1,1],[8,3,1,1],[15,3,1,1],[16,3,1,1],[19,3,1,1],[21,3,1,1],[33,3,1,1],[37,3,1,1],[40,3,1,1],[50,3,1,1],[51,3,1,1],[59,3,1,1],[64,3,1,1],[66,3,1,1],[73,3,3,3],[80,3,1,1],[87,3,1,1],[90,3,1,1],[99,3,1,1],[102,3,1,1],[103,3,1,1],[104,3,1,1],[48,2,2,2],[61,2,2,2],[70,2,2,2],[82,2,2,2],[108,2,2,2],[109,2,2,2],[1,1,1,1],[6,1,1,1],[7,1,1,1],[14,1,1,1],[17,1,1,1],[31,1,1,1],[36,1,1,1],[46,1,1,1],[53,1,1,1],[71,1,1,1],[72,1,1,1],[77,1,1,1],[86,1,1,1],[88,1,1,1],[91,1,1,1]]}, + {"level":"module","id":"csharp:Acme.Order.Adapter","name":"Acme.Order.Adapter","kind":"namespace","parent":0,"entities":10,"roles":{"module":1,"type":2,"operation":2,"attribute":1,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":2,"conventional":0,"numeric":0},"distinct":9,"distinctDomain":8,"weight":54,"occurrences":12,"terms":[[2,16,2,2],[106,12,2,2],[22,8,1,1],[52,8,1,1],[76,3,1,1],[92,3,1,1],[29,2,2,2],[1,1,1,1],[69,1,1,1]]}, + {"level":"module","id":"csharp:Acme.Order.Extensions","name":"Acme.Order.Extensions","kind":"namespace","parent":0,"entities":8,"roles":{"module":1,"type":1,"operation":3,"attribute":1,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":10,"distinctDomain":8,"weight":48,"occurrences":13,"terms":[[35,16,2,2],[62,9,2,2],[47,7,2,2],[112,4,1,1],[30,3,1,1],[39,3,1,1],[42,3,1,1],[1,1,1,1],[69,1,1,1],[79,1,1,1]]}, + {"level":"module","id":"csharp:Acme.Order.Legacy","name":"Acme.Order.Legacy","kind":"namespace","parent":1,"entities":9,"roles":{"module":1,"type":1,"operation":1,"attribute":3,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":7,"weight":35,"occurrences":10,"terms":[[44,9,3,3],[54,8,1,1],[58,8,1,1],[101,5,2,2],[55,3,1,1],[1,1,1,1],[69,1,1,1]]}, + {"level":"type","id":"csharp:Acme.Order.Adapter/LedgerAdapter","name":"LedgerAdapter","kind":"class","parent":2,"entities":7,"roles":{"module":0,"type":1,"operation":2,"attribute":1,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":5,"weight":28,"occurrences":7,"terms":[[2,8,1,1],[52,8,1,1],[106,4,1,1],[76,3,1,1],[92,3,1,1],[29,2,2,2]]}, + {"level":"type","id":"csharp:Acme.Order.Adapter/TrailConverter","name":"TrailConverter","kind":"class","parent":2,"entities":2,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":16,"occurrences":2,"terms":[[22,8,1,1],[106,8,1,1]]}, + {"level":"type","id":"csharp:Acme.Order.Extensions/MoneyExtensions","name":"MoneyExtensions","kind":"class","parent":3,"entities":7,"roles":{"module":0,"type":1,"operation":3,"attribute":1,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":8,"distinctDomain":6,"weight":38,"occurrences":10,"terms":[[62,9,2,2],[35,8,1,1],[47,7,2,2],[112,4,1,1],[30,3,1,1],[39,3,1,1],[42,3,1,1],[79,1,1,1]]}, + {"level":"type","id":"csharp:Acme.Order.Legacy/List","name":"List","kind":"class","parent":4,"entities":8,"roles":{"module":0,"type":1,"operation":1,"attribute":3,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":4,"weight":25,"occurrences":7,"terms":[[44,9,3,3],[58,8,1,1],[101,5,2,2],[55,3,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/AbstractOrder","name":"AbstractOrder","kind":"class","parent":1,"entities":9,"roles":{"module":0,"type":1,"operation":2,"attribute":3,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":6,"weight":36,"occurrences":9,"terms":[[83,9,3,3],[0,8,1,1],[69,8,1,1],[105,4,1,1],[27,3,1,1],[78,3,1,1],[73,1,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/AuditedAttribute","name":"AuditedAttribute","kind":"class","parent":1,"entities":5,"roles":{"module":0,"type":1,"operation":0,"attribute":2,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":3,"weight":25,"occurrences":5,"terms":[[10,8,1,1],[11,8,1,1],[100,5,2,2],[110,4,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/Basket","name":"Basket","kind":"class","parent":1,"entities":13,"roles":{"module":0,"type":1,"operation":2,"attribute":5,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":2,"conventional":1,"numeric":0},"distinct":9,"distinctDomain":7,"weight":39,"occurrences":11,"terms":[[12,8,1,1],[105,8,2,2],[23,4,1,1],[45,4,1,1],[57,4,1,1],[3,3,1,1],[25,3,1,1],[40,3,1,1],[56,2,2,2]]}, + {"level":"type","id":"csharp:Acme.Order/Basket.Cursor","name":"Cursor","kind":"class","parent":11,"entities":6,"roles":{"module":0,"type":1,"operation":1,"attribute":2,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":4,"weight":20,"occurrences":5,"terms":[[25,8,1,1],[12,5,2,2],[45,4,1,1],[64,3,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/Basket.Line","name":"Line","kind":"class","parent":11,"entities":4,"roles":{"module":0,"type":1,"operation":0,"attribute":1,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":13,"occurrences":3,"terms":[[56,8,1,1],[5,5,2,2]]}, + {"level":"type","id":"csharp:Acme.Order/Basket.Line.Discount","name":"Discount","kind":"class","parent":13,"entities":4,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":3,"weight":15,"occurrences":4,"terms":[[27,8,1,1],[8,3,1,1],[103,3,1,1],[5,1,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/Batch","name":"Batch","kind":"class","parent":1,"entities":13,"roles":{"module":0,"type":1,"operation":4,"attribute":2,"parameter":4,"local":0,"other":0},"skipped":{"unnamed":2,"conventional":0,"numeric":0},"distinct":8,"distinctDomain":8,"weight":32,"occurrences":11,"terms":[[13,8,1,1],[24,5,2,2],[81,5,2,2],[96,4,2,2],[4,3,1,1],[26,3,1,1],[80,3,1,1],[7,1,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/Channel","name":"Channel","kind":"enum","parent":1,"entities":5,"roles":{"module":0,"type":1,"operation":0,"attribute":3,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":4,"weight":20,"occurrences":4,"terms":[[20,8,1,1],[74,4,1,1],[98,4,1,1],[111,4,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/EmptyBasketException","name":"EmptyBasketException","kind":"class","parent":1,"entities":3,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":3,"weight":25,"occurrences":4,"terms":[[12,8,1,1],[32,8,1,1],[34,8,1,1],[61,1,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/IDiscountable","name":"IDiscountable","kind":"interface","parent":1,"entities":3,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":3,"weight":12,"occurrences":3,"terms":[[28,8,1,1],[27,3,1,1],[73,1,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/IPriceable","name":"IPriceable","kind":"interface","parent":1,"entities":2,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":11,"occurrences":2,"terms":[[79,8,1,1],[78,3,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/Money","name":"Money","kind":"record","parent":1,"entities":12,"roles":{"module":0,"type":1,"operation":3,"attribute":1,"parameter":4,"local":0,"other":0},"skipped":{"unnamed":2,"conventional":1,"numeric":0},"distinct":8,"distinctDomain":8,"weight":25,"occurrences":9,"terms":[[62,8,1,1],[18,5,2,2],[78,3,1,1],[102,3,1,1],[112,3,1,1],[36,1,1,1],[53,1,1,1],[86,1,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/Notifications","name":"Notifications","kind":"class","parent":1,"entities":25,"roles":{"module":0,"type":1,"operation":7,"attribute":2,"parameter":8,"local":1,"other":0},"skipped":{"unnamed":6,"conventional":0,"numeric":0},"distinct":16,"distinctDomain":13,"weight":56,"occurrences":22,"terms":[[95,11,3,3],[65,8,1,1],[39,4,2,2],[68,4,1,1],[69,4,4,4],[16,3,1,1],[19,3,1,1],[59,3,1,1],[66,3,1,1],[67,3,1,1],[99,3,1,1],[112,3,1,1],[38,1,1,1],[43,1,1,1],[61,1,1,1],[91,1,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/Notifications.Free","name":"Free","kind":"class","parent":21,"entities":5,"roles":{"module":0,"type":1,"operation":1,"attribute":1,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":16,"occurrences":4,"terms":[[39,8,1,1],[78,8,3,3]]}, + {"level":"type","id":"csharp:Acme.Order/Order","name":"Order","kind":"class","parent":1,"entities":12,"roles":{"module":0,"type":1,"operation":1,"attribute":4,"parameter":4,"local":0,"other":0},"skipped":{"unnamed":2,"conventional":0,"numeric":0},"distinct":8,"distinctDomain":8,"weight":35,"occurrences":11,"terms":[[20,9,3,3],[69,8,1,1],[57,4,1,1],[60,4,1,1],[106,4,1,1],[27,3,1,1],[83,2,2,2],[73,1,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/OrderService","name":"OrderService","kind":"class","parent":1,"entities":14,"roles":{"module":0,"type":1,"operation":3,"attribute":3,"parameter":4,"local":2,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":9,"distinctDomain":8,"weight":43,"occurrences":14,"terms":[[69,10,3,3],[93,8,1,1],[9,6,2,2],[52,5,2,2],[75,4,1,1],[107,4,1,1],[15,3,1,1],[70,2,2,2],[46,1,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/Reporting","name":"Reporting","kind":"class","parent":1,"entities":23,"roles":{"module":0,"type":1,"operation":8,"attribute":0,"parameter":8,"local":4,"other":0},"skipped":{"unnamed":2,"conventional":0,"numeric":0},"distinct":20,"distinctDomain":18,"weight":47,"occurrences":22,"terms":[[84,8,1,1],[26,3,1,1],[38,3,1,1],[50,3,1,1],[51,3,1,1],[57,3,1,1],[60,3,1,1],[67,3,1,1],[87,3,1,1],[104,3,1,1],[108,2,2,2],[109,2,2,2],[6,1,1,1],[14,1,1,1],[17,1,1,1],[20,1,1,1],[62,1,1,1],[71,1,1,1],[72,1,1,1],[88,1,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/Repository","name":"Repository","kind":"class","parent":1,"entities":4,"roles":{"module":0,"type":1,"operation":0,"attribute":2,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":2,"weight":16,"occurrences":3,"terms":[[85,8,1,1],[23,4,1,1],[49,4,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/Repository`1","name":"Repository","kind":"class","parent":1,"entities":8,"roles":{"module":0,"type":1,"operation":2,"attribute":1,"parameter":2,"local":1,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":3,"weight":21,"occurrences":7,"terms":[[85,8,1,1],[49,4,1,1],[37,3,1,1],[90,3,1,1],[48,2,2,2],[77,1,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/Résumé","name":"Résumé","kind":"class","parent":1,"entities":3,"roles":{"module":0,"type":1,"operation":0,"attribute":1,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":12,"occurrences":2,"terms":[[89,8,1,1],[63,4,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/ShipmentHandler","name":"ShipmentHandler","kind":"delegate","parent":1,"entities":2,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":2,"weight":17,"occurrences":3,"terms":[[43,8,1,1],[94,8,1,1],[69,1,1,1]]}, + {"level":"type","id":"csharp:Acme.Order/StockGuard","name":"StockGuard","kind":"class","parent":1,"entities":7,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":2,"local":1,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":5,"weight":25,"occurrences":7,"terms":[[41,8,1,1],[97,8,1,1],[21,3,1,1],[33,3,1,1],[82,2,2,2],[31,1,1,1]]} + ], + "diagnostics": {"unplaced":[],"withoutLexicon":[]} +} diff --git a/fixtures/elixir/README.md b/fixtures/elixir/README.md index acf99c2..b6472c8 100644 --- a/fixtures/elixir/README.md +++ b/fixtures/elixir/README.md @@ -57,3 +57,28 @@ must skip it, count it, and still write its file record. | `lib/acme_order/two_modules.ex` | Two `defmodule`s in one file, both children of it; `@channels` is an attribute whose value is an array of unevaluated atoms; `&Atom.to_string/1` a reference to `/Atom`; `alias AcmeOrder.Channel` inside the second names this file — a self-edge, dropped. | | | `lib/acme_order/promo#2024.ex` | A `#` in a file name → `promo%232024.ex` in the key, the written path in `name`; `%Money{money \| amount: …}` from another module: a READ of `Money.amount` in the head and a WRITE in the update. | `#` is a reserved separator of the rendered id; cross-module field accesses are what the navigator lists. | | `test/acme_order/order_test.exs` | `use ExUnit.Case` → `/ExUnit.Case`; the `test` blocks are module-level code, so their calls (`Order.create/2`, `Order.total/1`, `Money.zero/0`) are invocations FROM the test module; `%Line{…}` is a reference to `Line` whose fields are unbound (Ecto-injected). | Tests are corpus; a `test` macro's body is compile-time code of the module. | + +## The vocabulary: `pricing`, counted by hand + +`expected/vocabulary.json` is the words of this model's names +(`@codegraph/vocabulary`, PLAN.md §20), rebuilt byte for byte by +`pnpm --filter @codegraph/vocabulary test` (`CODEGRAPH_UPDATE_SNAPSHOTS=1` +rewrites it). A word weighs the role of the entity whose name carries it — +module and type 8, attribute 4, operation 3, parameter and local 1 — except a +word of the name's qualifier, before the last separator of a qualified kind, +which weighs 1. One word, counted by hand from the model and pinned by the +vocabulary's tests: + +| name | kind | role | weight | +|---|---|---|---| +| `lib/acme_order/pricing.ex` | file | module | 8 | +| `AcmeOrder.Pricing` | module | type | 8 | +| `pricing` (`AcmeOrder.Order`'s struct field) | field | attribute | 4 | +| `pricing_for` | function | operation | 3 | +| `pricing` | parameter | parameter | 1 | +| `lib/acme_order/pricing/premium.ex`, `…/standard.ex` | file | qualifier | 2 × 1 | +| `AcmeOrder.Pricing.Premium`, `AcmeOrder.Pricing.Standard` | module | qualifier | 2 × 1 | + +**`pricing`: weight 28, 9 occurrences, 9 carriers.** A module's dots and a +file's slashes both qualify: `AcmeOrder.Pricing.Premium` speaks `premium` at 8 +and `acme`, `order`, `pricing` at 1. diff --git a/fixtures/elixir/expected/vocabulary.json b/fixtures/elixir/expected/vocabulary.json new file mode 100644 index 0000000..1ac5a64 --- /dev/null +++ b/fixtures/elixir/expected/vocabulary.json @@ -0,0 +1,164 @@ +{ + "kind": "codegraph.vocabulary/1", + "generatedBy": "@codegraph/vocabulary", + "view": {"name":"all","filters":[]}, + "corpus": {"name":"src","roots":["fixtures/elixir/src"]}, + "langs": ["ex"], + "weights": {"module":8,"type":8,"operation":3,"attribute":4,"parameter":1,"local":1,"other":1,"qualifier":1}, + "classes": ["domain","technical","keyword","short"], + "technical": {"words":125,"sha256":"c16bd05be59a0c3281696359e4b4f7b3efe7860512b4ab37519f8c3a063c95f7"}, + "terms": [ + {"text":"a","class":"short","modules":1,"types":1,"exclusive":8}, + {"text":"acme","class":"domain","modules":21,"types":27}, + {"text":"add","class":"technical","modules":2,"types":2}, + {"text":"all","class":"domain","modules":1,"types":1,"exclusive":20}, + {"text":"amount","class":"domain","modules":1,"types":1,"exclusive":8}, + {"text":"any","class":"domain","modules":1,"types":1,"exclusive":11}, + {"text":"application","class":"domain","modules":2,"types":2}, + {"text":"apply","class":"domain","modules":1,"types":1,"exclusive":15}, + {"text":"args","class":"technical","modules":1,"types":1,"exclusive":3}, + {"text":"assert","class":"domain","modules":1,"types":1,"exclusive":7}, + {"text":"atom","class":"domain","modules":1,"types":0,"exclusive":5}, + {"text":"attrs","class":"technical","modules":1,"types":1,"exclusive":18}, + {"text":"available","class":"domain","modules":1,"types":1,"exclusive":19}, + {"text":"b","class":"short","modules":1,"types":1,"exclusive":8}, + {"text":"broken","class":"domain","modules":1,"types":0,"exclusive":6}, + {"text":"call","class":"domain","modules":1,"types":1,"exclusive":3}, + {"text":"changeset","class":"technical","modules":1,"types":1,"exclusive":18}, + {"text":"channel","class":"domain","modules":4,"types":5}, + {"text":"channels","class":"domain","modules":1,"types":1,"exclusive":20}, + {"text":"chars","class":"domain","modules":1,"types":1,"exclusive":8}, + {"text":"config","class":"technical","modules":1,"types":0,"exclusive":1}, + {"text":"create","class":"technical","modules":1,"types":1,"exclusive":10}, + {"text":"currency","class":"domain","modules":2,"types":5}, + {"text":"deliver","class":"domain","modules":1,"types":2,"exclusive":9}, + {"text":"deps","class":"domain","modules":1,"types":1,"exclusive":21}, + {"text":"describe","class":"domain","modules":2,"types":2}, + {"text":"discount","class":"domain","modules":2,"types":2}, + {"text":"dynamic","class":"domain","modules":1,"types":1,"exclusive":3}, + {"text":"email","class":"domain","modules":1,"types":1,"exclusive":9}, + {"text":"errors","class":"technical","modules":1,"types":2,"exclusive":4}, + {"text":"fail","class":"domain","modules":1,"types":1,"exclusive":4}, + {"text":"for","class":"technical","modules":2,"types":2}, + {"text":"fun","class":"domain","modules":2,"types":2}, + {"text":"gateway","class":"domain","modules":1,"types":1,"exclusive":9}, + {"text":"hello","class":"domain","modules":1,"types":1,"exclusive":5}, + {"text":"helper","class":"technical","modules":1,"types":0,"exclusive":23}, + {"text":"is","class":"technical","modules":1,"types":1,"exclusive":7}, + {"text":"item","class":"domain","modules":2,"types":2}, + {"text":"legacy","class":"domain","modules":2,"types":1}, + {"text":"length","class":"domain","modules":1,"types":1,"exclusive":9}, + {"text":"lib","class":"technical","modules":19,"types":0}, + {"text":"line","class":"domain","modules":4,"types":4}, + {"text":"lines","class":"domain","modules":2,"types":2}, + {"text":"list","class":"domain","modules":1,"types":1,"exclusive":9}, + {"text":"macros","class":"domain","modules":1,"types":1,"exclusive":7}, + {"text":"many","class":"domain","modules":1,"types":1,"exclusive":4}, + {"text":"max","class":"domain","modules":2,"types":2}, + {"text":"message","class":"domain","modules":1,"types":1,"exclusive":4}, + {"text":"mix","class":"technical","modules":1,"types":1,"exclusive":21}, + {"text":"mod","class":"domain","modules":2,"types":2}, + {"text":"module","class":"domain","modules":1,"types":0,"exclusive":5}, + {"text":"modules","class":"domain","modules":1,"types":0,"exclusive":20}, + {"text":"money","class":"domain","modules":4,"types":5}, + {"text":"name","class":"domain","modules":1,"types":1,"exclusive":3}, + {"text":"names","class":"domain","modules":1,"types":1,"exclusive":20}, + {"text":"new","class":"technical","modules":1,"types":1,"exclusive":8}, + {"text":"notifier","class":"domain","modules":1,"types":3,"exclusive":9}, + {"text":"notify","class":"domain","modules":1,"types":1,"exclusive":9}, + {"text":"of","class":"technical","modules":1,"types":1,"exclusive":17}, + {"text":"opts","class":"technical","modules":4,"types":4}, + {"text":"order","class":"domain","modules":21,"types":27}, + {"text":"peek","class":"domain","modules":1,"types":1,"exclusive":19}, + {"text":"pid","class":"technical","modules":2,"types":2}, + {"text":"premium","class":"domain","modules":1,"types":1,"exclusive":12}, + {"text":"price","class":"domain","modules":6,"types":9}, + {"text":"priceable","class":"domain","modules":1,"types":4,"exclusive":11}, + {"text":"pricing","class":"domain","modules":5,"types":5}, + {"text":"project","class":"domain","modules":1,"types":1,"exclusive":21}, + {"text":"promo","class":"domain","modules":1,"types":0,"exclusive":15}, + {"text":"promo2024","class":"domain","modules":1,"types":1,"exclusive":15}, + {"text":"qty","class":"domain","modules":1,"types":1,"exclusive":19}, + {"text":"reason","class":"domain","modules":1,"types":1,"exclusive":4}, + {"text":"reference","class":"domain","modules":1,"types":1,"exclusive":10}, + {"text":"release","class":"domain","modules":2,"types":2}, + {"text":"repo","class":"technical","modules":1,"types":1,"exclusive":16}, + {"text":"reporting","class":"domain","modules":1,"types":1,"exclusive":17}, + {"text":"reserve","class":"domain","modules":1,"types":1,"exclusive":19}, + {"text":"rethrow","class":"domain","modules":1,"types":1,"exclusive":4}, + {"text":"schema","class":"technical","modules":1,"types":0,"exclusive":18}, + {"text":"send","class":"domain","modules":1,"types":1,"exclusive":3}, + {"text":"sku","class":"domain","modules":3,"types":3}, + {"text":"sms","class":"domain","modules":1,"types":1,"exclusive":9}, + {"text":"snapshot","class":"domain","modules":1,"types":1,"exclusive":17}, + {"text":"standard","class":"domain","modules":1,"types":1,"exclusive":13}, + {"text":"start","class":"domain","modules":1,"types":1,"exclusive":2}, + {"text":"state","class":"domain","modules":1,"types":1,"exclusive":19}, + {"text":"stock","class":"domain","modules":2,"types":2}, + {"text":"string","class":"domain","modules":1,"types":1,"exclusive":8}, + {"text":"table","class":"domain","modules":1,"types":1,"exclusive":19}, + {"text":"test","class":"technical","modules":2,"types":1}, + {"text":"text","class":"domain","modules":1,"types":1,"exclusive":9}, + {"text":"too","class":"domain","modules":1,"types":1,"exclusive":4}, + {"text":"total","class":"domain","modules":1,"types":1,"exclusive":10}, + {"text":"two","class":"domain","modules":1,"types":0,"exclusive":20}, + {"text":"value","class":"domain","modules":1,"types":1,"exclusive":7}, + {"text":"with","class":"technical","modules":1,"types":1,"exclusive":3}, + {"text":"zero","class":"domain","modules":1,"types":1,"exclusive":8} + ], + "scopes": [ + {"level":"corpus","name":"src","entities":181,"roles":{"module":23,"type":28,"operation":50,"attribute":11,"parameter":49,"local":0,"other":0},"skipped":{"unnamed":14,"conventional":6,"numeric":1},"distinct":97,"distinctDomain":75,"weight":887,"occurrences":318,"terms":[[60,99,62,55],[1,49,49,47],[52,35,7,7],[66,28,9,9],[64,27,9,9],[89,26,5,3],[41,23,7,7],[17,20,6,6],[6,19,3,3],[40,19,19,19],[65,19,5,5],[86,19,3,3],[56,18,4,4],[22,17,6,6],[29,17,3,3],[27,16,2,2],[42,16,3,3],[44,16,2,2],[48,16,2,2],[63,16,2,2],[74,16,2,2],[75,16,2,2],[83,16,2,2],[54,11,2,2],[67,11,2,2],[20,9,2,1],[49,9,2,2],[5,8,1,1],[10,8,1,1],[14,8,1,1],[28,8,1,1],[33,8,1,1],[35,8,1,1],[45,8,1,1],[46,8,2,2],[50,8,1,1],[51,8,1,1],[68,8,1,1],[69,8,1,1],[80,8,6,6],[91,8,1,1],[93,8,1,1],[47,7,2,2],[2,6,2,2],[23,6,2,2],[25,6,2,2],[26,6,2,2],[31,6,2,2],[73,6,2,2],[4,5,2,2],[72,5,2,2],[18,4,1,1],[59,4,4,4],[88,4,1,1],[3,3,1,1],[7,3,1,1],[9,3,1,1],[12,3,1,1],[15,3,1,1],[16,3,1,1],[21,3,1,1],[24,3,1,1],[30,3,1,1],[34,3,1,1],[36,3,1,1],[37,3,3,3],[38,3,3,3],[39,3,1,1],[55,3,1,1],[57,3,1,1],[58,3,1,1],[61,3,1,1],[76,3,1,1],[77,3,1,1],[79,3,1,1],[82,3,1,1],[84,3,1,1],[92,3,1,1],[95,3,1,1],[96,3,1,1],[32,2,2,2],[62,2,2,2],[85,2,2,2],[94,2,2,2],[0,1,1,1],[8,1,1,1],[11,1,1,1],[13,1,1,1],[19,1,1,1],[43,1,1,1],[53,1,1,1],[70,1,1,1],[71,1,1,1],[78,1,1,1],[81,1,1,1],[87,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ex:config%2Fconfig.exs","name":"config/config.exs","kind":"file","parent":0,"entities":1,"roles":{"module":1,"type":0,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":1,"distinctDomain":0,"weight":9,"occurrences":2,"terms":[[20,9,2,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Fapplication.ex","name":"lib/acme_order/application.ex","kind":"file","parent":0,"entities":5,"roles":{"module":1,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":2,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":4,"weight":24,"occurrences":8,"terms":[[6,16,2,2],[84,3,1,1],[1,2,2,2],[60,2,2,2],[40,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Fdynamic.ex","name":"lib/acme_order/dynamic.ex","kind":"file","parent":0,"entities":14,"roles":{"module":1,"type":1,"operation":4,"attribute":0,"parameter":8,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":18,"distinctDomain":13,"weight":50,"occurrences":22,"terms":[[27,16,2,2],[66,4,2,2],[15,3,1,1],[31,3,1,1],[64,3,1,1],[73,3,1,1],[79,3,1,1],[95,3,1,1],[1,2,2,2],[60,2,2,2],[8,1,1,1],[32,1,1,1],[40,1,1,1],[41,1,1,1],[49,1,1,1],[53,1,1,1],[62,1,1,1],[80,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Ferrors.ex","name":"lib/acme_order/errors.ex","kind":"file","parent":0,"entities":11,"roles":{"module":1,"type":2,"operation":3,"attribute":2,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":13,"distinctDomain":11,"weight":67,"occurrences":20,"terms":[[29,17,3,3],[42,8,1,1],[45,8,1,1],[91,8,1,1],[47,7,2,2],[46,4,1,1],[1,3,3,3],[30,3,1,1],[60,3,3,3],[77,3,1,1],[32,1,1,1],[40,1,1,1],[71,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Flegacy%2Fatom_module.ex","name":"lib/acme_order/legacy/atom_module.ex","kind":"file","parent":0,"entities":3,"roles":{"module":1,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":8,"distinctDomain":7,"weight":32,"occurrences":9,"terms":[[10,8,1,1],[49,8,1,1],[50,8,1,1],[34,3,1,1],[38,2,2,2],[1,1,1,1],[40,1,1,1],[60,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Flegacy%2Fbroken.ex","name":"lib/acme_order/legacy/broken.ex","kind":"file","parent":0,"entities":1,"roles":{"module":1,"type":0,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":4,"weight":12,"occurrences":5,"terms":[[14,8,1,1],[1,1,1,1],[38,1,1,1],[40,1,1,1],[60,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Fmacros.ex","name":"lib/acme_order/macros.ex","kind":"file","parent":0,"entities":8,"roles":{"module":1,"type":1,"operation":2,"attribute":0,"parameter":3,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":1,"numeric":0},"distinct":9,"distinctDomain":6,"weight":36,"occurrences":14,"terms":[[44,16,2,2],[17,6,2,2],[9,3,1,1],[36,3,1,1],[1,2,2,2],[60,2,2,2],[94,2,2,2],[40,1,1,1],[59,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Fmoney.ex","name":"lib/acme_order/money.ex","kind":"file","parent":0,"entities":14,"roles":{"module":1,"type":2,"operation":3,"attribute":2,"parameter":4,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":1,"numeric":0},"distinct":13,"distinctDomain":8,"weight":54,"occurrences":21,"terms":[[52,24,3,3],[4,5,2,2],[22,5,2,2],[1,3,3,3],[2,3,1,1],[55,3,1,1],[60,3,3,3],[96,3,1,1],[0,1,1,1],[13,1,1,1],[19,1,1,1],[40,1,1,1],[87,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Fnotifier.ex","name":"lib/acme_order/notifier.ex","kind":"file","parent":0,"entities":12,"roles":{"module":1,"type":3,"operation":4,"attribute":0,"parameter":4,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":12,"distinctDomain":11,"weight":60,"occurrences":24,"terms":[[56,18,4,4],[28,8,1,1],[33,8,1,1],[23,6,2,2],[60,6,6,6],[1,4,4,4],[39,3,1,1],[57,3,1,1],[40,1,1,1],[43,1,1,1],[81,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Forder.ex","name":"lib/acme_order/order.ex","kind":"file","parent":0,"entities":17,"roles":{"module":1,"type":1,"operation":4,"attribute":5,"parameter":6,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":14,"distinctDomain":10,"weight":66,"occurrences":24,"terms":[[60,21,7,5],[42,8,2,2],[72,5,2,2],[17,4,1,1],[41,4,2,2],[46,4,1,1],[66,4,1,1],[2,3,1,1],[21,3,1,1],[25,3,1,1],[92,3,1,1],[1,2,2,2],[40,1,1,1],[59,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Fpriceable.ex","name":"lib/acme_order/priceable.ex","kind":"file","parent":0,"entities":21,"roles":{"module":1,"type":4,"operation":8,"attribute":0,"parameter":4,"local":0,"other":0},"skipped":{"unnamed":4,"conventional":0,"numeric":0},"distinct":9,"distinctDomain":8,"weight":86,"occurrences":35,"terms":[[65,19,5,5],[60,16,9,6],[22,12,4,4],[64,12,4,4],[52,9,2,2],[5,8,1,1],[1,7,7,5],[37,2,2,2],[40,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Fpricing%2Fpremium.ex","name":"lib/acme_order/pricing/premium.ex","kind":"file","parent":0,"entities":5,"roles":{"module":1,"type":1,"operation":1,"attribute":0,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":8,"distinctDomain":7,"weight":28,"occurrences":12,"terms":[[63,16,2,2],[64,3,1,1],[1,2,2,2],[60,2,2,2],[66,2,2,2],[17,1,1,1],[40,1,1,1],[41,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Fpricing%2Fstandard.ex","name":"lib/acme_order/pricing/standard.ex","kind":"file","parent":0,"entities":8,"roles":{"module":1,"type":1,"operation":2,"attribute":0,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":2,"conventional":0,"numeric":0},"distinct":9,"distinctDomain":7,"weight":31,"occurrences":13,"terms":[[83,16,2,2],[26,3,1,1],[64,3,1,1],[1,2,2,2],[60,2,2,2],[66,2,2,2],[40,1,1,1],[52,1,1,1],[59,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Fpricing.ex","name":"lib/acme_order/pricing.ex","kind":"file","parent":0,"entities":4,"roles":{"module":1,"type":1,"operation":2,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":5,"weight":27,"occurrences":9,"terms":[[66,16,2,2],[26,3,1,1],[64,3,1,1],[1,2,2,2],[60,2,2,2],[40,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Fpromo%232024.ex","name":"lib/acme_order/promo#2024.ex","kind":"file","parent":0,"entities":4,"roles":{"module":1,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":1},"distinct":7,"distinctDomain":6,"weight":25,"occurrences":9,"terms":[[68,8,1,1],[69,8,1,1],[7,3,1,1],[1,2,2,2],[60,2,2,2],[40,1,1,1],[52,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Frepo.ex","name":"lib/acme_order/repo.ex","kind":"file","parent":0,"entities":2,"roles":{"module":1,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":2,"weight":21,"occurrences":7,"terms":[[74,16,2,2],[1,2,2,2],[60,2,2,2],[40,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Freporting.ex","name":"lib/acme_order/reporting.ex","kind":"file","parent":0,"entities":7,"roles":{"module":1,"type":1,"operation":3,"attribute":0,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":10,"distinctDomain":8,"weight":41,"occurrences":15,"terms":[[75,16,2,2],[60,6,4,4],[25,3,1,1],[58,3,1,1],[64,3,1,1],[82,3,1,1],[86,3,1,1],[1,2,2,2],[37,1,1,1],[40,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Fschema%2Fline.ex","name":"lib/acme_order/schema/line.ex","kind":"file","parent":0,"entities":7,"roles":{"module":1,"type":1,"operation":2,"attribute":0,"parameter":3,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":9,"distinctDomain":4,"weight":34,"occurrences":14,"terms":[[41,17,3,3],[80,4,2,2],[16,3,1,1],[31,3,1,1],[1,2,2,2],[60,2,2,2],[11,1,1,1],[40,1,1,1],[78,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Fstock.ex","name":"lib/acme_order/stock.ex","kind":"file","parent":0,"entities":23,"roles":{"module":1,"type":1,"operation":4,"attribute":1,"parameter":8,"local":0,"other":0},"skipped":{"unnamed":4,"conventional":4,"numeric":0},"distinct":14,"distinctDomain":11,"weight":45,"occurrences":20,"terms":[[86,16,2,2],[88,4,1,1],[12,3,1,1],[61,3,1,1],[73,3,1,1],[76,3,1,1],[80,3,3,3],[1,2,2,2],[60,2,2,2],[85,2,2,2],[40,1,1,1],[59,1,1,1],[62,1,1,1],[70,1,1,1]]}, + {"level":"module","id":"ex:lib%2Facme_order%2Ftwo_modules.ex","name":"lib/acme_order/two_modules.ex","kind":"file","parent":0,"entities":6,"roles":{"module":1,"type":2,"operation":2,"attribute":1,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":9,"distinctDomain":8,"weight":50,"occurrences":15,"terms":[[54,11,2,2],[17,9,2,2],[51,8,1,1],[93,8,1,1],[18,4,1,1],[1,3,3,3],[3,3,1,1],[60,3,3,3],[40,1,1,1]]}, + {"level":"module","id":"ex:mix.exs","name":"mix.exs","kind":"file","parent":0,"entities":5,"roles":{"module":1,"type":1,"operation":3,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":5,"weight":35,"occurrences":8,"terms":[[48,16,2,2],[67,11,2,2],[6,3,1,1],[24,3,1,1],[1,1,1,1],[60,1,1,1]]}, + {"level":"module","id":"ex:test%2Facme_order%2Forder_test.exs","name":"test/acme_order/order_test.exs","kind":"file","parent":0,"entities":2,"roles":{"module":1,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":2,"weight":37,"occurrences":9,"terms":[[60,18,4,2],[89,17,3,2],[1,2,2,2]]}, + {"level":"module","id":"ex:test%2Ftest_helper.exs","name":"test/test_helper.exs","kind":"file","parent":0,"entities":1,"roles":{"module":1,"type":0,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":0,"weight":17,"occurrences":3,"terms":[[89,9,2,1],[35,8,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fapplication.ex/AcmeOrder%2EApplication","name":"AcmeOrder.Application","kind":"module","parent":2,"entities":4,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":2,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":4,"weight":13,"occurrences":4,"terms":[[6,8,1,1],[84,3,1,1],[1,1,1,1],[60,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fdynamic.ex/AcmeOrder%2EDynamic","name":"AcmeOrder.Dynamic","kind":"module","parent":3,"entities":13,"roles":{"module":0,"type":1,"operation":4,"attribute":0,"parameter":8,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":17,"distinctDomain":13,"weight":39,"occurrences":18,"terms":[[27,8,1,1],[66,4,2,2],[15,3,1,1],[31,3,1,1],[64,3,1,1],[73,3,1,1],[79,3,1,1],[95,3,1,1],[1,1,1,1],[8,1,1,1],[32,1,1,1],[41,1,1,1],[49,1,1,1],[53,1,1,1],[60,1,1,1],[62,1,1,1],[80,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Ferrors.ex/AcmeOrder%2EErrors","name":"AcmeOrder.Errors","kind":"module","parent":4,"entities":5,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":6,"weight":18,"occurrences":7,"terms":[[29,8,1,1],[30,3,1,1],[77,3,1,1],[1,1,1,1],[32,1,1,1],[60,1,1,1],[71,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Ferrors.ex/AcmeOrder%2EErrors%2ETooManyLines","name":"AcmeOrder.Errors.TooManyLines","kind":"module","parent":4,"entities":5,"roles":{"module":0,"type":1,"operation":1,"attribute":2,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":8,"distinctDomain":7,"weight":38,"occurrences":9,"terms":[[42,8,1,1],[45,8,1,1],[91,8,1,1],[47,7,2,2],[46,4,1,1],[1,1,1,1],[29,1,1,1],[60,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Flegacy%2Fatom_module.ex/legacy%2Emod","name":"legacy.mod","kind":"module","parent":5,"entities":2,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":3,"weight":12,"occurrences":3,"terms":[[49,8,1,1],[34,3,1,1],[38,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fmacros.ex/AcmeOrder%2EMacros","name":"AcmeOrder.Macros","kind":"module","parent":7,"entities":7,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":3,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":1,"numeric":0},"distinct":8,"distinctDomain":6,"weight":25,"occurrences":10,"terms":[[44,8,1,1],[17,6,2,2],[9,3,1,1],[36,3,1,1],[94,2,2,2],[1,1,1,1],[59,1,1,1],[60,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fmoney.ex/AcmeOrder%2EMoney","name":"AcmeOrder.Money","kind":"module","parent":8,"entities":10,"roles":{"module":0,"type":1,"operation":3,"attribute":2,"parameter":4,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":10,"distinctDomain":6,"weight":31,"occurrences":12,"terms":[[52,8,1,1],[4,5,2,2],[22,5,2,2],[2,3,1,1],[55,3,1,1],[96,3,1,1],[0,1,1,1],[1,1,1,1],[13,1,1,1],[60,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fmoney.ex/String%2EChars%2EAcmeOrder%2EMoney","name":"String.Chars.AcmeOrder.Money","kind":"module","parent":8,"entities":3,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":1,"numeric":0},"distinct":5,"distinctDomain":5,"weight":12,"occurrences":5,"terms":[[52,8,1,1],[1,1,1,1],[19,1,1,1],[60,1,1,1],[87,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fnotifier.ex/AcmeOrder%2ENotifier","name":"AcmeOrder.Notifier","kind":"module","parent":9,"entities":5,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":6,"weight":18,"occurrences":7,"terms":[[56,8,1,1],[39,3,1,1],[57,3,1,1],[60,2,2,2],[1,1,1,1],[43,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fnotifier.ex/AcmeOrder%2ENotifier%2EEmail","name":"AcmeOrder.Notifier.Email","kind":"module","parent":9,"entities":3,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":5,"weight":15,"occurrences":6,"terms":[[28,8,1,1],[23,3,1,1],[60,2,2,2],[1,1,1,1],[56,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fnotifier.ex/AcmeOrder%2ENotifier%2ESms%2EGateway","name":"AcmeOrder.Notifier.Sms.Gateway","kind":"module","parent":9,"entities":3,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":7,"weight":16,"occurrences":7,"terms":[[33,8,1,1],[23,3,1,1],[1,1,1,1],[56,1,1,1],[60,1,1,1],[81,1,1,1],[90,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Forder.ex/AcmeOrder%2EOrder","name":"AcmeOrder.Order","kind":"module","parent":10,"entities":16,"roles":{"module":0,"type":1,"operation":4,"attribute":5,"parameter":6,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":13,"distinctDomain":10,"weight":55,"occurrences":20,"terms":[[60,12,5,4],[42,8,2,2],[72,5,2,2],[17,4,1,1],[41,4,2,2],[46,4,1,1],[66,4,1,1],[2,3,1,1],[21,3,1,1],[25,3,1,1],[92,3,1,1],[1,1,1,1],[59,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fpriceable.ex/AcmeOrder%2EPriceable","name":"AcmeOrder.Priceable","kind":"protocol","parent":11,"entities":5,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":6,"weight":18,"occurrences":7,"terms":[[65,8,1,1],[22,3,1,1],[64,3,1,1],[37,2,2,2],[1,1,1,1],[60,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fpriceable.ex/AcmeOrder%2EPriceable%2EAcmeOrder%2EMoney","name":"AcmeOrder.Priceable.AcmeOrder.Money","kind":"module","parent":11,"entities":5,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":6,"weight":20,"occurrences":9,"terms":[[52,9,2,2],[22,3,1,1],[64,3,1,1],[1,2,2,1],[60,2,2,1],[65,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fpriceable.ex/AcmeOrder%2EPriceable%2EAcmeOrder%2EOrder","name":"AcmeOrder.Priceable.AcmeOrder.Order","kind":"module","parent":11,"entities":5,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":5,"weight":20,"occurrences":9,"terms":[[60,11,4,2],[22,3,1,1],[64,3,1,1],[1,2,2,1],[65,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fpriceable.ex/AcmeOrder%2EPriceable%2EAny","name":"AcmeOrder.Priceable.Any","kind":"module","parent":11,"entities":5,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":2,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":6,"weight":17,"occurrences":6,"terms":[[5,8,1,1],[22,3,1,1],[64,3,1,1],[1,1,1,1],[60,1,1,1],[65,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fpricing%2Fpremium.ex/AcmeOrder%2EPricing%2EPremium","name":"AcmeOrder.Pricing.Premium","kind":"module","parent":12,"entities":4,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":7,"weight":16,"occurrences":7,"terms":[[63,8,1,1],[64,3,1,1],[1,1,1,1],[17,1,1,1],[41,1,1,1],[60,1,1,1],[66,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fpricing%2Fstandard.ex/AcmeOrder%2EPricing%2EStandard","name":"AcmeOrder.Pricing.Standard","kind":"module","parent":13,"entities":7,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":2,"conventional":0,"numeric":0},"distinct":8,"distinctDomain":7,"weight":19,"occurrences":8,"terms":[[83,8,1,1],[26,3,1,1],[64,3,1,1],[1,1,1,1],[52,1,1,1],[59,1,1,1],[60,1,1,1],[66,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fpricing.ex/AcmeOrder%2EPricing","name":"AcmeOrder.Pricing","kind":"module","parent":14,"entities":3,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":5,"weight":16,"occurrences":5,"terms":[[66,8,1,1],[26,3,1,1],[64,3,1,1],[1,1,1,1],[60,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fpromo%232024.ex/AcmeOrder%2EPromo2024","name":"AcmeOrder.Promo2024","kind":"module","parent":15,"entities":3,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":5,"weight":14,"occurrences":5,"terms":[[69,8,1,1],[7,3,1,1],[1,1,1,1],[52,1,1,1],[60,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Frepo.ex/AcmeOrder%2ERepo","name":"AcmeOrder.Repo","kind":"module","parent":16,"entities":1,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":2,"weight":10,"occurrences":3,"terms":[[74,8,1,1],[1,1,1,1],[60,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Freporting.ex/AcmeOrder%2EReporting","name":"AcmeOrder.Reporting","kind":"module","parent":17,"entities":6,"roles":{"module":0,"type":1,"operation":3,"attribute":0,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":9,"distinctDomain":8,"weight":30,"occurrences":11,"terms":[[75,8,1,1],[60,5,3,3],[25,3,1,1],[58,3,1,1],[64,3,1,1],[82,3,1,1],[86,3,1,1],[1,1,1,1],[37,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fschema%2Fline.ex/AcmeOrder%2ELine","name":"AcmeOrder.Line","kind":"module","parent":18,"entities":6,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":3,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":4,"weight":22,"occurrences":9,"terms":[[41,9,2,2],[80,4,2,2],[16,3,1,1],[31,3,1,1],[1,1,1,1],[11,1,1,1],[60,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Fstock.ex/AcmeOrder%2EStock","name":"AcmeOrder.Stock","kind":"module","parent":19,"entities":22,"roles":{"module":0,"type":1,"operation":4,"attribute":1,"parameter":8,"local":0,"other":0},"skipped":{"unnamed":4,"conventional":4,"numeric":0},"distinct":13,"distinctDomain":11,"weight":34,"occurrences":16,"terms":[[86,8,1,1],[88,4,1,1],[12,3,1,1],[61,3,1,1],[73,3,1,1],[76,3,1,1],[80,3,3,3],[85,2,2,2],[1,1,1,1],[59,1,1,1],[60,1,1,1],[62,1,1,1],[70,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Ftwo_modules.ex/AcmeOrder%2EChannel","name":"AcmeOrder.Channel","kind":"module","parent":20,"entities":3,"roles":{"module":0,"type":1,"operation":1,"attribute":1,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":5,"weight":17,"occurrences":5,"terms":[[17,8,1,1],[18,4,1,1],[3,3,1,1],[1,1,1,1],[60,1,1,1]]}, + {"level":"type","id":"ex:lib%2Facme_order%2Ftwo_modules.ex/AcmeOrder%2EChannel%2ENames","name":"AcmeOrder.Channel.Names","kind":"module","parent":20,"entities":2,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":4,"weight":14,"occurrences":5,"terms":[[54,11,2,2],[1,1,1,1],[17,1,1,1],[60,1,1,1]]}, + {"level":"type","id":"ex:mix.exs/AcmeOrder%2EMixProject","name":"AcmeOrder.MixProject","kind":"module","parent":21,"entities":4,"roles":{"module":0,"type":1,"operation":3,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":5,"weight":27,"occurrences":7,"terms":[[67,11,2,2],[48,8,1,1],[6,3,1,1],[24,3,1,1],[1,1,1,1],[60,1,1,1]]}, + {"level":"type","id":"ex:test%2Facme_order%2Forder_test.exs/AcmeOrder%2EOrderTest","name":"AcmeOrder.OrderTest","kind":"module","parent":22,"entities":1,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":2,"weight":18,"occurrences":4,"terms":[[60,9,2,1],[89,8,1,1],[1,1,1,1]]} + ], + "diagnostics": {"unplaced":[],"withoutLexicon":[]} +} diff --git a/fixtures/java/README.md b/fixtures/java/README.md index e4da463..482879e 100644 --- a/fixtures/java/README.md +++ b/fixtures/java/README.md @@ -123,3 +123,25 @@ Spoon 11.5.0, `setNoClasspath(true)`, `setComplianceLevel(17)`, Re-measure with a throwaway Spoon program, not from memory, whenever this corpus changes. + +## The vocabulary: `order`, counted by hand + +`expected/vocabulary.json` is the words of this model's names +(`@codegraph/vocabulary`, PLAN.md §20), rebuilt byte for byte by +`pnpm --filter @codegraph/vocabulary test` (`CODEGRAPH_UPDATE_SNAPSHOTS=1` +rewrites it). A word weighs the role of the entity whose name carries it — +module and type 8, attribute 4, operation 3, parameter and local 1 — except a +word of the name's qualifier, before the last separator of a qualified kind, +which weighs 1. One word, counted by hand from the model and pinned by the +vocabulary's tests: + +| name | kind | role | weight | +|---|---|---|---| +| `com.acme.order` | package | module | 8 | +| `AbstractOrder`, `Order`, `OrderService` | class | type | 3 × 8 | +| `order` × 5 (`both`, `freeOf`, `notifyShipped`, `bill`, a lambda) | parameter | parameter | 5 × 1 | +| `order` (in `archive`) | localVariable | local | 1 | +| `com.acme.order.adapter`, `com.acme.order.legacy` | package | qualifier | 2 × 1 | + +**`order` at the corpus: weight 40, 12 occurrences, 12 carriers.** The two +`orders` parameters of `archive` are another word: nothing stems. diff --git a/fixtures/java/expected/vocabulary.json b/fixtures/java/expected/vocabulary.json new file mode 100644 index 0000000..69b04a8 --- /dev/null +++ b/fixtures/java/expected/vocabulary.json @@ -0,0 +1,123 @@ +{ + "kind": "codegraph.vocabulary/1", + "generatedBy": "@codegraph/vocabulary", + "view": {"name":"all","filters":[]}, + "corpus": {"name":"src","roots":["fixtures/java/src"]}, + "langs": ["java"], + "weights": {"module":8,"type":8,"operation":3,"attribute":4,"parameter":1,"local":1,"other":1,"qualifier":1}, + "classes": ["domain","technical","keyword","short"], + "technical": {"words":125,"sha256":"c16bd05be59a0c3281696359e4b4f7b3efe7860512b4ab37519f8c3a063c95f7"}, + "terms": [ + {"text":"abstract","class":"keyword","modules":1,"types":1,"exclusive":1}, + {"text":"acme","class":"domain","modules":3,"types":0}, + {"text":"adapter","class":"technical","modules":1,"types":1,"exclusive":2}, + {"text":"add","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"allocator","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"amount","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"apply","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"archive","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"audited","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"base","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"basket","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"batch","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"best","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"bill","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"both","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"cents","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"chain","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"channel","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"check","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"com","class":"technical","modules":3,"types":0}, + {"text":"counts","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"cursor","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"discount","class":"domain","modules":1,"types":3,"exclusive":1}, + {"text":"discountable","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"document","class":"domain","modules":1,"types":1,"exclusive":2}, + {"text":"empty","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"ensure","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"exception","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"factor","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"first","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"free","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"guard","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"head","class":"domain","modules":1,"types":1,"exclusive":3}, + {"text":"index","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"invoice","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"join","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"ledger","class":"domain","modules":2,"types":2}, + {"text":"legacy","class":"domain","modules":1,"types":0,"exclusive":3}, + {"text":"length","class":"domain","modules":1,"types":1,"exclusive":3}, + {"text":"line","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"lines","class":"domain","modules":1,"types":3,"exclusive":1}, + {"text":"list","class":"domain","modules":1,"types":1,"exclusive":3}, + {"text":"log","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"max","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"message","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"money","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"next","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"notifications","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"notify","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"of","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"on","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"order","class":"domain","modules":3,"types":4}, + {"text":"orders","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"out","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"part","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"parts","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"percent","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"phone","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"post","class":"domain","modules":1,"types":1,"exclusive":2}, + {"text":"price","class":"domain","modules":1,"types":6,"exclusive":1}, + {"text":"priceable","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"priced","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"prices","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"quantity","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"reference","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"reporting","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"rows","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"running","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"scaled","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"second","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"service","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"shipped","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"size","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"stock","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"store","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"tail","class":"domain","modules":1,"types":1,"exclusive":3}, + {"text":"times","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"to","class":"technical","modules":1,"types":1,"exclusive":1}, + {"text":"today","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"total","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"trail","class":"domain","modules":2,"types":2}, + {"text":"value","class":"domain","modules":1,"types":2,"exclusive":1}, + {"text":"values","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"web","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"zero","class":"domain","modules":1,"types":1,"exclusive":1} + ], + "scopes": [ + {"level":"corpus","name":"src","entities":152,"roles":{"module":3,"type":19,"operation":43,"attribute":21,"parameter":35,"local":6,"other":0},"skipped":{"unnamed":25,"conventional":0,"numeric":0},"distinct":85,"distinctDomain":75,"weight":503,"occurrences":146,"terms":[[51,40,12,12],[59,18,6,6],[2,16,2,2],[10,16,2,2],[17,16,4,4],[22,14,3,3],[36,13,3,3],[21,11,2,2],[40,11,3,3],[79,11,3,3],[64,10,5,5],[5,9,4,4],[39,9,2,2],[0,8,1,1],[8,8,1,1],[11,8,1,1],[15,8,3,3],[23,8,1,1],[25,8,1,1],[27,8,1,1],[31,8,1,1],[32,8,3,3],[37,8,1,1],[41,8,1,1],[45,8,1,1],[47,8,1,1],[60,8,1,1],[65,8,1,1],[70,8,1,1],[73,8,1,1],[80,8,2,2],[43,7,2,2],[67,7,2,2],[71,7,2,2],[7,6,2,2],[20,5,2,2],[62,5,2,2],[75,5,2,2],[29,4,2,2],[33,4,1,1],[50,4,1,1],[57,4,1,1],[74,4,1,1],[81,4,2,2],[83,4,1,1],[1,3,3,3],[3,3,1,1],[4,3,1,1],[6,3,1,1],[13,3,1,1],[14,3,1,1],[16,3,1,1],[18,3,1,1],[19,3,3,3],[26,3,1,1],[30,3,1,1],[35,3,1,1],[38,3,1,1],[42,3,1,1],[46,3,1,1],[48,3,1,1],[49,3,1,1],[58,3,1,1],[61,3,1,1],[68,3,1,1],[72,3,1,1],[76,3,1,1],[77,3,1,1],[78,3,1,1],[84,3,1,1],[44,2,2,2],[52,2,2,2],[56,2,2,2],[63,2,2,2],[82,2,2,2],[9,1,1,1],[12,1,1,1],[24,1,1,1],[28,1,1,1],[34,1,1,1],[53,1,1,1],[54,1,1,1],[55,1,1,1],[66,1,1,1],[69,1,1,1]]}, + {"level":"module","id":"java:com.acme.order","name":"com.acme.order","kind":"package","parent":0,"entities":137,"roles":{"module":1,"type":17,"operation":40,"attribute":18,"parameter":32,"local":6,"other":0},"skipped":{"unnamed":23,"conventional":0,"numeric":0},"distinct":77,"distinctDomain":68,"weight":433,"occurrences":126,"terms":[[51,38,10,10],[59,18,6,6],[10,16,2,2],[17,16,4,4],[22,14,3,3],[21,11,2,2],[40,11,3,3],[79,11,3,3],[64,10,5,5],[5,9,4,4],[39,9,2,2],[0,8,1,1],[8,8,1,1],[11,8,1,1],[15,8,3,3],[23,8,1,1],[25,8,1,1],[27,8,1,1],[31,8,1,1],[45,8,1,1],[47,8,1,1],[60,8,1,1],[65,8,1,1],[70,8,1,1],[73,8,1,1],[43,7,2,2],[67,7,2,2],[71,7,2,2],[7,6,2,2],[20,5,2,2],[36,5,2,2],[62,5,2,2],[29,4,2,2],[33,4,1,1],[50,4,1,1],[57,4,1,1],[74,4,1,1],[80,4,1,1],[81,4,2,2],[83,4,1,1],[3,3,1,1],[4,3,1,1],[6,3,1,1],[13,3,1,1],[14,3,1,1],[16,3,1,1],[18,3,1,1],[26,3,1,1],[30,3,1,1],[35,3,1,1],[42,3,1,1],[46,3,1,1],[48,3,1,1],[49,3,1,1],[61,3,1,1],[68,3,1,1],[72,3,1,1],[76,3,1,1],[77,3,1,1],[78,3,1,1],[84,3,1,1],[44,2,2,2],[52,2,2,2],[56,2,2,2],[63,2,2,2],[82,2,2,2],[1,1,1,1],[9,1,1,1],[12,1,1,1],[19,1,1,1],[28,1,1,1],[34,1,1,1],[53,1,1,1],[54,1,1,1],[55,1,1,1],[66,1,1,1],[69,1,1,1]]}, + {"level":"module","id":"java:com.acme.order.adapter","name":"com.acme.order.adapter","kind":"package","parent":1,"entities":6,"roles":{"module":1,"type":1,"operation":1,"attribute":1,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":8,"distinctDomain":6,"weight":35,"occurrences":9,"terms":[[2,16,2,2],[36,8,1,1],[80,4,1,1],[58,3,1,1],[1,1,1,1],[19,1,1,1],[24,1,1,1],[51,1,1,1]]}, + {"level":"module","id":"java:com.acme.order.legacy","name":"com.acme.order.legacy","kind":"package","parent":1,"entities":9,"roles":{"module":1,"type":1,"operation":2,"attribute":2,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":8,"distinctDomain":7,"weight":35,"occurrences":11,"terms":[[32,8,3,3],[37,8,1,1],[41,8,1,1],[75,5,2,2],[38,3,1,1],[1,1,1,1],[19,1,1,1],[51,1,1,1]]}, + {"level":"type","id":"java:com.acme.order.adapter/LedgerAdapter","name":"LedgerAdapter","kind":"class","parent":2,"entities":5,"roles":{"module":0,"type":1,"operation":1,"attribute":1,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":4,"weight":24,"occurrences":5,"terms":[[2,8,1,1],[36,8,1,1],[80,4,1,1],[58,3,1,1],[24,1,1,1]]}, + {"level":"type","id":"java:com.acme.order.legacy/List","name":"List","kind":"class","parent":3,"entities":8,"roles":{"module":0,"type":1,"operation":2,"attribute":2,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":4,"weight":24,"occurrences":7,"terms":[[32,8,3,3],[41,8,1,1],[75,5,2,2],[38,3,1,1]]}, + {"level":"type","id":"java:com.acme.order/AbstractOrder","name":"AbstractOrder","kind":"class","parent":1,"entities":7,"roles":{"module":0,"type":1,"operation":2,"attribute":2,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":4,"weight":31,"occurrences":7,"terms":[[0,8,1,1],[51,8,1,1],[64,8,3,3],[79,4,1,1],[59,3,1,1]]}, + {"level":"type","id":"java:com.acme.order/Audited","name":"Audited","kind":"annotation","parent":1,"entities":2,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":11,"occurrences":2,"terms":[[8,8,1,1],[81,3,1,1]]}, + {"level":"type","id":"java:com.acme.order/Basket","name":"Basket","kind":"class","parent":1,"entities":8,"roles":{"module":0,"type":1,"operation":3,"attribute":2,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":5,"weight":26,"occurrences":7,"terms":[[10,8,1,1],[79,7,2,2],[40,4,1,1],[3,3,1,1],[21,3,1,1],[39,1,1,1]]}, + {"level":"type","id":"java:com.acme.order/Basket.Cursor","name":"Cursor","kind":"class","parent":8,"entities":4,"roles":{"module":0,"type":1,"operation":1,"attribute":1,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":3,"weight":15,"occurrences":3,"terms":[[21,8,1,1],[33,4,1,1],[46,3,1,1]]}, + {"level":"type","id":"java:com.acme.order/Basket.Line","name":"Line","kind":"class","parent":8,"entities":5,"roles":{"module":0,"type":1,"operation":1,"attribute":1,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":16,"occurrences":4,"terms":[[5,8,3,3],[39,8,1,1]]}, + {"level":"type","id":"java:com.acme.order/Basket.Line.Discount","name":"Discount","kind":"class","parent":10,"entities":4,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":3,"weight":15,"occurrences":4,"terms":[[22,8,1,1],[6,3,1,1],[77,3,1,1],[5,1,1,1]]}, + {"level":"type","id":"java:com.acme.order/Batch","name":"Batch","kind":"class","parent":1,"entities":16,"roles":{"module":0,"type":1,"operation":6,"attribute":3,"parameter":3,"local":0,"other":0},"skipped":{"unnamed":3,"conventional":0,"numeric":0},"distinct":10,"distinctDomain":9,"weight":41,"occurrences":13,"terms":[[11,8,1,1],[67,7,2,2],[20,5,2,2],[62,5,2,2],[4,3,1,1],[59,3,1,1],[61,3,1,1],[68,3,1,1],[72,3,1,1],[9,1,1,1]]}, + {"level":"type","id":"java:com.acme.order/Channel","name":"Channel","kind":"enum","parent":1,"entities":6,"roles":{"module":0,"type":1,"operation":1,"attribute":3,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":5,"weight":23,"occurrences":5,"terms":[[17,8,1,1],[57,4,1,1],[74,4,1,1],[83,4,1,1],[59,3,1,1]]}, + {"level":"type","id":"java:com.acme.order/Discountable","name":"Discountable","kind":"interface","parent":1,"entities":3,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":3,"weight":12,"occurrences":3,"terms":[[23,8,1,1],[22,3,1,1],[56,1,1,1]]}, + {"level":"type","id":"java:com.acme.order/EmptyBasketException","name":"EmptyBasketException","kind":"class","parent":1,"entities":3,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":3,"weight":25,"occurrences":4,"terms":[[10,8,1,1],[25,8,1,1],[27,8,1,1],[44,1,1,1]]}, + {"level":"type","id":"java:com.acme.order/Money","name":"Money","kind":"record","parent":1,"entities":9,"roles":{"module":0,"type":1,"operation":4,"attribute":1,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":6,"weight":26,"occurrences":8,"terms":[[15,8,3,3],[45,8,1,1],[59,3,1,1],[76,3,1,1],[84,3,1,1],[28,1,1,1]]}, + {"level":"type","id":"java:com.acme.order/Notifications","name":"Notifications","kind":"class","parent":1,"entities":22,"roles":{"module":0,"type":1,"operation":6,"attribute":1,"parameter":7,"local":0,"other":0},"skipped":{"unnamed":7,"conventional":0,"numeric":0},"distinct":14,"distinctDomain":12,"weight":47,"occurrences":18,"terms":[[47,8,1,1],[71,7,2,2],[50,4,1,1],[51,4,4,4],[14,3,1,1],[16,3,1,1],[30,3,1,1],[42,3,1,1],[48,3,1,1],[49,3,1,1],[59,3,1,1],[29,1,1,1],[44,1,1,1],[69,1,1,1]]}, + {"level":"type","id":"java:com.acme.order/Order","name":"Order","kind":"class","parent":1,"entities":12,"roles":{"module":0,"type":1,"operation":2,"attribute":3,"parameter":4,"local":0,"other":0},"skipped":{"unnamed":2,"conventional":0,"numeric":0},"distinct":8,"distinctDomain":8,"weight":34,"occurrences":11,"terms":[[17,8,3,3],[51,8,1,1],[40,4,1,1],[43,4,1,1],[80,4,1,1],[22,3,1,1],[64,2,2,2],[56,1,1,1]]}, + {"level":"type","id":"java:com.acme.order/OrderService","name":"OrderService","kind":"class","parent":1,"entities":12,"roles":{"module":0,"type":1,"operation":3,"attribute":1,"parameter":4,"local":2,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":6,"weight":35,"occurrences":12,"terms":[[51,10,3,3],[70,8,1,1],[7,6,2,2],[36,5,2,2],[13,3,1,1],[52,2,2,2],[34,1,1,1]]}, + {"level":"type","id":"java:com.acme.order/Priceable","name":"Priceable","kind":"interface","parent":1,"entities":2,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":11,"occurrences":2,"terms":[[60,8,1,1],[59,3,1,1]]}, + {"level":"type","id":"java:com.acme.order/Reporting","name":"Reporting","kind":"class","parent":1,"entities":15,"roles":{"module":0,"type":1,"operation":5,"attribute":0,"parameter":4,"local":4,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":13,"distinctDomain":13,"weight":31,"occurrences":14,"terms":[[65,8,1,1],[29,3,1,1],[35,3,1,1],[40,3,1,1],[43,3,1,1],[78,3,1,1],[82,2,2,2],[12,1,1,1],[53,1,1,1],[54,1,1,1],[55,1,1,1],[66,1,1,1],[81,1,1,1]]}, + {"level":"type","id":"java:com.acme.order/StockGuard","name":"StockGuard","kind":"class","parent":1,"entities":6,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":5,"weight":24,"occurrences":6,"terms":[[31,8,1,1],[73,8,1,1],[18,3,1,1],[26,3,1,1],[63,2,2,2]]} + ], + "diagnostics": {"unplaced":[],"withoutLexicon":[]} +} diff --git a/fixtures/ruby/README.md b/fixtures/ruby/README.md index c6545b5..a4e702c 100644 --- a/fixtures/ruby/README.md +++ b/fixtures/ruby/README.md @@ -140,3 +140,24 @@ writes the one a program that loaded it would produce and merges it with | `Ledger#open?` is defined at `ledger.rb:6` | nothing, counted: a method no `def` writes has no entity, and gets none | The files and the entities are the same lines with and without it. + +## The vocabulary: `pricing`, counted by hand + +`expected/vocabulary.json` is the words of this model's names +(`@codegraph/vocabulary`, PLAN.md §20), rebuilt byte for byte by +`pnpm --filter @codegraph/vocabulary test` (`CODEGRAPH_UPDATE_SNAPSHOTS=1` +rewrites it). A word weighs the role of the entity whose name carries it — +module and type 8, attribute 4, operation 3, parameter and local 1 — except a +word of the name's qualifier, before the last separator of a qualified kind, +which weighs 1. One word, counted by hand from the model and pinned by the +vocabulary's tests: + +| name | kind | role | weight | +|---|---|---|---| +| `@pricing` | field | attribute | 4 | +| `lib/acme_order/pricing/premium.rb`, `…/standard.rb` | file | qualifier | 2 × 1 | +| `AcmeOrder::Pricing::Premium`, `AcmeOrder::Pricing::Standard` | class | qualifier | 2 × 1 | + +**`pricing`: weight 8, 5 occurrences, 5 carriers** — no entity is named +`Pricing` itself, so the word is only ever a qualifier or a field. A constant +path's `::` and a file's `/` qualify alike. diff --git a/fixtures/ruby/expected/vocabulary.json b/fixtures/ruby/expected/vocabulary.json new file mode 100644 index 0000000..80c1df8 --- /dev/null +++ b/fixtures/ruby/expected/vocabulary.json @@ -0,0 +1,186 @@ +{ + "kind": "codegraph.vocabulary/1", + "generatedBy": "@codegraph/vocabulary", + "view": {"name":"all","filters":[]}, + "corpus": {"name":"src","roots":["fixtures/ruby/src"]}, + "langs": ["rb"], + "weights": {"module":8,"type":8,"operation":3,"attribute":4,"parameter":1,"local":1,"other":1,"qualifier":1}, + "classes": ["domain","technical","keyword","short"], + "technical": {"words":125,"sha256":"c16bd05be59a0c3281696359e4b4f7b3efe7860512b4ab37519f8c3a063c95f7"}, + "terms": [ + {"text":"a","class":"short","modules":1,"types":0,"exclusive":6}, + {"text":"acme","class":"domain","modules":25,"types":31}, + {"text":"add","class":"technical","modules":1,"types":1,"exclusive":18}, + {"text":"all","class":"domain","modules":1,"types":1,"exclusive":12}, + {"text":"annotate","class":"domain","modules":1,"types":1,"exclusive":26}, + {"text":"archive","class":"domain","modules":1,"types":1,"exclusive":25}, + {"text":"audit","class":"domain","modules":2,"types":2}, + {"text":"auditable","class":"domain","modules":1,"types":2,"exclusive":7}, + {"text":"audited","class":"domain","modules":1,"types":1,"exclusive":7}, + {"text":"base","class":"technical","modules":2,"types":2}, + {"text":"bin","class":"domain","modules":1,"types":0,"exclusive":4}, + {"text":"blank","class":"domain","modules":1,"types":1,"exclusive":9}, + {"text":"block","class":"domain","modules":1,"types":1,"exclusive":18}, + {"text":"broken","class":"domain","modules":1,"types":0,"exclusive":15}, + {"text":"build","class":"technical","modules":3,"types":2}, + {"text":"call","class":"domain","modules":1,"types":1,"exclusive":18}, + {"text":"cents","class":"domain","modules":1,"types":1,"exclusive":17}, + {"text":"class","class":"keyword","modules":1,"types":2,"exclusive":7}, + {"text":"close","class":"domain","modules":1,"types":1,"exclusive":14}, + {"text":"config","class":"technical","modules":1,"types":0,"exclusive":5}, + {"text":"console","class":"domain","modules":1,"types":0,"exclusive":4}, + {"text":"core","class":"technical","modules":1,"types":0,"exclusive":9}, + {"text":"currency","class":"domain","modules":2,"types":2}, + {"text":"current","class":"domain","modules":1,"types":1,"exclusive":14}, + {"text":"default","class":"technical","modules":1,"types":1,"exclusive":18}, + {"text":"divisor","class":"domain","modules":1,"types":1,"exclusive":17}, + {"text":"dynamic","class":"domain","modules":1,"types":5,"exclusive":10}, + {"text":"error","class":"technical","modules":1,"types":1,"exclusive":11}, + {"text":"errors","class":"technical","modules":1,"types":0,"exclusive":11}, + {"text":"export","class":"domain","modules":1,"types":1,"exclusive":15}, + {"text":"ext","class":"technical","modules":1,"types":0,"exclusive":9}, + {"text":"for","class":"keyword","modules":2,"types":2}, + {"text":"format","class":"domain","modules":1,"types":1,"exclusive":15}, + {"text":"found","class":"domain","modules":1,"types":1,"exclusive":11}, + {"text":"gemfile","class":"domain","modules":1,"types":0,"exclusive":1}, + {"text":"ghost","class":"domain","modules":1,"types":1,"exclusive":10}, + {"text":"handlers","class":"domain","modules":1,"types":1,"exclusive":10}, + {"text":"helper","class":"technical","modules":1,"types":0,"exclusive":31}, + {"text":"hook","class":"domain","modules":1,"types":1,"exclusive":24}, + {"text":"import","class":"domain","modules":1,"types":1,"exclusive":15}, + {"text":"install","class":"domain","modules":1,"types":1,"exclusive":24}, + {"text":"invoice","class":"domain","modules":1,"types":1,"exclusive":12}, + {"text":"invoices","class":"domain","modules":1,"types":1,"exclusive":12}, + {"text":"label","class":"domain","modules":1,"types":1,"exclusive":13}, + {"text":"lat","class":"domain","modules":1,"types":1,"exclusive":19}, + {"text":"ledger","class":"domain","modules":1,"types":1,"exclusive":14}, + {"text":"legacy","class":"domain","modules":2,"types":1}, + {"text":"lib","class":"technical","modules":24,"types":0}, + {"text":"line","class":"domain","modules":1,"types":2,"exclusive":18}, + {"text":"lines","class":"domain","modules":2,"types":2}, + {"text":"lng","class":"domain","modules":1,"types":1,"exclusive":19}, + {"text":"load","class":"technical","modules":1,"types":1,"exclusive":10}, + {"text":"loader","class":"domain","modules":2,"types":1}, + {"text":"many","class":"domain","modules":1,"types":1,"exclusive":11}, + {"text":"max","class":"domain","modules":2,"types":2}, + {"text":"methods","class":"domain","modules":1,"types":2,"exclusive":7}, + {"text":"money","class":"domain","modules":1,"types":1,"exclusive":17}, + {"text":"name","class":"domain","modules":1,"types":1,"exclusive":7}, + {"text":"names","class":"domain","modules":1,"types":1,"exclusive":7}, + {"text":"not","class":"keyword","modules":1,"types":1,"exclusive":11}, + {"text":"note","class":"domain","modules":1,"types":1,"exclusive":18}, + {"text":"odd","class":"domain","modules":1,"types":1,"exclusive":10}, + {"text":"options","class":"technical","modules":1,"types":1,"exclusive":18}, + {"text":"order","class":"domain","modules":25,"types":31}, + {"text":"orders","class":"domain","modules":1,"types":0,"exclusive":29}, + {"text":"other","class":"domain","modules":1,"types":1,"exclusive":17}, + {"text":"patch","class":"domain","modules":1,"types":0,"exclusive":6}, + {"text":"path","class":"domain","modules":1,"types":1,"exclusive":15}, + {"text":"ping","class":"domain","modules":1,"types":1,"exclusive":10}, + {"text":"point","class":"domain","modules":1,"types":1,"exclusive":19}, + {"text":"pong","class":"domain","modules":1,"types":1,"exclusive":10}, + {"text":"premium","class":"domain","modules":1,"types":1,"exclusive":21}, + {"text":"price","class":"domain","modules":4,"types":4}, + {"text":"priceable","class":"domain","modules":1,"types":1,"exclusive":20}, + {"text":"priced","class":"domain","modules":1,"types":1,"exclusive":20}, + {"text":"pricing","class":"domain","modules":3,"types":3}, + {"text":"promo","class":"domain","modules":1,"types":0,"exclusive":23}, + {"text":"promo2024","class":"domain","modules":1,"types":1,"exclusive":23}, + {"text":"qty","class":"domain","modules":1,"types":1,"exclusive":18}, + {"text":"rails","class":"domain","modules":1,"types":1,"exclusive":24}, + {"text":"railtie","class":"domain","modules":1,"types":0,"exclusive":24}, + {"text":"rakefile","class":"technical","modules":1,"types":0,"exclusive":2}, + {"text":"recent","class":"domain","modules":1,"types":1,"exclusive":25}, + {"text":"record","class":"domain","modules":1,"types":1,"exclusive":25}, + {"text":"reference","class":"domain","modules":2,"types":2}, + {"text":"reporting","class":"domain","modules":1,"types":1,"exclusive":26}, + {"text":"rest","class":"domain","modules":1,"types":1,"exclusive":18}, + {"text":"root","class":"domain","modules":1,"types":1,"exclusive":28}, + {"text":"rounding","class":"domain","modules":1,"types":1,"exclusive":18}, + {"text":"roundings","class":"domain","modules":1,"types":1,"exclusive":18}, + {"text":"settle","class":"domain","modules":1,"types":1,"exclusive":12}, + {"text":"shout","class":"domain","modules":1,"types":1,"exclusive":13}, + {"text":"sku","class":"domain","modules":1,"types":1,"exclusive":18}, + {"text":"spec","class":"technical","modules":2,"types":0}, + {"text":"stamp","class":"domain","modules":1,"types":1,"exclusive":7}, + {"text":"stamped","class":"domain","modules":1,"types":2,"exclusive":7}, + {"text":"standard","class":"domain","modules":1,"types":1,"exclusive":22}, + {"text":"string","class":"domain","modules":1,"types":1,"exclusive":9}, + {"text":"summary","class":"domain","modules":1,"types":1,"exclusive":26}, + {"text":"tasks","class":"domain","modules":1,"types":0,"exclusive":29}, + {"text":"text","class":"domain","modules":1,"types":1,"exclusive":13}, + {"text":"too","class":"domain","modules":1,"types":1,"exclusive":11}, + {"text":"total","class":"domain","modules":1,"types":1,"exclusive":18}, + {"text":"unnamed","class":"domain","modules":1,"types":1,"exclusive":26}, + {"text":"version","class":"domain","modules":1,"types":1,"exclusive":27}, + {"text":"wrapped","class":"domain","modules":1,"types":1,"exclusive":10}, + {"text":"zero","class":"domain","modules":1,"types":1,"exclusive":17} + ], + "scopes": [ + {"level":"corpus","name":"src","entities":157,"roles":{"module":31,"type":32,"operation":35,"attribute":16,"parameter":31,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":11,"numeric":1},"distinct":107,"distinctDomain":88,"weight":1004,"occurrences":326,"terms":[[63,147,75,66],[1,96,61,61],[47,24,24,24],[26,21,7,7],[93,18,4,2],[7,17,3,3],[9,17,3,3],[49,17,4,4],[17,16,2,2],[41,16,2,2],[43,16,2,2],[45,16,2,2],[52,16,2,2],[55,16,2,2],[56,16,2,2],[69,16,2,2],[71,16,2,2],[73,16,2,2],[83,16,2,2],[85,16,2,2],[96,16,2,2],[97,16,2,2],[22,14,5,5],[72,12,4,4],[104,12,2,2],[46,10,3,3],[14,9,3,3],[48,9,2,2],[95,9,2,2],[0,8,1,1],[13,8,1,1],[19,8,1,1],[20,8,1,1],[27,8,1,1],[28,8,1,1],[33,8,1,1],[34,8,1,1],[35,8,1,1],[37,8,1,1],[38,8,1,1],[53,8,1,1],[59,8,1,1],[61,8,1,1],[64,8,1,1],[66,8,1,1],[75,8,5,5],[76,8,1,1],[77,8,1,1],[80,8,1,1],[81,8,1,1],[101,8,1,1],[105,8,1,1],[84,7,4,4],[6,6,2,2],[15,6,2,2],[31,6,2,2],[90,6,2,2],[54,5,2,2],[78,5,2,2],[92,5,2,2],[16,4,1,1],[24,4,1,1],[36,4,1,1],[44,4,1,1],[50,4,1,1],[60,4,1,1],[89,4,1,1],[2,3,1,1],[3,3,1,1],[4,3,1,1],[5,3,1,1],[8,3,1,1],[11,3,1,1],[18,3,1,1],[23,3,1,1],[29,3,1,1],[39,3,1,1],[40,3,1,1],[51,3,1,1],[68,3,1,1],[70,3,1,1],[74,3,1,1],[82,3,1,1],[87,3,1,1],[91,3,1,1],[94,3,1,1],[98,3,1,1],[102,3,1,1],[103,3,1,1],[106,3,1,1],[12,2,2,2],[25,2,2,2],[62,2,2,2],[65,2,2,2],[10,1,1,1],[21,1,1,1],[30,1,1,1],[32,1,1,1],[42,1,1,1],[57,1,1,1],[58,1,1,1],[67,1,1,1],[79,1,1,1],[86,1,1,1],[88,1,1,1],[99,1,1,1],[100,1,1,1]]}, + {"level":"module","id":"rb:Gemfile","name":"Gemfile","kind":"file","parent":0,"entities":1,"roles":{"module":1,"type":0,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":1,"distinctDomain":1,"weight":8,"occurrences":1,"terms":[[34,8,1,1]]}, + {"level":"module","id":"rb:Rakefile","name":"Rakefile","kind":"file","parent":0,"entities":1,"roles":{"module":1,"type":0,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":1,"distinctDomain":0,"weight":8,"occurrences":1,"terms":[[81,8,1,1]]}, + {"level":"module","id":"rb:acme_order.gemspec","name":"acme_order.gemspec","kind":"file","parent":0,"entities":1,"roles":{"module":1,"type":0,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":16,"occurrences":2,"terms":[[1,8,1,1],[63,8,1,1]]}, + {"level":"module","id":"rb:bin%2Fconsole","name":"bin/console","kind":"file","parent":0,"entities":1,"roles":{"module":1,"type":0,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":9,"occurrences":2,"terms":[[20,8,1,1],[10,1,1,1]]}, + {"level":"module","id":"rb:config.ru","name":"config.ru","kind":"file","parent":0,"entities":1,"roles":{"module":1,"type":0,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":1,"distinctDomain":0,"weight":8,"occurrences":1,"terms":[[19,8,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Fa_order_patch.rb","name":"lib/acme_order/a_order_patch.rb","kind":"file","parent":0,"entities":3,"roles":{"module":1,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":4,"weight":40,"occurrences":10,"terms":[[63,18,4,2],[0,8,1,1],[66,8,1,1],[6,3,1,1],[1,2,2,2],[47,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Fauditable.rb","name":"lib/acme_order/auditable.rb","kind":"file","parent":0,"entities":12,"roles":{"module":1,"type":4,"operation":3,"attribute":0,"parameter":3,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":1,"numeric":0},"distinct":13,"distinctDomain":10,"weight":81,"occurrences":26,"terms":[[7,17,3,3],[17,16,2,2],[55,16,2,2],[95,9,2,2],[1,5,5,5],[63,5,5,5],[6,3,1,1],[8,3,1,1],[94,3,1,1],[9,1,1,1],[47,1,1,1],[57,1,1,1],[58,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Fbase.rb","name":"lib/acme_order/base.rb","kind":"file","parent":0,"entities":3,"roles":{"module":1,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":1,"numeric":0},"distinct":4,"distinctDomain":2,"weight":21,"occurrences":7,"terms":[[9,16,2,2],[1,2,2,2],[63,2,2,2],[47,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Fcore_ext%2Fstring.rb","name":"lib/acme_order/core_ext/string.rb","kind":"file","parent":0,"entities":3,"roles":{"module":1,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":4,"weight":24,"occurrences":8,"terms":[[97,16,2,2],[11,3,1,1],[1,1,1,1],[21,1,1,1],[30,1,1,1],[47,1,1,1],[63,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Fdynamic.rb","name":"lib/acme_order/dynamic.rb","kind":"file","parent":0,"entities":10,"roles":{"module":1,"type":5,"operation":3,"attribute":1,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":12,"distinctDomain":10,"weight":81,"occurrences":30,"terms":[[26,21,7,7],[35,8,1,1],[52,8,1,1],[61,8,1,1],[105,8,1,1],[1,7,7,7],[63,7,7,7],[36,4,1,1],[51,3,1,1],[68,3,1,1],[70,3,1,1],[47,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Ferrors.rb","name":"lib/acme_order/errors.rb","kind":"file","parent":0,"entities":6,"roles":{"module":1,"type":3,"operation":0,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":1,"numeric":0},"distinct":11,"distinctDomain":7,"weight":66,"occurrences":17,"terms":[[27,8,1,1],[28,8,1,1],[33,8,1,1],[49,8,1,1],[53,8,1,1],[59,8,1,1],[101,8,1,1],[1,4,4,4],[63,4,4,4],[47,1,1,1],[54,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Finvoice.rb","name":"lib/acme_order/invoice.rb","kind":"file","parent":0,"entities":6,"roles":{"module":1,"type":1,"operation":3,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":8,"distinctDomain":6,"weight":34,"occurrences":12,"terms":[[41,16,2,2],[90,6,2,2],[3,3,1,1],[14,3,1,1],[1,2,2,2],[63,2,2,2],[42,1,1,1],[47,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Flabel.rb","name":"lib/acme_order/label.rb","kind":"file","parent":0,"entities":5,"roles":{"module":1,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":1,"numeric":0},"distinct":6,"distinctDomain":5,"weight":25,"occurrences":9,"terms":[[43,16,2,2],[91,3,1,1],[1,2,2,2],[63,2,2,2],[47,1,1,1],[100,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Fledger.rb","name":"lib/acme_order/ledger.rb","kind":"file","parent":0,"entities":4,"roles":{"module":1,"type":1,"operation":2,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":5,"weight":27,"occurrences":9,"terms":[[45,16,2,2],[18,3,1,1],[23,3,1,1],[1,2,2,2],[63,2,2,2],[47,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Flegacy%2Fbroken.rb","name":"lib/acme_order/legacy/broken.rb","kind":"file","parent":0,"entities":6,"roles":{"module":1,"type":1,"operation":2,"attribute":0,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":9,"distinctDomain":8,"weight":30,"occurrences":12,"terms":[[46,9,2,2],[13,8,1,1],[29,3,1,1],[39,3,1,1],[1,2,2,2],[63,2,2,2],[32,1,1,1],[47,1,1,1],[67,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Flegacy%2Floader.rb","name":"lib/acme_order/legacy/loader.rb","kind":"file","parent":0,"entities":1,"roles":{"module":1,"type":0,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":4,"weight":12,"occurrences":5,"terms":[[52,8,1,1],[1,1,1,1],[46,1,1,1],[47,1,1,1],[63,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Fmoney.rb","name":"lib/acme_order/money.rb","kind":"file","parent":0,"entities":14,"roles":{"module":1,"type":1,"operation":1,"attribute":2,"parameter":5,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":4,"numeric":0},"distinct":9,"distinctDomain":8,"weight":37,"occurrences":15,"terms":[[56,16,2,2],[22,5,2,2],[16,4,1,1],[106,3,1,1],[1,2,2,2],[25,2,2,2],[63,2,2,2],[65,2,2,2],[47,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Forder.rb","name":"lib/acme_order/order.rb","kind":"file","parent":0,"entities":35,"roles":{"module":1,"type":2,"operation":6,"attribute":10,"parameter":13,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":2,"numeric":0},"distinct":23,"distinctDomain":18,"weight":120,"occurrences":51,"terms":[[63,26,12,6],[22,9,3,3],[48,9,2,2],[49,9,3,3],[1,6,6,6],[15,6,2,2],[84,6,3,3],[78,5,2,2],[92,5,2,2],[24,4,1,1],[54,4,1,1],[60,4,1,1],[75,4,1,1],[89,4,1,1],[2,3,1,1],[14,3,1,1],[72,3,1,1],[102,3,1,1],[12,2,2,2],[62,2,2,2],[47,1,1,1],[86,1,1,1],[88,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Fpoint.rb","name":"lib/acme_order/point.rb","kind":"file","parent":0,"entities":5,"roles":{"module":1,"type":1,"operation":0,"attribute":2,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":1,"numeric":0},"distinct":6,"distinctDomain":5,"weight":29,"occurrences":9,"terms":[[69,16,2,2],[44,4,1,1],[50,4,1,1],[1,2,2,2],[63,2,2,2],[47,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Fpriceable.rb","name":"lib/acme_order/priceable.rb","kind":"file","parent":0,"entities":4,"roles":{"module":1,"type":1,"operation":2,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":5,"weight":27,"occurrences":9,"terms":[[73,16,2,2],[72,3,1,1],[74,3,1,1],[1,2,2,2],[63,2,2,2],[47,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Fpricing%2Fpremium.rb","name":"lib/acme_order/pricing/premium.rb","kind":"file","parent":0,"entities":4,"roles":{"module":1,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":5,"weight":30,"occurrences":12,"terms":[[71,16,2,2],[31,3,1,1],[63,3,3,3],[72,3,1,1],[1,2,2,2],[75,2,2,2],[47,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Fpricing%2Fstandard.rb","name":"lib/acme_order/pricing/standard.rb","kind":"file","parent":0,"entities":4,"roles":{"module":1,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":5,"weight":30,"occurrences":12,"terms":[[96,16,2,2],[31,3,1,1],[63,3,3,3],[72,3,1,1],[1,2,2,2],[75,2,2,2],[47,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Fpromo%232024.rb","name":"lib/acme_order/promo#2024.rb","kind":"file","parent":0,"entities":2,"roles":{"module":1,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":1},"distinct":5,"distinctDomain":4,"weight":21,"occurrences":7,"terms":[[76,8,1,1],[77,8,1,1],[1,2,2,2],[63,2,2,2],[47,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Frailtie.rb","name":"lib/acme_order/railtie.rb","kind":"file","parent":0,"entities":3,"roles":{"module":1,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":6,"weight":39,"occurrences":9,"terms":[[1,9,2,2],[63,9,2,2],[38,8,1,1],[80,8,1,1],[40,3,1,1],[47,1,1,1],[79,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Frecord.rb","name":"lib/acme_order/record.rb","kind":"file","parent":0,"entities":4,"roles":{"module":1,"type":1,"operation":2,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":5,"weight":27,"occurrences":9,"terms":[[83,16,2,2],[5,3,1,1],[82,3,1,1],[1,2,2,2],[63,2,2,2],[47,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Freporting.rb","name":"lib/acme_order/reporting.rb","kind":"file","parent":0,"entities":8,"roles":{"module":1,"type":1,"operation":3,"attribute":0,"parameter":3,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":8,"distinctDomain":7,"weight":33,"occurrences":13,"terms":[[85,16,2,2],[63,4,4,4],[4,3,1,1],[98,3,1,1],[103,3,1,1],[1,2,2,2],[47,1,1,1],[84,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order%2Fversion.rb","name":"lib/acme_order/version.rb","kind":"file","parent":0,"entities":3,"roles":{"module":1,"type":1,"operation":0,"attribute":1,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":3,"weight":33,"occurrences":9,"terms":[[104,12,2,2],[1,10,3,3],[63,10,3,3],[47,1,1,1]]}, + {"level":"module","id":"rb:lib%2Facme_order.rb","name":"lib/acme_order.rb","kind":"file","parent":0,"entities":3,"roles":{"module":1,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":3,"weight":36,"occurrences":6,"terms":[[1,16,2,2],[63,16,2,2],[87,3,1,1],[47,1,1,1]]}, + {"level":"module","id":"rb:lib%2Ftasks%2Forders.rake","name":"lib/tasks/orders.rake","kind":"file","parent":0,"entities":1,"roles":{"module":1,"type":0,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":2,"weight":10,"occurrences":3,"terms":[[64,8,1,1],[47,1,1,1],[99,1,1,1]]}, + {"level":"module","id":"rb:spec%2Facme_order%2Forder_spec.rb","name":"spec/acme_order/order_spec.rb","kind":"file","parent":0,"entities":2,"roles":{"module":1,"type":0,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":2,"weight":25,"occurrences":7,"terms":[[63,12,3,2],[93,9,2,1],[14,3,1,1],[1,1,1,1]]}, + {"level":"module","id":"rb:spec%2Fspec_helper.rb","name":"spec/spec_helper.rb","kind":"file","parent":0,"entities":1,"roles":{"module":1,"type":0,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":0,"weight":17,"occurrences":3,"terms":[[93,9,2,1],[37,8,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fa_order_patch.rb/AcmeOrder::Order","name":"AcmeOrder::Order","kind":"class","parent":6,"entities":2,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":3,"weight":13,"occurrences":4,"terms":[[63,9,2,1],[6,3,1,1],[1,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fauditable.rb/AcmeOrder::Auditable","name":"AcmeOrder::Auditable","kind":"module","parent":7,"entities":2,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":4,"weight":13,"occurrences":4,"terms":[[7,8,1,1],[6,3,1,1],[1,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fauditable.rb/AcmeOrder::Auditable::ClassMethods","name":"AcmeOrder::Auditable::ClassMethods","kind":"module","parent":33,"entities":3,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":6,"weight":23,"occurrences":7,"terms":[[17,8,1,1],[55,8,1,1],[8,3,1,1],[1,1,1,1],[7,1,1,1],[58,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fauditable.rb/AcmeOrder::Stamped","name":"AcmeOrder::Stamped","kind":"module","parent":7,"entities":3,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":1,"numeric":0},"distinct":4,"distinctDomain":3,"weight":11,"occurrences":4,"terms":[[95,8,1,1],[1,1,1,1],[9,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fauditable.rb/AcmeOrder::Stamped::ClassMethods","name":"AcmeOrder::Stamped::ClassMethods","kind":"module","parent":35,"entities":3,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":6,"weight":23,"occurrences":7,"terms":[[17,8,1,1],[55,8,1,1],[94,3,1,1],[1,1,1,1],[57,1,1,1],[63,1,1,1],[95,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fbase.rb/AcmeOrder::Base","name":"AcmeOrder::Base","kind":"class","parent":8,"entities":2,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":1,"numeric":0},"distinct":3,"distinctDomain":2,"weight":10,"occurrences":3,"terms":[[9,8,1,1],[1,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fcore_ext%2Fstring.rb/String","name":"String","kind":"class","parent":9,"entities":2,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":11,"occurrences":2,"terms":[[97,8,1,1],[11,3,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fdynamic.rb/AcmeOrder::Dynamic","name":"AcmeOrder::Dynamic","kind":"module","parent":10,"entities":2,"roles":{"module":0,"type":1,"operation":0,"attribute":1,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":4,"weight":17,"occurrences":7,"terms":[[26,9,2,2],[36,4,1,1],[1,2,2,2],[63,2,2,2]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fdynamic.rb/AcmeOrder::Dynamic::Ghost","name":"AcmeOrder::Dynamic::Ghost","kind":"class","parent":39,"entities":3,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":6,"weight":17,"occurrences":6,"terms":[[35,8,1,1],[68,3,1,1],[70,3,1,1],[1,1,1,1],[26,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fdynamic.rb/AcmeOrder::Dynamic::Loader","name":"AcmeOrder::Dynamic::Loader","kind":"class","parent":39,"entities":2,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":4,"weight":14,"occurrences":5,"terms":[[52,8,1,1],[51,3,1,1],[1,1,1,1],[26,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fdynamic.rb/AcmeOrder::Dynamic::Odd","name":"AcmeOrder::Dynamic::Odd","kind":"class","parent":39,"entities":1,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":4,"weight":11,"occurrences":4,"terms":[[61,8,1,1],[1,1,1,1],[26,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fdynamic.rb/AcmeOrder::Dynamic::Wrapped","name":"AcmeOrder::Dynamic::Wrapped","kind":"class","parent":39,"entities":1,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":4,"weight":11,"occurrences":4,"terms":[[105,8,1,1],[1,1,1,1],[26,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Ferrors.rb/AcmeOrder::Error","name":"AcmeOrder::Error","kind":"class","parent":11,"entities":1,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":2,"weight":10,"occurrences":3,"terms":[[27,8,1,1],[1,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Ferrors.rb/AcmeOrder::NotFound","name":"AcmeOrder::NotFound","kind":"class","parent":11,"entities":1,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":3,"weight":18,"occurrences":4,"terms":[[33,8,1,1],[59,8,1,1],[1,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Ferrors.rb/AcmeOrder::TooManyLines","name":"AcmeOrder::TooManyLines","kind":"class","parent":11,"entities":3,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":1,"numeric":0},"distinct":6,"distinctDomain":6,"weight":27,"occurrences":6,"terms":[[49,8,1,1],[53,8,1,1],[101,8,1,1],[1,1,1,1],[54,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Finvoice.rb/AcmeOrder::Invoice","name":"AcmeOrder::Invoice","kind":"class","parent":12,"entities":5,"roles":{"module":0,"type":1,"operation":3,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":6,"weight":23,"occurrences":8,"terms":[[41,8,1,1],[90,6,2,2],[3,3,1,1],[14,3,1,1],[1,1,1,1],[42,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Flabel.rb/AcmeOrder::Label","name":"AcmeOrder::Label","kind":"class","parent":13,"entities":4,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":1,"numeric":0},"distinct":5,"distinctDomain":5,"weight":14,"occurrences":5,"terms":[[43,8,1,1],[91,3,1,1],[1,1,1,1],[63,1,1,1],[100,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fledger.rb/AcmeOrder::Ledger","name":"AcmeOrder::Ledger","kind":"class","parent":14,"entities":3,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":5,"weight":16,"occurrences":5,"terms":[[45,8,1,1],[18,3,1,1],[23,3,1,1],[1,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Flegacy%2Fbroken.rb/AcmeOrder::Legacy","name":"AcmeOrder::Legacy","kind":"class","parent":15,"entities":5,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":7,"weight":18,"occurrences":7,"terms":[[46,8,1,1],[29,3,1,1],[39,3,1,1],[1,1,1,1],[32,1,1,1],[63,1,1,1],[67,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fmoney.rb/AcmeOrder::Money","name":"AcmeOrder::Money","kind":"class","parent":17,"entities":13,"roles":{"module":0,"type":1,"operation":1,"attribute":2,"parameter":5,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":4,"numeric":0},"distinct":8,"distinctDomain":8,"weight":26,"occurrences":11,"terms":[[56,8,1,1],[22,5,2,2],[16,4,1,1],[106,3,1,1],[25,2,2,2],[65,2,2,2],[1,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Forder.rb/AcmeOrder::Order","name":"AcmeOrder::Order","kind":"class","parent":18,"entities":28,"roles":{"module":0,"type":1,"operation":6,"attribute":8,"parameter":11,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":1,"numeric":0},"distinct":20,"distinctDomain":16,"weight":88,"occurrences":39,"terms":[[63,15,8,4],[22,9,3,3],[49,9,3,3],[15,6,2,2],[84,6,3,3],[1,4,4,4],[24,4,1,1],[54,4,1,1],[60,4,1,1],[75,4,1,1],[89,4,1,1],[2,3,1,1],[14,3,1,1],[72,3,1,1],[102,3,1,1],[12,2,2,2],[62,2,2,2],[48,1,1,1],[86,1,1,1],[88,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Forder.rb/AcmeOrder::Order::Line","name":"AcmeOrder::Order::Line","kind":"class","parent":52,"entities":6,"roles":{"module":0,"type":1,"operation":0,"attribute":2,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":1,"numeric":0},"distinct":5,"distinctDomain":5,"weight":21,"occurrences":8,"terms":[[48,8,1,1],[78,5,2,2],[92,5,2,2],[63,2,2,1],[1,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fpoint.rb/AcmeOrder::Point","name":"AcmeOrder::Point","kind":"class","parent":19,"entities":4,"roles":{"module":0,"type":1,"operation":0,"attribute":2,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":1,"numeric":0},"distinct":5,"distinctDomain":5,"weight":18,"occurrences":5,"terms":[[69,8,1,1],[44,4,1,1],[50,4,1,1],[1,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fpriceable.rb/AcmeOrder::Priceable","name":"AcmeOrder::Priceable","kind":"module","parent":20,"entities":3,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":5,"weight":16,"occurrences":5,"terms":[[73,8,1,1],[72,3,1,1],[74,3,1,1],[1,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fpricing%2Fpremium.rb/AcmeOrder::Pricing::Premium","name":"AcmeOrder::Pricing::Premium","kind":"class","parent":21,"entities":3,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":5,"weight":18,"occurrences":7,"terms":[[71,8,1,1],[31,3,1,1],[72,3,1,1],[63,2,2,2],[1,1,1,1],[75,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fpricing%2Fstandard.rb/AcmeOrder::Pricing::Standard","name":"AcmeOrder::Pricing::Standard","kind":"class","parent":22,"entities":3,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":5,"weight":18,"occurrences":7,"terms":[[96,8,1,1],[31,3,1,1],[72,3,1,1],[63,2,2,2],[1,1,1,1],[75,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fpromo%232024.rb/AcmeOrder::Promo2024","name":"AcmeOrder::Promo2024","kind":"class","parent":23,"entities":1,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":3,"weight":10,"occurrences":3,"terms":[[77,8,1,1],[1,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Frailtie.rb/Rails::AcmeOrderHook","name":"Rails::AcmeOrderHook","kind":"class","parent":24,"entities":2,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":5,"weight":28,"occurrences":5,"terms":[[1,8,1,1],[38,8,1,1],[63,8,1,1],[40,3,1,1],[79,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Frecord.rb/AcmeOrder::Record","name":"AcmeOrder::Record","kind":"class","parent":25,"entities":3,"roles":{"module":0,"type":1,"operation":2,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":5,"weight":16,"occurrences":5,"terms":[[83,8,1,1],[5,3,1,1],[82,3,1,1],[1,1,1,1],[63,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Freporting.rb/AcmeOrder::Reporting","name":"AcmeOrder::Reporting","kind":"module","parent":26,"entities":7,"roles":{"module":0,"type":1,"operation":3,"attribute":0,"parameter":3,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":7,"weight":22,"occurrences":9,"terms":[[85,8,1,1],[4,3,1,1],[63,3,3,3],[98,3,1,1],[103,3,1,1],[1,1,1,1],[84,1,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order%2Fversion.rb/AcmeOrder","name":"AcmeOrder","kind":"module","parent":27,"entities":2,"roles":{"module":0,"type":1,"operation":0,"attribute":1,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":3,"weight":22,"occurrences":5,"terms":[[1,9,2,2],[63,9,2,2],[104,4,1,1]]}, + {"level":"type","id":"rb:lib%2Facme_order.rb/AcmeOrder","name":"AcmeOrder","kind":"module","parent":28,"entities":2,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":3,"weight":19,"occurrences":3,"terms":[[1,8,1,1],[63,8,1,1],[87,3,1,1]]} + ], + "diagnostics": {"unplaced":[],"withoutLexicon":[]} +} diff --git a/fixtures/typescript/README.md b/fixtures/typescript/README.md index 5646c1e..ae6c54d 100644 --- a/fixtures/typescript/README.md +++ b/fixtures/typescript/README.md @@ -75,3 +75,23 @@ edges : 127 (self-edges dropped: 1, indirect calls dropped: 2, computed imports Every dropped thing is counted: the model is a lower bound that says where it stopped, never a guess. + +## The vocabulary: `legacy`, counted by hand + +`expected/vocabulary.json` is the words of this model's names +(`@codegraph/vocabulary`, PLAN.md §20), rebuilt byte for byte by +`pnpm --filter @codegraph/vocabulary test` (`CODEGRAPH_UPDATE_SNAPSHOTS=1` +rewrites it). A word weighs the role of the entity whose name carries it — +module and type 8, attribute 4, operation 3, parameter and local 1 — except a +word of the name's qualifier, before the last separator of a qualified kind, +which weighs 1. One word, counted by hand from the model and pinned by the +vocabulary's tests: + +| name | kind | role | weight | +|---|---|---|---| +| `legacy-lib` | module | module | 8 | +| `legacy/acme-report.ts`, `legacy/acme.ts`, `packages/order/src/legacy/list.ts` | module | qualifier | 3 × 1 | + +**`legacy`: weight 11, 4 occurrences, 4 carriers.** A module is named by its +path: the directories qualify the file, so `packages` (21 modules) weighs 21, +not 168. diff --git a/fixtures/typescript/expected/vocabulary.json b/fixtures/typescript/expected/vocabulary.json new file mode 100644 index 0000000..6914b78 --- /dev/null +++ b/fixtures/typescript/expected/vocabulary.json @@ -0,0 +1,170 @@ +{ + "kind": "codegraph.vocabulary/1", + "generatedBy": "@codegraph/vocabulary", + "view": {"name":"all","filters":[]}, + "corpus": {"name":"src","roots":["fixtures/typescript/src"]}, + "langs": ["ts"], + "weights": {"module":8,"type":8,"operation":3,"attribute":4,"parameter":1,"local":1,"other":1,"qualifier":1}, + "classes": ["domain","technical","keyword","short"], + "technical": {"words":125,"sha256":"c16bd05be59a0c3281696359e4b4f7b3efe7860512b4ab37519f8c3a063c95f7"}, + "terms": [ + {"text":"a","class":"short","modules":1,"types":0,"exclusive":21}, + {"text":"abstract","class":"technical","modules":1,"types":1,"exclusive":4}, + {"text":"acme","class":"domain","modules":3,"types":1}, + {"text":"adapter","class":"technical","modules":1,"types":1,"exclusive":14}, + {"text":"add","class":"technical","modules":1,"types":1,"exclusive":7}, + {"text":"ambient","class":"domain","modules":1,"types":0,"exclusive":5}, + {"text":"amount","class":"domain","modules":3,"types":2}, + {"text":"apply","class":"domain","modules":1,"types":0,"exclusive":24}, + {"text":"as","class":"technical","modules":1,"types":1,"exclusive":16}, + {"text":"audited","class":"domain","modules":1,"types":0,"exclusive":9}, + {"text":"augment","class":"domain","modules":1,"types":0,"exclusive":6}, + {"text":"b","class":"short","modules":1,"types":0,"exclusive":21}, + {"text":"basket","class":"domain","modules":1,"types":1,"exclusive":7}, + {"text":"best","class":"domain","modules":1,"types":1,"exclusive":9}, + {"text":"bill","class":"domain","modules":1,"types":1,"exclusive":18}, + {"text":"both","class":"domain","modules":1,"types":0,"exclusive":17}, + {"text":"cents","class":"domain","modules":1,"types":2,"exclusive":16}, + {"text":"chain","class":"domain","modules":1,"types":0,"exclusive":17}, + {"text":"channel","class":"domain","modules":2,"types":2}, + {"text":"context","class":"technical","modules":1,"types":0,"exclusive":9}, + {"text":"count","class":"domain","modules":1,"types":1,"exclusive":7}, + {"text":"create","class":"technical","modules":1,"types":0,"exclusive":7}, + {"text":"currency","class":"domain","modules":1,"types":1,"exclusive":18}, + {"text":"d","class":"short","modules":1,"types":0,"exclusive":5}, + {"text":"decorators","class":"domain","modules":1,"types":0,"exclusive":9}, + {"text":"default","class":"keyword","modules":1,"types":0,"exclusive":11}, + {"text":"describe","class":"domain","modules":2,"types":1}, + {"text":"discount","class":"domain","modules":3,"types":3}, + {"text":"discountable","class":"domain","modules":1,"types":1,"exclusive":10}, + {"text":"dynamic","class":"domain","modules":1,"types":0,"exclusive":13}, + {"text":"ensure","class":"domain","modules":1,"types":0,"exclusive":21}, + {"text":"error","class":"technical","modules":1,"types":1,"exclusive":21}, + {"text":"extra","class":"domain","modules":1,"types":1,"exclusive":3}, + {"text":"first","class":"domain","modules":1,"types":0,"exclusive":17}, + {"text":"handler","class":"technical","modules":1,"types":0,"exclusive":11}, + {"text":"head","class":"domain","modules":1,"types":1,"exclusive":15}, + {"text":"high","class":"domain","modules":1,"types":1,"exclusive":8}, + {"text":"id","class":"technical","modules":2,"types":2}, + {"text":"index","class":"domain","modules":2,"types":0}, + {"text":"input","class":"domain","modules":1,"types":0,"exclusive":21}, + {"text":"items","class":"domain","modules":1,"types":1,"exclusive":2}, + {"text":"lazy","class":"domain","modules":1,"types":0,"exclusive":13}, + {"text":"ledger","class":"domain","modules":1,"types":1,"exclusive":14}, + {"text":"legacy","class":"domain","modules":4,"types":0}, + {"text":"length","class":"domain","modules":1,"types":1,"exclusive":15}, + {"text":"lib","class":"technical","modules":1,"types":0,"exclusive":3}, + {"text":"line","class":"domain","modules":2,"types":3}, + {"text":"lines","class":"domain","modules":2,"types":2}, + {"text":"list","class":"domain","modules":1,"types":1,"exclusive":15}, + {"text":"load","class":"technical","modules":2,"types":0}, + {"text":"log","class":"domain","modules":1,"types":0,"exclusive":17}, + {"text":"low","class":"domain","modules":1,"types":1,"exclusive":8}, + {"text":"max","class":"domain","modules":3,"types":2}, + {"text":"message","class":"domain","modules":2,"types":1}, + {"text":"money","class":"domain","modules":1,"types":1,"exclusive":16}, + {"text":"name","class":"domain","modules":1,"types":0,"exclusive":13}, + {"text":"note","class":"domain","modules":1,"types":1,"exclusive":22}, + {"text":"notifications","class":"domain","modules":1,"types":0,"exclusive":17}, + {"text":"on","class":"technical","modules":1,"types":0,"exclusive":17}, + {"text":"open","class":"domain","modules":1,"types":1,"exclusive":6}, + {"text":"ops","class":"domain","modules":1,"types":0,"exclusive":7}, + {"text":"order","class":"domain","modules":22,"types":4}, + {"text":"orders","class":"domain","modules":1,"types":0,"exclusive":21}, + {"text":"packages","class":"domain","modules":21,"types":0}, + {"text":"parse","class":"domain","modules":1,"types":1,"exclusive":18}, + {"text":"pct","class":"domain","modules":3,"types":3}, + {"text":"phone","class":"domain","modules":1,"types":1,"exclusive":8}, + {"text":"post","class":"domain","modules":1,"types":1,"exclusive":14}, + {"text":"price","class":"domain","modules":4,"types":4}, + {"text":"priceable","class":"domain","modules":1,"types":1,"exclusive":19}, + {"text":"priced","class":"domain","modules":1,"types":1,"exclusive":24}, + {"text":"pricing","class":"domain","modules":1,"types":0,"exclusive":24}, + {"text":"priority","class":"domain","modules":1,"types":1,"exclusive":8}, + {"text":"promo","class":"domain","modules":1,"types":0,"exclusive":20}, + {"text":"props","class":"domain","modules":1,"types":0,"exclusive":23}, + {"text":"rate","class":"domain","modules":1,"types":0,"exclusive":24}, + {"text":"reason","class":"domain","modules":1,"types":1,"exclusive":10}, + {"text":"reference","class":"domain","modules":2,"types":2}, + {"text":"registry","class":"domain","modules":1,"types":1,"exclusive":2}, + {"text":"reject","class":"domain","modules":1,"types":0,"exclusive":21}, + {"text":"render","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"report","class":"domain","modules":2,"types":2}, + {"text":"reporting","class":"domain","modules":2,"types":0}, + {"text":"row","class":"domain","modules":1,"types":0,"exclusive":23}, + {"text":"rows","class":"domain","modules":1,"types":0,"exclusive":23}, + {"text":"résumé","class":"domain","modules":1,"types":1,"exclusive":22}, + {"text":"second","class":"domain","modules":1,"types":0,"exclusive":17}, + {"text":"secret","class":"domain","modules":1,"types":1,"exclusive":16}, + {"text":"shipped","class":"domain","modules":1,"types":0,"exclusive":17}, + {"text":"shout","class":"domain","modules":1,"types":0,"exclusive":17}, + {"text":"src","class":"technical","modules":21,"types":0}, + {"text":"store","class":"domain","modules":1,"types":1,"exclusive":8}, + {"text":"table","class":"domain","modules":1,"types":0,"exclusive":23}, + {"text":"tag","class":"domain","modules":1,"types":0,"exclusive":9}, + {"text":"tail","class":"domain","modules":1,"types":1,"exclusive":15}, + {"text":"target","class":"domain","modules":1,"types":0,"exclusive":9}, + {"text":"tax","class":"domain","modules":1,"types":0,"exclusive":24}, + {"text":"text","class":"domain","modules":2,"types":2}, + {"text":"thing","class":"domain","modules":2,"types":2}, + {"text":"total","class":"domain","modules":2,"types":2}, + {"text":"ui","class":"domain","modules":1,"types":0,"exclusive":23}, + {"text":"value","class":"domain","modules":2,"types":2}, + {"text":"values","class":"domain","modules":1,"types":1,"exclusive":9}, + {"text":"verbose","class":"domain","modules":1,"types":0,"exclusive":9}, + {"text":"web","class":"domain","modules":1,"types":1,"exclusive":8}, + {"text":"window","class":"domain","modules":1,"types":1,"exclusive":6}, + {"text":"wrapper","class":"technical","modules":1,"types":1,"exclusive":6}, + {"text":"zero","class":"domain","modules":1,"types":1,"exclusive":16} + ], + "scopes": [ + {"level":"corpus","name":"src","entities":179,"roles":{"module":24,"type":22,"operation":37,"attribute":28,"parameter":39,"local":6,"other":4},"skipped":{"unnamed":18,"conventional":1,"numeric":1},"distinct":108,"distinctDomain":90,"weight":812,"occurrences":244,"terms":[[61,79,35,32],[2,30,6,6],[81,24,3,3],[18,21,4,4],[63,21,21,21],[90,21,21,21],[46,18,4,4],[1,16,2,2],[3,16,2,2],[12,16,2,2],[28,16,2,2],[38,16,2,2],[42,16,2,2],[48,16,2,2],[54,16,2,2],[69,16,2,2],[85,16,2,2],[16,13,3,3],[6,12,5,5],[47,12,3,3],[68,12,4,4],[73,12,2,2],[43,11,4,4],[52,11,3,3],[92,11,2,2],[27,9,3,3],[31,9,2,2],[82,9,2,2],[98,9,2,2],[5,8,1,1],[10,8,1,1],[23,8,1,1],[24,8,1,1],[34,8,1,1],[41,8,1,1],[45,8,1,1],[57,8,1,1],[70,8,1,1],[72,8,1,1],[78,8,1,1],[99,8,2,2],[105,8,1,1],[106,8,1,1],[96,7,2,2],[26,6,2,2],[37,6,3,3],[49,6,2,2],[64,6,2,2],[77,6,3,3],[22,4,1,1],[35,4,1,1],[36,4,1,1],[40,4,1,1],[51,4,1,1],[56,4,1,1],[58,4,1,1],[60,4,1,1],[66,4,1,1],[75,4,1,1],[76,4,1,1],[83,4,2,2],[87,4,1,1],[88,4,1,1],[89,4,1,1],[91,4,1,1],[94,4,1,1],[97,4,2,2],[104,4,1,1],[4,3,1,1],[7,3,1,1],[8,3,1,1],[9,3,1,1],[14,3,1,1],[15,3,1,1],[17,3,1,1],[20,3,1,1],[21,3,1,1],[25,3,1,1],[30,3,1,1],[32,3,1,1],[44,3,1,1],[50,3,1,1],[53,3,3,3],[59,3,1,1],[65,3,3,3],[67,3,1,1],[79,3,1,1],[80,3,1,1],[107,3,1,1],[101,2,2,2],[0,1,1,1],[11,1,1,1],[13,1,1,1],[19,1,1,1],[29,1,1,1],[33,1,1,1],[39,1,1,1],[55,1,1,1],[62,1,1,1],[71,1,1,1],[74,1,1,1],[84,1,1,1],[86,1,1,1],[93,1,1,1],[95,1,1,1],[100,1,1,1],[102,1,1,1],[103,1,1,1]]}, + {"level":"module","id":"ts:legacy%2Facme-report.ts","name":"legacy/acme-report.ts","kind":"module","parent":0,"entities":6,"roles":{"module":1,"type":2,"operation":1,"attribute":0,"parameter":0,"local":0,"other":2},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":6,"weight":38,"occurrences":8,"terms":[[81,16,2,2],[2,9,2,2],[46,8,1,1],[80,3,1,1],[43,1,1,1],[61,1,1,1]]}, + {"level":"module","id":"ts:legacy%2Facme.ts","name":"legacy/acme.ts","kind":"module","parent":0,"entities":5,"roles":{"module":1,"type":1,"operation":0,"attribute":1,"parameter":0,"local":0,"other":2},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":5,"weight":23,"occurrences":6,"terms":[[2,9,2,2],[78,8,1,1],[40,4,1,1],[43,1,1,1],[61,1,1,1]]}, + {"level":"module","id":"ts:legacy-lib","name":"legacy-lib","kind":"module","parent":0,"entities":7,"roles":{"module":1,"type":1,"operation":3,"attribute":1,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":4,"weight":38,"occurrences":8,"terms":[[43,8,1,1],[45,8,1,1],[98,8,1,1],[37,5,2,2],[26,3,1,1],[32,3,1,1],[49,3,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Fabstract-order.ts","name":"packages/order/src/abstract-order.ts","kind":"module","parent":0,"entities":9,"roles":{"module":1,"type":1,"operation":2,"attribute":2,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":9,"distinctDomain":7,"weight":51,"occurrences":13,"terms":[[61,17,3,2],[1,16,2,2],[77,5,2,2],[99,4,1,1],[27,3,1,1],[68,3,1,1],[63,1,1,1],[65,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Fambient.d.ts","name":"packages/order/src/ambient.d.ts","kind":"module","parent":0,"entities":1,"roles":{"module":1,"type":0,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":3,"weight":19,"occurrences":5,"terms":[[5,8,1,1],[23,8,1,1],[61,1,1,1],[63,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Faugment.ts","name":"packages/order/src/augment.ts","kind":"module","parent":0,"entities":7,"roles":{"module":1,"type":2,"operation":1,"attribute":1,"parameter":1,"local":1,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":10,"distinctDomain":7,"weight":44,"occurrences":11,"terms":[[2,12,2,2],[10,8,1,1],[105,8,1,1],[106,8,1,1],[59,3,1,1],[37,1,1,1],[61,1,1,1],[63,1,1,1],[90,1,1,1],[98,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Fbasket.ts","name":"packages/order/src/basket.ts","kind":"module","parent":0,"entities":18,"roles":{"module":1,"type":2,"operation":3,"attribute":5,"parameter":3,"local":0,"other":0},"skipped":{"unnamed":3,"conventional":1,"numeric":0},"distinct":13,"distinctDomain":10,"weight":63,"occurrences":18,"terms":[[12,16,2,2],[46,10,3,3],[47,8,2,2],[6,5,2,2],[52,4,1,1],[60,4,1,1],[99,4,1,1],[4,3,1,1],[20,3,1,1],[21,3,1,1],[61,1,1,1],[63,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Fchannel.ts","name":"packages/order/src/channel.ts","kind":"module","parent":0,"entities":8,"roles":{"module":1,"type":2,"operation":0,"attribute":5,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":10,"distinctDomain":9,"weight":47,"occurrences":11,"terms":[[18,16,2,2],[72,8,1,1],[36,4,1,1],[51,4,1,1],[66,4,1,1],[91,4,1,1],[104,4,1,1],[61,1,1,1],[63,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Fdecorators.ts","name":"packages/order/src/decorators.ts","kind":"module","parent":0,"entities":12,"roles":{"module":1,"type":1,"operation":2,"attribute":0,"parameter":5,"local":2,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":14,"distinctDomain":12,"weight":32,"occurrences":14,"terms":[[24,8,1,1],[81,8,1,1],[9,3,1,1],[52,3,1,1],[13,1,1,1],[19,1,1,1],[61,1,1,1],[63,1,1,1],[90,1,1,1],[93,1,1,1],[95,1,1,1],[101,1,1,1],[102,1,1,1],[103,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Fdiscountable.ts","name":"packages/order/src/discountable.ts","kind":"module","parent":0,"entities":5,"roles":{"module":1,"type":1,"operation":1,"attribute":1,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":6,"weight":27,"occurrences":8,"terms":[[28,16,2,2],[76,4,1,1],[27,3,1,1],[61,1,1,1],[63,1,1,1],[65,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Fhandler.ts","name":"packages/order/src/handler.ts","kind":"module","parent":0,"entities":3,"roles":{"module":1,"type":0,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":2,"weight":15,"occurrences":6,"terms":[[34,8,1,1],[25,3,1,1],[61,2,2,2],[63,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Findex.ts","name":"packages/order/src/index.ts","kind":"module","parent":0,"entities":1,"roles":{"module":1,"type":0,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":3,"weight":11,"occurrences":4,"terms":[[38,8,1,1],[61,1,1,1],[63,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Flazy.ts","name":"packages/order/src/lazy.ts","kind":"module","parent":0,"entities":5,"roles":{"module":1,"type":0,"operation":1,"attribute":0,"parameter":1,"local":2,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":8,"distinctDomain":6,"weight":17,"occurrences":8,"terms":[[41,8,1,1],[49,3,1,1],[29,1,1,1],[55,1,1,1],[61,1,1,1],[63,1,1,1],[82,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Fledger-adapter.ts","name":"packages/order/src/ledger-adapter.ts","kind":"module","parent":0,"entities":4,"roles":{"module":1,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":4,"weight":39,"occurrences":9,"terms":[[3,16,2,2],[42,16,2,2],[67,3,1,1],[61,2,2,2],[63,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Flegacy%2Flist.ts","name":"packages/order/src/legacy/list.ts","kind":"module","parent":0,"entities":5,"roles":{"module":1,"type":1,"operation":1,"attribute":2,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":8,"distinctDomain":7,"weight":31,"occurrences":9,"terms":[[48,16,2,2],[35,4,1,1],[94,4,1,1],[44,3,1,1],[43,1,1,1],[61,1,1,1],[63,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Fmoney.ts","name":"packages/order/src/money.ts","kind":"module","parent":0,"entities":13,"roles":{"module":1,"type":2,"operation":5,"attribute":2,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":12,"distinctDomain":10,"weight":55,"occurrences":16,"terms":[[54,16,2,2],[16,13,3,3],[6,6,2,2],[87,4,1,1],[8,3,1,1],[68,3,1,1],[97,3,1,1],[107,3,1,1],[61,1,1,1],[63,1,1,1],[90,1,1,1],[101,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Fnotifications.ts","name":"packages/order/src/notifications.ts","kind":"module","parent":0,"entities":18,"roles":{"module":1,"type":0,"operation":3,"attribute":2,"parameter":6,"local":0,"other":0},"skipped":{"unnamed":6,"conventional":0,"numeric":0},"distinct":13,"distinctDomain":11,"weight":38,"occurrences":16,"terms":[[57,8,1,1],[58,4,1,1],[88,4,1,1],[89,4,1,1],[15,3,1,1],[17,3,1,1],[50,3,1,1],[61,3,3,3],[53,2,2,2],[33,1,1,1],[63,1,1,1],[86,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Forder.ts","name":"packages/order/src/order.ts","kind":"module","parent":0,"entities":14,"roles":{"module":1,"type":1,"operation":4,"attribute":3,"parameter":4,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":13,"distinctDomain":12,"weight":51,"occurrences":17,"terms":[[61,17,3,2],[64,6,2,2],[18,5,2,2],[22,4,1,1],[47,4,1,1],[52,4,1,1],[14,3,1,1],[27,3,1,1],[63,1,1,1],[65,1,1,1],[77,1,1,1],[90,1,1,1],[97,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Fpriceable.ts","name":"packages/order/src/priceable.ts","kind":"module","parent":0,"entities":3,"roles":{"module":1,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":4,"weight":22,"occurrences":6,"terms":[[69,16,2,2],[68,3,1,1],[61,1,1,1],[63,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Fpromo%232024.ts","name":"packages/order/src/promo#2024.ts","kind":"module","parent":0,"entities":2,"roles":{"module":1,"type":0,"operation":0,"attribute":1,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":1},"distinct":4,"distinctDomain":3,"weight":15,"occurrences":5,"terms":[[73,12,2,2],[61,1,1,1],[63,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Freporting.ts","name":"packages/order/src/reporting.ts","kind":"module","parent":0,"entities":17,"roles":{"module":1,"type":1,"operation":3,"attribute":0,"parameter":7,"local":1,"other":0},"skipped":{"unnamed":4,"conventional":0,"numeric":0},"distinct":13,"distinctDomain":9,"weight":44,"occurrences":17,"terms":[[61,11,4,4],[31,9,2,2],[82,8,1,1],[26,3,1,1],[30,3,1,1],[79,3,1,1],[0,1,1,1],[11,1,1,1],[39,1,1,1],[53,1,1,1],[62,1,1,1],[63,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Frésumé.ts","name":"packages/order/src/résumé.ts","kind":"module","parent":0,"entities":3,"roles":{"module":1,"type":1,"operation":0,"attribute":1,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":4,"weight":23,"occurrences":6,"terms":[[85,16,2,2],[56,4,1,1],[61,1,1,1],[63,1,1,1],[90,1,1,1]]}, + {"level":"module","id":"ts:packages%2Forder%2Fsrc%2Fui%2Forder-table.tsx","name":"packages/order/src/ui/order-table.tsx","kind":"module","parent":0,"entities":7,"roles":{"module":1,"type":0,"operation":2,"attribute":0,"parameter":3,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":8,"distinctDomain":7,"weight":32,"occurrences":12,"terms":[[61,12,3,2],[92,11,2,2],[83,4,2,2],[63,1,1,1],[74,1,1,1],[84,1,1,1],[90,1,1,1],[100,1,1,1]]}, + {"level":"module","id":"ts:packages%2Fpricing%2Fsrc%2Findex.ts","name":"packages/pricing/src/index.ts","kind":"module","parent":0,"entities":6,"roles":{"module":1,"type":1,"operation":2,"attribute":1,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":10,"distinctDomain":9,"weight":37,"occurrences":11,"terms":[[38,8,1,1],[70,8,1,1],[96,7,2,2],[75,4,1,1],[7,3,1,1],[68,3,1,1],[6,1,1,1],[63,1,1,1],[71,1,1,1],[90,1,1,1]]}, + {"level":"type","id":"ts:legacy%2Facme-report.ts/Acme.Order.Line","name":"Line","kind":"typeAlias","parent":1,"entities":1,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":1,"distinctDomain":1,"weight":8,"occurrences":1,"terms":[[46,8,1,1]]}, + {"level":"type","id":"ts:legacy%2Facme-report.ts/Acme.Order.Report","name":"Report","kind":"class","parent":1,"entities":2,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":11,"occurrences":2,"terms":[[81,8,1,1],[80,3,1,1]]}, + {"level":"type","id":"ts:legacy%2Facme.ts/Acme.Order.Registry","name":"Registry","kind":"class","parent":2,"entities":2,"roles":{"module":0,"type":1,"operation":0,"attribute":1,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":12,"occurrences":2,"terms":[[78,8,1,1],[40,4,1,1]]}, + {"level":"type","id":"ts:legacy-lib/Thing","name":"Thing","kind":"class","parent":3,"entities":4,"roles":{"module":0,"type":1,"operation":2,"attribute":1,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":3,"weight":18,"occurrences":4,"terms":[[98,8,1,1],[37,4,1,1],[26,3,1,1],[32,3,1,1]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Fabstract-order.ts/AbstractOrder","name":"AbstractOrder","kind":"abstractClass","parent":4,"entities":8,"roles":{"module":0,"type":1,"operation":2,"attribute":2,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":7,"distinctDomain":6,"weight":32,"occurrences":8,"terms":[[1,8,1,1],[61,8,1,1],[77,5,2,2],[99,4,1,1],[27,3,1,1],[68,3,1,1],[65,1,1,1]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Faugment.ts/AcmeWindow","name":"AcmeWindow","kind":"interface","parent":6,"entities":2,"roles":{"module":0,"type":1,"operation":0,"attribute":1,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":20,"occurrences":3,"terms":[[2,12,2,2],[105,8,1,1]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Faugment.ts/Wrapper","name":"Wrapper","kind":"class","parent":6,"entities":4,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":1,"local":1,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":2,"weight":13,"occurrences":4,"terms":[[106,8,1,1],[59,3,1,1],[37,1,1,1],[98,1,1,1]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Fbasket.ts/Basket","name":"Basket","kind":"class","parent":7,"entities":8,"roles":{"module":0,"type":1,"operation":2,"attribute":2,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":5,"weight":24,"occurrences":7,"terms":[[12,8,1,1],[47,4,1,1],[99,4,1,1],[4,3,1,1],[20,3,1,1],[46,2,2,2]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Fbasket.ts/Line","name":"Line","kind":"class","parent":7,"entities":4,"roles":{"module":0,"type":1,"operation":0,"attribute":1,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":13,"occurrences":3,"terms":[[46,8,1,1],[6,5,2,2]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Fchannel.ts/Channel","name":"Channel","kind":"enum","parent":8,"entities":4,"roles":{"module":0,"type":1,"operation":0,"attribute":3,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":4,"weight":20,"occurrences":4,"terms":[[18,8,1,1],[66,4,1,1],[91,4,1,1],[104,4,1,1]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Fchannel.ts/Priority","name":"Priority","kind":"enum","parent":8,"entities":3,"roles":{"module":0,"type":1,"operation":0,"attribute":2,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":3,"weight":16,"occurrences":3,"terms":[[72,8,1,1],[36,4,1,1],[51,4,1,1]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Fdecorators.ts/Report","name":"Report","kind":"class","parent":9,"entities":5,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":1,"local":2,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":5,"distinctDomain":5,"weight":14,"occurrences":5,"terms":[[81,8,1,1],[52,3,1,1],[13,1,1,1],[101,1,1,1],[102,1,1,1]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Fdiscountable.ts/Discountable","name":"Discountable","kind":"interface","parent":10,"entities":4,"roles":{"module":0,"type":1,"operation":1,"attribute":1,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":4,"weight":16,"occurrences":4,"terms":[[28,8,1,1],[76,4,1,1],[27,3,1,1],[65,1,1,1]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Fledger-adapter.ts/LedgerAdapter","name":"LedgerAdapter","kind":"class","parent":14,"entities":3,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":3,"weight":20,"occurrences":4,"terms":[[3,8,1,1],[42,8,1,1],[67,3,1,1],[61,1,1,1]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Flegacy%2Flist.ts/List","name":"List","kind":"class","parent":15,"entities":4,"roles":{"module":0,"type":1,"operation":1,"attribute":2,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":4,"distinctDomain":4,"weight":19,"occurrences":4,"terms":[[48,8,1,1],[35,4,1,1],[94,4,1,1],[44,3,1,1]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Fmoney.ts/Cents","name":"Cents","kind":"typeAlias","parent":16,"entities":1,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":1,"distinctDomain":1,"weight":8,"occurrences":1,"terms":[[16,8,1,1]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Fmoney.ts/Money","name":"Money","kind":"class","parent":16,"entities":11,"roles":{"module":0,"type":1,"operation":5,"attribute":2,"parameter":2,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":9,"distinctDomain":8,"weight":36,"occurrences":11,"terms":[[54,8,1,1],[6,6,2,2],[16,5,2,2],[87,4,1,1],[8,3,1,1],[68,3,1,1],[97,3,1,1],[107,3,1,1],[101,1,1,1]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Forder.ts/Order","name":"Order","kind":"class","parent":18,"entities":13,"roles":{"module":0,"type":1,"operation":4,"attribute":3,"parameter":4,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":11,"distinctDomain":11,"weight":40,"occurrences":13,"terms":[[61,8,1,1],[64,6,2,2],[18,5,2,2],[22,4,1,1],[47,4,1,1],[52,4,1,1],[14,3,1,1],[27,3,1,1],[65,1,1,1],[77,1,1,1],[97,1,1,1]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Fpriceable.ts/Priceable","name":"Priceable","kind":"interface","parent":19,"entities":2,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":11,"occurrences":2,"terms":[[69,8,1,1],[68,3,1,1]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Freporting.ts/OrderError","name":"OrderError","kind":"class","parent":21,"entities":3,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":1,"local":0,"other":0},"skipped":{"unnamed":1,"conventional":0,"numeric":0},"distinct":3,"distinctDomain":2,"weight":17,"occurrences":3,"terms":[[31,8,1,1],[61,8,1,1],[53,1,1,1]]}, + {"level":"type","id":"ts:packages%2Forder%2Fsrc%2Frésumé.ts/Résumé","name":"Résumé","kind":"class","parent":22,"entities":2,"roles":{"module":0,"type":1,"operation":0,"attribute":1,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":12,"occurrences":2,"terms":[[85,8,1,1],[56,4,1,1]]}, + {"level":"type","id":"ts:packages%2Fpricing%2Fsrc%2Findex.ts/Priced","name":"Priced","kind":"interface","parent":24,"entities":2,"roles":{"module":0,"type":1,"operation":1,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":2,"weight":11,"occurrences":2,"terms":[[70,8,1,1],[68,3,1,1]]} + ], + "diagnostics": {"unplaced":[],"withoutLexicon":[]} +} diff --git a/fixtures/unicode/README.md b/fixtures/unicode/README.md index cc328fb..128a1ce 100644 --- a/fixtures/unicode/README.md +++ b/fixtures/unicode/README.md @@ -43,3 +43,8 @@ than the model's. to the storage engine.** A query that needs canonical order must sort in TypeScript with `compareIds`, or `ORDER BY` a column the importer wrote in canonical order — never rely on SQLite's own collation to reproduce it. + +`expected/vocabulary.json` pins the same order on WORDS: the terms are sorted by +code unit, so `𠀀` (the short word of `𠀀Supplementary`) comes before `a` (of +`AFullwidth`), and the type scopes run `Ascii`, `𠀀Supplementary`, +`AFullwidth` — the vocabulary sorts in TypeScript, like everything else. diff --git a/fixtures/unicode/expected/vocabulary.json b/fixtures/unicode/expected/vocabulary.json new file mode 100644 index 0000000..f519bf3 --- /dev/null +++ b/fixtures/unicode/expected/vocabulary.json @@ -0,0 +1,26 @@ +{ + "kind": "codegraph.vocabulary/1", + "generatedBy": "@codegraph/vocabulary", + "view": {"name":"all","filters":[]}, + "corpus": {"name":"src","roots":["fixtures/unicode/src"]}, + "langs": ["java"], + "weights": {"module":8,"type":8,"operation":3,"attribute":4,"parameter":1,"local":1,"other":1,"qualifier":1}, + "classes": ["domain","technical","keyword","short"], + "technical": {"words":125,"sha256":"c16bd05be59a0c3281696359e4b4f7b3efe7860512b4ab37519f8c3a063c95f7"}, + "terms": [ + {"text":"ascii","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"fullwidth","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"sorting","class":"domain","modules":1,"types":0,"exclusive":1}, + {"text":"supplementary","class":"domain","modules":1,"types":1,"exclusive":1}, + {"text":"𠀀","class":"short","modules":1,"types":1,"exclusive":1}, + {"text":"a","class":"short","modules":1,"types":1,"exclusive":1} + ], + "scopes": [ + {"level":"corpus","name":"src","entities":4,"roles":{"module":1,"type":3,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":4,"weight":48,"occurrences":6,"terms":[[0,8,1,1],[1,8,1,1],[2,8,1,1],[3,8,1,1],[4,8,1,1],[5,8,1,1]]}, + {"level":"module","id":"java:sorting","name":"sorting","kind":"package","parent":0,"entities":4,"roles":{"module":1,"type":3,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":6,"distinctDomain":4,"weight":48,"occurrences":6,"terms":[[0,8,1,1],[1,8,1,1],[2,8,1,1],[3,8,1,1],[4,8,1,1],[5,8,1,1]]}, + {"level":"type","id":"java:sorting/Ascii","name":"Ascii","kind":"class","parent":1,"entities":1,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":1,"distinctDomain":1,"weight":8,"occurrences":1,"terms":[[0,8,1,1]]}, + {"level":"type","id":"java:sorting/𠀀Supplementary","name":"𠀀Supplementary","kind":"class","parent":1,"entities":1,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":1,"weight":16,"occurrences":2,"terms":[[3,8,1,1],[4,8,1,1]]}, + {"level":"type","id":"java:sorting/AFullwidth","name":"AFullwidth","kind":"class","parent":1,"entities":1,"roles":{"module":0,"type":1,"operation":0,"attribute":0,"parameter":0,"local":0,"other":0},"skipped":{"unnamed":0,"conventional":0,"numeric":0},"distinct":2,"distinctDomain":1,"weight":16,"occurrences":2,"terms":[[1,8,1,1],[5,8,1,1]]} + ], + "diagnostics": {"unplaced":[],"withoutLexicon":[]} +} diff --git a/packages/cli/src/commands/profiles.ts b/packages/cli/src/commands/profiles.ts index acc4621..87b1314 100644 --- a/packages/cli/src/commands/profiles.ts +++ b/packages/cli/src/commands/profiles.ts @@ -199,6 +199,10 @@ function lexiconLines(lexicon: Lexicon | undefined): readonly string[] { ]; if (lexicon.technical !== undefined) lines.push(` technical ${list([...lexicon.technical])}`); if (lexicon.fileKinds !== undefined) lines.push(` file kinds ${list([...lexicon.fileKinds])}`); + if (lexicon.qualified !== undefined) { + const kinds = Object.entries(lexicon.qualified).map(([kind, separator]) => `${kind} ${separator}`); + lines.push(` qualified ${list(kinds)}`); + } if (lexicon.interfacePrefix === true) lines.push(" interface prefix yes"); return lines; } @@ -245,6 +249,7 @@ function canonicalLexicon(lexicon: Lexicon): Record { }; if (lexicon.technical !== undefined) out["technical"] = lexicon.technical; if (lexicon.fileKinds !== undefined) out["fileKinds"] = lexicon.fileKinds; + if (lexicon.qualified !== undefined) out["qualified"] = lexicon.qualified; if (lexicon.interfacePrefix !== undefined) out["interfacePrefix"] = lexicon.interfacePrefix; return out; } diff --git a/packages/cli/test/profiles.test.ts b/packages/cli/test/profiles.test.ts index ea9d081..14603a0 100644 --- a/packages/cli/test/profiles.test.ts +++ b/packages/cli/test/profiles.test.ts @@ -116,6 +116,9 @@ describe("one language's full spec", () => { ` conventional ${lexicon.conventional.length === 0 ? "(none)" : lexicon.conventional.join(", ")}`, ...(lexicon.technical === undefined ? [] : [` technical ${lexicon.technical.join(", ")}`]), ...(lexicon.fileKinds === undefined ? [] : [` file kinds ${lexicon.fileKinds.join(", ")}`]), + ...(lexicon.qualified === undefined + ? [] + : [` qualified ${Object.entries(lexicon.qualified).map(([kind, sep]) => `${kind} ${sep}`).join(", ")}`]), ...(lexicon.interfacePrefix === true ? [" interface prefix yes"] : []), ]; expect(start, "no lexicon section").toBeGreaterThan(0); @@ -142,6 +145,14 @@ describe("one language's full spec", () => { expect(invoke(["profiles", "--lang", "java"]).io.stdout()).not.toContain("file kinds"); }); + it("prints each qualified kind with its separator, only where a profile declares them", () => { + expect(invoke(["profiles", "--lang", "rb"]).io.stdout()).toContain( + "\n qualified file /, class ::, module ::, constant ::\n", + ); + expect(invoke(["profiles", "--lang", "php"]).io.stdout()).toContain("\n qualified codeFile /, namespace \\\n"); + expect(invoke(["profiles", "--lang", "go"]).io.stdout()).not.toContain("qualified"); + }); + it("prints the declaration spaces exactly where a profile declares them", () => { const ts = invoke(["profiles", "--lang", "ts"]).io.stdout(); expect(ts).toContain("space"); @@ -192,8 +203,9 @@ describe("--json prints the profile data as-is", () => { const [json] = payload(["profiles", "--lang", lang]) as readonly Profile[]; return Object.keys(json?.lexicon ?? {}); }; - expect(keysOf("csharp")).toEqual(["keywords", "conventional", "technical", "interfacePrefix"]); - expect(keysOf("ts")).toEqual(["keywords", "conventional", "technical", "fileKinds"]); + expect(keysOf("csharp")).toEqual(["keywords", "conventional", "technical", "qualified", "interfacePrefix"]); + expect(keysOf("ts")).toEqual(["keywords", "conventional", "technical", "fileKinds", "qualified"]); + expect(keysOf("go")).toEqual(["keywords", "conventional", "technical"]); }); it("says the same thing as the text form (decision 8)", () => { diff --git a/packages/core/src/profile.ts b/packages/core/src/profile.ts index 5630654..d230c94 100644 --- a/packages/core/src/profile.ts +++ b/packages/core/src/profile.ts @@ -59,6 +59,13 @@ export interface Lexicon { readonly technical?: readonly string[] | undefined; /** Kinds whose name is a file path (`src/order.ts`): the extension is not a word. */ readonly fileKinds?: readonly string[] | undefined; + /** + * Kinds the language names by a qualified path, each with the separator that + * ends a qualifier: a Ruby class `AcmeOrder::Order::Line`, a file + * `lib/order.rb`. What precedes the last separator names a container, so its + * words weigh as incidental ones; they are still words. + */ + readonly qualified?: Readonly> | undefined; /** C#'s convention: `IOrderService` names an interface `OrderService`; the `I` is a marker, not a word. */ readonly interfacePrefix?: true | undefined; } @@ -69,6 +76,7 @@ export const LexiconSchema = z.object({ conventional: z.array(z.string()), technical: z.array(z.string()).optional(), fileKinds: z.array(z.string()).optional(), + qualified: z.record(z.string(), z.string()).optional(), interfacePrefix: z.literal(true).optional(), }); @@ -107,6 +115,7 @@ export const VALIDATION_CODES = [ "lexicon-not-a-word", "lexicon-keyword-overlap", "lexicon-unknown-kind", + "lexicon-not-a-separator", ] as const; export type ValidationCode = (typeof VALIDATION_CODES)[number]; @@ -467,11 +476,13 @@ export function validateProfile(profile: Profile): ValidationIssue[] { const SPLITTER_TOKEN = /^[\p{L}\p{M}\p{N}]+$/u; const LETTER = /\p{L}/u; const WHITESPACE = /\s/u; +const WORD_CHARACTER = /[\p{L}\p{M}\p{N}]/u; /** * An entry that could never do what its list says: listed twice, a keyword no * word can equal, a technical word the splitter cannot yield, a name the - * keyword class would claim first, a file kind the profile never declares. + * keyword class would claim first, a kind the profile never declares, a + * separator that would cut a word. */ function lexiconIssues(lexicon: Lexicon, kinds: Profile["kinds"]): ValidationIssue[] { const issues: ValidationIssue[] = []; @@ -551,6 +562,22 @@ function lexiconIssues(lexicon: Lexicon, kinds: Profile["kinds"]): ValidationIss }); } } + for (const [kind, separator] of Object.entries(lexicon.qualified ?? {})) { + if (!Object.hasOwn(kinds, kind)) { + issues.push({ + code: "lexicon-unknown-kind", + path: "lexicon.qualified", + message: `qualified kind "${kind}" is not a kind the profile defines`, + }); + } + if (separator === "" || WORD_CHARACTER.test(separator)) { + issues.push({ + code: "lexicon-not-a-separator", + path: "lexicon.qualified", + message: `${JSON.stringify(separator)} cannot end a qualifier: a separator is non-empty and holds no letter, mark or digit`, + }); + } + } return issues; } diff --git a/packages/core/src/profiles/clojure.ts b/packages/core/src/profiles/clojure.ts index 8e054c9..17dcbae 100644 --- a/packages/core/src/profiles/clojure.ts +++ b/packages/core/src/profiles/clojure.ts @@ -141,5 +141,6 @@ export const clojureProfile: Profile = { technical: [ "coll", "kw", "ns", "seq", "sym", "xf", "xform", ], + qualified: { namespace: "." }, }, }; diff --git a/packages/core/src/profiles/csharp.ts b/packages/core/src/profiles/csharp.ts index 5ab8482..3a15484 100644 --- a/packages/core/src/profiles/csharp.ts +++ b/packages/core/src/profiles/csharp.ts @@ -248,6 +248,7 @@ export const csharpProfile: Profile = { technical: [ "attribute", "enumerable", "enumerator", "extension", "extensions", "linq", ], + qualified: { namespace: "." }, interfacePrefix: true, }, }; diff --git a/packages/core/src/profiles/elixir.ts b/packages/core/src/profiles/elixir.ts index 0e884ae..a3873b8 100644 --- a/packages/core/src/profiles/elixir.ts +++ b/packages/core/src/profiles/elixir.ts @@ -159,5 +159,6 @@ export const elixirProfile: Profile = { "phoenix", "pid", "plug", "schema", "server", "socket", "sup", "supervisor", "web", ], fileKinds: ["file"], + qualified: { file: "/", module: ".", protocol: "." }, }, }; diff --git a/packages/core/src/profiles/java.ts b/packages/core/src/profiles/java.ts index 403f516..c7fb826 100644 --- a/packages/core/src/profiles/java.ts +++ b/packages/core/src/profiles/java.ts @@ -206,5 +206,6 @@ export const javaProfile: Profile = { technical: [ "annotation", "bean", "beans", "ejb", "jaxb", "jdbc", "jndi", "jpa", "pojo", "servlet", ], + qualified: { package: "." }, }, }; diff --git a/packages/core/src/profiles/javascript.ts b/packages/core/src/profiles/javascript.ts index 015227e..1b28e47 100644 --- a/packages/core/src/profiles/javascript.ts +++ b/packages/core/src/profiles/javascript.ts @@ -113,5 +113,5 @@ export const javascriptProfile: Profile = { ], // The module is the file: its name is a path whose extension is not a word. - lexicon: { ...ECMASCRIPT_LEXICON, fileKinds: ["module"] }, + lexicon: { ...ECMASCRIPT_LEXICON, fileKinds: ["module"], qualified: { module: "/" } }, }; diff --git a/packages/core/src/profiles/php.ts b/packages/core/src/profiles/php.ts index 7dbf31f..7a975bd 100644 --- a/packages/core/src/profiles/php.ts +++ b/packages/core/src/profiles/php.ts @@ -151,5 +151,6 @@ export const phpProfile: Profile = { "arr", "closure", "facade", "middleware", "php", ], fileKinds: ["codeFile"], + qualified: { codeFile: "/", namespace: "\\" }, }, }; diff --git a/packages/core/src/profiles/python.ts b/packages/core/src/profiles/python.ts index a749f65..dad09e5 100644 --- a/packages/core/src/profiles/python.ts +++ b/packages/core/src/profiles/python.ts @@ -133,5 +133,6 @@ export const pythonProfile: Profile = { technical: [ "dict", "kwargs", "metaclass", "py", "tuple", ], + qualified: { module: "." }, }, }; diff --git a/packages/core/src/profiles/ruby.ts b/packages/core/src/profiles/ruby.ts index 48f6239..c9aee01 100644 --- a/packages/core/src/profiles/ruby.ts +++ b/packages/core/src/profiles/ruby.ts @@ -136,5 +136,6 @@ export const rubyProfile: Profile = { "proc", "rake", "rakefile", ], fileKinds: ["file"], + qualified: { file: "/", class: "::", module: "::", constant: "::" }, }, }; diff --git a/packages/core/src/profiles/typescript.ts b/packages/core/src/profiles/typescript.ts index 9d3aad9..85c80ad 100644 --- a/packages/core/src/profiles/typescript.ts +++ b/packages/core/src/profiles/typescript.ts @@ -249,5 +249,5 @@ export const typescriptProfile: Profile = { ], // The module is the file: its name is a path whose extension is not a word. - lexicon: { ...ECMASCRIPT_LEXICON, fileKinds: ["module"] }, + lexicon: { ...ECMASCRIPT_LEXICON, fileKinds: ["module"], qualified: { module: "/" } }, }; diff --git a/packages/core/test/profile.test.ts b/packages/core/test/profile.test.ts index cf35586..b4eb8b3 100644 --- a/packages/core/test/profile.test.ts +++ b/packages/core/test/profile.test.ts @@ -422,6 +422,28 @@ describe("validateProfile — the lexicon", () => { expect(codes(issues)).toEqual(["lexicon-unknown-kind"]); expect(issues[0]?.path).toBe("lexicon.fileKinds"); }); + + it("accepts a qualified kind whose separator holds no word character", () => { + for (const separator of [".", "::", "/", "\\"]) { + expect(validateProfile(withLexicon({ ...base, qualified: { class: separator } })), separator).toEqual([]); + } + }); + + it("rejects a qualified kind the profile does not declare", () => { + const issues = validateProfile(withLexicon({ ...base, qualified: { module: "." } })); + expect(codes(issues)).toEqual(["lexicon-unknown-kind"]); + expect(issues[0]?.path).toBe("lexicon.qualified"); + }); + + // A separator is where a qualifier ends: one holding a letter or a digit + // would cut a word in two, and an empty one would end the qualifier anywhere. + it("rejects a separator that is empty or holds a letter, a mark or a digit", () => { + for (const separator of ["", "a", "::x", "1", "\u0301"]) { + const issues = validateProfile(withLexicon({ ...base, qualified: { class: separator } })); + expect(codes(issues), JSON.stringify(separator)).toEqual(["lexicon-not-a-separator"]); + expect(issues[0]?.path).toBe("lexicon.qualified"); + } + }); }); describe("ProfileSchema mirrors Profile", () => { @@ -436,6 +458,7 @@ describe("ProfileSchema mirrors Profile", () => { technical: ["impl"], interfacePrefix: true, fileKinds: ["class"], + qualified: { class: "::" }, }, }; expect(ProfileSchema.parse(full)).toEqual(full); diff --git a/packages/core/test/profiles.test.ts b/packages/core/test/profiles.test.ts index af8cf33..88f7015 100644 --- a/packages/core/test/profiles.test.ts +++ b/packages/core/test/profiles.test.ts @@ -842,4 +842,31 @@ describe("the lexicon of every shipped profile", () => { ts: ["module"], }); }); + + // A kind is qualified when the LANGUAGE names it by a path: a Java package is + // `com.acme.order`, a Ruby class `AcmeOrder::Order::Line`, an Elixir module + // `AcmeOrder.Pricing`. Go's package and Rust's module are named by one + // identifier (`package order`, `mod line`), their paths being something else. + it("declares exactly the kinds a language names by a qualified path", () => { + const qualified = Object.fromEntries( + profiles.flatMap(([lang, profile]) => (profile.lexicon?.qualified === undefined ? [] : [[lang, profile.lexicon.qualified]])), + ); + expect(qualified).toEqual({ + clj: { namespace: "." }, + csharp: { namespace: "." }, + ex: { file: "/", module: ".", protocol: "." }, + java: { package: "." }, + js: { module: "/" }, + php: { codeFile: "/", namespace: "\\" }, + python: { module: "." }, + rb: { file: "/", class: "::", module: "::", constant: "::" }, + ts: { module: "/" }, + }); + }); + + it("qualifies every file kind by its directories", () => { + for (const [lang, profile] of profiles) { + for (const kind of profile.lexicon?.fileKinds ?? []) expect(profile.lexicon?.qualified?.[kind], `${lang}:${kind}`).toBe("/"); + } + }); }); diff --git a/packages/vocabulary/package.json b/packages/vocabulary/package.json index d451e86..90136cc 100644 --- a/packages/vocabulary/package.json +++ b/packages/vocabulary/package.json @@ -19,6 +19,7 @@ "test": "vitest run" }, "dependencies": { + "@codegraph/analyzer": "workspace:*", "@codegraph/core": "workspace:*" }, "devDependencies": { diff --git a/packages/vocabulary/src/aggregate.ts b/packages/vocabulary/src/aggregate.ts new file mode 100644 index 0000000..6040b23 --- /dev/null +++ b/packages/vocabulary/src/aggregate.ts @@ -0,0 +1,289 @@ +import { createHash } from "node:crypto"; +import { getProfile, type Entity, type EntityId } from "@codegraph/core"; +import { + compareIds, + entityName, + folderFor, + hasTrait, + identityView, + type CodeGraph, + type View, +} from "@codegraph/analyzer"; +import { + VOCABULARY_ARTEFACT_KIND, + VOCABULARY_GENERATOR, + type ScopeLevel, + type TermRow, + type VocabularyArtifact, + type VocabularyScope, + type VocabularyTerm, +} from "./artifact.js"; +import { DEFAULT_TECHNICAL, TERM_CLASSES, createClassifier, type Classify, type TermClass } from "./classify.js"; +import { splitIdentifier } from "./split.js"; +import { DEFAULT_WEIGHTS, ROLES, WEIGHT_KEYS, roleOf, structuralSlots, type Role, type Weights } from "./weight.js"; + +export interface VocabularyOptions { + readonly view?: View; + readonly weights?: Weights; + /** Replaces the shared technical list whole; each profile's own words apply either way. */ + readonly technical?: readonly string[]; + /** Display name for the corpus; defaults to the model roots' basenames. */ + readonly name?: string; +} + +/** One scope while it fills; a table row is `[weight, occurrences, carriers]` by interned term. */ +interface Tally { + readonly level: ScopeLevel; + readonly entity?: Entity; + entities: number; + readonly roles: Record; + readonly skipped: { unnamed: number; conventional: number; numeric: number }; + readonly table: Map; +} + +/** + * Count the words of every kept entity's name into the scopes the FOLD puts it + * in (PLAN.md §20 principle 5): its type (`containingType`), its module + * (`containingModule`) and the corpus. Pure: the graph is read, never written. + * + * Kept is the view's verdict on a non-stub entity. A stub's name is a word + * another corpus chose (`String`), so stubs never speak, whatever the view. + * An entity's language is its model's — read from the union, never from its id. + */ +export function buildVocabulary(graph: CodeGraph, options: VocabularyOptions = {}): VocabularyArtifact { + const view = options.view ?? identityView; + const weights = options.weights ?? DEFAULT_WEIGHTS; + const technical = options.technical ?? DEFAULT_TECHNICAL; + const folder = folderFor(graph); + const slots = structuralSlots(graph.entities.values()); + const langOf = languagesOf(graph); + const corpus = corpusOf(graph, options.name); + + const kept: Entity[] = []; + for (const id of graph.ids()) { + const entity = graph.entity(id); + if (entity !== undefined && !graph.isStub(id) && view.entity(entity, graph)) kept.push(entity); + } + + // --- the scopes: kept modules and types, by trait -------------------- + const root = tally("corpus"); + const modules = new Map(); + const types = new Map(); + for (const entity of kept) { + if (hasTrait(entity, "TModule")) modules.set(entity.id, tally("module", entity)); + else if (hasTrait(entity, "TType")) types.set(entity.id, tally("type", entity)); + } + + // --- the words: one entity at a time, into every scope it folds into --- + const terms = new Interner(); + const classifiers = new Map(); + const classifierOf = (lang: string): Classify => { + let classify = classifiers.get(lang); + if (classify === undefined) { + classify = createClassifier(getProfile(lang)?.lexicon, technical); + classifiers.set(lang, classify); + } + return classify; + }; + const unplaced: EntityId[] = []; + for (const entity of kept) { + const module = modules.get(folder.containingModule(entity.id) ?? ""); + const type = types.get(folder.containingType(entity.id) ?? ""); + if (module === undefined) unplaced.push(entity.id); + const into = [root, ...(module === undefined ? [] : [module]), ...(type === undefined ? [] : [type])]; + for (const scope of into) scope.entities += 1; + + const name = entityName(entity); + if (name === undefined) { + for (const scope of into) scope.skipped.unnamed += 1; + continue; + } + const lang = langOf.get(entity.id) ?? ""; + const split = splitIdentifier(name, entity.kind, getProfile(lang)?.lexicon); + if (split.conventional) { + for (const scope of into) scope.skipped.conventional += 1; + continue; + } + const role = roleOf(entity, slots); + const classify = classifierOf(lang); + + // This entity's own table first: a word it says twice has one carrier. + const own = new Map(); + split.tokens.forEach((token, i) => { + const term = terms.intern(token, classify(token)); + const row = own.get(term) ?? [0, 0]; + row[0] += i < split.qualifier ? weights.qualifier : weights[role]; + row[1] += 1; + own.set(term, row); + }); + for (const scope of into) { + scope.skipped.numeric += split.numeric; + scope.roles[role] += 1; + for (const [term, [weight, occurrences]] of own) { + const row = scope.table.get(term) ?? [0, 0, 0]; + row[0] += weight; + row[1] += occurrences; + row[2] += 1; + scope.table.set(term, row); + } + } + } + + // --- the artifact: terms sorted, scopes indexed, rows remapped -------- + const order = terms.sorted(); + const index = new Map(order.map((term, i) => [term.id, i] as const)); + const tallies = [root, ...sortedById(modules), ...sortedById(types)]; + const scopeIndex = new Map(); + tallies.forEach((scope, i) => { + if (scope.entity !== undefined) scopeIndex.set(scope.entity.id, i); + }); + + const spread = order.map(() => ({ modules: 0, types: 0, exclusive: -1 })); + const scopes: VocabularyScope[] = tallies.map((scope, i) => { + const rows: TermRow[] = [...scope.table] + .map(([term, [weight, occurrences, carriers]]): TermRow => [index.get(term) ?? -1, weight, occurrences, carriers]) + .sort((a, b) => b[1] - a[1] || a[0] - b[0]); + for (const [term] of rows) { + const counts = spread[term]; + if (counts === undefined || scope.level === "corpus") continue; + if (scope.level === "type") counts.types += 1; + else { + counts.modules += 1; + counts.exclusive = i; + } + } + return scopeOf(scope, rows, order, parentScope(graph, scope.entity, scopeIndex), corpus.name); + }); + + const words = [...new Set(technical)].sort(compareIds); + return { + kind: VOCABULARY_ARTEFACT_KIND, + generatedBy: VOCABULARY_GENERATOR, + view: view.descriptor, + corpus, + langs: graph.union.langs, + weights: Object.fromEntries(WEIGHT_KEYS.map((key) => [key, weights[key]])) as Weights, + classes: TERM_CLASSES, + technical: { words: words.length, sha256: createHash("sha256").update(words.join("\n"), "utf8").digest("hex") }, + terms: order.map(({ text, class: termClass }, i): VocabularyTerm => { + const { modules: speaking, types: typed, exclusive } = spread[i] ?? { modules: 0, types: 0, exclusive: -1 }; + return { text, class: termClass, modules: speaking, types: typed, ...(speaking === 1 ? { exclusive } : {}) }; + }), + scopes, + diagnostics: { + unplaced, + withoutLexicon: graph.union.langs.filter((lang) => getProfile(lang)?.lexicon === undefined), + }, + }; +} + +function tally(level: ScopeLevel, entity?: Entity): Tally { + return { + level, + ...(entity === undefined ? {} : { entity }), + entities: 0, + roles: Object.fromEntries(ROLES.map((role) => [role, 0])) as Record, + skipped: { unnamed: 0, conventional: 0, numeric: 0 }, + table: new Map(), + }; +} + +function sortedById(scopes: ReadonlyMap): Tally[] { + return [...scopes].sort(([a], [b]) => compareIds(a, b)).map(([, scope]) => scope); +} + +function scopeOf( + scope: Tally, + rows: readonly TermRow[], + terms: readonly { readonly class: TermClass }[], + parent: number | undefined, + corpusName: string, +): VocabularyScope { + const { entity } = scope; + return { + level: scope.level, + ...(entity === undefined ? {} : { id: entity.id }), + name: entity === undefined ? corpusName : (entityName(entity) ?? entity.id), + ...(entity === undefined ? {} : { kind: entity.kind }), + ...(parent === undefined ? {} : { parent }), + entities: scope.entities, + roles: scope.roles, + skipped: scope.skipped, + distinct: rows.length, + distinctDomain: rows.filter(([term]) => terms[term]?.class === "domain").length, + weight: rows.reduce((sum, row) => sum + row[1], 0), + occurrences: rows.reduce((sum, row) => sum + row[2], 0), + terms: rows, + }; +} + +/** The nearest scope up the containment chain the model writes; the corpus when there is none. */ +function parentScope( + graph: CodeGraph, + entity: Entity | undefined, + scopeIndex: ReadonlyMap, +): number | undefined { + if (entity === undefined) return undefined; + const seen = new Set([entity.id]); + for (let cursor = graph.parentOf(entity.id); cursor !== undefined && !seen.has(cursor); cursor = graph.parentOf(cursor)) { + const found = scopeIndex.get(cursor); + if (found !== undefined) return found; + seen.add(cursor); + } + return 0; +} + +/** Each id's language, from the model that declares it first — the graph's own rule. */ +function languagesOf(graph: CodeGraph): Map { + const langs = new Map(); + for (const model of graph.union.models) { + for (const entity of model.entities) if (!langs.has(entity.id)) langs.set(entity.id, model.lang); + } + return langs; +} + +/** Words interned once per (text, class): a class is decided per language. */ +class Interner { + private readonly ids = new Map>(); + private readonly entries: { readonly id: number; readonly text: string; readonly class: TermClass }[] = []; + + intern(text: string, termClass: TermClass): number { + let byClass = this.ids.get(text); + if (byClass === undefined) { + byClass = new Map(); + this.ids.set(text, byClass); + } + const existing = byClass.get(termClass); + if (existing !== undefined) return existing; + const id = this.entries.length; + byClass.set(termClass, id); + this.entries.push({ id, text, class: termClass }); + return id; + } + + /** By text in code-unit order, then by the classes' order: the same bytes on every machine. */ + sorted(): readonly { readonly id: number; readonly text: string; readonly class: TermClass }[] { + return [...this.entries].sort( + (a, b) => compareIds(a.text, b.text) || TERM_CLASSES.indexOf(a.class) - TERM_CLASSES.indexOf(b.class), + ); + } +} + +/** + * The corpus line of the artifact — the city's and the navigator's rule: + * deduped, sorted root basenames (or the caller's override), roots verbatim. + */ +function corpusOf(graph: CodeGraph, name: string | undefined): { name: string; roots: readonly string[] } { + const roots = [...new Set(graph.union.models.map((model) => model.root))] + .filter((root) => root.length > 0) + .sort(); + const basenames = [ + ...new Set( + roots + .map((root) => root.replace(/\/+$/, "")) + .map((root) => root.slice(root.lastIndexOf("/") + 1)) + .filter((base) => base.length > 0), + ), + ].sort(); + return { name: name ?? (basenames.length > 0 ? basenames.join(" + ") : "codegraph"), roots }; +} diff --git a/packages/vocabulary/src/artifact.ts b/packages/vocabulary/src/artifact.ts new file mode 100644 index 0000000..7f41e63 --- /dev/null +++ b/packages/vocabulary/src/artifact.ts @@ -0,0 +1,116 @@ +import type { ViewDescriptor } from "@codegraph/analyzer"; +import type { TermClass } from "./classify.js"; +import type { Role, Weights } from "./weight.js"; + +/** + * THE VOCABULARY ARTIFACT (PLAN.md §20.1): the words of a corpus's names, + * counted per scope — the corpus, each module, each type — as the fold places + * every entity. A third model beside the city's and the navigator's, derived + * from the first and never written back into it. + * + * Indexes, not ids, address its rows: a term is its position in `terms`, a + * scope's parent its position in `scopes`. Scopes carry the entity's rendered + * id, opaque, so another artifact's building or tree node finds its words. + */ + +export const VOCABULARY_ARTEFACT_KIND = "codegraph.vocabulary/1"; +export const VOCABULARY_GENERATOR = "@codegraph/vocabulary"; + +export const SCOPE_LEVELS = ["corpus", "module", "type"] as const; +export type ScopeLevel = (typeof SCOPE_LEVELS)[number]; + +/** `[term, weight, occurrences, carriers]`: `term` indexes `terms`; carriers are distinct entities. */ +export type TermRow = readonly [term: number, weight: number, occurrences: number, carriers: number]; + +export interface VocabularyTerm { + readonly text: string; + /** Decided per language: one word two languages class apart is two terms. */ + readonly class: TermClass; + /** How many module scopes, and how many type scopes, speak it. */ + readonly modules: number; + readonly types: number; + /** The one module scope that speaks it, when exactly one does. */ + readonly exclusive?: number; +} + +/** What a scope's entities did not contribute. */ +export interface Skipped { + /** Entities with no name: a constructor, a lambda. */ + readonly unnamed: number; + /** Names the language dictates (`toString`): no word at all. */ + readonly conventional: number; + /** Pieces of digits only (`2024` of `promo#2024`), dropped. */ + readonly numeric: number; +} + +export interface VocabularyScope { + readonly level: ScopeLevel; + /** The entity's rendered id; absent on the corpus. Compared, never parsed. */ + readonly id?: string; + readonly name: string; + /** The entity kind, verbatim; absent on the corpus. */ + readonly kind?: string; + /** The nearest scope the model declares around it; absent on the corpus. */ + readonly parent?: number; + /** Kept entities the fold puts here: each one unnamed, conventional, or counted in `roles`. */ + readonly entities: number; + /** The entities that spoke words, by the role of each. */ + readonly roles: Readonly>; + readonly skipped: Skipped; + /** Terms in the table, and the `domain` ones among them: the cognitive-load proxy. */ + readonly distinct: number; + readonly distinctDomain: number; + /** Sums over the table. */ + readonly weight: number; + readonly occurrences: number; + /** By weight descending, then term. */ + readonly terms: readonly TermRow[]; +} + +export interface VocabularyDiagnostics { + /** Kept entities no module contains, sorted: counted at the corpus alone. */ + readonly unplaced: readonly string[]; + /** Languages core has no lexicon for, sorted: split by the universal rules alone. */ + readonly withoutLexicon: readonly string[]; +} + +export interface VocabularyArtifact { + /** Always `codegraph.vocabulary/1` — derived, never an interchange model.jsonl. */ + readonly kind: string; + readonly generatedBy: string; + /** A count without its view is not a fact; nor without its weights or its technical list. */ + readonly view: ViewDescriptor; + readonly corpus: { readonly name: string; readonly roots: readonly string[] }; + readonly langs: readonly string[]; + readonly weights: Weights; + readonly classes: readonly TermClass[]; + /** The shared technical list: its size, and a sha256 over its sorted words, one per line. */ + readonly technical: { readonly words: number; readonly sha256: string }; + /** Sorted by text, then class. */ + readonly terms: readonly VocabularyTerm[]; + /** The corpus first, then modules, then types, each level in id order. */ + readonly scopes: readonly VocabularyScope[]; + readonly diagnostics: VocabularyDiagnostics; +} + +/** + * The artifact as JSON, deterministically: every array the builder writes is + * sorted, so insertion order is the byte order. Compact by default (thousands + * of scopes on a real corpus); `pretty` puts one term and one scope per line, + * the form of the committed snapshots, so a diff reads word by word. + */ +export function vocabularyToJsonString( + artifact: VocabularyArtifact, + options?: { readonly pretty?: boolean }, +): string { + if (options?.pretty !== true) return JSON.stringify(artifact); + const entries = Object.entries(artifact).map(([key, value]) => { + const text = Array.isArray(value) && (key === "terms" || key === "scopes") ? rows(value) : JSON.stringify(value); + return ` ${JSON.stringify(key)}: ${text}`; + }); + return `{\n${entries.join(",\n")}\n}\n`; +} + +function rows(values: readonly unknown[]): string { + return values.length === 0 ? "[]" : `[\n${values.map((value) => ` ${JSON.stringify(value)}`).join(",\n")}\n ]`; +} diff --git a/packages/vocabulary/src/index.ts b/packages/vocabulary/src/index.ts index c7b58de..68675fb 100644 --- a/packages/vocabulary/src/index.ts +++ b/packages/vocabulary/src/index.ts @@ -9,3 +9,5 @@ export * from "./split.js"; export * from "./classify.js"; export * from "./weight.js"; +export * from "./artifact.js"; +export * from "./aggregate.js"; diff --git a/packages/vocabulary/src/split.ts b/packages/vocabulary/src/split.ts index 351e26d..90db7d4 100644 --- a/packages/vocabulary/src/split.ts +++ b/packages/vocabulary/src/split.ts @@ -2,21 +2,28 @@ import type { Lexicon } from "@codegraph/core"; /** * The split (PLAN.md §20.1): one name → the words it is made of. The lexicon - * says which names are conventional, which kinds are named by a file path and - * whether an interface marker leads the name; every word boundary is universal, - * so the algorithm knows no language. + * says which names are conventional, which kinds are named by a file path or a + * qualified path and whether an interface marker leads the name; every word + * boundary is universal, so the algorithm knows no language. */ export interface SplitName { /** The name's words, lowercased, in the order the name spells them. */ readonly tokens: readonly string[]; + /** How many of `tokens`, from the first, spell the qualifier (`AcmeOrder::` of `AcmeOrder::Order`). */ + readonly qualifier: number; /** Pieces made of digits only, dropped (`2024` of `promo#2024`). */ readonly numeric: number; /** The whole name is one the language dictates (`toString`): it has no word. */ readonly conventional: boolean; } -const CONVENTIONAL: SplitName = Object.freeze({ tokens: Object.freeze([]), numeric: 0, conventional: true }); +const CONVENTIONAL: SplitName = Object.freeze({ + tokens: Object.freeze([]), + qualifier: 0, + numeric: 0, + conventional: true, +}); /** Maximal runs of letters, marks and digits: every other character breaks a word. */ const PIECE = /[\p{L}\p{M}\p{N}]+/gu; @@ -32,18 +39,38 @@ export function splitIdentifier(name: string, kind: string, lexicon: Lexicon | u let text = name; if (lexicon?.fileKinds?.includes(kind) === true) text = dropExtension(text); - if (lexicon?.interfacePrefix === true) text = text.replace(INTERFACE_PREFIX, ""); + let qualifier = ""; + let own = text; + const separator = separatorOf(lexicon, kind); + const cut = separator === undefined ? -1 : text.lastIndexOf(separator); + if (separator !== undefined && cut >= 0) { + qualifier = text.slice(0, cut); + own = text.slice(cut + separator.length); + } + // Own-name rules read what follows the qualifier: `Acme.IOrder` names `Order`. + if (lexicon?.interfacePrefix === true) own = own.replace(INTERFACE_PREFIX, ""); const tokens: string[] = []; let numeric = 0; - for (const [piece] of text.matchAll(PIECE)) { - for (const word of camelWords(piece)) { - if (DIGITS_ONLY.test(word)) numeric += 1; - // `toLowerCase` is locale-independent: a Turkish machine writes the same bytes. - else tokens.push(word.toLowerCase()); + const push = (part: string): void => { + for (const [piece] of part.matchAll(PIECE)) { + for (const word of camelWords(piece)) { + if (DIGITS_ONLY.test(word)) numeric += 1; + // `toLowerCase` is locale-independent: a Turkish machine writes the same bytes. + else tokens.push(word.toLowerCase()); + } } - } - return { tokens, numeric, conventional: false }; + }; + push(qualifier); + const qualifierTokens = tokens.length; + push(own); + return { tokens, qualifier: qualifierTokens, numeric, conventional: false }; +} + +/** An own key only: Java has a `constructor` kind, and every object inherits one. */ +function separatorOf(lexicon: Lexicon | undefined, kind: string): string | undefined { + const qualified = lexicon?.qualified; + return qualified !== undefined && Object.hasOwn(qualified, kind) ? qualified[kind] : undefined; } /** The extension of the last path segment; a leading dot (`.eslintrc`) is not one. */ diff --git a/packages/vocabulary/src/weight.ts b/packages/vocabulary/src/weight.ts index 95b9f83..5af3b3c 100644 --- a/packages/vocabulary/src/weight.ts +++ b/packages/vocabulary/src/weight.ts @@ -10,7 +10,10 @@ import type { Entity, EntityId } from "@codegraph/core"; /** From the outermost container in; `other` is a named entity no role trait describes. */ export const ROLES = ["module", "type", "operation", "attribute", "parameter", "local", "other"] as const; export type Role = (typeof ROLES)[number]; -export type Weights = Readonly>; +/** Every role, then the words of a name's qualifier, whatever its role (`AcmeOrder::` of `AcmeOrder::Order`). */ +export const WEIGHT_KEYS = [...ROLES, "qualifier"] as const; +export type WeightKey = (typeof WEIGHT_KEYS)[number]; +export type Weights = Readonly>; export const DEFAULT_WEIGHTS: Weights = Object.freeze({ module: 8, // a concept the corpus names once and refers to everywhere @@ -20,6 +23,7 @@ export const DEFAULT_WEIGHTS: Weights = Object.freeze({ parameter: 1, // incidental names local: 1, other: 1, // a TS namespace, a PHP code file: no trait says what it is + qualifier: 1, // a container's words, spoken again by every name it qualifies }); /** The structural entities some entity lists as its parameters or its locals. */ diff --git a/packages/vocabulary/test/aggregate.test.ts b/packages/vocabulary/test/aggregate.test.ts new file mode 100644 index 0000000..1d05129 --- /dev/null +++ b/packages/vocabulary/test/aggregate.test.ts @@ -0,0 +1,294 @@ +import { createHash } from "node:crypto"; +import type { Entity } from "@codegraph/core"; +import { composeViews, internalOnly, makeView } from "@codegraph/analyzer"; +import fc from "fast-check"; +import { describe, expect, it } from "vitest"; +import { buildVocabulary } from "../src/aggregate.js"; +import { VOCABULARY_ARTEFACT_KIND, VOCABULARY_GENERATOR, vocabularyToJsonString, type VocabularyArtifact } from "../src/artifact.js"; +import { DEFAULT_TECHNICAL, TERM_CLASSES } from "../src/classify.js"; +import { DEFAULT_WEIGHTS, ROLES } from "../src/weight.js"; +import { expectClosed } from "./closure.js"; +import { cls, graphOf, method, orderCorpus, pkg, structural } from "./toy.js"; + +/** + * Aggregation (PLAN.md §20.1): every kept, named entity's words, weighted by + * its role, summed into the scopes the FOLD puts it in — its type, its module, + * the corpus. The corpus below is counted by hand, word by word. + */ + +const java = (entities: readonly Entity[]) => graphOf([{ lang: "java", entities }]); + +/** A scope's table as `term → [weight, occurrences, carriers]`, in row order. */ +function table(artifact: VocabularyArtifact, index: number): readonly (readonly [string, number, number, number])[] { + return (artifact.scopes[index]?.terms ?? []).map(([term, w, n, c]) => [artifact.terms[term]?.text ?? "?", w, n, c]); +} + +const NO_ROLES = Object.fromEntries(ROLES.map((role) => [role, 0])); + +describe("the hand-counted corpus", () => { + const graph = java(orderCorpus()); + const artifact = buildVocabulary(graph); + + it("states what it is, and under which scheme it counted", () => { + expect(artifact.kind).toBe(VOCABULARY_ARTEFACT_KIND); + expect(artifact.generatedBy).toBe(VOCABULARY_GENERATOR); + expect(artifact.view).toEqual({ name: "all", filters: [] }); + expect(artifact.corpus).toEqual({ name: "java", roots: ["toy/java"] }); + expect(artifact.langs).toEqual(["java"]); + expect(artifact.weights).toEqual(DEFAULT_WEIGHTS); + expect(artifact.classes).toEqual(TERM_CLASSES); + const digest = createHash("sha256").update([...DEFAULT_TECHNICAL].sort().join("\n"), "utf8").digest("hex"); + expect(artifact.technical).toEqual({ words: DEFAULT_TECHNICAL.length, sha256: digest }); + }); + + it("interns every word once, sorted, with its class and its spread", () => { + expect(artifact.terms).toEqual([ + { text: "acme", class: "domain", modules: 2, types: 0 }, + { text: "billing", class: "domain", modules: 1, types: 0, exclusive: 2 }, + { text: "com", class: "technical", modules: 2, types: 0 }, + { text: "get", class: "technical", modules: 1, types: 1, exclusive: 1 }, + { text: "invoice", class: "domain", modules: 1, types: 1, exclusive: 2 }, + { text: "line", class: "domain", modules: 1, types: 2, exclusive: 1 }, + { text: "lines", class: "domain", modules: 1, types: 1, exclusive: 1 }, + { text: "order", class: "domain", modules: 2, types: 1 }, + { text: "qty", class: "domain", modules: 1, types: 1, exclusive: 1 }, + { text: "total", class: "domain", modules: 1, types: 1, exclusive: 1 }, + ]); + }); + + it("orders the scopes root, modules, types, each with its parent scope", () => { + expect(artifact.scopes.map(({ level, id, name, kind, parent }) => ({ level, id, name, kind, parent }))).toEqual([ + { level: "corpus", id: undefined, name: "java", kind: undefined, parent: undefined }, + { level: "module", id: "java:com.acme.order", name: "com.acme.order", kind: "package", parent: 0 }, + { level: "module", id: "java:com.acme.order.billing", name: "com.acme.order.billing", kind: "package", parent: 1 }, + { level: "type", id: "java:com.acme.order.billing/Invoice", name: "Invoice", kind: "class", parent: 2 }, + { level: "type", id: "java:com.acme.order/Order", name: "Order", kind: "class", parent: 1 }, + // A nested type is its own scope, under its outer type. + { level: "type", id: "java:com.acme.order/Order$Line", name: "Line", kind: "class", parent: 4 }, + ]); + }); + + it("weighs each word by the role of its carrier, a qualifier at 1", () => { + // order: the package (8), `order.` qualifying billing (1), the class (8), orderLine (1). + expect(table(artifact, 0)).toEqual([ + ["order", 18, 4, 4], + ["line", 9, 2, 2], + ["billing", 8, 1, 1], + ["invoice", 8, 1, 1], + ["lines", 4, 1, 1], + ["qty", 4, 1, 1], + ["total", 4, 2, 2], + ["get", 3, 1, 1], + ["acme", 2, 2, 2], + ["com", 2, 2, 2], + ]); + expect(artifact.scopes[0]).toMatchObject({ + entities: 12, + roles: { module: 2, type: 3, operation: 1, attribute: 2, parameter: 1, local: 1, other: 0 }, + skipped: { unnamed: 1, conventional: 1, numeric: 0 }, + distinct: 10, + distinctDomain: 8, + weight: 62, + occurrences: 17, + }); + }); + + it("folds a member into its nearest type and module, never the outer type", () => { + expect(table(artifact, 1)).toEqual([ + ["order", 17, 3, 3], + ["line", 9, 2, 2], + ["lines", 4, 1, 1], + ["qty", 4, 1, 1], + ["total", 4, 2, 2], + ["get", 3, 1, 1], + ["acme", 1, 1, 1], + ["com", 1, 1, 1], + ]); + expect(table(artifact, 2)).toEqual([ + ["billing", 8, 1, 1], + ["invoice", 8, 1, 1], + ["acme", 1, 1, 1], + ["com", 1, 1, 1], + ["order", 1, 1, 1], + ]); + expect(table(artifact, 3)).toEqual([["invoice", 8, 1, 1]]); + expect(table(artifact, 4)).toEqual([ + ["order", 9, 2, 2], + ["lines", 4, 1, 1], + ["total", 4, 2, 2], + ["get", 3, 1, 1], + ["line", 1, 1, 1], + ]); + expect(table(artifact, 5)).toEqual([ + ["line", 8, 1, 1], + ["qty", 4, 1, 1], + ]); + expect(artifact.scopes.map((scope) => scope.entities)).toEqual([12, 10, 2, 1, 7, 2]); + expect(artifact.scopes[4]).toMatchObject({ + roles: { ...NO_ROLES, type: 1, operation: 1, attribute: 1, parameter: 1, local: 1 }, + skipped: { unnamed: 1, conventional: 1, numeric: 0 }, + }); + }); + + it("never counts a stub, nor makes one a scope", () => { + expect(artifact.terms.map((term) => term.text)).not.toContain("string"); + expect(artifact.scopes.map((scope) => scope.id)).not.toContain("java:java.lang"); + expect(artifact.diagnostics).toEqual({ unplaced: [], withoutLexicon: [] }); + }); + + it("is closed", () => { + expectClosed(artifact, graph); + }); +}); + +describe("options", () => { + const graph = java(orderCorpus()); + + it("counts under the view, and says which", () => { + const noLocals = makeView("noLocals", (entity) => entity.kind !== "localVariable", () => true); + const view = composeViews(internalOnly, noLocals); + const artifact = buildVocabulary(graph, { view }); + expect(artifact.view).toEqual(view.descriptor); + expect(table(artifact, 0)).toContainEqual(["total", 3, 1, 1]); + expect(artifact.scopes[0]?.entities).toBe(11); + }); + + it("weighs with the weights given, and records them; a word weighing 0 still occurs", () => { + const weights = { ...DEFAULT_WEIGHTS, qualifier: 0, type: 5 }; + const artifact = buildVocabulary(graph, { weights }); + expect(artifact.weights).toEqual(weights); + expect(table(artifact, 0)).toContainEqual(["acme", 0, 2, 2]); + expect(table(artifact, 0)).toContainEqual(["invoice", 5, 1, 1]); + }); + + it("classifies with the technical list given, and records its digest, whatever its order", () => { + const artifact = buildVocabulary(graph, { technical: ["order", "total"] }); + const reversed = buildVocabulary(graph, { technical: ["total", "order"] }); + expect(artifact.terms.find((term) => term.text === "order")?.class).toBe("technical"); + expect(artifact.terms.find((term) => term.text === "get")?.class).toBe("domain"); + expect(artifact.technical.words).toBe(2); + expect(artifact.technical.sha256).toBe(createHash("sha256").update("order\ntotal", "utf8").digest("hex")); + expect(reversed.technical).toEqual(artifact.technical); + }); + + it("names the corpus as asked", () => { + expect(buildVocabulary(graph, { name: "acme" }).scopes[0]?.name).toBe("acme"); + }); +}); + +describe("what no scope holds", () => { + it("counts an entity no module contains at the root alone, and names it", () => { + const graph = java([pkg("java:p", "shop"), cls("java:p/Order", "Order", "java:p"), cls("java:Loose", "Loose")]); + const artifact = buildVocabulary(graph); + expect(artifact.diagnostics.unplaced).toEqual(["java:Loose"]); + expect(table(artifact, 0)).toEqual([ + ["loose", 8, 1, 1], + ["order", 8, 1, 1], + ["shop", 8, 1, 1], + ]); + // A type is a scope whether or not a module holds it. + expect(artifact.scopes.map((scope) => scope.id)).toEqual([undefined, "java:p", "java:Loose", "java:p/Order"]); + expect(artifact.scopes[2]?.parent).toBe(0); + expectClosed(artifact, graph); + }); + + it("splits a language with no lexicon by the universal rules, and says so", () => { + const graph = graphOf([{ lang: "cobol", entities: [pkg("cobol:m", "PAYROLL-RUN"), cls("cobol:m/A", "toString", "cobol:m")] }]); + const artifact = buildVocabulary(graph); + expect(artifact.diagnostics.withoutLexicon).toEqual(["cobol"]); + expect(artifact.terms.map((term) => term.text)).toEqual(["payroll", "run", "string", "to"]); + expect(artifact.terms.find((term) => term.text === "run")?.class).toBe("technical"); + }); +}); + +describe("a word said twice", () => { + // Ruby names a class by its constant path and a file by its path: the + // qualifier's words weigh 1, the own name's 8, and one entity is one carrier. + const graph = graphOf([ + { + lang: "rb", + entities: [ + { ...pkg("rb:lib/acme_order/order.rb", "lib/acme_order/order.rb"), kind: "file" } as Entity, + { ...cls("rb:lib/acme_order/order.rb/AcmeOrder::Order", "AcmeOrder::Order", "rb:lib/acme_order/order.rb"), kind: "class" } as Entity, + ], + }, + ]); + const artifact = buildVocabulary(graph); + + it("counts every occurrence and one carrier per entity", () => { + expect(table(artifact, 0)).toEqual([ + ["order", 18, 4, 2], + ["acme", 2, 2, 2], + ["lib", 1, 1, 1], + ]); + expectClosed(artifact, graph); + }); +}); + +describe("several languages", () => { + // `type` is reserved by Go and an identifier to Java: one word, two classes, + // so two terms — a class is decided per language, never averaged. + const graph = graphOf([ + { lang: "java", entities: [pkg("java:p", "shop"), cls("java:p/Type", "Type", "java:p")] }, + { lang: "go", entities: [pkg("go:q", "type")] }, + ]); + const artifact = buildVocabulary(graph); + + it("interns a word once per class it takes", () => { + expect(artifact.langs).toEqual(["go", "java"]); + expect(artifact.terms.filter((term) => term.text === "type").map((term) => term.class)).toEqual(["domain", "keyword"]); + expect(artifact.corpus.roots).toEqual(["toy/go", "toy/java"]); + expectClosed(artifact, graph); + }); +}); + +describe("properties", () => { + // Small corpora of packages, classes, members and parameters, named from + // words the classes split differently, with the odd unnamed or conventional one. + const WORDS = ["order", "Order", "line", "LINE", "getTotal", "toString", "acme_order", "HTTPServer", "id", "x", "new", "2024", "promo#2024"]; + const name = fc.array(fc.constantFrom(...WORDS), { minLength: 1, maxLength: 3 }).map((ws) => ws.join("")); + const member = fc.record({ name: fc.option(name, { nil: undefined }), params: fc.array(name, { maxLength: 2 }) }); + const type = fc.record({ name, members: fc.array(member, { maxLength: 3 }), nested: fc.boolean() }); + const module = fc.record({ name, types: fc.array(type, { maxLength: 3 }) }); + const corpus = fc.array(module, { minLength: 1, maxLength: 3 }).map((modules) => { + const entities: Entity[] = []; + modules.forEach((m, mi) => { + const mid = `java:m${mi}`; + entities.push(pkg(mid, `com.acme.${m.name}`)); + m.types.forEach((t, ti) => { + const parent = t.nested && ti > 0 ? `${mid}/T${ti - 1}` : mid; + const tid = `${mid}/T${ti}`; + entities.push(cls(tid, t.name, parent)); + t.members.forEach((mem, k) => { + const id = `${tid}.m${k}`; + const params = mem.params.map((_, p) => `${id}#p${p}`); + entities.push(method(id, mem.name, tid, { parameters: params })); + mem.params.forEach((p, i) => entities.push(structural(params[i] ?? "", "parameter", p, id))); + }); + }); + }); + return entities; + }); + + it("is closed, and every kept entity lands in one module", () => { + fc.assert( + fc.property(corpus, (entities) => { + const graph = java(entities); + expectClosed(buildVocabulary(graph), graph); + }), + ); + }); + + it("writes the same bytes whatever the order the entities arrive in", () => { + fc.assert( + fc.property( + corpus.chain((entities) => fc.tuple(fc.constant(entities), fc.shuffledSubarray(entities, { minLength: entities.length }))), + ([entities, shuffled]) => { + const a = vocabularyToJsonString(buildVocabulary(java(entities))); + const b = vocabularyToJsonString(buildVocabulary(java(shuffled))); + expect(b).toBe(a); + }, + ), + ); + }); +}); diff --git a/packages/vocabulary/test/artifact.test.ts b/packages/vocabulary/test/artifact.test.ts new file mode 100644 index 0000000..5343949 --- /dev/null +++ b/packages/vocabulary/test/artifact.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest"; +import { buildVocabulary } from "../src/aggregate.js"; +import { vocabularyToJsonString } from "../src/artifact.js"; +import { graphOf, orderCorpus } from "./toy.js"; + +/** + * The artifact as text. Compact by default, like the navigator's: a real + * corpus has thousands of scopes. Pretty is the committed snapshots' form — + * one term and one scope per line, so a review reads a diff of words. + */ +describe("vocabularyToJsonString", () => { + const artifact = buildVocabulary(graphOf([{ lang: "java", entities: orderCorpus() }])); + + it("writes one line by default, and the artifact parses back whole", () => { + const compact = vocabularyToJsonString(artifact); + expect(compact).not.toContain("\n"); + expect(JSON.parse(compact)).toEqual(JSON.parse(JSON.stringify(artifact))); + }); + + it("writes one term and one scope per line when pretty, saying the same thing", () => { + const pretty = vocabularyToJsonString(artifact, { pretty: true }); + expect(JSON.parse(pretty)).toEqual(JSON.parse(vocabularyToJsonString(artifact))); + expect(pretty.endsWith("}\n")).toBe(true); + const lines = new Set(pretty.split("\n").map((line) => line.trim().replace(/,$/, ""))); + for (const term of artifact.terms) expect(lines.has(JSON.stringify(term)), term.text).toBe(true); + for (const scope of artifact.scopes) expect(lines.has(JSON.stringify(scope)), scope.name).toBe(true); + expect(pretty.split("\n").length).toBeLessThan(artifact.terms.length + artifact.scopes.length + 30); + expect(pretty.startsWith(`{\n "kind": "codegraph.vocabulary/1",\n`)).toBe(true); + }); +}); diff --git a/packages/vocabulary/test/closure.ts b/packages/vocabulary/test/closure.ts new file mode 100644 index 0000000..1aadebf --- /dev/null +++ b/packages/vocabulary/test/closure.ts @@ -0,0 +1,117 @@ +import { hasTrait, type CodeGraph } from "@codegraph/analyzer"; +import { expect } from "vitest"; +import { TERM_CLASSES } from "../src/classify.js"; +import type { VocabularyArtifact } from "../src/artifact.js"; + +/** + * The artifact's closure (PLAN.md §20.1): every scope id is an entity the + * model declares, kept and of the scope's level; every index is in range; and + * every count is what the tables add up to. A dangling index is unwritable. + * + * Checked in plain code and asserted once, listing every breach: an `expect` + * per row made the check sixteen times slower than the build it checks, and + * timed a property out on CI. + */ +export function expectClosed(artifact: VocabularyArtifact, graph: CodeGraph): void { + const { terms, scopes } = artifact; + const problems: string[] = []; + const check = (holds: boolean, problem: () => string): void => { + if (!holds) problems.push(problem()); + }; + const classOrder = (c: string): number => TERM_CLASSES.indexOf(c as (typeof TERM_CLASSES)[number]); + + // Terms: interned once per (text, class), sorted by text, then class. + for (let i = 1; i < terms.length; i += 1) { + const [a, b] = [terms[i - 1], terms[i]]; + if (a === undefined || b === undefined) throw new Error("unreachable"); + check(a.text < b.text || (a.text === b.text && classOrder(a.class) < classOrder(b.class)), () => `${a.text} (${a.class}) before ${b.text} (${b.class})`); + } + + const [root, ...rest] = scopes; + check(root?.level === "corpus", () => `the first scope is the corpus, not ${root?.level}`); + check(root?.id === undefined, () => `the root has no id, not ${root?.id}`); + check(root?.parent === undefined, () => `the root has no parent, not ${root?.parent}`); + const modules = rest.filter((scope) => scope.level === "module"); + const types = rest.filter((scope) => scope.level === "type"); + // Root, then modules in id order, then types in id order. + const leveled = [...modules, ...types]; + check(rest.length === leveled.length && rest.every((scope, i) => scope === leveled[i]), () => "modules come before types, and nothing else follows the root"); + for (const level of [modules, types]) { + const ids = level.map((scope) => scope.id ?? ""); + ids.forEach((id, i) => { + const previous = ids[i - 1]; + check(previous === undefined || previous < id, () => `${previous} before ${id}, once`); + }); + } + + const modulesSpeaking = new Map(); + const typesSpeaking = new Map(); + const speakers = new Map(); + scopes.forEach((scope, index) => { + if (scope.level !== "corpus") { + const entity = graph.entity(scope.id ?? ""); + check(entity !== undefined, () => `${scope.id} is declared`); + if (entity === undefined) return; + check(!graph.isStub(entity.id), () => `${scope.id} is no stub`); + check(hasTrait(entity, scope.level === "module" ? "TModule" : "TType"), () => `${scope.id} is a ${scope.level}`); + check(scope.kind === entity.kind, () => `${scope.id}: kind ${scope.kind}, declared ${entity.kind}`); + const { parent } = scope; + check(parent !== undefined && parent !== index && parent >= 0 && parent < scopes.length, () => `${scope.id}: parent scope ${parent} of ${scopes.length}, itself ${index}`); + } + + const roles = Object.values(scope.roles).reduce((sum, n) => sum + n, 0); + const accounted = roles + scope.skipped.unnamed + scope.skipped.conventional; + check(scope.entities === accounted, () => `${scope.name}: ${scope.entities} entities, ${accounted} unnamed, conventional or carrying a role`); + + let weight = 0; + let occurrences = 0; + const seen = new Set(); + scope.terms.forEach(([term, w, n, carriers], row) => { + const at = `${scope.name} row ${row}`; + check(term >= 0 && term < terms.length, () => `${at}: term ${term} of ${terms.length}`); + check(!seen.has(term), () => `${at}: ${terms[term]?.text} once`); + seen.add(term); + check(n > 0, () => `${at}: ${n} occurrences`); + check(carriers > 0 && carriers <= n && carriers <= scope.entities, () => `${at}: ${carriers} carriers, ${n} occurrences, ${scope.entities} entities`); + check(w >= 0, () => `${at}: weight ${w}`); + const previous = scope.terms[row - 1]; + if (previous !== undefined) { + check(previous[1] > w || (previous[1] === w && previous[0] < term), () => `${at}: rows by weight, then term`); + } + weight += w; + occurrences += n; + if (scope.level === "module") modulesSpeaking.set(term, (modulesSpeaking.get(term) ?? 0) + 1); + if (scope.level === "type") typesSpeaking.set(term, (typesSpeaking.get(term) ?? 0) + 1); + if (scope.level === "module") speakers.set(term, [...(speakers.get(term) ?? []), index]); + }); + check(Object.is(scope.weight, weight), () => `${scope.name}: weight ${scope.weight}, rows ${weight}`); + check(Object.is(scope.occurrences, occurrences), () => `${scope.name}: ${scope.occurrences} occurrences, rows ${occurrences}`); + check(scope.distinct === scope.terms.length, () => `${scope.name}: ${scope.distinct} distinct, ${scope.terms.length} rows`); + const domain = scope.terms.filter(([term]) => terms[term]?.class === "domain").length; + check(scope.distinctDomain === domain, () => `${scope.name}: ${scope.distinctDomain} distinct domain, ${domain} rows`); + }); + + // The root speaks every term; the spread is what the scope tables say. + const spoken = new Set(root?.terms.map(([term]) => term)); + check(spoken.size === terms.length && terms.every((_, index) => spoken.has(index)), () => `the root speaks ${spoken.size} of ${terms.length} terms`); + terms.forEach((term, index) => { + const only = speakers.get(index); + const exclusive = only?.length === 1 ? only[0] : undefined; + check(term.modules === (modulesSpeaking.get(index) ?? 0), () => `${term.text}: ${term.modules} modules, ${modulesSpeaking.get(index) ?? 0} say it`); + check(term.types === (typesSpeaking.get(index) ?? 0), () => `${term.text}: ${term.types} types, ${typesSpeaking.get(index) ?? 0} say it`); + check(term.exclusive === exclusive, () => `${term.text}: exclusive to ${term.exclusive}, not ${exclusive}`); + }); + + // Every kept entity folds into one module scope, or is unplaced and said so. + const sum = (key: "entities" | "weight" | "occurrences"): number => + modules.reduce((total, scope) => total + scope[key], 0); + const { unplaced } = artifact.diagnostics; + check(root?.entities === sum("entities") + unplaced.length, () => `the root holds ${root?.entities} entities, the modules ${sum("entities")} and ${unplaced.length} unplaced`); + if (unplaced.length === 0) { + check(Object.is(root?.weight, sum("weight")), () => `the root weighs ${root?.weight}, the modules ${sum("weight")}`); + check(Object.is(root?.occurrences, sum("occurrences")), () => `the root has ${root?.occurrences} occurrences, the modules ${sum("occurrences")}`); + } + for (const id of unplaced) check(graph.entity(id) !== undefined, () => `${id}, unplaced, is declared`); + + expect(problems).toEqual([]); +} diff --git a/packages/vocabulary/test/fixtures.test.ts b/packages/vocabulary/test/fixtures.test.ts new file mode 100644 index 0000000..110419d --- /dev/null +++ b/packages/vocabulary/test/fixtures.test.ts @@ -0,0 +1,71 @@ +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { readModelFileSync } from "@codegraph/core"; +import { buildGraph, loadModels, type CodeGraph } from "@codegraph/analyzer"; +import { describe, expect, it } from "vitest"; +import { buildVocabulary } from "../src/aggregate.js"; +import { vocabularyToJsonString } from "../src/artifact.js"; +import { expectClosed } from "./closure.js"; + +/** + * Every fixture's model → `fixtures//expected/vocabulary.json`, byte for + * byte (PLAN.md §20 principle 6). The snapshot is the pretty form, one term and + * one scope per line, so a change to the split, the classes or the weights + * reads in review as the words it moved. `CODEGRAPH_UPDATE_SNAPSHOTS=1` + * rewrites them first — review the diff. + */ + +const FIXTURES = ["java", "csharp", "typescript", "elixir", "ruby", "unicode"] as const; + +const path = (fixture: string, file: string): string => + fileURLToPath(new URL(`../../../fixtures/${fixture}/expected/${file}`, import.meta.url)); + +function graphOf(fixture: string): CodeGraph { + const model = readModelFileSync(path(fixture, "model.jsonl")); + return buildGraph(loadModels(model, { sources: [`fixtures/${fixture}`] }).union); +} + +describe.each(FIXTURES)("fixtures/%s", (fixture) => { + const graph = graphOf(fixture); + const text = vocabularyToJsonString(buildVocabulary(graph), { pretty: true }); + const snapshot = path(fixture, "vocabulary.json"); + if (process.env["CODEGRAPH_UPDATE_SNAPSHOTS"] !== undefined) writeFileSync(snapshot, text); + + it("reproduces expected/vocabulary.json exactly", () => { + expect(existsSync(snapshot), `${snapshot} is committed`).toBe(true); + expect(text).toBe(readFileSync(snapshot, "utf8")); + }); + + it("is closed", () => { + expectClosed(buildVocabulary(graph), graph); + }); + + it("is the same on a second run over a fresh graph", () => { + expect(vocabularyToJsonString(buildVocabulary(graphOf(fixture)), { pretty: true })).toBe(text); + }); +}); + +// The words each fixture's README counts by hand, name by name. A snapshot +// can be rewritten in one command; these cannot, so a change to the split, the +// qualifiers or the weights has to be argued against a count a person made. +describe("the hand counts of the fixtures' READMEs", () => { + const rows: readonly (readonly [fixture: string, word: string, weight: number, occurrences: number, carriers: number])[] = [ + ["java", "order", 40, 12, 12], + ["csharp", "order", 42, 14, 14], + ["csharp", "priceable", 9, 2, 2], + ["typescript", "legacy", 11, 4, 4], + ["typescript", "packages", 21, 21, 21], + ["elixir", "pricing", 28, 9, 9], + ["ruby", "pricing", 8, 5, 5], + ]; + + it.each(rows)("%s: %s weighs %i over %i occurrences, %i carriers", (fixture, word, weight, occurrences, carriers) => { + const artifact = buildVocabulary(graphOf(fixture)); + const term = artifact.terms.findIndex((entry) => entry.text === word && entry.class === "domain"); + expect(artifact.scopes[0]?.terms.find(([index]) => index === term)?.slice(1)).toEqual([weight, occurrences, carriers]); + }); + + it("csharp: the interface marker is no word", () => { + expect(buildVocabulary(graphOf("csharp")).terms.map((entry) => entry.text)).not.toContain("i"); + }); +}); diff --git a/packages/vocabulary/test/split.test.ts b/packages/vocabulary/test/split.test.ts index 1b5c907..12280fb 100644 --- a/packages/vocabulary/test/split.test.ts +++ b/packages/vocabulary/test/split.test.ts @@ -18,18 +18,27 @@ function lexiconOf(lang: string): Lexicon { return lexicon; } -/** [kind, name, tokens, numeric pieces dropped]; `null` tokens = a conventional name. */ -type Row = readonly [kind: string, name: string, tokens: readonly string[] | null, numeric?: number]; +/** + * [kind, name, tokens, counts]; `null` tokens = a conventional name. `numeric` + * counts the digit-only pieces dropped, `qualifier` the leading tokens that + * spell the name's qualifier. + */ +type Row = readonly [ + kind: string, + name: string, + tokens: readonly string[] | null, + counts?: { readonly numeric?: number; readonly qualifier?: number }, +]; -function check(lang: string | undefined, rows: readonly Row[]): void { - const lexicon = lang === undefined ? undefined : lexiconOf(lang); - for (const [kind, name, tokens, numeric = 0] of rows) { +function check(lang: string | Lexicon | undefined, rows: readonly Row[]): void { + const lexicon = typeof lang === "string" ? lexiconOf(lang) : lang; + for (const [kind, name, tokens, { numeric = 0, qualifier = 0 } = {}] of rows) { it(`${kind} ${name}`, () => { const split = splitIdentifier(name, kind, lexicon); if (tokens === null) { - expect(split).toEqual({ tokens: [], numeric: 0, conventional: true }); + expect(split).toEqual({ tokens: [], qualifier: 0, numeric: 0, conventional: true }); } else { - expect(split).toEqual({ tokens, numeric, conventional: false }); + expect(split).toEqual({ tokens, qualifier, numeric, conventional: false }); } }); } @@ -52,9 +61,9 @@ describe("camel case and acronyms, with no lexicon", () => { ["class", "V2Api", ["v2", "api"]], ["class", "HTTP2Server", ["http2", "server"]], ["module", "i18n", ["i18n"]], - ["class", "2FA", ["fa"], 1], + ["class", "2FA", ["fa"], { numeric: 1 }], ["class", "Promo2024", ["promo2024"]], - ["field", "line_2", ["line"], 1], + ["field", "line_2", ["line"], { numeric: 1 }], // Every character that is not a letter, a mark or a digit breaks a word. ["method", "->", []], ["method", "a--b", ["a", "b"]], @@ -78,7 +87,7 @@ describe("Unicode letters, not ASCII", () => { describe("Java", () => { check("java", [ - ["package", "com.acme.order", ["com", "acme", "order"]], + ["package", "com.acme.order", ["com", "acme", "order"], { qualifier: 2 }], ["class", "EmptyBasketException", ["empty", "basket", "exception"]], ["method", "notifyShipped", ["notify", "shipped"]], ["method", "toString", null], @@ -97,8 +106,8 @@ describe("C#", () => { ["method", "IsFree", ["is", "free"]], ["class", "IO", ["io"]], ["class", "IOError", ["io", "error"]], - ["class", "Repository`1", ["repository"], 1], - ["namespace", "Acme.Order.Adapter", ["acme", "order", "adapter"]], + ["class", "Repository`1", ["repository"], { numeric: 1 }], + ["namespace", "Acme.Order.Adapter", ["acme", "order", "adapter"], { qualifier: 2 }], ["method", "op_Addition", null], ["property", "this[]", null], ["method", "ToString", null], @@ -107,10 +116,10 @@ describe("C#", () => { describe("TypeScript", () => { check("ts", [ - ["module", "legacy/acme-report.ts", ["legacy", "acme", "report"]], - ["module", "packages/order/src/promo#2024.ts", ["packages", "order", "src", "promo"], 1], - ["module", "packages/order/src/ambient.d.ts", ["packages", "order", "src", "ambient", "d"]], - ["module", "packages/order/src/ui/order-table.tsx", ["packages", "order", "src", "ui", "order", "table"]], + ["module", "legacy/acme-report.ts", ["legacy", "acme", "report"], { qualifier: 1 }], + ["module", "packages/order/src/promo#2024.ts", ["packages", "order", "src", "promo"], { numeric: 1, qualifier: 3 }], + ["module", "packages/order/src/ambient.d.ts", ["packages", "order", "src", "ambient", "d"], { qualifier: 3 }], + ["module", "packages/order/src/ui/order-table.tsx", ["packages", "order", "src", "ui", "order", "table"], { qualifier: 4 }], ["module", "legacy-lib", ["legacy", "lib"]], // The extension is dropped for a FILE kind only. ["class", "legacy/acme.ts", ["legacy", "acme", "ts"]], @@ -125,7 +134,7 @@ describe("TypeScript", () => { describe("JavaScript", () => { check("js", [ - ["module", "src/app.mjs", ["src", "app"]], + ["module", "src/app.mjs", ["src", "app"], { qualifier: 1 }], ["module", ".eslintrc", ["eslintrc"]], ["method", "toJSON", null], ]); @@ -133,13 +142,13 @@ describe("JavaScript", () => { describe("Ruby", () => { check("rb", [ - ["class", "AcmeOrder::Order::Line", ["acme", "order", "order", "line"]], - ["constant", "AcmeOrder::Order::MAX_LINES", ["acme", "order", "order", "max", "lines"]], + ["class", "AcmeOrder::Order::Line", ["acme", "order", "order", "line"], { qualifier: 3 }], + ["constant", "AcmeOrder::Order::MAX_LINES", ["acme", "order", "order", "max", "lines"], { qualifier: 3 }], ["method", "valid?", ["valid"]], ["method", "save!", ["save"]], ["method", "name=", ["name"]], ["field", "@total", ["total"]], - ["file", "lib/acme_order/promo#2024.rb", ["lib", "acme", "order", "promo"], 1], + ["file", "lib/acme_order/promo#2024.rb", ["lib", "acme", "order", "promo"], { numeric: 1, qualifier: 3 }], ["file", "Gemfile", ["gemfile"]], ["file", "acme_order.gemspec", ["acme", "order"]], ["method", "<=>", null], @@ -152,9 +161,9 @@ describe("Ruby", () => { describe("Elixir", () => { check("ex", [ // A module's dots are not an extension: `module` is not a file kind here. - ["module", "AcmeOrder.Notifier.Sms.Gateway", ["acme", "order", "notifier", "sms", "gateway"]], - ["file", "lib/acme_order/notifier.ex", ["lib", "acme", "order", "notifier"]], - ["file", "test/acme_order/order_test.exs", ["test", "acme", "order", "order", "test"]], + ["module", "AcmeOrder.Notifier.Sms.Gateway", ["acme", "order", "notifier", "sms", "gateway"], { qualifier: 4 }], + ["file", "lib/acme_order/notifier.ex", ["lib", "acme", "order", "notifier"], { qualifier: 3 }], + ["file", "test/acme_order/order_test.exs", ["test", "acme", "order", "order", "test"], { qualifier: 3 }], ["function", "available?", ["available"]], ["function", "fail!", ["fail"]], ["attribute", "@max_lines", ["max", "lines"]], @@ -195,8 +204,8 @@ describe("Rust", () => { describe("PHP", () => { check("php", [ - ["codeFile", "src/Order/OrderRepository.php", ["src", "order", "order", "repository"]], - ["namespace", "App\\Order", ["app", "order"]], + ["codeFile", "src/Order/OrderRepository.php", ["src", "order", "order", "repository"], { qualifier: 2 }], + ["namespace", "App\\Order", ["app", "order"], { qualifier: 1 }], ["property", "$orderTotal", ["order", "total"]], ["method", "__construct", null], ["method", "__toString", null], @@ -209,11 +218,48 @@ describe("Clojure", () => { ["function", "order-total", ["order", "total"]], ["function", "valid?", ["valid"]], ["var", "*out*", ["out"]], - ["namespace", "acme.order.core", ["acme", "order", "core"]], + ["namespace", "acme.order.core", ["acme", "order", "core"], { qualifier: 2 }], ["function", "-main", null], ]); }); +// The words before a qualified kind's LAST separator name its container: they +// are still words, and the split says how many lead the list. +describe("qualified names", () => { + describe("in the shipped lexicons", () => { + check("rb", [ + ["module", "AcmeOrder", ["acme", "order"]], + // `::` is the separator: one colon is a break between words, nothing more. + ["class", "Order:Line", ["order", "line"]], + ["class", "AcmeOrder::", ["acme", "order"], { qualifier: 2 }], + ["file", "lib/2024/order.rb", ["lib", "order"], { numeric: 1, qualifier: 1 }], + ["method", "AcmeOrder::Order.create", ["acme", "order", "order", "create"]], + ]); + check("ex", [ + ["protocol", "AcmeOrder.Priceable", ["acme", "order", "priceable"], { qualifier: 2 }], + ["module", "AcmeOrder.Priceable.AcmeOrder.Money", ["acme", "order", "priceable", "acme", "order", "money"], { qualifier: 5 }], + ["module", "legacy.mod", ["legacy", "mod"], { qualifier: 1 }], + ["attribute", "@max_lines", ["max", "lines"]], + ]); + }); + + describe("by own kind only", () => { + // `constructor` is a Java kind and a key every object inherits: read as an + // inherited key, `Object` itself would be the separator, spelled as its source. + check({ keywords: [], conventional: [], qualified: { package: "." } }, [ + ["constructor", "function Object() { [native code] }", ["function", "object", "native", "code"]], + ]); + }); + + describe("with an interface prefix", () => { + // The marker leads the entity's OWN name, after its qualifier. + check({ keywords: [], conventional: [], qualified: { interface: "." }, interfacePrefix: true }, [ + ["interface", "Acme.IOrder", ["acme", "order"], { qualifier: 1 }], + ["interface", "IOrder", ["order"]], + ]); + }); +}); + describe("the unicode fixture through the splitter", () => { // fixtures/unicode exists to break code-unit assumptions: a fullwidth letter // and a supplementary-plane letter (a surrogate pair in UTF-16). @@ -308,6 +354,7 @@ describe("properties", () => { const right = splitIdentifier(b, "class", undefined); expect(splitIdentifier(a + sep + b, "class", undefined)).toEqual({ tokens: [...left.tokens, ...right.tokens], + qualifier: 0, numeric: left.numeric + right.numeric, conventional: false, }); @@ -315,11 +362,43 @@ describe("properties", () => { ); }); + // Qualification decides which words weigh as a container's, never which + // words there are. C# is left out: its interface prefix reads the own name. + it("moves no word: the same split with the qualified kinds removed", () => { + const plain = fc.constantFrom(lexiconOf("java"), lexiconOf("ts"), lexiconOf("rb"), lexiconOf("ex"), lexiconOf("php")); + const anyKind = fc.constantFrom("class", "module", "file", "package", "constant", "protocol", "method"); + fc.assert( + fc.property(identifier, anyKind, plain, (name, kind, lexicon) => { + const { qualifier, ...split } = splitIdentifier(name, kind, lexicon); + const { qualifier: none, ...unqualified } = splitIdentifier(name, kind, { ...lexicon, qualified: undefined }); + expect(split).toEqual(unqualified); + expect(none).toBe(0); + expect(qualifier).toBeLessThanOrEqual(split.tokens.length); + if (lexicon.qualified?.[kind] === undefined) expect(qualifier).toBe(0); + }), + ); + }); + + it("counts as the qualifier the words before the last separator", () => { + const cases = fc.constantFrom(["rb", "class", "::"], ["ex", "module", "."], ["java", "package", "."]) ; + fc.assert( + fc.property(cases, identifier, identifier, ([lang, kind, sep], a, b) => { + fc.pre(!b.includes(sep)); + const split = splitIdentifier(a + sep + b, kind, lexiconOf(lang)); + fc.pre(!split.conventional); + const left = splitIdentifier(a, kind, undefined); + const right = splitIdentifier(b, kind, undefined); + expect(split.tokens).toEqual([...left.tokens, ...right.tokens]); + expect(split.qualifier).toBe(left.tokens.length); + }), + ); + }); + it("gives a conventional name no word at all", () => { const java = lexiconOf("java"); fc.assert( fc.property(fc.constantFrom(...java.conventional), kinds, (name, kind) => { - expect(splitIdentifier(name, kind, java)).toEqual({ tokens: [], numeric: 0, conventional: true }); + expect(splitIdentifier(name, kind, java)).toEqual({ tokens: [], qualifier: 0, numeric: 0, conventional: true }); }), ); }); diff --git a/packages/vocabulary/test/toy.ts b/packages/vocabulary/test/toy.ts new file mode 100644 index 0000000..26e2220 --- /dev/null +++ b/packages/vocabulary/test/toy.ts @@ -0,0 +1,103 @@ +import type { Edge, Entity, Model } from "@codegraph/core"; +import { buildGraph, loadModels, type CodeGraph } from "@codegraph/analyzer"; + +/** Toy corpora for the aggregation: entities written as an extractor would. */ + +export function graphOf(models: readonly { lang: string; entities: readonly Entity[]; edges?: readonly Edge[] }[]): CodeGraph { + const payloads: Model[] = models.map(({ lang, entities, edges = [] }) => ({ + schemaVersion: "1.0.0", + lang, + extractor: { name: "test", version: "0.0.0" }, + root: `toy/${lang}`, + entities: [...entities], + edges: [...edges], + })); + return buildGraph(loadModels(payloads, { sources: payloads.map((model) => `toy-${model.lang}`) }).union); +} + +const child = (parent: string | undefined): Record => + parent === undefined ? {} : { parent }; +const childOf = (parent: string | undefined): readonly string[] => (parent === undefined ? [] : ["TChildOf"]); + +export function pkg(id: string, name: string, parent?: string, isStub = false): Entity { + return { + id, + kind: "package", + traits: ["TNamed", "TWithChildren", "TModule", ...childOf(parent)], + name, + isStub, + definedIn: isStub ? [] : ["T.java"], + ...child(parent), + } as Entity; +} + +export function cls(id: string, name: string, parent?: string, isStub = false): Entity { + return { + id, + kind: "class", + traits: ["TNamed", "TWithChildren", "TType", ...childOf(parent)], + name, + isStub, + ...child(parent), + } as Entity; +} + +export function method( + id: string, + name: string | undefined, + parent: string, + slots: { parameters?: readonly string[]; localVariables?: readonly string[] } = {}, +): Entity { + return { + id, + kind: name === undefined ? "constructor" : "method", + traits: [ + ...(name === undefined ? [] : ["TNamed"]), + "TInvocable", + "TWithParameters", + "TWithLocalVariables", + "TChildOf", + ], + ...(name === undefined ? {} : { name }), + signature: `${name ?? ""}()`, + parameters: [...(slots.parameters ?? [])], + localVariables: [...(slots.localVariables ?? [])], + parent, + } as Entity; +} + +export function structural(id: string, kind: string, name: string, parent: string): Entity { + return { id, kind, traits: ["TNamed", "TStructural", "TChildOf"], name, parent } as Entity; +} + +/** + * The hand-counted corpus of aggregate.test.ts: two packages, three types (one + * nested), a method with a parameter and a local, a conventional and an + * unnamed member, and two stubs whose names must never count. + */ +export function orderCorpus(): readonly Entity[] { + const order = "java:com.acme.order"; + const billing = "java:com.acme.order.billing"; + const Order = `${order}/Order`; + const getTotal = `${Order}.getTotal(Line)`; + const Line = `${Order}$Line`; + return [ + pkg(order, "com.acme.order"), + pkg(billing, "com.acme.order.billing", order), + cls(Order, "Order", order), + method(getTotal, "getTotal", Order, { + parameters: [`${getTotal}#param:orderLine`], + localVariables: [`${getTotal}#local:total`], + }), + structural(`${getTotal}#param:orderLine`, "parameter", "orderLine", getTotal), + structural(`${getTotal}#local:total`, "localVariable", "total", getTotal), + method(`${Order}.toString()`, "toString", Order), + method(`${Order}.()`, undefined, Order), + structural(`${Order}#lines`, "attribute", "lines", Order), + cls(Line, "Line", Order), + structural(`${Line}#qty`, "attribute", "qty", Line), + cls(`${billing}/Invoice`, "Invoice", billing), + pkg("java:java.lang", "java.lang", undefined, true), + cls("java:java.lang/String", "String", "java:java.lang", true), + ]; +} diff --git a/packages/vocabulary/test/weight.test.ts b/packages/vocabulary/test/weight.test.ts index b1de65f..aac8cc0 100644 --- a/packages/vocabulary/test/weight.test.ts +++ b/packages/vocabulary/test/weight.test.ts @@ -2,7 +2,7 @@ import { fileURLToPath } from "node:url"; import { PROFILES, TRAIT_NAMES, readModelFileSync, type Entity, type TraitName } from "@codegraph/core"; import fc from "fast-check"; import { describe, expect, it } from "vitest"; -import { DEFAULT_WEIGHTS, ROLES, roleOf, structuralSlots, type Role } from "../src/weight.js"; +import { DEFAULT_WEIGHTS, ROLES, WEIGHT_KEYS, roleOf, structuralSlots, type Role } from "../src/weight.js"; /** * The role of the entity carrying a name decides the weight of its words @@ -30,8 +30,15 @@ describe("the roles and their default weights", () => { parameter: 1, local: 1, other: 1, + qualifier: 1, }); - expect(Object.keys(DEFAULT_WEIGHTS).sort()).toEqual([...ROLES].sort()); + }); + + // A qualifier (`AcmeOrder::` of `AcmeOrder::Order`) is not a role: it is the + // part of a name that names a container, whatever the name's role. + it("weigh every role, and a qualifier's words, once each", () => { + expect(WEIGHT_KEYS).toEqual([...ROLES, "qualifier"]); + expect(Object.keys(DEFAULT_WEIGHTS).sort()).toEqual([...WEIGHT_KEYS].sort()); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb7c213..0bd25a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -373,6 +373,9 @@ importers: packages/vocabulary: dependencies: + '@codegraph/analyzer': + specifier: workspace:* + version: link:../analyzer '@codegraph/core': specifier: workspace:* version: link:../core diff --git a/website/content/docs/reference/metamodel/profiles.md b/website/content/docs/reference/metamodel/profiles.md index bc73be4..b44dd55 100644 --- a/website/content/docs/reference/metamodel/profiles.md +++ b/website/content/docs/reference/metamodel/profiles.md @@ -11,7 +11,7 @@ A profile is **data, not code**: the contract stating what a given language's ex | `kinds` | `Record` | the licit trait compositions per entity kind | | `edges` | EdgeKind[] | edge kinds this language can emit | | `notes` | string[] | documented static-analysis blind spots (reflection, `Class.forName`, dynamic `require`, macros pre-expansion, …) | -| `lexicon` | `{keywords, conventional, technical?, fileKinds?, interfacePrefix?}` | the language's words, for the vocabulary — see [The lexicon](#the-lexicon) | +| `lexicon` | `{keywords, conventional, technical?, fileKinds?, qualified?, interfacePrefix?}` | the language's words, for the vocabulary — see [The lexicon](#the-lexicon) | The eleven shipped profiles are rendered in full on [Language profiles](/docs/reference/profiles/). @@ -45,9 +45,10 @@ The vocabulary splits every name into words — `OrderManagerRepository` is `ord | `conventional` | whole identifiers the language or its runtime dictates — overrides, protocol members, operators (`toString`, `__init__`, `<=>`). Their words say nothing about the domain, so such a name contributes none | | `technical` | plumbing words of the language beyond the vocabulary's shared list, spelled as the splitter yields them | | `fileKinds` | kinds whose name is a file path (TypeScript's `module`, Ruby's `file`): the extension is not a word | +| `qualified` | kinds the language names by a qualified path, each with the separator that ends the qualifier: a Java `package` by `.`, a Ruby `class` by `::`, every file kind by `/`. The words before the last separator name a container (`AcmeOrder::` of `AcmeOrder::Order`), so they weigh as incidental words — still counted, never dropped | | `interfacePrefix` | `true` in C# alone: `IOrderService` names an interface `OrderService` | -Profile validation rejects an entry that could never do what its list says: one listed twice, a keyword with no letter, a technical entry the splitter cannot yield, a name a keyword already claims, a file kind the profile does not define. +Profile validation rejects an entry that could never do what its list says: one listed twice, a keyword with no letter, a technical entry the splitter cannot yield, a name a keyword already claims, a file or qualified kind the profile does not define, a separator that is empty or would cut a word. ## Framework profiles diff --git a/website/content/docs/reference/profiles.md b/website/content/docs/reference/profiles.md index f54494c..7e19ebc 100644 --- a/website/content/docs/reference/profiles.md +++ b/website/content/docs/reference/profiles.md @@ -57,6 +57,7 @@ A profile that is specified but has no shipped extractor is complete, not defici - Reserved words: `def` · `if` · `do` · `let` · `quote` · `var` · `fn` · `loop` · `recur` · `throw` · `try` · `catch` · `finally` · `monitor-enter` · `monitor-exit` · `new` · `set!` · `nil` · `true` · `false` - Conventional names: `-main` · `-init` · `-post-init` · `-toString` · `-equals` · `-hashCode` - Technical words: `coll` · `kw` · `ns` · `seq` · `sym` · `xf` · `xform` +- Qualified kinds: `namespace` by `.` **Notes** @@ -101,6 +102,7 @@ A profile that is specified but has no shipped extractor is complete, not defici - Reserved words: `abstract` · `as` · `base` · `bool` · `break` · `byte` · `case` · `catch` · `char` · `checked` · `class` · `const` · `continue` · `decimal` · `default` · `delegate` · `do` · `double` · `else` · `enum` · `event` · `explicit` · `extern` · `false` · `finally` · `fixed` · `float` · `for` · `foreach` · `goto` · `if` · `implicit` · `in` · `int` · `interface` · `internal` · `is` · `lock` · `long` · `namespace` · `new` · `null` · `object` · `operator` · `out` · `override` · `params` · `private` · `protected` · `public` · `readonly` · `ref` · `return` · `sbyte` · `sealed` · `short` · `sizeof` · `stackalloc` · `static` · `string` · `struct` · `switch` · `this` · `throw` · `true` · `try` · `typeof` · `uint` · `ulong` · `unchecked` · `unsafe` · `ushort` · `using` · `virtual` · `void` · `volatile` · `while` - Conventional names: `ToString` · `Equals` · `GetHashCode` · `Finalize` · `Main` · `CompareTo` · `Dispose` · `DisposeAsync` · `GetEnumerator` · `GetAsyncEnumerator` · `MoveNext` · `MoveNextAsync` · `Clone` · `Deconstruct` · `this[]` · `op_Addition` · `op_Subtraction` · `op_Multiply` · `op_Division` · `op_Modulus` · `op_Equality` · `op_Inequality` · `op_LessThan` · `op_GreaterThan` · `op_LessThanOrEqual` · `op_GreaterThanOrEqual` · `op_Implicit` · `op_Explicit` · `op_UnaryNegation` · `op_UnaryPlus` · `op_LogicalNot` · `op_OnesComplement` · `op_Increment` · `op_Decrement` · `op_True` · `op_False` · `op_BitwiseAnd` · `op_BitwiseOr` · `op_ExclusiveOr` · `op_LeftShift` · `op_RightShift` · `op_UnsignedRightShift` - Technical words: `attribute` · `enumerable` · `enumerator` · `extension` · `extensions` · `linq` +- Qualified kinds: `namespace` by `.` - Interface prefix: a leading `I` before Upper + lower is a marker, not a word **Notes** @@ -149,6 +151,7 @@ A profile that is specified but has no shipped extractor is complete, not defici - Conventional names: `__using__` · `__before_compile__` · `__after_compile__` · `__after_verify__` · `__on_definition__` · `__struct__` · `__info__` · `__deriving__` · `__impl__` · `__protocol__` · `start_link` · `child_spec` · `init` · `handle_call` · `handle_cast` · `handle_info` · `handle_continue` · `terminate` · `code_change` · `format_status` · `to_string` · `inspect` · `@moduledoc` · `@doc` · `@typedoc` · `@spec` · `@type` · `@typep` · `@opaque` · `@callback` · `@macrocallback` · `@optional_callbacks` · `@impl` · `@behaviour` · `@derive` · `@enforce_keys` · `@external_resource` · `@compile` · `@deprecated` · `@dialyzer` · `@on_load` · `@vsn` · `@before_compile` · `@after_compile` · `@after_verify` · `@on_definition` · `@nifs` · `@fallback_to_any` · `@file` - Technical words: `attrs` · `behaviour` · `changeset` · `conn` · `ecto` · `ets` · `gen` · `live` · `mix` · `otp` · `phoenix` · `pid` · `plug` · `schema` · `server` · `socket` · `sup` · `supervisor` · `web` - File kinds: `file` +- Qualified kinds: `file` by `/` · `module` by `.` · `protocol` by `.` **Notes** @@ -233,6 +236,7 @@ A profile that is specified but has no shipped extractor is complete, not defici - Reserved words: `abstract` · `assert` · `boolean` · `break` · `byte` · `case` · `catch` · `char` · `class` · `const` · `continue` · `default` · `do` · `double` · `else` · `enum` · `extends` · `final` · `finally` · `float` · `for` · `goto` · `if` · `implements` · `import` · `instanceof` · `int` · `interface` · `long` · `native` · `new` · `package` · `private` · `protected` · `public` · `return` · `short` · `static` · `strictfp` · `super` · `switch` · `synchronized` · `this` · `throw` · `throws` · `transient` · `try` · `void` · `volatile` · `while` · `true` · `false` · `null` - Conventional names: `toString` · `equals` · `hashCode` · `clone` · `finalize` · `main` · `compareTo` · `iterator` · `hasNext` · `valueOf` · `serialVersionUID` · `readObject` · `writeObject` · `readObjectNoData` · `readResolve` · `writeReplace` - Technical words: `annotation` · `bean` · `beans` · `ejb` · `jaxb` · `jdbc` · `jndi` · `jpa` · `pojo` · `servlet` +- Qualified kinds: `package` by `.` **Notes** @@ -275,6 +279,7 @@ A profile that is specified but has no shipped extractor is complete, not defici - Conventional names: `constructor` · `toString` · `toLocaleString` · `valueOf` · `toJSON` · `[Symbol.iterator]` · `[Symbol.asyncIterator]` · `[Symbol.hasInstance]` · `[Symbol.toPrimitive]` · `[Symbol.toStringTag]` · `[Symbol.dispose]` · `[Symbol.asyncDispose]` - Technical words: `cb` · `fn` · `promise` · `prototype` - File kinds: `module` +- Qualified kinds: `module` by `/` **Notes** @@ -317,6 +322,7 @@ A profile that is specified but has no shipped extractor is complete, not defici - Conventional names: `__construct` · `__destruct` · `__call` · `__callStatic` · `__get` · `__set` · `__isset` · `__unset` · `__sleep` · `__wakeup` · `__serialize` · `__unserialize` · `__toString` · `__invoke` · `__set_state` · `__clone` · `__debugInfo` - Technical words: `arr` · `closure` · `facade` · `middleware` · `php` - File kinds: `codeFile` +- Qualified kinds: `codeFile` by `/` · `namespace` by `\` **Notes** @@ -352,6 +358,7 @@ A profile that is specified but has no shipped extractor is complete, not defici - Reserved words: `False` · `None` · `True` · `and` · `as` · `assert` · `async` · `await` · `break` · `class` · `continue` · `def` · `del` · `elif` · `else` · `except` · `finally` · `for` · `from` · `global` · `if` · `import` · `in` · `is` · `lambda` · `nonlocal` · `not` · `or` · `pass` · `raise` · `return` · `try` · `while` · `with` · `yield` - Conventional names: `self` · `cls` · `__init__` · `__new__` · `__del__` · `__repr__` · `__str__` · `__bytes__` · `__format__` · `__hash__` · `__bool__` · `__eq__` · `__ne__` · `__lt__` · `__le__` · `__gt__` · `__ge__` · `__len__` · `__iter__` · `__next__` · `__aiter__` · `__anext__` · `__reversed__` · `__contains__` · `__getitem__` · `__setitem__` · `__delitem__` · `__missing__` · `__getattr__` · `__getattribute__` · `__setattr__` · `__delattr__` · `__dir__` · `__get__` · `__set__` · `__delete__` · `__set_name__` · `__call__` · `__enter__` · `__exit__` · `__aenter__` · `__aexit__` · `__await__` · `__init_subclass__` · `__class_getitem__` · `__post_init__` · `__add__` · `__sub__` · `__mul__` · `__matmul__` · `__truediv__` · `__floordiv__` · `__mod__` · `__pow__` · `__neg__` · `__pos__` · `__abs__` · `__invert__` · `__and__` · `__or__` · `__xor__` · `__lshift__` · `__rshift__` · `__int__` · `__float__` · `__index__` · `__round__` · `__copy__` · `__deepcopy__` · `__reduce__` · `__getstate__` · `__setstate__` · `__slots__` · `__all__` · `__name__` · `__main__` · `__doc__` · `__dict__` - Technical words: `dict` · `kwargs` · `metaclass` · `py` · `tuple` +- Qualified kinds: `module` by `.` **Notes** @@ -386,6 +393,7 @@ A profile that is specified but has no shipped extractor is complete, not defici - Conventional names: `initialize` · `initialize_copy` · `to_s` · `to_str` · `to_a` · `to_ary` · `to_h` · `to_hash` · `to_proc` · `inspect` · `hash` · `eql?` · `==` · `===` · `=~` · `!~` · `!=` · `<=>` · `<` · `>` · `<=` · `>=` · `+` · `-` · `*` · `/` · `%` · `**` · `<<` · `>>` · `&` · `|` · `^` · `~` · `!` · `+@` · `-@` · `[]` · `[]=` · `each` · `coerce` · `method_missing` · `respond_to_missing?` · `respond_to?` · `included` · `extended` · `inherited` · `prepended` · `method_added` · `const_missing` - Technical words: `accessor` · `attr` · `concern` · `concerns` · `ext` · `gem` · `gemspec` · `klass` · `lambda` · `proc` · `rake` · `rakefile` - File kinds: `file` +- Qualified kinds: `file` by `/` · `class` by `::` · `module` by `::` · `constant` by `::` **Notes** @@ -487,6 +495,7 @@ Declaration spaces: `abstractClass`, `arrowFunction`, `class`, `enum`, `function - Conventional names: `constructor` · `toString` · `toLocaleString` · `valueOf` · `toJSON` · `[Symbol.iterator]` · `[Symbol.asyncIterator]` · `[Symbol.hasInstance]` · `[Symbol.toPrimitive]` · `[Symbol.toStringTag]` · `[Symbol.dispose]` · `[Symbol.asyncDispose]` - Technical words: `cb` · `fn` · `promise` · `prototype` - File kinds: `module` +- Qualified kinds: `module` by `/` **Notes** diff --git a/website/scripts/profiles-page.mjs b/website/scripts/profiles-page.mjs index 5b4047c..7db2ab5 100644 --- a/website/scripts/profiles-page.mjs +++ b/website/scripts/profiles-page.mjs @@ -31,7 +31,7 @@ function parse(text) { // ` keywords a, b` … up to the blank line; absent rows stay absent. const lexicon = {}; for (let i = lines.indexOf("lexicon:") + 1; i > 0 && i < lines.length && lines[i] !== ""; i++) { - const row = /^ {2}(keywords|conventional|technical|file kinds|interface prefix)\s+(.*)$/.exec(lines[i]); + const row = /^ {2}(keywords|conventional|technical|file kinds|qualified|interface prefix)\s+(.*)$/.exec(lines[i]); if (row) lexicon[row[1]] = row[2] === "(none)" ? [] : row[2].split(", "); } const notes = []; @@ -91,6 +91,9 @@ for (const lang of langs) { out.push("- Conventional names: " + words(lx["conventional"])); if (lx["technical"]) out.push("- Technical words: " + words(lx["technical"])); if (lx["file kinds"]) out.push("- File kinds: " + words(lx["file kinds"])); + // ` qualified class ::, file /`: a kind, a space, its separator. + const by = (entry) => entry.replace(/^(\S+) (.+)$/, "`$1` by `$2`"); + if (lx["qualified"]) out.push("- Qualified kinds: " + lx["qualified"].map(by).join(" · ")); if (lx["interface prefix"]) out.push("- Interface prefix: a leading `I` before Upper + lower is a marker, not a word"); out.push("", "**Notes**", ""); for (const note of p.notes) out.push("- " + note);