1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
16 | return new (P || (P = Promise))(function (resolve, reject) {
|
17 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
18 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
19 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
20 | step((generator = generator.apply(thisArg, _arguments || [])).next());
|
21 | });
|
22 | };
|
23 | Object.defineProperty(exports, "__esModule", { value: true });
|
24 | const generator_1 = require("@babel/generator");
|
25 | const babel = require("@babel/types");
|
26 | const doctrine = require("doctrine");
|
27 | const model_1 = require("../model/model");
|
28 | const declaration_property_handlers_1 = require("../polymer/declaration-property-handlers");
|
29 | const polymer_element_1 = require("../polymer/polymer-element");
|
30 | const polymer2_config_1 = require("../polymer/polymer2-config");
|
31 | const polymer2_mixin_scanner_1 = require("../polymer/polymer2-mixin-scanner");
|
32 | const astValue = require("./ast-value");
|
33 | const ast_value_1 = require("./ast-value");
|
34 | const esutil = require("./esutil");
|
35 | const esutil_1 = require("./esutil");
|
36 | const jsdoc = require("./jsdoc");
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 | class ClassScanner {
|
48 | scan(document, visit) {
|
49 | return __awaiter(this, void 0, void 0, function* () {
|
50 | const classFinder = new ClassFinder(document);
|
51 | const elementDefinitionFinder = new CustomElementsDefineCallFinder(document);
|
52 | const prototypeMemberFinder = new PrototypeMemberFinder(document);
|
53 | yield visit(prototypeMemberFinder);
|
54 | const mixinFinder = new polymer2_mixin_scanner_1.MixinVisitor(document, prototypeMemberFinder);
|
55 |
|
56 | yield Promise.all([
|
57 | visit(classFinder),
|
58 | visit(elementDefinitionFinder),
|
59 | visit(mixinFinder),
|
60 | ]);
|
61 | const mixins = mixinFinder.mixins;
|
62 | const elementDefinitionsByClassName = new Map();
|
63 |
|
64 |
|
65 | const elementDefinitionsByClassExpression = new Map();
|
66 | for (const defineCall of elementDefinitionFinder.calls) {
|
67 |
|
68 |
|
69 | if (defineCall.class_.type === 'MaybeChainedIdentifier') {
|
70 | elementDefinitionsByClassName.set(defineCall.class_.name, defineCall);
|
71 | }
|
72 | else {
|
73 | elementDefinitionsByClassExpression.set(defineCall.class_, defineCall);
|
74 | }
|
75 | }
|
76 |
|
77 |
|
78 | const mixinClassExpressions = new Set();
|
79 | for (const mixin of mixins) {
|
80 | if (mixin.classAstNode) {
|
81 | mixinClassExpressions.add(mixin.classAstNode);
|
82 | }
|
83 | }
|
84 |
|
85 | const customElements = [];
|
86 | const normalClasses = [];
|
87 | const classMap = new Map();
|
88 | for (const class_ of classFinder.classes) {
|
89 | if (class_.astNode.language === 'js' &&
|
90 | mixinClassExpressions.has(class_.astNode.node)) {
|
91 |
|
92 | continue;
|
93 | }
|
94 | if (class_.name) {
|
95 | classMap.set(class_.name, class_);
|
96 | }
|
97 |
|
98 | if (babel.isClassExpression(class_.astNode.node)) {
|
99 | const definition = elementDefinitionsByClassExpression.get(class_.astNode.node);
|
100 | if (definition) {
|
101 | customElements.push({ class_, definition });
|
102 | continue;
|
103 | }
|
104 | }
|
105 |
|
106 | const definition = elementDefinitionsByClassName.get(class_.name) ||
|
107 | elementDefinitionsByClassName.get(class_.localName);
|
108 | if (definition) {
|
109 | customElements.push({ class_, definition });
|
110 | continue;
|
111 | }
|
112 |
|
113 |
|
114 | if (jsdoc.hasTag(class_.jsdoc, 'customElement') ||
|
115 | jsdoc.hasTag(class_.jsdoc, 'polymerElement')) {
|
116 | customElements.push({ class_ });
|
117 | continue;
|
118 | }
|
119 |
|
120 | normalClasses.push(class_);
|
121 | }
|
122 | for (const [name, members] of prototypeMemberFinder.members) {
|
123 | if (classMap.has(name)) {
|
124 | const cls = classMap.get(name);
|
125 | cls.finishInitialization(members.methods, members.properties);
|
126 | }
|
127 | }
|
128 | const scannedFeatures = [];
|
129 | for (const element of customElements) {
|
130 | scannedFeatures.push(this._makeElementFeature(element, document));
|
131 | }
|
132 | for (const scannedClass of normalClasses) {
|
133 | scannedFeatures.push(scannedClass);
|
134 | }
|
135 | for (const mixin of mixins) {
|
136 | scannedFeatures.push(mixin);
|
137 | }
|
138 | const collapsedClasses = this.collapseEphemeralSuperclasses(scannedFeatures, classFinder);
|
139 | return {
|
140 | features: collapsedClasses,
|
141 | warnings: [
|
142 | ...elementDefinitionFinder.warnings,
|
143 | ...classFinder.warnings,
|
144 | ...mixinFinder.warnings,
|
145 | ]
|
146 | };
|
147 | });
|
148 | }
|
149 | |
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 | collapseEphemeralSuperclasses(allClasses, classFinder) {
|
159 | const possibleEphemeralsById = new Map();
|
160 | const classesBySuperClassId = new Map();
|
161 | for (const cls of allClasses) {
|
162 | if (cls.name === undefined) {
|
163 | continue;
|
164 | }
|
165 | if (classFinder.fromVariableDeclarators.has(cls) &&
|
166 | cls.privacy === 'private') {
|
167 | possibleEphemeralsById.set(cls.name, cls);
|
168 | }
|
169 | if (cls.superClass !== undefined) {
|
170 | const superClassId = cls.superClass.identifier;
|
171 | const childClasses = classesBySuperClassId.get(superClassId);
|
172 | if (childClasses === undefined) {
|
173 | classesBySuperClassId.set(superClassId, [cls]);
|
174 | }
|
175 | else {
|
176 | childClasses.push(cls);
|
177 | }
|
178 | }
|
179 | }
|
180 | const ephemerals = new Set();
|
181 | for (const [superClassId, childClasses] of classesBySuperClassId) {
|
182 | const superClass = possibleEphemeralsById.get(superClassId);
|
183 | if (superClass === undefined) {
|
184 | continue;
|
185 | }
|
186 | let isEphemeral = false;
|
187 | for (const childClass of childClasses) {
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 | childClass.superClass = superClass.superClass;
|
194 | childClass.mixins.push(...superClass.mixins);
|
195 | isEphemeral = true;
|
196 | }
|
197 | if (isEphemeral) {
|
198 | ephemerals.add(superClass);
|
199 | }
|
200 | }
|
201 | return allClasses.filter((cls) => !ephemerals.has(cls));
|
202 | }
|
203 | _makeElementFeature(element, document) {
|
204 | const class_ = element.class_;
|
205 | const astNode = element.class_.astNode;
|
206 | const docs = element.class_.jsdoc;
|
207 | const customElementTag = jsdoc.getTag(class_.jsdoc, 'customElement');
|
208 | let tagName = undefined;
|
209 | if (element.definition &&
|
210 | element.definition.tagName.type === 'string-literal') {
|
211 | tagName = element.definition.tagName.value;
|
212 | }
|
213 | else if (customElementTag && customElementTag.description) {
|
214 | tagName = customElementTag.description;
|
215 | }
|
216 | else if (babel.isClassExpression(astNode.node) ||
|
217 | babel.isClassDeclaration(astNode.node)) {
|
218 | tagName = polymer2_config_1.getIsValue(astNode.node);
|
219 | }
|
220 | let warnings = [];
|
221 | let scannedElement;
|
222 | let methods = new Map();
|
223 | let staticMethods = new Map();
|
224 | let observers = [];
|
225 |
|
226 |
|
227 | if (babel.isClassExpression(astNode.node) ||
|
228 | babel.isClassDeclaration(astNode.node)) {
|
229 | const observersResult = this._getObservers(astNode.node, document);
|
230 | observers = [];
|
231 | if (observersResult) {
|
232 | observers = observersResult.observers;
|
233 | warnings = warnings.concat(observersResult.warnings);
|
234 | }
|
235 | const polymerProps = polymer2_config_1.getPolymerProperties(astNode.node, document);
|
236 | for (const prop of polymerProps) {
|
237 | const constructorProp = class_.properties.get(prop.name);
|
238 | let finalProp;
|
239 | if (constructorProp) {
|
240 | finalProp = polymer_element_1.mergePropertyDeclarations(constructorProp, prop);
|
241 | }
|
242 | else {
|
243 | finalProp = prop;
|
244 | }
|
245 | class_.properties.set(prop.name, finalProp);
|
246 | }
|
247 | methods = esutil_1.getMethods(astNode.node, document);
|
248 | staticMethods = esutil_1.getStaticMethods(astNode.node, document);
|
249 | }
|
250 | const extends_ = getExtendsTypeName(docs);
|
251 |
|
252 | scannedElement = new polymer_element_1.ScannedPolymerElement({
|
253 | className: class_.name,
|
254 | tagName,
|
255 | astNode,
|
256 | statementAst: class_.statementAst,
|
257 | properties: [...class_.properties.values()],
|
258 | methods,
|
259 | staticMethods,
|
260 | observers,
|
261 | events: astNode.language === 'js' ?
|
262 | esutil.getEventComments(astNode.node) :
|
263 | new Map(),
|
264 | attributes: new Map(),
|
265 | behaviors: [],
|
266 | extends: extends_,
|
267 | listeners: [],
|
268 | description: class_.description,
|
269 | sourceRange: class_.sourceRange,
|
270 | superClass: class_.superClass,
|
271 | jsdoc: class_.jsdoc,
|
272 | abstract: class_.abstract,
|
273 | mixins: class_.mixins,
|
274 | privacy: class_.privacy,
|
275 | isLegacyFactoryCall: false,
|
276 | });
|
277 | if (babel.isClassExpression(astNode.node) ||
|
278 | babel.isClassDeclaration(astNode.node)) {
|
279 | const observedAttributes = this._getObservedAttributes(astNode.node, document);
|
280 | if (observedAttributes != null) {
|
281 |
|
282 |
|
283 |
|
284 | scannedElement.attributes.clear();
|
285 | for (const attr of observedAttributes) {
|
286 | scannedElement.attributes.set(attr.name, attr);
|
287 | }
|
288 | }
|
289 | }
|
290 | warnings.forEach((w) => scannedElement.warnings.push(w));
|
291 | return scannedElement;
|
292 | }
|
293 | _getObservers(node, document) {
|
294 | const returnedValue = polymer2_config_1.getStaticGetterValue(node, 'observers');
|
295 | if (returnedValue) {
|
296 | return declaration_property_handlers_1.extractObservers(returnedValue, document);
|
297 | }
|
298 | }
|
299 | _getObservedAttributes(node, document) {
|
300 | const returnedValue = polymer2_config_1.getStaticGetterValue(node, 'observedAttributes');
|
301 | if (returnedValue && babel.isArrayExpression(returnedValue)) {
|
302 | return this._extractAttributesFromObservedAttributes(returnedValue, document);
|
303 | }
|
304 | }
|
305 | |
306 |
|
307 |
|
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 | _extractAttributesFromObservedAttributes(arry, document) {
|
320 | const results = [];
|
321 | for (const expr of arry.elements) {
|
322 | const value = astValue.expressionToValue(expr);
|
323 | if (value && typeof value === 'string') {
|
324 | let description = '';
|
325 | let type = null;
|
326 | const comment = esutil.getAttachedComment(expr);
|
327 | if (comment) {
|
328 | const annotation = jsdoc.parseJsdoc(comment);
|
329 | description = annotation.description || description;
|
330 | const tags = annotation.tags || [];
|
331 | for (const tag of tags) {
|
332 | if (tag.title === 'type') {
|
333 | type = type || doctrine.type.stringify(tag.type);
|
334 | }
|
335 |
|
336 |
|
337 |
|
338 | description = description || tag.description || '';
|
339 | }
|
340 | }
|
341 | const attribute = {
|
342 | name: value,
|
343 | description: description,
|
344 | sourceRange: document.sourceRangeForNode(expr),
|
345 | astNode: { language: 'js', containingDocument: document, node: expr },
|
346 | warnings: [],
|
347 | };
|
348 | if (type) {
|
349 | attribute.type = type;
|
350 | }
|
351 | results.push(attribute);
|
352 | }
|
353 | }
|
354 | return results;
|
355 | }
|
356 | }
|
357 | exports.ClassScanner = ClassScanner;
|
358 | class PrototypeMemberFinder {
|
359 | constructor(document) {
|
360 | this.members = new model_1.MapWithDefault(() => ({
|
361 | methods: new Map(),
|
362 | properties: new Map()
|
363 | }));
|
364 | this._document = document;
|
365 | }
|
366 | enterExpressionStatement(node) {
|
367 | if (babel.isAssignmentExpression(node.expression)) {
|
368 | this._createMemberFromAssignment(node.expression, getJSDocAnnotationForNode(node));
|
369 | }
|
370 | else if (babel.isMemberExpression(node.expression)) {
|
371 | this._createMemberFromMemberExpression(node.expression, getJSDocAnnotationForNode(node));
|
372 | }
|
373 | }
|
374 | _createMemberFromAssignment(node, jsdocAnn) {
|
375 | if (!babel.isMemberExpression(node.left) ||
|
376 | !babel.isMemberExpression(node.left.object) ||
|
377 | !babel.isIdentifier(node.left.property)) {
|
378 | return;
|
379 | }
|
380 | const leftExpr = node.left.object;
|
381 | const leftProperty = node.left.property;
|
382 | const cls = ast_value_1.getIdentifierName(leftExpr.object);
|
383 | if (!cls || ast_value_1.getIdentifierName(leftExpr.property) !== 'prototype') {
|
384 | return;
|
385 | }
|
386 | if (babel.isFunctionExpression(node.right)) {
|
387 | const prop = this._createMethodFromExpression(leftProperty.name, node.right, jsdocAnn);
|
388 | if (prop) {
|
389 | this._addMethodToClass(cls, prop);
|
390 | }
|
391 | }
|
392 | else {
|
393 | const method = this._createPropertyFromExpression(leftProperty.name, node, jsdocAnn);
|
394 | if (method) {
|
395 | this._addPropertyToClass(cls, method);
|
396 | }
|
397 | }
|
398 | }
|
399 | _addMethodToClass(cls, member) {
|
400 | const classMembers = this.members.get(cls);
|
401 | classMembers.methods.set(member.name, member);
|
402 | }
|
403 | _addPropertyToClass(cls, member) {
|
404 | const classMembers = this.members.get(cls);
|
405 | classMembers.properties.set(member.name, member);
|
406 | }
|
407 | _createMemberFromMemberExpression(node, jsdocAnn) {
|
408 | const left = node.object;
|
409 |
|
410 | if (!babel.isIdentifier(node.property) || !babel.isMemberExpression(left) ||
|
411 | ast_value_1.getIdentifierName(left.property) !== 'prototype') {
|
412 | return;
|
413 | }
|
414 | const cls = ast_value_1.getIdentifierName(left.object);
|
415 | if (!cls) {
|
416 | return;
|
417 | }
|
418 | if (jsdoc.hasTag(jsdocAnn, 'function')) {
|
419 | const method = this._createMethodFromExpression(node.property.name, node, jsdocAnn);
|
420 | if (method) {
|
421 | this._addMethodToClass(cls, method);
|
422 | }
|
423 | }
|
424 | else {
|
425 | const prop = this._createPropertyFromExpression(node.property.name, node, jsdocAnn);
|
426 | if (prop) {
|
427 | this._addPropertyToClass(cls, prop);
|
428 | }
|
429 | }
|
430 | }
|
431 | _createPropertyFromExpression(name, node, jsdocAnn) {
|
432 | let description;
|
433 | let type;
|
434 | let readOnly = false;
|
435 | const privacy = esutil_1.getOrInferPrivacy(name, jsdocAnn);
|
436 | const sourceRange = this._document.sourceRangeForNode(node);
|
437 | const warnings = [];
|
438 | if (jsdocAnn) {
|
439 | description = jsdoc.getDescription(jsdocAnn);
|
440 | readOnly = jsdoc.hasTag(jsdocAnn, 'readonly');
|
441 | }
|
442 | let detectedType;
|
443 | if (babel.isAssignmentExpression(node)) {
|
444 | detectedType =
|
445 | esutil_1.getClosureType(node.right, jsdocAnn, sourceRange, this._document);
|
446 | }
|
447 | else {
|
448 | detectedType =
|
449 | esutil_1.getClosureType(node, jsdocAnn, sourceRange, this._document);
|
450 | }
|
451 | if (detectedType.successful) {
|
452 | type = detectedType.value;
|
453 | }
|
454 | else {
|
455 | warnings.push(detectedType.error);
|
456 | type = '?';
|
457 | }
|
458 | return {
|
459 | name,
|
460 | astNode: { language: 'js', containingDocument: this._document, node },
|
461 | type,
|
462 | jsdoc: jsdocAnn,
|
463 | sourceRange,
|
464 | description,
|
465 | privacy,
|
466 | warnings,
|
467 | readOnly,
|
468 | };
|
469 | }
|
470 | _createMethodFromExpression(name, node, jsdocAnn) {
|
471 | let description;
|
472 | let ret;
|
473 | const privacy = esutil_1.getOrInferPrivacy(name, jsdocAnn);
|
474 | const params = new Map();
|
475 | if (jsdocAnn) {
|
476 | description = jsdoc.getDescription(jsdocAnn);
|
477 | ret = esutil_1.getReturnFromAnnotation(jsdocAnn);
|
478 | if (babel.isFunctionExpression(node)) {
|
479 | (node.params || []).forEach((nodeParam) => {
|
480 | const param = esutil_1.toMethodParam(nodeParam, jsdocAnn);
|
481 | params.set(param.name, param);
|
482 | });
|
483 | }
|
484 | else {
|
485 | for (const tag of (jsdocAnn.tags || [])) {
|
486 | if (tag.title !== 'param' || !tag.name) {
|
487 | continue;
|
488 | }
|
489 | let tagType;
|
490 | let tagDescription;
|
491 | if (tag.type) {
|
492 | tagType = doctrine.type.stringify(tag.type);
|
493 | }
|
494 | if (tag.description) {
|
495 | tagDescription = tag.description;
|
496 | }
|
497 | params.set(tag.name, { name: tag.name, type: tagType, description: tagDescription });
|
498 | }
|
499 | }
|
500 | }
|
501 | if (ret === undefined && babel.isFunctionExpression(node)) {
|
502 | ret = esutil_1.inferReturnFromBody(node);
|
503 | }
|
504 | return {
|
505 | name,
|
506 | type: ret !== undefined ? ret.type : undefined,
|
507 | description,
|
508 | sourceRange: this._document.sourceRangeForNode(node),
|
509 | warnings: [],
|
510 | astNode: { language: 'js', containingDocument: this._document, node },
|
511 | jsdoc: jsdocAnn,
|
512 | params: Array.from(params.values()),
|
513 | return: ret,
|
514 | privacy
|
515 | };
|
516 | }
|
517 | }
|
518 | exports.PrototypeMemberFinder = PrototypeMemberFinder;
|
519 |
|
520 |
|
521 |
|
522 | class ClassFinder {
|
523 | constructor(document) {
|
524 | this.classes = [];
|
525 | this.warnings = [];
|
526 | this.fromVariableDeclarators = new Set();
|
527 | this.alreadyMatched = new Set();
|
528 | this._document = document;
|
529 | }
|
530 | enterAssignmentExpression(node, _parent, path) {
|
531 | this.handleGeneralAssignment(astValue.getIdentifierName(node.left), node.right, path);
|
532 | }
|
533 | enterVariableDeclarator(node, _parent, path) {
|
534 | if (node.init) {
|
535 | this.handleGeneralAssignment(astValue.getIdentifierName(node.id), node.init, path);
|
536 | }
|
537 | }
|
538 | enterFunctionDeclaration(node, _parent, path) {
|
539 | this.handleGeneralAssignment(astValue.getIdentifierName(node.id), node.body, path);
|
540 | }
|
541 |
|
542 | handleGeneralAssignment(assignedName, value, path) {
|
543 | const doc = jsdoc.parseJsdoc(esutil.getBestComment(path) || '');
|
544 | if (babel.isClassExpression(value)) {
|
545 | const name = assignedName ||
|
546 | value.id && astValue.getIdentifierName(value.id) || undefined;
|
547 | this._classFound(name, doc, value, path);
|
548 | }
|
549 | else if (jsdoc.hasTag(doc, 'constructor') ||
|
550 |
|
551 | jsdoc.hasTag(doc, 'customElement') ||
|
552 | jsdoc.hasTag(doc, 'polymerElement')) {
|
553 | this._classFound(assignedName, doc, value, path);
|
554 | }
|
555 | }
|
556 | enterClassExpression(node, parent, path) {
|
557 |
|
558 |
|
559 |
|
560 |
|
561 | if (this.alreadyMatched.has(node)) {
|
562 | return;
|
563 | }
|
564 | const name = node.id ? astValue.getIdentifierName(node.id) : undefined;
|
565 | const comment = esutil.getAttachedComment(node) ||
|
566 | esutil.getAttachedComment(parent) || '';
|
567 | this._classFound(name, jsdoc.parseJsdoc(comment), node, path);
|
568 | }
|
569 | enterClassDeclaration(node, parent, path) {
|
570 | const name = astValue.getIdentifierName(node.id);
|
571 | const comment = esutil.getAttachedComment(node) ||
|
572 | esutil.getAttachedComment(parent) || '';
|
573 | this._classFound(name, jsdoc.parseJsdoc(comment), node, path);
|
574 | }
|
575 | _classFound(name, doc, astNode, path) {
|
576 | const namespacedName = name && ast_value_1.getNamespacedIdentifier(name, doc);
|
577 | const warnings = [];
|
578 | const properties = extractPropertiesFromClass(astNode, this._document);
|
579 | const methods = esutil_1.getMethods(astNode, this._document);
|
580 | const constructorMethod = esutil_1.getConstructorMethod(astNode, this._document);
|
581 | const scannedClass = new model_1.ScannedClass(namespacedName, name, { language: 'js', containingDocument: this._document, node: astNode }, esutil.getCanonicalStatement(path), doc, (doc.description || '').trim(), this._document.sourceRangeForNode(astNode), properties, methods, constructorMethod, esutil_1.getStaticMethods(astNode, this._document), this._getExtends(astNode, doc, warnings, this._document, path), jsdoc.getMixinApplications(this._document, astNode, doc, warnings, path), esutil_1.getOrInferPrivacy(namespacedName || '', doc), warnings, jsdoc.hasTag(doc, 'abstract'), jsdoc.extractDemos(doc));
|
582 | this.classes.push(scannedClass);
|
583 | if (babel.isVariableDeclarator(path.node)) {
|
584 | this.fromVariableDeclarators.add(scannedClass);
|
585 | }
|
586 | if (babel.isClassExpression(astNode)) {
|
587 | this.alreadyMatched.add(astNode);
|
588 | }
|
589 | }
|
590 | |
591 |
|
592 |
|
593 | _getExtends(node, docs, warnings, document, path) {
|
594 | const extendsId = getExtendsTypeName(docs);
|
595 |
|
596 | if (extendsId !== undefined) {
|
597 |
|
598 | const sourceRange = document.sourceRangeForNode(node);
|
599 | if (extendsId == null) {
|
600 | warnings.push(new model_1.Warning({
|
601 | code: 'class-extends-annotation-no-id',
|
602 | message: '@extends annotation with no identifier',
|
603 | severity: model_1.Severity.WARNING,
|
604 | sourceRange,
|
605 | parsedDocument: this._document
|
606 | }));
|
607 | }
|
608 | else {
|
609 | return new model_1.ScannedReference('class', extendsId, sourceRange, undefined, path);
|
610 | }
|
611 | }
|
612 | else if (babel.isClassDeclaration(node) || babel.isClassExpression(node)) {
|
613 |
|
614 | const superClass = node.superClass;
|
615 | if (superClass != null) {
|
616 | let extendsId = ast_value_1.getIdentifierName(superClass);
|
617 | if (extendsId != null) {
|
618 | if (extendsId.startsWith('window.')) {
|
619 | extendsId = extendsId.substring('window.'.length);
|
620 | }
|
621 | const sourceRange = document.sourceRangeForNode(superClass);
|
622 | return new model_1.ScannedReference('class', extendsId, sourceRange, {
|
623 | language: 'js',
|
624 | node: node.superClass,
|
625 | containingDocument: document
|
626 | }, path);
|
627 | }
|
628 | }
|
629 | }
|
630 | }
|
631 | }
|
632 |
|
633 | class CustomElementsDefineCallFinder {
|
634 | constructor(document) {
|
635 | this.warnings = [];
|
636 | this.calls = [];
|
637 | this._document = document;
|
638 | }
|
639 | enterCallExpression(node) {
|
640 | const callee = astValue.getIdentifierName(node.callee);
|
641 | if (!(callee === 'window.customElements.define' ||
|
642 | callee === 'customElements.define')) {
|
643 | return;
|
644 | }
|
645 | const tagNameExpression = this._getTagNameExpression(node.arguments[0]);
|
646 | if (tagNameExpression == null) {
|
647 | return;
|
648 | }
|
649 | const elementClassExpression = this._getElementClassExpression(node.arguments[1]);
|
650 | if (elementClassExpression == null) {
|
651 | return;
|
652 | }
|
653 | this.calls.push({ tagName: tagNameExpression, class_: elementClassExpression });
|
654 | }
|
655 | _getTagNameExpression(expression) {
|
656 | if (expression == null) {
|
657 | return;
|
658 | }
|
659 | const tryForLiteralString = astValue.expressionToValue(expression);
|
660 | if (tryForLiteralString != null &&
|
661 | typeof tryForLiteralString === 'string') {
|
662 | return {
|
663 | type: 'string-literal',
|
664 | value: tryForLiteralString,
|
665 | sourceRange: this._document.sourceRangeForNode(expression)
|
666 | };
|
667 | }
|
668 | if (babel.isMemberExpression(expression)) {
|
669 |
|
670 | const isPropertyNameIs = (babel.isIdentifier(expression.property) &&
|
671 | expression.property.name === 'is') ||
|
672 | (astValue.expressionToValue(expression.property) === 'is');
|
673 | const className = astValue.getIdentifierName(expression.object);
|
674 | if (isPropertyNameIs && className) {
|
675 | return {
|
676 | type: 'is',
|
677 | className,
|
678 | classNameSourceRange: this._document.sourceRangeForNode(expression.object)
|
679 | };
|
680 | }
|
681 | }
|
682 | this.warnings.push(new model_1.Warning({
|
683 | code: 'cant-determine-element-tagname',
|
684 | message: `Unable to evaluate this expression down to a definitive string ` +
|
685 | `tagname.`,
|
686 | severity: model_1.Severity.WARNING,
|
687 | sourceRange: this._document.sourceRangeForNode(expression),
|
688 | parsedDocument: this._document
|
689 | }));
|
690 | return undefined;
|
691 | }
|
692 | _getElementClassExpression(elementDefn) {
|
693 | if (elementDefn == null) {
|
694 | return null;
|
695 | }
|
696 | const className = astValue.getIdentifierName(elementDefn);
|
697 | if (className) {
|
698 | return {
|
699 | type: 'MaybeChainedIdentifier',
|
700 | name: className,
|
701 | sourceRange: this._document.sourceRangeForNode(elementDefn)
|
702 | };
|
703 | }
|
704 | if (babel.isClassExpression(elementDefn)) {
|
705 | return elementDefn;
|
706 | }
|
707 | this.warnings.push(new model_1.Warning({
|
708 | code: 'cant-determine-element-class',
|
709 | message: `Unable to evaluate this expression down to a class reference.`,
|
710 | severity: model_1.Severity.WARNING,
|
711 | sourceRange: this._document.sourceRangeForNode(elementDefn),
|
712 | parsedDocument: this._document,
|
713 | }));
|
714 | return null;
|
715 | }
|
716 | }
|
717 | function extractPropertiesFromClass(astNode, document) {
|
718 | const properties = new Map();
|
719 | if (!babel.isClass(astNode)) {
|
720 | return properties;
|
721 | }
|
722 | const construct = esutil_1.getConstructorClassMethod(astNode);
|
723 | if (construct) {
|
724 | const props = extractPropertiesFromConstructor(construct, document);
|
725 | for (const prop of props.values()) {
|
726 | properties.set(prop.name, prop);
|
727 | }
|
728 | }
|
729 | for (const prop of esutil
|
730 | .extractPropertiesFromClassOrObjectBody(astNode, document)
|
731 | .values()) {
|
732 | const existing = properties.get(prop.name);
|
733 | if (!existing) {
|
734 | properties.set(prop.name, prop);
|
735 | }
|
736 | else {
|
737 | properties.set(prop.name, {
|
738 | name: prop.name,
|
739 | astNode: prop.astNode,
|
740 | type: prop.type || existing.type,
|
741 | jsdoc: prop.jsdoc,
|
742 | sourceRange: prop.sourceRange,
|
743 | description: prop.description || existing.description,
|
744 | privacy: prop.privacy || existing.privacy,
|
745 | warnings: prop.warnings,
|
746 | readOnly: prop.readOnly === undefined ?
|
747 | existing.readOnly : prop.readOnly
|
748 | });
|
749 | }
|
750 | }
|
751 | return properties;
|
752 | }
|
753 | exports.extractPropertiesFromClass = extractPropertiesFromClass;
|
754 | function extractPropertyFromExpressionStatement(statement, document) {
|
755 | let name;
|
756 | let astNode;
|
757 | let defaultValue;
|
758 | if (babel.isAssignmentExpression(statement.expression)) {
|
759 |
|
760 |
|
761 |
|
762 | name = getPropertyNameOnThisExpression(statement.expression.left);
|
763 | astNode = statement.expression.left;
|
764 | defaultValue = generator_1.default(statement.expression.right).code;
|
765 | }
|
766 | else if (babel.isMemberExpression(statement.expression)) {
|
767 |
|
768 |
|
769 |
|
770 | name = getPropertyNameOnThisExpression(statement.expression);
|
771 | astNode = statement;
|
772 | }
|
773 | else {
|
774 | return null;
|
775 | }
|
776 | if (name === undefined) {
|
777 | return null;
|
778 | }
|
779 | const annotation = getJSDocAnnotationForNode(statement);
|
780 | if (!annotation) {
|
781 | return null;
|
782 | }
|
783 | return {
|
784 | name,
|
785 | astNode: { language: 'js', containingDocument: document, node: astNode },
|
786 | type: getTypeFromAnnotation(annotation),
|
787 | default: defaultValue,
|
788 | jsdoc: annotation,
|
789 | sourceRange: document.sourceRangeForNode(astNode),
|
790 | description: jsdoc.getDescription(annotation),
|
791 | privacy: esutil_1.getOrInferPrivacy(name, annotation),
|
792 | warnings: [],
|
793 | readOnly: jsdoc.hasTag(annotation, 'const'),
|
794 | };
|
795 | }
|
796 | function extractPropertiesFromConstructor(method, document) {
|
797 | const properties = new Map();
|
798 | for (const statement of method.body.body) {
|
799 | if (!babel.isExpressionStatement(statement)) {
|
800 | continue;
|
801 | }
|
802 | const prop = extractPropertyFromExpressionStatement(statement, document);
|
803 | if (!prop) {
|
804 | continue;
|
805 | }
|
806 | properties.set(prop.name, prop);
|
807 | }
|
808 | return properties;
|
809 | }
|
810 | function getJSDocAnnotationForNode(node) {
|
811 | const comment = esutil.getAttachedComment(node);
|
812 | const jsdocAnn = comment === undefined ? undefined : jsdoc.parseJsdoc(comment);
|
813 | if (!jsdocAnn || jsdocAnn.tags.length === 0) {
|
814 |
|
815 |
|
816 |
|
817 |
|
818 | return undefined;
|
819 | }
|
820 | return jsdocAnn;
|
821 | }
|
822 | function getTypeFromAnnotation(jsdocAnn) {
|
823 | const typeTag = jsdoc.getTag(jsdocAnn, 'type');
|
824 | let type = undefined;
|
825 | if (typeTag && typeTag.type) {
|
826 | type = doctrine.type.stringify(typeTag.type);
|
827 | }
|
828 | return type;
|
829 | }
|
830 | function getPropertyNameOnThisExpression(node) {
|
831 | if (!babel.isMemberExpression(node) || node.computed ||
|
832 | !babel.isThisExpression(node.object) ||
|
833 | !babel.isIdentifier(node.property)) {
|
834 | return;
|
835 | }
|
836 | return node.property.name;
|
837 | }
|
838 |
|
839 |
|
840 |
|
841 |
|
842 | function getExtendsTypeName(docs) {
|
843 | const tag = jsdoc.getTag(docs, 'extends');
|
844 | if (!tag) {
|
845 | return undefined;
|
846 | }
|
847 | return tag.type ? doctrine.type.stringify(tag.type) : tag.name;
|
848 | }
|
849 |
|
\ | No newline at end of file |