first commit

This commit is contained in:
s.golasch
2023-08-01 13:49:46 +02:00
commit 1fc239fd54
20238 changed files with 3112246 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
/**
* @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
*/
// jshint node: true
'use strict';
var esutil = require('./esutil');
var astValue = require('./ast-value');
var analyzeProperties = function(node) {
var analyzedProps = [];
if (node.type != 'ObjectExpression') {
return analyzedProps;
}
for (var i = 0; i < node.properties.length; i++) {
var property = node.properties[i];
var prop = esutil.toPropertyDescriptor(property);
prop.published = true;
if (property.value.type == 'ObjectExpression') {
/**
* Parse the expression inside a property object block.
* property: {
* key: {
* type: String,
* notify: true,
* value: -1,
* readOnly: true,
* reflectToAttribute: true
* }
* }
*/
for (var j = 0; j < property.value.properties.length; j++) {
var propertyArg = property.value.properties[j];
var propertyKey = esutil.objectKeyToString(propertyArg.key);
switch(propertyKey) {
case 'type': {
prop.type = esutil.objectKeyToString(propertyArg.value);
if (prop.type === undefined) {
throw {
message: 'Invalid type in property object.',
location: propertyArg.loc.start
};
}
}
break;
case 'notify': {
prop.notify = astValue.expressionToValue(propertyArg.value);
if (prop.notify === undefined)
prop.notify = astValue.CANT_CONVERT;
}
break;
case 'observer': {
prop.observer = astValue.expressionToValue(propertyArg.value);
prop.observerNode = propertyArg.value;
if (prop.observer === undefined)
prop.observer = astValue.CANT_CONVERT;
}
break;
case 'readOnly': {
prop.readOnly = astValue.expressionToValue(propertyArg.value);
if (prop.readOnly === undefined)
prop.readOnly = astValue.CANT_CONVERT;
}
break;
case 'reflectToAttribute': {
prop.reflectToAttribute = astValue.expressionToValue(propertyArg);
if (prop.reflectToAttribute === undefined)
prop.reflectToAttribute = astValue.CANT_CONVERT;
}
break;
case 'value': {
prop.default = astValue.expressionToValue(propertyArg.value);
if (prop.default === undefined)
prop.default = astValue.CANT_CONVERT;
}
break;
default:
break;
}
}
}
if (!prop.type) {
throw {
message: 'Unable to determine name for property key.',
location: node.loc.start
};
}
analyzedProps.push(prop);
}
return analyzedProps;
};
module.exports = analyzeProperties;

View File

@@ -0,0 +1,160 @@
/**
* @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
*/
// jshint node: true
'use strict';
// useful tool to visualize AST: http://esprima.org/demo/parse.html
/**
* converts literal: {"type": "Literal", "value": 5, "raw": "5" }
* to string
*/
function literalToValue(literal) {
return literal.value;
}
/**
* converts unary to string
* unary: { type: 'UnaryExpression', operator: '-', argument: { ... } }
*/
function unaryToValue(unary) {
var argValue = expressionToValue(unary.argument);
if (argValue === undefined)
return;
return unary.operator + argValue;
}
/**
* converts identifier to its value
* identifier { "type": "Identifier", "name": "Number }
*/
function identifierToValue(identifier) {
return identifier.name;
}
/**
* Function is a block statement.
*/
function functionDeclarationToValue(fn) {
if (fn.body.type == "BlockStatement")
return blockStatementToValue(fn.body);
}
function functionExpressionToValue(fn) {
if (fn.body.type == "BlockStatement")
return blockStatementToValue(fn.body);
}
/**
* Block statement: find last return statement, and return its value
*/
function blockStatementToValue(block) {
for (var i=block.body.length - 1; i>= 0; i--) {
if (block.body[i].type === "ReturnStatement")
return returnStatementToValue(block.body[i]);
}
}
/**
* Evaluates return's argument
*/
function returnStatementToValue(ret) {
return expressionToValue(ret.argument);
}
/**
* Enclose containing values in []
*/
function arrayExpressionToValue(arry) {
var value = '[';
for (var i=0; i<arry.elements.length; i++) {
var v = expressionToValue(arry.elements[i]);
if (v === undefined)
continue;
if (i !== 0)
value += ', ';
value += v;
}
value += ']';
return value;
}
/**
* Make it look like an object
*/
function objectExpressionToValue(obj) {
var value = '{';
for (var i=0; i<obj.properties.length; i++) {
var k = expressionToValue(obj.properties[i].key);
var v = expressionToValue(obj.properties[i].value);
if (v === undefined)
continue;
if (i !== 0)
value += ', ';
value += '"' + k + '": ' + v;
}
value += '}';
return value;
}
/**
* BinaryExpressions are of the form "literal" + "literal"
*/
function binaryExpressionToValue(member) {
if (member.operator == "+") {
return expressionToValue(member.left) + expressionToValue(member.right);
}
return
}
/**
* MemberExpression references a variable with name
*/
function memberExpressionToValue(member) {
return expressionToValue(member.object) + "." + expressionToValue(member.property);
}
/**
* Tries to get a value from expression. Handles Literal, UnaryExpression
* returns undefined on failure
* valueExpression example:
* { type: "Literal",
*/
function expressionToValue(valueExpression) {
switch(valueExpression.type) {
case 'Literal':
return literalToValue(valueExpression);
case 'UnaryExpression':
return unaryToValue(valueExpression);
case 'Identifier':
return identifierToValue(valueExpression);
case 'FunctionDeclaration':
return functionDeclarationToValue(valueExpression);
case 'FunctionExpression':
return functionExpressionToValue(valueExpression);
case 'ArrayExpression':
return arrayExpressionToValue(valueExpression);
case 'ObjectExpression':
return objectExpressionToValue(valueExpression);
case 'Identifier':
return identifierToValue(valueExpression);
case 'MemberExpression':
return memberExpressionToValue(valueExpression);
case 'BinaryExpression':
return binaryExpressionToValue(valueExpression);
default:
return;
}
}
var CANT_CONVERT = 'UNKNOWN';
module.exports = {
CANT_CONVERT: CANT_CONVERT,
expressionToValue: expressionToValue
};

