Git Product home page Git Product logo

typescript-is's Issues

Feature: function argument specific error messages

This may be getting into the territory of custom error reporting (io-ts#error-reporting for example). But it could also just be a special assertion generator. The idea is that I would like to validate a function's parameters. typescript-is provides all the validation I need to make that happen, but the errors it gives back are not as helpful to an end user. This is easier shown than explained:

type ArgumentTypes<F extends Function> = F extends (...args: infer A) => any ? A : never
type Fn = (a: number, b: string) => boolean
type Args = ArgumentTypes<Fn>

const validateFn = createAssertType<ArgumentTypes<Fn>>()
const fn: Fn = (...args) => {
    validateFn(args)
    const [a, b] = args
    return true
}


fn() // TypeGuardError: validation failed at $: expected an array of length 2
// could be: TypeGuardError: validation failed at $: expected 2 arguments

fn(1, 2) // TypeGuardError: validation failed at $.[1]: expected a string
// could be TypeGuardError: validation failed at argument 2: expected a string

fn(1, 'b') // correct usage

I think a wrapAssertFunctionParams<T> would be handy, as a higher order function. Usage would look like:

// generic type argument could be optional
const fnWithValidation = wrapAssertFunctionParams<Fn>(fn)

For the time being I will be writing something myself for my library which does just this. I can post the implementation here if it is interesting

Usage without ttypescript?

Is there a way to manually run a transform and import the transformed code for this library? I'm using create-react-app and don't want to eject in order to change my webpack loaders

Possibility to use with react-scripts

Hello!

I use react-scripts to set up my react project. It would be nice to use typescript-is with it without "ejecting". For the first trial I have added the plugins section in my tsconfig.json.

Unfortunately, I've got this error:

Error: This module should not be used in runtime. Instead, use a transformer during compilation.

Do you have any information what to do when I want to use it with react-scripts?

[Feature Request] use custom error when throwing assertions

It would be nice if the error thrown from assertType and createAssertType used a custom error and exposed that error from the library.

Currently my code using assertions looks like this:

// assert.ts
const assertConfigType = (config: any) => {
  try {
    assertType<Config>(config)
  } catch (e) {
    throw new RuntimeTypeError(e.message)
  }
}
// further up the stack
try {
  ...
} catch (e) {
  if (e instanceof RuntimeTypeError) {
    console.log('poorly formed config given')
    console.log(e.message)
    process.exit(1)
  }
}

It would be nice if I could simplify the code to this:

// assert.ts
const assertConfigType = (config: any) => assertType<Config>(config)

// further up the stack
import { RuntimeTypeError } from 'typescript-is'
try {
  ...
} catch (e) {
    if (e instanceof RuntimeTypeError) {
      ...
    }
}

For reference, node-fetch does this with the FetchError class https://www.npmjs.com/package/node-fetch#class-fetcherror

ttsc --watch does not emit type validators

Having the following code:

createAssertType<{
  data: {
    node: { a: string; closed: string; b: string };
  };
}>()({ data: { node: { a: "a", closed: false, b: "b" } } });

When running yarn ttsc --watch and making changes in this file, here is the compiled result:

image

But if I run yarn ttsc, then the compiled result becomes correct:

image

Allow at least typeof/instanceof checks for basic methods/functions

I know you mentioned before that this strives to only work for serializable data.

However, 😏 would it be possible, happily hidden behind an options flag, to not simply ignore methods/functions but to allow at least a simple type check of sorts, to at least make the minimal guarantee that the provided value is actually a function and not e.g. a number?

Performance regression in 0.10

Input:

import { createIs } from 'typescript-is';

export const enum MyEnum {
  Alpha = 'alpha',
  Bravo = 'bravo',
  Charlie = 'charlie',
  Delta = 'delta',
  Echo = 'echo',
  Foxtrot = 'foxtrot',
  Golf = 'golf',
  Hotel = 'hotel',
  India = 'india',
  Juliet = 'juliet',
}
export const isMyEnum = createIs<MyEnum>();

typescript-is 0.9 output:

const isMyEnum = object => { return object === "alpha" || object === "bravo" || object === "charlie" || object === "delta" || object === "echo" || object === "foxtrot" || object === "golf" || object === "hotel" || object === "india" || object === "juliet"; };

export { isMyEnum };

typescript-is 0.10 output:

import { createIs } from 'typescript-is';

const isMyEnum = createIs(object => { var path = ["$"]; function _74(object) { if (object !== "alpha")
    return "validation failed at " + path.join(".") + ": expected string 'alpha'";
else
    return null; } function _76(object) { if (object !== "bravo")
    return "validation failed at " + path.join(".") + ": expected string 'bravo'";
else
    return null; } function _78(object) { if (object !== "charlie")
    return "validation failed at " + path.join(".") + ": expected string 'charlie'";
else
    return null; } function _80(object) { if (object !== "delta")
    return "validation failed at " + path.join(".") + ": expected string 'delta'";
else
    return null; } function _82(object) { if (object !== "echo")
    return "validation failed at " + path.join(".") + ": expected string 'echo'";
else
    return null; } function _84(object) { if (object !== "foxtrot")
    return "validation failed at " + path.join(".") + ": expected string 'foxtrot'";
else
    return null; } function _86(object) { if (object !== "golf")
    return "validation failed at " + path.join(".") + ": expected string 'golf'";
else
    return null; } function _88(object) { if (object !== "hotel")
    return "validation failed at " + path.join(".") + ": expected string 'hotel'";
else
    return null; } function _90(object) { if (object !== "india")
    return "validation failed at " + path.join(".") + ": expected string 'india'";
else
    return null; } function _92(object) { if (object !== "juliet")
    return "validation failed at " + path.join(".") + ": expected string 'juliet'";
else
    return null; } function _94(object) { var conditions = [_74, _76, _78, _80, _82, _84, _86, _88, _90, _92]; for (const condition of conditions) {
    var error = condition(object);
    if (!error)
        return null;
} return "validation failed at " + path.join(".") + ": there are no valid alternatives"; } var error = _94(object); return error; });

export { isMyEnum };

typescript-is 0.11 seems to have the same output as typescript-is 0.10

Especially given that the output of the isMyEnum function is just a boolean, this seems extremely wasteful in the 0.10+ versions.

Ignore Methods not Supported or Support RegExp Validation

I just tried to validate that one of my optional config variables is a RegExp. typescript-is complained and told me:

Error: Encountered a method declaration, but methods are not supported. Please check the README

Would it be possible to either support RegExp, or to opt-out of this error explicitly in the is<...>(...)?

Feature request: support .enforce<SomeType>() syntax

Hi.

Currently, to wrap some async request call with the library, one has to write:

const res = assertType<{
  name: string;
  age?: number;
}>(await client.request(
  “https://example.com”,
  { some_post_data: 123, other: “abc” }
));

It doesn’t look nice:

  1. Too many nested parens.
  2. The response shape goes before the request shape which is counter-intuitive.

A way better syntax would be:

const res = await client
  .request(
    “https://example.com”,
    { some_post_data: 123, other: “abc” }
  )
  .enforce<{
    name: string;
    age?: number;
  }>();

(This is how Prettier would format it.)

Is it possible to implement this .postfix-like syntax in the transformer?

Notice 3 things here:

  1. The whole purpose is to let it work with promises. Without working with promises such syntax is pretty useless since it produces nested parens again.
  2. It’s okay for request() to return something custom (in terms of typing information), like a promise with additional enforce() method in it (along with .then and .catch - Promise<T> & EnforcePromiseMixin<T>)), it’s easily expressible in TS I guess.
  3. If the validation fails, it’s critical to have a detailed error message with all the info about mismatching properties.

