1 | import * as ts from 'typescript';
|
2 | import {Set} from 'immutable';
|
3 | import {namedProp} from "./transformer";
|
4 |
|
5 | function membersMatch(first, second) {
|
6 | if (first.kind !== second.kind) {
|
7 | return false;
|
8 | }
|
9 | if (first.name.text !== second.name.text) {
|
10 | return false;
|
11 | }
|
12 | if (first.type.kind !== second.type.kind) {
|
13 | return false;
|
14 | }
|
15 | if (first.type.kind === ts.SyntaxKind.ArrayType && second.type.kind === ts.SyntaxKind.ArrayType) {
|
16 | if (first.type.elementType.kind !== second.type.elementType.kind) {
|
17 | return false;
|
18 | }
|
19 | }
|
20 | return true;
|
21 | }
|
22 |
|
23 | function isEmptyArrayType(member) {
|
24 | if (member.type.kind === ts.SyntaxKind.ArrayType) {
|
25 | if (member.type.elementType.kind === ts.SyntaxKind.AnyKeyword) {
|
26 | return true;
|
27 | }
|
28 | }
|
29 | return false;
|
30 | }
|
31 |
|
32 | export function collapseInterfaces(interfaces: any[]): any[] {
|
33 | return interfaces.reduce((acc, current) => {
|
34 |
|
35 | const currentName = current.name.text;
|
36 | const currentMemberNames = Set(current.members.map(x => (x.name || x.label).text));
|
37 |
|
38 | const matchingInterfaceIndex = acc.findIndex(x => (x.name || x.label).text === currentName);
|
39 |
|
40 | if (matchingInterfaceIndex > -1) {
|
41 | return acc
|
42 | .map((int, index) => {
|
43 | if (index === matchingInterfaceIndex) {
|
44 |
|
45 | const prevMemberNames = Set(int.members.map(x => (x.name || x.label).text));
|
46 |
|
47 | // if the current interface has less props than a previous one
|
48 | // we need to back-track and make the previous one optional
|
49 | if (currentMemberNames.size < prevMemberNames.size) {
|
50 | // elements that existed before, but not in the current
|
51 | const missing = int.members.filter(x => !currentMemberNames.has(x.name.text));
|
52 | missing.forEach(mem => {
|
53 | mem.questionToken = ts.createNode(ts.SyntaxKind.QuestionToken);
|
54 | });
|
55 | }
|
56 |
|
57 | if (currentMemberNames.has(int.name.text)) {
|
58 | console.log('exists in both, maybe union', int.name.text);
|
59 | } else {
|
60 | // console.log('incoming current does not exist in prev');
|
61 | const existinMemberNames = Set(int.members.map(x => x.name.text));
|
62 | const newMembers = int.members.slice();
|
63 |
|
64 | // Loop over incoming current members
|
65 | current.members.forEach(mem => {
|
66 |
|
67 | const existingIndex = int.members.findIndex(x => x.name.text === mem.name.text);
|
68 | const existingMember = int.members[existingIndex];
|
69 |
|
70 | // Here, the current member does NOT already exist in this
|
71 | // interface, so we add it, but as optional
|
72 | if (!existingMember) {
|
73 | mem.questionToken = ts.createNode(ts.SyntaxKind.QuestionToken);
|
74 | newMembers.push(mem);
|
75 | } else {
|
76 | // here it exists in both, are the types the same?
|
77 | // console.log(ts.SyntaxKind[mem.type.kind]);
|
78 | // console.log(existingMember.kind, mem.kind);
|
79 | if (membersMatch(existingMember, mem)) {
|
80 | return;
|
81 | } else {
|
82 | const updatedMember = namedProp({name: existingMember.name.text});
|
83 | // const exists = existingMember.type.types.some(x => x.kind === mem.kind);
|
84 |
|
85 | // already a union, so just push a new type
|
86 | if (existingMember.type.kind === ts.SyntaxKind.UnionType) {
|
87 | const asSet = Set(existingMember.type.types.map(x => x.kind));
|
88 | if (!asSet.contains(mem.type.kind)) {
|
89 | existingMember.type.types.push(mem.type);
|
90 | newMembers[existingIndex] = existingMember;
|
91 | }
|
92 | } else { // not a union yet, so create one for next time around
|
93 |
|
94 | // was this previously marked as an empty array? eg: any[]
|
95 | // if so & the next item is NOT, then we can ignore the any[]
|
96 | if (isEmptyArrayType(existingMember) && !isEmptyArrayType(mem)) {
|
97 | updatedMember.type = ts.createNode(ts.SyntaxKind.ArrayType);
|
98 | updatedMember.type.elementType = mem.type.elementType;
|
99 | newMembers[existingIndex] = updatedMember;
|
100 | } else {
|
101 | // If the INCOMING member type is an empty array, but we already have an array element with items, we bail
|
102 | if (isEmptyArrayType(mem) && existingMember.type.kind === ts.SyntaxKind.ArrayType && (!isEmptyArrayType(existingMember))) {
|
103 | return;
|
104 | }
|
105 | const memberNodes = [existingMember.type, mem.type];
|
106 | updatedMember.type = ts.createUnionOrIntersectionTypeNode(ts.SyntaxKind.UnionType, memberNodes);
|
107 | newMembers[existingIndex] = updatedMember;
|
108 | }
|
109 | }
|
110 | }
|
111 | // console.log(ts.compareDataObjects(existingMember, mem));
|
112 | }
|
113 | });
|
114 |
|
115 | int.members = newMembers;
|
116 | return int;
|
117 | }
|
118 | }
|
119 | return int;
|
120 | });
|
121 | } else {
|
122 | // console.log('Agressive merge here?')
|
123 | }
|
124 |
|
125 | return acc.concat(current);
|
126 | }, []);
|
127 | } |
\ | No newline at end of file |