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

773
build/node_modules/hydrolysis/lib/analyzer.js generated vendored Normal file
View File

@@ -0,0 +1,773 @@
/**
* @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';
// jshint -W079
var Promise = global.Promise || require('es6-promise').Promise;
// jshint +W079
var dom5 = require('dom5');
var url = require('url');
var docs = require('./ast-utils/docs');
var FileLoader = require('./loader/file-loader');
var importParse = require('./ast-utils/import-parse');
var jsParse = require('./ast-utils/js-parse');
var NoopResolver = require('./loader/noop-resolver');
var StringResolver = require('./loader/string-resolver');
function reduceMetadata(m1, m2) {
return {
elements: m1.elements.concat(m2.elements),
features: m1.features.concat(m2.features),
behaviors: m1.behaviors.concat(m2.behaviors),
};
}
var EMPTY_METADATA = {elements: [], features: [], behaviors: []};
/**
* Parse5's representation of a parsed html document
* @typedef {Object} DocumentAST
* @memberof hydrolysis
*/
/**
* espree's representation of a parsed html document
* @typedef {Object} JSAST
* @memberof hydrolysis
*/
/**
* Package of a parsed JS script
* @typedef {Object} ParsedJS
* @property {JSAST} ast The script's AST
* @property {DocumentAST} scriptElement If inline, the script's containing tag.
* @memberof hydrolysis
*/
/**
* The metadata for a single polymer element
* @typedef {Object} ElementDescriptor
* @memberof hydrolysis
*/
/**
* The metadata for a Polymer feature.
* @typedef {Object} FeatureDescriptor
* @memberof hydrolysis
*/
/**
* The metadata for a Polymer behavior mixin.
* @typedef {Object} BehaviorDescriptor
* @memberof hydrolysis
*/
/**
* The metadata for all features and elements defined in one document
* @typedef {Object} DocumentDescriptor
* @memberof hydrolysis
* @property {Array<ElementDescriptor>} elements The elements from the document
* @property {Array<FeatureDescriptor>} features The features from the document
* @property {Array<FeatureDescriptor>} behaviors The behaviors from the document
*/
/**
* The metadata of an entire HTML document, in promises.
* @typedef {Object} AnalyzedDocument
* @memberof hydrolysis
* @property {string} href The url of the document.
* @property {Promise<ParsedImport>} htmlLoaded The parsed representation of
* the doc. Use the `ast` property to get the full `parse5` ast
*
* @property {Promise<Array<string>>} depsLoaded Resolves to the list of this
* Document's transitive import dependencies
*
* @property {Array<string>} depHrefs The direct dependencies of the document.
*
* @property {Promise<DocumentDescriptor>} metadataLoaded Resolves to the list of
* this Document's import dependencies
*/
/**
* A database of Polymer metadata defined in HTML
*
* @constructor
* @memberOf hydrolysis
* @param {boolean} attachAST If true, attach a parse5 compliant AST
* @param {FileLoader=} loader An optional `FileLoader` used to load external
* resources
*/
var Analyzer = function Analyzer(attachAST,
loader) {
this.loader = loader;
/**
* A list of all elements the `Analyzer` has metadata for.
* @member {Array.<ElementDescriptor>}
*/
this.elements = [];
/**
* A view into `elements`, keyed by tag name.
* @member {Object.<string,ElementDescriptor>}
*/
this.elementsByTagName = {};
/**
* A list of API features added to `Polymer.Base` encountered by the
* analyzer.
* @member {Array<FeatureDescriptor>}
*/
this.features = [];
/**
* The behaviors collected by the analysis pass.
*
* @member {Array<BehaviorDescriptor>}
*/
this.behaviors = [];
/**
* The behaviors collected by the analysis pass by name.
*
* @member {Object<string,BehaviorDescriptor>}
*/
this.behaviorsByName = {};
/**
* A map, keyed by absolute path, of Document metadata.
* @member {Object<string,AnalyzedDocument>}
*/
this.html = {};
/**
* A map, keyed by path, of HTML document ASTs.
* @type {Object}
*/
this.parsedDocuments = {};
/**
* A map, keyed by path, of JS script ASTs.
*
* If the path is an HTML file with multiple scripts, the entry will be an array of scripts.
*
* @type {Object<string,Array<ParsedJS>>}
*/
this.parsedScripts = {};
/**
* A map, keyed by path, of document content.
* @type {Object}
*/
this._content = {};
};
/**
* Options for `Analyzer.analzye`
* @typedef {Object} LoadOptions
* @memberof hydrolysis
* @property {boolean} noAnnotations Whether `annotate()` should be skipped.
* @property {String=} content Content to resolve `href` to instead of loading
* from the file system.
* @property {boolean} clean Whether the generated descriptors should be cleaned
* of redundant data.
* @property {string=} resolver.
* `xhr` to use XMLHttpRequest
* `fs` to use the local filesystem.
* `permissive` to use the local filesystem and return empty files when a
* path can't be found.
* Default is `fs` in node and `xhr` in the browser.
* @property {function(string): boolean} filter A predicate function that
* indicates which files should be ignored by the loader. By default all
* files not located under the dirname of `href` will be ignored.
*/
/**
* Shorthand for transitively loading and processing all imports beginning at
* `href`.
*
* In order to properly filter paths, `href` _must_ be an absolute URI.
*
* @param {string} href The root import to begin loading from.
* @param {LoadOptions=} options Any additional options for the load.
* @return {Promise<Analyzer>} A promise that will resolve once `href` and its
* dependencies have been loaded and analyzed.
*/
Analyzer.analyze = function analyze(href, options) {
options = options || {};
options.filter = options.filter || _defaultFilter(href);
var loader = new FileLoader();
var resolver = options.resolver;
if (resolver === undefined) {
if (typeof window === 'undefined') {
resolver = 'fs';
} else {
resolver = 'xhr';
}
}
var PrimaryResolver;
if (resolver === 'fs') {
PrimaryResolver = require('./loader/fs-resolver');
} else if (resolver === 'xhr') {
PrimaryResolver = require('./loader/xhr-resolver');
} else if (resolver === 'permissive') {
PrimaryResolver = require('./loader/error-swallowing-fs-resolver');
} else {
throw new Error("Resolver must be one of 'fs' or 'xhr'");
}
loader.addResolver(new PrimaryResolver(options));
if (options.content) {
loader.addResolver(new StringResolver({url: href, content: options.content}));
}
loader.addResolver(new NoopResolver({test: options.filter}));
var analyzer = new this(null, loader);
return analyzer.metadataTree(href).then(function(root) {
if (!options.noAnnotations) {
analyzer.annotate();
}
if (options.clean) {
analyzer.clean();
}
return Promise.resolve(analyzer);
});
};
/**
* @private
* @param {string} href
* @return {function(string): boolean}
*/
function _defaultFilter(href) {
// Everything up to the last `/` or `\`.
var base = href.match(/^(.*?)[^\/\\]*$/)[1];
return function(uri) {
return uri.indexOf(base) !== 0;
};
}
Analyzer.prototype.load = function load(href) {
return this.loader.request(href).then(function(content) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
this._content[href] = content;
resolve(this._parseHTML(content, href));
}.bind(this), 0);
}.bind(this)).catch(function(err){
console.error("Error processing document at " + href);
throw err;
});
}.bind(this));
};
/**
* Returns an `AnalyzedDocument` representing the provided document
* @private
* @param {string} htmlImport Raw text of an HTML document
* @param {string} href The document's URL.
* @return {AnalyzedDocument} An `AnalyzedDocument`
*/
Analyzer.prototype._parseHTML = function _parseHTML(htmlImport,
href) {
if (href in this.html) {
return this.html[href];
}
var depsLoaded = [];
var depHrefs = [];
var metadataLoaded = Promise.resolve(EMPTY_METADATA);
var parsed;
try {
parsed = importParse(htmlImport, href);
} catch (err) {
console.error('Error parsing!');
throw err;
}
var htmlLoaded = Promise.resolve(parsed);
if (parsed.script) {
metadataLoaded = this._processScripts(parsed.script, href);
}
var commentText = parsed.comment.map(function(comment){
return dom5.getTextContent(comment);
});
var pseudoElements = docs.parsePseudoElements(commentText);
pseudoElements.forEach(function(element){
element.contentHref = href;
this.elements.push(element);
this.elementsByTagName[element.is] = element;
}.bind(this));
metadataLoaded = metadataLoaded.then(function(metadata){
var metadataEntry = {
elements: pseudoElements,
features: [],
behaviors: []
};
return [metadata, metadataEntry].reduce(reduceMetadata);
});
depsLoaded.push(metadataLoaded);
if (this.loader) {
var baseUri = href;
if (parsed.base.length > 1) {
console.error("Only one base tag per document!");
throw "Multiple base tags in " + href;
} else if (parsed.base.length == 1) {
var baseHref = dom5.getAttribute(parsed.base[0], "href");
if (baseHref) {
baseHref = baseHref + "/";
baseUri = url.resolve(baseUri, baseHref);
}
}
parsed.import.forEach(function(link) {
var linkurl = dom5.getAttribute(link, 'href');
if (linkurl) {
var resolvedUrl = url.resolve(baseUri, linkurl);
depHrefs.push(resolvedUrl);
depsLoaded.push(this._dependenciesLoadedFor(resolvedUrl, href));
}
}.bind(this));
parsed.style.forEach(function(styleElement) {
if (polymerExternalStyle(styleElement)) {
var styleHref = dom5.getAttribute(styleElement, 'href');
if (href) {
styleHref = url.resolve(baseUri, styleHref);
depsLoaded.push(this.loader.request(styleHref).then(function(content){
this._content[styleHref] = content;
}.bind(this)));
}
}
}.bind(this));
}
depsLoaded = Promise.all(depsLoaded)
.then(function() {return depHrefs;})
.catch(function(err) {throw err;});
this.parsedDocuments[href] = parsed.ast;
this.html[href] = {
href: href,
htmlLoaded: htmlLoaded,
metadataLoaded: metadataLoaded,
depHrefs: depHrefs,
depsLoaded: depsLoaded
};
return this.html[href];
};
Analyzer.prototype._processScripts = function _processScripts(scripts, href) {
var scriptPromises = [];
scripts.forEach(function(script) {
scriptPromises.push(this._processScript(script, href));
}.bind(this));
return Promise.all(scriptPromises).then(function(metadataList) {
return metadataList.reduce(reduceMetadata, EMPTY_METADATA);
});
};
Analyzer.prototype._processScript = function _processScript(script, href) {
var src = dom5.getAttribute(script, 'src');
var parsedJs;
if (!src) {
try {
parsedJs = jsParse((script.childNodes.length) ? script.childNodes[0].value : '');
} catch (err) {
// Figure out the correct line number for the error.
var line = 0;
var col = 0;
if (script.__ownerDocument && script.__ownerDocument == href) {
line = script.__locationDetail.line - 1;
col = script.__locationDetail.column - 1;
}
line += err.lineNumber;
col += err.column;
var message = "Error parsing script in " + href + " at " + line + ":" + col;
message += "\n" + err.stack;
var fixedErr = new Error(message);
fixedErr.location = {line: line, column: col};
fixedErr.ownerDocument = script.__ownerDocument;
return Promise.reject(fixedErr);
}
if (parsedJs.elements) {
parsedJs.elements.forEach(function(element) {
element.scriptElement = script;
element.contentHref = href;
this.elements.push(element);
if (element.is in this.elementsByTagName) {
console.warn('Ignoring duplicate element definition: ' + element.is);
} else {
this.elementsByTagName[element.is] = element;
}
}.bind(this));
}
if (parsedJs.features) {
parsedJs.features.forEach(function(feature){
feature.contentHref = href;
feature.scriptElement = script;
});
this.features = this.features.concat(parsedJs.features);
}
if (parsedJs.behaviors) {
parsedJs.behaviors.forEach(function(behavior){
behavior.contentHref = href;
this.behaviorsByName[behavior.is] = behavior;
this.behaviorsByName[behavior.symbol] = behavior;
}.bind(this));
this.behaviors = this.behaviors.concat(parsedJs.behaviors);
}
if (!Object.hasOwnProperty.call(this.parsedScripts, href)) {
this.parsedScripts[href] = [];
}
var scriptElement;
if (script.__ownerDocument && script.__ownerDocument == href) {
scriptElement = script;
}
this.parsedScripts[href].push({
ast: parsedJs.parsedScript,
scriptElement: scriptElement
});
return parsedJs;
}
if (this.loader) {
var resolvedSrc = url.resolve(href, src);
return this.loader.request(resolvedSrc).then(function(content) {
this._content[resolvedSrc] = content;
var resolvedScript = Object.create(script);
resolvedScript.childNodes = [{value: content}];
resolvedScript.attrs = resolvedScript.attrs.slice();
dom5.removeAttribute(resolvedScript, 'src');
return this._processScript(resolvedScript, resolvedSrc);
}.bind(this)).catch(function(err) {throw err;});
} else {
return Promise.resolve(EMPTY_METADATA);
}
};
Analyzer.prototype._dependenciesLoadedFor = function _dependenciesLoadedFor(href, root) {
var found = {};
if (root !== undefined) {
found[root] = true;
}
return this._getDependencies(href, found).then(function(deps) {
var depMetadataLoaded = [];
var depPromises = deps.map(function(depHref){
return this.load(depHref).then(function(htmlMonomer) {
return htmlMonomer.metadataLoaded;
});
}.bind(this));
return Promise.all(depPromises);
}.bind(this));
};
/**
* List all the html dependencies for the document at `href`.
* @param {string} href The href to get dependencies for.
* @param {Object.<string,boolean>=} found An object keyed by URL of the
* already resolved dependencies.
* @param {boolean=} transitive Whether to load transitive
* dependencies. Defaults to true.
* @return {Array.<string>} A list of all the html dependencies.
*/
Analyzer.prototype._getDependencies = function _getDependencies(href, found, transitive) {
if (found === undefined) {
found = {};
found[href] = true;
}
if (transitive === undefined) {
transitive = true;
}
var deps = [];
return this.load(href).then(function(htmlMonomer) {
var transitiveDeps = [];
htmlMonomer.depHrefs.forEach(function(depHref){
if (found[depHref]) {
return;
}
deps.push(depHref);
found[depHref] = true;
if (transitive) {
transitiveDeps.push(this._getDependencies(depHref, found));
}
}.bind(this));
return Promise.all(transitiveDeps);
}.bind(this)).then(function(transitiveDeps) {
var alldeps = transitiveDeps.reduce(function(a, b) {
return a.concat(b);
}, []).concat(deps);
return alldeps;
});
};
function matchesDocumentFolder(descriptor, href) {
if (!descriptor.contentHref) {
return false;
}
var descriptorDoc = url.parse(descriptor.contentHref);
if (!descriptorDoc || !descriptorDoc.pathname) {
return false;
}
var searchDoc = url.parse(href);
if (!searchDoc || !searchDoc.pathname) {
return false;
}
var searchPath = searchDoc.pathname;
var lastSlash = searchPath.lastIndexOf("/");
if (lastSlash > 0) {
searchPath = searchPath.slice(0, lastSlash);
}
return descriptorDoc.pathname.indexOf(searchPath) === 0;
}
/**
* Returns the elements defined in the folder containing `href`.
* @param {string} href path to search.
* @return {Array.<ElementDescriptor>}
*/
Analyzer.prototype.elementsForFolder = function elementsForFolder(href) {
return this.elements.filter(function(element){
return matchesDocumentFolder(element, href);
});
};
/**
* Returns the behaviors defined in the folder containing `href`.
* @param {string} href path to search.
* @return {Array.<BehaviorDescriptor>}
*/
Analyzer.prototype.behaviorsForFolder = function behaviorsForFolder(href) {
return this.behaviors.filter(function(behavior){
return matchesDocumentFolder(behavior, href);
});
};
/**
* Returns a promise that resolves to a POJO representation of the import
* tree, in a format that maintains the ordering of the HTML imports spec.
* @param {string} href the import to get metadata for.
* @return {Promise}
*/
Analyzer.prototype.metadataTree = function metadataTree(href) {
return this.load(href).then(function(monomer){
var loadedHrefs = {};
loadedHrefs[href] = true;
return this._metadataTree(monomer, loadedHrefs);
}.bind(this));
};
Analyzer.prototype._metadataTree = function _metadataTree(htmlMonomer,
loadedHrefs) {
if (loadedHrefs === undefined) {
loadedHrefs = {};
}
return htmlMonomer.metadataLoaded.then(function(metadata) {
metadata = {
elements: metadata.elements,
features: metadata.features,
href: htmlMonomer.href
};
return htmlMonomer.depsLoaded.then(function(hrefs) {
var depMetadata = [];
hrefs.forEach(function(href) {
var metadataPromise = Promise.resolve(true);
if (depMetadata.length > 0) {
metadataPromise = depMetadata[depMetadata.length - 1];
}
metadataPromise = metadataPromise.then(function() {
if (!loadedHrefs[href]) {
loadedHrefs[href] = true;
return this._metadataTree(this.html[href], loadedHrefs);
} else {
return Promise.resolve({});
}
}.bind(this));
depMetadata.push(metadataPromise);
}.bind(this));
return Promise.all(depMetadata).then(function(importMetadata) {
metadata.imports = importMetadata;
return htmlMonomer.htmlLoaded.then(function(parsedHtml) {
metadata.html = parsedHtml;
if (metadata.elements) {
metadata.elements.forEach(function(element) {
attachDomModule(parsedHtml, element);
});
}
return metadata;
});
});
}.bind(this));
}.bind(this));
};
function matchingImport(importElement) {
var matchesTag = dom5.predicates.hasTagName(importElement.tagName);
var matchesHref = dom5.predicates.hasAttrValue('href', dom5.getAttribute(importElement, 'href'));
var matchesRel = dom5.predicates.hasAttrValue('rel', dom5.getAttribute(importElement, 'rel'));
return dom5.predicates.AND(matchesTag, matchesHref, matchesRel);
}
// TODO(ajo): Refactor out of vulcanize into dom5.
var polymerExternalStyle = dom5.predicates.AND(
dom5.predicates.hasTagName('link'),
dom5.predicates.hasAttrValue('rel', 'import'),
dom5.predicates.hasAttrValue('type', 'css')
);
var externalScript = dom5.predicates.AND(
dom5.predicates.hasTagName('script'),
dom5.predicates.hasAttr('src')
);
var isHtmlImportNode = dom5.predicates.AND(
dom5.predicates.hasTagName('link'),
dom5.predicates.hasAttrValue('rel', 'import'),
dom5.predicates.NOT(
dom5.predicates.hasAttrValue('type', 'css')
)
);
Analyzer.prototype._inlineStyles = function _inlineStyles(ast, href) {
var cssLinks = dom5.queryAll(ast, polymerExternalStyle);
cssLinks.forEach(function(link) {
var linkHref = dom5.getAttribute(link, 'href');
var uri = url.resolve(href, linkHref);
var content = this._content[uri];
var style = dom5.constructors.element('style');
dom5.setTextContent(style, '\n' + content + '\n');
dom5.replace(link, style);
}.bind(this));
return cssLinks.length > 0;
};
Analyzer.prototype._inlineScripts = function _inlineScripts(ast, href) {
var scripts = dom5.queryAll(ast, externalScript);
scripts.forEach(function(script) {
var scriptHref = dom5.getAttribute(script, 'src');
var uri = url.resolve(href, scriptHref);
var content = this._content[uri];
var inlined = dom5.constructors.element('script');
dom5.setTextContent(inlined, '\n' + content + '\n');
dom5.replace(script, inlined);
}.bind(this));
return scripts.length > 0;
};
Analyzer.prototype._inlineImports = function _inlineImports(ast, href, loaded) {
var imports = dom5.queryAll(ast, isHtmlImportNode);
imports.forEach(function(htmlImport) {
var importHref = dom5.getAttribute(htmlImport, 'href');
var uri = url.resolve(href, importHref);
if (loaded[uri]) {
dom5.remove(htmlImport);
return;
}
var content = this.getLoadedAst(uri, loaded);
dom5.replace(htmlImport, content);
}.bind(this));
return imports.length > 0;
};
/**
* Returns a promise resolving to a form of the AST with all links replaced
* with the document they link to. .css and .script files become &lt;style&gt; and
* &lt;script&gt;, respectively.
*
* The elements in the loaded document are unmodified from their original
* documents.
*
* @param {string} href The document to load.
* @param {Object.<string,boolean>=} loaded An object keyed by already loaded documents.
* @return {Promise.<DocumentAST>}
*/
Analyzer.prototype.getLoadedAst = function getLoadedAst(href, loaded) {
if (!loaded) {
loaded = {};
}
loaded[href] = true;
var parsedDocument = this.parsedDocuments[href];
var analyzedDocument = this.html[href];
var astCopy = dom5.parse(dom5.serialize(parsedDocument));
// Whenever we inline something, reset inlined to true to know that anoather
// inlining pass is needed;
this._inlineStyles(astCopy, href);
this._inlineScripts(astCopy, href);
this._inlineImports(astCopy, href, loaded);
return astCopy;
};
/**
* Calls `dom5.nodeWalkAll` on each document that `Anayzler` has laoded.
* @param {Object} predicate A dom5 predicate.
* @return {Object}
*/
Analyzer.prototype.nodeWalkDocuments = function nodeWalkDocuments(predicate) {
for (var href in this.parsedDocuments) {
var match = dom5.nodeWalk(this.parsedDocuments[href], predicate);
if (match) {
return match;
}
}
return null;
};
/**
* Calls `dom5.nodeWalkAll` on each document that `Anayzler` has laoded.
* @param {Object} predicate A dom5 predicate.
* @return {Object}
*/
Analyzer.prototype.nodeWalkAllDocuments = function nodeWalkDocuments(predicate) {
var results = [];
for (var href in this.parsedDocuments) {
var newNodes = dom5.nodeWalkAll(this.parsedDocuments[href], predicate);
results = results.concat(newNodes);
}
return results;
};
/** Annotates all loaded metadata with its documentation. */
Analyzer.prototype.annotate = function annotate() {
if (this.features.length > 0) {
var featureEl = docs.featureElement(this.features);
this.elements.unshift(featureEl);
this.elementsByTagName[featureEl.is] = featureEl;
}
var behaviorsByName = this.behaviorsByName;
var elementHelper = function(descriptor){
docs.annotateElement(descriptor, behaviorsByName);
};
this.elements.forEach(elementHelper);
this.behaviors.forEach(elementHelper); // Same shape.
this.behaviors.forEach(function(behavior){
if (behavior.is !== behavior.symbol && behavior.symbol) {
this.behaviorsByName[behavior.symbol] = undefined;
}
}.bind(this));
};
function attachDomModule(parsedImport, element) {
var domModules = parsedImport['dom-module'];
for (var i = 0, domModule; i < domModules.length; i++) {
domModule = domModules[i];
if (dom5.getAttribute(domModule, 'id') === element.is) {
element.domModule = domModule;
return;
}
}
}
/** Removes redundant properties from the collected descriptors. */
Analyzer.prototype.clean = function clean() {
this.elements.forEach(docs.cleanElement);
};
module.exports = Analyzer;

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
};

