diff --git a/packages/babel-traverse/src/path/inference/inferer-reference.js b/packages/babel-traverse/src/path/inference/inferer-reference.js index 0af6f1eedb04..94505566e684 100644 --- a/packages/babel-traverse/src/path/inference/inferer-reference.js +++ b/packages/babel-traverse/src/path/inference/inferer-reference.js @@ -91,9 +91,15 @@ function getTypeAnnotationBindingConstantViolations(binding, path, name) { } } - if (types.length) { - return t.createUnionTypeAnnotation(types); + if (!types.length) { + return; + } + + if (t.isTSTypeAnnotation(types[0]) && t.createTSUnionType) { + return t.createTSUnionType(types); } + + return t.createUnionTypeAnnotation(types); } function getConstantViolationsBefore(binding, path, functions) { @@ -201,6 +207,13 @@ function getConditionalAnnotation(binding, path, name) { } if (types.length) { + if (t.isTSTypeAnnotation(types[0]) && t.createTSUnionType) { + return { + typeAnnotation: t.createTSUnionType(types), + ifStatement, + }; + } + return { typeAnnotation: t.createUnionTypeAnnotation(types), ifStatement, diff --git a/packages/babel-traverse/src/path/inference/inferers.js b/packages/babel-traverse/src/path/inference/inferers.js index fb9a467b1f64..876516725557 100644 --- a/packages/babel-traverse/src/path/inference/inferers.js +++ b/packages/babel-traverse/src/path/inference/inferers.js @@ -83,17 +83,29 @@ export function BinaryExpression(node) { } export function LogicalExpression() { - return t.createUnionTypeAnnotation([ + const argumentTypes = [ this.get("left").getTypeAnnotation(), this.get("right").getTypeAnnotation(), - ]); + ]; + + if (t.isTSTypeAnnotation(argumentTypes[0]) && t.createTSUnionType) { + return t.createTSUnionType(argumentTypes); + } + + return t.createUnionTypeAnnotation(argumentTypes); } export function ConditionalExpression() { - return t.createUnionTypeAnnotation([ + const argumentTypes = [ this.get("consequent").getTypeAnnotation(), this.get("alternate").getTypeAnnotation(), - ]); + ]; + + if (t.isTSTypeAnnotation(argumentTypes[0]) && t.createTSUnionType) { + return t.createTSUnionType(argumentTypes); + } + + return t.createUnionTypeAnnotation(argumentTypes); } export function SequenceExpression() { diff --git a/packages/babel-traverse/test/fixtures/type-reference/input.ts b/packages/babel-traverse/test/fixtures/type-reference/input.ts new file mode 100644 index 000000000000..9fc2c011bd9c --- /dev/null +++ b/packages/babel-traverse/test/fixtures/type-reference/input.ts @@ -0,0 +1,13 @@ +function foo() { + const x = 1 ? a() : b(); + + return [...x]; +} + +function a(): number[] { + return []; +} + +function b(): number[] { + return []; +} diff --git a/packages/babel-traverse/test/fixtures/type-reference/options.json b/packages/babel-traverse/test/fixtures/type-reference/options.json new file mode 100644 index 000000000000..f809bfc203e2 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/type-reference/options.json @@ -0,0 +1,4 @@ +{ + "plugins": ["transform-spread"], + "presets": ["typescript"] +} diff --git a/packages/babel-types/src/asserts/generated/index.js b/packages/babel-types/src/asserts/generated/index.js index 06464cd3a347..a99c58081d41 100644 --- a/packages/babel-types/src/asserts/generated/index.js +++ b/packages/babel-types/src/asserts/generated/index.js @@ -1202,6 +1202,9 @@ export function assertTSTypeElement(node: Object, opts?: Object = {}): void { export function assertTSType(node: Object, opts?: Object = {}): void { assert("TSType", node, opts); } +export function assertTSBaseType(node: Object, opts?: Object = {}): void { + assert("TSBaseType", node, opts); +} export function assertNumberLiteral(node: Object, opts: Object): void { console.trace( "The node type NumberLiteral has been renamed to NumericLiteral", diff --git a/packages/babel-types/src/builders/typescript/createTSUnionType.js b/packages/babel-types/src/builders/typescript/createTSUnionType.js new file mode 100644 index 000000000000..49138ad029f3 --- /dev/null +++ b/packages/babel-types/src/builders/typescript/createTSUnionType.js @@ -0,0 +1,19 @@ +import { TSUnionType } from "../generated"; +import removeTypeDuplicates from "../../modifications/typescript/removeTypeDuplicates"; + +/** + * Takes an array of `types` and flattens them, removing duplicates and + * returns a `UnionTypeAnnotation` node containg them. + */ +export default function createTSUnionType( + typeAnnotations: Array, +): Object { + const types = typeAnnotations.map(type => type.typeAnnotations); + const flattened = removeTypeDuplicates(types); + + if (flattened.length === 1) { + return flattened[0]; + } else { + return TSUnionType(flattened); + } +} diff --git a/packages/babel-types/src/constants/generated/index.js b/packages/babel-types/src/constants/generated/index.js index 1f4b576a87b6..acdf21744af7 100644 --- a/packages/babel-types/src/constants/generated/index.js +++ b/packages/babel-types/src/constants/generated/index.js @@ -51,3 +51,4 @@ export const JSX_TYPES = FLIPPED_ALIAS_KEYS["JSX"]; export const PRIVATE_TYPES = FLIPPED_ALIAS_KEYS["Private"]; export const TSTYPEELEMENT_TYPES = FLIPPED_ALIAS_KEYS["TSTypeElement"]; export const TSTYPE_TYPES = FLIPPED_ALIAS_KEYS["TSType"]; +export const TSBASETYPE_TYPES = FLIPPED_ALIAS_KEYS["TSBaseType"]; diff --git a/packages/babel-types/src/definitions/typescript.js b/packages/babel-types/src/definitions/typescript.js index 608ea8ab2207..421b16c47d6c 100644 --- a/packages/babel-types/src/definitions/typescript.js +++ b/packages/babel-types/src/definitions/typescript.js @@ -143,14 +143,14 @@ const tsKeywordTypes = [ for (const type of tsKeywordTypes) { defineType(type, { - aliases: ["TSType"], + aliases: ["TSType", "TSBaseType"], visitor: [], fields: {}, }); } defineType("TSThisType", { - aliases: ["TSType"], + aliases: ["TSType", "TSBaseType"], visitor: [], fields: {}, }); @@ -300,7 +300,7 @@ defineType("TSMappedType", { }); defineType("TSLiteralType", { - aliases: ["TSType"], + aliases: ["TSType", "TSBaseType"], visitor: ["literal"], fields: { literal: validateType([ diff --git a/packages/babel-types/src/index.js b/packages/babel-types/src/index.js index b0586a4f90ec..a2126a1fe81c 100644 --- a/packages/babel-types/src/index.js +++ b/packages/babel-types/src/index.js @@ -10,6 +10,7 @@ export * from "./asserts/generated"; // builders export { default as createTypeAnnotationBasedOnTypeof } from "./builders/flow/createTypeAnnotationBasedOnTypeof"; export { default as createUnionTypeAnnotation } from "./builders/flow/createUnionTypeAnnotation"; +export { default as createTSUnionType } from "./builders/typescript/createTSUnionType"; export * from "./builders/generated"; // clone diff --git a/packages/babel-types/src/modifications/typescript/removeTypeDuplicates.js b/packages/babel-types/src/modifications/typescript/removeTypeDuplicates.js new file mode 100644 index 000000000000..1ed0e34f3f2b --- /dev/null +++ b/packages/babel-types/src/modifications/typescript/removeTypeDuplicates.js @@ -0,0 +1,65 @@ +import { + isTSAnyKeyword, + isTSUnionType, + isTSBaseType, +} from "../../validators/generated"; + +/** + * Dedupe type annotations. + */ +export default function removeTypeDuplicates( + nodes: Array, +): Array { + const generics = {}; + const bases = {}; + + // store union type groups to circular references + const typeGroups = []; + + const types = []; + + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + if (!node) continue; + + // detect duplicates + if (types.indexOf(node) >= 0) { + continue; + } + + // this type matches anything + if (isTSAnyKeyword(node.type)) { + return [node]; + } + + // Analogue of FlowBaseAnnotation + if (isTSBaseType(node)) { + bases[node.type] = node; + continue; + } + + if (isTSUnionType(node)) { + if (typeGroups.indexOf(node.types) < 0) { + nodes = nodes.concat(node.types); + typeGroups.push(node.types); + } + continue; + } + + // TODO: add generic types + + types.push(node); + } + + // add back in bases + for (const type of Object.keys(bases)) { + types.push(bases[type]); + } + + // add back in generics + for (const name of Object.keys(generics)) { + types.push(generics[name]); + } + + return types; +} diff --git a/packages/babel-types/src/validators/generated/index.js b/packages/babel-types/src/validators/generated/index.js index f78c4127ef0b..4fc045c201a9 100644 --- a/packages/babel-types/src/validators/generated/index.js +++ b/packages/babel-types/src/validators/generated/index.js @@ -4611,6 +4611,36 @@ export function isTSType(node: ?Object, opts?: Object): boolean { return false; } +export function isTSBaseType(node: ?Object, opts?: Object): boolean { + if (!node) return false; + + const nodeType = node.type; + if ( + nodeType === "TSBaseType" || + "TSAnyKeyword" === nodeType || + "TSBooleanKeyword" === nodeType || + "TSBigIntKeyword" === nodeType || + "TSNeverKeyword" === nodeType || + "TSNullKeyword" === nodeType || + "TSNumberKeyword" === nodeType || + "TSObjectKeyword" === nodeType || + "TSStringKeyword" === nodeType || + "TSSymbolKeyword" === nodeType || + "TSUndefinedKeyword" === nodeType || + "TSUnknownKeyword" === nodeType || + "TSVoidKeyword" === nodeType || + "TSThisType" === nodeType || + "TSLiteralType" === nodeType + ) { + if (typeof opts === "undefined") { + return true; + } else { + return shallowEqual(node, opts); + } + } + + return false; +} export function isNumberLiteral(node: ?Object, opts?: Object): boolean { console.trace( "The node type NumberLiteral has been renamed to NumericLiteral",