-
-
Notifications
You must be signed in to change notification settings - Fork 353
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
Some api feedbacks while defining typescript types for remark and unified modules #387
Comments
I'll take a look at those Issues/PRs tonight. I'd like to see if I can give a hand! |
I see that all the overriding is ill suited indeed and how we can easily end up in a lot of unnecessary boilerplate. That been said, I don’t know how a class would help, since it will most likely mean duplication of code, of some sorts. I wonder if an adaptor function that transforms any |
Could you tell me which class wouldn't be helpful?
It also looks good to me. :) |
Hi again, (there's been some family issues that kept me from online presence 😅) So, the way I see it is that there is little we can do to avoid overloading, unless we split implementation of tokenize and tokenizeSilently, like you suggested. It's only a feeling, but I guess that may be more cumbersome to do. As to the adaptor/coerce trick, it is based on the following: 1/ having a strict type definition of tokenize (and other method with the use of overloading) and then 2/ implement the tokenizers in a more "forgiving" (as in "not so strict") fashion like so: // import Node type
declare namespace Unist {
class Node {
type = "node";
}
}
// import remark-parse
declare module remarkParse {
type Eat = (value: string) => {};
interface TokenizerCall {
(e: Eat, value: string, silent: true): boolean;
(e: Eat, value: string, silent?: false): Unist.Node;
(e: Eat, value: string, silent?: boolean): Unist.Node | boolean;
}
// Totally equivalent to TokenizerCall
// type TokenizerCallAlt = ((e: Eat, value: string, silent: true) => boolean) &
// ((e: Eat, value: string, silent?: false) => Unist.Node) &
// ((e: Eat, value: string, silent?: boolean) => Unist.Node | boolean);
}
// define a generic magic coerce function that will transform any collection of function overloads into a simple (and forgiving) function definition
function unistCoerce<TFunc extends (...args: any[]) => any>(
func: (...args: Parameters<TFunc>) => ReturnType<TFunc>
): TFunc {
return func as any;
}
// use it to define your custom tokenizers
const tokenizeMention = unistCoerce<remarkParse.TokenizerCall>(
(
eat: remarkParse.Eat,
value: string,
silent?: boolean
): Unist.Node | boolean => {
return new Unist.Node();
}
); I'd be very interested in seeing how this may help your codebase in the real world 😊 |
Sorry for letting this sit.
Yes, that’s confusing. But I don’t know of a different way to do it. How could a plugin configure a constructor that is not yet instantiated?
That does make it a bit harder to understand. But also much more performant. Same reason locators exist. If this can be fixed, should be done in micromark as well.
This seems to be solved in micromark: https://github.com/micromark/micromark/blob/master/lib/atx-heading.ts ✨
This is an issue with unified. And has to do with how other related middleware-accepting projects work, and how unified is used from the CLI. Additionally, as you mentioned, global settings add complexity. And presets. I don’t think this can be made easier. |
I think we can ignore Other two answers look fine to me. I'll keep watching changes in micromark. 👍 So I think this issue can be closed now. Thanks for the answers! |
hah! thanks for posting the questions, it’s good that the API is reviewed and for it to be easy and a to have a relatively small surface area. How the API of micromark looks, is still up for debate, so if you have thoughts on that, I’d appreciate them there: micromark/micromark#5 |
As I mentioned in #383 (comment), I found defining typescript types for remark is quite confusing. In my experience, it is some kind of sign that we can improve our api more straight forward. So I want to share some of my idea. But I don't mind if you don't accept it because most of them should cause huge breaking changes.
Manipulating constructor form outside
To be honest, I'm not a fan of prototype based programming. Although it makes our expressions fluent, it usually makes them chaotic. One example is copying a constructor function via https://github.com/wooorm/unherit. In our code, we are using it to prepare Parser and Compiler constructors. And to edit it, we have to access prototype. It just makes the problem worse.
Suggestion
To deal with this problem, I think we should split the roles with classes.
class Parser
keeps configuration forparse
method and is instantiated when the processor become frozen.class ParseContext
keeps current state while parsing and is instantiated when start parsing.Tokenizer
Overloading
Overloading is sometimes useful. But it definitely makes code hard to understand. For an example, to make custom tokenizer in typescript, we have to do like the below.
Functions with props
Tokenizer is a function with some props. This makes code looks monkey patched.
Suggestion
We can make Tokenizer as class and separate tokenize method into two.
Too many options to configure remark
Same to tokenizer,
Processor#use
method also uses overloading a lot. So the typing was quite difficult and made our code meaninglessly bloated.Suggestion
I think we should provide one of the ways below without global configuration.
The text was updated successfully, but these errors were encountered: