Add skip_typescript attribute to prevent .d.ts emit (#2016)

* Add skip_typescript attribute to prevent .d.ts emit

* Add guide page for typescript attribute
This commit is contained in:
Joey Watts 2020-03-03 10:34:28 -05:00 committed by GitHub
parent 3f4acc453b
commit 7ffb5ed70c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 227 additions and 56 deletions

View File

@ -221,6 +221,7 @@ pub struct Function {
pub rust_attrs: Vec<syn::Attribute>,
pub rust_vis: syn::Visibility,
pub r#async: bool,
pub generate_typescript: bool,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
@ -231,6 +232,7 @@ pub struct Struct {
pub fields: Vec<StructField>,
pub comments: Vec<String>,
pub is_inspectable: bool,
pub generate_typescript: bool,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
@ -243,6 +245,7 @@ pub struct StructField {
pub getter: Ident,
pub setter: Ident,
pub comments: Vec<String>,
pub generate_typescript: bool,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
@ -252,6 +255,7 @@ pub struct Enum {
pub variants: Vec<Variant>,
pub comments: Vec<String>,
pub hole: u32,
pub generate_typescript: bool,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]

View File

@ -206,6 +206,7 @@ fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Functi
Function {
arg_names,
name: &func.name,
generate_typescript: func.generate_typescript,
}
}
@ -218,6 +219,7 @@ fn shared_enum<'a>(e: &'a ast::Enum, intern: &'a Interner) -> Enum<'a> {
.map(|v| shared_variant(v, intern))
.collect(),
comments: e.comments.iter().map(|s| &**s).collect(),
generate_typescript: e.generate_typescript,
}
}
@ -307,6 +309,7 @@ fn shared_struct<'a>(s: &'a ast::Struct, intern: &'a Interner) -> Struct<'a> {
.collect(),
comments: s.comments.iter().map(|s| &**s).collect(),
is_inspectable: s.is_inspectable,
generate_typescript: s.generate_typescript,
}
}
@ -318,6 +321,7 @@ fn shared_struct_field<'a>(s: &'a ast::StructField, intern: &'a Interner) -> Str
},
readonly: s.readonly,
comments: s.comments.iter().map(|s| &**s).collect(),
generate_typescript: s.generate_typescript,
}
}

View File

