1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
13 | return new (P || (P = Promise))(function (resolve, reject) {
|
14 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
15 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
16 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
17 | step((generator = generator.apply(thisArg, _arguments || [])).next());
|
18 | });
|
19 | };
|
20 | Object.defineProperty(exports, "__esModule", { value: true });
|
21 | const minimatch = require("minimatch");
|
22 | const path = require("path");
|
23 | const analyzer = require("polymer-analyzer");
|
24 | const closure_types_1 = require("./closure-types");
|
25 | const ts = require("./ts-ast");
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | function generateDeclarations(rootDir, config) {
|
31 | return __awaiter(this, void 0, void 0, function* () {
|
32 | const a = new analyzer.Analyzer({
|
33 | urlLoader: new analyzer.FSUrlLoader(rootDir),
|
34 | urlResolver: new analyzer.PackageUrlResolver(),
|
35 | });
|
36 | const analysis = yield a.analyzePackage();
|
37 | const outFiles = new Map();
|
38 | for (const tsDoc of analyzerToAst(analysis, config, rootDir)) {
|
39 | outFiles.set(tsDoc.path, tsDoc.serialize());
|
40 | }
|
41 | return outFiles;
|
42 | });
|
43 | }
|
44 | exports.generateDeclarations = generateDeclarations;
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | function analyzerToAst(analysis, config, rootDir) {
|
50 | const exclude = (config.exclude || ['test/**', 'demo/**'])
|
51 | .map((p) => new minimatch.Minimatch(p));
|
52 | const addReferences = config.addReferences || {};
|
53 | const removeReferencesResolved = new Set((config.removeReferences || []).map((r) => path.resolve(rootDir, r)));
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 | const declarationDocs = new Map();
|
61 | for (const jsDoc of analysis.getFeatures({ kind: 'js-document' })) {
|
62 | if (exclude.some((r) => r.match(jsDoc.url))) {
|
63 | continue;
|
64 | }
|
65 | const filename = makeDeclarationsFilename(jsDoc.url);
|
66 | let docs = declarationDocs.get(filename);
|
67 | if (!docs) {
|
68 | docs = [];
|
69 | declarationDocs.set(filename, docs);
|
70 | }
|
71 | docs.push(jsDoc);
|
72 | }
|
73 | const tsDocs = [];
|
74 | for (const [declarationsFilename, analyzerDocs] of declarationDocs) {
|
75 | const tsDoc = new ts.Document({
|
76 | path: declarationsFilename,
|
77 | header: makeHeader(analyzerDocs.map((d) => d.url)),
|
78 | });
|
79 | for (const analyzerDoc of analyzerDocs) {
|
80 | handleDocument(analyzerDoc, tsDoc);
|
81 | }
|
82 | for (const ref of tsDoc.referencePaths) {
|
83 | const resolvedRef = path.resolve(rootDir, path.dirname(tsDoc.path), ref);
|
84 | if (removeReferencesResolved.has(resolvedRef)) {
|
85 | tsDoc.referencePaths.delete(ref);
|
86 | }
|
87 | }
|
88 | for (const ref of addReferences[tsDoc.path] || []) {
|
89 | tsDoc.referencePaths.add(path.relative(path.dirname(tsDoc.path), ref));
|
90 | }
|
91 | tsDoc.simplify();
|
92 |
|
93 |
|
94 |
|
95 | tsDocs.push(tsDoc);
|
96 | }
|
97 | return tsDocs;
|
98 | }
|
99 |
|
100 |
|
101 |
|
102 |
|
103 | function makeDeclarationsFilename(sourceUrl) {
|
104 | const parsed = path.parse(sourceUrl);
|
105 | return path.join(parsed.dir, parsed.name) + '.d.ts';
|
106 | }
|
107 |
|
108 |
|
109 |
|
110 | function makeHeader(sourceUrls) {
|
111 | return `DO NOT EDIT
|
112 |
|
113 | This file was automatically generated by
|
114 | https://github.com/Polymer/gen-typescript-declarations
|
115 |
|
116 | To modify these typings, edit the source file(s):
|
117 | ${sourceUrls.map((url) => ' ' + url).join('\n')}`;
|
118 | }
|
119 |
|
120 |
|
121 |
|
122 |
|
123 | function handleDocument(doc, root) {
|
124 | for (const feature of doc.getFeatures()) {
|
125 | if (feature.privacy === 'private') {
|
126 | continue;
|
127 | }
|
128 | if (feature.kinds.has('element')) {
|
129 | handleElement(feature, root);
|
130 | }
|
131 | else if (feature.kinds.has('behavior')) {
|
132 | handleBehavior(feature, root);
|
133 | }
|
134 | else if (feature.kinds.has('element-mixin')) {
|
135 | handleMixin(feature, root);
|
136 | }
|
137 | else if (feature.kinds.has('class')) {
|
138 | handleClass(feature, root);
|
139 | }
|
140 | else if (feature.kinds.has('function')) {
|
141 | handleFunction(feature, root);
|
142 | }
|
143 | else if (feature.kinds.has('namespace')) {
|
144 | handleNamespace(feature, root);
|
145 | }
|
146 | else if (feature.kinds.has('import')) {
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 | if (feature.sourceRange && feature.sourceRange.file === doc.url) {
|
154 | handleImport(feature, root);
|
155 | }
|
156 | }
|
157 | }
|
158 | }
|
159 |
|
160 |
|
161 |
|
162 | function handleElement(feature, root) {
|
163 |
|
164 |
|
165 | let constructable;
|
166 | let fullName;
|
167 | let shortName;
|
168 | let parent;
|
169 | if (feature.className) {
|
170 | constructable = true;
|
171 | let namespacePath;
|
172 | [namespacePath, shortName] = splitReference(feature.className);
|
173 | fullName = feature.className;
|
174 | parent = findOrCreateNamespace(root, namespacePath);
|
175 | }
|
176 | else if (feature.tagName) {
|
177 | constructable = false;
|
178 | shortName = kebabToCamel(feature.tagName);
|
179 | fullName = shortName;
|
180 |
|
181 | parent = root;
|
182 | }
|
183 | else {
|
184 | console.error('Could not find a name.');
|
185 | return;
|
186 | }
|
187 | if (constructable) {
|
188 |
|
189 | const c = new ts.Class({
|
190 | name: shortName,
|
191 | description: feature.description || feature.summary,
|
192 | extends: (feature.extends) ||
|
193 | (isPolymerElement(feature) ? 'Polymer.Element' : 'HTMLElement'),
|
194 | mixins: feature.mixins.map((mixin) => mixin.identifier),
|
195 | properties: handleProperties(feature.properties.values()),
|
196 | methods: handleMethods(feature.methods.values()),
|
197 | });
|
198 | parent.members.push(c);
|
199 | }
|
200 | else {
|
201 |
|
202 |
|
203 |
|
204 | const i = new ts.Interface({
|
205 | name: shortName,
|
206 | description: feature.description || feature.summary,
|
207 | properties: handleProperties(feature.properties.values()),
|
208 | methods: handleMethods(feature.methods.values()),
|
209 | });
|
210 | if (isPolymerElement(feature)) {
|
211 | i.extends.push('Polymer.Element');
|
212 | i.extends.push(...feature.behaviorAssignments.map((behavior) => behavior.name));
|
213 | }
|
214 | parent.members.push(i);
|
215 | }
|
216 |
|
217 |
|
218 |
|
219 |
|
220 | if (feature.tagName) {
|
221 | const elementMap = findOrCreateInterface(root, 'HTMLElementTagNameMap');
|
222 | elementMap.properties.push(new ts.Property({
|
223 | name: feature.tagName,
|
224 | type: new ts.NameType(fullName),
|
225 | }));
|
226 | }
|
227 | }
|
228 |
|
229 |
|
230 |
|
231 |
|
232 | function handleBehavior(feature, root) {
|
233 | if (!feature.className) {
|
234 | console.error('Could not find a name for behavior.');
|
235 | return;
|
236 | }
|
237 | const [namespacePath, className] = splitReference(feature.className);
|
238 | const i = new ts.Interface({ name: className });
|
239 | i.description = feature.description || feature.summary;
|
240 | i.properties = handleProperties(feature.properties.values());
|
241 | i.methods = handleMethods(feature.methods.values());
|
242 | findOrCreateNamespace(root, namespacePath).members.push(i);
|
243 | }
|
244 |
|
245 |
|
246 |
|
247 | function handleMixin(feature, root) {
|
248 | const [namespacePath, name] = splitReference(feature.name);
|
249 | const namespace_ = findOrCreateNamespace(root, namespacePath);
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 | const function_ = new ts.Mixin({ name });
|
256 | function_.description = feature.description;
|
257 | function_.interfaces = [name, ...feature.mixins.map((m) => m.identifier)];
|
258 | namespace_.members.push(function_);
|
259 | const interface_ = new ts.Interface({ name });
|
260 | interface_.properties = handleProperties(feature.properties.values());
|
261 | interface_.methods = handleMethods(feature.methods.values());
|
262 | namespace_.members.push(interface_);
|
263 | }
|
264 |
|
265 |
|
266 |
|
267 | function handleClass(feature, root) {
|
268 | if (!feature.className) {
|
269 | console.error('Could not find a name for class.');
|
270 | return;
|
271 | }
|
272 | const [namespacePath, name] = splitReference(feature.className);
|
273 | const m = new ts.Class({ name });
|
274 | m.description = feature.description;
|
275 | m.properties = handleProperties(feature.properties.values());
|
276 | m.methods = handleMethods(feature.methods.values());
|
277 | findOrCreateNamespace(root, namespacePath).members.push(m);
|
278 | }
|
279 |
|
280 |
|
281 |
|
282 | function handleFunction(feature, root) {
|
283 | const [namespacePath, name] = splitReference(feature.name);
|
284 | const f = new ts.Function({
|
285 | name,
|
286 | description: feature.description || feature.summary,
|
287 | templateTypes: feature.templateTypes,
|
288 | returns: closure_types_1.closureTypeToTypeScript(feature.return && feature.return.type, feature.templateTypes),
|
289 | returnsDescription: feature.return && feature.return.desc
|
290 | });
|
291 | for (const param of feature.params || []) {
|
292 |
|
293 |
|
294 | const { type, optional, rest } = closure_types_1.closureParamToTypeScript(param.type, feature.templateTypes);
|
295 | f.params.push(new ts.Param({ name: param.name, type, optional, rest }));
|
296 | }
|
297 | findOrCreateNamespace(root, namespacePath).members.push(f);
|
298 | }
|
299 |
|
300 |
|
301 |
|
302 |
|
303 | function handleProperties(analyzerProperties) {
|
304 | const tsProperties = [];
|
305 | for (const property of analyzerProperties) {
|
306 | if (property.inheritedFrom || property.privacy === 'private') {
|
307 | continue;
|
308 | }
|
309 | const p = new ts.Property({
|
310 | name: property.name,
|
311 |
|
312 |
|
313 | type: closure_types_1.closureTypeToTypeScript(property.type),
|
314 | });
|
315 | p.description = property.description || '';
|
316 | tsProperties.push(p);
|
317 | }
|
318 | return tsProperties;
|
319 | }
|
320 |
|
321 |
|
322 |
|
323 |
|
324 | function handleMethods(analyzerMethods) {
|
325 | const tsMethods = [];
|
326 | for (const method of analyzerMethods) {
|
327 | if (method.inheritedFrom || method.privacy === 'private') {
|
328 | continue;
|
329 | }
|
330 | const m = new ts.Method({
|
331 | name: method.name,
|
332 | returns: closure_types_1.closureTypeToTypeScript(method.return && method.return.type),
|
333 | returnsDescription: method.return && method.return.desc
|
334 | });
|
335 | m.description = method.description || '';
|
336 | let requiredAhead = false;
|
337 | for (const param of reverseIter(method.params || [])) {
|
338 | let { type, optional, rest } = closure_types_1.closureParamToTypeScript(param.type);
|
339 | if (param.defaultValue !== undefined) {
|
340 |
|
341 |
|
342 |
|
343 |
|
344 | if (!requiredAhead) {
|
345 | optional = true;
|
346 | }
|
347 | else {
|
348 | type = new ts.UnionType([type, ts.undefinedType]);
|
349 | }
|
350 | }
|
351 | else if (!optional) {
|
352 | requiredAhead = true;
|
353 | }
|
354 |
|
355 |
|
356 | rest = rest || !!param.rest;
|
357 | if (rest && type.kind !== 'array') {
|
358 |
|
359 |
|
360 | type = new ts.ArrayType(type);
|
361 | }
|
362 | m.params.unshift(new ts.Param({
|
363 | name: param.name,
|
364 | description: param.description,
|
365 | type,
|
366 | optional,
|
367 | rest
|
368 | }));
|
369 | }
|
370 | tsMethods.push(m);
|
371 | }
|
372 | return tsMethods;
|
373 | }
|
374 |
|
375 |
|
376 |
|
377 | function* reverseIter(arr) {
|
378 | for (let i = arr.length - 1; i >= 0; i--) {
|
379 | yield arr[i];
|
380 | }
|
381 | }
|
382 |
|
383 |
|
384 |
|
385 | function handleNamespace(feature, tsDoc) {
|
386 | const ns = findOrCreateNamespace(tsDoc, feature.name.split('.'));
|
387 | if (ns.kind === 'namespace') {
|
388 | ns.description = feature.description || feature.summary || '';
|
389 | }
|
390 | }
|
391 |
|
392 |
|
393 |
|
394 |
|
395 |
|
396 |
|
397 |
|
398 |
|
399 |
|
400 |
|
401 | function handleImport(feature, tsDoc) {
|
402 | if (!feature.url) {
|
403 | return;
|
404 | }
|
405 |
|
406 |
|
407 |
|
408 |
|
409 |
|
410 |
|
411 |
|
412 |
|
413 |
|
414 |
|
415 | const url = feature.url.replace(/^(bower_components|node_modules)\//, '../');
|
416 | tsDoc.referencePaths.add(path.relative(path.dirname(tsDoc.path), makeDeclarationsFilename(url)));
|
417 | }
|
418 |
|
419 |
|
420 |
|
421 |
|
422 | function findOrCreateNamespace(root, path) {
|
423 | if (!path.length) {
|
424 | return root;
|
425 | }
|
426 | let first;
|
427 | for (const member of root.members) {
|
428 | if (member.kind === 'namespace' && member.name === path[0]) {
|
429 | first = member;
|
430 | break;
|
431 | }
|
432 | }
|
433 | if (!first) {
|
434 | first = new ts.Namespace({ name: path[0] });
|
435 | root.members.push(first);
|
436 | }
|
437 | return findOrCreateNamespace(first, path.slice(1));
|
438 | }
|
439 |
|
440 |
|
441 |
|
442 |
|
443 | function findOrCreateInterface(root, reference) {
|
444 | const [namespacePath, name] = splitReference(reference);
|
445 | const namespace_ = findOrCreateNamespace(root, namespacePath);
|
446 | for (const member of namespace_.members) {
|
447 | if (member.kind === 'interface' && member.name === name) {
|
448 | return member;
|
449 | }
|
450 | }
|
451 | const i = new ts.Interface({ name });
|
452 | namespace_.members.push(i);
|
453 | return i;
|
454 | }
|
455 |
|
456 |
|
457 |
|
458 | function isPolymerElement(feature) {
|
459 | return feature.kinds.has('polymer-element');
|
460 | }
|
461 |
|
462 |
|
463 |
|
464 | function kebabToCamel(s) {
|
465 | return s.replace(/(^|-)(.)/g, (_match, _p0, p1) => p1.toUpperCase());
|
466 | }
|
467 |
|
468 |
|
469 |
|
470 |
|
471 | function splitReference(reference) {
|
472 | const parts = reference.split('.');
|
473 | const namespacePath = parts.slice(0, -1);
|
474 | const name = parts[parts.length - 1];
|
475 | return [namespacePath, name];
|
476 | }
|
477 |
|
\ | No newline at end of file |