Skip to content

Profiles

A profile is data, not code: the contract stating what a given language’s extractor may produce. A profile must be specifiable without being implemented.

AttributeTypeMeaning
langstringlanguage id, matches the EntityId prefix
kindsRecord<kind, {required: TraitName[], optional: TraitName[]}>the licit trait compositions per entity kind
edgesEdgeKind[]edge kinds this language can emit
notesstring[]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 eleven shipped profiles are rendered in full on Language profiles.

Validation

Validation of an entity against its profile:

  1. kind exists in the profile;
  2. required(kind) ⊆ entity.traits ⊆ required(kind) ∪ optional(kind) — strict equality rejected (too brittle for TComment), free subset rejected (hides extractor bugs);
  3. each declared trait’s attributes are present and well-typed.

Relations: licenses entities and edges; cross-language analyses operate on the intersection of the profiles involved — in practice the import layer plus whatever both profiles share.

Vocabularies are closed referential sets

Kinds, trait names, edge kinds and provenance values are finite sets owned by core. An encoding may represent a member by reference — an index into a dictionary, a foreign key — as long as the reference resolves to a canonical name that core validates. An unknown name is a hard error, never a passthrough. This is what licenses the header dictionaries of the model.jsonl encoding and the lookup tables of the model.db store.

Validity is a function of (kind, trait set)

The rule above reads only the kind and the set of traits — never the entity carrying them. So a consumer may decide each distinct (kind, trait set) pair once and share the verdict across every entity with that composition. Step 3 — the trait-key check — still runs per entity, because the keys’ values differ.

Two bounds on that licence: a stub’s exemption from the lower bound is an entity-level fact, so it is part of what the verdict is keyed on; and the verdict is per profile, since two profiles may name the same kind under different rules. It is a reader-side optimisation and requires no encoding support.

The lexicon

The vocabulary splits every name into words — OrderManagerRepository is order, manager, repository — and tags each one. Splitting is universal: every character that is not a letter, a mark or a digit ends a word, and so does a camel-case boundary, so sigils, separators and suffixes (@total, valid?, Order::Line) need no data. What a language adds is its lexicon, optional on a profile and present on every shipped one:

KeyMeaning
keywordsthe specification’s reserved words, as the language spells them. A contextual keyword (record, var, TypeScript’s type) is a legal identifier, often a domain word, and is not listed
conventionalwhole 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
technicalplumbing words of the language beyond the vocabulary’s shared list, spelled as the splitter yields them
fileKindskinds whose name is a file path (TypeScript’s module, Ruby’s file): the extension is not a word
interfacePrefixtrue 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.

Framework profiles

The same rule one level up, consumed by the analyzer rather than the extractor. What an annotation means — @Autowired marks an injection point, @Service a stereotype, @Qualifier narrows candidates, @Primary wins a tie — is a declarative table mapping annotation identity (simple name plus declaring module) to a role. It lives in the analyzer: the extractor stays framework-blind, and the wiring a framework profile licenses is derived in memory, never serialized.

Matching reads the referenced entity’s name and parent chain — never a parsed id — and tolerates the target being a stub, which is the normal case for a corpus whose framework jars are absent. Injection points and roles are selected on annotationUse edges, and qualifier narrowing reads their arguments.

A framework RULE that is not an annotation is data too: Spring 4.3+ treats the sole constructor of a bean as an injection point with nothing written on it, so the profile carries that as a flag. A meta-annotated stereotype is deliberately NOT followed: resolving @MyService to the @Service it carries needs the annotation type’s own declaration, which is exactly what a stub does not have — so such a type is unclassified rather than classified by a guess.

--framework spring is accepted by analyze, city, domain-facts and explain.

Last updated on