@ -111,7 +111,7 @@ impl<'a> Context<'a> {
&mut self,
export_name: &str,
contents: &str,
comments: Option<String>,
comments: Option<&str>,
) -> Result<(), Error> {
let definition_name = generate_identifier(export_name, &mut self.defined_identifiers);
if contents.starts_with("class") && definition_name != export_name {
@ -119,9 +119,8 @@ impl<'a> Context<'a> {
}
let contents = contents.trim();
if let Some(ref c) = comments {
if let Some(c) = comments {
self.globals.push_str(c);
self.typescript.push_str(c);
}
let global = match self.config.mode {
OutputMode::Node {
@ -804,7 +803,7 @@ impl<'a> Context<'a> {
dst.push_str("}\n");
ts_dst.push_str("}\n");
self.export(&name, &dst, Some(class.comments.clone()))?;
self.export(&name, &dst, Some(&class.comments))?;
self.typescript.push_str(&ts_dst);
Ok(())
@ -2153,41 +2152,58 @@ impl<'a> Context<'a> {
// on what's being exported.
match kind {
Kind::Export(export) => {
let ts_sig = match export.generate_typescript {
true => Some(ts_sig.as_str()),
false => None,
};
let docs = format_doc_comments(&export.comments, Some(js_doc));
match &export.kind {
AuxExportKind::Function(name) => {
self.export(&name, &format!("function{}", code), Some(docs))?;
self.globals.push_str("\n");
if let Some(ts_sig) = ts_sig {
self.typescript.push_str(&docs);
self.typescript.push_str("export function ");
self.typescript.push_str(&name);
self.typescript.push_str(&ts_sig);
self.typescript.push_str(ts_sig);
self.typescript.push_str(";\n");
}
self.export(&name, &format!("function{}", code), Some(&docs))?;
self.globals.push_str("\n");
}
AuxExportKind::Constructor(class) => {
let exported = require_class(&mut self.exported_classes, class);
if exported.has_constructor {
bail!("found duplicate constructor for class `{}`", class);
}
exported.has_constructor = true;
exported.push(&docs, "constructor", "", &code, &ts_sig);
exported.push(&docs, "constructor", "", &code, ts_sig);
}
AuxExportKind::Getter { class, field } => {
let ret_ty = ts_ret_ty.unwrap();
let ret_ty = match export.generate_typescript {
true => match &ts_ret_ty {
Some(s) => Some(s.as_str()),
_ => None,
},
false => None,
};
let exported = require_class(&mut self.exported_classes, class);
exported.push_getter(&docs, field, &code, &ret_ty);
exported.push_getter(&docs, field, &code, ret_ty);
}
AuxExportKind::Setter { class, field } => {
let arg_ty = ts_arg_tys[0].clone();
let arg_ty = match export.generate_typescript {
true => Some(ts_arg_tys[0].as_str()),
false => None,
};
let exported = require_class(&mut self.exported_classes, class);
exported.push_setter(&docs, field, &code, &arg_ty, might_be_optional_field);
exported.push_setter(&docs, field, &code, arg_ty, might_be_optional_field);
}
AuxExportKind::StaticFunction { class, name } => {
let exported = require_class(&mut self.exported_classes, class);
exported.push(&docs, name, "static ", &code, &ts_sig);
exported.push(&docs, name, "static ", &code, ts_sig);
}
AuxExportKind::Method { class, name, .. } => {
let exported = require_class(&mut self.exported_classes, class);
exported.push(&docs, name, "", &code, &ts_sig);
exported.push(&docs, name, "", &code, ts_sig);
}
}
}
@ -2865,19 +2881,27 @@ impl<'a> Context<'a> {
}
fn generate_enum(&mut self, enum_: &AuxEnum) -> Result<(), Error> {
let docs = format_doc_comments(&enum_.comments, None);
let mut variants = String::new();
if enum_.generate_typescript {
self.typescript.push_str(&docs);
self.typescript
.push_str(&format!("export enum {} {{", enum_.name));
}
for (name, value) in enum_.variants.iter() {
variants.push_str(&format!("{}:{},", name, value));
if enum_.generate_typescript {
self.typescript.push_str(&format!("\n {},", name));
}
}
if enum_.generate_typescript {
self.typescript.push_str("\n}\n");
}
self.export(
&enum_.name,
&format!("Object.freeze({{ {} }})", variants),
Some(format_doc_comments(&enum_.comments, None)),
Some(&docs),
)?;
Ok(())
@ -3163,12 +3187,20 @@ fn require_class<'a>(
}
impl ExportedClass {
fn push(&mut self, docs: &str, function_name: &str, function_prefix: &str, js: &str, ts: &str) {
fn push(
&mut self,
docs: &str,
function_name: &str,
function_prefix: &str,
js: &str,
ts: Option<&str>,
) {
self.contents.push_str(docs);
self.contents.push_str(function_prefix);
self.contents.push_str(function_name);
self.contents.push_str(js);
self.contents.push_str("\n");
if let Some(ts) = ts {
self.typescript.push_str(docs);
self.typescript.push_str(" ");
self.typescript.push_str(function_prefix);
@ -3176,11 +3208,15 @@ impl ExportedClass {
self.typescript.push_str(ts);
self.typescript.push_str(";\n");
}
}
/// Used for adding a getter to a class, mainly to ensure that TypeScript
/// generation is handled specially.
fn push_getter(&mut self, docs: &str, field: &str, js: &str, ret_ty: &str) {
self.push_accessor(docs, field, js, "get ", ret_ty);
fn push_getter(&mut self, docs: &str, field: &str, js: &str, ret_ty: Option<&str>) {
self.push_accessor(docs, field, js, "get ");
if let Some(ret_ty) = ret_ty {
self.push_accessor_ts(field, ret_ty);
}
self.readable_properties.push(field.to_string());
}
@ -3191,28 +3227,18 @@ impl ExportedClass {
docs: &str,
field: &str,
js: &str,
ret_ty: &str,
ret_ty: Option<&str>,
might_be_optional_field: bool,
) {
let (has_setter, is_optional) = self.push_accessor(docs, field, js, "set ", ret_ty);
self.push_accessor(docs, field, js, "set ");
if let Some(ret_ty) = ret_ty {
let (has_setter, is_optional) = self.push_accessor_ts(field, ret_ty);
*has_setter = true;
*is_optional = might_be_optional_field;
}
}
fn push_accessor(
&mut self,
docs: &str,
field: &str,
js: &str,
prefix: &str,
ret_ty: &str,
) -> (&mut bool, &mut bool) {
self.contents.push_str(docs);
self.contents.push_str(prefix);
self.contents.push_str(field);
self.contents.push_str(js);
self.contents.push_str("\n");
fn push_accessor_ts(&mut self, field: &str, ret_ty: &str) -> (&mut bool, &mut bool) {
let (ty, has_setter, is_optional) = self
.typescript_fields
.entry(field.to_string())
@ -3221,6 +3247,14 @@ impl ExportedClass {
*ty = ret_ty.to_string();
(has_setter, is_optional)
}
fn push_accessor(&mut self, docs: &str, field: &str, js: &str, prefix: &str) {
self.contents.push_str(docs);
self.contents.push_str(prefix);
self.contents.push_str(field);
self.contents.push_str(js);
self.contents.push_str("\n");
}
}
#[test]

View File

@ -454,6 +454,7 @@ impl<'a> Context<'a> {
comments: concatenate_comments(&export.comments),
arg_names: Some(export.function.arg_names),
kind,
generate_typescript: export.function.generate_typescript,
},
);
Ok(())
@ -767,6 +768,7 @@ impl<'a> Context<'a> {
.iter()
.map(|v| (v.name.to_string(), v.value))
.collect(),
generate_typescript: enum_.generate_typescript,
};
self.aux.enums.push(aux);
Ok(())
@ -799,6 +801,7 @@ impl<'a> Context<'a> {
class: struct_.name.to_string(),
field: field.name.to_string(),
},
generate_typescript: field.generate_typescript,
},
);
@ -824,6 +827,7 @@ impl<'a> Context<'a> {
class: struct_.name.to_string(),
field: field.name.to_string(),
},
generate_typescript: field.generate_typescript,
},
);
}
@ -831,6 +835,7 @@ impl<'a> Context<'a> {
name: struct_.name.to_string(),
comments: concatenate_comments(&struct_.comments),
is_inspectable: struct_.is_inspectable,
generate_typescript: struct_.generate_typescript,
};
self.aux.structs.push(aux);
@ -1048,6 +1053,7 @@ impl<'a> Context<'a> {
comments: String::new(),
arg_names: None,
kind,
generate_typescript: true,
};
assert!(self.aux.export_map.insert(id, export).is_none());
}

View File

@ -73,6 +73,8 @@ pub struct AuxExport {
pub arg_names: Option<Vec<String>>,
/// What kind of function this is and where it shows up
pub kind: AuxExportKind,
/// Whether typescript bindings should be generated for this export.
pub generate_typescript: bool,
}
/// All possible kinds of exports from a wasm module.
@ -131,7 +133,10 @@ pub struct AuxEnum {
/// The copied Rust comments to forward to JS
pub comments: String,
/// A list of variants with their name and value
/// and whether typescript bindings should be generated for each variant
pub variants: Vec<(String, u32)>,
/// Whether typescript bindings should be generated for this enum.
pub generate_typescript: bool,
}
#[derive(Debug)]
@ -142,6 +147,8 @@ pub struct AuxStruct {
pub comments: String,
/// Whether to generate helper methods for inspecting the class
pub is_inspectable: bool,
/// Whether typescript bindings should be generated for this struct.
pub generate_typescript: bool,
}
/// All possible types of imports that can be imported by a wasm module.

View File

@ -51,6 +51,7 @@ macro_rules! attrgen {
(vendor_prefix, VendorPrefix(Span, Ident)),
(variadic, Variadic(Span)),
(typescript_custom_section, TypescriptCustomSection(Span)),
(skip_typescript, SkipTypescript(Span)),
(start, Start(Span)),
(skip, Skip(Span)),
(typescript_type, TypeScriptType(Span, String, Span)),
@ -354,9 +355,11 @@ impl<'a> ConvertToAst<BindgenAttrs> for &'a mut syn::ItemStruct {
getter: Ident::new(&getter, Span::call_site()),
setter: Ident::new(&setter, Span::call_site()),
comments,
generate_typescript: attrs.skip_typescript().is_none(),
});
attrs.check_used()?;
}
let generate_typescript = attrs.skip_typescript().is_none();
let comments: Vec<String> = extract_doc_comments(&self.attrs);
attrs.check_used()?;
Ok(ast::Struct {
@ -365,6 +368,7 @@ impl<'a> ConvertToAst<BindgenAttrs> for &'a mut syn::ItemStruct {
fields,
comments,
is_inspectable,
generate_typescript,
})
}
}
@ -720,6 +724,7 @@ fn function_from_decl(
rust_attrs: attrs,
rust_vis: vis,
r#async: sig.asyncness.is_some(),
generate_typescript: opts.skip_typescript().is_none(),
},
method_self,
))
@ -798,11 +803,12 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
};
f.macro_parse(program, opts)?;
}
syn::Item::Enum(e) => {
if let Some(opts) = opts {
opts.check_used()?;
}
e.macro_parse(program, (tokens,))?;
syn::Item::Enum(mut e) => {
let opts = match opts {
Some(opts) => opts,
None => BindgenAttrs::find(&mut e.attrs)?,
};
e.macro_parse(program, (tokens, opts))?;
}
syn::Item::Const(mut c) => {
let opts = match opts {
@ -1032,15 +1038,17 @@ fn import_enum(enum_: syn::ItemEnum, program: &mut ast::Program) -> Result<(), D
Ok(())
}
impl<'a> MacroParse<(&'a mut TokenStream,)> for syn::ItemEnum {
impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
fn macro_parse(
self,
program: &mut ast::Program,
(tokens,): (&'a mut TokenStream,),
(tokens, opts): (&'a mut TokenStream, BindgenAttrs),
) -> Result<(), Diagnostic> {
if self.variants.len() == 0 {
bail_span!(self, "cannot export empty enums to JS");
}
let generate_typescript = opts.skip_typescript().is_none();
opts.check_used()?;
// Check if the first value is a string literal
match self.variants[0].discriminant {
@ -1141,8 +1149,8 @@ impl<'a> MacroParse<(&'a mut TokenStream,)> for syn::ItemEnum {
variants,
comments,
hole,
generate_typescript,
});
Ok(())
}
}

View File

@ -100,6 +100,7 @@ macro_rules! shared_api {
name: &'a str,
variants: Vec<EnumVariant<'a>>,
comments: Vec<&'a str>,
generate_typescript: bool,
}
struct EnumVariant<'a> {
@ -110,6 +111,7 @@ macro_rules! shared_api {
struct Function<'a> {
arg_names: Vec<String>,
name: &'a str,
generate_typescript: bool,
}
struct Struct<'a> {
@ -117,12 +119,14 @@ macro_rules! shared_api {
fields: Vec<StructField<'a>>,
comments: Vec<&'a str>,
is_inspectable: bool,
generate_typescript: bool,
}
struct StructField<'a> {
name: &'a str,
readonly: bool,
comments: Vec<&'a str>,
generate_typescript: bool,
}
struct LocalModule<'a> {

View File

@ -7,6 +7,7 @@ edition = "2018"
[dependencies]
wasm-bindgen = { path = '../..' }
web-sys = { path = '../web-sys', features = [ 'HtmlElement', 'Node', 'Document' ] }
js-sys = { path = '../js-sys' }
[lib]
crate-type = ['cdylib']

View File

@ -1,5 +1,6 @@
pub mod custom_section;
pub mod getters_setters;
pub mod omit_definition;
pub mod opt_args_and_ret;
pub mod optional_fields;
pub mod simple_fn;

View File

@ -0,0 +1,34 @@
use wasm_bindgen::prelude::*;
#[wasm_bindgen(typescript_custom_section)]
const TYPE_GET_VALUE: &'static str =
"export function take_function(func: (x: number) => void): void;";
#[wasm_bindgen(skip_typescript)]
pub fn take_function(_: js_sys::Function) {}
#[wasm_bindgen]
pub struct MyExportedStruct {
#[wasm_bindgen(skip_typescript)]
pub field: bool,
}
#[wasm_bindgen]
impl MyExportedStruct {
#[wasm_bindgen(skip_typescript)]
pub fn method(&mut self) {
self.field = !self.field;
}
#[wasm_bindgen(skip_typescript)]
pub fn static_func() {
panic!("oh no!");
}
}
#[wasm_bindgen(skip_typescript)]
pub enum MyEnum {
One,
Two,
Three,
}

View File

@ -0,0 +1,26 @@
import * as wbg from '../pkg/typescript_tests';
wbg.take_function((value) => {
// `value` should be inferred as `number` because of the
// custom typescript section. If `typescript = false` does
// not prevent the generation of a signature that takes any
// function, then this will trigger a noImplicitAny error.
console.log(value);
});
declare function assert<T>(message: T): void;
type EnableIfEnum = "MyEnum" extends keyof typeof wbg ? never : string;
assert<EnableIfEnum>("`MyEnum` type should not be exported.");
type EnableIfStruct = "MyStruct" extends keyof typeof wbg ? never : string;
assert<EnableIfStruct>("`MyStruct` type should not be exported.");
type EnableIfField = "field" extends keyof wbg.MyExportedStruct ? never : string;
assert<EnableIfField>("`field` should not exist on `MyExportedStruct`.");
type EnableIfMethod = "method" extends keyof wbg.MyExportedStruct ? never : string;
assert<EnableIfMethod>("`method` should not exist on `MyExportedStruct`.");
type EnableIfStaticMethod = "static_func" extends keyof typeof wbg.MyExportedStruct ? never : string;
assert<EnableIfStaticMethod>("`static_func` should not exist on `MyExportedStruct`.");

View File

@ -0,0 +1,42 @@
# `skip_typescript`
By default, Rust exports exposed to JavaScript will generate TypeScript definitions (unless `--no-typescript` is used). The `skip_typescript` attribute can be used to disable type generation per function, enum, struct, or field. For example:
```rust
#[wasm_bindgen(skip_typescript)]
pub enum MyHiddenEnum {
One,
Two,
Three
}
#[wasm_bindgen]
pub struct MyPoint {
pub x: u32,
#[wasm_bindgen(skip_typescript)]
pub y: u32,
}
#[wasm_bindgen]
impl MyPoint {
#[wasm_bindgen(skip_typescript)]
pub fn stringify(&self) -> String {
format!("({}, {})", self.x, self.y)
}
}
```
Will generate the following `.d.ts` file:
```ts
/* tslint:disable */
/* eslint-disable */
export class MyPoint {
free(): void;
x: number;
}
```
When combined with [the `typescript_custom_section` attribute](typescript_custom_section.html), this can be used to manually specify more specific function types instead of using the generated definitions.