View File

@@ -0,0 +1,30 @@
/**
* @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 FSResolver = require('./fs-resolver');
function ErrorSwallowingFSResolver(config) {
FSResolver.call(this, config);
}
ErrorSwallowingFSResolver.prototype = Object.create(FSResolver.prototype);
ErrorSwallowingFSResolver.prototype.accept = function(uri, deferred) {
var reject = deferred.reject;
deferred.reject = function(arg) {
deferred.resolve("");
};
return FSResolver.prototype.accept.call(this, uri, deferred);
};
module.exports = ErrorSwallowingFSResolver;

View File

@@ -0,0 +1,99 @@
/**
* @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';
// jshint -W079
// Promise polyfill
var Promise = global.Promise || require('es6-promise').Promise;
// jshint +W079
function Deferred() {
var self = this;
this.promise = new Promise(function(resolve, reject) {
self.resolve = resolve;
self.reject = reject;
});
}
/**
* An object that knows how to resolve resources.
* @typedef {Object} Resolver
* @memberof hydrolysis
* @property {function(string, Deferred): boolean} accept Attempt to resolve
* `deferred` with the contents the specified URL. Returns false if the
* Resolver is unable to resolve the URL.
*/
/**
* A FileLoader lets you resolve URLs with a set of potential resolvers.
* @constructor
* @memberof hydrolysis
*/
function FileLoader() {
this.resolvers = [];
// map url -> Deferred
this.requests = {};
}
FileLoader.prototype = {
/**
* Add an instance of a Resolver class to the list of url resolvers
*
* Ordering of resolvers is most to least recently added
* The first resolver to "accept" the url wins.
* @param {Resolver} resolver The resolver to add.
*/
addResolver: function(resolver) {
this.resolvers.push(resolver);
},
/**
* Return a promise for an absolute url
*
* Url requests are deduplicated by the loader, returning the same Promise for
* identical urls
*
* @param {string} url The absolute url to request.
* @return {Promise.<string>} A promise that resolves to the contents of the URL.
*/
request: function(uri) {
var promise;
if (!(uri in this.requests)) {
var handled = false;
var deferred = new Deferred();
this.requests[uri] = deferred;
// loop backwards through resolvers until one "accepts" the request
for (var i = this.resolvers.length - 1, r; i >= 0; i--) {
r = this.resolvers[i];
if (r.accept(uri, deferred)) {
handled = true;
break;
}
}
if (!handled) {
deferred.reject(new Error('no resolver found for ' + uri));
}
promise = deferred.promise;
} else {
promise = this.requests[uri].promise;
}
return promise;
}
};
module.exports = FileLoader;

