Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow generic type parameters for custom extensions #3230

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Cipscis
Copy link

@Cipscis Cipscis commented Mar 15, 2024

Marked version: 11.1.0

Markdown flavor: n/a

Description

This PR adds optional generic type parameters to the types used for creating custom extensions. This allows marked to communicate more specific expectations to TypeScript for the token returned by a custom tokenizer function, and for the token passed to a custom renderer function.

Example usage:

const myCustomExtension: TokenizerAndRendererExtension<
  'myCustomExtension',
  { example: string }
> = {
  name: 'myCustomExtension',
  level: 'inline',
  start(src) {
    // ...
  },
  tokenizer(src, tokens) {
    // ...

    return {
      type: 'myCustomExtension',
      raw: src,
      example: 'must be a string',
      tokens: [],
    };
  },
  renderer(token) {
    token.type; // <- 'myCustomExtension'
    token.example; // <- string
    // ...
  },
};

Unfortunately, it's not quite so easy to set this to allow the generic type parameters to be inferred as I had hoped. I'd forgotten when writing the issue that TypeScript is only able to infer generic types in function parameters, so that can't be done with type updates alone.

Enabling this sort of type inference would require a noop function wrapper to allow TypeScript to do inference, and I'm not sure if that would be a useful addition to marked's API:

function createExtension<
  N extends string = string,
  T extends Record<string, unknown> = Record<string, any>
>(extension: TokenizerAndRendererExtension<N, T>): TokenizerAndRendererExtension<N, T> {
  return extension;
}

const myCustomExtension = createExtension({
  // extension code as in previous example
});
// ^ TokenizerAndRendererExtension<"myCustomExtension", {
//   type: "myCustomExtension";
//   raw: string;
//   example: string;
//   tokens: never[];
// }>

I'm also less certain about how valuable it is to include the name in the generic parameter. Particularly as it's necessary to declare as const if the token is not immediately returned, e.g.

tokenizer(src, tokens) {
  // ...

  const token = {
    type: 'myCustomExtension' as const,
    raw,
    example: 'must be a string',
    tokens: [],
  };
  return token;
},

Because this PR only contains type changes, no actual functionality should have changed. And because the only changes have been adding generic type arguments with defaults that replicate the existing behaviour, I expect there wouldn't be any breaking changes with existing TypeScript code.

I'm not sure where best to document this feature's PR, so currently have not included any new documentation.

Contributor

  • Test(s) exist to ensure functionality and minimize regression (if no tests added, list tests covering this PR); or,
  • no tests required for this PR.
  • If submitting new feature, it has been documented in the appropriate places.

Committer

In most cases, this should be a different person than the contributor.

Copy link

vercel bot commented Mar 15, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
marked-website ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 15, 2024 8:24am

@UziTech
Copy link
Member

UziTech commented Mar 16, 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Improved TypeScript types for TokenizerAndRendererExtension
2 participants