Prepare for autogeneration of selene type definitions

This commit is contained in:
Filip Tibell 2023-02-16 12:28:17 +01:00
parent a1f48fae47
commit b080d78253
No known key found for this signature in database
8 changed files with 46 additions and 736 deletions

19
.gitignore vendored
View file

@ -1,6 +1,21 @@
# Annoying macOS finder stuff
/.DS_Store
/**/.DS_Store
# Autogenerated dirs
/bin
/target
/wiki
.DS_Store
*/.DS_Store
# Autogenerated files
lune.yml
luneDocs.json
luneTypes.d.luau
# NOTE: Don't remove the typedefs file in the docs dir!
# We depend on it to autogenerate all of the above files.
!/docs/luneTypes.d.luau

View file

@ -5,6 +5,8 @@
"luau-lsp.types.definitionFiles": ["luneTypes.d.luau"],
"luau-lsp.types.documentationFiles": ["luneDocs.json"],
"luau-lsp.require.mode": "relativeToFile",
// Luau - ignore type defs file in docs dir
"luau-lsp.ignoreGlobs": ["docs/*.d.luau"],
// Rust
"rust-analyzer.check.command": "clippy",
// Formatting

138
lune.yml
View file

@ -1,138 +0,0 @@
# Lune v0.4.0
---
globals:
# FS (filesystem)
fs.readFile:
must_use: true
args:
- type: string
fs.readDir:
must_use: true
args:
- type: string
fs.writeFile:
args:
- type: string
- type: string
fs.writeDir:
args:
- type: string
fs.removeFile:
args:
- type: string
fs.removeDir:
args:
- type: string
fs.isFile:
must_use: true
args:
- type: string
fs.isDir:
must_use: true
args:
- type: string
# Net (networking)
net.jsonEncode:
must_use: true
args:
- type: any
- required: false
type: boolean
net.jsonDecode:
must_use: true
args:
- type: string
net.request:
args:
- type: string | table
net.socket:
must_use: true
args:
- type: string
net.serve:
args:
- type: number
- type: function | table
# Processs
process.args:
property: read-only
process.cwd:
property: read-only
process.env:
property: new-fields
process.exit:
args:
- required: false
type: number
process.spawn:
args:
- type: string
- required: false
type: table
- required: false
type: table
# Stdio
stdio.color:
must_use: true
args:
- type: string
stdio.style:
must_use: true
args:
- type: string
console.format:
must_use: true
args:
- type: "..."
stdio.write:
args:
- type: string
stdio.ewrite:
args:
- type: string
stdio.prompt:
must_use: true
args:
- required: false
type: string
- required: false
type: string
- required: false
type: string | boolean | table
# Task
task.cancel:
args:
- type: thread
task.defer:
args:
- type: thread | function
- type: "..."
task.delay:
args:
- required: false
type: number
- type: thread | function
- type: "..."
task.spawn:
args:
- type: thread | function
- type: "..."
task.wait:
args:
- required: false
type: number
# Misc
print:
args:
- type: "..."
info:
args:
- type: "..."
warn:
args:
- type: "..."
error:
args:
- type: any
- required: false
type: number

View file

