-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Exposed and used TypeScript's internal isTypeAssignableTo for type co…
…mparisons Removes the clunky homegrown type checking logic and replaces it with the method within the type checker. The method is exposed on the tc by manually rewriting TypeScript's `tsc.js`. Very dirty. My next commit will make it check the first few hundred chars of the file for a `/* TypeStat! */` comment so it doesn't have to re-read that whole file constantly.
- Loading branch information
Josh Goldberg
committed
Feb 11, 2019
1 parent
7e554e5
commit 0129b6d
Showing
15 changed files
with
157 additions
and
248 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/* | ||
This file is a ridiculous, disgusting hack and should not be considered acceptable code to write. | ||
Shame on you for even reading this header, let alone continuing down. | ||
TypeScript uses a function called "isTypeAssignableTo" internally that checks whether something is "assignable" to another. | ||
For example, `5` would be considered assignable to `5`, `number`, `number | string`, `any`, and so on. | ||
The `isTypeAssignableTo` function is not exposed externally. | ||
https://github.com/Microsoft/TypeScript/issues/9879 tracks work to do that properly. | ||
In the meantime, TypeStat needs to use that method, so we do the following: | ||
1. Cry internally | ||
2. Modify the TypeScript file given by `require.resolve` to add `isTypeAssignableTo` to created type checkers | ||
3. Cry some more | ||
Yes, you read that second step correct. | ||
This file finds `typescript.js` on disk and writes a change to expose the function. | ||
💩. | ||
*/ | ||
|
||
import { fs } from "mz"; | ||
import { Type } from "typescript"; | ||
|
||
/* tslint:disable no-dynamic-delete no-unsafe-any no-require-imports */ | ||
|
||
type ArgumentTypes<TFunction> = TFunction extends (...args: infer TArgs) => any ? TArgs : never; | ||
|
||
type ReplaceReturnType<TOriginalType, TReturnType> = (...args: ArgumentTypes<TOriginalType>) => TReturnType; | ||
|
||
type Replace<TOriginalType, TReplacements extends any> = { | ||
[Property in keyof TOriginalType]: Property extends keyof TReplacements ? TReplacements[Property] : TOriginalType[Property] | ||
}; | ||
|
||
export type ExposedTypeScript = Replace< | ||
typeof import("typescript"), | ||
{ | ||
createProgram: ReplaceReturnType<(typeof import("typescript"))["createProgram"], ExposedProgram>; | ||
} | ||
>; | ||
|
||
export type ExposedProgram = Replace< | ||
import("typescript").Program, | ||
{ | ||
getTypeChecker: ReplaceReturnType<import("typescript").Program["getTypeChecker"], ExposedTypeChecker>; | ||
} | ||
>; | ||
|
||
export type ExposedTypeChecker = import("typescript").TypeChecker & { | ||
isTypeAssignableTo(source: Type, target: Type): boolean; | ||
}; | ||
|
||
export const requireExposedTypeScript = (): ExposedTypeScript => { | ||
// Find where the file should be required from | ||
const localRequireFile = require.resolve("typescript"); | ||
const originalContent = fs.readFileSync(localRequireFile).toString(); | ||
|
||
// Save and clear any existing "typescript" module from the require cache | ||
const originalLocalRequireFile = require.cache[localRequireFile]; | ||
delete require.cache[localRequireFile]; | ||
|
||
// Write an export blurb to add `isTypeAssignableTo` to created `checker`s | ||
const exposedContent = originalContent.replace("var checker = {", "var checker = { /* TypeStat! */ isTypeAssignableTo,"); | ||
fs.writeFileSync(localRequireFile, exposedContent); | ||
|
||
// Require this new TypeScript that exposes `isTypeAssignableTo` | ||
const exposedTypeScript = require(localRequireFile); | ||
|
||
// Add back whatever existing module was cached, and reset file contents | ||
delete require.cache[localRequireFile]; | ||
fs.writeFileSync(localRequireFile, originalContent); | ||
require.cache[localRequireFile] = originalLocalRequireFile; | ||
|
||
// Return the exposed version of TypeScript, and never speak of this again | ||
return exposedTypeScript; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.