UNPKG

7.72 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 ast_value_1 = require("./ast-value");
26const esutil = require("./esutil");
27const jsdoc = require("./jsdoc");
28const namespace_1 = require("./namespace");
29/**
30 * Find namespaces from source code.
31 */
32class NamespaceScanner {
33 scan(document, visit) {
34 return __awaiter(this, void 0, void 0, function* () {
35 const visitor = new NamespaceVisitor(document);
36 yield visit(visitor);
37 return {
38 features: [...visitor.namespaces.values()],
39 warnings: visitor.warnings
40 };
41 });
42 }
43}
44exports.NamespaceScanner = NamespaceScanner;
45class NamespaceVisitor {
46 constructor(document) {
47 this.namespaces = new Map();
48 this.warnings = [];
49 this.document = document;
50 }
51 /**
52 * Look for object declarations with @namespace in the docs.
53 */
54 enterVariableDeclaration(node, _parent) {
55 if (node.declarations.length !== 1) {
56 return; // Ambiguous.
57 }
58 this._initNamespace(node, node.declarations[0].id);
59 }
60 /**
61 * Look for object assignments with @namespace in the docs.
62 */
63 enterAssignmentExpression(node, parent) {
64 this._initNamespace(parent, node.left);
65 }
66 enterObjectProperty(node, _parent) {
67 this._initNamespace(node, node.key);
68 }
69 enterExpressionStatement(node) {
70 if (!babel.isAssignmentExpression(node.expression) &&
71 !babel.isMemberExpression(node.expression)) {
72 return;
73 }
74 const jsdocAnn = jsdoc.parseJsdoc(esutil.getAttachedComment(node) || '');
75 if (!jsdoc.hasTag(jsdocAnn, 'memberof')) {
76 return;
77 }
78 const memberofTag = jsdoc.getTag(jsdocAnn, 'memberof');
79 const namespaceName = memberofTag && memberofTag.description;
80 let prop = undefined;
81 let namespacedIdentifier;
82 if (!namespaceName || !this.namespaces.has(namespaceName)) {
83 return;
84 }
85 if (babel.isAssignmentExpression(node.expression)) {
86 if (babel.isFunctionExpression(node.expression.right)) {
87 return;
88 }
89 namespacedIdentifier = ast_value_1.getIdentifierName(node.expression.left);
90 }
91 else if (babel.isMemberExpression(node.expression)) {
92 namespacedIdentifier = ast_value_1.getIdentifierName(node.expression);
93 }
94 if (!namespacedIdentifier ||
95 namespacedIdentifier.indexOf('.prototype.') !== -1) {
96 return;
97 }
98 const namespace = this.namespaces.get(namespaceName);
99 const name = namespacedIdentifier.substring(namespacedIdentifier.lastIndexOf('.') + 1);
100 prop = this._createPropertyFromExpression(name, node.expression, jsdocAnn);
101 if (prop) {
102 namespace.properties.set(name, prop);
103 }
104 }
105 _createPropertyFromExpression(name, node, jsdocAnn) {
106 let description;
107 let type;
108 let readOnly = false;
109 const privacy = esutil.getOrInferPrivacy(name, jsdocAnn);
110 const sourceRange = this.document.sourceRangeForNode(node);
111 const warnings = [];
112 if (jsdocAnn) {
113 description = jsdoc.getDescription(jsdocAnn);
114 readOnly = jsdoc.hasTag(jsdocAnn, 'readonly');
115 }
116 let detectedType;
117 if (babel.isAssignmentExpression(node)) {
118 detectedType = esutil.getClosureType(node.right, jsdocAnn, sourceRange, this.document);
119 }
120 else {
121 detectedType =
122 esutil.getClosureType(node, jsdocAnn, sourceRange, this.document);
123 }
124 if (detectedType.successful) {
125 type = detectedType.value;
126 }
127 else {
128 warnings.push(detectedType.error);
129 type = '?';
130 }
131 return {
132 name,
133 astNode: { language: 'js', node, containingDocument: this.document },
134 type,
135 jsdoc: jsdocAnn,
136 sourceRange,
137 description,
138 privacy,
139 warnings,
140 readOnly,
141 };
142 }
143 _initNamespace(node, nameNode) {
144 const comment = esutil.getAttachedComment(node);
145 // Quickly filter down to potential candidates.
146 if (!comment || comment.indexOf('@namespace') === -1) {
147 return;
148 }
149 const analyzedName = ast_value_1.getIdentifierName(nameNode);
150 const docs = jsdoc.parseJsdoc(comment);
151 const namespaceTag = jsdoc.getTag(docs, 'namespace');
152 const explicitName = namespaceTag && namespaceTag.name;
153 let namespaceName;
154 if (explicitName) {
155 namespaceName = explicitName;
156 }
157 else if (analyzedName) {
158 namespaceName = ast_value_1.getNamespacedIdentifier(analyzedName, docs);
159 }
160 else {
161 // TODO(fks): Propagate a warning if name could not be determined
162 return;
163 }
164 const sourceRange = this.document.sourceRangeForNode(node);
165 if (!sourceRange) {
166 throw new Error(`Unable to determine sourceRange for @namespace: ${comment}`);
167 }
168 const summaryTag = jsdoc.getTag(docs, 'summary');
169 const summary = (summaryTag && summaryTag.description) || '';
170 const description = docs.description;
171 const properties = getNamespaceProperties(node, this.document);
172 this.namespaces.set(namespaceName, new namespace_1.ScannedNamespace(namespaceName, description, summary, { language: 'js', node, containingDocument: this.document }, properties, docs, sourceRange));
173 }
174}
175/**
176 * Extracts properties from a given namespace node.
177 */
178function getNamespaceProperties(node, document) {
179 const properties = new Map();
180 let child;
181 if (babel.isVariableDeclaration(node)) {
182 if (node.declarations.length !== 1) {
183 return properties;
184 }
185 const declaration = node.declarations[0].init;
186 if (!babel.isObjectExpression(declaration)) {
187 return properties;
188 }
189 child = declaration;
190 }
191 else if (babel.isExpressionStatement(node) &&
192 babel.isAssignmentExpression(node.expression) &&
193 babel.isObjectExpression(node.expression.right)) {
194 child = node.expression.right;
195 }
196 else {
197 return properties;
198 }
199 return esutil.extractPropertiesFromClassOrObjectBody(child, document);
200}
201//# sourceMappingURL=namespace-scanner.js.map
\No newline at end of file