If it’s possible to implement such a thing (is it possible in theory at all?), it would be a huge relief IMHO. There is no other library which supports such things.

Support TypeScript 3.7 Assertions

TypeScript 3.7 introduces the assert keyword microsoft/TypeScript#32695, it allows code to be written this way:

import { assertType } from 'typescript-is';

try {
    assertType<string>(object); // No need to reassign
    object.toUpperCasse();
} catch (error) {
    // ...
}

instead of this:

import { assertType } from 'typescript-is';

try {
    const asString = assertType<string>(object);
    asString.toUpperCasse();
} catch (error) {
    // ...
}

It'd be good if the library provided support for it, maybe using a new function if backwards compatibility is required.

This module should not be used in runtime. Instead, use a transformer during compilation.

I just wanted to drop this in here for your information. I don't know the root cause, or whether this issue even belongs in this repo but I thought it might be helpful for you to know about this.

When running with ts-node-dev --compiler ttypescript --respawn --transpileOnly src/index.ts, I get the error This module should not be used in runtime. Instead, use a transformer during compilation. when the validator runs. I don't get the error in parts of the program where the validator is not running.

Removing the --transpileOnly fixes the problem.

Using types in interfaces/complex types throws a Class error

I'm trying to use typescript-is to provide runtime validation for a library that manipulates a custom AST that is serialized/deserialized from well structured JSON. I've written out the type definition using only interfaces and a few basic types. When trying to compile it throws the following error

NestedError: Failed to transform node at: /Users/tristan/code/mobiledoc-core/lib/index.ts:53:32
    at transformNodeAndChildren (/Users/tristan/code/mobiledoc-core/node_modules/typescript-is/lib/transform-inline/transformer.js:33:15)
    at ts.visitEachChild (/Users/tristan/code/mobiledoc-core/node_modules/typescript-is/lib/transform-inline/transformer.js:35:62)
    at visitNode (/Users/tristan/code/mobiledoc-core/node_modules/typescript/lib/typescript.js:60504:23)
    at Object.visitEachChild (/Users/tristan/code/mobiledoc-core/node_modules/typescript/lib/typescript.js:60778:59)
    at transformNodeAndChildren (/Users/tristan/code/mobiledoc-core/node_modules/typescript-is/lib/transform-inline/transformer.js:35:15)
    at ts.visitEachChild (/Users/tristan/code/mobiledoc-core/node_modules/typescript-is/lib/transform-inline/transformer.js:35:62)
    at visitNodes (/Users/tristan/code/mobiledoc-core/node_modules/typescript/lib/typescript.js:60555:48)
    at Object.visitEachChild (/Users/tristan/code/mobiledoc-core/node_modules/typescript/lib/typescript.js:60774:45)
    at transformNodeAndChildren (/Users/tristan/code/mobiledoc-core/node_modules/typescript-is/lib/transform-inline/transformer.js:35:15)
    at ts.visitEachChild (/Users/tristan/code/mobiledoc-core/node_modules/typescript-is/lib/transform-inline/transformer.js:35:62)
Caused By: Error: Classes cannot be validated. https://github.com/woutervh-/typescript-is/issues/3
    at Object.checkIsClass (/Users/tristan/code/mobiledoc-core/node_modules/typescript-is/lib/transform-inline/visitor-utils.js:23:19)
    at visitObjectType (/Users/tristan/code/mobiledoc-core/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:155:22)
    at visitType (/Users/tristan/code/mobiledoc-core/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:346:16)
    at visitTypeReference (/Users/tristan/code/mobiledoc-core/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:142:20)
    at visitType (/Users/tristan/code/mobiledoc-core/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:338:16)
    at visitTypeReference (/Users/tristan/code/mobiledoc-core/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:142:20)
    at visitType (/Users/tristan/code/mobiledoc-core/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:338:16)
    at ts.createFunctionDeclaration.ts.createBlock.propertyInfos.map (/Users/tristan/code/mobiledoc-core/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:100:23)
    at Array.map (<anonymous>)
    at VisitorUtils.setFunctionIfNotExists (/Users/tristan/code/mobiledoc-core/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:94:30)

Here are the type definitions

// Cards
type CardPayload = Record<string, any>
type Card = [string, CardPayload]

// Atoms
type Atom = [string, string, unknown]

// Markers
// [typeIdentifier, openMarkupsIndexes, numberOfClosedMarkups, text]
type TextMarker = [0, number[], number, string]
// [typeIdentifier, openMarkupsIndexes, numberOfClosedMarkups, atomIndex]
type AtomMarker = [1, number[], number, number]
type Marker = TextMarker | AtomMarker

// Sections Tags enums / unions
type MarkupSectionTag = 'aside' | 'blockquote' | 'pull-quote' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p'
type ListSectionTag = 'ul' | 'ol'

// Section Identifiers
type MarkupIdentifer = 1
type ImageIdentifer = 2
type ListIdentifer = 3
type CardIdentifer = 10

// Sections
type MarkupSection = [MarkupIdentifer, MarkupSectionTag, Marker[]]
type ImageSection = [ImageIdentifer, string]
type ListSection = [ListIdentifer, ListSectionTag, Marker[][]]
type CardSection = [CardIdentifer, number]
type Section = MarkupSection | ImageSection | ListSection | CardSection