View File

@@ -0,0 +1,225 @@
/**
* @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
*/
// jshint node: true
'use strict';
var estraverse = require('estraverse');
var docs = require('./docs');
var esutil = require('./esutil');
var jsdoc = require('./jsdoc');
var analyzeProperties = require('./analyze-properties');
var astValue = require('./ast-value.js');
var declarationPropertyHandlers = require('./declaration-property-handlers');
module.exports = function behaviorFinder() {
/** @type {!Array<BehaviorDescriptor>} The behaviors we've found. */
var behaviors = [];
var currentBehavior = null;
var propertyHandlers = null;
/**
* merges behavior with preexisting behavior with the same name.
* here to support multiple @polymerBehavior tags referring
* to same behavior. See iron-multi-selectable for example.
*/
function mergeBehavior(newBehavior) {
var isBehaviorImpl = function(b) { // filter out BehaviorImpl
return b.indexOf(newBehavior.is) === -1;
};
for (var i=0; i<behaviors.length; i++) {
if (newBehavior.is !== behaviors[i].is)
continue;
// merge desc, longest desc wins
if (newBehavior.desc) {
if (behaviors[i].desc) {
if (newBehavior.desc.length > behaviors[i].desc.length)
behaviors[i].desc = newBehavior.desc;
}
else {
behaviors[i].desc = newBehavior.desc;
}
}
// merge demos
behaviors[i].demos = (behaviors[i].demos || []).concat(newBehavior.demos || []);
// merge events,
behaviors[i].events = (behaviors[i].events || []).concat(newBehavior.events || []);
// merge properties
behaviors[i].properties = (behaviors[i].properties || []).concat(newBehavior.properties || []);
// merge observers
behaviors[i].observers = (behaviors[i].observers || []).concat(newBehavior.observers || []);
// merge behaviors
behaviors[i].behaviors =
(behaviors[i].behaviors || []).concat(newBehavior.behaviors || [])
.filter(isBehaviorImpl);
return behaviors[i];
}
return newBehavior;
}
/**
* gets the expression representing a behavior from a node.
*/
function behaviorExpression(node) {
switch(node.type) {
case 'ExpressionStatement':
return node.expression.right;
case 'VariableDeclaration':
return node.declarations.length > 0 ? node.declarations[0].init : null;
}
}
/**
* checks whether an expression is a simple array containing only member
* expressions or identifiers.
*/
function isSimpleBehaviorArray(expression) {
if (!expression || expression.type !== 'ArrayExpression') return false;
for (var i=0; i < expression.elements.length; i++) {
if (expression.elements[i].type !== 'MemberExpression' &&
expression.elements[i].type !== 'Identifier') {
return false;
}
}
return true;
}
var templatizer = "Polymer.Templatizer";
var visitors = {
/**
* Look for object declarations with @behavior in the docs.
*/
enterVariableDeclaration: function(node, parent) {
if (node.declarations.length !== 1) return; // Ambiguous.
this._initBehavior(node, function () {
return esutil.objectKeyToString(node.declarations[0].id);
});
},
/**
* Look for object assignments with @polymerBehavior in the docs.
*/
enterAssignmentExpression: function(node, parent) {
this._initBehavior(parent, function () {
return esutil.objectKeyToString(node.left);
});
},
_parseChainedBehaviors: function(node) {
// if current behavior is part of an array, it gets extended by other behaviors
// inside the array. Ex:
// Polymer.IronMultiSelectableBehavior = [ {....}, Polymer.IronSelectableBehavior]
// We add these to behaviors array
var expression = behaviorExpression(node);
var chained = [];
if (expression && expression.type === 'ArrayExpression') {
for (var i=0; i < expression.elements.length; i++) {
if (expression.elements[i].type === 'MemberExpression' ||
expression.elements[i].type === 'Identifier') {
chained.push(astValue.expressionToValue(expression.elements[i]));
}
}
if (chained.length > 0)
currentBehavior.behaviors = chained;
}
},
_initBehavior: function(node, getName) {
var comment = esutil.getAttachedComment(node);
var symbol = getName();
// Quickly filter down to potential candidates.
if (!comment || comment.indexOf('@polymerBehavior') === -1) {
if (symbol !== templatizer) {
return;
}
}
currentBehavior = {
type: 'behavior',
desc: comment,
events: esutil.getEventComments(node).map( function(event) {
return { desc: event};
})
};
propertyHandlers = declarationPropertyHandlers(currentBehavior);
docs.annotateBehavior(currentBehavior);
// Make sure that we actually parsed a behavior tag!
if (!jsdoc.hasTag(currentBehavior.jsdoc, 'polymerBehavior') &&
symbol !== templatizer) {
currentBehavior = null;
propertyHandlers = null;
return;
}
var name = jsdoc.getTag(currentBehavior.jsdoc, 'polymerBehavior', 'name');
currentBehavior.symbol = symbol;
if (!name) {
name = currentBehavior.symbol;
}
if (!name) {
console.warn('Unable to determine name for @polymerBehavior:', comment);
}
currentBehavior.is = name;
this._parseChainedBehaviors(node);
currentBehavior = mergeBehavior(currentBehavior);
propertyHandlers = declarationPropertyHandlers(currentBehavior);
// Some behaviors are just lists of other behaviors. If this is one then
// add it to behaviors right away.
if (isSimpleBehaviorArray(behaviorExpression(node))) {
// TODO(ajo): Add a test to confirm the presence of `properties`
if (!currentBehavior.observers) currentBehavior.observers = [];
if (!currentBehavior.properties) currentBehavior.properties = [];
if (behaviors.indexOf(currentBehavior) === -1)
behaviors.push(currentBehavior);
currentBehavior = null;
propertyHandlers = null;
}
},
/**
* We assume that the object expression after such an assignment is the
* behavior's declaration. Seems to be a decent assumption for now.
*/
enterObjectExpression: function(node, parent) {
if (!currentBehavior || currentBehavior.properties) return;
currentBehavior.properties = currentBehavior.properties || [];
currentBehavior.observers = currentBehavior.observers || [];
for (var i = 0; i < node.properties.length; i++) {
var prop = node.properties[i];
var name = esutil.objectKeyToString(prop.key);
if (!name) {
throw {
message: 'Cant determine name for property key.',
location: node.loc.start
};
}
if (name in propertyHandlers) {
propertyHandlers[name](prop.value);
}
else {
currentBehavior.properties.push(esutil.toPropertyDescriptor(prop));
}
}
behaviors.push(currentBehavior);
currentBehavior = null;
},
};
return {visitors: visitors, behaviors: behaviors};
};