110
build/node_modules/hydrolysis/lib/loader/fs-resolver.js generated vendored Normal file
View File

@@ -0,0 +1,110 @@
/**
* @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 fs = require('fs');
var path = require('path');
var pathIsAbsolute = require('path-is-absolute');
var url = require('url');
function getFile(filePath, deferred, secondPath) {
fs.readFile(filePath, 'utf-8', function(err, content) {
if (err) {
if (secondPath) {
getFile(secondPath, deferred);
} else {
console.log("ERROR finding " + filePath);
deferred.reject(err);
}
} else {
deferred.resolve(content);
}
});
}
/**
* Returns true if `patha` is a sibling or aunt of `pathb`.
* @return {boolean}
*/
function isSiblingOrAunt(patha, pathb) {
var parent = path.dirname(patha);
if (pathb.indexOf(patha) === -1 && pathb.indexOf(parent) === 0) {
return true;
}
return false;
}
/**
* Change `localPath` from a sibling of `basePath` to be a child of
* `basePath` joined with `redirect`.
* @return {string}
*/
function redirectSibling(basePath, localPath, redirect) {
var parent = path.dirname(basePath);
var redirected = path.join(basePath, redirect, localPath.slice(parent.length));
return redirected;
}
/**
* Resolves requests via the file system.
* @constructor
* @memberof hydrolysis
* @param {Object} config configuration options.
* @param {string} config.host Hostname to match for absolute urls.
* Matches "/" by default
* @param {string} config.basePath Prefix directory for components in url.
* Defaults to "/".
* @param {string} config.root Filesystem root to search. Defaults to the
* current working directory.
* @param {string} config.redirect Where to redirect lookups to siblings.
*/
function FSResolver(config) {
this.config = config || {};
}
FSResolver.prototype = {
accept: function(uri, deferred) {
var parsed = url.parse(uri);
var host = this.config.host;
var base = this.config.basePath && decodeURIComponent(this.config.basePath);
var root = this.config.root && path.normalize(this.config.root);
var redirect = this.config.redirect;
var local;
if (!parsed.hostname || parsed.hostname === host) {
local = parsed.pathname;
}
if (local) {
// un-escape HTML escapes
local = decodeURIComponent(local);
if (base) {
local = path.relative(base, local);
}
if (root) {
local = path.join(root, local);
}
var backup;
if (redirect && isSiblingOrAunt(root, local)) {
backup = redirectSibling(root, local, redirect);
}
getFile(local, deferred, backup);
return true;
}
return false;
}
};
module.exports = FSResolver;