// Markups
type MarkupTag = 'a' | 'b' | 'code' | 'em' | 'i' | 's' | 'strong' | 'sub' | 'sup' | 'u'

type Markup = [MarkupTag, string[] | undefined]

// Mobiledoc Interface
export interface Mobiledoc_0_3_2 {
  atoms: Atom[]
  cards: Card[]
  markups: Markup[]
  sections: Section[]
  readonly version: '0.3.2'
}

is<Mobiledoc_0_3_2>(data) //=> throws compilation error
is<Section>(data) //=> throws compilation error
is<Markup>(data) //=> throws compilation error
is<Card>(data) //=> ok
is<Atom>(data) //=> ok

The interesting thing is that if I make the Mobiledoc_0_3_2 only have sections and cards properties it still throws an error, however if I make them not an array of those types it seems to work fine. Perhaps Array is being seen as a class?

Methods are not ignored in mapped types (maked with keyof syntax)

Since at the moment typescript-is does not support using is<T>() when T is a class, I have used a mapped type as this:

class MyClass {
    method() : string {
        return "";
    };
}

type MappedType = {
    [P in keyof MyClass]: MyClass[P];
}

but typescript-is does not check properly MappedType and throws a error, though the flag "ignoreMethods" is true: it not ignore the method, but expect the property contains an object:

let mappedType : MappedType = {
    method() {
        return "!!!";
    }
};

assertType<MappedType>(mappedType);

error log:

TypeGuardError: validation failed at $.method: expected an object
at Object.assertType (...\node_modules\typescript-is\index.js:62:15)
at Object. (...\my_test.js:10:21)
at Module._compile (internal/modules/cjs/loader.js:736:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:747:10)
at Module.load (internal/modules/cjs/loader.js:628:32)
at tryModuleLoad (internal/modules/cjs/loader.js:568:12)
at Function.Module._load (internal/modules/cjs/loader.js:560:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:801:12)
at executeUserCode (internal/bootstrap/node.js:526:15)
at startMainThreadExecution (internal/bootstrap/node.js:439:3)

Transformer Performance

I was just trying out your transformer as I planned to write something similar myself until I found yours, but on a medium sized library just enabling the transformer I see a 21% increase in compile time (from 3.82 seconds to 4.63 on a 16 core machine with all NVMe storage).

Is that expected?

Using ttypescript (ttsc)

Problem with Date

interface IDevice {
  createdAt: Date;
}

const foo = { createdAt: new Date() } as any;
assertType<IDevice>(foo);

This causes:

Error: Encountered a method declaration, but methods are not supported. Issue: https://github.com/woutervh-/typescript-is/issues/5

is/assertType functions throw error when interface has Date field

I have an interface which has a date field and when I try to assert an object against this interface the library throws the following error