View File

@@ -0,0 +1,68 @@
/**
* @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
*/
// jshint node: true
'use strict';
var astValue = require('./ast-value');
var analyzeProperties = require('./analyze-properties');
/**
* Returns an object containing functions that will annotate `declaration` with
* the polymer-specificmeaning of the value nodes for the named properties.
*
* @param {ElementDescriptor} declaration The descriptor to annotate.
* @return {object.<string,function>} An object containing property
* handlers.
*/
function declarationPropertyHandlers(declaration) {
return {
is: function(node) {
if (node.type == 'Literal') {
declaration.is = node.value;
}
},
properties: function(node) {
var props = analyzeProperties(node);
for (var i=0; i<props.length; i++) {
declaration.properties.push(props[i]);
}
},
behaviors: function(node) {
if (node.type != 'ArrayExpression') {
return;
}
for (var i=0; i<node.elements.length; i++) {
var v = astValue.expressionToValue(node.elements[i]);
if (v === undefined)
v = astValue.CANT_CONVERT;
declaration.behaviors.push(v);
}
},
observers: function(node) {
if (node.type != 'ArrayExpression') {
return;
}
for (var i=0; i<node.elements.length; i++) {
var v = astValue.expressionToValue(node.elements[i]);
if (v === undefined)
v = astValue.CANT_CONVERT;
declaration.observers.push({
javascriptNode: node.elements[i],
expression: v
});
}
}
};
}
module.exports = declarationPropertyHandlers;

485
build/node_modules/hydrolysis/lib/ast-utils/docs.js generated vendored Normal file
View File

