Repository: sandersn/downlevel-dts
Pull request #15 (2020-01-23 22:01:58)

Transform Omit into the equivilant Pick type declaration
This pull request aims to add the downlevel transformation of the Omit type. I basically cobbled it together by looking at the [ts-ast-viewer](https://ts-ast-viewer.com). One thing I struggled with is how to make sure that the Omit type at hand is actually the one from TypeScript and not some other user defined type which just has the same name.

sandersn: I think you can use getSymbolAtLocation and then walk up the symbol.valueDeclaration's parent chain and see whether the SourceFile at the top is lib.d.ts. That's similar to the way that Find All References works.
chrisguttandin: Thanks for pointing me in the right direction. I made a few more changes and added a test case to make sure user defined types remain untouched.

Please let me know if I need to change anything else.

Patch:
diff --git a/index.js b/index.js
index 0d6f314..1619ce0 100644
--- a/index.js
+++ b/index.js
@@ -29,7 +29,8 @@ function main(src, target) {
   const printer = ts.createPrinter({
     newLine: ts.NewLineKind.CarriageReturnLineFeed
   });
-  for (const t of ts.transform(files, [doTransform]).transformed) {
+  for (const t of ts.transform(files, [doTransform.bind(null, checker)])
+    .transformed) {
     const f = /** @type {import("typescript").SourceFile} */ (t);
     const targetPath = path.join(target, f.fileName.slice(src.length));
     sh.mkdir("-p", path.dirname(targetPath));
@@ -44,8 +45,11 @@ if (!(/** @type {*} */ (module.parent))) {
   main(src, target);
 }
 
-/** @param {import("typescript").TransformationContext} k */
-function doTransform(k) {
+/**
+ * @param {import("typescript").TypeChecker} checker
+ * @param {import("typescript").TransformationContext} k
+ */
+function doTransform(checker, k) {
   /**
    * @param {Node} n
    * @return {import("typescript").VisitResult<Node>}
@@ -121,6 +125,30 @@ function doTransform(k) {
       );
     } else if (ts.isImportClause(n) && n.isTypeOnly) {
       return ts.createImportClause(n.name, n.namedBindings);
+    } else if (
+      ts.isTypeReferenceNode(n) &&
+      ts.isIdentifier(n.typeName) &&
+      n.typeName.escapedText === "Omit"
+    ) {
+      const symbol = checker.getSymbolAtLocation(n.typeName);
+      const typeArguments = n.typeArguments;
+
+      if (
+        symbol &&
+        symbol.declarations.length &&
+        symbol.declarations[0]
+          .getSourceFile()
+          .fileName.includes("node_modules/typescript/lib/lib") &&
+        typeArguments
+      ) {
+        return ts.createTypeReferenceNode(ts.createIdentifier("Pick"), [
+          typeArguments[0],
+          ts.createTypeReferenceNode(ts.createIdentifier("Exclude"), [
+            ts.createTypeOperatorNode(typeArguments[0]),
+            typeArguments[1]
+          ])
+        ]);
+      }
     }
     return ts.visitEachChild(n, transform, k);
   };


Test patch:
diff --git a/baselines/ts3.4/src/test.d.ts b/baselines/ts3.4/src/test.d.ts
index 4da9559..62a03dd 100644
--- a/baselines/ts3.4/src/test.d.ts
+++ b/baselines/ts3.4/src/test.d.ts
@@ -11,3 +11,9 @@ export namespace N {
     }
 }
 export { C as DetectiveComics };
+export type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
+export interface E {
+    a: number;
+    b: number;
+}
+export type F = Omit<E, 'a'>;
diff --git a/baselines/ts3.4/test.d.ts b/baselines/ts3.4/test.d.ts
index 7e1711c..ffc1bcc 100644
--- a/baselines/ts3.4/test.d.ts
+++ b/baselines/ts3.4/test.d.ts
@@ -16,3 +16,8 @@ export namespace N {
 import { C as CD } from "./src/test";
 import * as rex_1 from "./src/test";
 export { rex_1 as rex } from "./src/test";
+export interface E {
+    a: number;
+    b: number;
+}
+export type F = Pick<E, Exclude<keyof E, 'a'>>;
diff --git a/test/src/test.d.ts b/test/src/test.d.ts
index 93c1def..efc39b4 100644
--- a/test/src/test.d.ts
+++ b/test/src/test.d.ts
@@ -14,3 +14,12 @@ export namespace N {
 }
 
 export type { C as DetectiveComics };
+
+export type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
+
+export interface E {
+  a: number;
+  b: number;
+}
+
+export type F = Omit<E, 'a'>;
\ No newline at end of file
diff --git a/test/test.d.ts b/test/test.d.ts
index 17a6fbb..5dbe13e 100644
--- a/test/test.d.ts
+++ b/test/test.d.ts
@@ -18,3 +18,10 @@ export namespace N {
 import type { C as CD } from "./src/test";
 
 export * as rex from "./src/test";
+
+export interface E {
+  a: number;
+  b: number;
+}
+
+export type F = Omit<E, 'a'>;