Failed to transform node at: at transformNodeAndChildren (/xxx/node_modules/typescript-is/lib/transform-inline/transformer.js:27

interface MyInterface {
  id: string;
  createdAt: Date;
  modifiedAt: Date;
}

Does the library doesnt support interfaces with Date fields?

Why not check classes?

"This library will not check classes. Instead, you are encouraged to use the native instanceof operator."

Wouldn't it be easy to have this library generate the instanceof code? What's the reason not to?

ts-auto-guard collab?

Hey @woutervh-. Looks like we're both working on similar tools. I would have emailed you privately but I can't find your contact details.

Maybe it would make sense to work together instead of building the two tools in parallel?

I've got a debug mode that will explain why the check failed. You've got some of my planned features (assertions etc). Generally speaking I think I prefer your API and your project seems more mature than ts-auto-guard. Without having looked I assume you've got a nicer code base too. ;)

Let me know what you think.

NestedException on transforming a type

It might be easier to read from the bottom as I simplified and narrowed down the source of the error over time

I get the following exception having updated a type. The update is non-trivial, and I've been unable (so far) to pin down the exact cause, but here's the jist of what the type is.

It represents a set of jobs, each of which is an async function with either parameters in an array via a params member, or a single parameter via a param member, like this:

interface ValidJobs {
  testJob: typeof testJob,
  rolloverJob: typeof rolloverJob,
  replicationTask: typeof replicationTask,
  processOutstanding: typeof processOutstanding,
}

type GenericJobFunction = (...args: any[]) => Promise<any>;
type JobParams<J extends GenericJobFunction> = Parameters<J> extends { length: 1 }
  ? { params: Parameters<J> } | { param: Parameters<J>[0] }
  : Parameters<J> extends { length: 0 } 
    ? undefined 
    : { params: Parameters<J> }

type JobSpec<J extends GenericJobFunction> = {
  runsOn?: Roles[] | undefined | null | false,
  interval: number | 'startup' | 'never'; 
  timeout?: number; 
} & JobParams<J>

export type Jobs = {
  [jobName in keyof ValidJobs]?: JobSpec<ValidJobs[jobName]> | false;
};

A sample of the object passed to validate is something like:

{
  "rolloverJob": {
    "interval":3,
    "param":{"key":"837hdy487srt56x" }
  },
  "replicationTask": {
    "interval":10,
    "params":[12,"hourly check"]
  },
  "processOutstanding": {
    "interval":5,
    "runsOn": ["masternodes"]
  },
}

The previous version of the type was much less strict about defining the relationship between the keys of Jobs and the parameters (ie it was possible to pass parameters for one job to the definition of another).

Obviously, if I do find what causes/suppresses the error I'll let you know, but I'm hoping the stack dump & defns will point you (and therefore me!) in the right direction.

Version: "typescript-is": "^0.13.0"

NestedError: Failed to transform node at: /Users/matthew.woolf/git/news-search/f-extract/config/index.ts:175:13
    at transformNodeAndChildren (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/transformer.js:33:15)
    at ts.visitEachChild (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/transformer.js:35:62)
    at visitNode (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript/lib/typescript.js:67586:23)
    at Object.visitEachChild (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript/lib/typescript.js:67814:45)
    at transformNodeAndChildren (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/transformer.js:35:15)
    at ts.visitEachChild (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/transformer.js:35:62)
    at visitNode (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript/lib/typescript.js:67586:23)
    at Object.visitEachChild (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript/lib/typescript.js:67846:52)
    at transformNodeAndChildren (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/transformer.js:35:15)
    at ts.visitEachChild (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/transformer.js:35:62)
Caused By: Error: Could not generate type-check; unsupported type with flags: 16777216
    at visitType (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:369:15)
    at ts.createFunctionDeclaration.ts.createBlock.propertyInfos.map (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:100:23)
    at Array.map (<anonymous>)
    at VisitorUtils.setFunctionIfNotExists (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:94:30)
    at Object.setFunctionIfNotExists (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/visitor-utils.js:34:46)
    at visitRegularObjectType (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:76:25)
    at visitObjectType (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:168:16)
    at visitType (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:346:16)
    at intersectionType.types.map (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:202:72)
    at Array.map (<anonymous>)

Nested generic types result in "Unbound type parameter, missing type node."

Not sure if this is intended behavior, please let me know if so!

ttypescript version: 3.2.4
typescript-is version: 0.7.17
node version: 11.6.0

Steps to reproduce:
Try compiling a file containing the following:

import { is } from 'typescript-is';

interface Wrapped<T> {
    wrap: T;
}

interface X<T> {
    a: Wrapped<T>;
}

is<X<number>>({ a: { wrap: 2 } });

Actual results:
The compilation fails with an error like this:

typescript-is: transforming program with 30 source files; using TypeScript 3.2.4.

undefined:86995
                throw e;
                ^
NestedError: Failed to transform node at: /home/geomaster/Projects/tests/typescript-is/index.ts:9:2
    at transformNodeAndChildren (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/transformer.js:27:15)
    at ts.visitEachChild (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/transformer.js:29:62)
    at visitNode (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript/lib/typescript.js:63390:23)
    at Object.visitEachChild (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript/lib/typescript.js:63664:59)
    at transformNodeAndChildren (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/transformer.js:29:15)
    at ts.visitEachChild (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/transformer.js:29:62)
    at visitNodes (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript/lib/typescript.js:63441:48)
    at visitLexicalEnvironment (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript/lib/typescript.js:63474:22)
    at Object.visitEachChild (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript/lib/typescript.js:63778:54)
    at transformNodeAndChildren (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/transformer.js:29:15)
Caused By: Error: Unbound type parameter, missing type node.
    at visitTypeParameter (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/visitor.js:255:15)
    at visitType (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/visitor.js:362:16)
    at visitTypeParameter (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/visitor.js:257:12)
    at visitType (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/visitor.js:362:16)
    at createPropertyCheck (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/visitor.js:8:28)
    at visitPropertySignature (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/visitor.js:41:16)
    at visitDeclaration (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/visitor.js:49:16)
    at visitPropertySymbol (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/visitor.js:77:25)
    at visitRegularObjectType (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/visitor.js:131:29)
    at visitObjectType (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/visitor.js:172:16)

Expected results:
Since the T type parameter is bound to number in X<number>, I'd expect wrap to also be of known type, precisely, Wrapped<number>. However, it looks like typescript-is isn't able to deduce this, and so thinks that T in a: Wrapped<T> is an unbound type parameter.

Thanks and let me know if I can help with more info!

Usage in linked modules

When developing a module we often npm link it to a parent application for testing. This way we temporarily rely on the parent app's compiler - which has neither ttypescript, nor it checks the linked module's tsconfig file -> meaning it will not run the transformer and is<...> will not be generated. This way it's pretty much impossible to develop using npm link unless I'm missing something.

Do you have an idea for this situation?

JSON Schema generation?

In addition to the current functionality, it would be very nice to have JSON schema generation as part of the transform. This would provide a means of communicating types to other non-typescript systems. For example, the ability to perform client side validation of data by pushing out JSON schema to the clients so they could do their own local validation prior to submitting data to an HTTP API, for example.

document how to use typescript-is without ttypescript

In my project I use webpack and I am able to use typescript-is without the ttypescript dependency. ts-loader has the ability to pass transformers directly into it. The following is enough to get typescript-is validators up and running.

const typescriptIsTransformer = require('typescript-is/lib/transform-inline/transformer').default

module.exports = {
  // I am hiding the rest of the webpack config
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        loader: 'ts-loader',
        options: {
          getCustomTransformers: program => ({
            before: [typescriptIsTransformer(program)]
          })
        }
      },

}

Just a thought if you want to drop it into your readme

Optional property set to undefined fails validation

interface Test {
  a?: string
}
assertType<Test>({ a: undefined })
// TypeGuardError: validation failed at $.a: expected a string

Is this expected behavior? I thought a?: string and a: string | undefined were equivalent in TypeScript.

is<Isomething>() -> NestedError: Failed to transform node

attach.zip

kai@desktop:/bigdata/KAI/projects/Mechanic$ ./prepare-checkin.sh
yarn run v1.13.0
$ ttsc
typescript-is: transforming program with 98 source files; using TypeScript 3.2.4.

/bigdata/KAI/projects/Mechanic/node_modules/typescript/lib/typescript.js:86995
throw e;
^
NestedError: Failed to transform node at: /bigdata/KAI/projects/Mechanic/src/io/readRuleFile.ts:28:19
at transformNodeAndChildren (/bigdata/KAI/projects/Mechanic/node_modules/typescript-is/lib/transform-inline/transformer.js:27:15)
at ts.visitEachChild (/bigdata/KAI/projects/Mechanic/node_modules/typescript-is/lib/transform-inline/transformer.js:29:62)
at visitNode (/bigdata/KAI/projects/Mechanic/node_modules/typescript/lib/typescript.js:63390:23)
at Object.visitEachChild (/bigdata/KAI/projects/Mechanic/node_modules/typescript/lib/typescript.js:63694:154)
at transformNodeAndChildren (/bigdata/KAI/projects/Mechanic/node_modules/typescript-is/lib/transform-inline/transformer.js:29:15)
at ts.visitEachChild (/bigdata/KAI/projects/Mechanic/node_modules/typescript-is/lib/transform-inline/transformer.js:29:62)
at visitNodes (/bigdata/KAI/projects/Mechanic/node_modules/typescript/lib/typescript.js:63441:48)
at Object.visitEachChild (/bigdata/KAI/projects/Mechanic/node_modules/typescript/lib/typescript.js:63696:63)
at transformNodeAndChildren (/bigdata/KAI/projects/Mechanic/node_modules/typescript-is/lib/transform-inline/transformer.js:29:15)
at ts.visitEachChild (/bigdata/KAI/projects/Mechanic/node_modules/typescript-is/lib/transform-inline/transformer.js:29:62)
Caused By: Error: Unsupported declaration kind: 155
at visitDeclaration (/bigdata/KAI/projects/Mechanic/node_modules/typescript-is/lib/transform-inline/visitor.js:52:15)
at visitPropertySymbol (/bigdata/KAI/projects/Mechanic/node_modules/typescript-is/lib/transform-inline/visitor.js:77:25)
at visitRegularObjectType (/bigdata/KAI/projects/Mechanic/node_modules/typescript-is/lib/transform-inline/visitor.js:131:29)
at visitObjectType (/bigdata/KAI/projects/Mechanic/node_modules/typescript-is/lib/transform-inline/visitor.js:172:16)
at visitType (/bigdata/KAI/projects/Mechanic/node_modules/typescript-is/lib/transform-inline/visitor.js:371:20)
at type.types.map (/bigdata/KAI/projects/Mechanic/node_modules/typescript-is/lib/transform-inline/visitor.js:213:24)
at Array.map ()
at visitUnionOrIntersectionType (/bigdata/KAI/projects/Mechanic/node_modules/typescript-is/lib/transform-inline/visitor.js:213:10)
at visitType (/bigdata/KAI/projects/Mechanic/node_modules/typescript-is/lib/transform-inline/visitor.js:380:16)
at visitArrayObjectType (/bigdata/KAI/projects/Mechanic/node_modules/typescript-is/lib/transform-inline/visitor.js:66:33)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