@@ -0,0 +1,485 @@
/**
* @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
*/
'use strict';
// jshint node:true
var jsdoc = require('./jsdoc');
var dom5 = require('dom5');
/** Properties on element prototypes that are purely configuration. */
var ELEMENT_CONFIGURATION = [
'attached',
'attributeChanged',
'configure',
'constructor',
'created',
'detached',
'enableCustomStyleProperties',
'extends',
'hostAttributes',
'is',
'listeners',
'mixins',
'properties',
'ready',
'registered'
];
/** Tags understood by the annotation process, to be removed during `clean`. */
var HANDLED_TAGS = [
'param',
'return',
'type',
];
/**
* Annotates Hydrolysis descriptors, processing any `desc` properties as JSDoc.
*
* You probably want to use a more specialized version of this, such as
* `annotateElement`.
*
* Processed JSDoc values will be made available via the `jsdoc` property on a
* descriptor node.
*
* @param {Object} descriptor The descriptor node to process.
* @return {Object} The descriptor that was given.
*/
function annotate(descriptor) {
if (!descriptor || descriptor.jsdoc) return descriptor;
if (typeof descriptor.desc === 'string') {
descriptor.jsdoc = jsdoc.parseJsdoc(descriptor.desc);
// We want to present the normalized form of a descriptor.
descriptor.jsdoc.orig = descriptor.desc;
descriptor.desc = descriptor.jsdoc.description;
}
return descriptor;
}
/**
* Annotates @event, @hero, & @demo tags
*/
function annotateElementHeader(descriptor) {
if (descriptor.events) {
descriptor.events.forEach(function(event) {
_annotateEvent(event);
});
descriptor.events.sort( function(a,b) {
return a.name.localeCompare(b.name);
});
}
descriptor.demos = [];
if (descriptor.jsdoc && descriptor.jsdoc.tags) {
descriptor.jsdoc.tags.forEach( function(tag) {
switch(tag.tag) {
case 'hero':
descriptor.hero = tag.name || 'hero.png';
break;
case 'demo':
descriptor.demos.push({
desc: tag.description || 'demo',
path: tag.name || 'demo/index.html'
});
}
});
}
}
function matchByName(propa, propb) {
return propa.name == propb.name;
}
function copyProperties(from, to, behaviorsByName) {
if (from.properties) {
from.properties.forEach(function(fromProp){
for (var toProp, i = 0; i < to.properties.length; i++) {
toProp = to.properties[i];
if (fromProp.name === toProp.name) {
return;
}
}
var newProp = {__fromBehavior: from.is};
if (fromProp.__fromBehavior) {
return;
}
Object.keys(fromProp).forEach(function(propertyField){
newProp[propertyField] = fromProp[propertyField];
});
to.properties.push(newProp);
});
from.events.forEach(function(fromEvent){
for (var toEvent, i = 0; i < to.events.length; i++) {
toEvent = to.events[i];
if (fromEvent.name === toEvent.name) {
return;
}
}
if (fromEvent.__fromBehavior) {
return;
}
var newEvent = Object.create(fromEvent);
newEvent.__fromBehavior = from.is;
to.events.push(newEvent);
});
}
if (!from.behaviors) {
return;
}
for (var i = from.behaviors.length - 1; i >= 0; i--) {
var behavior = from.behaviors[i];
var definedBehavior = behaviorsByName[behavior] || behaviorsByName[behavior.symbol];
if (!definedBehavior) {
console.warn("Behavior " + behavior + " not found when mixing " +
"properties into " + to.is + "!");
return;
}
copyProperties(definedBehavior, to, behaviorsByName);
}
}
function mixinBehaviors(descriptor, behaviorsByName) {
if (descriptor.behaviors) {
for (var i = descriptor.behaviors.length - 1; i >= 0; i--) {
var behavior = descriptor.behaviors[i];
if (!behaviorsByName[behavior]) {
console.warn("Behavior " + behavior + " not found when mixing " +
"properties into " + descriptor.is + "!");
break;
}
var definedBehavior = behaviorsByName[behavior];
copyProperties(definedBehavior, descriptor, behaviorsByName);
}
}
}
/**
* Annotates documentation found within a Hydrolysis element descriptor. Also
* supports behaviors.
*
* If the element was processed via `hydrolize`, the element's documentation
* will also be extracted via its <dom-module>.
*
* @param {Object} descriptor The element descriptor.
* @return {Object} The descriptor that was given.
*/
function annotateElement(descriptor, behaviorsByName) {
if (!descriptor.desc && descriptor.type === 'element') {
descriptor.desc = _findElementDocs(descriptor.is,
descriptor.domModule,
descriptor.scriptElement);
}
annotate(descriptor);
// The `<dom-module>` is too low level for most needs, and it is _not_
// serializable. So we drop it now that we've extracted all the useful bits
// from it.
// TODO: Don't worry about serializability here, provide an API to get JSON.
delete descriptor.domModule;
mixinBehaviors(descriptor, behaviorsByName);
// Descriptors that should have their `desc` properties parsed as JSDoc.
descriptor.properties.forEach(function(property) {
// Feature properties are special, configuration is really just a matter of
// inheritance...
annotateProperty(property, descriptor.abstract);
});
// It may seem like overkill to always sort, but we have an assumption that
// these properties are typically being consumed by user-visible tooling.
// As such, it's good to have consistent output/ordering to aid the user.
descriptor.properties.sort(function(a, b) {
// Private properties are always last.
if (a.private && !b.private) {
return 1;
} else if (!a.private && b.private) {
return -1;
// Otherwise, we're just sorting alphabetically.
} else {
return a.name.localeCompare(b.name);
}
});
annotateElementHeader(descriptor);
return descriptor;
}
/**
* Annotates behavior descriptor.
* @param {Object} descriptor behavior descriptor
* @return {Object} descriptor passed in as param
*/
function annotateBehavior(descriptor, behaviorsByName) {
annotate(descriptor);
annotateElementHeader(descriptor);
return descriptor;
}
/**
* Annotates event documentation
*/
function _annotateEvent(descriptor) {
annotate(descriptor);
// process @event
var eventTag = jsdoc.getTag(descriptor.jsdoc, 'event');
descriptor.name = eventTag ? eventTag.description : "N/A";
// process @params
descriptor.params = (descriptor.jsdoc.tags || [])
.filter( function(tag) {
return tag.tag === 'param';
})
.map( function(tag) {
return {
type: tag.type || "N/A",
desc: tag.description,
name: tag.name || "N/A"
};
});
// process @params
return descriptor;
}
/**
* Annotates documentation found about a Hydrolysis property descriptor.
*
* @param {Object} descriptor The property descriptor.
* @param {boolean} ignoreConfiguration If true, `configuration` is not set.
* @return {Object} The descriptior that was given.
*/
function annotateProperty(descriptor, ignoreConfiguration) {
annotate(descriptor);
if (descriptor.name[0] === '_' || jsdoc.hasTag(descriptor.jsdoc, 'private')) {
descriptor.private = true;
}
if (!ignoreConfiguration && ELEMENT_CONFIGURATION.indexOf(descriptor.name) !== -1) {
descriptor.private = true;
descriptor.configuration = true;
}
// @type JSDoc wins
descriptor.type = jsdoc.getTag(descriptor.jsdoc, 'type', 'type') || descriptor.type;
if (descriptor.type.match(/^function/i)) {
_annotateFunctionProperty(descriptor);
}
// @default JSDoc wins
var defaultTag = jsdoc.getTag(descriptor.jsdoc, 'default');
if (defaultTag !== null) {
var newDefault = (defaultTag.name || '') + (defaultTag.description || '');
if (newDefault !== '') {
descriptor.default = newDefault;
}
}
return descriptor;
}
/** @param {Object} descriptor */
function _annotateFunctionProperty(descriptor) {
descriptor.function = true;
var returnTag = jsdoc.getTag(descriptor.jsdoc, 'return');
if (returnTag) {
descriptor.return = {
type: returnTag.type,
desc: returnTag.description,
};
}
var paramsByName = {};
(descriptor.params || []).forEach(function(param) {
paramsByName[param.name] = param;
});
(descriptor.jsdoc && descriptor.jsdoc.tags || []).forEach(function(tag) {
if (tag.tag !== 'param') return;
var param = paramsByName[tag.name];
if (!param) {
return;
}
param.type = tag.type || param.type;
param.desc = tag.description;
});
}
/**
* Converts raw features into an abstract `Polymer.Base` element.
*
* Note that docs on this element _are not processed_. You must call
* `annotateElement` on it yourself if you wish that.
*
* @param {Array<FeatureDescriptor>} features
* @return {ElementDescriptor}
*/
function featureElement(features) {
var properties = features.reduce(function(result, feature) {
return result.concat(feature.properties);
}, []);
return {
type: 'element',
is: 'Polymer.Base',
abstract: true,
properties: properties,
desc: '`Polymer.Base` acts as a base prototype for all Polymer ' +
'elements. It is composed via various calls to ' +
'`Polymer.Base._addFeature()`.\n' +
'\n' +
'The properties reflected here are the combined view of all ' +
'features found in this library. There may be more properties ' +
'added via other libraries, as well.',
};
}
/**
* Cleans redundant properties from a descriptor, assuming that you have already
* called `annotate`.
*
* @param {Object} descriptor
*/
function clean(descriptor) {
if (!descriptor.jsdoc) return;
// The doctext was written to `descriptor.desc`
delete descriptor.jsdoc.description;
delete descriptor.jsdoc.orig;
var cleanTags = [];
(descriptor.jsdoc.tags || []).forEach(function(tag) {
// Drop any tags we've consumed.
if (HANDLED_TAGS.indexOf(tag.tag) !== -1) return;
cleanTags.push(tag);
});
if (cleanTags.length === 0) {
// No tags? no docs left!
delete descriptor.jsdoc;
} else {
descriptor.jsdoc.tags = cleanTags;
}
}
/**
* Cleans redundant properties from an element, assuming that you have already
* called `annotateElement`.
*
* @param {ElementDescriptor|BehaviorDescriptor} element
*/
function cleanElement(element) {
clean(element);
element.properties.forEach(cleanProperty);
}
/**
* Cleans redundant properties from a property, assuming that you have already
* called `annotateProperty`.
*
* @param {PropertyDescriptor} property
*/
function cleanProperty(property) {
clean(property);
}
/**
* Parse elements defined only in comments.
* @param {comments} Array<string> A list of comments to parse.
* @return {ElementDescriptor} A list of pseudo-elements.
*/
function parsePseudoElements(comments) {
var elements = [];
comments.forEach(function(comment) {
var parsed = jsdoc.parseJsdoc(comment);
var pseudoTag = jsdoc.getTag(parsed, 'pseudoElement', 'name');
if (pseudoTag) {
parsed.is = pseudoTag;
parsed.jsdoc = {description: parsed.description, tags: parsed.tags};
parsed.properties = [];
parsed.desc = parsed.description;
parsed.description = undefined;
parsed.tags = undefined;
annotateElementHeader(parsed);
elements.push(parsed);
}
});
return elements;
}
/**
* @param {string} elementId
* @param {DocumentAST} domModule
* @param {DocumentAST} scriptElement The script that the element was defined in.
*/
function _findElementDocs(elementId, domModule, scriptElement) {
// Note that we concatenate docs from all sources if we find them.
// element can be defined in:
// html comment right before dom-module
// html commnet right before script defining the module, if dom-module is empty
var found = [];
// Do we have a HTML comment on the `<dom-module>` or `<script>`?
//
// Confusingly, with our current style, the comment will be attached to
// `<head>`, rather than being a sibling to the `<dom-module>`
var searchRoot = domModule || scriptElement;
var parents = dom5.nodeWalkAllPrior(searchRoot, dom5.isCommentNode);
var comment = parents.length > 0 ? parents[0] : null;
if (comment && comment.data) {
found.push(comment.data);
}
if (found.length === 0) return null;
return found
.filter(function(comment) {
// skip @license comments
if (comment && comment.indexOf('@license' === -1)) {
return true;
}
else {
return false;
}
})
.map(jsdoc.unindent).join('\n');
}
function _findLastChildNamed(name, parent) {
var children = parent.childNodes;
for (var i = children.length - 1, child; i >= 0; i--) {
child = children[i];
if (child.nodeName === name) return child;
}
return null;
}
// TODO(nevir): parse5-utils!
function _getNodeAttribute(node, name) {
for (var i = 0, attr; i < node.attrs.length; i++) {
attr = node.attrs[i];
if (attr.name === name) {
return attr.value;
}
}
}
module.exports = {
annotate: annotate,
annotateElement: annotateElement,
annotateBehavior: annotateBehavior,
clean: clean,
cleanElement: cleanElement,
featureElement: featureElement,
parsePseudoElements: parsePseudoElements
};

