UNPKG

27.4 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
5 * This code may only be used under the BSD style license found at
6 * http://polymer.github.io/LICENSE.txt
7 * The complete set of authors may be found at
8 * http://polymer.github.io/AUTHORS.txt
9 * The complete set of contributors may be found at
10 * http://polymer.github.io/CONTRIBUTORS.txt
11 * Code distributed by Google as part of the polymer project is also
12 * subject to an additional IP rights grant found at
13 * http://polymer.github.io/PATENTS.txt
14 */
15Object.defineProperty(exports, "__esModule", { value: true });
16const generator_1 = require("@babel/generator");
17const traverse_1 = require("@babel/traverse");
18const babel = require("@babel/types");
19const assert = require("assert");
20const doctrine = require("doctrine");
21const util = require("util");
22const model_1 = require("../model/model");
23const docs = require("../polymer/docs");
24const docs_1 = require("../polymer/docs");
25const astValue = require("./ast-value");
26const jsdoc = require("./jsdoc");
27/**
28 * Returns whether a Babel node matches a particular object path.
29 *
30 * e.g. you have a MemberExpression node, and want to see whether it represents
31 * `Foo.Bar.Baz`:
32 * matchesCallExpressio
33 (node, ['Foo', 'Bar', 'Baz'])
34 *
35 * @param {babel.Node} expression The Babel node to match against.
36 * @param {Array<string>} path The path to look for.
37 */
38function matchesCallExpression(expression, path) {
39 if (!expression.property || !expression.object) {
40 return false;
41 }
42 assert(path.length >= 2);
43 if (!babel.isIdentifier(expression.property)) {
44 return false;
45 }
46 // Unravel backwards, make sure properties match each step of the way.
47 if (expression.property.name !== path[path.length - 1]) {
48 return false;
49 }
50 // We've got ourselves a final member expression.
51 if (path.length === 2 && babel.isIdentifier(expression.object)) {
52 return expression.object.name === path[0];
53 }
54 // Nested expressions.
55 if (path.length > 2 && babel.isMemberExpression(expression.object)) {
56 return matchesCallExpression(expression.object, path.slice(0, path.length - 1));
57 }
58 return false;
59}
60exports.matchesCallExpression = matchesCallExpression;
61/**
62 * Given a property or method, return its name, or undefined if that name can't
63 * be determined.
64 */
65function getPropertyName(prop) {
66 const key = prop.key;
67 // {foo: bar} // note that `foo` is not quoted, so it's an identifier
68 if (!prop.computed && babel.isIdentifier(key)) {
69 return key.name;
70 }
71 // Otherwise, try to statically evaluate the expression
72 const keyValue = astValue.expressionToValue(key);
73 if (keyValue !== undefined) {
74 return '' + keyValue;
75 }
76 return undefined;
77}
78exports.getPropertyName = getPropertyName;
79/**
80 * Yields properties and methods, filters out spread expressions or anything
81 * else.
82 */
83function* getSimpleObjectProperties(node) {
84 for (const property of node.properties) {
85 if (babel.isObjectProperty(property) || babel.isObjectMethod(property)) {
86 yield property;
87 }
88 }
89}
90exports.getSimpleObjectProperties = getSimpleObjectProperties;
91/** Like getSimpleObjectProperties but deals with paths. */
92function* getSimpleObjectPropPaths(nodePath) {
93 // tslint:disable-next-line: no-any typings are wrong here
94 const props = nodePath.get('properties');
95 for (const propPath of props) {
96 if (propPath.isObjectProperty() || propPath.isObjectMethod()) {
97 yield propPath;
98 }
99 }
100}
101exports.getSimpleObjectPropPaths = getSimpleObjectPropPaths;
102exports.CLOSURE_CONSTRUCTOR_MAP = new Map([['Boolean', 'boolean'], ['Number', 'number'], ['String', 'string']]);
103const VALID_EXPRESSION_TYPES = new Map([
104 ['ArrayExpression', 'Array'],
105 ['BlockStatement', 'Function'],
106 ['BooleanLiteral', 'boolean'],
107 ['FunctionExpression', 'Function'],
108 ['NullLiteral', 'null'],
109 ['NumericLiteral', 'number'],
110 ['ObjectExpression', 'Object'],
111 ['RegExpLiteral', 'RegExp'],
112 ['StringLiteral', 'string'],
113 ['TemplateLiteral', 'string'],
114]);
115/**
116 * AST expression -> Closure type.
117 *
118 * Accepts literal values, and native constructors.
119 *
120 * @param {Node} node A Babel expression node.
121 * @return {string} The type of that expression, in Closure terms.
122 */
123function getClosureType(node, parsedJsdoc, sourceRange, document) {
124 if (parsedJsdoc) {
125 const typeTag = jsdoc.getTag(parsedJsdoc, 'type');
126 if (typeTag) {
127 return { successful: true, value: doctrine.type.stringify(typeTag.type) };
128 }
129 }
130 const type = VALID_EXPRESSION_TYPES.get(node.type);
131 if (type) {
132 return { successful: true, value: type };
133 }
134 if (babel.isIdentifier(node)) {
135 return {
136 successful: true,
137 value: exports.CLOSURE_CONSTRUCTOR_MAP.get(node.name) || node.name
138 };
139 }
140 const warning = new model_1.Warning({
141 code: 'no-closure-type',
142 message: `Unable to determine closure type for expression of type ` +
143 `${node.type}`,
144 severity: model_1.Severity.WARNING,
145 sourceRange,
146 parsedDocument: document,
147 });
148 return { successful: false, error: warning };
149}
150exports.getClosureType = getClosureType;
151/**
152 * Tries to find the comment for the given node.
153 *
154 * Will look up the tree at comments on parents as appropriate, but should
155 * not look at unrelated nodes. Stops at the nearest statement boundary.
156 */
157function getBestComment(nodePath) {
158 const maybeComment = getAttachedComment(nodePath.node);
159 if (maybeComment !== undefined) {
160 return maybeComment;
161 }
162 const parent = nodePath.parentPath;
163 if (parent === undefined) {
164 return undefined;
165 }
166 if (!isStatementWithUniqueStatementChild(parent.node) &&
167 babel.isStatement(nodePath.node)) {
168 // Don't walk up above the nearest statement.
169 return undefined;
170 }
171 if (babel.isVariableDeclaration(parent.node) &&
172 parent.node.declarations.length !== 1) {
173 // The parent node is multiple declarations. We can't be sure its
174 // comment applies to us.
175 return undefined;
176 }
177 if (parent.isClassBody() || nodePath.isObjectMember()) {
178 // don't go above an object or class member.
179 return undefined;
180 }
181 return getBestComment(parent);
182}
183exports.getBestComment = getBestComment;
184function getAttachedComment(node) {
185 const comments = getLeadingComments(node) || [];
186 return comments && comments[comments.length - 1];
187}
188exports.getAttachedComment = getAttachedComment;
189/**
190 * Returns all comments from a tree defined with @event.
191 */
192function getEventComments(node) {
193 const eventComments = new Set();
194 traverse_1.default(node, {
195 enter(path) {
196 const node = path.node;
197 [...(node.leadingComments || []), ...(node.trailingComments || [])]
198 .map((commentAST) => commentAST.value)
199 .filter((comment) => comment.indexOf('@event') !== -1)
200 .forEach((comment) => eventComments.add(comment));
201 },
202 noScope: true,
203 });
204 const events = [...eventComments]
205 .map((comment) => docs_1.annotateEvent(jsdoc.parseJsdoc(jsdoc.removeLeadingAsterisks(comment).trim())))
206 .filter((ev) => !!ev)
207 .sort((ev1, ev2) => ev1.name.localeCompare(ev2.name));
208 return new Map(events.map((e) => [e.name, e]));
209}
210exports.getEventComments = getEventComments;
211function getLeadingComments(node) {
212 if (!node) {
213 return;
214 }
215 const comments = [];
216 for (const comment of node.leadingComments || []) {
217 // Espree says any comment that immediately precedes a node is
218 // "leading", but we want to be stricter and require them to be
219 // touching. If we don't have locations for some reason, err on the
220 // side of including the comment.
221 if (!node.loc || !comment.loc ||
222 node.loc.start.line - comment.loc.end.line < 2) {
223 comments.push(comment.value);
224 }
225 }
226 return comments.length ? comments : undefined;
227}
228function getPropertyValue(node, name) {
229 for (const property of getSimpleObjectProperties(node)) {
230 if (getPropertyName(property) === name) {
231 return property.value;
232 }
233 }
234}
235exports.getPropertyValue = getPropertyValue;
236/**
237 * Create a ScannedMethod object from an estree Property AST node.
238 */
239function toScannedMethod(node, sourceRange, document) {
240 const parsedJsdoc = jsdoc.parseJsdoc(getAttachedComment(node) || '');
241 const description = parsedJsdoc.description.trim();
242 const maybeName = getPropertyName(node);
243 const warnings = [];
244 if (!maybeName) {
245 warnings.push(new model_1.Warning({
246 code: 'unknown-method-name',
247 message: `Could not determine name of method from expression of type: ` +
248 `${node.key.type}`,
249 sourceRange: sourceRange,
250 severity: model_1.Severity.INFO,
251 parsedDocument: document
252 }));
253 }
254 const value = babel.isObjectProperty(node) ? node.value : node;
255 const result = getClosureType(value, parsedJsdoc, sourceRange, document);
256 const type = result.successful === true ? result.value : 'Function';
257 const name = maybeName || '';
258 const scannedMethod = {
259 name,
260 type,
261 description,
262 sourceRange,
263 warnings,
264 astNode: { language: 'js', node, containingDocument: document },
265 jsdoc: parsedJsdoc,
266 privacy: getOrInferPrivacy(name, parsedJsdoc)
267 };
268 if (value && babel.isFunction(value)) {
269 if (scannedMethod.jsdoc !== undefined) {
270 scannedMethod.return = getReturnFromAnnotation(scannedMethod.jsdoc);
271 }
272 if (scannedMethod.return === undefined) {
273 scannedMethod.return = inferReturnFromBody(value);
274 }
275 scannedMethod.params =
276 (value.params ||
277 []).map((nodeParam) => toMethodParam(nodeParam, scannedMethod.jsdoc));
278 }
279 return scannedMethod;
280}
281exports.toScannedMethod = toScannedMethod;
282function getReturnFromAnnotation(jsdocAnn) {
283 const tag = jsdoc.getTag(jsdocAnn, 'return') || jsdoc.getTag(jsdocAnn, 'returns');
284 if (!tag || (!tag.type && !tag.description)) {
285 return undefined;
286 }
287 const type = {};
288 if (tag && (tag.type || tag.description)) {
289 if (tag.type) {
290 type.type = doctrine.type.stringify(tag.type);
291 }
292 if (tag.description) {
293 type.desc = tag.description;
294 }
295 }
296 return type;
297}
298exports.getReturnFromAnnotation = getReturnFromAnnotation;
299/**
300 * Examine the body of a function to see if we can infer something about its
301 * return type. This currently only handles the case where a function definitely
302 * returns void.
303 */
304function inferReturnFromBody(node) {
305 if (node.async === true || node.generator === true) {
306 // Async functions always return promises, and generators always return
307 // iterators, so they are never void.
308 return undefined;
309 }
310 if (babel.isArrowFunctionExpression(node) &&
311 !babel.isBlockStatement(node.body)) {
312 // An arrow function that immediately returns a value (e.g. () => 'foo').
313 return undefined;
314 }
315 let returnsVoid = true;
316 traverse_1.default(node, {
317 ReturnStatement(path) {
318 const statement = path.node;
319 // The typings claim that statement.argument is always an Expression, but
320 // actually when there is no argument it is null.
321 if (statement.argument !== null) {
322 returnsVoid = false;
323 path.stop();
324 }
325 },
326 // If this function contains another function, don't traverse into it. Only
327 // return statements in the immediate function scope matter.
328 FunctionDeclaration(path) {
329 path.skip();
330 },
331 FunctionExpression(path) {
332 path.skip();
333 },
334 ClassMethod(path) {
335 path.skip();
336 },
337 ArrowFunctionExpression(path) {
338 path.skip();
339 },
340 ObjectMethod(path) {
341 path.skip();
342 },
343 noScope: true
344 });
345 if (returnsVoid) {
346 return { type: 'void' };
347 }
348 return undefined;
349}
350exports.inferReturnFromBody = inferReturnFromBody;
351function toMethodParam(nodeParam, jsdocAnn) {
352 const paramTags = new Map();
353 let name;
354 let defaultValue;
355 let rest;
356 if (jsdocAnn) {
357 for (const tag of (jsdocAnn.tags || [])) {
358 if (tag.title === 'param' && tag.name) {
359 paramTags.set(tag.name, tag);
360 }
361 }
362 }
363 if (babel.isIdentifier(nodeParam)) {
364 // Basic parameter: method(param)
365 name = nodeParam.name;
366 }
367 else if (babel.isRestElement(nodeParam) &&
368 babel.isIdentifier(nodeParam.argument)) {
369 // Rest parameter: method(...param)
370 name = nodeParam.argument.name;
371 rest = true;
372 }
373 else if (babel.isAssignmentPattern(nodeParam) &&
374 babel.isIdentifier(nodeParam.left)) {
375 // Parameter with a default: method(param = "default")
376 name = nodeParam.left.name;
377 defaultValue = generator_1.default(nodeParam.right).code;
378 }
379 else {
380 // Some AST pattern we don't recognize. Hope the code generator does
381 // something reasonable.
382 name = generator_1.default(nodeParam).code;
383 }
384 let type;
385 let description;
386 const tag = paramTags.get(name);
387 if (tag) {
388 if (tag.type) {
389 type = doctrine.type.stringify(tag.type);
390 }
391 if (tag.description) {
392 description = tag.description;
393 }
394 }
395 const param = { name, type, defaultValue, rest, description };
396 return param;
397}
398exports.toMethodParam = toMethodParam;
399function getOrInferPrivacy(name, annotation, defaultPrivacy = 'public') {
400 const explicitPrivacy = jsdoc.getPrivacy(annotation);
401 const specificName = name.slice(name.lastIndexOf('.') + 1);
402 if (explicitPrivacy) {
403 return explicitPrivacy;
404 }
405 if (specificName.startsWith('__')) {
406 return 'private';
407 }
408 else if (specificName.startsWith('_')) {
409 return 'protected';
410 }
411 else if (specificName.endsWith('_')) {
412 return 'private';
413 }
414 else if (exports.configurationProperties.has(specificName)) {
415 return 'protected';
416 }
417 return defaultPrivacy;
418}
419exports.getOrInferPrivacy = getOrInferPrivacy;
420/**
421 * Properties on element prototypes that are part of the custom elment
422 * lifecycle or Polymer configuration syntax.
423 *
424 * TODO(rictic): only treat the Polymer ones as private when dealing with
425 * Polymer.
426 */
427exports.configurationProperties = new Set([
428 'attached',
429 'attributeChanged',
430 'beforeRegister',
431 'configure',
432 'constructor',
433 'created',
434 'detached',
435 'enableCustomStyleProperties',
436 'extends',
437 'hostAttributes',
438 'is',
439 'listeners',
440 'mixins',
441 'observers',
442 'properties',
443 'ready',
444 'registered',
445]);
446/**
447 * Scan any methods on the given node, if it's a class expression/declaration.
448 */
449function getMethods(node, document) {
450 const methods = new Map();
451 for (const statement of _getMethods(node)) {
452 if (statement.static === false) {
453 const method = toScannedMethod(statement, document.sourceRangeForNode(statement), document);
454 docs.annotate(method);
455 methods.set(method.name, method);
456 }
457 }
458 return methods;
459}
460exports.getMethods = getMethods;
461function getConstructorMethod(astNode, document) {
462 if (!babel.isClass(astNode)) {
463 return;
464 }
465 const statement = getConstructorClassMethod(astNode);
466 if (statement) {
467 const method = toScannedMethod(statement, document.sourceRangeForNode(statement), document);
468 const typeTag = getReturnFromAnnotation(jsdoc.parseJsdoc(getAttachedComment(statement) || ''));
469 if (typeTag) {
470 method.return = Object.assign({}, method.return, typeTag);
471 }
472 else {
473 method.return = undefined;
474 }
475 return method;
476 }
477}
478exports.getConstructorMethod = getConstructorMethod;
479function getConstructorClassMethod(astNode) {
480 for (const member of astNode.body.body) {
481 if (babel.isClassMethod(member) && member.kind === 'constructor') {
482 return member;
483 }
484 }
485}
486exports.getConstructorClassMethod = getConstructorClassMethod;
487/**
488 * Scan any static methods on the given node, if it's a class
489 * expression/declaration.
490 */
491function getStaticMethods(node, document) {
492 const methods = new Map();
493 for (const method of _getMethods(node)) {
494 if (method.static === true) {
495 const scannedMethod = toScannedMethod(method, document.sourceRangeForNode(method), document);
496 docs.annotate(scannedMethod);
497 methods.set(scannedMethod.name, scannedMethod);
498 }
499 }
500 return methods;
501}
502exports.getStaticMethods = getStaticMethods;
503function* _getMethods(node) {
504 if (!babel.isClassDeclaration(node) && !babel.isClassExpression(node)) {
505 return;
506 }
507 for (const statement of node.body.body) {
508 if (babel.isClassMethod(statement) && statement.kind === 'method') {
509 yield statement;
510 }
511 }
512}
513/*
514 * Extracts a property from a given getter or setter method,
515 * whether it be an object method or a class method.
516 */
517function extractPropertyFromGetterOrSetter(method, jsdocAnn, document) {
518 // TODO(43081j): remove this when static properties are supported
519 if (babel.isClassMethod(method) && method.static) {
520 return null;
521 }
522 if (method.kind !== 'get' && method.kind !== 'set') {
523 return null;
524 }
525 // TODO(43081j): use getPropertyName, see
526 // https://github.com/Polymer/polymer-analyzer/pull/867
527 const name = getPropertyName(method);
528 if (name === undefined) {
529 return null;
530 }
531 let type;
532 let description;
533 let privacy = 'public';
534 let readOnly = false;
535 if (jsdocAnn) {
536 const ret = getReturnFromAnnotation(jsdocAnn);
537 type = ret ? ret.type : undefined;
538 description = jsdoc.getDescription(jsdocAnn);
539 privacy = getOrInferPrivacy(name, jsdocAnn);
540 readOnly = jsdoc.hasTag(jsdocAnn, 'readonly');
541 }
542 return {
543 name,
544 astNode: { language: 'js', node: method, containingDocument: document },
545 type,
546 jsdoc: jsdocAnn,
547 sourceRange: document.sourceRangeForNode(method),
548 description,
549 privacy,
550 warnings: [],
551 readOnly,
552 };
553}
554exports.extractPropertyFromGetterOrSetter = extractPropertyFromGetterOrSetter;
555/**
556 * Extracts properties (including accessors) from a given class
557 * or object expression.
558 */
559function extractPropertiesFromClassOrObjectBody(node, document) {
560 const properties = new Map();
561 const accessors = new Map();
562 let body;
563 if (babel.isClass(node)) {
564 body = node.body.body;
565 }
566 else {
567 body = node.properties;
568 }
569 for (const member of body) {
570 if (!babel.isMethod(member) && !babel.isObjectProperty(member)) {
571 continue;
572 }
573 const name = getPropertyName(member);
574 if (name === undefined) {
575 continue;
576 }
577 if (babel.isMethod(member) || babel.isFunction(member.value)) {
578 if (babel.isMethod(member) &&
579 (member.kind === 'get' || member.kind === 'set')) {
580 let accessor = accessors.get(name);
581 if (!accessor) {
582 accessor = {};
583 accessors.set(name, accessor);
584 }
585 if (member.kind === 'get') {
586 accessor.getter = member;
587 }
588 else {
589 accessor.setter = member;
590 }
591 }
592 continue;
593 }
594 const astNode = member.key;
595 const sourceRange = document.sourceRangeForNode(member);
596 const jsdocAnn = jsdoc.parseJsdoc(getAttachedComment(member) || '');
597 const detectedType = getClosureType(member.value, jsdocAnn, sourceRange, document);
598 let type = undefined;
599 if (detectedType.successful) {
600 type = detectedType.value;
601 }
602 properties.set(name, {
603 name,
604 astNode: { language: 'js', node: astNode, containingDocument: document },
605 type,
606 jsdoc: jsdocAnn,
607 sourceRange,
608 description: jsdocAnn ? jsdoc.getDescription(jsdocAnn) : undefined,
609 privacy: getOrInferPrivacy(name, jsdocAnn),
610 warnings: [],
611 readOnly: jsdoc.hasTag(jsdocAnn, 'readonly'),
612 });
613 }
614 for (const val of accessors.values()) {
615 let getter = null;
616 let setter = null;
617 if (val.getter) {
618 const parsedJsdoc = jsdoc.parseJsdoc(getAttachedComment(val.getter) || '');
619 getter =
620 extractPropertyFromGetterOrSetter(val.getter, parsedJsdoc, document);
621 }
622 if (val.setter) {
623 const parsedJsdoc = jsdoc.parseJsdoc(getAttachedComment(val.setter) || '');
624 setter =
625 extractPropertyFromGetterOrSetter(val.setter, parsedJsdoc, document);
626 }
627 const prop = getter || setter;
628 if (!prop) {
629 continue;
630 }
631 if (!prop.readOnly) {
632 prop.readOnly = (val.setter === undefined);
633 }
634 properties.set(prop.name, prop);
635 }
636 return properties;
637}
638exports.extractPropertiesFromClassOrObjectBody = extractPropertiesFromClassOrObjectBody;
639/**
640 * Get the canonical statement or declaration for the given node.
641 *
642 * It would otherwise be difficult, or require specialized code for each kind of
643 * feature, to determine which node is the canonical node for a feature. This
644 * function is simple, it only walks up, and it stops once it reaches a clear
645 * feature boundary. And since we're calling this function both on the indexing
646 * and the lookup sides, we can be confident that both will agree on the same
647 * node.
648 *
649 * There may be more than one feature within a single statement (e.g. `export
650 * class Foo {}` is both a Class and an Export, but between `kind` and `id` we
651 * should still have enough info to narrow down to the intended feature.
652 *
653 * See `DeclaredWithStatement` and `BaseDocumentQuery` to see where this is
654 * used.
655 */
656function getCanonicalStatement(nodePath) {
657 const node = nodePath.node;
658 const parent = nodePath.parentPath;
659 if ((parent && !isStatementWithUniqueStatementChild(parent.node)) &&
660 babel.isStatement(node)) {
661 return node;
662 }
663 if (parent != null) {
664 return getCanonicalStatement(parent);
665 }
666 return undefined;
667}
668exports.getCanonicalStatement = getCanonicalStatement;
669/**
670 * Some statements have many statments as children, like a BlockStatement.
671 *
672 * Some statements have a single unique statement child, like
673 * ExportNamedDeclaration or ExportDefaultDeclaration. When we're talking up the
674 * node tree but we want to stay within a single statement, we don't want to
675 * walk up to a BlockStatement, as that's a group of many statements, but we do
676 * want to walk up to ExportNamedDeclaration.
677 */
678function isStatementWithUniqueStatementChild(node) {
679 return babel.isExportNamedDeclaration(node) ||
680 babel.isExportDefaultDeclaration(node);
681}
682/** What names does a declaration assign to? */
683function* getBindingNamesFromDeclaration(declaration) {
684 if (declaration == null) {
685 return;
686 }
687 switch (declaration.type) {
688 case 'ClassDeclaration':
689 case 'DeclareClass':
690 yield declaration.id.name;
691 break;
692 case 'VariableDeclaration':
693 for (const varDecl of declaration.declarations) {
694 yield* getNamesFromLValue(varDecl.id);
695 }
696 break;
697 case 'FunctionDeclaration':
698 case 'DeclareFunction':
699 case 'DeclareInterface':
700 case 'DeclareTypeAlias':
701 case 'InterfaceDeclaration':
702 case 'DeclareVariable':
703 case 'TypeAlias':
704 yield declaration.id.name;
705 break;
706 case 'ExportAllDeclaration':
707 // Can't do this syntactically. See Export#resolve.
708 break;
709 case 'ExportDefaultDeclaration':
710 yield 'default';
711 break;
712 case 'ExportNamedDeclaration':
713 for (const specifier of declaration.specifiers) {
714 if (specifier.exported.type === 'Identifier') {
715 yield specifier.exported.name;
716 }
717 }
718 yield* getBindingNamesFromDeclaration(declaration.declaration);
719 break;
720 case 'DeclareModule':
721 if (declaration.id.type === 'StringLiteral') {
722 yield declaration.id.value;
723 }
724 else {
725 yield declaration.id.name;
726 }
727 break;
728 case 'ImportDeclaration':
729 for (const specifier of declaration.specifiers) {
730 yield specifier.local.name;
731 }
732 break;
733 default:
734 assertNever(declaration);
735 }
736}
737exports.getBindingNamesFromDeclaration = getBindingNamesFromDeclaration;
738/**
739 * Given an LValue, what are the names it assigns to?
740 *
741 * Internal utility function for getBindingNamesFromDeclaration.
742 */
743function* getNamesFromLValue(lhs) {
744 switch (lhs.type) {
745 case 'Identifier':
746 // x = _;
747 yield lhs.name;
748 break;
749 case 'ArrayPattern':
750 // [a, b, c] = _;
751 for (const element of lhs.elements) {
752 if (babel.isLVal(element)) {
753 yield* getNamesFromLValue(element);
754 }
755 }
756 break;
757 case 'RestElement':
758 // the `...more` part of either
759 // [a, b, ...more] = _;
760 // {a: b, ...more} = _;
761 yield* getNamesFromLValue(lhs.argument);
762 break;
763 case 'MemberExpression':
764 // foo.bar = _;
765 const name = astValue.getIdentifierName(lhs);
766 if (name !== undefined) {
767 yield name;
768 }
769 break;
770 case 'ObjectPattern':
771 // {a: b, c} = _;
772 for (const prop of lhs.properties) {
773 switch (prop.type) {
774 case 'ObjectProperty':
775 // If the property has a 'value' (like)
776 yield* getNamesFromLValue(prop.value);
777 break;
778 case 'RestProperty':
779 yield* getNamesFromLValue(prop.argument);
780 break;
781 default:
782 assertNever(prop);
783 }
784 }
785 break;
786 case 'AssignmentPattern':
787 // var [a = 'defaultVal'] = _;
788 yield* getNamesFromLValue(lhs.left);
789 break;
790 default:
791 assertNever(lhs);
792 }
793}
794function assertNever(never) {
795 throw new Error(`Unexpected ast node: ${util.inspect(never)}`);
796}
797//# sourceMappingURL=esutil.js.map
\No newline at end of file