ignoreMethods has now effect

Using ignoreMethods has no effect for the following data

const test = {
    list: {
        listItem: "aaa",
        data: {
            id: {
                some: "span.to-details",
                convert: x => x
            }
        }
    }
};

validate against the following interface

interface IElement {
    some?: string;
    convert?: (value: string) => string;
}

interface IMap {
    id: string|IElement;
}

interface ITest {
    list: {
        listItem: string;
        data: IMap;
        convert?: (value: string) => string;
    };
}

by using assertEquals<ITest>(test). This results in TypeGuardError: validation failed at $.list.data.id: there are no valid alternatives. The following data results in TypeGuardError: validation failed at $.list.convert: expected an object

const test = {
    list: {
        listItem: "aaa",
        data: {
            id: {
                some: "span.to-details"
            }
        },
        convert: x => x
    },
};

Using

const test = {
    list: {
        listItem: "aaa",
        data: {
            id: {
                some: "span.to-details"
            }
        }
    },
};

works as expected.

Problem with optional properties

Hello!
I noticed some peculiarity when working with optional properties.
For example:

interface SomeInterface {
    someOptionalProperty?: string;
}

const value: SomeInterface = { someOptionalProperty: undefined };

This code is quite valid and can be compiled without any problems.

Example with assertType:
For example:

interface SomeInterface {
    someOptionalProperty?: string;
}

const value: SomeInterface = assertType<SomeInterface>({ someOptionalProperty: undefined });

In this case, I will get the error:

TypeGuardError: validation failed at $.someOptionalProperty: expected a string

[Request] add a changelog

Hi! I saw that a new version was published just a few days ago, though its hard to say that that release actually changed (though I did my best to dig through the commits). It would be awesome if you started keeping a CHANGELOG.txt or used github releases for future versions so we can see what new features/fixes/breaking changes are part of that release.

Of course, its more work to maintain, but it helps you (the maintainer) look back on what you have done over time too.

This may help, this may not, but on one of my personal projects (of which I am using typescript-is 🎈!) I automate the process of publishing so that travis ci runs npm publish for me upon creating a github release https://github.com/andykais/scrape-pages/blob/319dd19b0c952bd9b72ba4f23b3995c25fc6bd81/.travis.yml

TypeGuardError: validation failed at $: validation failed at $: superfluous property 'username' in object

interface HasPassword {
	password: string
}

interface HasUsername {
	username: string
}

type UsernamePassword = HasPassword & HasUsername

const object = {username: 'test', password: 'test'}

try {
	assertType<UsernamePassword>(object)
} catch (e) {
	console.log(e)
}

Result:

TypeGuardError: validation failed at $: validation failed at $: superfluous property 'username' in object
    at Object.assertType (/Users/LoganDark/nitrogen/backend/node_modules/typescript-is/index.js:58:15)
    at Object.<anonymous> (/Users/LoganDark/nitrogen/backend/index.ts:239:5)
    at step (/Users/LoganDark/nitrogen/backend/index.ts:32:23)
    at Object.next (/Users/LoganDark/nitrogen/backend/index.ts:13:53)
    at /Users/LoganDark/nitrogen/backend/index.ts:7:71
    at new Promise (<anonymous>)
    at __awaiter (/Users/LoganDark/nitrogen/backend/index.ts:3:12)
    at /Users/LoganDark/nitrogen/backend/index.ts:224:16
    at Layer.handle [as handle_request] (/Users/LoganDark/nitrogen/backend/node_modules/express/lib/router/layer.js:95:5)
    at next (/Users/LoganDark/nitrogen/backend/node_modules/express/lib/router/route.js:137:13) +1ms

Ignore the line numbers, I simplified the code for the issue.

Configuration:

{
	"transform": "typescript-is/lib/transform-inline/transformer",
	"ignoreClasses": true,
	"ignoreMethods": true,
	"disallowSuperfluousObjectProperties": true
}

The object does have username and password keys and they are both strings.

More detailed error message for "Xyz | null" cases (not just "there are no valid alternatives")

Adding | null alternative significantly degrades verboseness of the error message.

Example:

createAssertType<{
  data: {
    node: { a: string; closed: string; b: string } | null;
  };
}>()({ data: { node: { a: "a", closed: false, b: "b" } } });

// Result:
// TypeGuardError: validation failed at $.data.node: there are no valid alternatives

Notice that it doesn't mention the name of the mis-typed field ("closed"), so it's pretty hard to debug, what's the problem (especially when there is a lot of fields).

If I remove | null clause, it starts showing the field name:

createAssertType<{
  data: {
    node: { a: string; closed: string; b: string };
  };
}>()({ data: { node: { a: "a", closed: false, b: "b" } } });

// Result:
// TypeGuardError: validation failed at $.data.node.closed: expected a string

Is it (at least theoretically) possible to improve error messages here? The case when | null or | undefined or field?: type are used is pretty common (e.g. in GraphQL responses, the field is often times nullable).

Runtime issues when used with Jest

