New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

typroof

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

typroof - npm Package Compare versions

Comparing version 0.1.2 to 0.2.0

assertions/impl/beAny.d.ts

465

assertions/assert.d.ts

@@ -1,395 +0,69 @@

import type { NotToBeAny, ToBeAny } from './impl/toBeAny.js';
import type { NotToBeFalse, ToBeFalse } from './impl/toBeFalse.js';
import type { NotToBeNever, ToBeNever } from './impl/toBeNever.js';
import type { NotToBeNull, ToBeNull } from './impl/toBeNull.js';
import type { NotToBeNullish, ToBeNullish } from './impl/toBeNullish.js';
import type { NotToBeTrue, ToBeTrue } from './impl/toBeTrue.js';
import type { NotToBeUndefined, ToBeUndefined } from './impl/toBeUndefined.js';
import type { ToCover } from './impl/toCover.js';
import type { NotToEqual, ToEqual } from './impl/toEqual.js';
import type { ToExtend } from './impl/toExtend.js';
import type { Match, ToAnalyze } from './matcher.js';
import type {
NotToMatchBoolean,
ToMatchBoolean,
} from './impl/toMatchBoolean.js';
import type { ToStrictCover } from './impl/toStrictCover.js';
import type { ToStrictExtend } from './impl/toStrictExtend.js';
import type { NotToThrow, ToThrow } from './impl/toThrow.js';
Covers,
Equals,
Extends,
IsAny,
IsFalse,
IsNever,
IsNull,
IsNullish,
IsTrue,
IsUndefined,
MatchesBoolean,
StrictCovers,
StrictExtends,
} from '../tools/index.js';
import type { Project } from 'ts-morph';
/**
* Validators for matchers.
*/
export interface Validator<T = unknown, U = unknown> {
error: ToAnalyze;
equal: Equals<T, U>;
beAny: IsAny<T>;
beNever: IsNever<T>;
beNull: IsNull<T>;
beUndefined: IsUndefined<T>;
beNullish: IsNullish<T>;
matchBoolean: MatchesBoolean<T>;
beTrue: IsTrue<T>;
beFalse: IsFalse<T>;
extend: Extends<T, U>;
strictExtend: StrictExtends<T, U>;
cover: Covers<T, U>;
strictCover: StrictCovers<T, U>;
}
export interface Expect<T> {
/**
* Expect a pre emitted diagnostic between the start and end of the given type.
*
* @example
* ```typescript
* type IdNumber<N extends number> = N;
* expect<IdNumber<'foo'>>().toThrow(); // pass
* // ~~~~~
* // Type '"foo"' is not assignable to type 'number'.
* expect<IsNumber<42>>().toThrow(); // fail
* ```
*/
toThrow: ToThrow;
/**
* Expect a type to be equal to the given type.
*
* @example
* ```typescript
* expect<'foo'>().toEqual<'foo'>(); // pass
* expect<'foo'>().toEqual<'bar'>(); // fail
* expect<'foo'>().toEqual<'foo' | 'bar'>(); // fail
* ```
*/
toEqual: ToEqual<T>;
/**
* Expect a type to be `any`.
*
* @example
* ```typescript
* expect<'foo'>().toBeAny(); // fail
* expect<any>().toBeAny(); // pass
* ```
*/
toBeAny: ToBeAny<T>;
/**
* Expect a type to be `never`.
*
* @example
* ```typescript
* expect<'foo'>().toBeNever(); // fail
* expect<never>().toBeNever(); // pass
* ```
*/
toBeNever: ToBeNever<T>;
/**
* Expect a type to be `null`.
*
* @example
* ```typescript
* expect<'foo'>().toBeNull(); // fail
* expect<null>().toBeNull(); // pass
* ```
*/
toBeNull: ToBeNull<T>;
/**
* Expect a type to be `undefined`.
*
* @example
* ```typescript
* expect<'foo'>().toBeUndefined(); // fail
* expect<undefined>().toBeUndefined(); // pass
* ```
*/
toBeUndefined: ToBeUndefined<T>;
/**
* Expect a type to be `null` or `undefined`.
*
* @example
* ```typescript
* expect<'foo'>().toBeNullish(); // fail
* expect<null>().toBeNullish(); // pass
* expect<undefined>().toBeNullish(); // pass
* ```
*/
toBeNullish: ToBeNullish<T>;
/**
* @deprecated Use {@link toMatchBoolean} instead.
*
* @since 0.1.1
*/
toBeBoolean: ToMatchBoolean<T>;
/**
* Expect a type to be `true`, `false`, or `boolean`.
*
* @example
* ```typescript
* expect<'foo'>().toMatchBoolean(); // fail
* expect<true>().toMatchBoolean(); // pass
* expect<false>().toMatchBoolean(); // pass
* expect<boolean>().toMatchBoolean(); // pass
* ```
*
* @since 0.1.2
*/
toMatchBoolean: ToMatchBoolean<T>;
/**
* Expect a type to be `true`.
*
* @example
* ```typescript
* expect<'foo'>().toBeTrue(); // fail
* expect<true>().toBeTrue(); // pass
* expect<false>().toBeTrue(); // fail
* expect<boolean>().toBeTrue(); // fail
* ```
*
* @since 0.1.1
*/
toBeTrue: ToBeTrue<T>;
/**
* Expect a type to be `false`.
*
* @example
* ```typescript
* expect<'foo'>().toBeFalse(); // fail
* expect<true>().toBeFalse(); // fail
* expect<false>().toBeFalse(); // pass
* expect<boolean>().toBeFalse(); // fail
* ```
*
* @since 0.1.1
*/
toBeFalse: ToBeFalse<T>;
/**
* Expect a type to be assignable to the given type (i.e. the given type should be a supertype of
* the type).
*
* **Warning:** `any` is considered both subtype and supertype of all types in TypeScript, so
* both `expect<string>().toExtend<any>();` and `expect<any>().toExtend<string>();` will pass
* (`string` can be replaced with any other type, including `any`), so keep that in mind when
* using this. If you want to check if the type is assignable to the given type but not `any`,
* use {@link toStrictExtend} instead.
*
* @example
* ```typescript
* expect<'foo'>().toExtend<'foo'>(); // pass
* expect<'foo'>().toExtend<string>(); // pass
* expect<'foo'>().toExtend<'bar'>(); // fail
* expect<'foo'>().toExtend<'foo' | 'bar'>(); // pass
* expect<never>().toExtend<'foo'>(); // pass
* expect<'foo'>().toExtend<any>(); // pass
* ```
*/
toExtend: ToExtend<T>;
/**
* Like {@link toExtend}, but fails if either type is `never` or `any`.
*
* @example
* ```typescript
* expect<'foo'>().toStrictExtend<'foo'>(); // pass
* expect<'foo'>().toStrictExtend<string>(); // pass
* expect<'foo'>().toStrictExtend<'bar'>(); // fail
* expect<'foo'>().toStrictExtend<'foo' | 'bar'>(); // pass
* expect<never>().toStrictExtend<'foo'>(); // fail
* expect<'foo'>().toStrictExtend<any>(); // fail
* ```
*/
toStrictExtend: ToStrictExtend<T>;
/**
* Expect the given type to be assignable to the type (i.e. the given type should be a subtype of
* the type).
*
* **Warning:** `any` is considered both subtype and supertype of all types in TypeScript, so
* both `expect<string>().toCover<any>();` and `expect<any>().toCover<string>();` will pass
* (`string` can be replaced with any other type, including `any`), so keep that in mind when
* using this. If you want to check if the given type is assignable to the type but not `any`,
* use {@link toStrictCover} instead.
*
* @example
* ```typescript
* expect<'foo'>().toCover<'foo'>(); // pass
* expect<string>().toCover<'foo'>(); // pass
* expect<'foo' | 'bar'>().toCover<'foo'>(); // pass
* expect<'foo'>().toCover<'bar'>(); // fail
* expect<'foo'>().toCover<never>(); // pass
* expect<any>().toCover<'foo'>(); // pass
* ```
*/
toCover: ToCover<T>;
/**
* Like {@link toCover}, but fails if either type is `never` or `any`.
*
* @example
* ```typescript
* expect<'foo'>().toStrictCover<'foo'>(); // pass
* expect<string>().toStrictCover<'foo'>(); // pass
* expect<'foo' | 'bar'>().toStrictCover<'foo'>(); // pass
* expect<'foo'>().toStrictCover<'bar'>(); // fail
* expect<'foo'>().toStrictCover<never>(); // fail
* expect<any>().toStrictCover<'foo'>(); // fail
* ```
*/
toStrictCover: ToStrictCover<T>;
to: {
(match: never): 'fail';
<Tag extends keyof Validator<unknown, unknown>, U>(
match: Validator<T, U>[Tag] extends true ? () => Match<Tag, U> : never,
): 'pass';
<Tag extends keyof Validator<unknown, unknown>, U>(
match: Validator<T, U>[Tag] extends true ? Match<Tag, U> : never,
): 'pass';
<Tag extends keyof Validator<unknown, unknown>, U>(
match: Validator<T, U>[Tag] extends ToAnalyze<unknown>
? Match<Tag, U>
: never,
): Validator<T, U>[Tag];
};
not: {
to: {
(match: never): 'fail';
<Tag extends keyof Validator<unknown, unknown>, U>(
match: Validator<T, U>[Tag] extends false ? () => Match<Tag, U> : never,
): 'pass';
<Tag extends keyof Validator<unknown, unknown>, U>(
match: Validator<T, U>[Tag] extends false ? Match<Tag, U> : never,
): 'pass';
<Tag extends keyof Validator<unknown, unknown>, U>(
match: Validator<T, U>[Tag] extends ToAnalyze<unknown>
? Match<Tag, U>
: never,
): Validator<T, U>[Tag];
};
};
}
export interface ExpectNot<T> {
/**
* Expect no pre emitted diagnostic between the start and end of the given type.
*
* @example
* ```typescript
* type IdNumber<N extends number> = N;
* expect<IdNumber<'foo'>>().not.toThrow(); // fail
* // ~~~~~
* // Type '"foo"' is not assignable to type 'number'.
* expect<IsNumber<42>>().not.toThrow(); // pass
* ```
*/
toThrow: NotToThrow;
/**
* Expect a type not to be the same as the given type.
*
* @example
* ```typescript
* expect<'foo'>().not.toEqual<'foo'>(); // fail
* expect<'foo'>().not.toEqual<'bar'>(); // pass
* expect<'foo'>().not.toEqual<'foo' | 'bar'>(); // pass
* ```
*/
toEqual: NotToEqual<T>;
/**
* Expect a type not to be `any`.
*
* @example
* ```typescript
* expect<'foo'>().not.toBeAny(); // pass
* expect<any>().not.toBeAny(); // fail
* ```
*/
toBeAny: NotToBeAny<T>;
/**
* Expect a type not to be `never`.
*
* @example
* ```typescript
* expect<'foo'>().not.toBeNever(); // pass
* expect<never>().not.toBeNever(); // fail
* ```
*/
toBeNever: NotToBeNever<T>;
/**
* Expect a type not to be `null`.
*
* @example
* ```typescript
* expect<'foo'>().not.toBeNull(); // pass
* expect<null>().not.toBeNull(); // fail
* ```
*/
toBeNull: NotToBeNull<T>;
/**
* Expect a type not to be `undefined`.
*
* @example
* ```typescript
* expect<'foo'>().not.toBeUndefined(); // pass
* expect<undefined>().not.toBeUndefined(); // fail
* ```
*/
toBeUndefined: NotToBeUndefined<T>;
/**
* Expect a type not to be `null` or `undefined`.
*
* @example
* ```typescript
* expect<'foo'>().not.toBeNullish(); // pass
* expect<null>().not.toBeNullish(); // fail
* expect<undefined>().not.toBeNullish(); // fail
* ```
*/
toBeNullish: NotToBeNullish<T>;
/**
* @deprecated Use {@link toMatchBoolean} instead.
*
* @since 0.1.1
*/
toBeBoolean: NotToMatchBoolean<T>;
/**
* Expect a type not to be `true`, `false`, or `boolean`.
*
* @example
* ```typescript
* expect<'foo'>().not.toMatchBoolean(); // pass
* expect<true>().not.toMatchBoolean(); // fail
* expect<false>().not.toMatchBoolean(); // fail
* expect<boolean>().not.toMatchBoolean(); // fail
* ```
*
* @since 0.1.2
*/
toMatchBoolean: NotToMatchBoolean<T>;
/**
* Expect a type not to be `true`.
*
* @example
* ```typescript
* expect<'foo'>().not.toBeTrue(); // pass
* expect<true>().not.toBeTrue(); // fail
* expect<false>().not.toBeTrue(); // pass
* expect<boolean>().not.toBeTrue(); // pass
* ```
*
* @since 0.1.1
*/
toBeTrue: NotToBeTrue<T>;
/**
* Expect a type not to be `false`.
*
* @example
* ```typescript
* expect<'foo'>().not.toBeFalse(); // pass
* expect<true>().not.toBeFalse(); // pass
* expect<false>().not.toBeFalse(); // fail
* expect<boolean>().not.toBeFalse(); // pass
* ```
*
* @since 0.1.1
*/
toBeFalse: NotToBeFalse<T>;
/**
* Expect a type not to extend the given type.
*
* @example
* ```typescript
* expect<'foo'>().not.toExtend<'foo'>(); // fail
* expect<'foo'>().not.toExtend<string>(); // fail
* expect<'foo'>().not.toExtend<'bar'>(); // pass
* expect<'foo'>().not.toExtend<'foo' | 'bar'>(); // fail
* expect<never>().not.toExtend<'foo'>(); // fail
* expect<'foo'>().not.toExtend<any>(); // fail
* ```
*/
toExtend: ToExtend<T>;
/**
* Expect a type not to strictly extend the given type (i.e. both types should not be `never` or `any`).
*
* @example
* ```typescript
* expect<'foo'>().not.toStrictExtend<'foo'>(); // fail
* expect<'foo'>().not.toStrictExtend<string>(); // fail
* expect<'foo'>().not.toStrictExtend<'bar'>(); // pass
* expect<'foo'>().not.toStrictExtend<'foo' | 'bar'>(); // fail
* expect<never>().not.toStrictExtend<'foo'>(); // pass
* expect<'foo'>().not.toStrictExtend<any>(); // pass
* ```
*/
toStrictExtend: ToStrictExtend<T>;
/**
* Expect a type not to cover the given type (i.e. the given type should not extend the type).
*
* @example
* ```typescript
* expect<'foo'>().not.toCover<'foo'>(); // fail
* expect<string>().not.toCover<'foo'>(); // fail
* expect<'foo' | 'bar'>().not.toCover<'foo'>(); // fail
* expect<'foo'>().not.toCover<'bar'>(); // pass
* expect<'foo'>().not.toCover<never>(); // fail
* expect<any>().not.toCover<'foo'>(); // fail
* ```
*/
toCover: ToCover<T>;
/**
* Expect a type not to strictly cover the given type (i.e. both types should not be `never` or `any`).
*
* @example
* ```typescript
* expect<'foo'>().not.toStrictCover<'foo'>(); // fail
* expect<string>().not.toStrictCover<'foo'>(); // fail
* expect<'foo' | 'bar'>().not.toStrictCover<'foo'>(); // fail
* expect<'foo'>().not.toStrictCover<'bar'>(); // pass
* expect<'foo'>().not.toStrictCover<never>(); // pass
* expect<any>().not.toStrictCover<'foo'>(); // pass
* ```
*/
toStrictCover: ToStrictCover<T>;
}
/**

@@ -402,10 +76,9 @@ * Expect a type to satisfy a set of assertions.

*
* expect<'foo'>().toEqual<'foo'>(); // pass
* expect<'foo'>().toExtend<number>(); // fail
* expect<'foo'>().not.toExtend<number>(); // pass
* expect<'foo'>().to(equal<'foo'>); // pass
* expect<'foo'>().to(equal('foo')); // pass
* expect<'foo'>().to(extend<number>); // fail
* expect<'foo'>().not.to(extend<number>); // pass
* ```
*/
export declare const expect: <T>(t?: T) => Expect<T> & {
not: ExpectNot<T>;
};
export declare const expect: <T>(t?: T) => Expect<T>;
export declare const getExpectSymbol: (

@@ -412,0 +85,0 @@ project: Project,

import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { registerToBeFalse } from './impl/toBeFalse.js';
import { registerToBeNever } from './impl/toBeNever.js';
import { registerToBeNull } from './impl/toBeNull.js';
import { registerToBeNullish } from './impl/toBeNullish.js';
import { registerToBeTrue } from './impl/toBeTrue.js';
import { registerToBeUndefined } from './impl/toBeUndefined.js';
import { registerToCover } from './impl/toCover.js';
import { registerToEqual } from './impl/toEqual.js';
import { registerToExtend } from './impl/toExtend.js';
import { registerToMatchBoolean } from './impl/toMatchBoolean.js';
import { registerToStrictCover } from './impl/toStrictCover.js';
import { registerToStrictExtend } from './impl/toStrictExtend.js';
import { registerToThrow } from './impl/toThrow.js';
import { registerToBeFalse } from './impl/beFalse.js';
import { registerToBeNever } from './impl/beNever.js';
import { registerToBeNull } from './impl/beNull.js';
import { registerToBeNullish } from './impl/beNullish.js';
import { registerToBeTrue } from './impl/beTrue.js';
import { registerToBeUndefined } from './impl/beUndefined.js';
import { registerToCover } from './impl/cover.js';
import { registerToEqual } from './impl/equal.js';
import { registerToError } from './impl/error.js';
import { registerToExtend } from './impl/extend.js';
import { registerToMatchBoolean } from './impl/matchBoolean.js';
import { registerToStrictCover } from './impl/strictCover.js';
import { registerToStrictExtend } from './impl/strictExtend.js';
/* Register all assertions */

@@ -30,3 +30,3 @@ registerToEqual();

registerToStrictCover();
registerToThrow();
registerToError();
/**

@@ -39,15 +39,12 @@ * Expect a type to satisfy a set of assertions.

*
* expect<'foo'>().toEqual<'foo'>(); // pass
* expect<'foo'>().toExtend<number>(); // fail
* expect<'foo'>().not.toExtend<number>(); // pass
* expect<'foo'>().to(equal<'foo'>); // pass
* expect<'foo'>().to(equal('foo')); // pass
* expect<'foo'>().to(extend<number>); // fail
* expect<'foo'>().not.to(extend<number>); // pass
* ```
*/
export const expect = () =>
new Proxy(
{},
{
get: (_, key) =>
key === 'not' ? new Proxy({}, { get: () => {} }) : () => {},
},
);
export const expect = () => ({
to: () => {},
not: { to: () => {} },
});
const currentFilePathName = (() => {

@@ -54,0 +51,0 @@ let result = '';

export { expect } from './assert.js';
export { registerMatcher } from './matcher.js';
export type { Expect, ExpectNot } from './assert.js';
export type { Matcher, MatcherMeta } from './matcher.js';
export { registerAnalyzer } from './matcher.js';
export type { Validator } from './assert.js';
export type {
Analyzer,
AnalyzerMeta,
Match,
ToAnalyze,
match,
} from './matcher.js';
export { beAny } from './impl/beAny.js';
export { beFalse } from './impl/beFalse.js';
export { beNever } from './impl/beNever.js';
export { beNull } from './impl/beNull.js';
export { beNullish } from './impl/beNullish.js';
export { beTrue } from './impl/beTrue.js';
export { beUndefined } from './impl/beUndefined.js';
export { cover } from './impl/cover.js';
export { equal } from './impl/equal.js';
export { error } from './impl/error.js';
export { extend } from './impl/extend.js';
export { matchBoolean } from './impl/matchBoolean.js';
export { strictCover } from './impl/strictCover.js';
export { strictExtend } from './impl/strictExtend.js';
//# sourceMappingURL=index.d.ts.map
export { expect } from './assert.js';
export { registerMatcher } from './matcher.js';
export { registerAnalyzer } from './matcher.js';
export { beAny } from './impl/beAny.js';
export { beFalse } from './impl/beFalse.js';
export { beNever } from './impl/beNever.js';
export { beNull } from './impl/beNull.js';
export { beNullish } from './impl/beNullish.js';
export { beTrue } from './impl/beTrue.js';
export { beUndefined } from './impl/beUndefined.js';
export { cover } from './impl/cover.js';
export { equal } from './impl/equal.js';
export { error } from './impl/error.js';
export { extend } from './impl/extend.js';
export { matchBoolean } from './impl/matchBoolean.js';
export { strictCover } from './impl/strictCover.js';
export { strictExtend } from './impl/strictExtend.js';

@@ -0,4 +1,28 @@

import type { Validator } from './assert.js';
import type { TyproofProject } from '../test/index.js';
import type { Diagnostic, Node, SourceFile, Type, ts } from 'ts-morph';
export declare const matchers: Map<string, Matcher>;
import type {
CallExpression,
Diagnostic,
Node,
SourceFile,
Type,
ts,
} from 'ts-morph';
declare const analyze: unique symbol;
export interface ToAnalyze<T = never> {
[analyze]: T;
}
declare const matchTag: unique symbol;
export interface Match<
Tag extends keyof Validator<unknown, unknown>,
T = never,
> {
[matchTag]: Tag;
type: T;
}
export declare const match: <
Tag extends keyof Validator<unknown, unknown>,
T = never,
>() => Match<Tag, T>;
export declare const analyzers: Map<string, Analyzer<any>>;
interface Actual {

@@ -21,3 +45,3 @@ /**

}
export interface MatcherMeta {
export interface AnalyzerMeta {
/**

@@ -39,69 +63,112 @@ * The typroof project.

not: boolean;
/**
* The statement of the assertion.
*/
statement: CallExpression<ts.CallExpression>;
}
/**
* A matcher function.
* An analyzer function.
*/
export type Matcher = (
/**
* The type passed to `expect`. For example, if `expect<T>()` is called, then `actual` is `T`.
*/
actual: Actual,
/**
* The type arguments passed to the matcher function. For example, if the method is `expect<T>().toExtendOneOfTwo<U, V>()`,
* then `types` is `[U, V]`.
*/
types: Type<ts.Type>[],
/**
* The return type of the matcher function.
*/
returnType: Type<ts.Type>,
/**
* Meta data of the matcher function.
*/
meta: MatcherMeta,
export type Analyzer<Tag extends keyof Validator<unknown, unknown>> = (
...args: [
/**
* The type passed to `expect`. For example, if `expect<T>()` is called, then `actual` is `T`.
*/
actual: Actual,
/**
* The type argument passed to the matcher. For example, if the method is
* `expect<T>().to(equal<U>)`, then `type` is `U`.
*/
type: Type<ts.Type>,
...(Validator<unknown, unknown>[Tag] extends ToAnalyze<unknown>
? [
/**
* The return type of the validator.
*/
validationResult: Type<ts.Type>,
]
: [
/**
* Whether the validator passed.
*/
passed: boolean,
]),
/**
* Meta data of the analyzer function.
*/
meta: AnalyzerMeta,
]
) => void;
/**
* Register a matcher.
* @param name Method name to be mixed into `expect`.
* @param matcher The matcher function.
* Register an analyzer.
* @param tag The matcher tag.
* @param analyzer The analyzer function.
*
* @example
* ```typescript
* import { registerMatcher } from '../index.js';
* import { match, registerAnalyzer } from '../index.js';
*
* const toExtendOneOfTwo = 'toExtendOneOfTwo';
* // `equal` is a matcher that takes a type argument.
* // If no argument is needed, you can simply use `match<'matcherName'>()`
* // instead of a function.
* export const equal = <U>(y?: U) => match<'equal', U>();
*
* type ExtendsOneOfTwo<T, U, V> = T extends U ? true : T extends V ? true : false;
* type NotExtendsOneOfTwo<T, U, V> = T extends U ? false : T extends V ? false : true;
* // Check whether `T` is equal to `U`.
* // It is a utility type used in the type level validation step.
* type Equals<T, U> = (<G>() => G extends T ? 1 : 2) extends <G>() => G extends U ? 1 : 2
* ? true
* : false;
*
* // Register a matcher at type level
* // Define how the type level validation step works.
* // If type level validation is the only thing you need to do (e.g., `equal`),
* // it should return a boolean type.
* // Otherwise, it should return a `ToAnalyze<SomeType>`, e.g. `error` returns
* // `ToAnalyze<never>`, the `ToAnalyze` means to determine whether the assertion
* // passed or not needs further code analysis. You can pass any type to
* // `ToAnalyze` for the code analysis step to use, but here `error` does not need it.
* declare module '../index.js' {
* interface Expect<T> {
* // This mixes `toExtendOneOfTwo` into `expect`, i.e. `expect(...).toExtendOneOfTwo()`
* toExtendOneOfTwo: <U, V>() => ExtendsOneOfTwo<T, U, V>;
* // ^ You can use a specific type as the return type,
* // which can be accessed in the matcher function
* interface Validator<T, U> {
* // Here `equal` is the name of the matcher,
* // it must be the same as that in `match<'equal'>()`.
* equal: Equals<T, U>;
* }
*
* // If you want to mix `toExtendOneOfTwo` into `expect.not`, you can do this:
* interface ExpectNot<T> {
* toExtendOneOfTwo: <U, V>() => NotExtendsOneOfTwo<T, U, V>;
* }
* }
*
* // Register the matcher function at runtime
* registerMatcher(toExtendOneOfTwo, (actual, types, returnType, { not }) => {
* // Check whether `actual.type` extends `types[0]` or `types[1]` by the return type of the matcher function,
* // i.e. the `ExtendsOneOfTwo<T, U, V>` or `NotExtendsOneOfTwo<T, U, V>`
* if (returnType.isLiteral() && returnType.getText() === 'true') return;
* // The `registerToEqual` function is called somewhere before code analysis is executed.
* // If you need to define custom matchers, you should call the corresponding `registerTo...`
* // function first — The `typroof.config.ts` file is a good place to do this.
* export const registerToEqual = () => {
* // If it is a type level only matcher (i.e. The related validator returns a boolean type),
* // the third argument is a boolean indicating whether the validation step is passed.
* // Otherwise (i.e. The related validator returns a `ToAnalyze<SomeType>`), the third
* // argument is a ts-morph `Type` object representing the type to analyze, e.g., `error`
* // returns `ToAnalyze<never>`, so the third argument is a `Type` object representing `never`.
* registerAnalyzer('equal', (actual, expected, passed, { not }) => {
* if (passed) return;
*
* const actualText = chalk.bold(actual.text);
* const expectedType = `one of ${chalk.bold(types.map((t) => t.getText()).join(', '))}`;
* // Here `equal` is a type level only assertion, so we just need to report the error.
* // But you can do anything you want here, e.g., `error` checks if the type emits an
* // error. The fourth argument provides necessary metadata for you to achieve almost
* // anything you can via ts-morph.
*
* throw `Expect ${actualText} ${not ? 'not ' : ''}to extend ${expectedType}, but `${not ? 'did' : 'did not'}`.`;
* });
* const actualText = chalk.bold(actual.text);
* const expectedType = chalk.bold(expected.getText());
* const actualType = chalk.bold(actual.type.getText());
*
* // Throw a string to report the error.
* throw (
* `Expect ${actualText} ${not ? 'not ' : ''}to equal ${expectedType}, ` +
* `but got ${actualType}.`
* );
* });
* };
* ```
*/
export declare const registerMatcher: (name: string, matcher: Matcher) => void;
export declare const registerAnalyzer: <
Tag extends keyof Validator<unknown, unknown>,
>(
tag: Tag,
analyzer: Analyzer<Tag>,
) => void;
export {};
//# sourceMappingURL=matcher.d.ts.map

@@ -1,46 +0,71 @@

export const matchers = new Map();
export const match = () => ({});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const analyzers = new Map();
/**
* Register a matcher.
* @param name Method name to be mixed into `expect`.
* @param matcher The matcher function.
* Register an analyzer.
* @param tag The matcher tag.
* @param analyzer The analyzer function.
*
* @example
* ```typescript
* import { registerMatcher } from '../index.js';
* import { match, registerAnalyzer } from '../index.js';
*
* const toExtendOneOfTwo = 'toExtendOneOfTwo';
* // `equal` is a matcher that takes a type argument.
* // If no argument is needed, you can simply use `match<'matcherName'>()`
* // instead of a function.
* export const equal = <U>(y?: U) => match<'equal', U>();
*
* type ExtendsOneOfTwo<T, U, V> = T extends U ? true : T extends V ? true : false;
* type NotExtendsOneOfTwo<T, U, V> = T extends U ? false : T extends V ? false : true;
* // Check whether `T` is equal to `U`.
* // It is a utility type used in the type level validation step.
* type Equals<T, U> = (<G>() => G extends T ? 1 : 2) extends <G>() => G extends U ? 1 : 2
* ? true
* : false;
*
* // Register a matcher at type level
* // Define how the type level validation step works.
* // If type level validation is the only thing you need to do (e.g., `equal`),
* // it should return a boolean type.
* // Otherwise, it should return a `ToAnalyze<SomeType>`, e.g. `error` returns
* // `ToAnalyze<never>`, the `ToAnalyze` means to determine whether the assertion
* // passed or not needs further code analysis. You can pass any type to
* // `ToAnalyze` for the code analysis step to use, but here `error` does not need it.
* declare module '../index.js' {
* interface Expect<T> {
* // This mixes `toExtendOneOfTwo` into `expect`, i.e. `expect(...).toExtendOneOfTwo()`
* toExtendOneOfTwo: <U, V>() => ExtendsOneOfTwo<T, U, V>;
* // ^ You can use a specific type as the return type,
* // which can be accessed in the matcher function
* interface Validator<T, U> {
* // Here `equal` is the name of the matcher,
* // it must be the same as that in `match<'equal'>()`.
* equal: Equals<T, U>;
* }
*
* // If you want to mix `toExtendOneOfTwo` into `expect.not`, you can do this:
* interface ExpectNot<T> {
* toExtendOneOfTwo: <U, V>() => NotExtendsOneOfTwo<T, U, V>;
* }
* }
*
* // Register the matcher function at runtime
* registerMatcher(toExtendOneOfTwo, (actual, types, returnType, { not }) => {
* // Check whether `actual.type` extends `types[0]` or `types[1]` by the return type of the matcher function,
* // i.e. the `ExtendsOneOfTwo<T, U, V>` or `NotExtendsOneOfTwo<T, U, V>`
* if (returnType.isLiteral() && returnType.getText() === 'true') return;
* // The `registerToEqual` function is called somewhere before code analysis is executed.
* // If you need to define custom matchers, you should call the corresponding `registerTo...`
* // function first — The `typroof.config.ts` file is a good place to do this.
* export const registerToEqual = () => {
* // If it is a type level only matcher (i.e. The related validator returns a boolean type),
* // the third argument is a boolean indicating whether the validation step is passed.
* // Otherwise (i.e. The related validator returns a `ToAnalyze<SomeType>`), the third
* // argument is a ts-morph `Type` object representing the type to analyze, e.g., `error`
* // returns `ToAnalyze<never>`, so the third argument is a `Type` object representing `never`.
* registerAnalyzer('equal', (actual, expected, passed, { not }) => {
* if (passed) return;
*
* const actualText = chalk.bold(actual.text);
* const expectedType = `one of ${chalk.bold(types.map((t) => t.getText()).join(', '))}`;
* // Here `equal` is a type level only assertion, so we just need to report the error.
* // But you can do anything you want here, e.g., `error` checks if the type emits an
* // error. The fourth argument provides necessary metadata for you to achieve almost
* // anything you can via ts-morph.
*
* throw `Expect ${actualText} ${not ? 'not ' : ''}to extend ${expectedType}, but `${not ? 'did' : 'did not'}`.`;
* });
* const actualText = chalk.bold(actual.text);
* const expectedType = chalk.bold(expected.getText());
* const actualType = chalk.bold(actual.type.getText());
*
* // Throw a string to report the error.
* throw (
* `Expect ${actualText} ${not ? 'not ' : ''}to equal ${expectedType}, ` +
* `but got ${actualType}.`
* );
* });
* };
* ```
*/
export const registerMatcher = (name, matcher) => {
matchers.set(name, matcher);
export const registerAnalyzer = (tag, analyzer) => {
analyzers.set(tag, analyzer);
};
#!/usr/bin/env node
import path from 'node:path';
import meow from 'meow';
import { loadConfig } from './config.js';
import {

@@ -57,2 +58,3 @@ createTyproofProject,

tsConfigFilePath: path.join(cwd, 'tsconfig.json'),
...(await loadConfig({ cwd })),
...(testFiles && testFiles.length > 0 && { testFiles }),

@@ -59,0 +61,0 @@ });

@@ -5,2 +5,3 @@ import { typroof } from './typroof.js';

export * from './test/index.js';
export type { Config } from './config.js';
//# sourceMappingURL=index.d.ts.map
{
"name": "typroof",
"version": "0.1.2",
"description": "Test your TypeScript type definitions elegantly",
"version": "0.2.0",
"description": "🚀 Revolutionize your TS type testing with a fast, smooth, and flexible WYSIWYG experience!",
"keywords": [

@@ -21,3 +21,3 @@ "typescript",

"license": "MIT",
"author": "Snowflyt <gaoge011022@gmail.com>",
"author": "Snowflyt <gaoge011022@163.com>",
"type": "module",

@@ -32,3 +32,3 @@ "main": "./index.js",

"prebuild": "npm run clean",
"build": "tsc -p ./tsconfig.build.json && tsc-alias -p ./tsconfig.build.json && cpy ./dist/**/* ./build/ && rimraf dist && prettier --loglevel=silent --print-width 80 --write ./build/ && cpy ./package.json ./build/ && replace-in-file \" \\\"private\\\": true,\" \"\" ./build/package.json && replace-in-file \" \\\"prepare\\\": \\\"husky install\\\",\" \"\" ./build/package.json && prettier --loglevel=silent --print-width 80 --write ./build/package.json && cpy ./README.md ./build/ && cpy ./LICENSE ./build/ && cpy ./screenshot.png ./build/",
"build": "tsc -p ./tsconfig.build.json && tsc-alias -p ./tsconfig.build.json && cpy ./dist/**/* ./build/ && rimraf dist && prettier --loglevel=silent --print-width 80 --write ./build/ && cpy ./package.json ./build/ && replace-in-file \" \\\"private\\\": true,\" \"\" ./build/package.json && replace-in-file \" \\\"prepare\\\": \\\"husky install\\\",\" \"\" ./build/package.json && prettier --loglevel=silent --print-width 80 --write ./build/package.json && cpy ./README.md ./build/ && cpy ./LICENSE ./build/ && cpy ./screenshot.png ./build/ && cpy ./screenshot.gif ./build/",
"clean": "rimraf dist build",

@@ -47,15 +47,17 @@ "format": "prettier --write {src,test}/**/*.{js,ts,json} *.{js,cjs,mjs,ts,cts,mts,json,md} && prettier --write --parser json .hintrc",

"chalk": "^5.3.0",
"meow": "^13.0.0",
"esbuild": "^0.19.11",
"get-tsconfig": "^4.7.2",
"meow": "^13.1.0",
"ts-morph": "^21.0.1"
},
"devDependencies": {
"@commitlint/cli": "^18.4.3",
"@types/eslint": "^8.56.0",
"@commitlint/cli": "^18.4.4",
"@types/eslint": "^8.56.2",
"@types/lint-staged": "^13.3.0",
"@types/node": "^20.10.5",
"@types/node": "^20.11.0",
"@types/prettier": "^2.7.3",
"@typescript-eslint/eslint-plugin": "^6.16.0",
"@typescript-eslint/parser": "^6.16.0",
"@vitest/coverage-v8": "^1.1.0",
"@vitest/ui": "^1.1.0",
"@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.18.1",
"@vitest/coverage-v8": "^1.1.3",
"@vitest/ui": "^1.1.3",
"cpy-cli": "^5.0.0",

@@ -71,4 +73,4 @@ "eslint": "^8.56.0",

"prettier": "^2.8.8",
"prettier-plugin-packagejson": "^2.4.7",
"replace-in-file": "^7.0.2",
"prettier-plugin-packagejson": "^2.4.9",
"replace-in-file": "^7.1.0",
"rimraf": "^5.0.5",

@@ -78,4 +80,4 @@ "tsc-alias": "^1.8.8",

"typescript": "^5.3.3",
"vitest": "^1.1.0"
"vitest": "^1.1.3"
}
}
# Typroof
> Test your TypeScript type definitions elegantly
> 🚀 **Revolutionize** your TS **type testing** with a fast, smooth, and flexible **WYSIWYG** experience!
![Screenshot](./screenshot.gif)
## Installation
### npm
```bash
```shell
npm install --save-dev typroof
```
### yarn
```bash
# Or
yarn add -D typroof
```
### pnpm
```bash
# Or
pnpm add -D typroof

@@ -29,7 +21,7 @@ ```

By default, these test files should end with `.proof.ts` or be placed in a `proof` directory. They will not be really executed, but instead be parsed and statically analyzed against your type definitions. Special constructs such as `expect<Append<'foo', 'bar'>>().toEqual<'foobar'>()` or `expect<Append<'foo', 42>>().toThrow()` will be parsed and analyzed to see if they are valid.
By default, these test files should end with `.proof.ts` or be placed in a `proof/` directory. They will not be really executed, but instead be parsed and statically analyzed against your type definitions. Special constructs such as `expect<Append<'foo', 'bar'>>().to(equal<'foobar'>)` or `expect<Append<'foo', 42>>().to(error)` will be parsed and analyzed to see if they are valid.
The `typroof` CLI will search for these test files and run static analysis on them. It will then report the results to the console.
```bash
```shell
[npx] typroof [path]

@@ -47,2 +39,5 @@ ```

export type Prepend<S extends string, Start extends string> = `${Start}${S}`;
export const append = <S extends string, Ext extends string>(s: S, ext: Ext): Append<S, Ext> =>
`${s}${ext}`;
```

@@ -53,9 +48,10 @@

```typescript
import { describe, expect, it, test } from 'typroof';
import { describe, expect, it, test, equal, extend, error } from 'typroof';
type Append<S extends string, End extends string> = `${S}${End}`;
type Prepend<S extends string, Start extends string> = `${Start}${S}`;
import type { Append, Prepend } from './string-utils';
test('Append', () => {
expect<Append<'foo', 'bar'>>().toEqual<'foobar'>();
expect<Append<'foo', 'bar'>>().to(equal<'foobar'>);
expect<Append<'foo', 'bar'>>().to(extend<string>);
expect(append('foo', 'bar')).to(equal('foobar' as const));
});

@@ -65,3 +61,3 @@

it('should prepend a string to another', () => {
expect<Prepend<'foo', 'bar'>>().toEqual('foobar');
expect<Prepend<'foo', 'bar'>>().to(equal<'foobar'>);
});

@@ -73,3 +69,3 @@ });

You may notice that we use two different syntaxes for the `toEqual` function. You can either pass the expected type as a type argument, or pass it as a value argument. The former is more concise, but the latter is more readable. You can choose whichever you like. Even `expect` itself can use a value argument instead of a type argument, such as `expect('foobar').toEqual('foobar')`.
You may notice that we use two different syntaxes for the `equal` matcher. You can either pass the expected type as a type argument, or pass it as a value argument. The former is useful when testing generic types, while the latter will be useful when testing the return type of a function. You can choose whichever you like. Even `expect` itself can use a value argument instead of a type argument, such as `expect('foobar').to(equal<'foobar'>)`.

@@ -80,6 +76,6 @@ Then run `typroof` to test your type definitions:

The `toEqual` function strictly checks if the type is equal to the expected type. If you want to check if the type is assignable to the expected type, you can use the `toExtend` function:
The `equal` matcher strictly checks if the type is equal to the expected type. If you want to check if the type is assignable to the expected type, you can use the `extend` matcher:
```typescript
expect<Append<'foo', 'bar'>>().toExtend<string>();
expect<Append<'foo', 'bar'>>().to(extend<string>);
```

@@ -89,3 +85,3 @@

Matchers are functions that can be used via `expect<T>(x?: T).toXxx()` or `expect<T>(x?: T).not.toXxx()` to assert the type. Typroof provides some built-in matchers, and you can also create your own matchers (see [Custom Matchers](#custom-matchers)).
Matchers are indicators that can be used via `expect<T>(x?: T).to(matcher<...>)` or `expect<T>(x?: T).not.to(matcher<...>)` to assert the type. Typroof provides some built-in matchers, and you can also create your own matchers (see [Custom Matchers](#custom-matchers)).

@@ -95,77 +91,93 @@ For example:

```typescript
expect<Append<'foo', 'bar'>>().toEqual<'foobar'>();
expect<Append<1, 2>>().toThrow();
expect<Append<'foo', 'bar'>>().not.toBeAny();
expect(append('foo', 'bar')).toExtend<string>();
expect('baz' as const).toBe('baz');
expect<Append<'foo', 'bar'>>().to(equal<'foobar'>);
// @ts-expect-error - The `error` matcher will handle `@ts-expect-error` comments correctly
expect<Append<1, 2>>().to(error);
expect<Append<'foo', 'bar'>>().not.to(beAny);
expect(append('foo', 'bar')).to(extend<string>);
expect('baz' as const).to(equal('baz' as const));
```
The actual type to test can either be passed as a type argument to `expect`, or passed as a value argument to `expect`. The former is more concise, but the latter is more readable. You can choose whichever you like. All built-in matchers also support both syntaxes if it needs a type argument.
The actual type to test can either be passed as a type argument to `expect`, or passed as a value argument to `expect`.The former is useful when testing generic types, while the latter will be useful when testing the return type of a function. You can choose whichever you like.
Matchers can choose to support `expect.not` or not, but it is recommended to support `expect.not` if possible. All built-in matchers support `expect.not`.
Matchers are not forced to be named `toXxx`, but it is recommended to do so. All built-in matchers are named `toXxx`.
The following is a list of built-in matchers. Remember that you can also use `expect.not` to negate them.
### `.toThrow()`
### `error`
Expect a pre emitted diagnostic between the start and end of the given type.
### `.toEqual<U>(y?: U)`
### `equal<U>(y?: U)`
Expect the type to be equal to the given type.
### `.toBeAny()`
### `beAny`
Expect the type to be `any`.
### `.toBeNever()`
### `beNever`
Expect the type to be `never`.
### `.toBeNull()`
### `beNull`
Expect the type to be `null`.
### `.toBeUndefined()`
### `beUndefined`
Expect the type to be `undefined`.
### `.toBeNullish()`
### `beNullish`
Expect the type to be `null`, `undefined` or `null | undefined`.
### `.toMatchBoolean()`
### `matchBoolean`
Expect the type to be `true`, `false` or `boolean`.
### `.toBeTrue()`
### `beTrue`
Expect the type to be `true`.
### `.toBeFalse()`
### `beFalse`
Expect the type to be `false`.
### `.toExtend<U>(y?: U)`
### `extend<U>(y?: U)`
Expect the type to be assignable to the given type (i.e. the given type should be a supertype of the type).
**Warning:** `any` is considered both subtype and supertype of all types in TypeScript, so both `expect<string>().toExtend<any>()` and `expect<any>().toExtend<string>()` will pass (`string` can be replaced with any other type, including `any`), so keep that in mind when using this. If you want to check if the type is assignable to the given type but not `any`, use `.toStrictExtend` instead.
**Warning:** In TypeScript, `any` is both a subtype and a supertype of all other types. Therefore, `expect<string>().to(extend<any>)` and `expect<any>().to(extend<string>)` will both pass. The exception is `never`, which is not assignable to any type (thus `expect<any>().to(extend<never>)` fails). Keep this in mind, as it may lead to unexpected results when working with `any` or `never`. Use `strictExtend` for a stricter version that fails if either the type or the given type is `never` or `any`.
### `.toStrictExtend<U>(y?: U)`
### `strictExtend<U>(y?: U)`
Like `.toExtend`, but fails if either the type or the given type is `never` or `any`.
Like `extend`, but fails if either the type or the given type is `never` or `any`.
### `.toCover<U>(y?: U)`
### `cover<U>(y?: U)`
Expect the given type to be assignable to the type (i.e. the given type should be a subtype of the type).
**Warning:** `any` is considered both subtype and supertype of all types in TypeScript, so both `expect<string>().toCover<any>()` and `expect<any>().toCover<string>()` will pass (`string` can be replaced with any other type, including `any`), so keep that in mind when using this. If you want to check if the given type is assignable to the type but not `any`, use `.toStrictCover` instead.
**Warning:** In TypeScript, `any` is both a subtype and a supertype of all other types. Therefore, `expect<string>().to(cover<any>)` and `expect<any>().to(cover<string>)` will both pass. The exception is `never`, which is not assignable to any type (thus `expect<never>().to(cover<any>)` fails). Keep this in mind, as it may lead to unexpected results when working with `any` or `never`. Use `strictCover` for a stricter version that fails if either the type or the given type is `never` or `any`.
### `.toStrictCover<U>(y?: U)`
### `strictCover<U>(y?: U)`
Like `.toCover`, but fails if either the type or the given type is `never` or `any`.
Like `cover`, but fails if either the type or the given type is `never` or `any`.
## Configuration
You can create a `typroof.config.ts` file in the root directory of your project to configure Typroof. It should export the configuration object.
```typescript
// typroof.config.ts
import type { Config } from 'typroof';
export default {
testFiles: '**/*.proof.ts',
} satisfies Config;
```
Don't forget to add your config file to `tsconfig.json`.
Typroof provides type definitions for the configuration object, so you can get type hints in your editor.
You can use either `.ts`, `.mts`, `.cts`, `.js`, `.mjs` or `.cjs` as the extension of the config file. The priority is `.ts` > `.mts` > `.cts` > `.js` > `.mjs` > `.cjs`.
## API

@@ -193,43 +205,120 @@

You can create custom matchers by using the `registerMatcher` function.
Matchers are just indicators to tell Typroof how to assert the type. The process actually involves two steps: The type level validation and the code analysis using [ts-morph](https://github.com/dsherret/ts-morph).
For example, you can create a `toExtendOneOfTwo` matcher to check if the actual type to test extends one of the two given types:
Take a look at how the `equal` matcher is implemented:
```typescript
import chalk from 'chalk';
import { registerMatcher } from 'typroof';
import { match, registerAnalyzer } from 'typroof';
const toExtendOneOfTwo = 'toExtendOneOfTwo';
// `equal` is a matcher that takes a type argument.
// If no argument is needed, you can simply use `match<'matcherName'>()`
// instead of a function.
export const equal = <U>(y?: U) => match<'equal', U>();
type ExtendsOneOfTwo<T, U, V> = T extends U ? true : T extends V ? true : false;
type NotExtendsOneOfTwo<T, U, V> = T extends U ? false : T extends V ? false : true;
/**
* Check whether `T` is equal to `U`.
* It is a utility type used in the type level validation step.
*/
type Equals<T, U> = (<G>() => G extends T ? 1 : 2) extends <G>() => G extends U ? 1 : 2
? true
: false;
// Register a matcher at type level
// Define how the type level validation step works.
// If type level validation is the only thing you need to do (e.g., `equal`),
// it should return a boolean type.
// Otherwise, it should return a `ToAnalyze<SomeType>`, e.g. `error` returns
// `ToAnalyze<never>`, the `ToAnalyze` means to determine whether the assertion
// passed or not needs further code analysis. You can pass any type to
// `ToAnalyze` for the code analysis step to use, but here `error` does not need it.
declare module 'typroof' {
interface Expect<T> {
// This mixes `toExtendOneOfTwo` into `expect`, i.e. `expect(...).toExtendOneOfTwo()`
toExtendOneOfTwo: <U, V>() => ExtendsOneOfTwo<T, U, V>;
// ^ You can use a specific type as the return type,
// which can be accessed in the matcher function
interface Validator<T, U> {
// Here `equal` is the name of the matcher,
// it must be the same as that in `match<'equal'>()`.
equal: Equals<T, U>;
}
}
// If you want to mix `toExtendOneOfTwo` into `expect.not`, you can do this:
interface ExpectNot<T> {
toExtendOneOfTwo: <U, V>() => NotExtendsOneOfTwo<T, U, V>;
// The `registerToEqual` function is called somewhere before code analysis is executed.
// If you need to define custom matchers, you should call the corresponding `registerTo...`
// function first — The `typroof.config.ts` file is a good place to do this.
export const registerToEqual = () => {
// If it is a type level only matcher (i.e. The related validator returns a boolean type),
// the third argument is a boolean indicating whether the validation step is passed.
// Otherwise (i.e. The related validator returns a `ToAnalyze<SomeType>`), the third
// argument is a ts-morph `Type` object representing the type to analyze, e.g., `error`
// returns `ToAnalyze<never>`, so the third argument is a `Type` object representing `never`.
registerAnalyzer('equal', (actual, expected, passed, { not }) => {
if (passed) return;
// Here `equal` is a type level only assertion, so we just need to report the error.
// But you can do anything you want here, e.g., `error` checks if the type emits an
// error. The fourth argument provides necessary metadata for you to achieve almost
// anything you can via ts-morph.
const actualText = chalk.bold(actual.text);
const expectedType = chalk.bold(expected.getText());
const actualType = chalk.bold(actual.type.getText());
// Throw a string to report the error.
throw (
`Expect ${actualText} ${not ? 'not ' : ''}to equal ${expectedType}, ` +
`but got ${actualType}.`
);
});
};
```
And that is all. As you see here, it is really easy to create custom matchers, and highly customizable powered by ts-morph.
Take a look at how `error` is implemented to see how powerful it is:
```typescript
import { match, registerAnalyzer } from 'typroof';
import type { ToAnalyze } from 'typroof';
export const error = match<'error'>();
declare module 'typroof' {
interface Validator<T, U> {
error: ToAnalyze<never>;
}
}
// Register the matcher function at runtime
registerMatcher(toExtendOneOfTwo, (actual, types, returnType, { not }) => {
// Check whether `actual.type` extends `types[0]` or `types[1]` by the return type of the matcher function,
// i.e. the `ExtendsOneOfTwo<T, U, V>` or `NotExtendsOneOfTwo<T, U, V>`
if (returnType.isLiteral() && returnType.getText() === 'true') return;
export const registerToError = () => {
registerAnalyzer('error', (actual, _1, _2, { diagnostics, not, statement }) => {
const diagnostic = diagnostics.find((diagnostic) => {
const start = diagnostic.getStart();
if (!start) return false;
const length = diagnostic.getLength();
if (!length) return false;
const end = start + length;
return start >= actual.node.getStart() && end <= actual.node.getEnd();
});
const actualText = chalk.bold(actual.text);
const expectedType = `one of ${chalk.bold(types.map((t) => t.getText()).join(', '))}`;
const triggeredError =
!!diagnostic ||
// Check if the error is suppressed by `@ts-expect-error`
statement.getLeadingCommentRanges().some(
(range) =>
range
.getText()
.replace(/^\/\*+\s*/, '')
.replace(/^\/\/\s*/, '')
.startsWith('@ts-expect-error') &&
!diagnostics.find((diagnostic) => diagnostic.getStart() === range.getPos()),
);
throw `Expect ${actualText} ${not ? 'not ' : ''}to extend ${expectedType}, but `${not ? 'did' : 'did not'}`.`;
});
if (not ? triggeredError : !triggeredError) {
const actualText = chalk.bold(actual.text);
throw (
`Expect ${actualText} ${not ? 'not ' : ''}to trigger error, ` +
`but ${not ? 'did' : 'did not'}.`
);
}
});
};
```
Typroof uses [ts-morph](https://github.com/dsherret/ts-morph) to analyze the type definitions, and all relevant AST nodes are exposed to the matcher function, so you can implement almost any kind of matcher you want.
If you like, you can even define a matcher to check whether JSDoc information is correctly reserved through a series of really complex generics.
It is also possible to create an extension library for Typroof, you can guide your users to execute the `registerTo...` functions provided in your library and import validator definitions in their `typroof.config.ts` file.
import { ts } from 'ts-morph';
import type { TyproofProject } from './project.js';
import type { Diagnostic, Node, SourceFile, Type } from 'ts-morph';
import type {
CallExpression,
Diagnostic,
Node,
SourceFile,
Type,
} from 'ts-morph';
export interface Group {

@@ -13,7 +19,8 @@ description: string;

export interface Assertion {
statement: CallExpression<ts.CallExpression>;
actualNode: Node<ts.Node>;
methodName: string;
matcherName: string;
not: boolean;
types: Type<ts.Type>[];
returnType: Type<ts.Type>;
type: Type<ts.Type>;
passedOrValidationResult: boolean | Type<ts.Type>;
}

@@ -20,0 +27,0 @@ export interface AnalyzeResult {

@@ -96,15 +96,23 @@ /* eslint-disable no-irregular-whitespace */

if (access.getType().getCallSignatures().length === 0) continue;
const subCall = access.getParentIfKind(ts.SyntaxKind.CallExpression);
const methodName = access.getName();
const types =
subCall.getTypeArguments().length > 0
? subCall.getTypeArguments().map((a) => a.getType())
: subCall.getArguments().map((a) => a.getType());
const returnType = subCall.getReturnType();
const toCall = access.getParentIfKind(ts.SyntaxKind.CallExpression);
const matcher = toCall.getArguments()[0];
const match =
matcher.getType().getCallSignatures().length > 0
? matcher.getType().getCallSignatures()[0].getReturnType()
: matcher.getType();
const matcherName = match.getTypeArguments()[0].getText().slice(1, -1);
const type = match.getTypeArguments()[1];
const passedOrValidationResult =
toCall.getReturnType().getText() === '"pass"'
? true
: toCall.getReturnType().getText() === '"fail"'
? false
: toCall.getReturnType().getTypeArguments()[0];
test.assertions.push({
statement: toCall,
actualNode,
methodName,
matcherName,
not,
types,
returnType,
type,
passedOrValidationResult,
});

@@ -111,0 +119,0 @@ }

import { ts } from 'ts-morph';
import { matchers } from '../assertions/matcher.js';
import { analyzers } from '../assertions/matcher.js';
export const checkAnalyzeResult = ({

@@ -24,6 +24,13 @@ diagnostics,

for (const assertion of child.assertions) {
const { actualNode, methodName, not, returnType, types } = assertion;
const checker = matchers.get(methodName);
if (!checker)
throw new Error(`Can not find checker for '${methodName}'`);
const {
actualNode,
matcherName,
not,
passedOrValidationResult,
statement,
type,
} = assertion;
const analyzer = analyzers.get(matcherName);
if (!analyzer)
throw new Error(`Can not find analyzer for '${matcherName}'`);
const actual = {

@@ -36,5 +43,5 @@ text: ts.isTypeNode(actualNode.compilerNode)

};
const meta = { project, sourceFile, diagnostics, not };
const meta = { diagnostics, not, project, sourceFile, statement };
try {
checker(actual, types, returnType, meta);
analyzer(actual, type, passedOrValidationResult, meta);
} catch (error) {

@@ -41,0 +48,0 @@ if (typeof error === 'string') {

@@ -47,3 +47,7 @@ import path from 'node:path';

});
const testFiles = project.addSourceFilesAtPaths(testFileGlobs);
const testFiles = project.addSourceFilesAtPaths(
typeof testFileGlobs === 'string'
? ['!**/node_modules/**/*.*', testFileGlobs]
: ['!**/node_modules/**/*.*', ...testFileGlobs],
);
const expectSymbol = getExpectSymbol(project);

@@ -50,0 +54,0 @@ const { describeSymbol, itSymbol, testSymbol } = getTestSymbols(project);

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc