diff --git a/specta-jsdoc/Cargo.toml b/specta-jsdoc/Cargo.toml new file mode 100644 index 0000000..9b67bff --- /dev/null +++ b/specta-jsdoc/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "specta-jsdoc" +description = "Export your Rust types to JSDoc" +version = "0.0.3" +authors = ["Oscar Beaumont "] +edition = "2021" +license = "MIT" +repository = "https://github.com/oscartbeaumont/specta" +documentation = "https://docs.rs/specta-jsdoc/latest/specta-jsdoc" +keywords = ["async", "specta", "jsdoc", "rspc", "typescript", "typesafe"] +categories = ["web-programming", "asynchronous"] + +# /bin/sh RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features +[package.metadata."docs.rs"] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[features] +default = [] + +[lints] +workspace = true + +[dependencies] +specta = { version = "=2.0.0-rc.16", path = "../specta" } +specta-typescript = { version = "=0.0.3", path = "../specta-typescript" } +# TODO: Don't depend on serde +specta-serde = { version = "=0.0.3", path = "../specta-serde" } diff --git a/specta-jsdoc/src/lib.rs b/specta-jsdoc/src/lib.rs new file mode 100644 index 0000000..2b9868b --- /dev/null +++ b/specta-jsdoc/src/lib.rs @@ -0,0 +1,109 @@ +//! [JSDoc](https://jsdoc.app) language exporter. +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://github.com/oscartbeaumont/specta/raw/main/.github/logo-128.png", + html_favicon_url = "https://github.com/oscartbeaumont/specta/raw/main/.github/logo-128.png" +)] + +use std::{borrow::Cow, path::Path}; + +use specta::{Language, TypeMap}; +use specta_typescript::{BigIntExportBehavior, CommentFormatterFn, FormatterFn}; + +// TODO: Ensure this is up to our `Typescript` exporters standards. + +/// JSDoc language exporter. +#[derive(Debug, Clone, Default)] +pub struct JSDoc(pub specta_typescript::Typescript); + +impl From for JSDoc { + fn from(ts: specta_typescript::Typescript) -> Self { + Self(ts) + } +} + +impl JSDoc { + /// Construct a new JSDoc exporter with the default options configured. + pub fn new() -> Self { + Default::default() + } + + /// Configure a header for the file. + /// + /// This is perfect for configuring lint ignore rules or other file-level comments. + pub fn header(mut self, header: impl Into>) -> Self { + self.0.header = header.into(); + self + } + + // TODO: Only keep this is TS stays responsible for exporting which it probs won't. + /// Removes the default Specta header from the output. + pub fn remove_default_header(mut self) -> Self { + self.0.remove_default_header = true; + self + } + + /// Configure the BigInt handling behaviour + pub fn bigint(mut self, bigint: BigIntExportBehavior) -> Self { + self.0.bigint = bigint; + self + } + + /// Configure a function which is responsible for styling the comments to be exported + /// + /// Implementations: + /// - [`js_doc`](crate::lang::ts::js_doc) + /// + /// Not calling this method will default to the [`js_doc`](crate::lang::ts::js_doc) exporter. + /// `None` will disable comment exporting. + /// `Some(exporter)` will enable comment exporting using the provided exporter. + pub fn comment_style(mut self, exporter: CommentFormatterFn) -> Self { + self.0.comment_exporter = Some(exporter); + self + } + + /// Configure a function which is responsible for formatting the result file or files + /// + /// + /// Built-in implementations: + /// - [`prettier`](specta_typescript:formatter:::prettier) + /// - [`ESLint`](specta_typescript::formatter::eslint) + /// - [`Biome`](specta_typescript::formatter::biome)e + pub fn formatter(mut self, formatter: FormatterFn) -> Self { + self.0.formatter = Some(formatter); + self + } +} + +impl Language for JSDoc { + type Error = specta_typescript::ExportError; // TODO: Custom error type + + // TODO: Make this properly export JSDoc + fn export(&self, type_map: TypeMap) -> Result { + todo!("Coming soon..."); + // let mut out = self.0.header.to_string(); + // if !self.0.remove_default_header { + // out += "// This file has been generated by Specta. DO NOT EDIT.\n\n"; + // } + + // if let Some((ty_name, l0, l1)) = detect_duplicate_type_names(&type_map).into_iter().next() { + // return Err(ExportError::DuplicateTypeName(ty_name, l0, l1)); + // } + + // for (_, ty) in type_map.iter() { + // is_valid_ty(&ty.inner, &type_map)?; + + // out += &export_named_datatype(&self.0, ty, &type_map)?; + // out += "\n\n"; + // } + + // Ok(out) + } + + fn format(&self, path: &Path) -> Result<(), Self::Error> { + if let Some(formatter) = self.0.formatter { + formatter(path)?; + } + Ok(()) + } +} diff --git a/specta-typescript/src/formatter.rs b/specta-typescript/src/formatter.rs index 061e729..40d6f84 100644 --- a/specta-typescript/src/formatter.rs +++ b/specta-typescript/src/formatter.rs @@ -1,9 +1,9 @@ -use std::{io, path::PathBuf, process::Command}; +use std::{io, path::Path, process::Command}; use crate::typescript::FormatterFn; /// Format the specified file using [ESLint](https://eslint.org). -pub fn eslint(file: PathBuf) -> io::Result<()> { +pub fn eslint(file: &Path) -> io::Result<()> { Command::new("eslint") .arg("--fix") .arg(file) @@ -16,7 +16,7 @@ pub fn eslint(file: PathBuf) -> io::Result<()> { const _: FormatterFn = eslint; /// Format the specified file using [Prettier](https://prettier.io). -pub fn prettier(file: PathBuf) -> io::Result<()> { +pub fn prettier(file: &Path) -> io::Result<()> { Command::new("prettier") .arg("--write") .arg(file) @@ -29,7 +29,7 @@ pub fn prettier(file: PathBuf) -> io::Result<()> { const _: FormatterFn = prettier; /// Format the specified file using [Biome](https://prettier.io). -pub fn biome(file: PathBuf) -> io::Result<()> { +pub fn biome(file: &Path) -> io::Result<()> { Command::new("biome") .arg("format") .arg(file) diff --git a/specta-typescript/src/typescript.rs b/specta-typescript/src/typescript.rs index 62d887e..9518891 100644 --- a/specta-typescript/src/typescript.rs +++ b/specta-typescript/src/typescript.rs @@ -1,4 +1,8 @@ -use std::{borrow::Cow, io, path::PathBuf}; +use std::{ + borrow::Cow, + io, + path::{Path, PathBuf}, +}; use specta::{datatype::DeprecatedType, Language, TypeMap}; use specta_serde::is_valid_ty; @@ -16,7 +20,7 @@ pub struct CommentFormatterArgs<'a> { pub type CommentFormatterFn = fn(CommentFormatterArgs) -> String; // TODO: Returning `Cow`??? /// The signature for a function responsible for formatter a Typescript file. -pub type FormatterFn = fn(PathBuf) -> io::Result<()>; +pub type FormatterFn = fn(&Path) -> io::Result<()>; /// Allows you to configure how Specta's Typescript exporter will deal with BigInt types ([i64], [i128] etc). /// @@ -58,8 +62,6 @@ pub struct Typescript { pub comment_exporter: Option, /// How the resulting file should be formatted. pub formatter: Option, - /// The path to export the resulting bindings to. - pub path: Option, } impl Default for Typescript { @@ -70,7 +72,6 @@ impl Default for Typescript { bigint: Default::default(), comment_exporter: Some(comments::js_doc), formatter: None, - path: None, } } } @@ -126,21 +127,6 @@ impl Typescript { self.formatter = Some(formatter); self } - - /// TODO - pub fn path(mut self, path: impl Into) -> Self { - self.path = Some(path.into()); - self - } - - // TODO: Should this take a `path` or should it use `self.path`??? - /// Run the specified formatter on the given path. - pub fn run_format(&self, path: PathBuf) -> io::Result<()> { - if let Some(formatter) = self.formatter { - formatter(path)?; - } - Ok(()) - } } impl Language for Typescript { @@ -165,4 +151,11 @@ impl Language for Typescript { Ok(out) } + + fn format(&self, path: &Path) -> Result<(), Self::Error> { + if let Some(formatter) = self.formatter { + formatter(path)?; + } + Ok(()) + } } diff --git a/specta/src/language.rs b/specta/src/language.rs index cac4ff4..a00a542 100644 --- a/specta/src/language.rs +++ b/specta/src/language.rs @@ -1,3 +1,5 @@ +use std::path::Path; + use crate::TypeMap; /// TODO @@ -10,4 +12,21 @@ pub trait Language { /// TODO fn export(&self, type_map: TypeMap) -> Result; + + /// TODO + // TODO: Not sure I love this here but it's for Tauri Specta. + // TODO: Really a formatter can support multiple languages so it would be nice if we don't need `specta_typescript::eslint`, `specta_jsdoc::eslint`, etc. + fn format(&self, path: &Path) -> Result<(), Self::Error>; +} + +impl Language for &T { + type Error = T::Error; + + fn export(&self, type_map: TypeMap) -> Result { + (*self).export(type_map) + } + + fn format(&self, path: &Path) -> Result<(), Self::Error> { + (*self).format(path) + } } diff --git a/specta/src/type_map.rs b/specta/src/type_map.rs index 80821f8..5d5315a 100644 --- a/specta/src/type_map.rs +++ b/specta/src/type_map.rs @@ -54,6 +54,10 @@ impl TypeMap { self.map.remove(&sid).flatten() } + pub fn append(&mut self, type_map: &mut TypeMap) { + self.map.append(&mut type_map.map); + } + // TODO: It would be nice if this would a proper `Iterator` or `IntoIterator` implementation! pub fn iter(&self) -> impl Iterator { #[allow(clippy::unnecessary_filter_map)]