Type errors declaring a new language definition in TypeScript
See original GitHub issueDescribe the issue/behavior that seems buggy
I am attempting to declare and register a new language definition for an internal pseudolanguage (aimed at students, and very simplistic), but doing so in TypeScript and not plain JS. I am using 11.1.0
.
I declare the new rules in a function I want to pass into hljs.registerLanguage(...)
using the LanguageFn
type you export from index.d.ts
. However LanguageFn
annotates the hljs
argument as optional, which means that I cannot use something like hljs.QUOTE_STRING_MODE
in my function without a guard against hljs
being undefined as the type annotation says it might be.
Sample Code or Instructions to Reproduce
A minimal TypeScript example would be:
import {LanguageFn} from "highlight.js";
const myLanguageRules: LanguageFn = function(hljs) {
return {
name: 'myLanguage',
contains: [
hljs.QUOTE_STRING_MODE,
hljs.NUMBER_MODE,
]
};
}
where the references to hljs.QUOTE_STRING_MODE
and number mode will cause type errors since hljs
has the type HLJSApi | undefined
(the error on trying to use hljs
is TS2532: Object is possibly 'undefined'.
). This is such a simple case that you can see it will cause a problem without even needing to use the compiler.
The only way to do this guard in a way that allows me to use hljs
in many places in my definition seems to be the incredibly hacky if (!hljs) return {} as Language;
at the start, otherwise the type system complains about the return type of the function being incorrect. However I don’t understand why hljs
is marked as optional in the definition export type LanguageFn = (hljs?: HLJSApi) => Language
in the first place? Surely it is always passed in by the library?
Expected behavior
I would expect the LanguageFn
type to actually be export type LanguageFn = (hljs: HLJSApi) => Language
without the argument being optional, and then if I choose not to use the hljs
argument I can just name it _hljs
in my own function and TypeScript knows this means I do not intend to use the argument.
I could foresee changing this signature causing issues for anyone using TypeScript to define languages of their own if (and probably only if) they do not have a hljs
argument at all in their function. Otherwise it seems safe to change. Was there a reason it is declared the way it is? Is it autogenerated, or written by hand?
Issue Analytics
- State:
- Created 2 years ago
- Comments:7 (4 by maintainers)
@SleeplessByte - ah, thank you! (And neat example cases too!) I’d not put much thought into it, but yes, throwing an error is clearly the right thing to do. I’m not sure what highlight.js would do if the registration of the language encountered an error, but I guess my own code could just wrap the registration in a try-catch if I was actually worried about it propagating.
I agree the PR will fix it.
test3
above even suggests that the change will not break the case where someone declared their own function without thehljs
argument which is the only case I’d worried about.That’s actually interestingly not the case in TypeScript!
Consider these three function types/signatures.
See this playground
So when you write:
It means:
LanguageFn
is a function that returnsLanguage
(or throws)LanguageFn
may be called with an argument. It may beundefined
and it may be of shapeHLJSApi
. It’s not required to consume it. Calling it with an argument is optional.In the same way when you write:
It means:
LanguageFn2
is a function that returnsLanguage
(or throws)LanguageFn2
is always called with one argument. That argument is alwaysHLJSApi
in shape. It’s not required to consume it.@jsharkey13 was right that because it’s always called with the argument, the argument shouldn’t be optional. However, James, for future reference, if you require the argument to produce a sane response, the right way to handle that is:
It’s not hacky to require to narrow the type if the argument is truly optional.