UNPKG

10.8 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 dom5 = require("dom5/lib/index-next");
17const parse5 = require("parse5");
18const esutil_1 = require("../javascript/esutil");
19const jsdoc = require("../javascript/jsdoc");
20const model_1 = require("../model/model");
21function mergePropertyDeclarations(propA, propB) {
22 if (propA.name !== propB.name) {
23 throw new Error(`Tried to merge properties with different names: ` +
24 `'${propA.name}' and ' ${propB.name}'`);
25 }
26 const name = propA.name;
27 const description = jsdoc.pickBestDescription(propA.description, propB.description);
28 const jsdocAnn = { description: description || '', tags: [] };
29 if (propA.jsdoc) {
30 jsdocAnn.tags.push(...propA.jsdoc.tags);
31 }
32 if (propB.jsdoc) {
33 jsdocAnn.tags.push(...propB.jsdoc.tags);
34 }
35 const privacy = esutil_1.getOrInferPrivacy(propA.name, jsdocAnn);
36 const warnings = [...propA.warnings, ...propB.warnings];
37 // If either are marked as readOnly, both are.
38 const readOnly = propA.readOnly || propB.readOnly;
39 // Handle all regular property metadata.
40 const scannedRegularProperty = {
41 // calculated above with care
42 name,
43 privacy,
44 description,
45 warnings,
46 readOnly,
47 jsdoc: jsdocAnn,
48 // prefer A, but take B if there's no A.
49 sourceRange: propA.sourceRange || propB.sourceRange,
50 astNode: propA.astNode || propB.astNode,
51 changeEvent: propA.changeEvent || propB.changeEvent,
52 default: propA.default || propB.default,
53 type: propA.type || propB.type,
54 };
55 const scannedPolymerProperty = scannedRegularProperty;
56 // For the scannedPolymerProperty keys, set them if they're there
57 const keys = [
58 'published',
59 'notify',
60 'observer',
61 'observerNode',
62 'observerExpression',
63 'reflectToAttribute',
64 'computedExpression'
65 ];
66 for (const key of keys) {
67 if (propA[key] || propB[key]) {
68 scannedPolymerProperty[key] = propA[key] || propB[key];
69 }
70 }
71 if (propA.published || propB.published) {
72 scannedPolymerProperty.published = propA.published || propB.published;
73 }
74 return scannedPolymerProperty;
75}
76exports.mergePropertyDeclarations = mergePropertyDeclarations;
77class LocalId {
78 constructor(name, range, nodeName) {
79 this.name = name;
80 this.range = range;
81 this.nodeName = nodeName;
82 }
83}
84exports.LocalId = LocalId;
85function addProperty(target, prop) {
86 const existingProp = target.properties.get(prop.name);
87 if (existingProp) {
88 prop = mergePropertyDeclarations(existingProp, prop);
89 }
90 target.properties.set(prop.name, prop);
91 const attributeName = propertyToAttributeName(prop.name);
92 // Don't produce attributes or events for nonpublic properties, properties
93 // that aren't in Polymer's `properties` block (i.e. not published),
94 // or properties whose names can't be converted into attribute names.
95 if ((prop.privacy && prop.privacy !== 'public') || !attributeName ||
96 !prop.published) {
97 return;
98 }
99 target.attributes.set(attributeName, {
100 name: attributeName,
101 sourceRange: prop.sourceRange,
102 description: prop.description,
103 type: prop.type,
104 changeEvent: prop.notify ? `${attributeName}-changed` : undefined
105 });
106 if (prop.notify) {
107 const name = `${attributeName}-changed`;
108 target.events.set(name, {
109 name,
110 description: `Fired when the \`${prop.name}\` property changes.`,
111 sourceRange: prop.sourceRange,
112 astNode: prop.astNode,
113 warnings: [],
114 params: []
115 });
116 }
117}
118exports.addProperty = addProperty;
119function addMethod(target, method) {
120 target.methods.set(method.name, method);
121}
122exports.addMethod = addMethod;
123function addStaticMethod(target, method) {
124 target.staticMethods.set(method.name, method);
125}
126exports.addStaticMethod = addStaticMethod;
127/**
128 * The metadata for a single polymer element
129 */
130class ScannedPolymerElement extends model_1.ScannedElement {
131 constructor(options) {
132 super();
133 this.properties = new Map();
134 this.methods = new Map();
135 this.staticMethods = new Map();
136 this.observers = [];
137 this.listeners = [];
138 this.behaviorAssignments = [];
139 // Indicates if an element is a pseudo element
140 this.pseudo = false;
141 this.abstract = false;
142 this.tagName = options.tagName;
143 this.className = options.className;
144 this.superClass = options.superClass;
145 this.mixins = options.mixins;
146 this.extends = options.extends;
147 this.jsdoc = options.jsdoc;
148 this.description = options.description || '';
149 this.attributes = options.attributes;
150 this.observers = options.observers;
151 this.listeners = options.listeners;
152 this.behaviorAssignments = options.behaviors;
153 this.events = options.events;
154 this.abstract = options.abstract;
155 this.privacy = options.privacy;
156 this.astNode = options.astNode;
157 this.statementAst = options.statementAst;
158 this.sourceRange = options.sourceRange;
159 this.isLegacyFactoryCall = options.isLegacyFactoryCall || false;
160 if (options.properties) {
161 options.properties.forEach((p) => this.addProperty(p));
162 }
163 if (options.methods) {
164 options.methods.forEach((m) => this.addMethod(m));
165 }
166 if (options.staticMethods) {
167 options.staticMethods.forEach((m) => this.addStaticMethod(m));
168 }
169 const summaryTag = jsdoc.getTag(this.jsdoc, 'summary');
170 this.summary =
171 (summaryTag !== undefined && summaryTag.description != null) ?
172 summaryTag.description :
173 '';
174 }
175 addProperty(prop) {
176 addProperty(this, prop);
177 }
178 addMethod(method) {
179 addMethod(this, method);
180 }
181 addStaticMethod(method) {
182 addStaticMethod(this, method);
183 }
184 resolve(document) {
185 return new PolymerElement(this, document);
186 }
187}
188exports.ScannedPolymerElement = ScannedPolymerElement;
189class PolymerElement extends model_1.Element {
190 constructor(scannedElement, document) {
191 super(scannedElement, document);
192 this.observers = [];
193 this.listeners = [];
194 this.behaviorAssignments = [];
195 this.localIds = [];
196 this.kinds.add('polymer-element');
197 this.observers = Array.from(scannedElement.observers);
198 this.listeners = Array.from(scannedElement.listeners);
199 this.behaviorAssignments = Array.from(scannedElement.behaviorAssignments);
200 const domModules = scannedElement.tagName == null ?
201 new Set() :
202 document.getFeatures({
203 kind: 'dom-module',
204 id: scannedElement.tagName,
205 imported: true,
206 externalPackages: true
207 });
208 let domModule = undefined;
209 if (domModules.size === 1) {
210 // TODO(rictic): warn if this isn't true.
211 domModule = domModules.values().next().value;
212 }
213 if (domModule) {
214 this.domModule = domModule.node;
215 this.slots = this.slots.concat(domModule.slots);
216 this.localIds = domModule.localIds.slice();
217 // If there's a domModule and it's got a comment, that comment documents
218 // this element too. Extract its description and @demo annotations.
219 if (domModule.comment) {
220 const domModuleJsdoc = jsdoc.parseJsdoc(domModule.comment);
221 this.demos = [...jsdoc.extractDemos(domModuleJsdoc), ...this.demos];
222 if (domModuleJsdoc.description) {
223 this.description =
224 (domModuleJsdoc.description + '\n\n' + this.description).trim();
225 }
226 }
227 const template = dom5.query(domModule.node, dom5.predicates.hasTagName('template'));
228 if (template) {
229 this.template = {
230 kind: 'polymer-databinding',
231 contents: parse5.treeAdapters.default.getTemplateContent(template)
232 };
233 }
234 }
235 if (scannedElement.pseudo) {
236 this.kinds.add('pseudo-element');
237 }
238 this.isLegacyFactoryCall = scannedElement.isLegacyFactoryCall;
239 }
240 emitPropertyMetadata(property) {
241 return {
242 polymer: {
243 notify: property.notify,
244 observer: property.observer,
245 readOnly: property.readOnly,
246 attributeType: property.attributeType,
247 }
248 };
249 }
250 _getSuperclassAndMixins(document, init) {
251 const prototypeChain = super._getSuperclassAndMixins(document, init);
252 const { warnings, behaviors } = getBehaviors(init.behaviorAssignments, document);
253 this.warnings.push(...warnings);
254 prototypeChain.push(...behaviors);
255 return prototypeChain;
256 }
257}
258exports.PolymerElement = PolymerElement;
259/**
260 * Implements Polymer core's translation of property names to attribute names.
261 *
262 * Returns null if the property name cannot be so converted.
263 */
264function propertyToAttributeName(propertyName) {
265 // Polymer core will not map a property name that starts with an uppercase
266 // character onto an attribute.
267 if (propertyName[0].toUpperCase() === propertyName[0]) {
268 return null;
269 }
270 return propertyName.replace(/([A-Z])/g, (_, c1) => `-${c1.toLowerCase()}`);
271}
272function getBehaviors(behaviorReferences, document) {
273 const warnings = [];
274 const behaviors = [];
275 for (const scannedReference of behaviorReferences) {
276 const resolvedReference = scannedReference.resolve(document);
277 if (resolvedReference.warnings.length > 0) {
278 warnings.push(...resolvedReference.warnings);
279 }
280 if (resolvedReference.feature) {
281 behaviors.push(resolvedReference.feature);
282 }
283 }
284 return { warnings, behaviors };
285}
286exports.getBehaviors = getBehaviors;
287//# sourceMappingURL=polymer-element.js.map
\No newline at end of file