-
-
Notifications
You must be signed in to change notification settings - Fork 136
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
Export CustomConverter Interface #1077
Comments
Hey @bitPogo Asciidoctor.js is not a standard JavaScript library. In fact, the code is transpiled from Ruby (which has a different class hierarchy model). As a result, your code won't work (or at least will be incorrect): class MyCustomConverter extends Asciidoctor.Converter {
// ...
} If you want to create a custom converter, please take a look at: https://blog.yuzutech.fr/blog/custom-converter/index.html or https://asciidoctor-docs.netlify.app/asciidoctor.js/extend/converter/custom-converter/ |
Hey @Mogztter thx for the reply. import AsciiDoc, { Asciidoctor } from 'asciidoctor'
class MyAwesomeConverter implements Asciidoctor.AbstractConverter {
...
} But the hook is just a workaround never the less. So I am also happy with your approval to provide a PR. And by the way - thx for this lib! It makes my life actually much easier! Attachment: // This is a workaround for https://github.com/asciidoctor/asciidoctor.js/issues/1077
const fs = require('fs');
const ASCIIDOCTOR_DIR = `${__dirname}/../node_modules/asciidoctor/types`;
const file = fs.readFileSync(`${ASCIIDOCTOR_DIR}/index.d.ts`, 'utf8');
/* eslint-disable max-len */
const replacement = `interface AbstractConverter {
/**
* Converts an {AbstractNode} using the given transform.
* This method must be implemented by a concrete converter class.
*
* @param node - The concrete instance of AbstractNode to convert.
* @param [transform] - An optional String transform that hints at which transformation should be applied to this node.
* If a transform is not given, the transform is often derived from the value of the {AbstractNode#getNodeName} property. (optional, default: undefined)
* @param [opts]- An optional JSON of options hints about how to convert the node. (optional, default: undefined)
*
* @returns the {String} result.
*/
convert(node: AbstractNode, transform?: string, opts?: any): string;
}
class Converter implements AbstractConverter {`;
/* eslint-enable max-len */
const replacement2 = 'register(converter: AbstractConverter, backends?: string[]): void;';
const fixed = file
.replace('class Converter {', replacement)
.replace('register(converter: any, backends?: string[]): void;', replacement2);
fs.writeFileSync(`${ASCIIDOCTOR_DIR}/index.d.ts`, fixed); |
Oh I see, so the idea is to get coding assistance from TypeScript when writing a customer converter, right? I'm a bit torn because this interface does not really exist in the code but if we declare It's worth noting that the underlying Anyway, thanks for the detailed explanation, I now have a better understanding of the issue. I need to think it through to find a good balance between the flexibility of the API and the ease of development with TypeScript. One good thing about TypeScript is that it guides you when discovering a new API, so I can definitely see the value of the
Thanks! I'm glad to hear it 😳 |
Do you mean a factory or something like that? This could be also defined in way like: type AbstractConverterFactory = () => AbstractConverter;
interface AbstractConverterConstructor {
new(): AbstractConverter;
}
type RegisterPayload = AbstractConverterFactory | AbstractConverterConstructor | AbstractConverter;
class ConverterFactory {
...
register(converter: RegisterPayload, backends?: string[]): void;
...
}
Yes, thats correct. I think devs even should be force to do that, since that is the only public that counts for the actual converter and the program will die without it anyways. Since that will not interfere with pure JS in any case, I see no major tradeoff here, beside the correct definition what |
A small amendment: // node_modules/asciidoctor/types/index.d.ts
interface AbstractConstructor<T extends AbstractConverter> {
new( ...params: never[] ): T;
getInstance(): T;
}
// consumer of the lib
interface MyAwesomeConstructor<T extends AbstractConverter> extends AbstractConstructor<T> {
aAddMethod( param: 'hello' ): void;
}
class TestConverter implements AbstractConverter {
private static staticHelloTypescriptFlag = '';
public static getInstance(): AbstractConverter {
return new TestConverter( TestConverter.staticHelloTypescriptFlag )
}
public static aAddMethod( param: 'hello' ): void {
TestConverter.staticHelloTypescriptFlag = param
// do something more
...
}
constructor( a: string, ..._:any[] ) {
// do something with a
...
}
public convert( node: AbstractNode, transform?: string, opts?: any): string {
// interpret the actual AST
...
}
}
function myBuilder(): AbstractConstructor<AbstractConverter> {
const MyConverter: MyAwesomeConstructor<TestConverter> = TestConverter;
MyConverter.aAddMethod('hello')
...
return MyConverter;
} I guess that should give all the flexibility you want, right? |
You can see two usages of the Another usage would be to pass a "Ruby" class or instance transpiled by Opal. If you can come up with a type definition that accepts both an ES6 class or instance please feel free to open a pull request. asciidoctor.js/packages/core/types/tests.ts Line 557 in 9e7e378
asciidoctor.js/packages/core/types/tests.ts Line 1085 in 9e7e378
And you should add a new test to check that the |
Thx for the reply!
...
const functionalConverter = ( converterFactory, backend, opts ) => {
let converter;
try {
converter = new converterFactory( backend, opts );
} catch (err) {
converter = converterFactory( backend, opts );
}
return converter;
}
...
const result = functionalConverter( converter, backend, opts) //asciidoctor-extensions-api.js#L1333 And gets typed liked the constructor. This could be done in a separate commit/PR Side note: |
👍
Feel free to ask 😉
Yes and they should be optional.
Not sure about that, I think the two options are sufficient for now.
Indeed, we can still re-evaluate once the issue is fixed.
Thanks for noticing! |
Background
I have to use for a typescript project the CustomConverter option for asciidoctor.
Problem
I realised that the current types do not include a CustomConverter Interface. Interestedly extending form the
Converter
class break (at least for me) with:Solution
This could be easily solved by adding the following to the type definitions:
This could be implemented by the
Converter
class and a notable side effect theConverterFactory
could get rid of the any.The text was updated successfully, but these errors were encountered: