Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ We are open for contributions. If you're planning to contribute please make sure
* [`isFalsy`](#isfalsy)
* [`Nullish`](#nullish)
* [`isNullish`](#isnullish)
* [`hasProperty`](#hasproperty)
* [`hasDefinedProperty`](#hasdefinedproperty)
* [`hasValorizedProperty`](#hasvalorizedproperty)

## Union operators

Expand Down Expand Up @@ -130,6 +133,7 @@ We are open for contributions. If you're planning to contribute please make sure
* [`Overwrite<T, U>`](#overwritet-u)
* [`Assign<T, U>`](#assignt-u)
* [`ValuesType<T>`](#valuestypet)
* [`PossibleKeys<T>`](#possiblekeyst)

## Special operators

Expand Down Expand Up @@ -226,6 +230,48 @@ const consumer = (param: Nullish | string): string => {
};
```

### `hasProperty`
Copy link
Owner

Choose a reason for hiding this comment

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

looks great


Check if the object has the property, similar to [the `in` operator](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-in-operator-narrowing) `'key' in obj` but unexistent properties are not allowed and it allows intellisense

**Usage:**

```ts
import { hasProperty } from 'utility-types';

if (hasProperty(obj, 'prop')) {
// `prop` in `obj`
}
```

### `hasDefinedProperty`

Check if the object has the property and it is not `undefined`

**Usage:**

```ts
import { hasDefinedProperty } from 'utility-types';

if (hasDefinedProperty(obj, 'prop')) {
// `prop` in `obj` and `obj.prop` is not `undefined`
}
```

### `hasValorizedProperty`
Copy link
Owner

Choose a reason for hiding this comment

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

I'm gravitating towadrs hasNonEmptyProperty WDYT?


Check if the object has the property and it is not `undefined` and not `null`

**Usage:**

```ts
import { hasValorizedProperty } from 'utility-types';

if (hasValorizedProperty(obj, 'prop')) {
// `prop` in `obj` and `obj.prop` is not `undefined` and not `null`
}
```

[⇧ back to top](#table-of-contents)

### `SetIntersection<A, B>` (same as Extract)
Expand Down Expand Up @@ -422,6 +468,24 @@ type Keys = OptionalKeys<Props>;

[⇧ back to top](#table-of-contents)

### `PossibleKeys<T>`

Similar to [`$Keys`](#keyst) or `keyof`, but get keys also from union types

**Usage:**

```ts
type Props = { name: string; employeeId: string } | { name: string; guestId: string };
// Expect: "name" | "employeeId" | "guestId"
type PropsKeys1 = PossibleKeys<Props>;
// Expect: "name"
type PropsKeys2 = $Keys<Props>;
// Expect: "name"
type PropsKeys3 = keyof Props;
```

[⇧ back to top](#table-of-contents)

Comment on lines +471 to +488
Copy link
Owner

Choose a reason for hiding this comment

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

we already have another proposal for this type called UnionKeys.
I layed out specs there which are not respected here so I propose either:

  • update this PR according to specs
  • remove this type from this PR

### `Optional<T, K>`

From `T` make a set of properties by key `K` become optional
Expand Down
44 changes: 44 additions & 0 deletions src/__snapshots__/aliases-and-guards.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,50 @@ exports[`Falsy testType<Falsy>() (type) should match snapshot 1`] = `"Falsy"`;

exports[`Primitive testType<Primitive>() (type) should match snapshot 1`] = `"Primitive"`;

exports[`hasDefinedProperty obj (type) should match snapshot 1`] = `"{ name: string; } & { name: string; }"`;

exports[`hasDefinedProperty obj (type) should match snapshot 2`] = `"{ name: string; } & { name: string; }"`;

exports[`hasDefinedProperty obj1 (type) should match snapshot 1`] = `"{ name?: string | undefined; } & { name: string; }"`;

exports[`hasDefinedProperty obj1 (type) should match snapshot 2`] = `"{ name?: string | undefined; } & { name: string; }"`;

exports[`hasDefinedProperty obj1.name (type) should match snapshot 1`] = `"string"`;

exports[`hasDefinedProperty obj1.name (type) should match snapshot 2`] = `"string"`;

exports[`hasDefinedProperty obj2 (type) should match snapshot 1`] = `"{ name: string | undefined; } & { name: string; }"`;

exports[`hasDefinedProperty obj2 (type) should match snapshot 2`] = `"{ name: string | undefined; } & { name: string; }"`;

exports[`hasDefinedProperty obj2.name (type) should match snapshot 1`] = `"string"`;

exports[`hasDefinedProperty obj2.name (type) should match snapshot 2`] = `"string"`;

exports[`hasDefinedProperty obj3 (type) should match snapshot 1`] = `"{ name: string | null | undefined; } & { name: string | null; }"`;

exports[`hasDefinedProperty obj3 (type) should match snapshot 2`] = `"{ name: string | null | undefined; } & { name: string | null; }"`;

exports[`hasDefinedProperty obj3.name (type) should match snapshot 1`] = `"string | null"`;

exports[`hasDefinedProperty obj3.name (type) should match snapshot 2`] = `"string | null"`;

exports[`hasProperty obj (type) should match snapshot 1`] = `"{ name: string; }"`;

exports[`hasProperty obj1 (type) should match snapshot 1`] = `"{ name?: string | undefined; guestId: string; }"`;

exports[`hasProperty obj1.guestId (type) should match snapshot 1`] = `"string"`;

exports[`hasProperty obj1.name (type) should match snapshot 1`] = `"string | undefined"`;

exports[`hasProperty obj2 (type) should match snapshot 1`] = `"{ name: string | undefined; }"`;

exports[`hasProperty obj2.name (type) should match snapshot 1`] = `"string | undefined"`;

exports[`hasProperty obj3 (type) should match snapshot 1`] = `"{ name?: string | undefined; }"`;

exports[`hasProperty obj3.name (type) should match snapshot 1`] = `"string | undefined"`;

exports[`isFalsy param (type) should match snapshot 1`] = `"false | 0 | null | undefined"`;

exports[`isFalsy param (type) should match snapshot 2`] = `"string"`;
Expand Down
2 changes: 2 additions & 0 deletions src/__snapshots__/mapped-types.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`$PossibleKeys -> "name" | "employeeId" | "guestId" (type) should match snapshot 1`] = `"\\"name\\" | \\"employeeId\\" | \\"guestId\\""`;

exports[`Assign const result: Assign<{}, Omit<T, 'age'>> = rest (type) should match snapshot 1`] = `"any"`;

exports[`Assign testType<Assign<Props, NewProps>>() (type) should match snapshot 1`] = `"Pick<Pick<Props, \\"name\\" | \\"visible\\"> & Pick<NewProps, \\"age\\"> & Pick<NewProps, \\"other\\">, \\"name\\" | \\"age\\" | \\"visible\\" | \\"other\\">"`;
Expand Down
209 changes: 209 additions & 0 deletions src/aliases-and-guards.spec.snap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import {
isFalsy,
Nullish,
isNullish,
hasProperty,
hasDefinedProperty,
hasValorizedProperty,
} from './aliases-and-guards';

// @dts-jest:group Primitive
Expand Down Expand Up @@ -96,3 +99,209 @@ it('returns false for non-nullish', () => {
const testResults = nonNullishTestVals.map(isNullish);
testResults.forEach(val => expect(val).toBe(false));
});

// @dts-jest:group hasProperty
it('narrows to correct type', () => {
const obj1 = {
name: 'John',
guestId: '#123',
} as
| {
name?: string;
employeeId: string;
}
| {
name?: string;
guestId: string;
};
if (hasProperty(obj1, 'guestId')) {
// @dts-jest:pass:snap -> { name?: string | undefined; guestId: string; }
obj1;

// @dts-jest:pass:snap -> string | undefined
obj1.name;
expect(obj1.name).toBe('John');

// @dts-jest:pass:snap -> string
obj1.guestId;
expect(obj1.guestId).toBe('#123');
}

const obj2 = {
name: 'John',
} as {
name: string | undefined;
};
if (hasProperty(obj2, 'name')) {
// @dts-jest:pass:snap -> { name: string | undefined; }
obj2;

// @dts-jest:pass:snap -> string | undefined
obj2.name;
expect(obj2.name).toBe('John');
}

const obj3 = {
name: 'John',
} as {
name?: string;
};
if (hasProperty(obj3, 'name')) {
// @dts-jest:pass:snap -> { name?: string | undefined; }
obj3;

// @dts-jest:pass:snap -> string | undefined
obj3.name;
expect(obj3.name).toBe('John');
}
});

// @dts-jest:group hasProperty
it('returns false if property is not in object', () => {
const obj = {
name: 'John',
} as {
name: string;
};

// @ts-ignore
expect(hasProperty(obj, 'guestId')).toBe(false);

// @ts-ignore
if (hasProperty(obj, 'guestId')) {
// @dts-jest:pass:snap -> { name: string; }
obj;

throw new Error('should not reach here');
}
});

// @dts-jest:group hasDefinedProperty
it('narrows to correct type', () => {
const obj1 = {
name: 'John',
} as {
name?: string;
};
if (hasDefinedProperty(obj1, 'name')) {
// @dts-jest:pass:snap -> { name?: string | undefined; } & { name: string; }
obj1;

// @dts-jest:pass:snap -> string
obj1.name;
expect(obj1.name).toBe('John');
}

const obj2 = {
name: 'John',
} as {
name: string | undefined;
};
if (hasDefinedProperty(obj2, 'name')) {
// @dts-jest:pass:snap -> { name: string | undefined; } & { name: string; }
obj2;

// @dts-jest:pass:snap -> string
obj2.name;
expect(obj2.name).toBe('John');
}

const obj3 = {
name: 'John',
} as {
name: string | null | undefined;
};
if (hasDefinedProperty(obj3, 'name')) {
// @dts-jest:pass:snap -> { name: string | null | undefined; } & { name: string | null; }
obj3;

// @dts-jest:pass:snap -> string | null
obj3.name;
expect(obj3.name).toBe('John');
}
});

// @dts-jest:group hasDefinedProperty
it('returns false if property is not in object', () => {
const obj = {
name: 'John',
} as {
name: string;
};

// @ts-ignore
expect(hasDefinedProperty(obj, 'guestId')).toBe(false);

// @ts-ignore
if (hasDefinedProperty(obj, 'guestId')) {
// @dts-jest:pass:snap -> { name: string; } & { name: string; }
obj;

throw new Error('should not reach here');
}
});

// @dts-jest:group hasDefinedProperty
it('narrows to correct type', () => {
const obj1 = {
name: 'John',
} as {
name?: string;
};
if (hasDefinedProperty(obj1, 'name')) {
// @dts-jest:pass:snap -> { name?: string | undefined; } & { name: string; }
obj1;

// @dts-jest:pass:snap -> string
obj1.name;
expect(obj1.name).toBe('John');
}

const obj2 = {
name: 'John',
} as {
name: string | undefined;
};
if (hasDefinedProperty(obj2, 'name')) {
// @dts-jest:pass:snap -> { name: string | undefined; } & { name: string; }
obj2;

// @dts-jest:pass:snap -> string
obj2.name;
expect(obj2.name).toBe('John');
}

const obj3 = {
name: 'John',
} as {
name: string | null | undefined;
};
if (hasDefinedProperty(obj3, 'name')) {
// @dts-jest:pass:snap -> { name: string | null | undefined; } & { name: string | null; }
obj3;

// @dts-jest:pass:snap -> string | null
obj3.name;
expect(obj3.name).toBe('John');
}
});

// @dts-jest:group hasDefinedProperty
it('returns false if property is not in object', () => {
const obj = {
name: 'John',
} as {
name: string;
};

// @ts-ignore
expect(hasDefinedProperty(obj, 'guestId')).toBe(false);

// @ts-ignore
if (hasDefinedProperty(obj, 'guestId')) {
// @dts-jest:pass:snap -> { name: string; } & { name: string; }
obj;

throw new Error('should not reach here');
}
});
Loading