View File

@@ -0,0 +1,44 @@
/**
* @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';
/**
* A resolver that resolves to null any uri matching config.
* @constructor
* @memberof hydrolysis
* @param {string} config The url to `accept`.
*/
function NoopResolver(config) {
this.config = config;
}
NoopResolver.prototype = {
/**
* @param {string} uri The absolute URI being requested.
* @param {!Deferred} deferred The deferred promise that should be resolved if
* this resolver handles the URI.
* @return {boolean} Whether the URI is handled by this resolver.
*/
accept: function(uri, deferred) {
if (!this.config.test) {
if (uri.search(this.config) == -1) {
return false;
}
} else if (!this.config.test(uri)) return false;
deferred.resolve('');
return true;
}
};
module.exports = NoopResolver;

View File

@@ -0,0 +1,106 @@
/**
* @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 fs = require('fs');
var path = require('path');
var url = require('url');
var FSResolver = require('./fs-resolver');
/**
* A single redirect configuration
* @param {Object} config The configuration object
* @param {string} config.protocol The protocol this redirect matches.
* @param {string} config.hostname The host name this redirect matches.
* @param {string} config.path The part of the path to match and
* replace with 'redirectPath'
* @param {string} config.redirectPath The local filesystem path that should
* replace "protocol://hosname/path/"
*/
function ProtocolRedirect(config){
this.protocol = config.protocol;
this.hostname = config.hostname;
this.path = config.path;
this.redirectPath = config.redirectPath;
}
ProtocolRedirect.prototype = {
/**
* The protocol this redirect matches.
* @type {string}
*/
protocol: null,
/**
* The host name this redirect matches.
* @type {string}
*/
hostname: null,
/**
* The part of the path to match and replace with 'redirectPath'
* @type {string}
*/
path: null,
/**
* The local filesystem path that should replace "protocol://hosname/path/"
* @type {string}
*/
redirectPath: null,
redirect: function redirect(uri) {
var parsed = url.parse(uri);
if (this.protocol !== parsed.protocol) {
return null;
} else if (this.hostname !== parsed.hostname) {
return null;
} else if (parsed.pathname.indexOf(this.path) !== 0) {
return null;
}
return path.join(this.redirectPath,
parsed.pathname.slice(this.path.length));
}
};
/**
* Resolves protocol://hostname/path to the local filesystem.
* @constructor
* @memberof hydrolysis
* @param {Object} config configuration options.
* @param {string} config.root Filesystem root to search. Defaults to the
* current working directory.
* @param {Array.<ProtocolRedirect>} redirects A list of protocol redirects
* for the resolver. They are checked for matching first-to-last.
*/
function RedirectResolver(config) {
FSResolver.call(this, config);
this.redirects = config.redirects || [];
}
RedirectResolver.prototype = Object.create(FSResolver.prototype);
RedirectResolver.prototype.accept = function(uri, deferred) {
for (var i = 0; i < this.redirects.length; i++) {
var redirected = this.redirects[i].redirect(uri);
if (redirected) {
return FSResolver.prototype.accept.call(this, redirected, deferred);
}
}
return false;
};
RedirectResolver.prototype.constructor = RedirectResolver;
RedirectResolver.ProtocolRedirect = ProtocolRedirect;
module.exports = RedirectResolver;