@ -1,582 +0,0 @@
{
"@roblox/global/fs": {
"documentation": "Filesystem",
"keys": {
"fs": "@roblox/global/fs.fs"
},
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/net": {
"documentation": "Networking",
"keys": {
"net": "@roblox/global/net.net"
},
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/process": {
"documentation": "Current process & child processes",
"keys": {
"process": "@roblox/global/process.process"
},
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/stdio": {
"documentation": "Standard input / output & utility functions",
"keys": {
"stdio": "@roblox/global/stdio.stdio"
},
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/task": {
"documentation": "Task scheduler & thread spawning",
"keys": {
"task": "@roblox/global/task.task"
},
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/fs.readFile": {
"documentation": "Reads a file at `path`.\n\nAn error will be thrown in the following situations:\n\n* `path` does not point to an existing file.\n* The current process lacks permissions to read the file.\n* The contents of the file cannot be read as a UTF-8 string.\n* Some other I/O error occurred.",
"params": [
{
"name": "path",
"documentation": "@roblox/global/fs.readFile/param/0"
}
],
"returns": [
"@roblox/global/fs.readFile/return/0"
],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/fs.readDir": {
"documentation": "Reads entries in a directory at `path`.\n\nAn error will be thrown in the following situations:\n\n* `path` does not point to an existing directory.\n* The current process lacks permissions to read the contents of the directory.\n* Some other I/O error occurred.",
"params": [
{
"name": "path",
"documentation": "@roblox/global/fs.readDir/param/0"
}
],
"returns": [
"@roblox/global/fs.readDir/return/0"
],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/fs.writeFile": {
"documentation": "Writes to a file at `path`.\n\nAn error will be thrown in the following situations:\n\n* The file's parent directory does not exist.\n* The current process lacks permissions to write to the file.\n* Some other I/O error occurred.",
"params": [
{
"name": "path",
"documentation": "@roblox/global/fs.writeFile/param/0"
},
{
"name": "contents",
"documentation": "@roblox/global/fs.writeFile/param/1"
}
],
"returns": [],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/fs.writeDir": {
"documentation": "Creates a directory and its parent directories if they are missing.\n\nAn error will be thrown in the following situations:\n\n* `path` already points to an existing file or directory.\n* The current process lacks permissions to create the directory or its missing parents.\n* Some other I/O error occurred.",
"params": [
{
"name": "path",
"documentation": "@roblox/global/fs.writeDir/param/0"
}
],
"returns": [],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/fs.removeFile": {
"documentation": "Removes a file.\n\nAn error will be thrown in the following situations:\n\n* `path` does not point to an existing file.\n* The current process lacks permissions to remove the file.\n* Some other I/O error occurred.",
"params": [
{
"name": "path",
"documentation": "@roblox/global/fs.removeFile/param/0"
}
],
"returns": [],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/fs.removeDir": {
"documentation": "Removes a directory and all of its contents.\n\nAn error will be thrown in the following situations:\n\n* `path` is not an existing and empty directory.\n* The current process lacks permissions to remove the directory.\n* Some other I/O error occurred.",
"params": [
{
"name": "path",
"documentation": "@roblox/global/fs.removeDir/param/0"
}
],
"returns": [],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/fs.isFile": {
"documentation": "Checks if a given path is a file.\n\nAn error will be thrown in the following situations:\n\n* The current process lacks permissions to read at `path`.\n* Some other I/O error occurred.",
"params": [
{
"name": "path",
"documentation": "@roblox/global/fs.isFile/param/0"
}
],
"returns": [
"@roblox/global/fs.isFile/return/0"
],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/fs.isDir": {
"documentation": "Checks if a given path is a directory.\n\nAn error will be thrown in the following situations:\n\n* The current process lacks permissions to read at `path`.\n* Some other I/O error occurred.",
"params": [
{
"name": "path",
"documentation": "@roblox/global/fs.isDir/param/0"
}
],
"returns": [
"@roblox/global/fs.isDir/return/0"
],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/net.request": {
"documentation": "Sends an HTTP request using the given url and / or parameters, and returns a dictionary that describes the response received.\n\nOnly throws an error if a miscellaneous network or I/O error occurs, never for unsuccessful status codes.",
"params": [
{
"name": "config",
"documentation": "@roblox/global/net.request/param/0"
}
],
"returns": [
"@roblox/global/net.request/return/0"
],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/net.socket": {
"documentation": "Connects to a web socket at the given URL.\n\nThrows an error if the server at the given URL does not support\nweb sockets, or if a miscellaneous network or I/O error occurs.",
"params": [
{
"name": "url",
"documentation": "@roblox/global/net.socket/param/0"
}
],
"returns": [
"@roblox/global/net.socket/return/0"
],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/net.serve": {
"documentation": "Creates an HTTP server that listens on the given `port`.\n\nThis will ***not*** block and will keep listening for requests on the given `port`\nuntil the `stop` function on the returned `NetServeHandle` has been called.",
"params": [
{
"name": "port",
"documentation": "@roblox/global/net.serve/param/0"
},
{
"name": "handlerOrConfig",
"documentation": "@roblox/global/net.serve/param/1"
}
],
"returns": [],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/net.jsonEncode": {
"documentation": "Encodes the given value as JSON.",
"params": [
{
"name": "value",
"documentation": "@roblox/global/net.jsonEncode/param/0"
},
{
"name": "pretty",
"documentation": "@roblox/global/net.jsonEncode/param/1"
}
],
"returns": [
"@roblox/global/net.jsonEncode/return/0"
],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/net.jsonDecode": {
"documentation": "Decodes the given JSON string into a lua value.",
"params": [
{
"name": "encoded",
"documentation": "@roblox/global/net.jsonDecode/param/0"
}
],
"returns": [
"@roblox/global/net.jsonDecode/return/0"
],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/process.args": {
"documentation": "The arguments given when running the Lune script.",
"params": [],
"returns": [],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/process.cwd": {
"documentation": "The current working directory in which the Lune script is running.",
"params": [],
"returns": [],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/process.env": {
"documentation": "Current environment variables for this process.\n\nSetting a value on this table will set the corresponding environment variable.",
"params": [],
"returns": [],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/process.exit": {
"documentation": "Exits the currently running script as soon as possible with the given exit code.\n\nExit code 0 is treated as a successful exit, any other value is treated as an error.\n\nSetting the exit code using this function will override any otherwise automatic exit code.",
"params": [
{
"name": "code",
"documentation": "@roblox/global/process.exit/param/0"
}
],
"returns": [],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/process.spawn": {
"documentation": "Spawns a child process that will run the program `program`, and returns a dictionary that describes the final status and ouput of the child process.\n\nThe second argument, `params`, can be passed as a list of string parameters to give to the program.\n\nThe third argument, `options`, can be passed as a dictionary of options to give to the child process.\nThe available options inside of the `options` dictionary are:\n* `cwd` - The current working directory for the process\n* `env` - Extra environment variables to give to the process\n* `shell` - Whether to run in a shell or not - set to `true` to run using the default shell, or a string to run using a specific shell\n* `stdio` - How to treat output and error streams from the child process - set to \"inherit\" to pass output and error streams to the current process",
"params": [
{
"name": "program",
"documentation": "@roblox/global/process.spawn/param/0"
},
{
"name": "params",
"documentation": "@roblox/global/process.spawn/param/1"
},
{
"name": "options",
"documentation": "@roblox/global/process.spawn/param/2"
}
],
"returns": [
"@roblox/global/process.spawn/return/0"
],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/stdio.color": {
"documentation": "Return an ANSI string that can be used to modify the persistent output color.\n\nPass `\"reset\"` to get a string that can reset the persistent output color.\n\n### Example usage\n\n```lua\nstdio.write(stdio.color(\"red\"))\nprint(\"This text will be red\")\nstdio.write(stdio.color(\"reset\"))\nprint(\"This text will be normal\")\n```",
"params": [
{
"name": "color",
"documentation": "@roblox/global/stdio.color/param/0"
}
],
"returns": [
"@roblox/global/stdio.color/return/0"
],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/stdio.style": {
"documentation": "Return an ANSI string that can be used to modify the persistent output style.\n\nPass `\"reset\"` to get a string that can reset the persistent output style.\n\n### Example usage\n\n```lua\nstdio.write(stdio.style(\"bold\"))\nprint(\"This text will be bold\")\nstdio.write(stdio.style(\"reset\"))\nprint(\"This text will be normal\")\n```",
"params": [
{
"name": "style",
"documentation": "@roblox/global/stdio.style/param/0"
}
],
"returns": [
"@roblox/global/stdio.style/return/0"
],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/stdio.format": {
"documentation": "Formats arguments into a human-readable string with syntax highlighting for tables.",
"params": [
{
"name": "...",
"documentation": "@roblox/global/stdio.format/param/0"
}
],
"returns": [
"@roblox/global/stdio.format/return/0"
],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/stdio.write": {
"documentation": "Writes a string directly to stdout, without any newline.",
"params": [
{
"name": "s",
"documentation": "@roblox/global/stdio.write/param/0"
}
],
"returns": [],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/stdio.ewrite": {
"documentation": "Writes a string directly to stderr, without any newline.",
"params": [
{
"name": "s",
"documentation": "@roblox/global/stdio.ewrite/param/0"
}
],
"returns": [],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/stdio.prompt": {
"documentation": "Prompts for user input using the wanted kind of prompt:\n\n* `\"text\"` - Prompts for a plain text string from the user\n* `\"confirm\"` - Prompts the user to confirm with y / n\n* `\"select\"` - Prompts the user to select *one* value from a list\n* `\"multiselect\"` - Prompts the user to select *one or more* values from a list\n* `nil` - Equivalent to `\"text\"` with no extra arguments",
"params": [
{
"name": "kind",
"documentation": "@roblox/global/stdio.prompt/param/0"
},
{
"name": "message",
"documentation": "@roblox/global/stdio.prompt/param/1"
},
{
"name": "defaultOrOptions",
"documentation": "@roblox/global/stdio.prompt/param/2"
}
],
"returns": [],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/task.cancel": {
"documentation": "Stops a currently scheduled thread from resuming.",
"params": [
{
"name": "thread",
"documentation": "@roblox/global/task.cancel/param/0"
}
],
"returns": [],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/task.defer": {
"documentation": "Defers a thread or function to run at the end of the current task queue.",
"params": [
{
"name": "functionOrThread",
"documentation": "@roblox/global/task.defer/param/0"
}
],
"returns": [
"@roblox/global/task.defer/return/0"
],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/task.delay": {
"documentation": "Delays a thread or function to run after `duration` seconds.",
"params": [
{
"name": "functionOrThread",
"documentation": "@roblox/global/task.delay/param/0"
}
],
"returns": [
"@roblox/global/task.delay/return/0"
],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/task.spawn": {
"documentation": "Instantly runs a thread or function.\n\nIf the spawned task yields, the thread that spawned the task\nwill resume, letting the spawned task run in the background.",
"params": [
{
"name": "functionOrThread",
"documentation": "@roblox/global/task.spawn/param/0"
}
],
"returns": [
"@roblox/global/task.spawn/return/0"
],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/task.wait": {
"documentation": "Waits for the given duration, with a minimum wait time of 10 milliseconds.",
"params": [
{
"name": "duration",
"documentation": "@roblox/global/task.wait/param/0"
}
],
"returns": [
"@roblox/global/task.wait/return/0"
],
"learn_more_link": "",
"code_sample": ""
},
"@roblox/global/fs.readFile/param/0": {
"documentation": "The path to the file to read"
},
"@roblox/global/fs.readDir/param/0": {
"documentation": "The directory path to search in"
},
"@roblox/global/fs.writeFile/param/0": {
"documentation": "The path of the file"
},
"@roblox/global/fs.writeFile/param/1": {
"documentation": "The contents of the file"
},
"@roblox/global/fs.writeDir/param/0": {
"documentation": "The directory to create"
},
"@roblox/global/fs.removeFile/param/0": {
"documentation": "The file to remove"
},
"@roblox/global/fs.removeDir/param/0": {
"documentation": "The directory to remove"
},
"@roblox/global/fs.isFile/param/0": {
"documentation": "The file path to check"
},
"@roblox/global/fs.isDir/param/0": {
"documentation": "The directory path to check"
},
"@roblox/global/net.request/param/0": {
"documentation": "The URL or request config to use"
},
"@roblox/global/net.socket/param/0": {
"documentation": "The URL to connect to"
},
"@roblox/global/net.serve/param/0": {
"documentation": "The port to use for the server"
},
"@roblox/global/net.serve/param/1": {
"documentation": "The handler function or config to use for the server"
},
"@roblox/global/net.jsonEncode/param/0": {
"documentation": "The value to encode as JSON"
},
"@roblox/global/net.jsonEncode/param/1": {
"documentation": "If the encoded JSON string should include newlines and spaces. Defaults to false"
},
"@roblox/global/net.jsonDecode/param/0": {
"documentation": "The JSON string to decode"
},
"@roblox/global/process.exit/param/0": {
"documentation": "The exit code to set"
},
"@roblox/global/process.spawn/param/0": {
"documentation": "The program to spawn as a child process"
},
"@roblox/global/process.spawn/param/1": {
"documentation": "Additional parameters to pass to the program"
},
"@roblox/global/process.spawn/param/2": {
"documentation": "A dictionary of options for the child process"
},
"@roblox/global/stdio.color/param/0": {
"documentation": "The color to use"
},
"@roblox/global/stdio.style/param/0": {
"documentation": "The style to use"
},
"@roblox/global/stdio.format/param/0": {
"documentation": "The values to format"
},
"@roblox/global/stdio.write/param/0": {
"documentation": "The string to write to stdout"
},
"@roblox/global/stdio.ewrite/param/0": {
"documentation": "The string to write to stderr"
},
"@roblox/global/stdio.prompt/param/0": {
"documentation": "The kind of prompt to use"
},
"@roblox/global/stdio.prompt/param/1": {
"documentation": "The message to show the user"
},
"@roblox/global/stdio.prompt/param/2": {
"documentation": "The default value for the prompt, or options to choose from for selection prompts"
},
"@roblox/global/task.cancel/param/0": {
"documentation": "The thread to cancel"
},
"@roblox/global/task.defer/param/0": {
"documentation": "The function or thread to defer"
},
"@roblox/global/task.delay/param/0": {
"documentation": "The function or thread to delay"
},
"@roblox/global/task.spawn/param/0": {
"documentation": "The function or thread to spawn"
},
"@roblox/global/task.wait/param/0": {
"documentation": "The amount of time to wait"
},
"@roblox/global/fs.readFile/return/0": {
"documentation": "The contents of the file"
},
"@roblox/global/fs.readDir/return/0": {
"documentation": "A list of files & directories found"
},
"@roblox/global/fs.isFile/return/0": {
"documentation": "If the path is a file or not"
},
"@roblox/global/fs.isDir/return/0": {
"documentation": "If the path is a directory or not"
},
"@roblox/global/net.request/return/0": {
"documentation": "A dictionary representing the response for the request"
},
"@roblox/global/net.socket/return/0": {
"documentation": "A web socket handle"
},
"@roblox/global/net.jsonEncode/return/0": {
"documentation": "The encoded JSON string"
},
"@roblox/global/net.jsonDecode/return/0": {
"documentation": "The decoded lua value"
},
"@roblox/global/process.spawn/return/0": {
"documentation": "A dictionary representing the result of the child process"
},
"@roblox/global/stdio.color/return/0": {
"documentation": "A printable ANSI string"
},
"@roblox/global/stdio.style/return/0": {
"documentation": "A printable ANSI string"
},
"@roblox/global/stdio.format/return/0": {
"documentation": "The formatted string"
},
"@roblox/global/task.defer/return/0": {
"documentation": "The thread that will be deferred"
},
"@roblox/global/task.delay/return/0": {
"documentation": "The thread that will be delayed"
},
"@roblox/global/task.spawn/return/0": {
"documentation": "The thread that was spawned"
},
"@roblox/global/task.wait/return/0": {
"documentation": "The exact amount of time waited"
}
}

