Skip to content
Draft
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
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "spectacles-ts",
"version": "1.0.6",
"version": "1.0.7",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"sideEffects": false,
Expand All @@ -16,14 +16,15 @@
"watch:tsc": "yarn run tsc -w -p tsconfig.build.json",
"watch:tsd": "nodemon --watch './**/*.ts' --ignore './dist' --exec 'ts-node' --transpile-only scripts/run-type-tests.ts",
"fix:lint": "yarn run eslint . --fix",
"prepublishOnly": "yarn fix:lint && yarn test"
"prepublishOnly": "yarn fix:lint && yarn build && yarn test"
},
"dependencies": {
"monocle-ts": "^2.3.5"
},
"devDependencies": {
"@types/jest": "^26.0.20",
"@types/node": "^14.14.37",
"@types/react": "^18.0.1",
"@typescript-eslint/eslint-plugin": "^5.16.0",
"@typescript-eslint/parser": "^5.16.0",
"assert": "^2.0.0",
Expand Down
121 changes: 74 additions & 47 deletions src/util/Paths.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,80 @@
import type { Option, Some } from "fp-ts/Option";
import type { Either, Left, Right } from "fp-ts/Either";
import type { IsNull, IsRecord, TupleKeyof } from "./predicates";
import type { IsAny, IsNull, IsRecord, TupleKeyof } from "./predicates";
import type { EscapeSpecialChars } from "./segments";
import type { Cases, Discriminant } from "./sum";
import { B_extends_A, GetParentInterfaces, NewRec } from "./pathRecursion";

type Operation = "static" | "dynamic" | "upsert";

export type Paths<A, Op extends Operation = "static"> = _Paths<{ "": A }, Op>;
export type Paths<A, Op extends Operation = "static"> = _Paths<A, { "": A }, Op, never>;

type _Paths<A, Op extends Operation, Acc extends string = never> = true extends IsRecord<A>
? _Paths<keyof A extends never ? unknown : BubbleUp<A>, Op, Acc | Extract<keyof A, string>>
export type _Paths<
A,
Recursed,
Op extends Operation = "static",
Acc extends string = never,
It extends unknown[] = []
> = /* It["length"] extends 3
? {
A: A;
Recursed: Recursed;
Acc: Acc;
}
: */ true extends IsRecord<A>
? _Paths<
keyof A extends never ? unknown : BubbleUp<A, Recursed>,
NewRec<Recursed, A>,
Op,
Acc | Extract<keyof A, string>,
[...It, unknown]
>
: Acc;

type BubbleUp<A extends Record<string, any>> = UnionToIntersection<ValueOf<_BubbleUp<A>>>;
export type BubbleUp<A extends Record<string, any>, Recursed> = UnionToIntersection<ValueOf<_BubbleUp<A, Recursed>>>;

type _BubbleUp<A extends Record<string, any>> = {
[K in keyof A]-?: Match<
A[K],
{
nullable: Record<`${Extract<K, string>}?`, NonNullable<A[K]>>;
struct: {
[K2 in keyof A[K] as `${Extract<K, string>}${Extract<K, string> extends "" ? "" : "."}${EscapeSpecialChars<
Extract<K2, string>
>}`]: A[K][K2];
};
tuple: {
[K2 in TupleKeyof<A[K]> as `${Extract<K, string>}${Extract<K, string> extends "" ? "" : "."}[${Extract<
K2,
string
>}]`]: A[K][K2];
};
record: Record<
`${Extract<K, string>}${Extract<K, string> extends "" ? "" : "."}${"[string]" | "{}>"}`,
A[K][string]
>;
array: Record<
`${Extract<K, string>}${Extract<K, string> extends "" ? "" : "."}${"[number]" | "[]>"}`,
A[K][number]
>;
option: Record<
`${Extract<K, string>}${Extract<K, string> extends "" ? "" : "."}?some`,
Extract<A[K], Some<unknown>>["value"]
export type _BubbleUp<A extends Record<string, any>, Recursed> = {
[K in keyof A]-?: true extends B_extends_A<A[K], GetParentInterfaces<K, Recursed>>
? never
: Match<
A[K],
{
nullable: { [K1 in `${Extract<K, string>}?`]: NonNullable<A[K]> };
struct: {
[K2 in keyof A[K] as `${Extract<K, string>}${Extract<K, string> extends "" ? "" : "."}${EscapeSpecialChars<
Extract<K2, string>
>}`]: A[K][K2];
};
tuple: {
[K2 in TupleKeyof<A[K]> as `${Extract<K, string>}${Extract<K, string> extends "" ? "" : "."}[${Extract<
K2,
string
>}]`]: A[K][K2];
};
record: Record<
`${Extract<K, string>}${Extract<K, string> extends "" ? "" : "."}${"[string]" | "{}>"}`,
A[K][string]
>;
array: Record<
`${Extract<K, string>}${Extract<K, string> extends "" ? "" : "."}${"[number]" | "[]>"}`,
A[K][number]
>;
option: Record<
`${Extract<K, string>}${Extract<K, string> extends "" ? "" : "."}?some`,
Extract<A[K], Some<unknown>>["value"]
>;
either: Record<
`${Extract<K, string>}${Extract<K, string> extends "" ? "" : "."}?left`,
Extract<A[K], Left<unknown>>["left"]
> &
Record<
`${Extract<K, string>}${Extract<K, string> extends "" ? "" : "."}?right`,
Extract<A[K], Right<unknown>>["right"]
>;
sum: BubbleSum<A[K], Extract<K, string>>;
other: never;
}
>;
either: Record<
`${Extract<K, string>}${Extract<K, string> extends "" ? "" : "."}?left`,
Extract<A[K], Left<unknown>>["left"]
> &
Record<
`${Extract<K, string>}${Extract<K, string> extends "" ? "" : "."}?right`,
Extract<A[K], Right<unknown>>["right"]
>;
sum: BubbleSum<A[K], Extract<K, string>>;
other: never;
}
>;
};

type BubbleSum<
Expand Down Expand Up @@ -84,7 +105,13 @@ type Match<
sum: unknown;
other: unknown;
}
> = [A] extends [never]
> = unknown extends A
? Matches["other"]
: IsAny<A> extends true
? Matches["other"]
: [A] extends [never]
? Matches["other"]
: A extends (...a: any[]) => unknown
? Matches["other"]
: true extends IsNull<A>
? Matches["nullable"]
Expand All @@ -106,6 +133,6 @@ type Match<

// Credit to Stefan Baumgartner
// https://fettblog.eu/typescript-union-to-intersection/
type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R : never;
export type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R : never;

type ValueOf<A> = A[keyof A];
export type ValueOf<A> = A[keyof A];
56 changes: 56 additions & 0 deletions src/util/pathRecursion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { Option } from "fp-ts/Option";
import type { Either } from "fp-ts/Either";
import type { IsAny, IsNull, IsRecord, TupleKeyof } from "./predicates";

export type CanRecurse<A> = unknown extends A
? never
: IsAny<A> extends true
? never
: [A] extends [never]
? never
: A extends (...a: any[]) => unknown
? never
: true extends IsNull<A>
? never
: [A] extends [Option<unknown>]
? never
: [A] extends [Either<unknown, unknown>]
? never
: [A] extends [readonly unknown[]]
? TupleKeyof<A> extends never
? never
: true
: string extends keyof A
? never
: true extends IsRecord<A>
? true
: never;

export type GetParentInterfaces<Key, AllParents> = ValueOf<{
[K in keyof AllParents as Key extends `${Extract<K, string>}${string}` ? K : never]: AllParents[K];
}>;

export type NewRec<OldRec, A> = {
[K in keyof A]-?: GetParentInterfaces<K, OldRec> | (true extends CanRecurse<A[K]> ? A[K] : never);
};

export type AllKeys<A> = A extends unknown ? keyof A : never;

export type PossiblyExtendible<A, B> = B extends unknown ? (keyof A extends keyof B ? B : never) : never;

export type UnPartial<A> = {
[K in keyof Required<A>]: Required<A>[K] | Extract<undefined, A[Extract<K, keyof A>]>;
};

// check whether B extends A without putting A in the check type
export type B_extends_A<A, B> = true extends CanRecurse<A>
? A extends B
? true
: {
[K in AllKeys<PossiblyExtendible<A, B>>]: K extends keyof A ? A[K] : any;
} extends UnPartial<PossiblyExtendible<A, B>>
? true
: never
: never;

export type ValueOf<A> = A[keyof A];
6 changes: 5 additions & 1 deletion src/util/predicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,13 @@ type HasTraversals<Args extends unknown[]> = Args extends [infer First, ...infer
: HasTraversals<Tail>
: never;

// credit to Joe Calzaretta
// https://stackoverflow.com/a/49928360
export type IsAny<A> = (A extends never ? true : false) extends false ? false : true;

export type IsNull<A> = Extract<A, undefined | null> extends never ? never : true;

export type IsRecord<A> = unknown extends A ? never : [A] extends [Record<string, any>] ? true : never;
export type IsRecord<A> = [A] extends [Record<string, any>] ? true : never;

export type IsNonTupleArray<A> = [A] extends [readonly unknown[]]
? TupleKeyof<A> extends never
Expand Down
121 changes: 121 additions & 0 deletions src/util/scratch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { Paths, _Paths, BubbleUp, _BubbleUp, UnionToIntersection, ValueOf } from "./Paths";
import { AllKeys, B_extends_A, PossiblyExtendible, UnPartial, GetParentInterfaces, NewRec } from "./pathRecursion";

export type Element2 = {
readonly ownerDocument: Document;
};

type b26 = Paths<{ ownerDocument: Document }>;

// declare const yyy: b26["Recursed"];

// const a = yyy["ownerDocument.ownerDocument?"];
type ab1 = B_extends_A<
Document,
{
ownerDocument: Document;
}
>;

type a = Paths<{ a: { b: { c: number } }; x: { c: number } }>;
type b = Paths<Rec>;

interface Rec {
a: Rec;
}

interface Rec2 extends Rec {
b: Rec;
}

declare const rec: Rec2;

rec.a.a.a.a;

type r0 = _Paths<Rec, { "": Rec }, "static", Extract<keyof Rec, string>, []>;

type bup1 = _BubbleUp<Rec, { "": Rec }>;

type bup2 = B_extends_A<Rec[], Rec>;
type bup3 = {
[K in AllKeys<PossiblyExtendible<Rec[], Rec>>]: K extends keyof Rec[] ? Rec[][K] : any;
};
type bup4 = UnPartial<PossiblyExtendible<Rec[], Rec>>;

type r1 = _Paths<bup1, NewRec<{ "": Rec }, Rec>, "static", Extract<keyof Rec, string>>;
//
//
//
//
//
//
//
//
//
//

/* type z0 = B_extends_A<Rec, Rec2>;
type z1 = B_extends_A<{ a?: number }, Rec2>;
type z2 = z0 extends UnPartial<Rec2> ? 1 : 0;
type z3 = z1 extends UnPartial<Rec2> ? 1 : 0; */
type z96 = { a: number | undefined } extends { a?: Rec } ? 1 : 0;

type p0 = ` ${keyof HTMLInputElement}`;
//type b0 = Paths<ChildNode>;
//type b1 = Paths<ParentNode>;
//type b2 = Paths<Element>;
//type b3 = Paths<HTMLElement>;
// type b4 = Paths<HTMLElement & { a: number }>;
//type b5 = Paths<HTMLInputElement>;
//type b24 = Paths<{ ownerDocument: Document }>;
// type b25 = Paths<Element2 & { a: number }>;
// type b6 = Paths<Window>;
// type b7 = Paths<Document>;
// declare const b3: b2;
declare const doc: Document;

// b3 === "ownerDocument.documentElement";

/* const aaa = b3.Recursed["ownerDocument.activeElement?"]; */
/*type b5 = Paths<Document>;



const aaa = b5.Recursed["anchors"];*/

/*
Obj1 = { a: { b: number, x: boolean }, c: { d: { e: string } } }
Output1 = keyof Obj1 = 'a' | 'c'
Rec1 = { a: { b: number, x: boolean }, c: { d: { e: string } } }
|
V
Obj2 = { 'a.b': number, 'a.x': boolean, 'c.d': { e: string } }
Output2 = Output1 | keyof Obj2 = 'a' | 'c' | 'a.b' | 'a.x' | 'c.d'
Rec2 = { 'a.b': { b: number, x: boolean }, 'c.d': { d: { e: string } } }
|
V
Obj3 = { 'c.d.e': string }
Output3 = Output2 | keyof Obj3 = 'a' | 'c' | 'a.b' | 'a.x' | 'c.d' | 'c.d.e'
Rec3 = { 'c.d.e': { d: { e: string } } | { e: string } | string }
*/

type rec = { "a.b": { b: number; x: boolean }; "c.d": { d: { e: string } } };

type parens = GetParentInterfaces<"c.d.e", rec>;

type newrec = NewRec<rec, { "c.d.e": string }>;

type a1 = Paths<Rec>;
declare const a2: a1;

interface Document2 {
readonly documentElement: HTMLElement;
// readonly head: HTMLHeadElement;
// readonly embeds: HTMLCollectionOf<HTMLEmbedElement>;
// readonly forms: HTMLCollectionOf<HTMLFormElement>;
// readonly images: HTMLCollectionOf<HTMLImageElement>;
// readonly doctype: DocumentType | null;
}

// type b27 = Paths<{ ownerDocument: Document2 }>;
type b28 = Paths<Document2>;
Loading