View File

@@ -0,0 +1,54 @@
/**
* @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';
/**
* A resolver that resolves to `config.content` any uri matching config.
* @constructor
* @memberof hydrolysis
* @param {string|RegExp} config.url The url or rejex to accept.
* @param {string} config.content The content to serve for `url`.
*/
function StringResolver(config) {
this.url = config.url;
this.content = config.content;
if (!this.url || !this.content) {
throw new Error("Must provide a url and content to the string resolver.");
}
}
StringResolver.prototype = {
/**
* @param {string} uri The absolute URI being requested.
* @param {!Deferred} deferred The deferred promise that should be resolved if
* this resolver handles the URI.
* @return {boolean} Whether the URI is handled by this resolver.
*/
accept: function(uri, deferred) {
if (this.url.test) {
// this.url is a regex
if (!this.url.test(uri)) {
return false;
}
} else {
// this.url is a string
if (uri.search(this.url) == -1) {
return false;
}
}
deferred.resolve(this.content);
return true;
}
};
module.exports = StringResolver;

View File

@@ -0,0 +1,53 @@
/**
* @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';
function getFile(url, deferred, config) {
/* global XMLHttpRequest:false */
var x = new XMLHttpRequest();
x.onload = function() {
var status = x.status || 0;
if (status >= 200 && status < 300) {
deferred.resolve(x.response);
} else {
deferred.reject('xhr status: ' + status);
}
};
x.onerror = function(e) {
deferred.reject(e);
};
x.open('GET', url, true);
if (config && config.responseType) {
x.responseType = config.responseType;
}
x.send();
}
/**
* Construct a resolver that requests resources over XHR.
* @constructor
* @memberof hydrolysis
* @param {Object} config configuration arguments.
* @param {string} config.responseType Type of object to be returned by the
* XHR. Defaults to 'text', accepts 'document', 'arraybuffer', and 'json'.
*/
function XHRResolver(config) {
this.config = config;
}
XHRResolver.prototype = {
accept: function(uri, deferred) {
getFile(uri, deferred, this.config);
return true;
}
};
module.exports = XHRResolver;