View file

@ -7,7 +7,10 @@ use lune::Lune;
use tokio::fs::{read_to_string, write};
use crate::{
gen::{generate_docs_json_from_definitions, generate_wiki_dir_from_definitions},
gen::{
generate_docs_json_from_definitions, generate_selene_defs_from_definitions,
generate_wiki_dir_from_definitions,
},
utils::{
files::find_parse_file_path,
listing::{find_lune_scripts, print_lune_scripts, sort_lune_scripts},
@ -18,8 +21,7 @@ pub(crate) const FILE_NAME_SELENE_TYPES: &str = "lune.yml";
pub(crate) const FILE_NAME_LUAU_TYPES: &str = "luneTypes.d.luau";
pub(crate) const FILE_NAME_DOCS: &str = "luneDocs.json";
pub(crate) const FILE_CONTENTS_SELENE_TYPES: &str = include_str!("../../../lune.yml");
pub(crate) const FILE_CONTENTS_LUAU_TYPES: &str = include_str!("../../../luneTypes.d.luau");
pub(crate) const FILE_CONTENTS_LUAU_TYPES: &str = include_str!("../../../docs/luneTypes.d.luau");
/// A Luau script runner
#[derive(Parser, Debug, Default, Clone)]
@ -33,12 +35,12 @@ pub struct Cli {
/// List scripts found inside of a nearby `lune` directory
#[clap(long, short = 'l')]
list: bool,
/// Generate a Selene type definitions file in the current dir
#[clap(long)]
generate_selene_types: bool,
/// Generate a Luau type definitions file in the current dir
#[clap(long)]
generate_luau_types: bool,
/// Generate a Selene type definitions file in the current dir
#[clap(long)]
generate_selene_types: bool,
/// Generate a Lune documentation file for Luau LSP
#[clap(long)]
generate_docs_file: bool,
@ -111,23 +113,23 @@ impl Cli {
}
}
// Generate (save) definition files, if wanted
let generate_file_requested = self.generate_selene_types
|| self.generate_luau_types
let generate_file_requested = self.generate_luau_types
|| self.generate_selene_types
|| self.generate_docs_file
|| self.generate_wiki_dir;
if generate_file_requested {
if self.generate_selene_types {
generate_and_save_file(FILE_NAME_SELENE_TYPES, "Selene type definitions", || {
Ok(FILE_CONTENTS_SELENE_TYPES.to_string())
})
.await?;
}
if self.generate_luau_types {
generate_and_save_file(FILE_NAME_LUAU_TYPES, "Luau type definitions", || {
Ok(FILE_CONTENTS_LUAU_TYPES.to_string())
})
.await?;
}
if self.generate_selene_types {
generate_and_save_file(FILE_NAME_SELENE_TYPES, "Selene type definitions", || {
generate_selene_defs_from_definitions(FILE_CONTENTS_LUAU_TYPES)
})
.await?;
}
if self.generate_docs_file {
generate_and_save_file(FILE_NAME_DOCS, "Luau LSP documentation", || {
generate_docs_json_from_definitions(FILE_CONTENTS_LUAU_TYPES, "roblox/global")

View file

@ -1,8 +1,10 @@
mod doc;
mod docs_file;
mod selene_defs;
mod wiki_dir;
pub use docs_file::generate_from_type_definitions as generate_docs_json_from_definitions;
pub use selene_defs::generate_from_type_definitions as generate_selene_defs_from_definitions;
pub use wiki_dir::generate_from_type_definitions as generate_wiki_dir_from_definitions;
pub use self::doc::DocumentationVisitor;

View file

@ -0,0 +1,9 @@
use anyhow::{Context, Result};
use serde_yaml::Value as YamlValue;
use super::doc::DocumentationVisitor;
pub fn generate_from_type_definitions(contents: &str) -> Result<String> {
let _visitor = DocumentationVisitor::from_definitions(contents)?;
serde_yaml::to_string(&YamlValue::Null).context("Failed to encode docs as json")
}