@eriknaslund/skiss 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +88 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1071 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +174 -0
- package/dist/index.js +926 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/resolve.ts","../src/generators/linkml.ts","../src/generators/mermaid.ts","../src/parse.ts","../src/serialize.ts","../src/compile.ts","../src/diagnostics.ts","../src/index.ts"],"sourcesContent":["// The `skiss` command. Node-only, and the only file in the package that\n// touches files or `process` (ADR 0003). A thin layer: read, call the\n// library, write. Anything that looks like logic belongs in the library.\n\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { basename, extname } from 'node:path';\nimport { parseArgs } from 'node:util';\nimport { compile, formatDiagnostic, VERSION } from './index.ts';\n\nconst HELP = `Usage: skiss <command> [options]\n\nCommands:\n diagram <file> Write a Mermaid class diagram for a .skiss file.\n compile <file> Write a LinkML schema for a .skiss file.\n <file> may be - to read standard input.\n\nOptions:\n -o, --output <path> Write the output to <path> instead of standard output\n --notes diagram: emit \\`? text\\` doubts as Mermaid notes\n --json compile: write the schema as JSON instead of YAML\n --name <name> compile: the schema name. Defaults to the file's\n basename without its extension, or \\`sketch\\` for\n standard input.\n --strict Exit 1 when the input has any diagnostic\n -h, --help Show this help and exit\n --version Print the version and exit\n\nDiagnostics go to standard error, one per line:\n <file>:<line>:<col>: <severity> <CODE> <message>\n\nExit codes:\n 0 the output was produced\n 1 --strict and the input has diagnostics (the output is still written)\n 2 usage error, missing file or unreadable input\n 70 internal error, a bug in skiss\n`;\n\n/** Issue #6, AC4 and AC5. 70 is `EX_SOFTWARE` from `sysexits.h`. */\nconst EXIT_OK = 0;\nconst EXIT_STRICT = 1;\nconst EXIT_USAGE = 2;\nconst EXIT_INTERNAL = 70;\n\n/**\n * Which options each command takes. `--help` and `--version` are answered\n * before a command is read and are not listed. An option that belongs to the\n * other command is a usage error rather than something silently ignored.\n */\nconst COMMANDS = {\n diagram: ['output', 'notes', 'strict'],\n compile: ['output', 'json', 'name', 'strict'],\n} as const;\n\ntype Command = keyof typeof COMMANDS;\n\nconst isCommand = (text: string): text is Command => Object.hasOwn(COMMANDS, text);\n\n/** Thrown for anything that ends the run with exit code 2. */\nclass UsageError extends Error {}\n\n/**\n * Our own `UsageError`, or a `parseArgs` rejection (unknown option, `-o`\n * with no path), whose `code` is one of the `ERR_PARSE_ARGS_*` family.\n */\nfunction isUsageError(error: unknown): boolean {\n if (error instanceof UsageError) return true;\n if (typeof error !== 'object' || error === null || !('code' in error)) return false;\n return typeof error.code === 'string' && error.code.startsWith('ERR_PARSE_ARGS');\n}\n\nasync function main(argv: string[]): Promise<number> {\n if (argv.length === 0) {\n process.stderr.write(HELP);\n return EXIT_USAGE;\n }\n\n const { values, positionals } = parseArgs({\n args: argv,\n options: {\n output: { type: 'string', short: 'o' },\n notes: { type: 'boolean' },\n json: { type: 'boolean' },\n name: { type: 'string' },\n strict: { type: 'boolean' },\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean' },\n },\n allowPositionals: true,\n strict: true,\n });\n\n if (values.help === true) {\n process.stdout.write(HELP);\n return EXIT_OK;\n }\n if (values.version === true) {\n process.stdout.write(`${VERSION}\\n`);\n return EXIT_OK;\n }\n\n const [command, file, ...extra] = positionals;\n if (command === undefined) throw new UsageError('missing command');\n if (!isCommand(command)) throw new UsageError(`unknown command \\`${command}\\``);\n if (file === undefined)\n throw new UsageError(`${command} needs a <file>, or - for standard input`);\n if (extra.length > 0) throw new UsageError(`unexpected argument \\`${extra[0]}\\``);\n\n // `parseArgs` leaves an option out of `values` when it was not given, so\n // the keys here are exactly what was typed.\n const accepted: readonly string[] = COMMANDS[command];\n for (const option of Object.keys(values)) {\n if (!accepted.includes(option)) {\n throw new UsageError(`\\`${command}\\` does not take \\`--${option}\\``);\n }\n }\n\n const source = file === '-' ? await readStdin() : await readSource(file);\n const { output, diagnostics } =\n command === 'compile'\n ? compile(source, {\n target: 'linkml',\n schemaName: values.name ?? defaultSchemaName(file),\n format: values.json === true ? 'json' : 'yaml',\n })\n : compile(source, { target: 'mermaid', notes: values.notes });\n\n const label = file === '-' ? '<stdin>' : file;\n for (const d of diagnostics) process.stderr.write(`${formatDiagnostic(d, label)}\\n`);\n\n if (values.output === undefined) process.stdout.write(output);\n else await writeOutput(values.output, output);\n\n return values.strict === true && diagnostics.length > 0 ? EXIT_STRICT : EXIT_OK;\n}\n\n/**\n * Issue #24, AC2. `toLinkML` normalises this to a valid LinkML name\n * (SPEC §5.2), so a basename with a dot or a dash in it needs nothing here.\n */\nconst defaultSchemaName = (file: string): string =>\n file === '-' ? 'sketch' : basename(file, extname(file));\n\nasync function readSource(file: string): Promise<string> {\n try {\n return await readFile(file, 'utf8');\n } catch (error) {\n throw new UsageError(`cannot read \\`${file}\\`: ${reason(error)}`);\n }\n}\n\nasync function readStdin(): Promise<string> {\n try {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) chunks.push(chunk as Buffer);\n return Buffer.concat(chunks).toString('utf8');\n } catch (error) {\n throw new UsageError(`cannot read standard input: ${reason(error)}`);\n }\n}\n\n/** D1: the path is written verbatim; no extension is inferred from the target. */\nasync function writeOutput(path: string, text: string): Promise<void> {\n try {\n await writeFile(path, text, 'utf8');\n } catch (error) {\n throw new UsageError(`cannot write \\`${path}\\`: ${reason(error)}`);\n }\n}\n\n/** The OS error code when there is one (`ENOENT`), else the message. */\nfunction reason(error: unknown): string {\n if (error instanceof Error) {\n const code = (error as NodeJS.ErrnoException).code;\n return code ?? error.message;\n }\n return String(error);\n}\n\n// `process.exitCode` rather than `process.exit()`, so a piped stdout is\n// flushed before the process ends.\nmain(process.argv.slice(2)).then(\n (code) => {\n process.exitCode = code;\n },\n (error: unknown) => {\n if (isUsageError(error)) {\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write(`skiss: ${message}\\nTry 'skiss --help' for more information.\\n`);\n process.exitCode = EXIT_USAGE;\n return;\n }\n // Anything else is a bug (`compile` never throws by contract), so the\n // stack is the useful output and the `--help` hint would mislead.\n const detail =\n error instanceof Error && error.stack !== undefined ? error.stack : String(error);\n process.stderr.write(`skiss: internal error: ${detail}\\n`);\n process.exitCode = EXIT_INTERNAL;\n },\n);\n","// The cross-line pass: SPEC §3.2, §3.8 and §7, ADR 0004.\n//\n// `parse` reads every line on its own, so anything that needs two lines to\n// know is established here: whether a referenced class or field exists,\n// whether a name is duplicated, whether a class has more than one `*`. It\n// produces warnings only, never removes a node or a diagnostic, never throws,\n// and never touches its input.\n\nimport type {\n ClassNode,\n Diagnostic,\n DiagnosticCode,\n Document,\n FieldNode,\n JoinRef,\n Name,\n TypeRef,\n} from './ast.ts';\n\n/** What `resolve` returns: a `Document` whose `undeclared` list is always present. */\nexport interface ResolvedDocument extends Document {\n undeclared: Name[];\n}\n\nexport function resolve(doc: Document): ResolvedDocument {\n // A deep copy, so nothing in the output is shared with the input and the\n // identifier flags below can be cleared without mutating it (AC1).\n const out: ResolvedDocument = {\n ...doc,\n classes: doc.classes.map(copyClass),\n diagnostics: doc.diagnostics.map((d) => ({ ...d })),\n undeclared: [],\n };\n const classes = out.classes;\n\n const declared = new Set(classes.map((c) => c.name.text));\n const warnings: Diagnostic[] = [];\n const undeclared = new Map<string, Name>();\n\n const warn = (code: DiagnosticCode, message: string, at: Name): void => {\n warnings.push({ severity: 'warning', code, message, line: at.line, col: at.col, end: at.end });\n };\n\n // `: X`, `~ X` or `= X.f` with X not declared (SPEC §3.2). The reference is\n // kept; the name goes on the list a generator draws placeholders from.\n const reference = (name: Name): boolean => {\n if (declared.has(name.text)) return true;\n if (!undeclared.has(name.text)) undeclared.set(name.text, name);\n warn('W_UNDECLARED_CLASS', `class \\`${name.text}\\` is not declared`, name);\n return false;\n };\n\n // One walk in document order keeps the warnings in line order, and on one\n // line in the order the tokens are written.\n const firstClass = new Map<string, ClassNode>();\n for (const cls of classes) {\n const earlier = firstClass.get(cls.name.text);\n if (earlier === undefined) firstClass.set(cls.name.text, cls);\n else {\n // Working default D1: both are kept, the later one carries the warning.\n warn(\n 'W_DUPLICATE_CLASS',\n `class \\`${cls.name.text}\\` is already declared on line ${earlier.line}`,\n cls.name,\n );\n }\n\n // Working default D3: `~` to the class itself is not a warning in 0.1.0.\n if (cls.similarTo !== undefined) reference(cls.similarTo);\n\n const firstField = new Map<string, FieldNode>();\n let identifier: FieldNode | undefined;\n for (const field of cls.fields) {\n const earlierField = firstField.get(field.name.text);\n if (earlierField === undefined) firstField.set(field.name.text, field);\n else {\n warn(\n 'W_DUPLICATE_FIELD',\n `field \\`${field.name.text}\\` is already declared on line ${earlierField.line} in class \\`${cls.name.text}\\``,\n field.name,\n );\n }\n\n // SPEC §3.8: at most one identifier per class, the first wins. The\n // AST does not record where the `*` sits, so the range is the field\n // name it marks (issue #4, working default D5).\n if (field.identifier) {\n if (identifier === undefined) identifier = field;\n else {\n warn(\n 'W_MULTIPLE_IDENTIFIERS',\n `\\`*\\` on \\`${field.name.text}\\` is ignored: class \\`${cls.name.text}\\` already has the identifier \\`${identifier.name.text}\\` on line ${identifier.line}`,\n field.name,\n );\n field.identifier = false;\n }\n }\n\n if (field.type?.kind === 'unknown') {\n // SPEC §3.2: any other lowercase word is an unknown type; generators\n // treat it as `string`. The node stays as written.\n const word = field.type.name.text;\n const suggestion = closestTypeWord(word);\n warn(\n 'W_UNKNOWN_TYPE',\n suggestion === undefined\n ? `unknown type \\`${word}\\`, treated as \\`string\\``\n : `unknown type \\`${word}\\`, did you mean \\`${suggestion}\\`?`,\n field.type.name,\n );\n } else if (field.type?.kind === 'class') {\n reference(field.type.name);\n }\n\n if (field.joinsTo !== undefined) {\n const { className, fieldName } = field.joinsTo;\n // Working default D2: an undeclared class is one warning, not two.\n // Any declaration of a duplicated class may carry the field.\n const hasField = (c: ClassNode): boolean =>\n c.name.text === className.text && c.fields.some((f) => f.name.text === fieldName.text);\n if (reference(className) && !classes.some(hasField)) {\n warn(\n 'W_UNDECLARED_FIELD',\n `class \\`${className.text}\\` has no field \\`${fieldName.text}\\``,\n fieldName,\n );\n }\n }\n }\n }\n\n // Idempotence (AC1): a warning the document already carries is not added\n // again, so resolving a resolved document changes nothing.\n const present = new Set(out.diagnostics.map(key));\n for (const w of warnings) {\n if (!present.has(key(w))) out.diagnostics.push(w);\n }\n // Errors are in line order from `parse`; a stable sort slots the warnings\n // in and keeps errors before warnings on the same line.\n out.diagnostics.sort((a, b) => a.line - b.line);\n\n out.undeclared = [...undeclared.values()].map((n) => ({ ...n }));\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// The copy is written out for the known AST shape rather than done with\n// `structuredClone`: that throws on a document an editor has wrapped in a\n// Proxy (a reactive store) or on a node carrying a function, and `resolve`\n// must never throw. Spreads read through a Proxy's traps and off a frozen\n// object alike.\n\nconst copyName = (n: Name): Name => ({ ...n });\n\nfunction copyType(t: TypeRef): TypeRef {\n switch (t.kind) {\n case 'primitive':\n return { ...t, written: copyName(t.written) };\n case 'class':\n case 'unknown':\n return { ...t, name: copyName(t.name) };\n case 'enum':\n return { ...t, values: t.values.map(copyName) };\n }\n}\n\nconst copyJoin = (j: JoinRef): JoinRef => ({\n className: copyName(j.className),\n fieldName: copyName(j.fieldName),\n});\n\nfunction copyField(f: FieldNode): FieldNode {\n const out: FieldNode = { ...f, name: copyName(f.name) };\n if (f.type !== undefined) out.type = copyType(f.type);\n if (f.joinsTo !== undefined) out.joinsTo = copyJoin(f.joinsTo);\n return out;\n}\n\nfunction copyClass(c: ClassNode): ClassNode {\n const out: ClassNode = { ...c, name: copyName(c.name), fields: c.fields.map(copyField) };\n if (c.system !== undefined) out.system = copyName(c.system);\n if (c.similarTo !== undefined) out.similarTo = copyName(c.similarTo);\n return out;\n}\n\nconst key = (d: Diagnostic): string =>\n [d.severity, d.code, d.line, d.col, d.end, d.message].join(' ');\n\n// ---------------------------------------------------------------------------\n// Suggestions for unknown types (AC3): the closest accepted type word within\n// Levenshtein distance 2. On equal distance the candidate sharing the longest\n// prefix with the typed word wins (`flt` is `float`, not `int`); sharing the\n// first letter is the prefix rule at length one, so it needs no extra step.\n// Primitives come before the aliases in the list so a tie that survives goes\n// to the canonical name. Working default D4: inline, no dependency.\n\nconst TYPE_WORDS = [\n 'string',\n 'int',\n 'float',\n 'bool',\n 'date',\n 'datetime',\n 'uri',\n 'text',\n 'integer',\n 'boolean',\n];\n\nconst MAX_DISTANCE = 2;\n\nfunction closestTypeWord(word: string): string | undefined {\n let best: string | undefined;\n let bestDistance = MAX_DISTANCE + 1;\n let bestPrefix = -1;\n for (const candidate of TYPE_WORDS) {\n const distance = levenshtein(word, candidate);\n if (distance > MAX_DISTANCE || distance > bestDistance) continue;\n const prefix = commonPrefixLength(word, candidate);\n if (distance < bestDistance || prefix > bestPrefix) {\n best = candidate;\n bestDistance = distance;\n bestPrefix = prefix;\n }\n }\n return best;\n}\n\nfunction commonPrefixLength(a: string, b: string): number {\n let i = 0;\n while (i < a.length && i < b.length && a[i] === b[i]) i++;\n return i;\n}\n\n/** Edit distance with unit insert, delete and substitute costs. */\nfunction levenshtein(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n let previous = Array.from({ length: b.length + 1 }, (_, i) => i);\n for (let i = 1; i <= a.length; i++) {\n const current = [i];\n for (let j = 1; j <= b.length; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n current[j] = Math.min(\n (previous[j] ?? 0) + 1,\n (current[j - 1] ?? 0) + 1,\n (previous[j - 1] ?? 0) + cost,\n );\n }\n previous = current;\n }\n return previous[b.length] ?? 0;\n}\n","// Document -> LinkML schema as a plain object. SPEC §5 is the contract and\n// the `.linkml.yaml` fixtures are its reference output, checked by the real\n// LinkML toolchain.\n//\n// No YAML here: the schema object is the generator's output and `serialize`\n// turns it into text (ADR 0006). Fields become class-local `attributes` and\n// never top-level `slots` (ADR 0005). Never throws.\n\nimport type { ClassNode, Document, FieldNode, Primitive } from '../ast.ts';\nimport { resolve } from '../resolve.ts';\n\n/** A LinkML `annotations` block in the compact `tag: value` form (SPEC §5.3). */\nexport interface LinkMLAnnotations {\n [tag: string]: string | boolean;\n}\n\n/** One entry under a class's `attributes`. Key order is SPEC §5.3. */\nexport interface LinkMLAttribute {\n description?: string;\n range?: string;\n multivalued?: boolean;\n identifier?: boolean;\n annotations?: LinkMLAnnotations;\n}\n\n/** One entry under `classes`. Key order is SPEC §5.3. */\nexport interface LinkMLClass {\n description?: string;\n annotations?: LinkMLAnnotations;\n close_mappings?: string[];\n attributes?: Record<string, LinkMLAttribute>;\n}\n\n/** One entry under `enums`. A permissible value carries no body, hence `null`. */\nexport interface LinkMLEnum {\n permissible_values: Record<string, null>;\n}\n\n/** The whole schema. Plain data: every value here survives `JSON.stringify`. */\nexport interface LinkMLSchema {\n id: string;\n name: string;\n default_prefix: string;\n default_range: string;\n prefixes: Record<string, string>;\n imports: string[];\n classes?: Record<string, LinkMLClass>;\n enums?: Record<string, LinkMLEnum>;\n}\n\nexport interface LinkMLOptions {\n /** Normally the file name. Normalised to a valid LinkML name (SPEC §5.2). */\n schemaName: string;\n}\n\n/** SPEC §5.2: the base URI is a placeholder. */\nconst BASE_URI = 'https://example.org';\n\n/** SPEC §3.2 primitives to the LinkML built-in types `linkml:types` defines. */\nconst RANGE: Record<Primitive, string> = {\n string: 'string',\n int: 'integer',\n float: 'float',\n bool: 'boolean',\n date: 'date',\n datetime: 'datetime',\n uri: 'uri',\n};\n\nexport function toLinkML(doc: Document, opts: LinkMLOptions): LinkMLSchema {\n // A document that has not been through `resolve` has no `undeclared` list\n // and may still carry a second `*`. Resolving is idempotent, so a resolved\n // document comes out unchanged and the output is the same either way.\n const resolved = resolve(doc);\n const schemaName = linkmlName(opts.schemaName);\n\n const classes = emittedClasses(resolved.classes);\n // LinkML keeps classes and enums in one namespace, so the enum names have to\n // avoid every class this schema emits, the undeclared stubs included.\n const classNames = new Set(classes.map((cls) => cls.node.name.text));\n for (const name of resolved.undeclared) classNames.add(name.text);\n const enums = nameEnums(classes, classNames);\n\n // SPEC §5.2. `linkml` and the schema's own prefix come first; a system\n // prefix that would collide with either is not allowed to overwrite it.\n // `Object.hasOwn`, not `in`: `prefixes` is a plain object, so `in` is true\n // for `constructor` and the other `Object.prototype` keys and a real prefix\n // would be dropped.\n const prefixes: Record<string, string> = { linkml: 'https://w3id.org/linkml/' };\n if (!Object.hasOwn(prefixes, schemaName)) prefixes[schemaName] = `${BASE_URI}/${schemaName}/`;\n\n // One prefix per distinct `@System`, in the order the systems are written:\n // a class line before its own fields.\n const systemPrefix = new Map<string, string>();\n const addSystem = (system: string): void => {\n const prefix = linkmlName(system);\n if (!systemPrefix.has(system)) systemPrefix.set(system, prefix);\n if (!Object.hasOwn(prefixes, prefix)) prefixes[prefix] = `${BASE_URI}/system/${prefix}/`;\n };\n for (const { node, fields } of classes) {\n if (node.system !== undefined) addSystem(node.system.text);\n for (const field of fields) {\n if (field.system !== undefined) addSystem(field.system.text);\n }\n }\n\n // SPEC §5.1, mapping prefix: `~ Other` uses the prefix of Other's system\n // when Other is declared with an `@`, otherwise the schema's own prefix.\n const declaredSystem = new Map<string, string | undefined>();\n for (const { node } of classes) {\n if (!declaredSystem.has(node.name.text)) declaredSystem.set(node.name.text, node.system?.text);\n }\n const mappingPrefix = (target: string): string => {\n const system = declaredSystem.get(target);\n if (system === undefined) return schemaName;\n return systemPrefix.get(system) ?? schemaName;\n };\n\n const schema: LinkMLSchema = {\n id: `${BASE_URI}/${schemaName}`,\n name: schemaName,\n default_prefix: schemaName,\n default_range: 'string',\n prefixes,\n imports: ['linkml:types'],\n };\n\n const out: Record<string, LinkMLClass> = {};\n for (const cls of classes) {\n out[cls.node.name.text] = buildClass(cls, mappingPrefix, enums);\n }\n // SPEC §5.1: a reference to an undeclared class becomes a stub, after the\n // classes that are declared.\n for (const name of resolved.undeclared) {\n if (!(name.text in out)) out[name.text] = { annotations: { undeclared: true } };\n }\n if (Object.keys(out).length > 0) schema.classes = out;\n\n const enumBlock = buildEnums(classes, enums);\n if (Object.keys(enumBlock).length > 0) schema.enums = enumBlock;\n\n return schema;\n}\n\n// ---------------------------------------------------------------------------\n// Which nodes are emitted at all.\n\ninterface EmittedClass {\n node: ClassNode;\n /** The class's fields with duplicate names dropped, first declaration first. */\n fields: FieldNode[];\n}\n\n/**\n * Issue #17: `resolve` keeps both nodes of a duplicated class, which is what a\n * live preview should show, but LinkML classes are keyed by name and two nodes\n * with `*` would give one class two identifiers. The first declaration wins and\n * the later ones are not emitted; the `W_DUPLICATE_CLASS` warning already says\n * so. The same applies to a duplicate field inside one class.\n */\nfunction emittedClasses(classes: ClassNode[]): EmittedClass[] {\n const out: EmittedClass[] = [];\n const seen = new Set<string>();\n for (const node of classes) {\n if (seen.has(node.name.text)) continue;\n seen.add(node.name.text);\n const fields: FieldNode[] = [];\n const seenField = new Set<string>();\n for (const field of node.fields) {\n if (seenField.has(field.name.text)) continue;\n seenField.add(field.name.text);\n fields.push(field);\n }\n out.push({ node, fields });\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Enum naming, SPEC §5.1.\n\n/** The key an enum's name is looked up by: one attribute of one class. */\nconst useKey = (className: string, fieldName: string): string => `${className}\\u0000${fieldName}`;\n\n/**\n * An inline enum on field `climate` becomes `ClimateEnum`. Fields with the same\n * name and identical value sets share one enum; fields with the same name and\n * different value sets are each class-qualified (`PlanetClimateEnum`).\n *\n * Working default: \"identical\" is the values in the order written, and one\n * differing value set qualifies every field of that name, including the ones\n * that agree with each other.\n */\nfunction nameEnums(classes: EmittedClass[], classNames: ReadonlySet<string>): Map<string, string> {\n const uses: { className: string; fieldName: string; values: string }[] = [];\n for (const { node, fields } of classes) {\n for (const field of fields) {\n if (field.type?.kind !== 'enum') continue;\n uses.push({\n className: node.name.text,\n fieldName: field.name.text,\n values: field.type.values.map((v) => v.text).join('|'),\n });\n }\n }\n\n const valueSets = new Map<string, Set<string>>();\n for (const use of uses) {\n const seen = valueSets.get(use.fieldName) ?? new Set<string>();\n seen.add(use.values);\n valueSets.set(use.fieldName, seen);\n }\n\n // Working default: two value sets that still collide after qualification\n // (field `bookStatus` against `status` on class `Book`) keep separate enums,\n // the later one numbered. SPEC §5.1 does not cover the case. A name a class\n // already holds is numbered the same way: `gen-python` refuses a schema with\n // overlapping enum and class names.\n const taken = new Map<string, string>();\n const names = new Map<string, string>();\n for (const use of uses) {\n const shared = (valueSets.get(use.fieldName)?.size ?? 0) <= 1;\n const base = shared\n ? `${capitalise(use.fieldName)}Enum`\n : `${capitalise(use.className)}${capitalise(use.fieldName)}Enum`;\n let name = base;\n for (\n let n = 2;\n classNames.has(name) || (taken.has(name) && taken.get(name) !== use.values);\n n++\n ) {\n name = `${base}${n}`;\n }\n taken.set(name, use.values);\n names.set(useKey(use.className, use.fieldName), name);\n }\n return names;\n}\n\n/** The `enums` block, each enum at its first use and its values as written. */\nfunction buildEnums(\n classes: EmittedClass[],\n enums: Map<string, string>,\n): Record<string, LinkMLEnum> {\n const out: Record<string, LinkMLEnum> = {};\n for (const { node, fields } of classes) {\n for (const field of fields) {\n if (field.type?.kind !== 'enum') continue;\n const name = enums.get(useKey(node.name.text, field.name.text));\n if (name === undefined || name in out) continue;\n const values: Record<string, null> = {};\n for (const value of field.type.values) values[value.text] = null;\n out[name] = { permissible_values: values };\n }\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Classes and attributes. Key order is working default D1 of issue #23,\n// matching SPEC §5.3.\n\nfunction buildClass(\n cls: EmittedClass,\n mappingPrefix: (target: string) => string,\n enums: Map<string, string>,\n): LinkMLClass {\n const { node, fields } = cls;\n const out: LinkMLClass = {};\n\n if (node.description !== undefined) out.description = node.description;\n\n const annotations: LinkMLAnnotations = {};\n if (node.system !== undefined) annotations.system = node.system.text;\n if (node.note !== undefined) annotations.note = node.note;\n if (Object.keys(annotations).length > 0) out.annotations = annotations;\n\n if (node.similarTo !== undefined) {\n const target = node.similarTo.text;\n out.close_mappings = [`${mappingPrefix(target)}:${target}`];\n }\n\n const attributes: Record<string, LinkMLAttribute> = {};\n for (const field of fields) {\n attributes[field.name.text] = buildAttribute(field, node.name.text, enums);\n }\n if (Object.keys(attributes).length > 0) out.attributes = attributes;\n\n return out;\n}\n\nfunction buildAttribute(\n field: FieldNode,\n className: string,\n enums: Map<string, string>,\n): LinkMLAttribute {\n const out: LinkMLAttribute = {};\n\n if (field.description !== undefined) out.description = field.description;\n\n const range = rangeOf(field, className, enums);\n if (range !== undefined) out.range = range;\n if (field.type?.many === true) out.multivalued = true;\n if (field.identifier) out.identifier = true;\n\n // Annotation order is the order the markers are written on the line\n // (SPEC §4): `@System`, then `= Class.field`, then `?`.\n const annotations: LinkMLAnnotations = {};\n if (field.system !== undefined) annotations.system = field.system.text;\n if (field.joinsTo !== undefined) {\n annotations.joins_to = `${field.joinsTo.className.text}.${field.joinsTo.fieldName.text}`;\n }\n if (field.note !== undefined) annotations.note = field.note;\n if (Object.keys(annotations).length > 0) out.annotations = annotations;\n\n return out;\n}\n\n/**\n * SPEC §5.1. A field with no colon has no `range` at all and takes `string`\n * from `default_range`; an unknown type is a warning at resolve time and\n * compiles to `string` here (SPEC §3.2).\n */\nfunction rangeOf(\n field: FieldNode,\n className: string,\n enums: Map<string, string>,\n): string | undefined {\n const type = field.type;\n if (type === undefined) return undefined;\n switch (type.kind) {\n case 'primitive':\n return RANGE[type.name];\n case 'class':\n return type.name.text;\n case 'unknown':\n return 'string';\n case 'enum':\n return enums.get(useKey(className, field.name.text));\n }\n}\n\n// ---------------------------------------------------------------------------\n\n/**\n * The prefixes `linkml:types` binds. Every schema imports it, and LinkML\n * refuses a schema that binds one of these to another URI (`gen-python`:\n * \"Prefix: schema mismatch between schema and types\"). A schema or system\n * name that lands on one takes a trailing underscore instead.\n */\nconst RESERVED_PREFIXES = new Set(['linkml', 'xsd', 'shex', 'schema']);\n\n/**\n * SPEC §5.2: lowercase, every non-alphanumeric an underscore. A LinkML name\n * must match `^[a-zA-Z_][\\w.-]*$`, and the schema name is also its prefix,\n * which RDF forbids to be the bare `_` (blank nodes). So a name with no\n * letters or digits at all becomes `sketch`, one that starts with a digit\n * (`2024-inventory.skiss`) takes a leading underscore, and one that is a\n * prefix `linkml:types` already binds (`schema.skiss`) takes a trailing one.\n */\nconst linkmlName = (text: string): string => {\n const name = text.toLowerCase().replace(/[^a-z0-9]/g, '_');\n if (!/[a-z0-9]/.test(name)) return 'sketch';\n if (RESERVED_PREFIXES.has(name)) return `${name}_`;\n return /^[0-9]/.test(name) ? `_${name}` : name;\n};\n\nconst capitalise = (text: string): string => text.charAt(0).toUpperCase() + text.slice(1);\n","// Document -> Mermaid classDiagram text. The mapping table in\n// docs/ARCHITECTURE.md (Mermaid mapping) is the contract; the `.mmd`\n// fixtures are its reference output.\n//\n// A string builder and nothing more: Mermaid is a validation aid, not a\n// rendering layer, so no abstraction is built on top of it. Never throws.\n\nimport type { Document, FieldNode, TypeRef } from '../ast.ts';\nimport { resolve } from '../resolve.ts';\n\nexport interface MermaidOptions {\n /** Emit `? text` doubts as `note for` lines. Off by default. */\n notes?: boolean;\n}\n\nexport function toMermaid(doc: Document, opts?: MermaidOptions): string {\n // A document that has not been through `resolve` has no `undeclared`\n // list and may still carry a second `*`. Resolving is idempotent, so a\n // resolved document comes out unchanged and the output is the same either way.\n const resolved = resolve(doc);\n const notes = opts?.notes === true;\n\n // Output order per the table: classes, undeclared placeholders, notes,\n // relations. Notes must precede every relation line; Mermaid fails to\n // parse a note that follows a `..>` relation.\n const lines: string[] = ['classDiagram'];\n\n for (const cls of resolved.classes) {\n lines.push(` class ${cls.name.text} {`);\n if (cls.system !== undefined) lines.push(` <<${cls.system.text}>>`);\n for (const field of cls.fields) lines.push(` ${member(field)}`);\n lines.push(' }');\n }\n\n for (const name of resolved.undeclared) {\n lines.push(` class ${name.text} {`, ' <<undeclared>>', ' }');\n }\n\n if (notes) {\n for (const cls of resolved.classes) {\n if (cls.note !== undefined) lines.push(` note for ${cls.name.text} ${quote(cls.note)}`);\n for (const field of cls.fields) {\n if (field.note !== undefined) {\n lines.push(` note for ${cls.name.text} ${quote(`${field.name.text}: ${field.note}`)}`);\n }\n }\n }\n }\n\n for (const cls of resolved.classes) {\n const from = cls.name.text;\n // `~` is Mermaid's generic-type delimiter and vanishes from labels, so\n // the label is the word `similar`.\n if (cls.similarTo !== undefined) lines.push(` ${from} ..> ${cls.similarTo.text} : similar`);\n for (const field of cls.fields) {\n const type = field.type;\n if (type?.kind === 'class') {\n lines.push(\n type.many\n ? ` ${from} \"1\" --> \"*\" ${type.name.text} : ${field.name.text}`\n : ` ${from} --> ${type.name.text} : ${field.name.text}`,\n );\n }\n if (field.joinsTo !== undefined) {\n const { className, fieldName } = field.joinsTo;\n lines.push(` ${from} ..> ${className.text} : ${field.name.text} = ${fieldName.text}`);\n }\n }\n }\n\n return `${lines.join('\\n')}\\n`;\n}\n\n// `*` replaces the `+` visibility marker for an identifier; a trailing `*`\n// would be Mermaid's abstract-member marker and disappear.\nfunction member(field: FieldNode): string {\n const marker = field.identifier ? '*' : '+';\n const type = field.type === undefined ? '' : `${typeText(field.type)} `;\n const system = field.system === undefined ? '' : ` @${field.system.text}`;\n return `${marker}${type}${field.name.text}${system}`;\n}\n\n// The Skiss type text verbatim: an alias such as `integer` and an unknown\n// type such as `itn` are written as typed, enum values joined with `|`,\n// `[]` appended for many.\nfunction typeText(type: TypeRef): string {\n let text: string;\n switch (type.kind) {\n case 'primitive':\n text = type.written.text;\n break;\n case 'class':\n case 'unknown':\n text = type.name.text;\n break;\n case 'enum':\n text = type.values.map((v) => v.text).join('|');\n break;\n }\n return type.many ? `${text}[]` : text;\n}\n\n// A note is a double-quoted Mermaid string, so a double quote inside the\n// doubt becomes a single quote (issue #5, AC4).\nconst quote = (text: string): string => `\"${text.replaceAll('\"', \"'\")}\"`;\n","// The per-line parser: SPEC §3, §4 and §7, ADR 0004.\n//\n// Each line is read on its own. The one piece of state carried from line to\n// line is which class is current, so a field can attach to it and a field\n// with no current class can be reported as E_FIELD_WITHOUT_CLASS. No line\n// changes how another line is *read*; a line that fails to parse is skipped.\n// A failed class line clears the current class (ARCHITECTURE.md, diagnostics\n// table), so the fields under it are reported rather than silently attached\n// to the previous class.\n//\n// Nothing here throws. A line that does not match the grammar produces exactly\n// one diagnostic with severity `error` and is skipped.\n\nimport type {\n ClassNode,\n Diagnostic,\n DiagnosticCode,\n Document,\n FieldNode,\n Name,\n Primitive,\n TypeRef,\n} from './ast.ts';\n\n// SPEC §4: WS is spaces or tabs. Used for indentation, trailer delimiting and\n// working default D5 (trailing whitespace is ignored).\nconst isWs = (c: string | undefined): boolean => c === ' ' || c === '\\t';\nconst trimWs = (s: string): string => s.replace(/^[ \\t]+/, '').replace(/[ \\t]+$/, '');\n\nconst CLASS_NAME = /^[A-Z][A-Za-z0-9]*$/;\nconst FIELD_NAME = /^[a-z][A-Za-z0-9]*$/;\nconst SYSTEM_OR_VALUE = /^[A-Za-z][A-Za-z0-9_-]*$/;\n\nconst PRIMITIVES: Record<string, Primitive> = {\n string: 'string',\n int: 'int',\n float: 'float',\n bool: 'bool',\n date: 'date',\n datetime: 'datetime',\n uri: 'uri',\n text: 'string',\n integer: 'int',\n boolean: 'bool',\n};\n\nconst FIELD_ORDER = 'name, `*`, `: type`, `@System`, `= Class.field`, then `#` and `?`';\nconst CLASS_ORDER = 'name, `@System`, `~ Class`, then `#` and `?`';\n\nexport function parse(source: string): Document {\n const classes: ClassNode[] = [];\n const diagnostics: Diagnostic[] = [];\n let current: ClassNode | undefined;\n\n // A single leading U+FEFF is an editor's byte order mark, not content.\n // Stripping it keeps line 1 a class line and columns equal to what the\n // editor shows, since editors hide the BOM.\n const text0 = source.startsWith('\\ufeff') ? source.slice(1) : source;\n const lines = text0.split(/\\r\\n|\\n|\\r/);\n for (let i = 0; i < lines.length; i++) {\n const line = i + 1;\n const text = (lines[i] ?? '').replace(/[ \\t]+$/, '');\n const first = text[0];\n if (first === undefined || first === '#') continue; // blank or comment, SPEC §3.1\n\n if (isWs(first)) {\n const start = text.search(/[^ \\t]/);\n if (current === undefined) {\n diagnostics.push({\n severity: 'error',\n code: 'E_FIELD_WITHOUT_CLASS',\n message: 'An indented line is a field, but there is no valid class line above it',\n line,\n col: start,\n end: text.length,\n });\n continue;\n }\n const result = parseFieldLine(text, line);\n if (result.ok) current.fields.push(result.value);\n else diagnostics.push(result.diagnostic);\n continue;\n }\n\n if (/\\p{L}/u.test(first)) {\n const result = parseClassLine(text, line);\n if (result.ok) {\n classes.push(result.value);\n current = result.value;\n } else {\n // A failed class line clears the current class: the indented lines\n // under it report E_FIELD_WITHOUT_CLASS until the next class line\n // parses, instead of attaching to the previous class.\n diagnostics.push(result.diagnostic);\n current = undefined;\n }\n continue;\n }\n\n diagnostics.push({\n severity: 'error',\n code: 'E_UNPARSABLE',\n message: 'A line at column 0 must start with a class name or `#` for a comment',\n line,\n col: 0,\n end: text.length,\n });\n }\n\n return { classes, diagnostics };\n}\n\n// ---------------------------------------------------------------------------\n// Trailer: SPEC §3.9, Delimiting.\n\ninterface Trailer {\n head: string;\n description?: string;\n note?: string;\n}\n\n/** True when the `?` at `i` stands alone: whitespace before, whitespace or end after. */\nfunction isDoubtMarker(text: string, i: number): boolean {\n return text[i] === '?' && isWs(text[i - 1]) && (i + 1 === text.length || isWs(text[i + 1]));\n}\n\nfunction splitTrailer(text: string): Trailer {\n let start = -1;\n for (let i = 1; i < text.length; i++) {\n if (!isWs(text[i - 1])) continue;\n if (text[i] === '#' || isDoubtMarker(text, i)) {\n start = i;\n break;\n }\n }\n if (start < 0) return { head: text };\n\n const trailer: Trailer = { head: text.slice(0, start).replace(/[ \\t]+$/, '') };\n if (text[start] === '?') {\n // The doubt runs to the end of the line; a later `#` is text.\n const note = trimWs(text.slice(start + 1));\n if (note !== '') trailer.note = note;\n return trailer;\n }\n // The description comes first and the first standalone `?` ends it.\n let doubt = -1;\n for (let i = start + 1; i < text.length; i++) {\n if (isDoubtMarker(text, i)) {\n doubt = i;\n break;\n }\n }\n const description = trimWs(text.slice(start + 1, doubt < 0 ? text.length : doubt));\n if (description !== '') trailer.description = description;\n if (doubt >= 0) {\n const note = trimWs(text.slice(doubt + 1));\n if (note !== '') trailer.note = note;\n }\n return trailer;\n}\n\n// ---------------------------------------------------------------------------\n// Tokens. Whitespace between tokens is not significant; `[]` is one token.\n\ntype TokenKind = 'word' | '*' | ':' | '@' | '~' | '=' | '.' | '|' | '[]' | '[' | 'other';\n\ninterface Token {\n kind: TokenKind;\n text: string;\n col: number;\n end: number;\n}\n\nconst WORD = /[\\p{L}\\p{N}_-]+/uy;\nconst OPERATORS: readonly TokenKind[] = ['*', ':', '@', '~', '=', '.', '|', '['];\n\nfunction tokenize(head: string): Token[] {\n const tokens: Token[] = [];\n let i = 0;\n while (i < head.length) {\n const c = head[i] ?? '';\n if (isWs(c)) {\n i++;\n continue;\n }\n WORD.lastIndex = i;\n const word = WORD.exec(head);\n if (word !== null) {\n tokens.push({ kind: 'word', text: word[0], col: i, end: i + word[0].length });\n i += word[0].length;\n continue;\n }\n if (c === '[' && head[i + 1] === ']') {\n tokens.push({ kind: '[]', text: '[]', col: i, end: i + 2 });\n i += 2;\n continue;\n }\n // Any other character is one token. Whole code points, so a message can\n // show an astral character rather than half of it.\n const ch = String.fromCodePoint(head.codePointAt(i) ?? 0);\n const kind = OPERATORS.find((op) => op === ch) ?? 'other';\n tokens.push({ kind, text: ch, col: i, end: i + ch.length });\n i += ch.length;\n }\n return tokens;\n}\n\ninterface Cursor {\n tokens: Token[];\n pos: number;\n line: number;\n}\n\nconst peek = (c: Cursor): Token | undefined => c.tokens[c.pos];\n\nfunction take(c: Cursor): Token | undefined {\n const t = c.tokens[c.pos];\n if (t !== undefined) c.pos++;\n return t;\n}\n\nconst name = (t: Token, line: number): Name => ({ text: t.text, line, col: t.col, end: t.end });\n\n// ---------------------------------------------------------------------------\n// Results. Errors are values, never exceptions.\n\ntype Result<T> = { ok: true; value: T } | { ok: false; diagnostic: Diagnostic };\n\nfunction fail<T>(\n code: DiagnosticCode,\n message: string,\n line: number,\n span: { col: number; end: number },\n): Result<T> {\n return {\n ok: false,\n diagnostic: { severity: 'error', code, message, line, col: span.col, end: span.end },\n };\n}\n\n/** The span just after `after`, for \"expected something after X\" errors. */\nconst afterSpan = (after: Token) => ({ col: after.end, end: after.end });\n\n// ---------------------------------------------------------------------------\n// Class line: ClassName [ \"@\" System ] [ \"~\" ClassName ] [ trailer ]\n\nfunction parseClassLine(text: string, line: number): Result<ClassNode> {\n const trailer = splitTrailer(text);\n const c: Cursor = { tokens: tokenize(trailer.head), pos: 0, line };\n\n const head = take(c);\n if (head === undefined || head.kind !== 'word') {\n return fail('E_UNPARSABLE', 'A class line must start with a class name', line, {\n col: 0,\n end: Math.max(1, trailer.head.length),\n });\n }\n if (!CLASS_NAME.test(head.text)) {\n return fail(\n 'E_BAD_NAME',\n `Class names are UpperCamelCase (letters and digits, uppercase first): \\`${head.text}\\``,\n line,\n head,\n );\n }\n const node: Omit<ClassNode, 'fields'> = { name: name(head, line), line };\n\n const at = peek(c);\n if (at?.kind === '@') {\n take(c);\n const system = parseSystem(c, at);\n if (!system.ok) return system;\n node.system = system.value;\n }\n\n const tilde = peek(c);\n if (tilde?.kind === '~') {\n take(c);\n const target = take(c);\n if (target === undefined || target.kind !== 'word') {\n return fail(\n 'E_UNPARSABLE',\n 'Expected a class name after `~`, e.g. `CharacterPage @Community ~ Character`',\n line,\n target ?? afterSpan(tilde),\n );\n }\n if (!CLASS_NAME.test(target.text)) {\n return fail(\n 'E_BAD_NAME',\n `The class after \\`~\\` must be UpperCamelCase: \\`${target.text}\\``,\n line,\n target,\n );\n }\n node.similarTo = name(target, line);\n }\n\n const rest = peek(c);\n if (rest !== undefined) return unexpected(rest, line, 'class', CLASS_ORDER);\n\n if (trailer.description !== undefined) node.description = trailer.description;\n if (trailer.note !== undefined) node.note = trailer.note;\n return { ok: true, value: { ...node, fields: [] } };\n}\n\n// ---------------------------------------------------------------------------\n// Field line: WS field-name [ \"*\" ] [ \":\" type ] [ \"@\" System ]\n// [ \"=\" ClassName \".\" field-name ] [ trailer ]\n\nfunction parseFieldLine(text: string, line: number): Result<FieldNode> {\n const trailer = splitTrailer(text);\n const c: Cursor = { tokens: tokenize(trailer.head), pos: 0, line };\n\n const head = take(c);\n if (head === undefined) {\n // Nothing before the trailer. SPEC §3.1 defines comments at column 0\n // only, so an indented `#` line is a field line without a name.\n const col = text.search(/[^ \\t]/);\n const message =\n text[col] === '#'\n ? 'A `#` comment must start at column 0; an indented line is a field and needs a name'\n : 'A field line needs a field name before its trailer';\n return fail('E_UNPARSABLE', message, line, { col, end: Math.max(col + 1, text.length) });\n }\n if (head.kind !== 'word') return unexpected(head, line, 'field', FIELD_ORDER);\n if (!FIELD_NAME.test(head.text)) {\n return fail(\n 'E_BAD_NAME',\n `Field names are lowerCamelCase (letters and digits, lowercase first): \\`${head.text}\\``,\n line,\n head,\n );\n }\n const node: FieldNode = { name: name(head, line), line, identifier: false };\n\n if (peek(c)?.kind === '*') {\n take(c);\n node.identifier = true;\n }\n\n const colon = peek(c);\n if (colon?.kind === ':') {\n take(c);\n const type = parseType(c, colon);\n if (!type.ok) return type;\n node.type = type.value;\n }\n\n const at = peek(c);\n if (at?.kind === '@') {\n take(c);\n const system = parseSystem(c, at);\n if (!system.ok) return system;\n node.system = system.value;\n }\n\n const equals = peek(c);\n if (equals?.kind === '=') {\n take(c);\n const example = 'e.g. `characterId = Character.id`';\n const className = take(c);\n if (className === undefined || className.kind !== 'word') {\n return fail(\n 'E_UNPARSABLE',\n `Expected \\`Class.field\\` after \\`=\\`, ${example}`,\n line,\n className ?? afterSpan(equals),\n );\n }\n if (!CLASS_NAME.test(className.text)) {\n return fail(\n 'E_BAD_NAME',\n `The class after \\`=\\` must be UpperCamelCase: \\`${className.text}\\``,\n line,\n className,\n );\n }\n const dot = take(c);\n if (dot === undefined || dot.kind !== '.') {\n return fail(\n 'E_UNPARSABLE',\n `Expected \\`.\\` and a field name after \\`= ${className.text}\\`, ${example}`,\n line,\n dot ?? afterSpan(className),\n );\n }\n const fieldName = take(c);\n if (fieldName === undefined || fieldName.kind !== 'word') {\n return fail(\n 'E_UNPARSABLE',\n `Expected a field name after \\`= ${className.text}.\\`, ${example}`,\n line,\n fieldName ?? afterSpan(dot),\n );\n }\n if (!FIELD_NAME.test(fieldName.text)) {\n return fail(\n 'E_BAD_NAME',\n `The field after \\`= ${className.text}.\\` must be lowerCamelCase: \\`${fieldName.text}\\``,\n line,\n fieldName,\n );\n }\n node.joinsTo = { className: name(className, line), fieldName: name(fieldName, line) };\n }\n\n const rest = peek(c);\n if (rest !== undefined) return unexpected(rest, line, 'field', FIELD_ORDER);\n\n if (trailer.description !== undefined) node.description = trailer.description;\n if (trailer.note !== undefined) node.note = trailer.note;\n return { ok: true, value: node };\n}\n\n// \"@\" System, shared by class and field lines. SPEC §3.5. `at` is already consumed.\nfunction parseSystem(c: Cursor, at: Token): Result<Name> {\n const system = take(c);\n if (system === undefined || system.kind !== 'word') {\n return fail(\n 'E_UNPARSABLE',\n 'Expected a system name after `@`, e.g. `@Catalog`',\n c.line,\n system ?? afterSpan(at),\n );\n }\n if (!SYSTEM_OR_VALUE.test(system.text)) {\n return fail(\n 'E_UNPARSABLE',\n `A system name is letters, digits, \\`-\\` and \\`_\\`, starting with a letter: \\`${system.text}\\``,\n c.line,\n system,\n );\n }\n return { ok: true, value: name(system, c.line) };\n}\n\n// type = ( primitive | ClassName | enum ) [ \"[]\" ]. SPEC §3.2 to §3.4. `colon` is already consumed.\nfunction parseType(c: Cursor, colon: Token): Result<TypeRef> {\n const first = peek(c);\n // Working default D3: nothing type-like after the colon.\n if (first === undefined || first.kind === '*' || first.kind === '@' || first.kind === '=') {\n return fail(\n 'E_MISSING_TYPE',\n 'A colon needs a type after it, e.g. `crewSize: int`',\n c.line,\n colon,\n );\n }\n if (first.kind !== 'word') {\n return fail(\n 'E_UNPARSABLE',\n `Expected a type after \\`:\\`, found \\`${first.text}\\``,\n c.line,\n first,\n );\n }\n take(c);\n\n const words: Token[] = [first];\n for (let pipe = peek(c); pipe?.kind === '|'; pipe = peek(c)) {\n take(c);\n const value = take(c);\n if (value === undefined || value.kind !== 'word') {\n return fail(\n 'E_UNPARSABLE',\n 'Expected an enum value after `|`, e.g. `climate: arid|temperate|frozen`',\n c.line,\n value ?? afterSpan(pipe),\n );\n }\n words.push(value);\n }\n\n let many = false;\n const bracket = peek(c);\n if (bracket?.kind === '[]') {\n take(c);\n many = true;\n } else if (bracket?.kind === '[') {\n return fail(\n 'E_UNCLOSED_MANY',\n '`[` must be closed: `[]` marks a many-valued field',\n c.line,\n bracket,\n );\n }\n\n if (words.length > 1) {\n for (const w of words) {\n if (!SYSTEM_OR_VALUE.test(w.text)) {\n return fail(\n 'E_UNPARSABLE',\n `An enum value is letters, digits, \\`-\\` and \\`_\\`, starting with a letter: \\`${w.text}\\``,\n c.line,\n w,\n );\n }\n }\n return { ok: true, value: { kind: 'enum', values: words.map((w) => name(w, c.line)), many } };\n }\n\n const word = first.text;\n const primitive = PRIMITIVES[word];\n if (primitive !== undefined) {\n return {\n ok: true,\n value: { kind: 'primitive', name: primitive, written: name(first, c.line), many },\n };\n }\n if (/^[A-Z]/.test(word)) {\n if (!CLASS_NAME.test(word)) {\n return fail(\n 'E_BAD_NAME',\n `A referenced class must be UpperCamelCase: \\`${word}\\``,\n c.line,\n first,\n );\n }\n return { ok: true, value: { kind: 'class', name: name(first, c.line), many } };\n }\n if (/^[a-z]/.test(word)) {\n // SPEC §3.2: any other lowercase word is an unknown type; `resolve` warns.\n return { ok: true, value: { kind: 'unknown', name: name(first, c.line), many } };\n }\n return fail(\n 'E_UNPARSABLE',\n `A type is a primitive, a class name or an enum, not \\`${word}\\``,\n c.line,\n first,\n );\n}\n\n// A token left over once the fixed modifier sequence has been consumed.\nfunction unexpected<T>(\n token: Token,\n line: number,\n kind: 'class' | 'field',\n order: string,\n): Result<T> {\n const modifiers: TokenKind[] = kind === 'field' ? ['*', ':', '@', '='] : ['@', '~'];\n if (modifiers.includes(token.kind)) {\n return fail(\n 'E_UNPARSABLE',\n `\\`${token.text}\\` is out of order; a ${kind} line reads: ${order}`,\n line,\n token,\n );\n }\n if (token.text === '<') {\n return fail(\n 'E_UNPARSABLE',\n '`<` is reserved for inheritance and not yet part of the language',\n line,\n token,\n );\n }\n if (token.text === '?') {\n return fail(\n 'E_UNPARSABLE',\n '`?` attached to a word is not a marker; a doubt needs whitespace before `?` and after it',\n line,\n token,\n );\n }\n if (token.text === '#') {\n return fail('E_UNPARSABLE', 'A description needs whitespace before `#`', line, token);\n }\n return fail('E_UNPARSABLE', `Unexpected \\`${token.text}\\` on a ${kind} line`, line, token);\n}\n","// LinkML schema object -> text (ADR 0006). The generator returns data; this\n// is the only place that knows about YAML, and `yaml` is the library's only\n// runtime dependency.\n//\n// The YAML is held to SPEC §5.3 byte for byte, which means two-space\n// indentation, block style, no document markers, and a blank line before\n// `classes:`, before `enums:` and before every class.\n\nimport { isMap, isScalar, Scalar, visit, Document as YAMLDocument } from 'yaml';\nimport type { LinkMLSchema } from './generators/linkml.ts';\n\nexport type SerializeFormat = 'yaml' | 'json';\n\nexport function serialize(schema: LinkMLSchema, format: SerializeFormat): string {\n if (format === 'json') return `${JSON.stringify(schema, null, 2)}\\n`;\n\n // LinkML reads the file with PyYAML, which is YAML 1.1: there `yes`, `no`,\n // `on`, `off`, `y`, `n` and `12:30` are not strings. Building the document\n // in 1.1 makes the writer quote them, so a description stays the text the\n // sketch wrote.\n const doc = new YAMLDocument(schema, { version: '1.1' });\n const root = doc.contents;\n if (isMap(root)) {\n for (const pair of root.items) {\n const key = pair.key;\n if (!isScalar(key)) continue;\n if (key.value !== 'classes' && key.value !== 'enums') continue;\n key.spaceBefore = true;\n // A blank line before every class, the first one included, so the\n // section reads as separated blocks rather than one wall of keys.\n if (key.value === 'classes' && isMap(pair.value)) {\n for (const entry of pair.value.items) {\n if (isScalar(entry.key)) entry.key.spaceBefore = true;\n }\n }\n }\n }\n\n // A tab or another control character in a plain scalar is either illegal\n // YAML (PyYAML stops at a tab that starts a token) or lost; double quoting\n // escapes it. Tabs reach here because SPEC §4 counts them as whitespace, so\n // they survive into trailer text.\n visit(doc, {\n Scalar(_, node) {\n if (typeof node.value === 'string' && hasControlCharacter(node.value)) {\n node.type = Scalar.QUOTE_DOUBLE;\n }\n },\n });\n\n const text = doc.toString({\n directives: false,\n indent: 2,\n lineWidth: 0,\n // A permissible value carries no body: `arid:`, not `arid: null`.\n nullStr: '',\n });\n // A blank line before the first entry of a nested map is written at the\n // map's own indentation, so the `spaceBefore` separators above are the only\n // whitespace-only lines here: `lineWidth: 0` never folds a scalar, and a\n // scalar holding a newline is double-quoted by the pass above and stays on\n // one line. Emptying them cannot reach a value.\n return text.replace(/^[ \\t]+$/gm, '');\n}\n\n/** C0 controls and DEL. Written as codepoints: Biome bans them in a regex. */\nfunction hasControlCharacter(text: string): boolean {\n for (const character of text) {\n const code = character.codePointAt(0) ?? 0;\n if (code < 0x20 || code === 0x7f) return true;\n }\n return false;\n}\n","// The whole path in one call, for callers that want one (ADR 0006): parse,\n// resolve, generate. Never throws, because nothing it calls does; bad input\n// produces diagnostics and the output of the partial document.\n\nimport type { Diagnostic } from './ast.ts';\nimport { toLinkML } from './generators/linkml.ts';\nimport { type MermaidOptions, toMermaid } from './generators/mermaid.ts';\nimport { parse } from './parse.ts';\nimport { resolve } from './resolve.ts';\nimport { type SerializeFormat, serialize } from './serialize.ts';\n\n/** `toMermaid` text from the source. */\nexport interface MermaidCompileOptions extends MermaidOptions {\n target: 'mermaid';\n}\n\n/** `toLinkML` put through `serialize` (ADR 0006). */\nexport interface LinkMLCompileOptions {\n target: 'linkml';\n /** Normally the file name. Normalised to a valid LinkML name (SPEC §5.2). */\n schemaName: string;\n /** Defaults to `yaml`, the form LinkML itself reads. */\n format?: SerializeFormat;\n}\n\n/**\n * A union rather than one shape with optional keys, so `schemaName` is\n * required exactly when it is used and a `mermaid` call cannot carry a\n * `format` that would be ignored.\n */\nexport type CompileOptions = MermaidCompileOptions | LinkMLCompileOptions;\n\nexport interface CompileResult {\n /** The generated text, `\\n`-terminated, whatever the platform. */\n output: string;\n /** Errors from `parse` and warnings from `resolve`, in line order. */\n diagnostics: Diagnostic[];\n}\n\nexport function compile(source: string, opts: CompileOptions): CompileResult {\n const doc = resolve(parse(source));\n const output =\n opts.target === 'linkml'\n ? serialize(toLinkML(doc, { schemaName: opts.schemaName }), opts.format ?? 'yaml')\n : toMermaid(doc, { notes: opts.notes });\n return { output, diagnostics: doc.diagnostics };\n}\n","// One diagnostic as one line of text, in the `file:line:col: severity CODE\n// message` shape compilers print and editors and terminals know how to\n// jump to. Shared by the CLI and any editor that wants the same text\n// (issue #6, working default D4).\n\nimport type { Diagnostic } from './ast.ts';\n\n/**\n * `<file>:<line>:<col>: <severity> <CODE> <message>`, without a trailing\n * newline. The `<file>:` prefix is omitted when `file` is not given, and\n * `<col>` when the diagnostic has no column. Columns are printed 1-based, as\n * the `file:line:col` convention expects, while `Diagnostic.col` stays\n * 0-based for editors.\n */\nexport function formatDiagnostic(d: Diagnostic, file?: string): string {\n const where = [file, d.line, d.col === undefined ? undefined : d.col + 1]\n .filter((part) => part !== undefined)\n .join(':');\n return `${where}: ${d.severity} ${d.code} ${d.message}`;\n}\n","export type {\n ClassNode,\n Diagnostic,\n DiagnosticCode,\n Document,\n FieldNode,\n JoinRef,\n Name,\n Primitive,\n Severity,\n TypeRef,\n} from './ast.ts';\nexport {\n type CompileOptions,\n type CompileResult,\n compile,\n type LinkMLCompileOptions,\n type MermaidCompileOptions,\n} from './compile.ts';\nexport { formatDiagnostic } from './diagnostics.ts';\nexport {\n type LinkMLAnnotations,\n type LinkMLAttribute,\n type LinkMLClass,\n type LinkMLEnum,\n type LinkMLOptions,\n type LinkMLSchema,\n toLinkML,\n} from './generators/linkml.ts';\nexport { type MermaidOptions, toMermaid } from './generators/mermaid.ts';\nexport { parse } from './parse.ts';\nexport { type ResolvedDocument, resolve } from './resolve.ts';\nexport { type SerializeFormat, serialize } from './serialize.ts';\n\nexport const VERSION = '0.1.0';\n"],"mappings":"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","names":["name","name","name","key"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/** Lines are 1-based. Columns are 0-based UTF-16 code units, `end` exclusive. */
|
|
2
|
+
interface Name {
|
|
3
|
+
text: string;
|
|
4
|
+
line: number;
|
|
5
|
+
col: number;
|
|
6
|
+
end: number;
|
|
7
|
+
}
|
|
8
|
+
/** SPEC §3.2. The aliases `text`, `integer` and `boolean` are normalised to these. */
|
|
9
|
+
type Primitive = 'string' | 'int' | 'float' | 'bool' | 'date' | 'datetime' | 'uri';
|
|
10
|
+
/**
|
|
11
|
+
* The type after a colon (SPEC §3.2 to §3.4). `many` is the trailing `[]`
|
|
12
|
+
* and applies to the whole type. `unknown` is a lowercase word that is not a
|
|
13
|
+
* primitive: `resolve` turns it into a warning and generators treat it as
|
|
14
|
+
* `string`.
|
|
15
|
+
*/
|
|
16
|
+
type TypeRef = {
|
|
17
|
+
kind: 'primitive';
|
|
18
|
+
name: Primitive;
|
|
19
|
+
/** The token as written, so an alias such as `integer` survives verbatim. */
|
|
20
|
+
written: Name;
|
|
21
|
+
many: boolean;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'class';
|
|
24
|
+
name: Name;
|
|
25
|
+
many: boolean;
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'enum';
|
|
28
|
+
values: Name[];
|
|
29
|
+
many: boolean;
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'unknown';
|
|
32
|
+
name: Name;
|
|
33
|
+
many: boolean;
|
|
34
|
+
};
|
|
35
|
+
interface JoinRef {
|
|
36
|
+
className: Name;
|
|
37
|
+
fieldName: Name;
|
|
38
|
+
}
|
|
39
|
+
interface FieldNode {
|
|
40
|
+
name: Name;
|
|
41
|
+
identifier: boolean;
|
|
42
|
+
type?: TypeRef;
|
|
43
|
+
system?: Name;
|
|
44
|
+
joinsTo?: JoinRef;
|
|
45
|
+
description?: string;
|
|
46
|
+
note?: string;
|
|
47
|
+
line: number;
|
|
48
|
+
}
|
|
49
|
+
interface ClassNode {
|
|
50
|
+
name: Name;
|
|
51
|
+
system?: Name;
|
|
52
|
+
similarTo?: Name;
|
|
53
|
+
description?: string;
|
|
54
|
+
note?: string;
|
|
55
|
+
fields: FieldNode[];
|
|
56
|
+
line: number;
|
|
57
|
+
}
|
|
58
|
+
type Severity = 'error' | 'warning';
|
|
59
|
+
/** Codes from the table in docs/ARCHITECTURE.md. `parse` adds the `E_` codes, `resolve` the `W_` codes. */
|
|
60
|
+
type DiagnosticCode = 'E_UNPARSABLE' | 'E_FIELD_WITHOUT_CLASS' | 'E_MISSING_TYPE' | 'E_UNCLOSED_MANY' | 'E_BAD_NAME' | 'W_UNKNOWN_TYPE' | 'W_UNDECLARED_CLASS' | 'W_UNDECLARED_FIELD' | 'W_DUPLICATE_CLASS' | 'W_DUPLICATE_FIELD' | 'W_MULTIPLE_IDENTIFIERS';
|
|
61
|
+
interface Diagnostic {
|
|
62
|
+
severity: Severity;
|
|
63
|
+
code: DiagnosticCode;
|
|
64
|
+
message: string;
|
|
65
|
+
line: number;
|
|
66
|
+
col?: number;
|
|
67
|
+
end?: number;
|
|
68
|
+
}
|
|
69
|
+
interface Document {
|
|
70
|
+
classes: ClassNode[];
|
|
71
|
+
diagnostics: Diagnostic[];
|
|
72
|
+
/**
|
|
73
|
+
* Class names referenced by `:`, `~` or `=` but never declared, each once,
|
|
74
|
+
* in first-reference order and at its first reference's position. Absent
|
|
75
|
+
* until `resolve` has run; a generator draws these as placeholders.
|
|
76
|
+
*/
|
|
77
|
+
undeclared?: Name[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface MermaidOptions {
|
|
81
|
+
/** Emit `? text` doubts as `note for` lines. Off by default. */
|
|
82
|
+
notes?: boolean;
|
|
83
|
+
}
|
|
84
|
+
declare function toMermaid(doc: Document, opts?: MermaidOptions): string;
|
|
85
|
+
|
|
86
|
+
/** A LinkML `annotations` block in the compact `tag: value` form (SPEC §5.3). */
|
|
87
|
+
interface LinkMLAnnotations {
|
|
88
|
+
[tag: string]: string | boolean;
|
|
89
|
+
}
|
|
90
|
+
/** One entry under a class's `attributes`. Key order is SPEC §5.3. */
|
|
91
|
+
interface LinkMLAttribute {
|
|
92
|
+
description?: string;
|
|
93
|
+
range?: string;
|
|
94
|
+
multivalued?: boolean;
|
|
95
|
+
identifier?: boolean;
|
|
96
|
+
annotations?: LinkMLAnnotations;
|
|
97
|
+
}
|
|
98
|
+
/** One entry under `classes`. Key order is SPEC §5.3. */
|
|
99
|
+
interface LinkMLClass {
|
|
100
|
+
description?: string;
|
|
101
|
+
annotations?: LinkMLAnnotations;
|
|
102
|
+
close_mappings?: string[];
|
|
103
|
+
attributes?: Record<string, LinkMLAttribute>;
|
|
104
|
+
}
|
|
105
|
+
/** One entry under `enums`. A permissible value carries no body, hence `null`. */
|
|
106
|
+
interface LinkMLEnum {
|
|
107
|
+
permissible_values: Record<string, null>;
|
|
108
|
+
}
|
|
109
|
+
/** The whole schema. Plain data: every value here survives `JSON.stringify`. */
|
|
110
|
+
interface LinkMLSchema {
|
|
111
|
+
id: string;
|
|
112
|
+
name: string;
|
|
113
|
+
default_prefix: string;
|
|
114
|
+
default_range: string;
|
|
115
|
+
prefixes: Record<string, string>;
|
|
116
|
+
imports: string[];
|
|
117
|
+
classes?: Record<string, LinkMLClass>;
|
|
118
|
+
enums?: Record<string, LinkMLEnum>;
|
|
119
|
+
}
|
|
120
|
+
interface LinkMLOptions {
|
|
121
|
+
/** Normally the file name. Normalised to a valid LinkML name (SPEC §5.2). */
|
|
122
|
+
schemaName: string;
|
|
123
|
+
}
|
|
124
|
+
declare function toLinkML(doc: Document, opts: LinkMLOptions): LinkMLSchema;
|
|
125
|
+
|
|
126
|
+
type SerializeFormat = 'yaml' | 'json';
|
|
127
|
+
declare function serialize(schema: LinkMLSchema, format: SerializeFormat): string;
|
|
128
|
+
|
|
129
|
+
/** `toMermaid` text from the source. */
|
|
130
|
+
interface MermaidCompileOptions extends MermaidOptions {
|
|
131
|
+
target: 'mermaid';
|
|
132
|
+
}
|
|
133
|
+
/** `toLinkML` put through `serialize` (ADR 0006). */
|
|
134
|
+
interface LinkMLCompileOptions {
|
|
135
|
+
target: 'linkml';
|
|
136
|
+
/** Normally the file name. Normalised to a valid LinkML name (SPEC §5.2). */
|
|
137
|
+
schemaName: string;
|
|
138
|
+
/** Defaults to `yaml`, the form LinkML itself reads. */
|
|
139
|
+
format?: SerializeFormat;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* A union rather than one shape with optional keys, so `schemaName` is
|
|
143
|
+
* required exactly when it is used and a `mermaid` call cannot carry a
|
|
144
|
+
* `format` that would be ignored.
|
|
145
|
+
*/
|
|
146
|
+
type CompileOptions = MermaidCompileOptions | LinkMLCompileOptions;
|
|
147
|
+
interface CompileResult {
|
|
148
|
+
/** The generated text, `\n`-terminated, whatever the platform. */
|
|
149
|
+
output: string;
|
|
150
|
+
/** Errors from `parse` and warnings from `resolve`, in line order. */
|
|
151
|
+
diagnostics: Diagnostic[];
|
|
152
|
+
}
|
|
153
|
+
declare function compile(source: string, opts: CompileOptions): CompileResult;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* `<file>:<line>:<col>: <severity> <CODE> <message>`, without a trailing
|
|
157
|
+
* newline. The `<file>:` prefix is omitted when `file` is not given, and
|
|
158
|
+
* `<col>` when the diagnostic has no column. Columns are printed 1-based, as
|
|
159
|
+
* the `file:line:col` convention expects, while `Diagnostic.col` stays
|
|
160
|
+
* 0-based for editors.
|
|
161
|
+
*/
|
|
162
|
+
declare function formatDiagnostic(d: Diagnostic, file?: string): string;
|
|
163
|
+
|
|
164
|
+
declare function parse(source: string): Document;
|
|
165
|
+
|
|
166
|
+
/** What `resolve` returns: a `Document` whose `undeclared` list is always present. */
|
|
167
|
+
interface ResolvedDocument extends Document {
|
|
168
|
+
undeclared: Name[];
|
|
169
|
+
}
|
|
170
|
+
declare function resolve(doc: Document): ResolvedDocument;
|
|
171
|
+
|
|
172
|
+
declare const VERSION = "0.1.0";
|
|
173
|
+
|
|
174
|
+
export { type ClassNode, type CompileOptions, type CompileResult, type Diagnostic, type DiagnosticCode, type Document, type FieldNode, type JoinRef, type LinkMLAnnotations, type LinkMLAttribute, type LinkMLClass, type LinkMLCompileOptions, type LinkMLEnum, type LinkMLOptions, type LinkMLSchema, type MermaidCompileOptions, type MermaidOptions, type Name, type Primitive, type ResolvedDocument, type SerializeFormat, type Severity, type TypeRef, VERSION, compile, formatDiagnostic, parse, resolve, serialize, toLinkML, toMermaid };
|