Skip to content

Commit

Permalink
Improve typing of defineToken, throw error for non-token return in co…
Browse files Browse the repository at this point in the history
…nstant tokens
  • Loading branch information
hlysine committed Aug 19, 2023
1 parent 73c7a3c commit 2585092
Showing 1 changed file with 28 additions and 12 deletions.
40 changes: 28 additions & 12 deletions src/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1897,13 +1897,13 @@ const funcTokens = [
];

/**
* Ensures that incomplete tokens are marked with the {@link IncompleteToken} interface.
* Checks if a token intersects either the {@link RegExpToken} or the {@link IncompleteToken} interface.
*/
type IncompleteTokenCheck<TokenType, ResultType> = TokenType extends RegExpToken
? ResultType
type IncompleteTokenCheck<TokenType> = TokenType extends RegExpToken
? true
: TokenType extends IncompleteToken
? ResultType
: { error: 'Invalid token type: tokens with required parameters should intersect the IncompleteToken type.' };
? true
: false;

/**
* Transforms template string arguments to string literals while leaving other arguments unchanged.
Expand All @@ -1927,6 +1927,18 @@ type CustomTokenConfig<TokenType> = (TokenType extends RegExpToken
? { dynamic: (this: RegExpToken, ...args: TransformStringLiteralArgs<Args>) => ReturnType }
: {});

const invalidReturnMessage = (val: unknown) =>
`Invalid return value from a constant token: ${val}.\n` +
'If you want to return any other values (which are non-chainable), ' +
'you should implement a dynamic token without parameters to make the chain termination explicit.';

function ensureTokenReturned<T extends object>(value: T): T {
if ((typeof value !== 'object' && typeof value !== 'function') || value === null)
throw new Error(invalidReturnMessage(value));
if ('toRegExp' in value && 'toString' in value) return value;
throw new Error(invalidReturnMessage(value));
}

/**
* Define a custom token that can be used in conjunction with other tokens.
* For a detailed guide on custom tokens, please read https://github.com/hlysine/readable-regexp#custom-tokens
Expand Down Expand Up @@ -1977,10 +1989,14 @@ type CustomTokenConfig<TokenType> = (TokenType extends RegExpToken
* console.log(lineStart.severity.lineEnd.toString()); // ^(?:error|warning|info|debug)$
* ```
*/
export function defineToken<Name extends keyof RegExpToken>(
export function defineToken<Name extends keyof RegExpToken, Check = IncompleteTokenCheck<RegExpToken[Name]>>(
tokenName: Name,
config: IncompleteTokenCheck<RegExpToken[Name], CustomTokenConfig<RegExpToken[Name]>>
): RegExpToken[Name] {
config: Check extends true
? CustomTokenConfig<RegExpToken[Name]>
: {
error: 'Invalid token type: tokens should intersect the RegExpToken type if they are constant, or the IncompleteToken type if they are dynamic.';
}
): Check extends true ? RegExpToken[Name] : never {
if (tokenName in RegExpBuilder.prototype) throw new Error(`Token ${tokenName} already exists`);
Object.defineProperty(RegExpBuilder.prototype, tokenName, {
get() {
Expand All @@ -1995,15 +2011,15 @@ export function defineToken<Name extends keyof RegExpToken>(
value
);
} else {
throw new Error('Invalid arguments for ' + tokenName);
throw new Error('Invalid arguments for ' + tokenName + '. This is probably a bug.');
}
}
if (`constant` in config && !('dynamic' in config)) {
return config.constant.apply(this);
return ensureTokenReturned(config.constant.apply(this));
} else if (!(`constant` in config) && 'dynamic' in config) {
return bindAsIncomplete(configure, this, tokenName);
} else if (`constant` in config && 'dynamic' in config) {
return assign(configure.bind(this), config.constant.apply(this), false);
return assign(configure.bind(this), ensureTokenReturned(config.constant.apply(this)), false);
} else {
throw new Error(`The custom token ${tokenName} does not have any valid configurations.`);
}
Expand All @@ -2015,5 +2031,5 @@ export function defineToken<Name extends keyof RegExpToken>(
if (!('toRegExp' in token)) return;
Object.defineProperty(token, tokenName, Object.getOwnPropertyDescriptor(RegExpBuilder.prototype, tokenName)!);
});
return r[tokenName];
return r[tokenName] as ReturnType<typeof defineToken>;
}

0 comments on commit 2585092

Please sign in to comment.