Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
extract utility-types.js and convert FlowFixMe to any, fixes #200
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinbarabash committed Mar 7, 2021
1 parent bb1762d commit 8d789d8
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 86 deletions.
91 changes: 8 additions & 83 deletions src/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ const path = require("path");
const t = require("../babel-types/lib/index.js");

const declare = require("./transforms/declare.js");
const react = require("./transforms/react.js");
const reactTypes = require("./transforms/react-types.js");
const objectType = require("./transforms/object-type.js");
const utilityTypes = require("./transforms/utility-types.js");

const { trackComments } = require("./util.js");

Expand All @@ -30,69 +31,6 @@ const transformFunction = (path) => {
}
};

// TODO: figure out how to template these inline definitions
const utilityTypes = {
$Keys: (T) => {
// $Keys<T> -> keyof T
// TODO: patch @babel/types - tsTypeOperator should accept two arguments
// return t.tsTypeOperator(typeAnnotation, "keyof");
return {
type: "TSTypeOperator",
typeAnnotation: T,
operator: "keyof",
};
},
$Values: (T) => {
// $Keys<T> -> T[keyof T]
return t.tsIndexedAccessType(
T,
{
type: "TSTypeOperator",
typeAnnotation: T,
operator: "keyof",
}
// TODO: patch @babel/types - tsTypeOperator should accept two arguments
//t.tsTypeOperator(typeAnnotation, "keyof"),
);
},
$ReadOnly: (T) => {
// $ReadOnly<T> -> Readonly<T>
const typeName = t.identifier("Readonly");
const typeParameters = t.tsTypeParameterInstantiation([T]);
return t.tsTypeReference(typeName, typeParameters);
},
$Shape: (T) => {
// $Shape<T> -> Partial<T>
const typeName = t.identifier("Partial");
const typeParameters = t.tsTypeParameterInstantiation([T]);
return t.tsTypeReference(typeName, typeParameters);
},
$NonMaybeType: (T) => {
// $NonMaybeType<T> -> NonNullable<T>
const typeName = t.identifier("NonNullable");
const typeParameters = t.tsTypeParameterInstantiation([T]);
return t.tsTypeReference(typeName, typeParameters);
},
$Exact: (T) => {
// $Exact<T> -> T
return T;
},
$PropertyType: (T, name) => {
// $PropertyType<T, "name"> -> T["name"]
return t.tsIndexedAccessType(T, name);
},
Class: null, // TODO

// These are too complicated to inline so we'll leave them as imports
$Diff: null,
$ElementType: null,
$Call: null,

// The behavior of $Rest only differs when exact object types are involved.
// And since TypeScript doesn't have exact object types using $Diff is okay.
$Rest: "$Diff",
};

