UNPKG

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