From a1de3a0fd8f172544857c9a1d5a36be8f2eeae0e Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Sun, 21 Jan 2018 13:24:03 -0800 Subject: [PATCH 1/8] wip --- ...lapse-intersection-interfaces-transform.ts | 43 ++++++++++++++----- .../empty-empty/output.tsx | 2 +- .../repeated/input.tsx | 1 + .../repeated/output.tsx | 3 ++ .../output.tsx | 2 - .../initial-state-and-proprypes/output.tsx | 1 - .../end-to-end/multiple-components/output.tsx | 1 - 7 files changed, 37 insertions(+), 16 deletions(-) create mode 100644 test/collapse-intersection-interfaces-transform/repeated/input.tsx create mode 100644 test/collapse-intersection-interfaces-transform/repeated/output.tsx diff --git a/src/transforms/collapse-intersection-interfaces-transform.ts b/src/transforms/collapse-intersection-interfaces-transform.ts index 30395f9..7cb848b 100644 --- a/src/transforms/collapse-intersection-interfaces-transform.ts +++ b/src/transforms/collapse-intersection-interfaces-transform.ts @@ -1,4 +1,5 @@ import * as ts from 'typescript'; +import * as _ from 'lodash'; import * as helpers from '../helpers'; @@ -13,8 +14,8 @@ import * as helpers from '../helpers'; * type Foo = {foo: string; bar: number;} */ export function collapseIntersectionInterfacesTransformFactoryFactory( - typeChecker: ts.TypeChecker, - ): ts.TransformerFactory { + typeChecker: ts.TypeChecker, +): ts.TransformerFactory { return function collapseIntersectionInterfacesTransformFactory(context: ts.TransformationContext) { return function collapseIntersectionInterfacesTransform(sourceFile: ts.SourceFile) { const visited = ts.visitEachChild(sourceFile, visitor, context); @@ -32,27 +33,47 @@ export function collapseIntersectionInterfacesTransformFactoryFactory( function visitTypeAliasDeclaration(node: ts.TypeAliasDeclaration) { if ( - ts.isIntersectionTypeNode(node.type) - && node.type.types.every(ts.isTypeLiteralNode) + ts.isIntersectionTypeNode(node.type) && + node.type.types.every(ts.isTypeLiteralNode) && + node.type.types.every(type => type.forEachChild(node => ts.isPropertySignature(node)) || false) ) { + // debugger // We need cast `node.type.types` to `ts.NodeArray` // because TypeScript can't figure out `node.type.types.every(ts.isTypeLiteralNode)` - const allMembers = (node.type.types as ts.NodeArray) - .map((type) => type.members) - .reduce((all, members) => ts.createNodeArray(all.concat(members)), ts.createNodeArray([])); + const allIntersectionTypeNodeTypes = node.type.types as ts.NodeArray; + + // Flatten all members into an array + const allMembersOfAllIntersectionTypeNode = allIntersectionTypeNodeTypes + .map(typeLitteralNode => { + return typeLitteralNode.members as ts.NodeArray; + }) + .reduce((result, members) => [...result, ...members], [] as Array); + // debugger + // Dedupe members with exact same name and type ({ foo: string } & { foo: string }) + const dedupedMembers = _.uniqBy(allMembersOfAllIntersectionTypeNode, memeber => { + // debugger + return memeber.name && memeber.name.getText() + memeber.type; + }); + + // const allMembers = (node.type.types as ts.NodeArray) + // .map((type) => type.members) + // .reduce((all, members) => ts.createNodeArray(all.concat(members)), ts.createNodeArray([])); + + // const dedupedMembers = _.uniqBy(allMembers, (member)=> { + // return member.getText(sourceFile) + '___' + typeChecker.getTypeFromTypeNode(member.) + // }) return ts.createTypeAliasDeclaration( [], [], node.name.text, [], - ts.createTypeLiteralNode(allMembers), + ts.createTypeLiteralNode(dedupedMembers), ); } return node; } - } - } + }; + }; } - diff --git a/test/collapse-intersection-interfaces-transform/empty-empty/output.tsx b/test/collapse-intersection-interfaces-transform/empty-empty/output.tsx index 579668f..f06983c 100644 --- a/test/collapse-intersection-interfaces-transform/empty-empty/output.tsx +++ b/test/collapse-intersection-interfaces-transform/empty-empty/output.tsx @@ -1 +1 @@ -type Foo = {}; +type Foo = {} & {}; diff --git a/test/collapse-intersection-interfaces-transform/repeated/input.tsx b/test/collapse-intersection-interfaces-transform/repeated/input.tsx new file mode 100644 index 0000000..f5a3387 --- /dev/null +++ b/test/collapse-intersection-interfaces-transform/repeated/input.tsx @@ -0,0 +1 @@ +type A = { foo: string; } & { foo: string; }; diff --git a/test/collapse-intersection-interfaces-transform/repeated/output.tsx b/test/collapse-intersection-interfaces-transform/repeated/output.tsx new file mode 100644 index 0000000..43eea4b --- /dev/null +++ b/test/collapse-intersection-interfaces-transform/repeated/output.tsx @@ -0,0 +1,3 @@ +type A = { + foo: string, +}; diff --git a/test/end-to-end/initial-state-and-proprypes-and-set-state/output.tsx b/test/end-to-end/initial-state-and-proprypes-and-set-state/output.tsx index 95fae0f..93fec77 100644 --- a/test/end-to-end/initial-state-and-proprypes-and-set-state/output.tsx +++ b/test/end-to-end/initial-state-and-proprypes-and-set-state/output.tsx @@ -4,8 +4,6 @@ type MyComponentProps = { }; type MyComponentState = { dynamicState: number, - foo: number, - bar: string, }; export default class MyComponent extends React.Component { state = { foo: 1, bar: 'str' }; diff --git a/test/end-to-end/initial-state-and-proprypes/output.tsx b/test/end-to-end/initial-state-and-proprypes/output.tsx index 912e3b8..5dc80ee 100644 --- a/test/end-to-end/initial-state-and-proprypes/output.tsx +++ b/test/end-to-end/initial-state-and-proprypes/output.tsx @@ -4,7 +4,6 @@ type MyComponentProps = { }; type MyComponentState = { foo: number, - bar: string, }; export default class MyComponent extends React.Component { state = { foo: 1, bar: 'str' }; diff --git a/test/end-to-end/multiple-components/output.tsx b/test/end-to-end/multiple-components/output.tsx index 5495538..a91bcf8 100644 --- a/test/end-to-end/multiple-components/output.tsx +++ b/test/end-to-end/multiple-components/output.tsx @@ -12,7 +12,6 @@ const Hey: React.SFC = ({ name }) => { }; type MyComponentState = { foo: number, - bar: number, }; export default class MyComponent extends React.Component<{}, MyComponentState> { render() { From c5ede53a8041930767a467f3d0c8e1c5a28e2280 Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Sun, 21 Jan 2018 13:46:42 -0800 Subject: [PATCH 2/8] werk --- ...lapse-intersection-interfaces-transform.ts | 27 ++++++++++--------- tsconfig.json | 6 ++--- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/transforms/collapse-intersection-interfaces-transform.ts b/src/transforms/collapse-intersection-interfaces-transform.ts index 7cb848b..44c2391 100644 --- a/src/transforms/collapse-intersection-interfaces-transform.ts +++ b/src/transforms/collapse-intersection-interfaces-transform.ts @@ -37,7 +37,6 @@ export function collapseIntersectionInterfacesTransformFactoryFactory( node.type.types.every(ts.isTypeLiteralNode) && node.type.types.every(type => type.forEachChild(node => ts.isPropertySignature(node)) || false) ) { - // debugger // We need cast `node.type.types` to `ts.NodeArray` // because TypeScript can't figure out `node.type.types.every(ts.isTypeLiteralNode)` const allIntersectionTypeNodeTypes = node.type.types as ts.NodeArray; @@ -48,20 +47,24 @@ export function collapseIntersectionInterfacesTransformFactoryFactory( return typeLitteralNode.members as ts.NodeArray; }) .reduce((result, members) => [...result, ...members], [] as Array); - // debugger - // Dedupe members with exact same name and type ({ foo: string } & { foo: string }) + + // Dedupe members with exact same name and type ({ foo: string } & { foo: string } => { foo: string }) const dedupedMembers = _.uniqBy(allMembersOfAllIntersectionTypeNode, memeber => { - // debugger - return memeber.name && memeber.name.getText() + memeber.type; + return memeber.name && memeber.name.getText(sourceFile) + memeber.type; }); - // const allMembers = (node.type.types as ts.NodeArray) - // .map((type) => type.members) - // .reduce((all, members) => ts.createNodeArray(all.concat(members)), ts.createNodeArray([])); - - // const dedupedMembers = _.uniqBy(allMembers, (member)=> { - // return member.getText(sourceFile) + '___' + typeChecker.getTypeFromTypeNode(member.) - // }) + // Dedupe members with same name and different types ({ foo: string } & { foo: number } => { foo: string | number }) + // const membersMap = new Map>(); + // for (const member of dedupedMembers) { + // if (member.type) { + // const memberName = member.name.getText(); + // if (membersMap.has(memberName)) { + // membersMap.set(memberName, ts.createNodeArray(membersMap.get(memberName)!.concat(member.type))); + // } else { + // membersMap.set(memberName, ts.createNodeArray([member.type])) + // } + // } + // } return ts.createTypeAliasDeclaration( [], diff --git a/tsconfig.json b/tsconfig.json index 73cc4e3..cd9406b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,9 +9,9 @@ "experimentalDecorators": true, "sourceMap": true, "outDir": "dist", - "sourceRoot": "../src" + "sourceRoot": "../src", + "lib": ["dom", "es2015"] }, "exclude": ["node_modules", "test", "dist"], - "types": ["node", "jest"], - "lib": ["es2017"] + "types": ["node", "jest"] } From cf1e6bb9cb3f958e3e943e6484a5733d536c2797 Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Sun, 4 Feb 2018 13:32:14 -0800 Subject: [PATCH 3/8] WIP --- .vscode/settings.json | 15 ++++++ ...lapse-intersection-interfaces-transform.ts | 50 ++++++++++++------- .../advanced/output.tsx | 2 +- .../empty-empty/output.tsx | 2 +- .../repeated/input.tsx | 4 ++ .../repeated/output.tsx | 10 ++++ .../output.tsx | 2 + .../initial-state-and-proprypes/output.tsx | 1 + .../end-to-end/multiple-components/output.tsx | 1 + tsconfig.json | 1 + 10 files changed, 68 insertions(+), 20 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..098d484 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/node_modules": true, + "**/dist": true + }, + "search.exclude": { + "**/node_modules": true, + "**/dist": true + } +} diff --git a/src/transforms/collapse-intersection-interfaces-transform.ts b/src/transforms/collapse-intersection-interfaces-transform.ts index 44c2391..a8c49a1 100644 --- a/src/transforms/collapse-intersection-interfaces-transform.ts +++ b/src/transforms/collapse-intersection-interfaces-transform.ts @@ -32,11 +32,7 @@ export function collapseIntersectionInterfacesTransformFactoryFactory( } function visitTypeAliasDeclaration(node: ts.TypeAliasDeclaration) { - if ( - ts.isIntersectionTypeNode(node.type) && - node.type.types.every(ts.isTypeLiteralNode) && - node.type.types.every(type => type.forEachChild(node => ts.isPropertySignature(node)) || false) - ) { + if (ts.isIntersectionTypeNode(node.type) && node.type.types.every(ts.isTypeLiteralNode)) { // We need cast `node.type.types` to `ts.NodeArray` // because TypeScript can't figure out `node.type.types.every(ts.isTypeLiteralNode)` const allIntersectionTypeNodeTypes = node.type.types as ts.NodeArray; @@ -50,28 +46,46 @@ export function collapseIntersectionInterfacesTransformFactoryFactory( // Dedupe members with exact same name and type ({ foo: string } & { foo: string } => { foo: string }) const dedupedMembers = _.uniqBy(allMembersOfAllIntersectionTypeNode, memeber => { - return memeber.name && memeber.name.getText(sourceFile) + memeber.type; + if (ts.isIndexSignatureDeclaration(memeber)) return false; + return ( + memeber.name !== undefined && memeber.name.getText() + (memeber.type && memeber.type.kind) + ); }); // Dedupe members with same name and different types ({ foo: string } & { foo: number } => { foo: string | number }) - // const membersMap = new Map>(); - // for (const member of dedupedMembers) { - // if (member.type) { - // const memberName = member.name.getText(); - // if (membersMap.has(memberName)) { - // membersMap.set(memberName, ts.createNodeArray(membersMap.get(memberName)!.concat(member.type))); - // } else { - // membersMap.set(memberName, ts.createNodeArray([member.type])) - // } - // } - // } + const finalMembers: Array = []; + const membersMap = new Map>(); + for (const member of dedupedMembers) { + if (member.type) { + let nameNode = member.name; + if (ts.isIndexSignatureDeclaration(member)) { + finalMembers.push(member); + continue; + } + let memberName = nameNode.getText(); + if (membersMap.has(memberName)) { + membersMap.set(memberName, membersMap.get(memberName)!.concat(member.type!)); + } else { + membersMap.set(memberName, [member.type!]); + } + } + } + for (const [name, types] of membersMap.entries()) { + // if for this name there is only one type found use the first type, otherwise make a union of all types + let resultType = + types.length === 1 + ? types[0] + : ts.createUnionOrIntersectionTypeNode(ts.SyntaxKind.UnionType, types); + + finalMembers.push(ts.createPropertySignature([], name, undefined, resultType, undefined)); + } return ts.createTypeAliasDeclaration( [], [], node.name.text, [], - ts.createTypeLiteralNode(dedupedMembers), + ts.createTypeLiteralNode(finalMembers), ); } diff --git a/test/collapse-intersection-interfaces-transform/advanced/output.tsx b/test/collapse-intersection-interfaces-transform/advanced/output.tsx index a579223..11f28ad 100644 --- a/test/collapse-intersection-interfaces-transform/advanced/output.tsx +++ b/test/collapse-intersection-interfaces-transform/advanced/output.tsx @@ -1,7 +1,7 @@ type Foo = { + [key: string]: number, foo: string, stuff: boolean, other: () => void, bar: number, - [key: string]: number, }; diff --git a/test/collapse-intersection-interfaces-transform/empty-empty/output.tsx b/test/collapse-intersection-interfaces-transform/empty-empty/output.tsx index f06983c..579668f 100644 --- a/test/collapse-intersection-interfaces-transform/empty-empty/output.tsx +++ b/test/collapse-intersection-interfaces-transform/empty-empty/output.tsx @@ -1 +1 @@ -type Foo = {} & {}; +type Foo = {}; diff --git a/test/collapse-intersection-interfaces-transform/repeated/input.tsx b/test/collapse-intersection-interfaces-transform/repeated/input.tsx index f5a3387..2bfb4e6 100644 --- a/test/collapse-intersection-interfaces-transform/repeated/input.tsx +++ b/test/collapse-intersection-interfaces-transform/repeated/input.tsx @@ -1 +1,5 @@ type A = { foo: string; } & { foo: string; }; + +type B = { foo: string; bar: number; } & { foo: number; bar: number; } + +type C = { foo: string; bar: number; } & { foo: number; bar: number; } & { foo: string; } diff --git a/test/collapse-intersection-interfaces-transform/repeated/output.tsx b/test/collapse-intersection-interfaces-transform/repeated/output.tsx index 43eea4b..5dc0eec 100644 --- a/test/collapse-intersection-interfaces-transform/repeated/output.tsx +++ b/test/collapse-intersection-interfaces-transform/repeated/output.tsx @@ -1,3 +1,13 @@ type A = { foo: string, }; + +type B = { + foo: string | number, + bar: number, +}; + +type C = { + foo: string | number, + bar: number, +}; diff --git a/test/end-to-end/initial-state-and-proprypes-and-set-state/output.tsx b/test/end-to-end/initial-state-and-proprypes-and-set-state/output.tsx index 93fec77..95fae0f 100644 --- a/test/end-to-end/initial-state-and-proprypes-and-set-state/output.tsx +++ b/test/end-to-end/initial-state-and-proprypes-and-set-state/output.tsx @@ -4,6 +4,8 @@ type MyComponentProps = { }; type MyComponentState = { dynamicState: number, + foo: number, + bar: string, }; export default class MyComponent extends React.Component { state = { foo: 1, bar: 'str' }; diff --git a/test/end-to-end/initial-state-and-proprypes/output.tsx b/test/end-to-end/initial-state-and-proprypes/output.tsx index 5dc80ee..912e3b8 100644 --- a/test/end-to-end/initial-state-and-proprypes/output.tsx +++ b/test/end-to-end/initial-state-and-proprypes/output.tsx @@ -4,6 +4,7 @@ type MyComponentProps = { }; type MyComponentState = { foo: number, + bar: string, }; export default class MyComponent extends React.Component { state = { foo: 1, bar: 'str' }; diff --git a/test/end-to-end/multiple-components/output.tsx b/test/end-to-end/multiple-components/output.tsx index a91bcf8..5495538 100644 --- a/test/end-to-end/multiple-components/output.tsx +++ b/test/end-to-end/multiple-components/output.tsx @@ -12,6 +12,7 @@ const Hey: React.SFC = ({ name }) => { }; type MyComponentState = { foo: number, + bar: number, }; export default class MyComponent extends React.Component<{}, MyComponentState> { render() { diff --git a/tsconfig.json b/tsconfig.json index cd9406b..0a2ed71 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "module": "commonjs", "emitDecoratorMetadata": true, "experimentalDecorators": true, + "downlevelIteration": true, "sourceMap": true, "outDir": "dist", "sourceRoot": "../src", From 82b9aad7a782b5fbbaa42a7add4dd93fb6bee789 Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Sun, 4 Feb 2018 20:54:29 -0800 Subject: [PATCH 4/8] refactor --- ...lapse-intersection-interfaces-transform.ts | 145 ++++++++++++------ .../advanced/output.tsx | 2 +- 2 files changed, 97 insertions(+), 50 deletions(-) diff --git a/src/transforms/collapse-intersection-interfaces-transform.ts b/src/transforms/collapse-intersection-interfaces-transform.ts index a8c49a1..cd7c0f7 100644 --- a/src/transforms/collapse-intersection-interfaces-transform.ts +++ b/src/transforms/collapse-intersection-interfaces-transform.ts @@ -32,65 +32,112 @@ export function collapseIntersectionInterfacesTransformFactoryFactory( } function visitTypeAliasDeclaration(node: ts.TypeAliasDeclaration) { - if (ts.isIntersectionTypeNode(node.type) && node.type.types.every(ts.isTypeLiteralNode)) { - // We need cast `node.type.types` to `ts.NodeArray` - // because TypeScript can't figure out `node.type.types.every(ts.isTypeLiteralNode)` - const allIntersectionTypeNodeTypes = node.type.types as ts.NodeArray; - - // Flatten all members into an array - const allMembersOfAllIntersectionTypeNode = allIntersectionTypeNodeTypes - .map(typeLitteralNode => { - return typeLitteralNode.members as ts.NodeArray; - }) - .reduce((result, members) => [...result, ...members], [] as Array); - - // Dedupe members with exact same name and type ({ foo: string } & { foo: string } => { foo: string }) - const dedupedMembers = _.uniqBy(allMembersOfAllIntersectionTypeNode, memeber => { - if (ts.isIndexSignatureDeclaration(memeber)) return false; - return ( - memeber.name !== undefined && memeber.name.getText() + (memeber.type && memeber.type.kind) - ); - }); - - // Dedupe members with same name and different types ({ foo: string } & { foo: number } => { foo: string | number }) - const finalMembers: Array = []; - const membersMap = new Map>(); - for (const member of dedupedMembers) { - if (member.type) { - let nameNode = member.name; - if (ts.isIndexSignatureDeclaration(member)) { - finalMembers.push(member); - continue; - } - let memberName = nameNode.getText(); - if (membersMap.has(memberName)) { - membersMap.set(memberName, membersMap.get(memberName)!.concat(member.type!)); - } else { - membersMap.set(memberName, [member.type!]); - } - } - } - for (const [name, types] of membersMap.entries()) { - // if for this name there is only one type found use the first type, otherwise make a union of all types - let resultType = - types.length === 1 - ? types[0] - : ts.createUnionOrIntersectionTypeNode(ts.SyntaxKind.UnionType, types); - - finalMembers.push(ts.createPropertySignature([], name, undefined, resultType, undefined)); - } - + if (ts.isIntersectionTypeNode(node.type)) { return ts.createTypeAliasDeclaration( [], [], node.name.text, [], - ts.createTypeLiteralNode(finalMembers), + visitIntersectionTypeNode(node.type), ); } return node; } + + function visitIntersectionTypeNode(node: ts.IntersectionTypeNode) { + // Only intersection of type literals can be colapsed. + // We are currently ignoring intersections such as `{foo: string} & {bar: string} & TypeRef` + // TODO: handle mix of type references and multiple literal types + if (!node.types.every(typeNode => ts.isTypeLiteralNode(typeNode))) { + return node; + } + + // We need cast `node.type.types` to `ts.NodeArray` + // because TypeScript can't figure out `node.type.types.every(ts.isTypeLiteralNode)` + const types = node.types as ts.NodeArray; + + // Build a map of member names to all of types found in intersectioning type literals + // For instance {foo: string, bar: number} & { foo: number } will result in a map like this: + // Map { + // 'foo' => Set { 'string', 'number' }, + // 'bar' => Set { 'number' } + // } + const membersMap = new Map>(); + + // A sepecial member of type literal nodes is index signitures which don't have a name + // We use this symbol to track it in our members map + const INDEX_SIGNITUTRE_MEMBER = Symbol('Index signiture member'); + + // Keep a reference of first index signiture member parameters. (ignore rest) + let indexMemberParameter: ts.NodeArray | null = null; + + // Iterate through all of type literal nodes members and add them to the members map + types.forEach(typeNode => { + typeNode.members.forEach(member => { + if (ts.isIndexSignatureDeclaration(member)) { + if (member.type !== undefined) { + if (membersMap.has(INDEX_SIGNITUTRE_MEMBER)) { + membersMap.get(INDEX_SIGNITUTRE_MEMBER)!.add(member.type); + } else { + indexMemberParameter = member.parameters; + membersMap.set(INDEX_SIGNITUTRE_MEMBER, new Set([member.type])); + } + } + } else if (ts.isPropertySignature(member)) { + if (member.type !== undefined) { + const memberName = member.name.getText(); + if (membersMap.has(memberName)) { + membersMap.get(memberName)!.add(member.type); + } else { + membersMap.set(memberName, new Set([member.type])); + } + } + } + }); + }); + + // Result type literal members list + const finalMembers: Array = []; + + // Put together the map into a type literal that has member per each map entery and type of that + // member is a union of all types in vlues for that member name in members map + // if a member has only one type, create a simple type literal for it + for (const [name, types] of membersMap.entries()) { + if (typeof name === 'symbol') { + continue; + } + // if for this name there is only one type found use the first type, otherwise make a union of all types + let resultType = types.size === 1 ? Array.from(types)[0] : createUnionType(Array.from(types)); + + finalMembers.push(ts.createPropertySignature([], name, undefined, resultType, undefined)); + } + + // Handle index signiture member + if (membersMap.has(INDEX_SIGNITUTRE_MEMBER)) { + const indexTypes = Array.from(membersMap.get(INDEX_SIGNITUTRE_MEMBER)!); + let indexType = indexTypes[0]; + if (indexTypes.length > 1) { + indexType = createUnionType(indexTypes); + } + const indexSigniture = ts.createIndexSignature([], [], indexMemberParameter!, indexType); + finalMembers.push(indexSigniture); + } + + // Generate one single type literal node + return ts.createTypeLiteralNode(finalMembers); + } + + /** + * Create a union type from multiple type nodes + * @param types + */ + function createUnionType(types: ts.TypeNode[]) { + // first dedupe literal types + // TODO: this only works if all types are primitive types like string or number + const uniqueTypes = _.uniqBy(types, type => type.kind); + return ts.createUnionOrIntersectionTypeNode(ts.SyntaxKind.UnionType, uniqueTypes); + } }; }; } diff --git a/test/collapse-intersection-interfaces-transform/advanced/output.tsx b/test/collapse-intersection-interfaces-transform/advanced/output.tsx index 11f28ad..a579223 100644 --- a/test/collapse-intersection-interfaces-transform/advanced/output.tsx +++ b/test/collapse-intersection-interfaces-transform/advanced/output.tsx @@ -1,7 +1,7 @@ type Foo = { - [key: string]: number, foo: string, stuff: boolean, other: () => void, bar: number, + [key: string]: number, }; From 61a3dbfd2e8ffa607d0577d8d0d72f2e8ebf5e99 Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Sun, 4 Feb 2018 21:00:16 -0800 Subject: [PATCH 5/8] addEmitHelpers --- ...eact-move-prop-types-to-class-transform.ts | 4 +++- ...-remove-prop-types-assignment-transform.ts | 10 ++++---- .../react-remove-prop-types-import.ts | 4 +++- ...move-static-prop-types-member-transform.ts | 24 ++++++++++--------- ...stateless-function-make-props-transform.ts | 4 +++- 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/transforms/react-move-prop-types-to-class-transform.ts b/src/transforms/react-move-prop-types-to-class-transform.ts index 7b6d9a8..d954a11 100644 --- a/src/transforms/react-move-prop-types-to-class-transform.ts +++ b/src/transforms/react-move-prop-types-to-class-transform.ts @@ -33,7 +33,9 @@ export type Factory = ts.TransformerFactory; export function reactMovePropTypesToClassTransformFactoryFactory(typeChecker: ts.TypeChecker): Factory { return function reactMovePropTypesToClassTransformFactory(context: ts.TransformationContext) { return function reactMovePropTypesToClassTransform(sourceFile: ts.SourceFile) { - return visitSourceFile(sourceFile, typeChecker); + const visited = visitSourceFile(sourceFile, typeChecker); + ts.addEmitHelpers(visited, context.readEmitHelpers()); + return visited; }; }; } diff --git a/src/transforms/react-remove-prop-types-assignment-transform.ts b/src/transforms/react-remove-prop-types-assignment-transform.ts index e65ecf1..4b57fa9 100644 --- a/src/transforms/react-remove-prop-types-assignment-transform.ts +++ b/src/transforms/react-remove-prop-types-assignment-transform.ts @@ -15,13 +15,15 @@ export type Factory = ts.TransformerFactory; * After * class SomeComponent extends React.Component<{foo: number;}, {bar: string;}> {} */ -export function reactRemovePropTypesAssignmentTransformFactoryFactory(typeChecker: ts.TypeChecker): Factory{ +export function reactRemovePropTypesAssignmentTransformFactoryFactory(typeChecker: ts.TypeChecker): Factory { return function reactRemovePropTypesAssignmentTransformFactory(context: ts.TransformationContext) { return function reactRemovePropTypesAssignmentTransform(sourceFile: ts.SourceFile) { - return ts.updateSourceFileNode( + const visited = ts.updateSourceFileNode( sourceFile, sourceFile.statements.filter(s => !helpers.isReactPropTypeAssignmentStatement(s)), ); - } - } + ts.addEmitHelpers(visited, context.readEmitHelpers()); + return visited; + }; + }; } diff --git a/src/transforms/react-remove-prop-types-import.ts b/src/transforms/react-remove-prop-types-import.ts index 8710971..8644b8c 100644 --- a/src/transforms/react-remove-prop-types-import.ts +++ b/src/transforms/react-remove-prop-types-import.ts @@ -20,7 +20,7 @@ export type Factory = ts.TransformerFactory; export function reactRemovePropTypesImportTransformFactoryFactory(typeChecker: ts.TypeChecker): Factory { return function reactRemovePropTypesImportTransformFactory(context: ts.TransformationContext) { return function reactRemovePropTypesImportTransform(sourceFile: ts.SourceFile) { - return ts.updateSourceFileNode( + const visited = ts.updateSourceFileNode( sourceFile, sourceFile.statements .filter(s => { @@ -32,6 +32,8 @@ export function reactRemovePropTypesImportTransformFactoryFactory(typeChecker: t }) .map(updateReactImportIfNeeded), ); + ts.addEmitHelpers(visited, context.readEmitHelpers()); + return visited; }; }; } diff --git a/src/transforms/react-remove-static-prop-types-member-transform.ts b/src/transforms/react-remove-static-prop-types-member-transform.ts index e0fc471..7054884 100644 --- a/src/transforms/react-remove-static-prop-types-member-transform.ts +++ b/src/transforms/react-remove-static-prop-types-member-transform.ts @@ -21,7 +21,9 @@ export type Factory = ts.TransformerFactory; export function reactRemoveStaticPropTypesMemberTransformFactoryFactory(typeChecker: ts.TypeChecker): Factory { return function reactRemoveStaticPropTypesMemberTransformFactory(context: ts.TransformationContext) { return function reactRemoveStaticPropTypesMemberTransform(sourceFile: ts.SourceFile) { - return ts.visitEachChild(sourceFile, visitor, context); + const visited = ts.visitEachChild(sourceFile, visitor, context); + ts.addEmitHelpers(visited, context.readEmitHelpers()); + return visited; function visitor(node: ts.Node) { if (ts.isClassDeclaration(node) && helpers.isReactComponent(node, typeChecker)) { @@ -32,29 +34,29 @@ export function reactRemoveStaticPropTypesMemberTransformFactoryFactory(typeChec node.name, node.typeParameters, ts.createNodeArray(node.heritageClauses), - node.members.filter((member) => { + node.members.filter(member => { if ( - ts.isPropertyDeclaration(member) - && helpers.hasStaticModifier(member) - && helpers.isPropTypesMember(member, sourceFile) + ts.isPropertyDeclaration(member) && + helpers.hasStaticModifier(member) && + helpers.isPropTypesMember(member, sourceFile) ) { return false; } // propTypes getter if ( - ts.isGetAccessorDeclaration(member) - && helpers.hasStaticModifier(member) - && helpers.isPropTypesMember(member, sourceFile) + ts.isGetAccessorDeclaration(member) && + helpers.hasStaticModifier(member) && + helpers.isPropTypesMember(member, sourceFile) ) { return false; } return true; }), - ) + ); } return node; } - } - } + }; + }; } diff --git a/src/transforms/react-stateless-function-make-props-transform.ts b/src/transforms/react-stateless-function-make-props-transform.ts index 76bd40f..0222cec 100644 --- a/src/transforms/react-stateless-function-make-props-transform.ts +++ b/src/transforms/react-stateless-function-make-props-transform.ts @@ -36,7 +36,9 @@ export type Factory = ts.TransformerFactory; export function reactStatelessFunctionMakePropsTransformFactoryFactory(typeChecker: ts.TypeChecker): Factory { return function reactStatelessFunctionMakePropsTransformFactory(context: ts.TransformationContext) { return function reactStatelessFunctionMakePropsTransform(sourceFile: ts.SourceFile) { - return visitSourceFile(sourceFile, typeChecker); + const visited = visitSourceFile(sourceFile, typeChecker); + ts.addEmitHelpers(visited, context.readEmitHelpers()); + return visited; }; }; } From 8710db79c3c9738a2b433c778cc1609e8bb58ef1 Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Sun, 4 Feb 2018 21:07:40 -0800 Subject: [PATCH 6/8] .. From 8d5ab5487659f71eba83750004435fdd60e3861f Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Sun, 4 Feb 2018 21:10:34 -0800 Subject: [PATCH 7/8] Upgrade to TypeScript 2.7 --- .vscode/settings.json | 3 +++ package.json | 2 +- src/transforms/react-js-make-props-and-state-transform.ts | 2 +- yarn.lock | 6 +++--- 4 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..72446f4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/package.json b/package.json index 79c5aa9..c593227 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "glob": "^7.1.2", "lodash": "^4.17.4", "prettier": "^1.10.2", - "typescript": "^2.6.2" + "typescript": "2.7" }, "devDependencies": { "@types/chalk": "^0.4.31", diff --git a/src/transforms/react-js-make-props-and-state-transform.ts b/src/transforms/react-js-make-props-and-state-transform.ts index c896d35..c61d193 100644 --- a/src/transforms/react-js-make-props-and-state-transform.ts +++ b/src/transforms/react-js-make-props-and-state-transform.ts @@ -137,7 +137,7 @@ function getPropsTypeOfReactComponentClass( staticPropTypesGetterMember !== undefined && ts.isGetAccessorDeclaration(staticPropTypesGetterMember) // check to satisfy typechecker ) { - const returnStatement = _.find(staticPropTypesGetterMember.body.statements, statement => + const returnStatement = _.find(staticPropTypesGetterMember.body!.statements, statement => ts.isReturnStatement(statement), ); if ( diff --git a/yarn.lock b/yarn.lock index f6806ce..0b55516 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2567,9 +2567,9 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -typescript@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" +typescript@2.7: + version "2.7.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.1.tgz#bb3682c2c791ac90e7c6210b26478a8da085c359" uglify-js@^2.6: version "2.8.29" From 8851e723ba56b31faad173406b3a23d093b50730 Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Sun, 4 Feb 2018 21:24:04 -0800 Subject: [PATCH 8/8] todo --- .../collapse-intersection-interfaces-transform.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/transforms/collapse-intersection-interfaces-transform.ts b/src/transforms/collapse-intersection-interfaces-transform.ts index cd7c0f7..cca02a4 100644 --- a/src/transforms/collapse-intersection-interfaces-transform.ts +++ b/src/transforms/collapse-intersection-interfaces-transform.ts @@ -86,7 +86,16 @@ export function collapseIntersectionInterfacesTransformFactoryFactory( } } else if (ts.isPropertySignature(member)) { if (member.type !== undefined) { - const memberName = member.name.getText(); + let memberName = member.name.getText(sourceFile); + + // For unknown reasons, member.name.getText() is returning nothing in some cases + // This is probably because previous transformers did something with the AST that + // index of text string of member identifier is lost + // TODO: investigate + if (!memberName) { + memberName = (member.name as any).escapedText; + } + if (membersMap.has(memberName)) { membersMap.get(memberName)!.add(member.type); } else {