View File

@@ -0,0 +1,115 @@
/**
* @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
*/
// jshint node: true
'use strict';
var estraverse = require('estraverse');
var esutil = require('./esutil');
var findAlias = require('./find-alias');
var analyzeProperties = require('./analyze-properties');
var astValue = require('./ast-value');
var declarationPropertyHandlers = require('./declaration-property-handlers');
var elementFinder = function elementFinder() {
/**
* The list of elements exported by each traversed script.
*/
var elements = [];
/**
* The element being built during a traversal;
*/
var element = null;
var propertyHandlers = null;
var visitors = {
enterCallExpression: function enterCallExpression(node, parent) {
var callee = node.callee;
if (callee.type == 'Identifier') {
if (callee.name == 'Polymer') {
element = {
type: 'element',
desc: esutil.getAttachedComment(parent),
events: esutil.getEventComments(parent).map( function(event) {
return {desc: event};
})
};
propertyHandlers = declarationPropertyHandlers(element);
}
}
},
leaveCallExpression: function leaveCallExpression(node, parent) {
var callee = node.callee;
if (callee.type == 'Identifier') {
if (callee.name == 'Polymer') {
if (element) {
elements.push(element);
element = null;
propertyHandlers = null;
}
}
}
},
enterObjectExpression: function enterObjectExpression(node, parent) {
if (element && !element.properties) {
element.properties = [];
element.behaviors = [];
element.observers = [];
var getters = {};
var setters = {};
var definedProperties = {};
for (var i = 0; i < node.properties.length; i++) {
var prop = node.properties[i];
var name = esutil.objectKeyToString(prop.key);
if (!name) {
throw {
message: 'Cant determine name for property key.',
location: node.loc.start
};
}
if (name in propertyHandlers) {
propertyHandlers[name](prop.value);
continue;
}
var descriptor = esutil.toPropertyDescriptor(prop);
if (descriptor.getter) {
getters[descriptor.name] = descriptor;
} else if (descriptor.setter) {
setters[descriptor.name] = descriptor;
} else {
element.properties.push(esutil.toPropertyDescriptor(prop));
}
}
Object.keys(getters).forEach(function(getter) {
var get = getters[getter];
definedProperties[get.name] = get;
});
Object.keys(setters).forEach(function(setter) {
var set = setters[setter];
if (!(set.name in definedProperties)) {
definedProperties[set.name] = set;
} else {
definedProperties[set.name].setter = true;
}
});
Object.keys(definedProperties).forEach(function(p){
var prop = definedProperties[p];
element.properties.push(p);
});
return estraverse.VisitorOption.Skip;
}
}
};
return {visitors: visitors, elements: elements};
};
module.exports = elementFinder;