I've made the changes to jest.config.js to use ttypescript and I've modified my tsconfig.json to use typescript-is/lib/transform-inline/transformer as a transform plugin. I can compile everything fine. But when I try to use Jest, I get:

This module should not be used in runtime. Instead, use a transformer during compilation.

I've created a repo that reproduces the issue here:

https://github.com/xogeny/siren-types

Be sure to use the ttypescript branch.

Fails to transform on empty tuples

Sample :

import { assertType } from 'typescript-is';

type Works = [number];
assertType<Works>([0]);

type Fails = [];
assertType<Fails>([]);

If you comment out the final line, all is good, if you don't you get (at compile time):

NestedError: Failed to transform node at: _test/ex.ts:6:17
    at transformNodeAndChildren (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/transformer.js:33:15)
    at ts.visitEachChild (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/transformer.js:35:62)
    at visitNode (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript/lib/typescript.js:67586:23)
    at Object.visitEachChild (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript/lib/typescript.js:67860:59)
    at transformNodeAndChildren (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/transformer.js:35:15)
    at ts.visitEachChild (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/transformer.js:35:62)
    at visitNodes (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript/lib/typescript.js:67637:48)
    at visitLexicalEnvironment (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript/lib/typescript.js:67670:22)
    at Object.visitEachChild (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript/lib/typescript.js:67974:54)
    at transformNodeAndChildren (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/transformer.js:35:15)
Caused By: Error: Expected tuple type to have type arguments.
    at visitTupleObjectType (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/visitor-type-name.js:10:15)
    at visitObjectType (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/visitor-type-name.js:46:16)
    at Object.visitType (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/visitor-type-name.js:114:16)
    at visitTupleObjectType (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:12:34)
    at visitObjectType (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:161:16)
    at visitType (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:347:16)
    at visitTypeReference (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:143:20)
    at Object.visitType (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/visitor-type-check.js:339:16)
    at createArrowFunction (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/transform-node.js:16:36)
    at Object.transformNode (/Users/matthew.woolf/git/news-search/f-extract/node_modules/typescript-is/lib/transform-inline/transform-node.js:60:35)

Awesome project BTW.

Generic assertType results in "Unbound type parameter, missing type node."

This is a similar issue to #4. In my case I am getting the error when trying to use assertType<T>(data)

async function fetchContent<T>(filename: string): Promise<boolean> {
  const url = `/${filename}`;
  const res = await fetch(url);
  const data = await res.json();

  try {
    assertType<T>(data);
    return true;
  } catch (error) {
    console.log(error.message);
    return false;
  }
}

fetchContent<CMS.Homepage>('homepage.json');

Question - regexes on strings...?

Hi there, been playing about with this library and I think it looks really good - especially as a way of defining contracts for messages between client / server.

This is probably out of scope but I thought I'd ask - it might be useful to have a way of specifying regexes on strings.

I'm not sure how that would look or work though, some custom comment syntax or decorator?

Validation Context: Which property is not valid on an object

Is it possible to determine which property is valid on an object from the AssertType function? Currently I only see Type assertion failed

Something like the following would be helpful:

interface MyRequest {
  name: string,
  age: number
}

try {
  const asString = assertType<MyRequest>(unknownObj);
} catch (e) {
  console.log('error: ', e); // "error: Object does not have property: 'name'   "
}

Idea: Emit is.ts, bundling validators and assertions for all discovered types and interfaces

First: Thanks for this fantastic work! Finally bringing runtime sanity to objects, APIs, files, etc.

So I'm in the process of adding validators across my code; something like:

interface Person {
   name: string;
   age: number;
}

const isPerson = createIs<Person>();

which means that I'm repeating that isInterface = createIs<Interface>() line for all my interfaces and types.

Would you consider automatically generating is[Interface], assert[Interface] and other checker and assertion functions and emitting them in an is.ts (or is.js) file, usable for runtime?

Same idea as how TypeScript can produce declarations, your library can produce a validator/checker/assertion bundle.

Doesn’t Seem to Detect Additional Keys

Either I’m doing something wrong, or this is not yet supported:

interface Example {
    exists: true
}

const breaks = { exists: true, doesNotExist: true }

is<Example>(breaks)
// expected: false, received: true

Unable to use `Date` type in interfaces that will be validated

While using this library I have run into an issue where any interface I would like to be validated cannot hold any properties that hold a Date type. I have both "ignoreClasses": true and "ignoreMethods": true in my tsconfig.json. This problem occurs at run time, as validations with Date types fail every time, though it compiles just fine.

Glancing through the generated code, it seems a large list of checks is created for each Date typed property, that checks that it has a number of properties, including properties that aren't always supported. I found the first place that fails is a check that a Date has the getVarDate() method, which is only supported in Internet Explorer and obsolete according to MDN. I have not checked the rest. I am using this on server application for cursory incoming type checking.

Right now my workaround is to have a separate interface without any Date properties simply for use with validation and then checking for those properties manually. This would not work with the setting "disallowSuperfluousObjectProperties": true in the tsconfig.json.

Incorrect behavior for nullable properties with strictNullChecks disabled

When strictNullChecks is set to false, nullable properties are not allowed to be null. I suspect this is related to the TypeScript compiler transforming string | null to string in this mode. A potential fix could be allowing null/undefined for all types within typescript-is if it detects strictNullChecks are disabled?

import { createIs } from 'typescript-is'

interface Foo {
  nullableString: string | null
}

const isFoo = createIs<Foo>()

const validFoo: Foo = {
  nullableString: null,
}

// should be true, but returns false!
console.log(isFoo(validFoo))

[Feature Request] babel macro plugin

Hi! This one is a big ask, but I want to know if it is in the works at all. Currently, this is what my webpack loaders setup looks like:

  module: {
    rules: [
      {
        test: /\.(ts|js)$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      },
      {
        test: /\.runtime\.ts/,
        exclude: /node_modules/,
        use: [{ loader: 'ts-loader', options: { compiler: 'ttypescript' } }]
      },
    ]
  },

and my .babelrc

{
  "presets": [
    [
      "@babel/env",
      {
        "targets": {
          "node": "8.0"
        }
      }
    ],
    "@babel/typescript"
  ],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}

Before introducing typescript-is, I used the single babel-loader to transpile both typescript and javascript. Now, the code has to go through two different loaders, which slows the process significantly, since typechecking the entire project is essential for the loader to properly transform the code. It also adds more dependencies specifically for this library, and makes tsconfig.json non-standard.

