Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add assertion type guards #97

Merged
merged 5 commits into from
Jan 22, 2020
Merged

Add assertion type guards #97

merged 5 commits into from
Jan 22, 2020

Conversation

joelpurra
Copy link
Contributor

Uses the typescript v3.7 feature asserts value is T to create assert variants of the is type guards. The assertions are used to narrow types at compile time, and to throw TypeError at runtime for values which are not of the correct type.

import {assert} from '@sindresorhus/is';

assert.string(foo);
  • Each method in is is wrapped and mirrored in assert.
  • Tests for is are duplicated for assert.

Notes

  • The explicit typing in interface Assert is required for typescript to acknowledge the assertions.
  • Due to the assertions requiring explicit typing, using property for is.assert.string() (and so on) would require using namespace is, which was removed in Stop using TypeScript namespace #78. This also means that assert needs to be exported separately.
  • Custom descriptions are used to enhance some assertion error messages. The value is not included in the error message.
  • Could perhaps use Node.js' fitting AssertionError on the server-side, but it would require an import.

Fixes #91.

See

@joelpurra
Copy link
Contributor Author

joelpurra commented Jan 21, 2020

Due to typescript's syntax changes/additions in v3.7 (such as asserts) this pull request originally failed xo linting. Upgraded typescript/linting dependencies (and rebased the asserts on top) to fix that; it's now confirmed to work. Will submit a separate pull request (#101) to keep the changes apart.

@joelpurra
Copy link
Contributor Author

Awaiting #101; should be rebased on top of master after merge.

Uses the typescript v3.7 feature `asserts value is T` to create `assert` variants of the `is` type guards. The assertions are used to narrow types at compile time, and to throw `TypeError` at runtime for values which are not of the correct type.

```typescript
import {assert} from '@sindresorhus/is';

assert.string(foo);
```

- Each method in `is` is wrapped and mirrored in `assert`.
- Tests for `is` are duplicated for `assert`.

Notes

- The explicit typing in `interface Assert` is required for typescript to acknowledge the assertions.
- Due to the assertions requiring explicit typing, using ` property for is.assert.string()` (and so on) would require using `namespace is`, which was removed in #78. This also means that `assert` needs to be exported separately.
- Custom descriptions are used to enhance some assertion error messages. The value is not included in the error message.
- Could perhaps use Node.js' fitting `AssertionError` on the server-side, but it would require an `import`.

Fixes #91.

See

- #91
- https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions
- microsoft/TypeScript#32695
@sindresorhus
Copy link
Owner

The value is not included in the error message.

I think it would be useful to include the value.

Maybe something like:

Expected value to be `number`, got `foo` (string)

?

readme.md Outdated
@@ -37,6 +38,15 @@ is.number(6);
//=> true
```

Assertions perform the same type checks, but throw errors if the type does not match.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Assertions perform the same type checks, but throw errors if the type does not match.
[Assertions](#type-assertions) perform the same type checks, but throw errors if the type does not match.

readme.md Outdated
const {assert} = require('@sindresorhus/is');

assert.string(foo);
//=> foo is known to be a string
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//=> is usually used to show output, so I think the text should just be a normal comment above.

regExp: (value: unknown) => asserts value is RegExp;
date: (value: unknown) => asserts value is Date;
error: (value: unknown) => asserts value is Error;
map: <TKey = unknown, TValue = unknown>(value: unknown) => asserts value is Map<TKey, TValue>;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
map: <TKey = unknown, TValue = unknown>(value: unknown) => asserts value is Map<TKey, TValue>;
map: <Key = unknown, Value = unknown>(value: unknown) => asserts value is Map<Key, Value>;

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about adding generic type parameters to some of the is methods too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to propagate some more typings for the cases when typescript already knows about them. Separate pull request, right?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate pull request, right?

Yes, would be great :)

source/index.ts Outdated
truthy: (value: unknown) => asserts value is unknown;
falsy: (value: unknown) => asserts value is unknown;
nan: (value: unknown) => asserts value is unknown;
primitive: (value: unknown) => asserts value is unknown;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
primitive: (value: unknown) => asserts value is unknown;
primitive: (value: unknown) => asserts value is Primitive;

?

@joelpurra
Copy link
Contributor Author

The value is not included in the error message.

I think it would be useful to include the value.

Maybe something like:

Expected value to be `number`, got `foo` (string)

?

Useful, yes. Mostly avoided because outputting arbitrary (and in this case provably unexpected) input is scary. Can be seen as a security and information leakage issue. Could add something like received value of type ${is(value)} to add a hint though. Would that work?

@joelpurra
Copy link
Contributor Author

Force-pushed to remove code from #101 already in master, and WIP review fixes to be squashed upon approval.

@sindresorhus
Copy link
Owner

Could add something like received value of type ${is(value)} to add a hint though. Would that work?

👍

@sindresorhus sindresorhus merged commit 0c3f110 into sindresorhus:master Jan 22, 2020
@sindresorhus
Copy link
Owner

This looks good to me. Thank you 🙌

@joelpurra joelpurra deleted the asserts-type-guards branch January 22, 2020 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add "asserts" variants of type guards
2 participants