178
build/node_modules/hydrolysis/lib/ast-utils/esutil.js generated vendored Normal file
View File

@@ -0,0 +1,178 @@
/**
* @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
*/
// jshint node: true
'use strict';
var estraverse = require("estraverse");
/**
* Returns whether an Espree node matches a particular object path.
*
* e.g. you have a MemberExpression node, and want to see whether it represents
* `Foo.Bar.Baz`:
*
* matchesCallExpression(node, ['Foo', 'Bar', 'Baz'])
*
* @param {Node} expression The Espree node to match against.
* @param {Array<string>} path The path to look for.
*/
function matchesCallExpression(expression, path) {
if (!expression.property || !expression.object) return;
console.assert(path.length >= 2);
// Unravel backwards, make sure properties match each step of the way.
if (expression.property.name !== path[path.length - 1]) return false;
// We've got ourselves a final member expression.
if (path.length == 2 && expression.object.type === 'Identifier') {
return expression.object.name === path[0];
}
// Nested expressions.
if (path.length > 2 && expression.object.type == 'MemberExpression') {
return matchesCallExpression(expression.object, path.slice(0, path.length - 1));
}
return false;
}
/**
* @param {Node} key The node representing an object key or expression.
* @return {string} The name of that key.
*/
function objectKeyToString(key) {
if (key.type == 'Identifier') {
return key.name;
}
if (key.type == 'Literal') {
return key.value;
}
if (key.type == 'MemberExpression') {
return objectKeyToString(key.object) + '.' + objectKeyToString(key.property);
}
}
var CLOSURE_CONSTRUCTOR_MAP = {
'Boolean': 'boolean',
'Number': 'number',
'String': 'string',
};
/**
* AST expression -> Closure type.
*
* Accepts literal values, and native constructors.
*
* @param {Node} node An Espree expression node.
* @return {string} The type of that expression, in Closure terms.
*/
function closureType(node) {
if (node.type.match(/Expression$/)) {
return node.type.substr(0, node.type.length - 10);
} else if (node.type === 'Literal') {
return typeof node.value;
} else if (node.type === 'Identifier') {
return CLOSURE_CONSTRUCTOR_MAP[node.name] || node.name;
} else {
throw {
message: 'Unknown Closure type for node: ' + node.type,
location: node.loc.start,
};
}
}
/**
* @param {Node} node
* @return {?string}
*/
function getAttachedComment(node) {
var comments = getLeadingComments(node) || getLeadingComments(node.key);
if (!comments) {
return;
}
return comments[comments.length - 1];
}
/**
* Returns all comments from a tree defined with @event.
* @param {Node} node [description]
* @return {[type]} [description]
*/
function getEventComments(node) {
var eventComments = [];
estraverse.traverse(node, {
enter: function (node) {
var comments = (node.leadingComments || []).concat(node.trailingComments || [])
.map( function(commentAST) {
return commentAST.value;
})
.filter( function(comment) {
return comment.indexOf("@event") != -1;
});
eventComments = eventComments.concat(comments);
}
});
// dedup
return eventComments.filter( function(el, index, array) {
return array.indexOf(el) === index;
});
}
/**
* @param {Node} node
* @param
* @return {Array.<string>}
*/
function getLeadingComments(node) {
if (!node) {
return;
}
var comments = node.leadingComments;
if (!comments || comments.length === 0) return;
return comments.map(function(comment) {
return comment.value;
});
}
/**
* Converts a parse5 Property AST node into its Hydrolysis representation.
*
* @param {Node} node
* @return {PropertyDescriptor}
*/
function toPropertyDescriptor(node) {
var type = closureType(node.value);
if (type == "Function") {
if (node.kind === "get" || node.kind === "set") {
type = '';
node[node.kind+"ter"] = true;
}
}
var result = {
name: objectKeyToString(node.key),
type: type,
desc: getAttachedComment(node),
javascriptNode: node
};
if (type === 'Function') {
result.params = (node.value.params || []).map(function(param) {
return {name: param.name};
});
}
return result;
}
module.exports = {
closureType: closureType,
getAttachedComment: getAttachedComment,
getEventComments: getEventComments,
matchesCallExpression: matchesCallExpression,
objectKeyToString: objectKeyToString,
toPropertyDescriptor: toPropertyDescriptor,
};

View File

@@ -0,0 +1,56 @@
/**
* @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
*/
// jshint node: true
'use strict';
var estraverse = require('estraverse');
var esutil = require('./esutil');
var numFeatures = 0;
module.exports = function featureFinder() {
/** @type {!Array<FeatureDescriptor>} The features we've found. */
var features = [];
var visitors = {
enterCallExpression: function enterCallExpression(node, parent) {
if (!esutil.matchesCallExpression(node.callee, ['Polymer', 'Base', '_addFeature'])) {
return;
}
/** @type {!FeatureDescriptor} */
var feature = {};
this._extractDesc(feature, node, parent);
this._extractProperties(feature, node, parent);
features.push(feature);
},
_extractDesc: function _extractDesc(feature, node, parent) {
feature.desc = esutil.getAttachedComment(parent);
},
_extractProperties: function _extractProperties(feature, node, parent) {
var featureNode = node.arguments[0];
if (featureNode.type !== 'ObjectExpression') {
console.warn(
'Expected first argument to Polymer.Base._addFeature to be an object.',
'Got', featureNode.type, 'instead.');
return;
}
if (!featureNode.properties) return;
feature.properties = featureNode.properties.map(esutil.toPropertyDescriptor);
},
};
return {visitors: visitors, features: features};
};