A similar framework to the typescript transformer api in babel land is the babel-plugin-macros project. Importing a typescript-is.macro would operate identically to the current import/transform.

Of course, this hinges on the hope that a babel macro and a typescript transformer could share some core code. If not, then maintaining two alongside each other is likely too much of an undertaking.

Bug: Does not support recursive types

Recursive types do not compile, and throw a RangeError: Maximum call stack size exceeded. Let me know if this is a problem with ttypescript and I can report this bug upstream. Thank you for working on this library! 😄

stack trace:

$ npm run compile:runtime:config

/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript/lib/typescript.js:87527
                throw e;
                ^
NestedError: Failed to transform node at: /Users/andrew/Code/scratchwork/scrape-pages/src/settings/config/assert.ts:9:55
    at transformNodeAndChildren (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript-is/lib/transform-inline/transformer.js:30:15)
    at ts.visitEachChild (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript-is/lib/transform-inline/transformer.js:32:62)
    at visitNode (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript/lib/typescript.js:63887:23)
    at Object.visitEachChild (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript/lib/typescript.js:64161:59)
    at transformNodeAndChildren (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript-is/lib/transform-inline/transformer.js:32:15)
    at ts.visitEachChild (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript-is/lib/transform-inline/transformer.js:32:62)
    at visitNodes (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript/lib/typescript.js:63938:48)
    at Object.visitEachChild (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript/lib/typescript.js:64157:45)
    at transformNodeAndChildren (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript-is/lib/transform-inline/transformer.js:32:15)
    at ts.visitEachChild (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript-is/lib/transform-inline/transformer.js:32:62)
Caused By: RangeError: Maximum call stack size exceeded
    at Object.isNodeKind (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript/lib/typescript.js:13880:24)
    at Object.createNode (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript/lib/typescript.js:16206:22)
    at createSynthesizedNode (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript/lib/typescript.js:59514:23)
    at Object.createTypeOf (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript/lib/typescript.js:60561:20)
    at visitString (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript-is/lib/transform-inline/visitor.js:470:131)
    at visitType (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript-is/lib/transform-inline/visitor.js:514:16)
    at createPropertyCheck (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript-is/lib/transform-inline/visitor.js:12:24)
    at visitPropertySignature (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript-is/lib/transform-inline/visitor.js:51:12)
    at visitDeclaration (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript-is/lib/transform-inline/visitor.js:55:16)
    at visitPropertySymbol (/Users/andrew/Code/scratchwork/scrape-pages/node_modules/typescript-is/lib/transform-inline/visitor.js:111:16)

simplified failing snippet:

import { assertType } from 'typescript-is'
type ConfigInit = {
  folder: string
  children: ConfigInit
}

export const assertConfigType = (configInit: any) => {
  assertType<ConfigInit>(configInit)
}

tsconfig.json:

{
  "compilerOptions": {
    "rootDir": "../../../../src",
    "outDir": "./",
    "sourceMap": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "lib": ["es2015", "es2016", "es2017"],
    "module": "commonjs",
    "moduleResolution": "node",
    "target": "es2017",
    "allowJs": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true,
    "plugins": [
      {
        "transform": "typescript-is/lib/transform-inline/transformer"
      }
    ]
  },
  "include": ["src/**/*.ts", "custom.d.ts"],
  "files": ["../assert.ts", "../../../../custom.d.ts"]
}

`is` and `createIs` should error on excess properties

Is there a way to is or createIs that errors on excess properties?

interface IPerson {
   name: string;
}

const isPerson = createIs<IPerson>();

isPerson({name: "Arash", role: "Admin" }); // this should return false, but it returns true!

Use case: API server receives POST message with payload in the body, performs check to make sure payload conforms to expected schema, writes object to Cosmos DB (or other no-sql Document DB). Goal would be to 1. make sure payload has the properties that the interface expects, AND 2. payload does not have any other/extra properties not defined in the interface.

TypeScript itself errors on unexpected properties:

const person: IPerson = {name: "Arash", role: "Admin"}
// Type '{ name: string; role: string; }' is not assignable to type 'IPerson'.
// Object literal may only specify known properties, and 'role' does not exist in type 'IPerson'.ts(2322)

P.S. Big fan of typescript-is :)

"Unsupported declaration kind" when validating against a class containing a method

ttypescript version: 3.2.4
typescript-is version: 0.7.16
node version: 11.6.0

Steps to reproduce:
Try compiling a file containing the following:

import { is } from 'typescript-is';

class X {
    method() {}
}
console.log(is<X>(new X()));

Actual results:
The compilation fails with an error like this:

typescript-is: transforming program with 23 source files; using TypeScript 3.2.4.

undefined:86995
                throw e;
                ^
NestedError: Failed to transform node at: /home/geomaster/Projects/tests/typescript-is/index.ts:6:13
    at transformNodeAndChildren (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/transformer.js:27:15)
    at ts.visitEachChild (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/transformer.js:29:62)
    at visitNodes (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript/lib/typescript.js:63441:48)
    at Object.visitEachChild (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript/lib/typescript.js:63610:156)
    at transformNodeAndChildren (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/transformer.js:29:15)
    at ts.visitEachChild (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/transformer.js:29:62)
    at visitNode (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript/lib/typescript.js:63390:23)
    at Object.visitEachChild (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript/lib/typescript.js:63664:59)
    at transformNodeAndChildren (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/transformer.js:29:15)
    at ts.visitEachChild (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/transformer.js:29:62)
Caused By: Error: Unsupported declaration kind: 156
    at visitDeclaration (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/visitor.js:52:15)
    at visitPropertySymbol (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/visitor.js:77:25)
    at visitRegularObjectType (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/visitor.js:131:29)
    at visitObjectType (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/visitor.js:172:16)
    at Object.visitType (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/visitor.js:366:16)
    at createArrowFunction (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/transform-node.js:9:21)
    at Object.transformNode (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/transform-node.js:58:35)
    at transformNodeAndChildren (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/transformer.js:22:44)
    at ts.visitEachChild (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript-is/lib/transform-inline/transformer.js:29:62)
    at visitNodes (/home/geomaster/Projects/tests/typescript-is/node_modules/typescript/lib/typescript.js:63441:48)

Expected results:
I know that class validation is explicitly not supported, which is completely fine. But this only breaks when the class has a method, which is a bit hard to track down and confusing. It might be more intuitive to display a normal compile error as soon as a class is tested.