const transform = {
Program: {
enter(path, state) {
Expand Down Expand Up @@ -472,28 +410,15 @@ const transform = {
return;
}

if (typeName.name in utilityTypes) {
if (
(state.options.inlineUtilityTypes &&
typeof utilityTypes[typeName.name] === "function") ||
typeName.name === "$Exact" // $Exact doesn't exist in utility-types so we always inline it.
) {
const inline = utilityTypes[typeName.name];
path.replaceWith(inline(...typeParameters.params));
} else if (typeof utilityTypes[typeName.name] === "string") {
const replacementName = utilityTypes[typeName.name];
path.replaceWith(
t.tsTypeReference(t.identifier(replacementName), typeParameters)
);
state.usedUtilityTypes.add(replacementName);
} else {
state.usedUtilityTypes.add(typeName.name);
}
let replacement;

replacement = utilityTypes.GenericTypeAnnotation.exit(path, state);
if (replacement) {
path.replaceWith(replacement);
return;
}

const replacement = react.GenericTypeAnnotation.exit(path, state);
replacement = reactTypes.GenericTypeAnnotation.exit(path, state);
if (replacement) {
path.replaceWith(replacement);
return;
Expand All @@ -509,7 +434,7 @@ const transform = {
const left = qualification;
const right = id;

const replacement = react.QualifiedTypeIdentifier.exit(path, state);
const replacement = reactTypes.QualifiedTypeIdentifier.exit(path, state);
if (replacement) {
path.replaceWith(replacement);
return;
Expand Down
2 changes: 1 addition & 1 deletion src/transforms/declare.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const t = require("../../babel-types/lib/index.js");
const {trackComments} = require("../util.js");
const { trackComments } = require("../util.js");

exports.DeclareVariable = {
exit(path) {
Expand Down
4 changes: 2 additions & 2 deletions src/transforms/object-type.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const t = require("../../babel-types/lib/index.js");
const {trackComments} = require("../util.js");
const { trackComments } = require("../util.js");

exports.ObjectTypeAnnotation = {
enter(path, state) {
Expand Down Expand Up @@ -126,7 +126,7 @@ exports.QualifiedTypeIdentifier = {
path.replaceWith(replacement);
return;
}

// fallthrough case
path.replaceWith(t.tsQualifiedName(left, right));
},
Expand Down
File renamed without changes.
93 changes: 93 additions & 0 deletions src/transforms/utility-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
const t = require("../../babel-types/lib/index.js");

// TODO: figure out how to template these inline definitions
const utilityTypes = {
$Keys: (T) => {
// $Keys<T> -> keyof T
// TODO: patch @babel/types - tsTypeOperator should accept two arguments
// return t.tsTypeOperator(typeAnnotation, "keyof");
return {
type: "TSTypeOperator",
typeAnnotation: T,
operator: "keyof",
};
},
$Values: (T) => {
// $Keys<T> -> T[keyof T]
return t.tsIndexedAccessType(
T,
{
type: "TSTypeOperator",
typeAnnotation: T,
operator: "keyof",
}
// TODO: patch @babel/types - tsTypeOperator should accept two arguments
//t.tsTypeOperator(typeAnnotation, "keyof"),
);
},
$ReadOnly: (T) => {
// $ReadOnly<T> -> Readonly<T>
const typeName = t.identifier("Readonly");
const typeParameters = t.tsTypeParameterInstantiation([T]);
return t.tsTypeReference(typeName, typeParameters);
},
$Shape: (T) => {
// $Shape<T> -> Partial<T>
const typeName = t.identifier("Partial");
const typeParameters = t.tsTypeParameterInstantiation([T]);
return t.tsTypeReference(typeName, typeParameters);
},
$NonMaybeType: (T) => {
// $NonMaybeType<T> -> NonNullable<T>
const typeName = t.identifier("NonNullable");
const typeParameters = t.tsTypeParameterInstantiation([T]);
return t.tsTypeReference(typeName, typeParameters);
},
$Exact: (T) => {
// $Exact<T> -> T
return T;
},
$PropertyType: (T, name) => {
// $PropertyType<T, "name"> -> T["name"]
return t.tsIndexedAccessType(T, name);
},
$FlowFixMe: () => {
return t.tsAnyKeyword();
},
Class: null, // TODO

// These are too complicated to inline so we'll leave them as imports
$Diff: null,
$ElementType: null,
$Call: null,

// The behavior of $Rest only differs when exact object types are involved.
// And since TypeScript doesn't have exact object types using $Diff is okay.
$Rest: "$Diff",
};

exports.GenericTypeAnnotation = {
exit(path, state) {
const { id: typeName, typeParameters } = path.node;

if (typeName.name in utilityTypes) {
const value = utilityTypes[typeName.name];

if (
(typeof value === "function" && state.options.inlineUtilityTypes) ||
// $Exact and $FlowFixMe don't exist in utility-types so we always inline them.
typeName.name === "$Exact" ||
typeName.name === "$FlowFixMe"
) {
return typeParameters ? value(...typeParameters.params) : value();
}

if (typeof value === "string") {
state.usedUtilityTypes.add(value);
return t.tsTypeReference(t.identifier(value), typeParameters);
}

state.usedUtilityTypes.add(typeName.name);
}
},
};
2 changes: 2 additions & 0 deletions test/fixtures/convert/utility-types/$FlowFixMe/flow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// @flow
type A = $FlowFixMe;
1 change: 1 addition & 0 deletions test/fixtures/convert/utility-types/$FlowFixMe/ts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
type A = any;

0 comments on commit 8d789d8

Please sign in to comment.