server/device-sso-auth-lsp/src/language-server/tsUtils.ts (84 lines of code) (raw):
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
// For the Proof of concept, this file's code was copied from the AWS Toolkit for VS Code repo
// https://github.com/aws/aws-toolkit-vscode/blob/5d621c8405a8b20ffe571ad0ba10ae700178e051/src/shared/utilities/tsUtils.ts
export function stringOrProp(obj: any, prop: string): string {
if (obj === undefined || typeof obj === 'string') {
return obj ?? ''
}
return obj[prop] ?? ''
}
export function getMissingProps<T>(obj: T, ...props: (keyof T)[]): typeof props {
return props.filter(prop => obj[prop] === undefined)
}
export function hasProps<T, K extends keyof T>(obj: T, ...props: K[]): obj is Readonly<RequiredProps<T, K>> {
return getMissingProps(obj, ...props).length === 0
}
export function hasStringProps<T, K extends PropertyKey>(obj: T, ...props: K[]): obj is T & { [P in K]: string } {
return props.filter(prop => typeof (obj as unknown as Record<K, unknown>)[prop] !== 'string').length === 0
}
export function assertHasProps<T, K extends keyof T>(
obj: T | undefined,
...props: K[]
): asserts obj is Readonly<RequiredProps<T, K>> {
if (!isNonNullable(obj)) {
throw new TypeError(`Object was null or undefined, expected properties: ${props.join(', ')}`)
}
const missing = getMissingProps(obj, ...props)
if (missing.length > 0) {
throw new TypeError(`Object was missing properties: ${missing.join(', ')}`)
}
// May be easier/cleaner to just copy the object rather than freezing it
// Should also check the properties and make sure they're all data descriptors
Object.freeze(obj)
}
export function selectFrom<T, K extends keyof T>(obj: T, ...props: K[]): { [P in K]: T[P] } {
return props.map(p => [p, obj[p]] as const).reduce((a, [k, v]) => ((a[k] = v), a), {} as { [P in K]: T[P] })
}
export function isNonNullable<T>(obj: T | void): obj is NonNullable<T> {
return obj !== undefined && obj !== null
}
export function isKeyOf<T extends object>(key: PropertyKey, obj: T): key is keyof T {
return key in obj
}
export function hasKey<T extends object, K extends PropertyKey>(obj: T, key: K): obj is T & { [P in K]: unknown } {
return isKeyOf(key, obj)
}
/**
* Stricter form of {@link Object.keys} that gives slightly better types for object literals.
*/
export function keys<T extends Record<string, any>>(obj: T): [keyof T & string] {
return Object.keys(obj) as [keyof T & string]
}
/**
* Stricter form of {@link Object.entries} that gives slightly better types for object literals.
*/
export function entries<T extends Record<string, U>, U>(obj: T): { [P in keyof T]: [P, T[P]] }[keyof T][] {
return Object.entries(obj) as { [P in keyof T]: [P, T[P]] }[keyof T][]
}
export function isThenable<T>(obj: unknown): obj is Thenable<T> {
return isNonNullable(obj) && typeof (obj as Thenable<T>).then === 'function'
}
export type ConstantMap<K, V> = Omit<ReadonlyMap<K, V>, 'get' | 'has'> & {
get(key: K): V
get(key: any): V | undefined
has(key: any): key is K
}
export function createConstantMap<T extends PropertyKey, U extends PropertyKey>(obj: {
readonly [P in T]: U
}): ConstantMap<T, U> {
return new Map<T, U>(Object.entries(obj) as [T, U][]) as unknown as ConstantMap<T, U>
}
export function createFactoryFunction<T extends new (...args: any[]) => any>(ctor: T): FactoryFunction<T> {
return (...args) => new ctor(...args)
}
type NoSymbols<T> = { [Property in keyof T]: Property extends symbol ? never : Property }[keyof T]
export type InterfaceNoSymbol<T> = Pick<T, NoSymbols<T>>
/**
* Narrows a type from the public keys of class T, which is _semantically_ an interface (and behaviorly, in almost all cases).
*
* Note: TypeScript types are purely structural so externally the result looks the same as
* one declared with the `interface` keyword. The only difference from a literal `interface`
* is that it cannot be re-declared for extension.
*/
export type ClassToInterfaceType<T> = Pick<T, keyof T>
type Expand<T> = T extends infer O ? { [K in keyof O]+?: O[K] } : never
/**
* Forces a type to be resolved into its literal types.
*
* Normally imported types are left 'as-is' and are unable to be mapped. This alias uses
* type inference to effectively generate a type literal of the target type.
*
*/
export type ExpandWithObject<T> = Expand<T> extends Record<string, unknown> ? Expand<T> : never
/**
* Given two types, this yields a type that includes only the fields where both types were the exact same
* This is _almost_ equivalent to (T1 | T2) & T2 & T2, except that this type is distributive
*/
export type SharedTypes<T1, T2> = {
[P in keyof T1 & keyof T2]: (T1[P] | T2[P]) & T1[P] & T2[P]
}
/* All of the string keys of the shared type */
export type SharedProp<T1, T2> = string & keyof SharedTypes<T1, T2>
/* Any key that can be accumulated (i.e. an array) */
export type AccumulableKeys<T> = NonNullable<
{
[P in keyof T]: NonNullable<T[P]> extends any[] ? P : never
}[keyof T]
>
/** Similar to the nullish coalescing operator, but for types that can never occur */
export type Coalesce<T, U> = [T] extends [never] ? U : T
/** Makes all keys `K` of `T` non-nullable */
export type RequiredProps<T, K extends keyof T> = T & { [P in K]-?: NonNullable<T[P]> }
/** Analagous to shifting an array but for tuples */
export type Shift<T extends any[]> = T extends [infer _, ...infer U] ? U : []
/** Transforms a type into a mutable version */
export type Mutable<T> = { -readonly [P in keyof T]: T[P] }
export type FactoryFunction<T extends abstract new (...args: any[]) => any> = (
...args: ConstructorParameters<T>
) => InstanceType<T>
/** Can be used to isolate all number fields of a record `T` */
export type NumericKeys<T> = { [P in keyof T]-?: T[P] extends number | undefined ? P : never }[keyof T]