This is not a high-priority issue, but might be good to put it on the backlog - I had quite some trouble trying to figure out what exactly is happening here :)

Accessing the source data in TypeGuardError

TypeGuardError forms a nice error message, but the data which failed validation is not part of this error. I.e. I can catch & print "TypeGuardError: validation failed at ...", but I can't print "TypeGuardError: validation failed at ..., input data was: ..." down the stack.

Is it possible to add a field to TypeGuardError which holds the actual data failed validation? This would be super useful for debugging.

Add option to work in transpiled source

For much faster development we use transpileOnly option and do typechecking in different background thread. Unfortunately this library fails with this option (see #14).

It would be great to add an option to skip type checking in such a case. Then assertType would never throw, is would always return true, etc.

We could achieve this by hacking typescript-is/index.js:

function assertType(obj, getErrorMessage = () => null) {
...

function is(obj, getErrorMessage = () => null) {
...

function createIs(getErrorMessage = () => null) {
...

function createAssertType(getErrorMessage = () => null) {
...

I hope you understand what do I mean :-)

Incorrect transform for optional parameters

The length check for a Parameter list tuple is too strict in the case of optional parameters (or in fact any array where the final element(s) can validly be undefined).

Here's a case to reproduce the issue:

import * as assert from 'assert';
import { createIs } from '../index';

describe('is', () => {
    function foo(_a: number, _b?: string) { return; }
    type FooParams = Parameters<typeof foo>;

    const isFooParams = createIs<FooParams>();

    describe('isFooParams', () => {
        it('should return true for valid parameters, optional or not', () => {
            assert.deepStrictEqual(isFooParams([1, 'a']), true);
            assert.deepStrictEqual(isFooParams([1]), true);
            assert.deepStrictEqual(isFooParams([1, undefined]), true);
            assert.deepStrictEqual(isFooParams([1, null]), false);
        });
    });
});

The second assert (incorrectly) fails: element [1] of the array is indeed undefined.

In the generated output (below) there is an inappropriate test for Array.length===2.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const assert = require("assert");
const index_1 = require("../index");
describe('is', () => {
    function foo(_a, _b) { return; }
    const isFooParams = index_1.createIs(object => {
        var path = ["$"]; 
        function _number(object) {
            if (typeof object !== "number")
                return "validation failed at " + path.join(".") + ": expected a number";
            else
                return null;
        } 
        function _undefined(object) {
            if (object !== undefined)
                return "validation failed at " + path.join(".") + ": expected undefined";
            else
                return null;
        } 
        function _string(object) {
            if (typeof object !== "string")
                return "validation failed at " + path.join(".") + ": expected a string";
            else
                return null;
        } 
        function su__undefined__string_eu(object) {
            var conditions = [_undefined, _string]; for (const condition of conditions) {
                var error = condition(object);
                if (!error)
                    return null;
            } return "validation failed at " + path.join(".") + ": there are no valid alternatives";
        } 
        function st__number_su__undefined__string_eu_et_9_396(object) {
/* HERE: array.length might be <= 2 since all the final elements after length=1 can validly be undefined */
            if (!Array.isArray(object) || object.length !== 2) 
                return "validation failed at " + path.join(".") + ": expected an array of length 2"; {
                path.push("[0]");
                var error = _number(object[0]);
                path.pop();
                if (error)
                    return error;
            } {
                path.push("[1]");
                var error = su__undefined__string_eu(object[1]);
                path.pop();
                if (error)
                    return error;
            } return null;
        } var error = st__number_su__undefined__string_eu_et_9_396(object); return error;
    });
    describe('isFooParams', () => {
        it('should return true for valid parameters, optional or not', () => {
            assert.deepStrictEqual(isFooParams([1, 'a']), true);
            assert.deepStrictEqual(isFooParams([1]), true);
            assert.deepStrictEqual(isFooParams([1, undefined]), true);
            assert.deepStrictEqual(isFooParams([1, null]), false);
        });
    });
});

Output

Obtaining type info as JSON at runtime

Hi,

I have the following use case, and it seems your project does something pretty close, so was wondering if you might want to add it.

I've been working with the helpful CLI argument processing utility command-line-args, but found it redundant to have to define what is basically a schema of definitions and then represent that same schema all over again in my jsdoc (or TypeScript) annotations.

I'd like to be able to define interfaces in TS or jsdoc, and then at runtime, pass a JSON form of these interfaces onto a utility function.

So in place of this (I don't know if your code works with jsdoc typedef's, so if not, just imagine the typedefs are interfaces or types instead):

/**
 * @typedef {Object} MyCLIOptions
 * @property {boolean} v verbose
 * @property {string[]} src [default]
 * @property {number} t timeout
 */
const optionDefinitions = [
  { name: 'verbose', alias: 'v', type: Boolean },
  { name: 'src', type: String, multiple: true, defaultOption: true },
  { name: 'timeout', alias: 't', type: Number }
]

I'd like to be able to do something like this (I know it might not build the exact same JSON as the above array (and as a generic tool it might not parse the [default] text but could hopefully at least pass on the jsdoc names/descriptions), but if it could be parsed by a utility, that'd be enough):

import {typeAsJSON} from 'typescript-is';
/**
 * @typedef {Object} MyCLIOptions
 * @property {boolean} v verbose
 * @property {string[]} src [default]
 * @property {number} t timeout
 */
const optionDefinitions = typeAsJSON('MyCLIOptions');

// I think I prefer the line above for being valid JS (not even needing TS), but
// possibly these if necessary?

// const optionDefinitions = typeAsJSON(MyCLIOptions);
// Or this?
// const optionDefinitions : MyCLIOptions = typeAsJSON();

If this is beyond your project's scope, do you have any suggestions for a novice to TypeScript?

Tuples are not type-checked correctly

ttypescript version: 3.2.4
typescript-is version: 0.7.18
node version: 11.6.0

Steps to reproduce:
Try compiling a file containing the following:

import { is } from 'typescript-is';

type NumTuple = [number, number];

console.log(is<NumTuple>([0]));
console.log(is<NumTuple>([]));

Actual results:
Both statements print true.

Expected results:
Both statements should print false, as neither [0] nor [] satisfy the NumTuple type.

I feel like I'm submitting too many bug reports :) Let me know if I can help any further. Also, if there's a way to donate to the project, I'd be happy to - this is one of the most useful libraries I've used in a long time!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.