View File

@@ -0,0 +1,19 @@
/**
* @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
*/
// jshint node: true
'use strict';
var findAlias = function findAlias(names, aliases, name) {
if (!names) {
return null;
}
return aliases[names.indexOf(name)];
};
module.exports = findAlias;

View File

@@ -0,0 +1,141 @@
/**
* @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
*/
// jshint node: true
'use strict';
var dom5 = require('dom5');
var p = dom5.predicates;
var isHtmlImportNode = p.AND(
p.hasTagName('link'),
p.hasAttrValue('rel', 'import'),
p.NOT(
p.hasAttrValue('type', 'css')
)
);
var isStyleNode = p.OR(
// inline style
p.hasTagName('style'),
// external stylesheet
p.AND(
p.hasTagName('link'),
p.hasAttrValue('rel', 'stylesheet')
),
// polymer specific external stylesheet
p.AND(
p.hasTagName('link'),
p.hasAttrValue('rel', 'import'),
p.hasAttrValue('type', 'css')
)
);
var isJSScriptNode = p.AND(
p.hasTagName('script'),
p.OR(
p.NOT(p.hasAttr('type')),
p.hasAttrValue('type', 'text/javascript'),
p.hasAttrValue('type', 'application/javascript')
)
);
function addNode(node, registry) {
if (isHtmlImportNode(node)) {
registry.import.push(node);
} else if (isStyleNode(node)) {
registry.style.push(node);
} else if (isJSScriptNode(node)) {
registry.script.push(node);
} else if (node.tagName === 'base') {
registry.base.push(node);
} else if (node.tagName === 'template') {
registry.template.push(node);
} else if (node.tagName === 'dom-module') {
registry['dom-module'].push(node);
} else if (dom5.isCommentNode(node)) {
registry.comment.push(node);
}
}
function getLineAndColumn(string, charNumber) {
if (charNumber > string.length) {
return undefined;
}
// TODO(ajo): Caching the line lengths of each document could be much faster.
var sliced = string.slice(0,charNumber+1);
var split = sliced.split('\n');
var line = split.length;
var column = split[split.length - 1].length;
return {line: line, column: column};
}
/**
* Parse5's representation of a parsed html document.
* @typedef {Object} DocumentAST
*/
/**
* The ASTs of the HTML elements needed to represent Polymer elements.
* @typedef {Object} ParsedImport
* @property {Array<DocumentAST>} template The entry points to the AST at each outermost template tag.
* @property {Array<DocumentAST>} script The entry points to the AST at each script tag not inside a template.
* @property {Array<DocumentAST>} style The entry points to the AST at style tag outside a template.
* @property {Array<DocumentAST>} dom-module The entry points to the AST at each outermost dom-module element.
* @property {DocumentAST} ast The full parse5 ast for the document.
*/
/**
* Parse html into ASTs.
* @param {string} htmlString A utf8, html5 document containing polymer element or module definitons.
* @param {string} href The path of the document.
* @return {ParsedImport}
*/
var importParse = function importParse(htmlString, href) {
var doc;
try {
doc = dom5.parse(htmlString, {locationInfo: true});
} catch (err) {
console.log(err);
return null;
}
// Add line/column information
dom5.treeMap(doc, function(node) {
if (node.__location && node.__location.start >= 0) {
node.__locationDetail = getLineAndColumn(htmlString, node.__location.start);
if (href) {
node.__ownerDocument = href;
}
}
});
var registry = {
base: [],
template: [],
script: [],
style: [],
import: [],
'dom-module': [],
comment: []};
var queue = [].concat(doc.childNodes);
var nextNode;
while (queue.length > 0) {
nextNode = queue.shift();
if (nextNode) {
queue = queue.concat(nextNode.childNodes);
addNode(nextNode, registry);
}
}
registry.ast = doc;
return registry;
};
module.exports = importParse;

View File

@@ -0,0 +1,95 @@
/**
* @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
*/
/**
* Finds and annotates the Polymer() and modulate() calls in javascript.
*/
// jshint node: true
'use strict';
var espree = require('espree');
var estraverse = require('estraverse');
var behaviorFinder = require('./behavior-finder');
var elementFinder = require('./element-finder');
var featureFinder = require('./feature-finder');
function traverse(visitorRegistries) {
var visitor;
function applyVisitors(name, node, parent) {
var returnVal;
for (var i = 0; i < visitorRegistries.length; i++) {
if (name in visitorRegistries[i]) {
returnVal = visitorRegistries[i][name](node, parent);
if (returnVal) {
return returnVal;
}
}
}
}
return {
enter: function(node, parent) {
visitor = 'enter' + node.type;
return applyVisitors(visitor, node, parent);
},
leave: function(node, parent) {
visitor = 'leave' + node.type;
return applyVisitors(visitor, node, parent);
},
fallback: 'iteration',
};
}
var jsParse = function jsParse(jsString) {
var script = espree.parse(jsString, {
attachComment: true,
comment: true,
loc: true,
ecmaFeatures: {
arrowFunctions: true,
blockBindings: true,
destructuring: true,
regexYFlag: true,
regexUFlag: true,
templateStrings: true,
binaryLiterals: true,
unicodeCodePointEscapes: true,
defaultParams: true,
restParams: true,
forOf: true,
objectLiteralComputedProperties: true,
objectLiteralShorthandMethods: true,
objectLiteralShorthandProperties: true,
objectLiteralDuplicateProperties: true,
generators: true,
spread: true,
classes: true,
modules: true,
jsx: true,
globalReturn: true,
}
});
var featureInfo = featureFinder();
var behaviorInfo = behaviorFinder();
var elementInfo = elementFinder();
var visitors = [featureInfo, behaviorInfo, elementInfo].map(function(info) {
return info.visitors;
});
estraverse.traverse(script, traverse(visitors));
return {
behaviors: behaviorInfo.behaviors,
elements: elementInfo.elements,
features: featureInfo.features,
parsedScript: script
};
};
module.exports = jsParse;

