diff --git a/package.json b/package.json index 4fe9c55..04c08ce 100644 --- a/package.json +++ b/package.json @@ -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, @@ -16,7 +16,7 @@ "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" @@ -24,6 +24,7 @@ "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", diff --git a/src/util/Paths.ts b/src/util/Paths.ts index dd70831..56e10aa 100644 --- a/src/util/Paths.ts +++ b/src/util/Paths.ts @@ -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 = _Paths<{ "": A }, Op>; +export type Paths = _Paths; -type _Paths = true extends IsRecord - ? _Paths, Op, Acc | Extract> +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 + ? _Paths< + keyof A extends never ? unknown : BubbleUp, + NewRec, + Op, + Acc | Extract, + [...It, unknown] + > : Acc; -type BubbleUp> = UnionToIntersection>>; +export type BubbleUp, Recursed> = UnionToIntersection>>; -type _BubbleUp> = { - [K in keyof A]-?: Match< - A[K], - { - nullable: Record<`${Extract}?`, NonNullable>; - struct: { - [K2 in keyof A[K] as `${Extract}${Extract extends "" ? "" : "."}${EscapeSpecialChars< - Extract - >}`]: A[K][K2]; - }; - tuple: { - [K2 in TupleKeyof as `${Extract}${Extract extends "" ? "" : "."}[${Extract< - K2, - string - >}]`]: A[K][K2]; - }; - record: Record< - `${Extract}${Extract extends "" ? "" : "."}${"[string]" | "{}>"}`, - A[K][string] - >; - array: Record< - `${Extract}${Extract extends "" ? "" : "."}${"[number]" | "[]>"}`, - A[K][number] - >; - option: Record< - `${Extract}${Extract extends "" ? "" : "."}?some`, - Extract>["value"] +export type _BubbleUp, Recursed> = { + [K in keyof A]-?: true extends B_extends_A> + ? never + : Match< + A[K], + { + nullable: { [K1 in `${Extract}?`]: NonNullable }; + struct: { + [K2 in keyof A[K] as `${Extract}${Extract extends "" ? "" : "."}${EscapeSpecialChars< + Extract + >}`]: A[K][K2]; + }; + tuple: { + [K2 in TupleKeyof as `${Extract}${Extract extends "" ? "" : "."}[${Extract< + K2, + string + >}]`]: A[K][K2]; + }; + record: Record< + `${Extract}${Extract extends "" ? "" : "."}${"[string]" | "{}>"}`, + A[K][string] + >; + array: Record< + `${Extract}${Extract extends "" ? "" : "."}${"[number]" | "[]>"}`, + A[K][number] + >; + option: Record< + `${Extract}${Extract extends "" ? "" : "."}?some`, + Extract>["value"] + >; + either: Record< + `${Extract}${Extract extends "" ? "" : "."}?left`, + Extract>["left"] + > & + Record< + `${Extract}${Extract extends "" ? "" : "."}?right`, + Extract>["right"] + >; + sum: BubbleSum>; + other: never; + } >; - either: Record< - `${Extract}${Extract extends "" ? "" : "."}?left`, - Extract>["left"] - > & - Record< - `${Extract}${Extract extends "" ? "" : "."}?right`, - Extract>["right"] - >; - sum: BubbleSum>; - other: never; - } - >; }; type BubbleSum< @@ -84,7 +105,13 @@ type Match< sum: unknown; other: unknown; } -> = [A] extends [never] +> = unknown extends A + ? Matches["other"] + : IsAny extends true + ? Matches["other"] + : [A] extends [never] + ? Matches["other"] + : A extends (...a: any[]) => unknown ? Matches["other"] : true extends IsNull ? Matches["nullable"] @@ -106,6 +133,6 @@ type Match< // Credit to Stefan Baumgartner // https://fettblog.eu/typescript-union-to-intersection/ -type UnionToIntersection = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R : never; +export type UnionToIntersection = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R : never; -type ValueOf = A[keyof A]; +export type ValueOf = A[keyof A]; diff --git a/src/util/pathRecursion.ts b/src/util/pathRecursion.ts new file mode 100644 index 0000000..974ebe8 --- /dev/null +++ b/src/util/pathRecursion.ts @@ -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 = unknown extends A + ? never + : IsAny extends true + ? never + : [A] extends [never] + ? never + : A extends (...a: any[]) => unknown + ? never + : true extends IsNull + ? never + : [A] extends [Option] + ? never + : [A] extends [Either] + ? never + : [A] extends [readonly unknown[]] + ? TupleKeyof extends never + ? never + : true + : string extends keyof A + ? never + : true extends IsRecord + ? true + : never; + +export type GetParentInterfaces = ValueOf<{ + [K in keyof AllParents as Key extends `${Extract}${string}` ? K : never]: AllParents[K]; +}>; + +export type NewRec = { + [K in keyof A]-?: GetParentInterfaces | (true extends CanRecurse ? A[K] : never); +}; + +export type AllKeys = A extends unknown ? keyof A : never; + +export type PossiblyExtendible = B extends unknown ? (keyof A extends keyof B ? B : never) : never; + +export type UnPartial = { + [K in keyof Required]: Required[K] | Extract]>; +}; + +// check whether B extends A without putting A in the check type +export type B_extends_A = true extends CanRecurse + ? A extends B + ? true + : { + [K in AllKeys>]: K extends keyof A ? A[K] : any; + } extends UnPartial> + ? true + : never + : never; + +export type ValueOf = A[keyof A]; diff --git a/src/util/predicates.ts b/src/util/predicates.ts index 60ea53b..e78673c 100644 --- a/src/util/predicates.ts +++ b/src/util/predicates.ts @@ -58,9 +58,13 @@ type HasTraversals = Args extends [infer First, ...infer : HasTraversals : never; +// credit to Joe Calzaretta +// https://stackoverflow.com/a/49928360 +export type IsAny = (A extends never ? true : false) extends false ? false : true; + export type IsNull = Extract extends never ? never : true; -export type IsRecord = unknown extends A ? never : [A] extends [Record] ? true : never; +export type IsRecord = [A] extends [Record] ? true : never; export type IsNonTupleArray = [A] extends [readonly unknown[]] ? TupleKeyof extends never diff --git a/src/util/scratch.ts b/src/util/scratch.ts new file mode 100644 index 0000000..f0618d3 --- /dev/null +++ b/src/util/scratch.ts @@ -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; + +interface Rec { + a: Rec; +} + +interface Rec2 extends Rec { + b: Rec; +} + +declare const rec: Rec2; + +rec.a.a.a.a; + +type r0 = _Paths, []>; + +type bup1 = _BubbleUp; + +type bup2 = B_extends_A; +type bup3 = { + [K in AllKeys>]: K extends keyof Rec[] ? Rec[][K] : any; +}; +type bup4 = UnPartial>; + +type r1 = _Paths, "static", Extract>; +// +// +// +// +// +// +// +// +// +// + +/* type z0 = B_extends_A; +type z1 = B_extends_A<{ a?: number }, Rec2>; +type z2 = z0 extends UnPartial ? 1 : 0; +type z3 = z1 extends UnPartial ? 1 : 0; */ +type z96 = { a: number | undefined } extends { a?: Rec } ? 1 : 0; + +type p0 = ` ${keyof HTMLInputElement}`; +//type b0 = Paths; +//type b1 = Paths; +//type b2 = Paths; +//type b3 = Paths; +// type b4 = Paths; +//type b5 = Paths; +//type b24 = Paths<{ ownerDocument: Document }>; +// type b25 = Paths; +// type b6 = Paths; +// type b7 = Paths; +// declare const b3: b2; +declare const doc: Document; + +// b3 === "ownerDocument.documentElement"; + +/* const aaa = b3.Recursed["ownerDocument.activeElement?"]; */ +/*type b5 = Paths; + + + +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; + +type a1 = Paths; +declare const a2: a1; + +interface Document2 { + readonly documentElement: HTMLElement; + // readonly head: HTMLHeadElement; + // readonly embeds: HTMLCollectionOf; + // readonly forms: HTMLCollectionOf; + // readonly images: HTMLCollectionOf; + // readonly doctype: DocumentType | null; +} + +// type b27 = Paths<{ ownerDocument: Document2 }>; +type b28 = Paths; diff --git a/yarn.lock b/yarn.lock index f22b89f..2a34a4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -778,11 +778,30 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.3.2.tgz#fc8c2825e4ed2142473b4a81064e6e081463d1b3" integrity sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog== +"@types/prop-types@*": + version "15.7.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" + integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== + "@types/qs@^6.2.31": version "6.9.7" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== +"@types/react@^18.0.1": + version "18.0.1" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.1.tgz#1b2e02fb7613212518733946e49fb963dfc66e19" + integrity sha512-VnWlrVgG0dYt+NqlfMI0yUYb8Rdl4XUROyH+c6gq/iFCiZ805Vi//26UW38DHnxQkbDhnrIWTBiy6oKZqL11cw== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -1610,6 +1629,11 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" +csstype@^3.0.2: + version "3.0.11" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33" + integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw== + data-urls@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b"