UNPKG

8.44 kBPlain TextView Raw
1import * as tsm from 'ts-morph';
2import {
3 isClassMethodDeclaration,
4 isClassPropertyDeclaration,
5} from '../types/declaration-type-guards';
6import {
7 ClassConstructorDeclaration,
8 ClassDeclaration,
9 ClassMemberDeclarations,
10 ClassMethodDeclaration,
11 ClassPropertyDeclaration,
12 DeclarationKinds,
13} from '../types/module-declarations';
14import { formatClassMember } from './format';
15import { getApparentType } from './get-apparent-type';
16import { getJSDocs } from './get-jsdocs';
17import { getModifiersText } from './get-modifiers-text';
18import { getWrapperSignature } from './get-wrapper-signature';
19import { isInternalDeclaration } from './is-internal-declaration';
20import { sortByID } from './sort-by-id';
21import { SourceProvider } from './source-provider';
22import { toID } from './to-id';
23import { TypeChecker } from './type-checker';
24
25export function isClass(
26 declaration: tsm.Node
27): declaration is tsm.ClassDeclaration {
28 return tsm.Node.isClassDeclaration(declaration);
29}
30
31export function newClass({
32 id,
33 name,
34 declaration,
35 getSource,
36 getType,
37}: {
38 id: string;
39 name: string;
40 declaration: tsm.ClassDeclaration;
41 getSource: SourceProvider;
42 getType: TypeChecker;
43}): ClassDeclaration {
44 const kind = DeclarationKinds.ClassDeclaration;
45 const docs = getJSDocs({ declaration });
46 const source = getSource({ declaration });
47 const isAbstract = declaration.isAbstract();
48 const signature = getWrapperSignature({ declaration });
49
50 const constructors = getClassConstructors({
51 classID: id,
52 classDeclaration: declaration,
53 getSource,
54 });
55
56 const members = getClassMembers({
57 classID: id,
58 classDeclaration: declaration,
59 getSource,
60 getType,
61 });
62
63 return {
64 kind,
65 id,
66 name,
67 docs,
68 source,
69 signature,
70 isAbstract,
71 constructors,
72 members,
73 };
74}
75
76function getClassConstructors({
77 classID,
78 classDeclaration,
79 getSource,
80}: {
81 classID: string;
82 classDeclaration: tsm.ClassDeclaration;
83 getSource: SourceProvider;
84}): ClassConstructorDeclaration[] {
85 // `getConstructors()` returns all constructors for ambient modules
86 // but only the implementation constructor in normal modules
87 const declaration = classDeclaration.getConstructors()[0];
88 if (!declaration) {
89 return [];
90 }
91
92 // Manually retrieve all constructors from the first constructor
93 const overloads = declaration.getOverloads();
94 const implementation = declaration.getImplementation();
95 const constructors = [
96 ...overloads,
97 ...(implementation ? [implementation] : []),
98 ];
99
100 return constructors.flatMap((declaration, index) => {
101 if (isInternalDeclaration({ declaration })) {
102 return [];
103 }
104
105 const kind = DeclarationKinds.ClassConstructorDeclaration;
106 const name = 'constructor';
107 const id = toID(classID, `${index}-${name}`);
108 const docs = getJSDocs({ declaration });
109 const source = getSource({ declaration });
110 const signature = getClassConstructorSignature({ declaration });
111
112 return {
113 kind,
114 id,
115 name,
116 docs,
117 source,
118 signature,
119 };
120 });
121}
122
123function getClassConstructorSignature({
124 declaration,
125}: {
126 declaration: tsm.ConstructorDeclaration;
127}): string {
128 const modifiers = declaration
129 .getModifiers()
130 .map((modifier) => modifier.getText())
131 .join(' ');
132
133 const params = declaration
134 .getParameters()
135 .map((param) => {
136 const name = param.getName();
137 const type = getApparentType({ declaration: param });
138 const isRest = param.isRestParameter();
139 const dotsToken = isRest ? '...' : '';
140 const isOptional = param.isOptional();
141 const questionToken = !isRest && isOptional ? '?' : '';
142 return `${dotsToken}${name}${questionToken}: ${type}`;
143 })
144 .join(',');
145
146 const signature = `${modifiers} constructor(${params});`;
147 return formatClassMember(signature);
148}
149
150function getClassMembers({
151 classID,
152 classDeclaration,
153 getSource,
154 getType,
155}: {
156 classID: string;
157 classDeclaration: tsm.ClassDeclaration;
158 getSource: SourceProvider;
159 getType: TypeChecker;
160}): ClassMemberDeclarations {
161 const seenMethods = new Set<string>();
162 const members = [
163 ...classDeclaration.getStaticMembers(),
164 ...classDeclaration.getInstanceMembers(),
165 ]
166 .flatMap((declaration) => {
167 const name = declaration.getName();
168 const id = toID(classID, name);
169
170 if (isInternalDeclaration({ declaration, name })) {
171 return [];
172 }
173
174 if (
175 tsm.Node.isPropertyDeclaration(declaration) ||
176 tsm.Node.isParameterDeclaration(declaration)
177 ) {
178 return newProperty({ id, name, declaration, getSource });
179 }
180
181 if (tsm.Node.isGetAccessorDeclaration(declaration)) {
182 return newGetAccessor({ id, name, declaration, getSource });
183 }
184
185 if (tsm.Node.isMethodDeclaration(declaration)) {
186 // Skip overloaded methods
187 if (seenMethods.has(id)) {
188 return [];
189 }
190
191 seenMethods.add(id);
192 return newMethod({ id, name, declaration, getSource, getType });
193 }
194
195 return [];
196 })
197 .sort(sortByID);
198
199 return {
200 properties: members.filter(isClassPropertyDeclaration),
201 methods: members.filter(isClassMethodDeclaration),
202 };
203}
204
205function newProperty({
206 id,
207 name,
208 declaration,
209 getSource,
210}: {
211 id: string;
212 name: string;
213 declaration: tsm.PropertyDeclaration | tsm.ParameterDeclaration;
214 getSource: SourceProvider;
215}): ClassPropertyDeclaration {
216 const kind = DeclarationKinds.ClassPropertyDeclaration;
217 const docs = getJSDocs({ declaration });
218 const source = getSource({ declaration });
219 const modifiersText = getModifiersText({ declaration });
220 const isStatic =
221 tsm.Node.isPropertyDeclaration(declaration) && declaration.isStatic();
222 const isOptional = declaration.hasQuestionToken();
223 const optionalText = isOptional ? '?' : '';
224 const type = getApparentType({ declaration });
225 const signature = formatClassMember(
226 `${modifiersText} ${name} ${optionalText}: ${type}`
227 );
228
229 return {
230 kind,
231 id,
232 name,
233 docs,
234 source,
235 signature,
236 isStatic,
237 type,
238 };
239}
240
241function newGetAccessor({
242 id,
243 name,
244 declaration,
245 getSource,
246}: {
247 id: string;
248 name: string;
249 declaration: tsm.GetAccessorDeclaration;
250 getSource: SourceProvider;
251}): ClassPropertyDeclaration {
252 const kind = DeclarationKinds.ClassPropertyDeclaration;
253 const docs = getJSDocs({ declaration });
254 const source = getSource({ declaration });
255 const isStatic = declaration.isStatic();
256 const isReadonly = declaration.getSetAccessor() === undefined;
257 const type = getApparentType({ declaration: declaration });
258 const staticText = isStatic ? 'static' : '';
259 const readonlyText = isReadonly ? 'readonly' : '';
260 const signature = formatClassMember(
261 `${staticText} ${readonlyText} ${name}: ${type}`
262 );
263
264 return {
265 kind,
266 id,
267 name,
268 docs,
269 source,
270 signature,
271 isStatic,
272 type,
273 };
274}
275
276function newMethod({
277 id,
278 name,
279 declaration,
280 getSource,
281 getType,
282}: {
283 id: string;
284 name: string;
285 declaration: tsm.MethodDeclaration;
286 getSource: SourceProvider;
287 getType: TypeChecker;
288}): ClassMethodDeclaration {
289 const kind = DeclarationKinds.ClassMethodDeclaration;
290 const docs = getJSDocs({ declaration });
291 const source = getSource({ declaration });
292 const isStatic = declaration.isStatic();
293 const modifiersText = getModifiersText({ declaration });
294 const type = getType({ declaration });
295 const signature = formatClassMember(`${modifiersText} ${name}: ${type}`);
296
297 return {
298 kind,
299 id,
300 name,
301 docs,
302 source,
303 signature,
304 isStatic,
305 type,
306 };
307}