223
build/node_modules/hydrolysis/lib/ast-utils/jsdoc.js generated vendored Normal file
View File

@@ -0,0 +1,223 @@
/**
* @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
*/
// jshint node: true
'use strict';
var doctrine = require('doctrine');
/**
* An annotated JSDoc block tag, all fields are optionally processed except for
* the tag:
*
* @TAG {TYPE} NAME DESC
*
* `line` and `col` indicate the position of the first character of text that
* the tag was extracted from - relative to the first character of the comment
* contents (e.g. the value of `desc` on a descriptor node). Lines are
* 1-indexed.
*
* @typedef {{
* tag: string,
* type: ?string,
* name: ?string,
* description: ?string,
* }}
*/
var JsdocTag;
/**
* The parsed representation of a JSDoc comment.
*
* @typedef {{
* description: ?string,
* tags: Array<JsdocTag>,
* }}
*/
var JsdocAnnotation;
/**
* doctrine configuration,
* CURRENTLY UNUSED BECAUSE PRIVATE
*/
// function configureDoctrine() {
// // @hero [path/to/image]
// doctrine.Rules['hero'] = ['parseNamePathOptional', 'ensureEnd'];
// // // @demo [path/to/demo] [Demo title]
// doctrine.Rules['demo'] = ['parseNamePathOptional', 'parseDescription', 'ensureEnd'];
// // // @polymerBehavior [Polymer.BehaviorName]
// doctrine.Rules['polymerBehavior'] = ['parseNamePathOptional', 'ensureEnd'];
// }
// configureDoctrine();
// @demo [path] [title]
function parseDemo(tag) {
var match = (tag.description || "").match(/^\s*(\S*)\s*(.*)$/);
return {
tag: 'demo',
type: null,
name: match ? match[1] : null,
description: match ? match[2] : null
};
}
// @hero [path]
function parseHero(tag) {
return {
tag: tag.title,
type: null,
name: tag.description,
description: null
};
}
// @polymerBehavior [name]
function parsePolymerBehavior(tag) {
return {
tag: tag.title,
type: null,
name: tag.description,
description: null
};
}
// @pseudoElement name
function parsePseudoElement(tag) {
return {
tag: tag.title,
type: null,
name: tag.description,
description: null
};
}
var CUSTOM_TAGS = {
demo: parseDemo,
hero: parseHero,
polymerBehavior: parsePolymerBehavior,
pseudoElement: parsePseudoElement
};
/**
* Convert doctrine tags to hydrolysis tag format
*/
function _tagsToHydroTags(tags) {
if (!tags)
return null;
return tags.map( function(tag) {
if (tag.title in CUSTOM_TAGS) {
return CUSTOM_TAGS[tag.title](tag);
}
else {
return {
tag: tag.title,
type: tag.type ? doctrine.type.stringify(tag.type) : null,
name: tag.name,
description: tag.description,
};
}
});
}
/**
* removes leading *, and any space before it
* @param {string} description -- js doc description
*/
function _removeLeadingAsterisks(description) {
if ((typeof description) !== 'string')
return description;
return description
.split('\n')
.map( function(line) {
// remove leading '\s*' from each line
var match = line.match(/^[\s]*\*\s?(.*)$/);
return match ? match[1] : line;
})
.join('\n');
}
/**
* Given a JSDoc string (minus opening/closing comment delimiters), extract its
* description and tags.
*
* @param {string} docs
* @return {?JsdocAnnotation}
*/
function parseJsdoc(docs) {
docs = _removeLeadingAsterisks(docs);
var d = doctrine.parse(docs, {
unwrap: false,
lineNumber: true,
preserveWhitespace: true
});
return {
description: d.description,
tags: _tagsToHydroTags(d.tags)
};
}
// Utility
/**
* @param {JsdocAnnotation} jsdoc
* @param {string} tagName
* @return {boolean}
*/
function hasTag(jsdoc, tagName) {
if (!jsdoc || !jsdoc.tags) return false;
return jsdoc.tags.some(function(tag) { return tag.tag === tagName; });
}
/**
* Finds the first JSDoc tag matching `name` and returns its value at `key`.
*
* @param {JsdocAnnotation} jsdoc
* @param {string} tagName
* @param {string=} key If omitted, the entire tag object is returned.
* @return {?string|Object}
*/
function getTag(jsdoc, tagName, key) {
if (!jsdoc || !jsdoc.tags) return false;
for (var i = 0; i < jsdoc.tags.length; i++) {
var tag = jsdoc.tags[i];
if (tag.tag === tagName) {
return key ? tag[key] : tag;
}
}
return null;
}
/**
* @param {?string} text
* @return {?string}
*/
function unindent(text) {
if (!text) return text;
var lines = text.replace(/\t/g, ' ').split('\n');
var indent = lines.reduce(function(prev, line) {
if (/^\s*$/.test(line)) return prev; // Completely ignore blank lines.
var lineIndent = line.match(/^(\s*)/)[0].length;
if (prev === null) return lineIndent;
return lineIndent < prev ? lineIndent : prev;
}, null);
return lines.map(function(l) { return l.substr(indent); }).join('\n');
}
module.exports = {
getTag: getTag,
hasTag: hasTag,
parseJsdoc: parseJsdoc,
unindent: unindent
};