@open-wc/building-utils
Version:
Utils for @open-wc packages that do build stuff
636 lines (634 loc) • 17.6 kB
JavaScript
/* eslint-disable */
// @ts-nocheck
;
/**
* @license
* Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at
* http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at
* http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
var cloneObject = require('clone');
var parse5_1 = require('parse5');
function getAttributeIndex(element, name) {
if (!element.attrs) {
return -1;
}
var n = name.toLowerCase();
for (var i = 0; i < element.attrs.length; i++) {
if (element.attrs[i].name.toLowerCase() === n) {
return i;
}
}
return -1;
}
/**
* @returns `true` iff [element] has the attribute [name], `false` otherwise.
*/
function hasAttribute(element, name) {
return getAttributeIndex(element, name) !== -1;
}
module.exports.hasAttribute = hasAttribute;
function hasSpaceSeparatedAttrValue(name, value) {
return function (element) {
var attributeValue = getAttribute(element, name);
if (typeof attributeValue !== 'string') {
return false;
}
return attributeValue.split(' ').indexOf(value) !== -1;
};
}
module.exports.hasSpaceSeparatedAttrValue = hasSpaceSeparatedAttrValue;
/**
* @returns The string value of attribute `name`, or `null`.
*/
function getAttribute(element, name) {
var i = getAttributeIndex(element, name);
if (i > -1) {
return element.attrs[i].value;
}
return null;
}
module.exports.getAttribute = getAttribute;
function setAttribute(element, name, value) {
var i = getAttributeIndex(element, name);
if (i > -1) {
element.attrs[i].value = value;
} else {
element.attrs.push({ name: name, value: value });
}
}
module.exports.setAttribute = setAttribute;
function removeAttribute(element, name) {
var i = getAttributeIndex(element, name);
if (i > -1) {
element.attrs.splice(i, 1);
}
}
module.exports.removeAttribute = removeAttribute;
function hasTagName(name) {
var n = name.toLowerCase();
return function (node) {
if (!node.tagName) {
return false;
}
return node.tagName.toLowerCase() === n;
};
}
/**
* Returns true if `regex.match(tagName)` finds a match.
*
* This will use the lowercased tagName for comparison.
*/
function hasMatchingTagName(regex) {
return function (node) {
if (!node.tagName) {
return false;
}
return regex.test(node.tagName.toLowerCase());
};
}
function hasClass(name) {
return hasSpaceSeparatedAttrValue('class', name);
}
function collapseTextRange(parent, start, end) {
if (!parent.childNodes) {
return;
}
var text = '';
for (var i = start; i <= end; i++) {
text += getTextContent(parent.childNodes[i]);
}
parent.childNodes.splice(start, end - start + 1);
if (text) {
var tn = newTextNode(text);
tn.parentNode = parent;
parent.childNodes.splice(start, 0, tn);
}
}
/**
* Normalize the text inside an element
*
* Equivalent to `element.normalize()` in the browser
* See https://developer.mozilla.org/en-US/docs/Web/API/Node/normalize
*/
function normalize(node) {
if (!(isElement(node) || isDocument(node) || isDocumentFragment(node))) {
return;
}
if (!node.childNodes) {
return;
}
var textRangeStart = -1;
for (var i = node.childNodes.length - 1, n = void 0; i >= 0; i--) {
n = node.childNodes[i];
if (isTextNode(n)) {
if (textRangeStart === -1) {
textRangeStart = i;
}
if (i === 0) {
// collapse leading text nodes
collapseTextRange(node, 0, textRangeStart);
}
} else {
// recurse
normalize(n);
// collapse the range after this node
if (textRangeStart > -1) {
collapseTextRange(node, i + 1, textRangeStart);
textRangeStart = -1;
}
}
}
}
module.exports.normalize = normalize;
/**
* Return the text value of a node or element
*
* Equivalent to `node.textContent` in the browser
*/
function getTextContent(node) {
if (isCommentNode(node)) {
return node.data || '';
}
if (isTextNode(node)) {
return node.value || '';
}
var subtree = nodeWalkAll(node, isTextNode);
return subtree.map(getTextContent).join('');
}
module.exports.getTextContent = getTextContent;
/**
* Set the text value of a node or element
*
* Equivalent to `node.textContent = value` in the browser
*/
function setTextContent(node, value) {
if (isCommentNode(node)) {
node.data = value;
} else if (isTextNode(node)) {
node.value = value;
} else {
var tn = newTextNode(value);
tn.parentNode = node;
node.childNodes = [tn];
}
}
module.exports.setTextContent = setTextContent;
/**
* Match the text inside an element, textnode, or comment
*
* Note: nodeWalkAll with hasTextValue may return an textnode and its parent if
* the textnode is the only child in that parent.
*/
function hasTextValue(value) {
return function (node) {
return getTextContent(node) === value;
};
}
function OR() {
var rules = new Array(arguments.length);
for (var i = 0; i < arguments.length; i++) {
rules[i] = arguments[i];
}
return function (node) {
for (var i = 0; i < rules.length; i++) {
if (rules[i](node)) {
return true;
}
}
return false;
};
}
function AND() {
var rules = new Array(arguments.length);
for (var i = 0; i < arguments.length; i++) {
rules[i] = arguments[i];
}
return function (node) {
for (var i = 0; i < rules.length; i++) {
if (!rules[i](node)) {
return false;
}
}
return true;
};
}
/**
* negate an individual predicate, or a group with AND or OR
*/
function NOT(predicateFn) {
return function (node) {
return !predicateFn(node);
};
}
/**
* Returns a predicate that matches any node with a parent matching
* `predicateFn`.
*/
function parentMatches(predicateFn) {
return function (node) {
var parent = node.parentNode;
while (parent !== undefined) {
if (predicateFn(parent)) {
return true;
}
parent = parent.parentNode;
}
return false;
};
}
function hasAttr(attr) {
return function (node) {
return getAttributeIndex(node, attr) > -1;
};
}
function hasAttrValue(attr, value) {
return function (node) {
return getAttribute(node, attr) === value;
};
}
function isDocument(node) {
return node.nodeName === '#document';
}
module.exports.isDocument = isDocument;
function isDocumentFragment(node) {
return node.nodeName === '#document-fragment';
}
module.exports.isDocumentFragment = isDocumentFragment;
function isElement(node) {
return node.nodeName === node.tagName;
}
module.exports.isElement = isElement;
function isTextNode(node) {
return node.nodeName === '#text';
}
module.exports.isTextNode = isTextNode;
function isCommentNode(node) {
return node.nodeName === '#comment';
}
module.exports.isCommentNode = isCommentNode;
/**
* Applies `mapfn` to `node` and the tree below `node`, returning a flattened
* list of results.
*/
function treeMap(node, mapfn) {
var results = [];
nodeWalk(node, function (node) {
results = results.concat(mapfn(node));
return false;
});
return results;
}
module.exports.treeMap = treeMap;
module.exports.defaultChildNodes = function (node) {
return node.childNodes;
};
module.exports.childNodesIncludeTemplate = function (node) {
if (node.nodeName === 'template') {
return parse5_1.treeAdapters['default'].getTemplateContent(node).childNodes;
}
return node.childNodes;
};
/**
* Walk the tree down from `node`, applying the `predicate` function.
* Return the first node that matches the given predicate.
*
* @returns `null` if no node matches, parse5 node object if a node matches.
*/
function nodeWalk(node, predicate, getChildNodes) {
if (getChildNodes === void 0) {
getChildNodes = module.exports.defaultChildNodes;
}
if (predicate(node)) {
return node;
}
var match = null;
var childNodes = getChildNodes(node);
if (childNodes) {
for (var i = 0; i < childNodes.length; i++) {
match = nodeWalk(childNodes[i], predicate, getChildNodes);
if (match) {
break;
}
}
}
return match;
}
module.exports.nodeWalk = nodeWalk;
/**
* Walk the tree down from `node`, applying the `predicate` function.
* All nodes matching the predicate function from `node` to leaves will be
* returned.
*/
function nodeWalkAll(node, predicate, matches, getChildNodes) {
if (getChildNodes === void 0) {
getChildNodes = module.exports.defaultChildNodes;
}
if (!matches) {
matches = [];
}
if (predicate(node)) {
matches.push(node);
}
var childNodes = getChildNodes(node);
if (childNodes) {
for (var i = 0; i < childNodes.length; i++) {
nodeWalkAll(childNodes[i], predicate, matches, getChildNodes);
}
}
return matches;
}
module.exports.nodeWalkAll = nodeWalkAll;
function _reverseNodeWalkAll(node, predicate, matches, getChildNodes) {
if (getChildNodes === void 0) {
getChildNodes = module.exports.defaultChildNodes;
}
if (!matches) {
matches = [];
}
var childNodes = getChildNodes(node);
if (childNodes) {
for (var i = childNodes.length - 1; i >= 0; i--) {
nodeWalkAll(childNodes[i], predicate, matches, getChildNodes);
}
}
if (predicate(node)) {
matches.push(node);
}
return matches;
}
/**
* Equivalent to `nodeWalk`, but only returns nodes that are either
* ancestors or earlier cousins/siblings in the document.
*
* Nodes are searched in reverse document order, starting from the sibling
* prior to `node`.
*/
function nodeWalkPrior(node, predicate) {
// Search our earlier siblings and their descendents.
var parent = node.parentNode;
if (parent && parent.childNodes) {
var idx = parent.childNodes.indexOf(node);
var siblings = parent.childNodes.slice(0, idx);
for (var i = siblings.length - 1; i >= 0; i--) {
var sibling = siblings[i];
if (predicate(sibling)) {
return sibling;
}
var found = nodeWalk(sibling, predicate);
if (found) {
return found;
}
}
if (predicate(parent)) {
return parent;
}
return nodeWalkPrior(parent, predicate);
}
return undefined;
}
module.exports.nodeWalkPrior = nodeWalkPrior;
/**
* Walk the tree up from the parent of `node`, to its grandparent and so on to
* the root of the tree. Return the first ancestor that matches the given
* predicate.
*/
function nodeWalkAncestors(node, predicate) {
var parent = node.parentNode;
if (!parent) {
return undefined;
}
if (predicate(parent)) {
return parent;
}
return nodeWalkAncestors(parent, predicate);
}
module.exports.nodeWalkAncestors = nodeWalkAncestors;
/**
* Equivalent to `nodeWalkAll`, but only returns nodes that are either
* ancestors or earlier cousins/siblings in the document.
*
* Nodes are returned in reverse document order, starting from `node`.
*/
function nodeWalkAllPrior(node, predicate, matches) {
if (!matches) {
matches = [];
}
if (predicate(node)) {
matches.push(node);
}
// Search our earlier siblings and their descendents.
var parent = node.parentNode;
if (parent) {
var idx = parent.childNodes.indexOf(node);
var siblings = parent.childNodes.slice(0, idx);
for (var i = siblings.length - 1; i >= 0; i--) {
_reverseNodeWalkAll(siblings[i], predicate, matches);
}
nodeWalkAllPrior(parent, predicate, matches);
}
return matches;
}
module.exports.nodeWalkAllPrior = nodeWalkAllPrior;
/**
* Equivalent to `nodeWalk`, but only matches elements
*/
function query(node, predicate, getChildNodes) {
if (getChildNodes === void 0) {
getChildNodes = module.exports.defaultChildNodes;
}
var elementPredicate = AND(isElement, predicate);
return nodeWalk(node, elementPredicate, getChildNodes);
}
module.exports.query = query;
/**
* Equivalent to `nodeWalkAll`, but only matches elements
*/
function queryAll(node, predicate, matches, getChildNodes) {
if (getChildNodes === void 0) {
getChildNodes = module.exports.defaultChildNodes;
}
var elementPredicate = AND(isElement, predicate);
return nodeWalkAll(node, elementPredicate, matches, getChildNodes);
}
module.exports.queryAll = queryAll;
function newTextNode(value) {
return {
nodeName: '#text',
value: value,
parentNode: undefined,
attrs: [],
__location: undefined,
};
}
function newCommentNode(comment) {
return {
nodeName: '#comment',
data: comment,
parentNode: undefined,
attrs: [],
__location: undefined,
};
}
function newElement(tagName, namespace) {
return {
nodeName: tagName,
tagName: tagName,
childNodes: [],
namespaceURI: namespace || 'http://www.w3.org/1999/xhtml',
attrs: [],
parentNode: undefined,
__location: undefined,
};
}
function newDocumentFragment() {
return {
nodeName: '#document-fragment',
childNodes: [],
parentNode: undefined,
quirksMode: false,
// TODO(rictic): update parse5 typings upstream to mention that attrs and
// __location are optional and not always present.
attrs: undefined,
__location: null,
};
}
function cloneNode(node) {
// parent is a backreference, and we don't want to clone the whole tree, so
// make it null before cloning.
var parent = node.parentNode;
node.parentNode = undefined;
var clone = cloneObject(node);
node.parentNode = parent;
return clone;
}
module.exports.cloneNode = cloneNode;
/**
* Inserts `newNode` into `parent` at `index`, optionally replaceing the
* current node at `index`. If `newNode` is a DocumentFragment, its childNodes
* are inserted and removed from the fragment.
*/
function insertNode(parent, index, newNode, replace) {
if (!parent.childNodes) {
parent.childNodes = [];
}
var newNodes = [];
var removedNode = replace ? parent.childNodes[index] : null;
if (newNode) {
if (isDocumentFragment(newNode)) {
if (newNode.childNodes) {
newNodes = Array.from(newNode.childNodes);
newNode.childNodes.length = 0;
}
} else {
newNodes = [newNode];
remove(newNode);
}
}
if (replace) {
removedNode = parent.childNodes[index];
}
Array.prototype.splice.apply(parent.childNodes, [index, replace ? 1 : 0].concat(newNodes));
newNodes.forEach(function (n) {
n.parentNode = parent;
});
if (removedNode) {
removedNode.parentNode = undefined;
}
}
function replace(oldNode, newNode) {
var parent = oldNode.parentNode;
var index = parent.childNodes.indexOf(oldNode);
insertNode(parent, index, newNode, true);
}
module.exports.replace = replace;
function remove(node) {
var parent = node.parentNode;
if (parent && parent.childNodes) {
var idx = parent.childNodes.indexOf(node);
parent.childNodes.splice(idx, 1);
}
node.parentNode = undefined;
}
module.exports.remove = remove;
function insertBefore(parent, target, newNode) {
var index = parent.childNodes.indexOf(target);
insertNode(parent, index, newNode);
}
module.exports.insertBefore = insertBefore;
function insertAfter(parent, target, newNode) {
var index = parent.childNodes.indexOf(target);
insertNode(parent, index + 1, newNode);
}
module.exports.insertAfter = insertAfter;
/**
* Removes a node and places its children in its place. If the node
* has no parent, the operation is impossible and no action takes place.
*/
function removeNodeSaveChildren(node) {
// We can't save the children if there's no parent node to provide
// for them.
var fosterParent = node.parentNode;
if (!fosterParent) {
return;
}
var children = (node.childNodes || []).slice();
for (var _i = 0, children_1 = children; _i < children_1.length; _i++) {
var child = children_1[_i];
insertBefore(node.parentNode, node, child);
}
remove(node);
}
module.exports.removeNodeSaveChildren = removeNodeSaveChildren;
/**
* When parse5 parses an HTML document with `parse`, it injects missing root
* elements (html, head and body) if they are missing. This function removes
* these from the AST if they have no location info, so it requires that
* the `parse5.parse` be used with the `locationInfo` option of `true`.
*/
function removeFakeRootElements(ast) {
var injectedNodes = queryAll(
ast,
AND(function (node) {
return !node.__location;
}, hasMatchingTagName(/^(html|head|body)$/i)),
undefined,
// Don't descend past 3 levels 'document > html > head|body'
function (node) {
return node.parentNode && node.parentNode.parentNode ? undefined : node.childNodes;
},
);
injectedNodes.reverse().forEach(removeNodeSaveChildren);
}
module.exports.removeFakeRootElements = removeFakeRootElements;
function append(parent, newNode) {
var index = (parent.childNodes && parent.childNodes.length) || 0;
insertNode(parent, index, newNode);
}
module.exports.append = append;
module.exports.predicates = {
hasClass: hasClass,
hasAttr: hasAttr,
hasAttrValue: hasAttrValue,
hasMatchingTagName: hasMatchingTagName,
hasSpaceSeparatedAttrValue: hasSpaceSeparatedAttrValue,
hasTagName: hasTagName,
hasTextValue: hasTextValue,
AND: AND,
OR: OR,
NOT: NOT,
parentMatches: parentMatches,
};
module.exports.constructors = {
text: newTextNode,
comment: newCommentNode,
element: newElement,
fragment: newDocumentFragment,
};