@web/dev-server-core
Version:
184 lines (163 loc) • 5.31 kB
text/typescript
/**
* @license
* Copyright (c) 2018 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
*/
import cloneObject from 'clone';
import { isDocumentFragment, predicates as p } from './predicates.js';
import { queryAll } from './walking.js';
function newTextNode(value: string): any {
return {
nodeName: '#text',
value: value,
parentNode: undefined,
attrs: [],
__location: <any>undefined,
};
}
function newCommentNode(comment: string): any {
return {
nodeName: '#comment',
data: comment,
parentNode: undefined,
attrs: [],
__location: <any>undefined,
};
}
function newElement(tagName: string, namespace?: string): any {
return {
nodeName: tagName,
tagName: tagName,
childNodes: [],
namespaceURI: namespace || 'http://www.w3.org/1999/xhtml',
attrs: [],
parentNode: undefined,
__location: <any>undefined,
};
}
function newDocumentFragment(): any {
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 as any,
__location: null as any,
};
}
export function cloneNode(node: any): any {
// parent is a backreference, and we don't want to clone the whole tree, so
// make it null before cloning.
const parent = node.parentNode;
node.parentNode = undefined;
const clone = cloneObject(node);
node.parentNode = parent;
return clone;
}
/**
* 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: any, index: number, newNode: any, replace?: boolean) {
if (!parent.childNodes) {
parent.childNodes = [];
}
let newNodes: any[] = [];
let 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, (<any>[index, replace ? 1 : 0]).concat(newNodes));
newNodes.forEach(function (n) {
n.parentNode = parent;
});
if (removedNode) {
removedNode.parentNode = undefined;
}
}
export function replace(oldNode: any, newNode: any) {
const parent = oldNode.parentNode;
const index = parent!.childNodes!.indexOf(oldNode);
insertNode(parent!, index, newNode, true);
}
export function remove(node: any) {
const parent = node.parentNode;
if (parent && parent.childNodes) {
const idx = parent.childNodes.indexOf(node);
parent.childNodes.splice(idx, 1);
}
node.parentNode = undefined;
}
export function insertBefore(parent: any, target: any, newNode: any) {
const index = parent.childNodes!.indexOf(target);
insertNode(parent, index, newNode);
}
export function insertAfter(parent: any, target: any, newNode: any) {
const index = parent.childNodes!.indexOf(target);
insertNode(parent, index + 1, newNode);
}
/**
* 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.
*/
export function removeNodeSaveChildren(node: any) {
// We can't save the children if there's no parent node to provide
// for them.
const fosterParent = node.parentNode;
if (!fosterParent) {
return;
}
const children = (node.childNodes || []).slice();
for (const child of children) {
insertBefore(node.parentNode!, node, child);
}
remove(node);
}
/**
* 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`.
*/
export function removeFakeRootElements(ast: Node) {
const injectedNodes = queryAll(
ast,
p.AND(node => !node.__location, p.hasMatchingTagName(/^(html|head|body)$/i)),
undefined,
// Don't descend past 3 levels 'document > html > head|body'
node => (node.parentNode && node.parentNode.parentNode ? undefined : node.childNodes),
);
injectedNodes.reverse().forEach(removeNodeSaveChildren);
}
export function append(parent: any, newNode: any) {
const index = (parent.childNodes && parent.childNodes.length) || 0;
insertNode(parent, index, newNode);
}
export const constructors = {
text: newTextNode,
comment: newCommentNode,
element: newElement,
fragment: newDocumentFragment,
};