Parse jsdoc comments in Script(...) · Issue #1597 · sinclairzx81/typebox · GitHub
Skip to content

Parse jsdoc comments in Script(...) #1597

Closed

Description

@mmkal

It'd be great if we could write normal jsdoc comments on types passed to Script(...).

Example:

import {Script} from 'typebox'

const Person = Script(`{
  /** full name excluding any middle names */
  name: string
  /** iso-8601 format string */
  birthDate: string
}`)

Currently produces

{
  type: 'object',
  required: [ 'name', 'birthDate' ],
  properties: { name: { type: 'string' }, birthDate: { type: 'string' } }
}

But it'd be great if produced:

{
  type: 'object',
  required: [ 'name', 'birthDate' ],
  properties: {
    name: {
      type: 'string',
      description: 'full name excluding any middle names'
    },
    birthDate: {
      type: 'string',
      description: 'iso-8601 format string'
    }
  }
}

It'd allow defining normal typescript interfaces as strings, and have useful annotations reach the json-schema they produce, which can then inform downstream tools (UIs, API docs, CLIs, AI tools etc.)

I just tried locally and it seems the above (with its simple format, at least) is one-shottable with codex. So I'd be happy to open a PR, if you think this would be a good idea.

Activity

  1. sinclairzx81 commented on May 7, 2026

    @sinclairzx81
    Owner

    @mmkal Hi,

    It'd be great if we could write normal jsdoc comments on types passed to Script(...).

    At this stage, Type.Script(...) doesn't support for JSDoc parsing, but may in future if it can be done efficiently. The current TB parsing infrastructure is written to ignore comments for performance reasons, mostly due to TypeBox being up against some extremely restrictive type-level compute constraints just to achieve the feature.

    There are some other options available though.


    Options

    You can assign user defined annotations via the Options generic type.

    TypeScript Link Here

    import Type from 'typebox'
    
    const Person = Type.Script(`{
      name: Options<string, {
         description: 'full name excluding any middle names'
      }>
      birthDate: Options<string, {
        format: 'date',
        description: 'iso-8601 format string'
      }>
    }`)
    
    console.log(Person) // const Person = {
                        //   type: "object",
                        //   required: [ "name", "birthDate" ],
                        //   properties: {
                        //     name: {
                        //       type: "string",
                        //       description: "full name excluding any middle names"
                        //     },
                        //     birthDate: {
                        //       type: "string",
                        //       format: "date",
                        //       description: "iso-8601 format string"
                        //     }
                        //   }
                        // }

    The Options type was written to be expressable inside TypeScript definition modules (.d.ts) using a single line shim. The intention was to try and ensure Script types remain portable between TypeBox and TypeScript.

    TypeScript Link Here

    type Options<T, _> = T // <--- add this
    
    type Person = {
      name: Options<string, {
         description: 'full name excluding any middle names'
      }>
      birthDate: Options<string, {
        format: 'date',
        description: 'iso-8601 format string'
      }>
    }

    JSDoc

    There probably isn't much that can be done to support JSDoc at this stage. But if you are interested in looking at how TypeBox handles parsing, and where the feature would need to be added, the following are the relevant projects.

    ParseBox

    Link: https://github.com/sinclairzx81/parsebox

    This project is the type/runtime parsing infrastructure used by TypeBox. The comment discard is currently part of the tokenizer, but would need to be updated such that the comments can be observed as parsable syntax.

    Syntax

    Link: https://github.com/sinclairzx81/typebox/blob/main/design/syntax/syntax.ts

    The grammar for the JSDoc parser would need to be added to this module which represents the full grammar used to parse TypeScript. TypeBox uses this module to generate runtime and type-level parsers for each definition.


    Let me know if the Options type works for your use case. The Script function was partially written to provide a way to reduce token overhead when transmitting JSON Schema for Structured Outputs (as TypeScript syntax is much much leaner), but I haven't had much time to test a setup using it. I would be curious to know if Script is enabling options that weren't possible previously, so feedback on the feature is most welcome.

    Keep me posted!
    S

  2. mmkal commented on May 7, 2026

    @mmkal
    Author

    Thanks for the detailed reply!

    About "parsing infrastructure is written to ignore comments for performance reasons" - do you mean at the compile time or runtime? I'm interested in the runtime schema output. IMO the type-level parser does not need to preserve the literal jsdoc text (as far as I know that's not even possible with current typescript, and it would be inefficient as you say if it was).

    Given I was asking about runtime, my guess is that it'd be a lot easier and more efficient. In fact, typebox must already be recognising jsdoc comments, but just throwing them out. A very rough/ignorant-of-reality algorithm that would cover that majority of use cases, enough to be extremely useful:

    • keep track of text between /** and */
    • trim it once you reach */, store it as lastJsdocComment (maybe with some basic normalisation/white-space stripping)
    • if you reach a top-level property (i.e. something that becomes rootType.properties.whatever = {...}, add it as a the description: rootType.properties.whatever.description = lastJsdocComment

    Here's the attempt my codex made - it uses a near-identical algorithm to the internal DiscardMultilineComment, so I would hope there'd be no significant performance impact: https://github.com/sinclairzx81/typebox/compare/main...mmkal:codex/script-jsdoc-description?w=1 (note: just to keep in one eyeball-able diff I got it to change parser.ts directly, but as you say the proper way to do that is to change upstream in parsebox. This is just a demo of this feature hopefully not having a meaningful perf impact).


    Re: the Options type: good to know that feature exists! But for me it kills the benefit of using typebox like this - which is that you can just write normal typescript (with some limitations, but none that matter when defining types for API contracts, MCP tools, CLIs etc.) and you'll get valid json-schema and type inference in return.

  3. sinclairzx81 commented on May 7, 2026

    @sinclairzx81
    Owner

    @mmkal Hiya,

    More Info on Script

    About "parsing infrastructure is written to ignore comments for performance reasons" - do you mean at the compile time or runtime? I'm interested in the runtime schema output. IMO the type-level parser does not need to preserve the literal jsdoc text (as far as I know that's not even possible with current typescript, and it would be inefficient as you say if it was)

    The Script function needs to parse both. The reason is that Script is designed to be a symmetric / dual track runtime and type-level implementation of the TypeScript programming language. Basically anything the runtime parser does, the static parser needs to do the exact same thing. If the implementations diverge, the library considers it a drift (which would be a critical error and extremely difficult to fix)

    Runtime and Type Level Symmetric

    import Type from 'typebox'
    
    // The Script function produces a statically introspectable Json Schema object.
    
    const T = Type.Script(`{
      x: number
      y: string
      z: boolean
    }`)
    
    // ... where Json Schema properties can be introspected this way.
    
    T.type                                // 'object'
    T.required                            // ['x', 'y', 'z']
    T.properties.x                        // { type: 'number' }
    T.properties.y                        // { type: 'string' }
    T.properties.z                        // { type: 'boolean' }
    
    // ... and where properties must exactly match the output data structure.
    
    console.log(T)                        // {
                                          //   type: 'object',
                                          //   required: ['x', 'y', 'z'],
                                          //   properties: {
                                          //     x: { type: 'number' },
                                          //     y: { type: 'string' },
                                          //     z: { type: 'boolean' }
                                          //   }
                                          // }

    Based on the above, these are the aspects to consider with JSDoc:

    • Dual Track / Mapping: Should the parser consider the primary comment text for the description value, or should it only generate a description property when encountering a @description JSDoc annotation?. This kind of logic would need to be encoded into the parser (semantic actions, mapping.ts as you saw) and where the logic for mapping from JSDoc comment to JSON Schema must eventually result in that output structure that is statically and runtime observable (as above). The main consideration here would be how best to encode the rules for JSDoc within the context of a semantic action (mapping.ts), and is the approach good overall? (I am 50/50 on it)

    • Specification: JSDoc is a complete DSL specification language unto itself and there is no obvious mapping between JSDoc annotations and Json Schema, but for reference the Full JSDoc Spec. TypeBox previously attempted to map JSDoc to JSON Schema via the TypeBox Workbench but wasn't very keen on the implementation. Originally it seemed good in theory (and probably has some future utility), but intentionally avoided it for the 1.x build, both for performance (TB 1.x is built from scratch), and because I need a proper spec before implementation.

    • Performance: The Script parser would need to handle a extra branch per property to test for a potential JSDoc comment entry. This is of course possible (the codex implementation you provided looks moderately workable) but the issue is that it does add an additional degree of complexity and performance overhead, which is alot to take on if there is no well defined mapping from JSDoc to Json Schema (as per Specification)


    Internal Parser Detail (Comments as Syntax)

    Given I was asking about runtime, my guess is that it'd be a lot easier and more efficient. In fact, typebox must already be recognising jsdoc comments, but just throwing them out. A very rough/ignorant-of-reality algorithm that would cover that majority of use cases, enough to be extremely useful:

    As you've maybe seen, the parser is partly aware there is a comment, it's just not semantically aware there's a comment. The internal parser implementation (specifically the token / scanner) currently considers comments as whitespace, and whitespace is treated as insignificant (the parser will skip over it). The implementation was written this way mostly for performance (because it's easy to linear scan past a comment and discard), and because comments were not really a priority consideration for Script (as per Specification / Performance), at least not for 1.x.

    However, I do partially agree that comments probably "should" be considered by the parser as valid syntax, and could be achieved using CFG parsing, it's just that for now, keeping the performance of Script is more of a priority, and the Options generic type does offer a mechanism to assign annotations based on the TS type level syntax.

    This is just the current state of TB 1.x as of today, I am open to additional syntax support in future.


    TypeBox Codegen (TB 0.x) - JSDoc to JSON Schema translation

    which is that you can just write normal typescript (with some limitations, but none that matter when defining types for API contracts, MCP tools, CLIs etc.) and you'll get valid json-schema and type inference in return.

    Prior to Type.Script(...), the TypeBox-Codegen project was used as prototyping system for what eventually got developed into TB1.x. This project is also the backend system used by TypeBox Workbench to transform types. If you just need to transform TypeScript to JSON Schema, and want to use JSDoc, this project implements that, but you won't get type inference (but it sounds like that is ok?)

    The Codegen project does take a dependency on the TypeScript compiler (TSC), but if running code locally on a server / local process, that's probably going to be ok.

    Options type

    Re: the Options type: good to know that feature exists! But for me it kills the benefit of using typebox like this -

    To be honest, I'm not entirely happy with way the Options<T> has turned out (for other reasons), and I wasn't entirely happy with the JSDoc version either. Unfortunately, that really doesn't leave much in the way of prior art to work from. This said I expect the JSDoc implementation (Codegen) is probably the more forward looking direction, so will probably get implemented eventually.

    I wouldn't expect JSDoc support until TB 2.x, and only once the dust has settled and the ecosystem has shuffled over to TS7 native. For now, I encourage the ecosystem (or AI's) to hack around with the current implements (both TypeBox and ParseBox) and see what can be optimized, changed, added and removed.

    I am happy to work with developers on forks of either project.


    Other than the above, I don't really have too much else I can say about JSDoc support. I am open to supporting it, but am hesitant to do so until there's a clear path for mapping JSDoc to Json Schema, and the performance of TypeScript improves a bit with TS7 native optimizations (which is outside my control). The feature will likely be implemented eventually, but is a fairly low priority for now. Again TypeBox-Codegen might offer an alternative solution in the interim.

    Hope this brings some additional insight. Happy to discuss more if you wish.
    Cheers
    S

  4. mmkal commented on May 12, 2026

    @mmkal
    Author

    If the implementations diverge, the library considers it a drift

    That makes sense but surely comments are the exception! They basically don't exist to the typescript compiler. I don't think it should be considered some kind of dangerous drift, otherwise you just have to throw away this very useful information.


    About your aspects to consider - you are absolutely right, I think those ideally require careful thought. I would still argue that it'd be a good idea to add the trimmed jsdoc as-is as description, especially if you're considering a v2 in future where you would look into more sophisticated parsing of things like @description.

    However maybe a more API-design-neutral alternative would be to expose a way to hook into Type.Script. Maybe something like:

    import Type from 'typebox'
    
    const Script: typeof Type.Script = input => Type.Script(input, {
      transform: ({ schema, jsdoc }) => {
        return {
          ...schema,
          description: jsdoc.trim(), // I can do whatever parsing/normalisation I like here - it's my responsibility and not typebox's
        }
      }
    })
    
    const Person = Script(`{
      /** first+last pls */
      name: string
    }`)
    
    console.log(Person) // { type: 'object', required: ['name'], properties: { name: { type: string; description: 'first+last pls' } } }

    Would you be open to that? It'd leave the door open to any of the above ideas in future, without even a breaking change, and it'd even allow users to write their own custom jsdoc-parsers with fancy bespoke stuff like handling for @alias fooBar or whatever was needed, with nothing extra taken on by typebox.

    There are many ways to skin a cat. The only thing that can't be worked around by consumers of typebox is just throwing out the jsdoc comments while parsing like it does now!

  5. sinclairzx81 commented on Jun 15, 2026

    @sinclairzx81
    Owner

    @mmkal Hey, I finally have an update here.

    That makes sense but surely comments are the exception! They basically don't exist to the typescript compiler. I don't think it should be considered some kind of dangerous drift, otherwise you just have to throw away this very useful information.

    So I have decided I'm not going to be supporting JSDoc in TypeBox (at least for now). I've given this a lot of consideration over the past few weeks, but ultimately it has proven just too difficult to support two distinct DSL's (TS & JSDoc) under TB's constrained infrastructure (which is TS only).

    As noted previously, issues with JSDoc are also somewhat compounded with a lot of uncertainly as to how JSDoc should map into schematics. The main concern is that if TB was to invent a JSDoc mapping, if it gets it wrong; it could cripple TypeBox in future (so obviously I want to avoid that). Unless there is a clear specification that details exactly how JSDoc should map to JSON Schema, I'm going to consider JSDoc too dangerous to support in the long term.

    Any future support for JSDoc will likely only extend to TS comment generation only

    Image

    But would only consider the above if TypeScript provided programmable comment generation (which is currently not possible), and it would be limited to type inference only.


    With Keyword

    Instead of JSDoc, TypeBox will instead explore an optional extension keyword called with that enables one to assign arbitrary metadata to a schema. The with keyword replaces the former Option<Type, ...> and provides a more concise mechanism to assign auxiliary NON-TYPE SYSTEM AWARE data to schematics.

    https://sinclairzx81.github.io/typebox/#/docs/script/2_options

    const Vector = Type.Script(`{
      x: number with { minimum: 0, maximum: 1 },
      y: number with { minimum: 0, maximum: 1 },
      z: number with { minimum: 0, maximum: 1 },
    } with { additionalProperties: false }`)

    And where with supports all JS data types except symbol and function.

    Assignable Data Types

    import Type from 'typebox'
    
    const T = Type.Script(`number with { 
      // --------------------------------
      // contraints
      // --------------------------------
      minimum: 10, 
      maximum: 20,
    
      // --------------------------------
      // annotation data types
      // --------------------------------
      valueA: null,            // null
      valueB: undefined,       // undefined
      valueC: 'a number',      // string
      valueD: 100n             // bigint
      valueE: 100              // number
      valueF: true             // boolean
      valueG: [1, 2]           // tuple
      valueH: { x: 1 }         // object
    }`)
    
    // where T outputs:
    
    // const T = {
    //  type: 'number',
    //  minimum: 10,
    //  maximum: 20,
    //  
    //  valueA: null,
    //  valueB: undefined,
    //  valueC: "a number",
    //  valueD: 100n,
    //  valueE: 100,
    //  valueF: true,
    //  valueG: [1, 2],
    //  valueH: { x: 1 }
    // }

    Note that despite the with keyword being unknown syntax to TypeScript, I do consider it a better syntactic alternative to JSDoc because TypeBox can implement it optionally (and with with minimal overhead) and where the keyword already matches the existing Type.* last argument option passing.

    Additionally, the with keyword can be naturally supported in type expressions.

    Integration with Type Expressions

    // JSDoc wouldn't be able to participate in type expressions
    // of the given form, but where `with` naturally integrates.
    const { Vector } = Type.Script(`
      type Clamp<T> = { 
        [K in keyof T]: T[K] with { 
          minimum: 0, 
          maximum: 1 
        } 
      }
      type Vector = Clamp<{ 
        x: number, 
        y: number 
      }>
    `)
    // const Vector: Type.TObject<{
    //   x: Type.TWith<Type.TNumber, {
    //     minimum: 0;
    //     maximum: 1;
    //   }>;
    //   y: Type.TWith<Type.TNumber, {
    //     minimum: 0;
    //     maximum: 1;
    //   }>;
    // }>

    Future: Where Keyword

    I have also spent time reviewing dependent typing, and where potential exists to support an additional where syntax specifically for constraints,

    // today
    const  A = Type.Script(`number with { minimum: 10, maximum: 20 }`)
    
    // future
    const  B = Type.Script(`number where x => x >= 10 & x <= 20`)

    It's possible that TypeScript may introduce something similar to (B) in future, which would analog sub-type constraints seen in schema languages like CUE. At this stage, TypeBox is keeping an eye out for innovations happening elsewhere in the ecosystem, but the current with syntax should serve as a reasonable interim mechanism for open constraint assignment without forcing too much additional syntax.

    Dependent Typing (Refinements)

    Along with the with keyword, TB 1.2 also introduced additional syntax for type level refinements using if / then / else. This is experimental optional syntax for dynamic dependent typing in TypeBox, and to explore a plausible refinement syntax moving forward. Information on this feature is below.

    https://github.com/sinclairzx81/typebox/blob/main/changelog/1.2.0.md#dependent-types

    Dynamic Refinement Types

    import Type from 'typebox'
    
    const Response = Type.Script(`{
      status: 'success' | 'error'
    } & (
      if { status: 'success' } then {
        data: unknown
      } else {
        message: string
        code: number
      }
    )`)
    
    type Response = Type.Static<typeof Response>
    
    // type Response = {
    //   status: "success" | "error";
    // } & (({
    //   status: "success";
    // } & {
    //   data: unknown;
    // }) | {
    //   message: string;
    //   code: number;
    // })

    This syntax will be trialed over the course of 1.x, and has some relation to dependent value narrowing possible with the with keyword.


    Hopefully this all makes sense. There's been a lot of consideration given to JSDoc, but unfortunately it doesn't really sit well with TypeBox's longer term goals, and generally just sits orthogonal to everything.

    The new optional with, if and possible future where syntax would be a lot more inline with where I want to take TB eventually, but do acknowledge that having to extend TypeScript to support annotations isn't exactly ideal. The 1.2.x implementation is the best of a not very good situation (TypeBox is very much between a rock and a hard place), but I think the with keyword is the best TypeBox can do without TypeScript adding an inline annotation syntax (which ironically, would be JSDoc)

    The with syntax was partially considered with respect to the following projects.


    Will close out this issue for now as JSDoc will be deferred indefinitely, but happy to discuss the 1.2 design work if you have any follow up thoughts on this. I expect this feature to evolve over time, so feedback is most welcome.

    Cheers
    S

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions