UNPKG

12.1 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 */
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 assert = require("assert");
26const ast_value_1 = require("../javascript/ast-value");
27const esutil = require("../javascript/esutil");
28const jsdoc = require("../javascript/jsdoc");
29const model_1 = require("../model/model");
30const behavior_1 = require("./behavior");
31const declaration_property_handlers_1 = require("./declaration-property-handlers");
32const docs = require("./docs");
33const templatizer = 'Polymer.Templatizer';
34class BehaviorScanner {
35 scan(document, visit) {
36 return __awaiter(this, void 0, void 0, function* () {
37 const visitor = new BehaviorVisitor(document);
38 yield visit(visitor);
39 return {
40 features: Array.from(visitor.behaviors),
41 warnings: visitor.warnings
42 };
43 });
44 }
45}
46exports.BehaviorScanner = BehaviorScanner;
47class BehaviorVisitor {
48 constructor(document) {
49 /** The behaviors we've found. */
50 this.behaviors = new Set();
51 this.warnings = [];
52 this.currentBehavior = null;
53 this.propertyHandlers = null;
54 this.document = document;
55 }
56 /**
57 * Look for object declarations with @polymerBehavior in the docs.
58 */
59 enterVariableDeclarator(node, _parent, path) {
60 this._initBehavior(node, ast_value_1.getIdentifierName(node.id), path);
61 }
62 enterExportDefaultDeclaration(node, _parent, path) {
63 this._initBehavior(node, 'default', path);
64 }
65 /**
66 * Look for object assignments with @polymerBehavior in the docs.
67 */
68 enterAssignmentExpression(node, parent, path) {
69 this._initBehavior(parent, ast_value_1.getIdentifierName(node.left), path);
70 }
71 /**
72 * We assume that the object expression after such an assignment is the
73 * behavior's declaration. Seems to be a decent assumption for now.
74 */
75 enterObjectExpression(node, _parent) {
76 if (!this.currentBehavior || !this.propertyHandlers) {
77 return;
78 }
79 for (const prop of esutil.getSimpleObjectProperties(node)) {
80 const name = esutil.getPropertyName(prop);
81 if (!name) {
82 this.currentBehavior.warnings.push(new model_1.Warning({
83 code: 'cant-determine-name',
84 message: `Unable to determine property name from expression of type ` +
85 `${node.type}`,
86 severity: model_1.Severity.WARNING,
87 sourceRange: this.document.sourceRangeForNode(node),
88 parsedDocument: this.document
89 }));
90 continue;
91 }
92 if (name in this.propertyHandlers) {
93 this.propertyHandlers[name](prop.value);
94 }
95 else if ((babel.isMethod(prop) && prop.kind === 'method') ||
96 babel.isFunction(prop.value)) {
97 const method = esutil.toScannedMethod(prop, this.document.sourceRangeForNode(prop), this.document);
98 this.currentBehavior.addMethod(method);
99 }
100 }
101 for (const prop of esutil
102 .extractPropertiesFromClassOrObjectBody(node, this.document)
103 .values()) {
104 if (prop.name in this.propertyHandlers) {
105 continue;
106 }
107 this.currentBehavior.addProperty(Object.assign({}, prop, { isConfiguration: esutil.configurationProperties.has(prop.name) }));
108 }
109 this._finishBehavior();
110 }
111 _startBehavior(behavior) {
112 assert(this.currentBehavior == null);
113 this.currentBehavior = behavior;
114 }
115 _finishBehavior() {
116 assert(this.currentBehavior != null);
117 this.behaviors.add(this.currentBehavior);
118 this.currentBehavior = null;
119 }
120 _initBehavior(node, name, path) {
121 if (name === undefined) {
122 return;
123 }
124 const comment = esutil.getBestComment(path);
125 // Quickly filter down to potential candidates.
126 if (!comment || comment.indexOf('@polymerBehavior') === -1) {
127 if (name !== templatizer) {
128 return;
129 }
130 }
131 const parsedJsdocs = jsdoc.parseJsdoc(comment || '');
132 if (!jsdoc.hasTag(parsedJsdocs, 'polymerBehavior')) {
133 if (name !== templatizer) {
134 return;
135 }
136 }
137 this._startBehavior(new behavior_1.ScannedBehavior({
138 astNode: { language: 'js', node, containingDocument: this.document },
139 statementAst: esutil.getCanonicalStatement(path),
140 description: parsedJsdocs.description,
141 events: esutil.getEventComments(node),
142 sourceRange: this.document.sourceRangeForNode(node),
143 privacy: esutil.getOrInferPrivacy(name, parsedJsdocs),
144 abstract: jsdoc.hasTag(parsedJsdocs, 'abstract'),
145 attributes: new Map(),
146 properties: [],
147 behaviors: [],
148 className: undefined,
149 extends: undefined,
150 jsdoc: parsedJsdocs,
151 listeners: [],
152 methods: new Map(),
153 staticMethods: new Map(),
154 mixins: [],
155 observers: [],
156 superClass: undefined,
157 tagName: undefined,
158 isLegacyFactoryCall: false,
159 }));
160 const behavior = this.currentBehavior;
161 this.propertyHandlers =
162 declaration_property_handlers_1.declarationPropertyHandlers(behavior, this.document, path);
163 docs.annotateElementHeader(behavior);
164 const behaviorTag = jsdoc.getTag(behavior.jsdoc, 'polymerBehavior');
165 behavior.className = behaviorTag && behaviorTag.name ||
166 ast_value_1.getNamespacedIdentifier(name, behavior.jsdoc);
167 if (!behavior.className) {
168 throw new Error(`Unable to determine name for @polymerBehavior: ${comment}`);
169 }
170 behavior.privacy =
171 esutil.getOrInferPrivacy(behavior.className, behavior.jsdoc);
172 this._parseChainedBehaviors(node, path);
173 this.currentBehavior = this.mergeBehavior(behavior);
174 this.propertyHandlers =
175 declaration_property_handlers_1.declarationPropertyHandlers(this.currentBehavior, this.document, path);
176 // Some behaviors are just lists of other behaviors. If this is one then
177 // add it to behaviors right away.
178 if (isSimpleBehaviorArray(behaviorExpression(node))) {
179 this._finishBehavior();
180 }
181 }
182 /**
183 * merges behavior with preexisting behavior with the same name.
184 * here to support multiple @polymerBehavior tags referring
185 * to same behavior. See iron-multi-selectable for example.
186 */
187 mergeBehavior(newBehavior) {
188 const isBehaviorImpl = (b) => {
189 // filter out BehaviorImpl
190 return newBehavior.className === undefined ||
191 b.identifier.indexOf(newBehavior.className) === -1;
192 };
193 for (const behavior of this.behaviors) {
194 if (newBehavior.className !== behavior.className) {
195 continue;
196 }
197 // TODO(justinfagnani): what?
198 // merge desc, longest desc wins
199 if (newBehavior.description) {
200 if (behavior.description) {
201 if (newBehavior.description.length > behavior.description.length) {
202 behavior.description = newBehavior.description;
203 }
204 }
205 else {
206 behavior.description = newBehavior.description;
207 }
208 }
209 // TODO(justinfagnani): move into ScannedBehavior
210 behavior.demos = behavior.demos.concat(newBehavior.demos);
211 for (const [key, val] of newBehavior.events) {
212 behavior.events.set(key, val);
213 }
214 for (const property of newBehavior.properties.values()) {
215 behavior.addProperty(property);
216 }
217 behavior.observers = behavior.observers.concat(newBehavior.observers);
218 behavior.behaviorAssignments =
219 (behavior.behaviorAssignments)
220 .concat(newBehavior.behaviorAssignments)
221 .filter(isBehaviorImpl);
222 return behavior;
223 }
224 return newBehavior;
225 }
226 _parseChainedBehaviors(node, path) {
227 if (this.currentBehavior == null) {
228 throw new Error(`_parsedChainedBehaviors was called without a current behavior.`);
229 }
230 // If current behavior is part of an array, it gets extended by other
231 // behaviors inside the array. Ex:
232 // Polymer.IronMultiSelectableBehavior = [
233 // {....},
234 // Polymer.IronSelectableBehavior];
235 // We add these to the behaviors array.
236 const expression = behaviorExpression(node);
237 const chained = [];
238 if (expression && babel.isArrayExpression(expression)) {
239 for (const arrElement of expression.elements) {
240 const behaviorName = ast_value_1.getIdentifierName(arrElement);
241 if (behaviorName) {
242 chained.push(new model_1.ScannedReference('behavior', behaviorName, this.document.sourceRangeForNode(arrElement), {
243 language: 'js',
244 node: arrElement,
245 containingDocument: this.document
246 }, path));
247 }
248 }
249 if (chained.length > 0) {
250 this.currentBehavior.behaviorAssignments = chained;
251 }
252 }
253 }
254}
255/**
256 * gets the expression representing a behavior from a node.
257 */
258function behaviorExpression(node) {
259 if (babel.isVariableDeclarator(node)) {
260 return node.init;
261 }
262 if (babel.isAssignmentExpression(node)) {
263 return node.right;
264 }
265 if (babel.isExportDefaultDeclaration(node) ||
266 babel.isExportNamedDeclaration(node)) {
267 return behaviorExpression(node.declaration);
268 }
269 if (babel.isExpressionStatement(node)) {
270 return behaviorExpression(node.expression);
271 }
272 if (babel.isVariableDeclaration(node)) {
273 return behaviorExpression(node.declarations[0]);
274 }
275 if (babel.isObjectExpression(node) || babel.isArrayExpression(node)) {
276 return node;
277 }
278}
279/**
280 * checks whether an expression is a simple array containing only member
281 * expressions or identifiers.
282 */
283function isSimpleBehaviorArray(expression) {
284 if (!expression || !babel.isArrayExpression(expression)) {
285 return false;
286 }
287 for (const element of expression.elements) {
288 if (!babel.isMemberExpression(element) && !babel.isIdentifier(element)) {
289 return false;
290 }
291 }
292 return true;
293}
294//# sourceMappingURL=behavior-scanner.js.map
\No newline at end of file