UNPKG

polymer-analyzer

Version:
305 lines (303 loc) 13.2 kB
"use strict"; /** * @license * Copyright (c) 2015 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at * http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at * http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at * http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const estraverse = require("estraverse"); const ast_value_1 = require("../javascript/ast-value"); const esutil_1 = require("../javascript/esutil"); const jsdoc = require("../javascript/jsdoc"); const model_1 = require("../model/model"); const declaration_property_handlers_1 = require("./declaration-property-handlers"); const declaration_property_handlers_2 = require("./declaration-property-handlers"); const docs = require("./docs"); const expression_scanner_1 = require("./expression-scanner"); const js_utils_1 = require("./js-utils"); const polymer_element_1 = require("./polymer-element"); class PolymerElementScanner { scan(document, visit) { return __awaiter(this, void 0, void 0, function* () { const visitor = new ElementVisitor(document); yield visit(visitor); return { features: visitor.features, warnings: visitor.warnings }; }); } } exports.PolymerElementScanner = PolymerElementScanner; class ElementVisitor { constructor(document) { this.features = []; /** * The element being built during a traversal; */ this.element = null; this.propertyHandlers = null; this.classDetected = false; this.warnings = []; this.document = document; } enterClassDeclaration(node, _) { this.classDetected = true; const className = node.id.name; const docs = jsdoc.parseJsdoc(esutil_1.getAttachedComment(node) || ''); this.element = new polymer_element_1.ScannedPolymerElement({ astNode: node, description: docs.description, events: esutil_1.getEventComments(node), sourceRange: this.document.sourceRangeForNode(node), className, privacy: esutil_1.getOrInferPrivacy(className, docs), abstract: jsdoc.hasTag(docs, 'abstract'), attributes: new Map(), properties: [], behaviors: [], extends: undefined, jsdoc: docs, listeners: [], methods: new Map(), staticMethods: new Map(), mixins: [], observers: [], superClass: undefined, tagName: undefined }); this.propertyHandlers = declaration_property_handlers_2.declarationPropertyHandlers(this.element, this.document); } leaveClassDeclaration(_, _parent) { for (const property of this.element.properties.values()) { docs.annotate(property); } // TODO(justinfagnani): this looks wrong, class definitions can be nested // so a definition in a method in a Polymer() declaration would end the // declaration early. We should track which class induced the current // element and finish the element when leaving _that_ class. this.element = null; this.propertyHandlers = null; this.classDetected = false; } enterAssignmentExpression(node, _) { if (!this.element) { return; } const left = node.left; if (left && left.object && left.object.type !== 'ThisExpression') { return; } const prop = left.property; if (prop && prop.name && this.propertyHandlers) { const name = prop.name; if (name in this.propertyHandlers) { this.propertyHandlers[name](node.right); } } } enterMethodDefinition(node, _parent) { const element = this.element; if (!element) { return; } const prop = Object.assign({}, node, { method: true, shorthand: false, computed: false, }); if (node.kind === 'get') { const returnStatement = node.value.body.body[0]; const argument = returnStatement.argument; const propDesc = js_utils_1.toScannedPolymerProperty(prop, this.document.sourceRangeForNode(node), this.document); docs.annotate(propDesc); // We only support observers and behaviors getters that return array // literals. if ((propDesc.name === 'behaviors' || propDesc.name === 'observers') && !Array.isArray(argument.elements)) { return; } if (propDesc.name === 'behaviors') { argument.elements.forEach((argNode) => { const result = declaration_property_handlers_1.getBehaviorAssignmentOrWarning(argNode, this.document); if (result.kind === 'warning') { element.warnings.push(result.warning); } else { element.behaviorAssignments.push(result.assignment); } }); return; } if (propDesc.name === 'observers') { argument.elements.forEach((elementObject) => { const parseResult = expression_scanner_1.parseExpressionInJsStringLiteral(this.document, elementObject, 'callExpression'); element.warnings = element.warnings.concat(parseResult.warnings); let expressionText = undefined; if (elementObject.type === 'Literal') { expressionText = elementObject.raw; } element.observers.push({ javascriptNode: elementObject, expression: expressionText, parsedExpression: parseResult.databinding }); }); return; } element.addProperty(propDesc); return; } if (node.kind === 'method') { const methodDesc = esutil_1.toScannedMethod(prop, this.document.sourceRangeForNode(node), this.document); docs.annotate(methodDesc); element.addMethod(methodDesc); } } enterCallExpression(node, parent) { // When dealing with a class, enterCallExpression is called after the // parsing actually starts if (this.classDetected) { return estraverse.VisitorOption.Skip; } const callee = node.callee; if (callee.type === 'Identifier') { if (callee.name === 'Polymer') { const rawDescription = esutil_1.getAttachedComment(parent); let className = undefined; if (parent.type === 'AssignmentExpression') { className = ast_value_1.getIdentifierName(parent.left); } else if (parent.type === 'VariableDeclarator') { className = ast_value_1.getIdentifierName(parent.id); } const jsDoc = jsdoc.parseJsdoc(rawDescription || ''); this.element = new polymer_element_1.ScannedPolymerElement({ className, astNode: node, description: jsDoc.description, events: esutil_1.getEventComments(parent), sourceRange: this.document.sourceRangeForNode(node.arguments[0]), privacy: esutil_1.getOrInferPrivacy('', jsDoc), abstract: jsdoc.hasTag(jsDoc, 'abstract'), attributes: new Map(), properties: [], behaviors: [], extends: undefined, jsdoc: jsDoc, listeners: [], methods: new Map(), staticMethods: new Map(), mixins: [], observers: [], superClass: undefined, tagName: undefined }); this.element.description = (this.element.description || '').trim(); this.propertyHandlers = declaration_property_handlers_2.declarationPropertyHandlers(this.element, this.document); } } } leaveCallExpression(node, _) { const callee = node.callee; const args = node.arguments; if (callee.type === 'Identifier' && args.length === 1 && args[0].type === 'ObjectExpression') { if (callee.name === 'Polymer') { if (this.element) { this.features.push(this.element); this.element = null; this.propertyHandlers = null; } } } } enterObjectExpression(node, _) { // When dealing with a class, there is no single object that we can // parse to retrieve all properties. if (this.classDetected) { return estraverse.VisitorOption.Skip; } const element = this.element; if (element) { const getters = {}; const setters = {}; const definedProperties = {}; for (const prop of node.properties) { const name = esutil_1.objectKeyToString(prop.key); if (!name) { element.warnings.push(new model_1.Warning({ message: `Can't determine name for property key from expression with type ${prop.key .type}.`, code: 'cant-determine-property-name', severity: model_1.Severity.WARNING, sourceRange: this.document.sourceRangeForNode(prop.key), parsedDocument: this.document })); continue; } if (!this.propertyHandlers) { continue; } if (name in this.propertyHandlers) { this.propertyHandlers[name](prop.value); continue; } try { const scannedPolymerProperty = js_utils_1.toScannedPolymerProperty(prop, this.document.sourceRangeForNode(prop), this.document); if (prop.kind === 'get') { getters[scannedPolymerProperty.name] = scannedPolymerProperty; } else if (prop.kind === 'set') { setters[scannedPolymerProperty.name] = scannedPolymerProperty; } else if (prop.method === true || esutil_1.isFunctionType(prop.value)) { const scannedPolymerMethod = esutil_1.toScannedMethod(prop, this.document.sourceRangeForNode(prop), this.document); element.addMethod(scannedPolymerMethod); } else { element.addProperty(scannedPolymerProperty); } } catch (e) { if (e instanceof model_1.WarningCarryingException) { element.warnings.push(e.warning); continue; } throw e; } } Object.keys(getters).forEach((name) => { const prop = getters[name]; definedProperties[prop.name] = prop; prop.readOnly = !!setters[prop.name]; }); Object.keys(setters).forEach((name) => { const prop = setters[name]; if (!(prop.name in definedProperties)) { definedProperties[prop.name] = prop; } }); Object.keys(definedProperties).forEach((name) => { const prop = definedProperties[name]; element.addProperty(prop); }); return estraverse.VisitorOption.Skip; } } } //# sourceMappingURL=polymer-element-scanner.js.map