UNPKG

8.07 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright (c) 2017 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 */
15var __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};
23Object.defineProperty(exports, "__esModule", { value: true });
24const babel = require("@babel/types");
25const doctrine = require("doctrine");
26const source_range_1 = require("../model/source-range");
27const ast_value_1 = require("./ast-value");
28const esutil_1 = require("./esutil");
29const function_1 = require("./function");
30const jsdoc = require("./jsdoc");
31class FunctionScanner {
32 scan(document, visit) {
33 return __awaiter(this, void 0, void 0, function* () {
34 const visitor = new FunctionVisitor(document);
35 yield visit(visitor);
36 return {
37 features: [...visitor.functions].sort((a, b) => source_range_1.comparePosition(a.sourceRange.start, b.sourceRange.start)),
38 };
39 });
40 }
41}
42exports.FunctionScanner = FunctionScanner;
43class FunctionVisitor {
44 constructor(document) {
45 this.functions = new Set();
46 this.warnings = [];
47 this.document = document;
48 }
49 /**
50 * Scan standalone function declarations.
51 */
52 enterFunctionDeclaration(node, _parent, path) {
53 this.initFunction(node, path, ast_value_1.getIdentifierName(node.id));
54 }
55 /**
56 * Scan object method declarations.
57 */
58 enterObjectMethod(node, _parent, path) {
59 this.initFunction(node, path, ast_value_1.getIdentifierName(node.key));
60 }
61 /**
62 * Scan functions assigned to newly declared variables.
63 */
64 enterVariableDeclaration(node, _parent, path) {
65 if (node.declarations.length !== 1) {
66 return; // Ambiguous.
67 }
68 const declaration = node.declarations[0];
69 const declarationValue = declaration.init;
70 if (declarationValue && babel.isFunction(declarationValue)) {
71 this.initFunction(declarationValue, path, ast_value_1.getIdentifierName(declaration.id));
72 }
73 }
74 /**
75 * Scan functions assigned to variables and object properties.
76 */
77 enterAssignmentExpression(node, _parent, path) {
78 if (babel.isFunction(node.right)) {
79 this.initFunction(node.right, path, ast_value_1.getIdentifierName(node.left));
80 }
81 }
82 /**
83 * Scan functions defined inside of object literals.
84 */
85 enterObjectExpression(_node, _parent, path) {
86 for (const propPath of esutil_1.getSimpleObjectPropPaths(path)) {
87 const prop = propPath.node;
88 const propValue = prop.value;
89 const name = esutil_1.getPropertyName(prop);
90 if (babel.isFunction(propValue)) {
91 this.initFunction(propValue, propPath, name);
92 continue;
93 }
94 const comment = esutil_1.getBestComment(propPath) || '';
95 const docs = jsdoc.parseJsdoc(comment);
96 if (jsdoc.getTag(docs, 'function')) {
97 this.initFunction(prop, propPath, name);
98 continue;
99 }
100 }
101 }
102 initFunction(node, path, analyzedName) {
103 const docs = jsdoc.parseJsdoc(esutil_1.getBestComment(path) || '');
104 // The @function annotation can override the name.
105 const functionTag = jsdoc.getTag(docs, 'function');
106 if (functionTag && functionTag.name) {
107 analyzedName = functionTag.name;
108 }
109 if (!analyzedName) {
110 // TODO(fks): Propagate a warning if name could not be determined
111 return;
112 }
113 if (!jsdoc.hasTag(docs, 'global') && !jsdoc.hasTag(docs, 'memberof') &&
114 !this.isExported(path)) {
115 // Without this check we would emit a lot of functions not worthy of
116 // inclusion. Since we don't do scope analysis, we can't tell when a
117 // function is actually part of an exposed API. Only include functions
118 // that are explicitly @global, or declared as part of some namespace
119 // with @memberof.
120 return;
121 }
122 // TODO(justinfagnani): remove polymerMixin support
123 if (jsdoc.hasTag(docs, 'mixinFunction') ||
124 jsdoc.hasTag(docs, 'polymerMixin')) {
125 // This is a mixin, not a normal function.
126 return;
127 }
128 const functionName = ast_value_1.getNamespacedIdentifier(analyzedName, docs);
129 const sourceRange = this.document.sourceRangeForNode(node);
130 const summaryTag = jsdoc.getTag(docs, 'summary');
131 const summary = (summaryTag && summaryTag.description) || '';
132 const description = docs.description;
133 let functionReturn = esutil_1.getReturnFromAnnotation(docs);
134 if (functionReturn === undefined && babel.isFunction(node)) {
135 functionReturn = esutil_1.inferReturnFromBody(node);
136 }
137 // TODO(justinfagnani): consolidate with similar param processing code in
138 // docs.ts
139 const functionParams = [];
140 const templateTypes = [];
141 for (const tag of docs.tags) {
142 if (tag.title === 'param') {
143 functionParams.push({
144 type: tag.type ? doctrine.type.stringify(tag.type) : 'N/A',
145 desc: tag.description || '',
146 name: tag.name || 'N/A'
147 });
148 }
149 else if (tag.title === 'template') {
150 for (let t of (tag.description || '').split(',')) {
151 t = t.trim();
152 if (t.length > 0) {
153 templateTypes.push(t);
154 }
155 }
156 }
157 }
158 // TODO(fks): parse params directly from `fn`, merge with docs.tags data
159 const specificName = functionName.slice(functionName.lastIndexOf('.') + 1);
160 this.functions.add(new function_1.ScannedFunction(functionName, description, summary, esutil_1.getOrInferPrivacy(specificName, docs), { language: 'js', node, containingDocument: this.document }, docs, sourceRange, functionParams, functionReturn, templateTypes));
161 }
162 isExported(path) {
163 const node = path.node;
164 if (babel.isObjectExpression(node)) {
165 // This function recurses up the AST until it finds an exported statement.
166 // That's a little crude, since being within an exported statement doesn't
167 // necessarily mean the function itself is exported. There are lots of
168 // cases where this fails, but a method defined on an object is a common
169 // one.
170 return false;
171 }
172 if (babel.isStatement(node)) {
173 const parent = path.parent;
174 if (parent && babel.isExportDefaultDeclaration(parent) ||
175 babel.isExportNamedDeclaration(parent)) {
176 return true;
177 }
178 return false;
179 }
180 const parentPath = path.parentPath;
181 if (parentPath == null) {
182 return false;
183 }
184 return this.isExported(parentPath);
185 }
186}
187//# sourceMappingURL=function-scanner.js.map
\No newline at end of file