Skip to content

Commit

Permalink
fix: Restore ES5 compatibility (#452)
Browse files Browse the repository at this point in the history
by adding a ponyfill for `Array.prototype.find` to conventions
and converting `let` to `var`
  • Loading branch information
karfau committed Oct 31, 2022
1 parent 27fec1f commit 2debbf3
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 16 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.

This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.8.5](https://github.com/xmldom/xmldom/compare/0.8.4...0.8.5)

### Fixed

- fix: Restore ES5 compatibility [`#452`](https://github.com/xmldom/xmldom/pull/452) / [`#453`](https://github.com/xmldom/xmldom/issues/453)

Thank you, [@fengxinming](https://github.com/fengxinming), for your contributions


## [0.8.4](https://github.com/xmldom/xmldom/compare/0.8.3...0.8.4)

### Fixed
Expand Down
33 changes: 33 additions & 0 deletions lib/conventions.js
@@ -1,5 +1,37 @@
'use strict'

/**
* Ponyfill for `Array.prototype.find` which is only available in ES6 runtimes.
*
* Works with anything that has a `length` property and index access properties, including NodeList.
*
* @template {unknown} T
* @param {Array<T> | ({length:number, [number]: T})} list
* @param {function (item: T, index: number, list:Array<T> | ({length:number, [number]: T})):boolean} predicate
* @param {Partial<Pick<ArrayConstructor['prototype'], 'find'>>?} ac `Array.prototype` by default,
* allows injecting a custom implementation in tests
* @returns {T | undefined}
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
* @see https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.find
*/
function find(list, predicate, ac) {
if (ac === undefined) {
ac = Array.prototype;
}
if (list && typeof ac.find === 'function') {
return ac.find.call(list, predicate);
}
for (var i = 0; i < list.length; i++) {
if (Object.prototype.hasOwnProperty.call(list, i)) {
var item = list[i];
if (predicate.call(undefined, item, i, list)) {
return item;
}
}
}
}

/**
* "Shallow freezes" an object to render it immutable.
* Uses `Object.freeze` if available,
Expand Down Expand Up @@ -165,6 +197,7 @@ var NAMESPACE = freeze({
})

exports.assign = assign;
exports.find = find;
exports.freeze = freeze;
exports.MIME_TYPE = MIME_TYPE;
exports.NAMESPACE = NAMESPACE;
23 changes: 8 additions & 15 deletions lib/dom.js
@@ -1,5 +1,6 @@
var conventions = require("./conventions");

var find = conventions.find;
var NAMESPACE = conventions.NAMESPACE;

/**
Expand Down Expand Up @@ -176,14 +177,6 @@ NodeList.prototype = {
}
return buf.join('');
},
/**
* @private
* @param {function (Node):boolean} predicate
* @returns {Node | undefined}
*/
find: function (predicate) {
return Array.prototype.find.call(this, predicate);
},
/**
* @private
* @param {function (Node):boolean} predicate
Expand Down Expand Up @@ -748,10 +741,10 @@ function isTextNode(node) {
*/
function isElementInsertionPossible(doc, child) {
var parentChildNodes = doc.childNodes || [];
if (parentChildNodes.find(isElementNode) || isDocTypeNode(child)) {
if (find(parentChildNodes, isElementNode) || isDocTypeNode(child)) {
return false;
}
var docTypeNode = parentChildNodes.find(isDocTypeNode);
var docTypeNode = find(parentChildNodes, isDocTypeNode);
return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
}
/**
Expand Down Expand Up @@ -786,24 +779,24 @@ function _insertBefore(parent, node, child) {
var nodeChildNodes = node.childNodes || [];
if (parent.nodeType === Node.DOCUMENT_NODE) {
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
let nodeChildElements = nodeChildNodes.filter(isElementNode);
if (nodeChildElements.length > 1 || nodeChildNodes.find(isTextNode)) {
var nodeChildElements = nodeChildNodes.filter(isElementNode);
if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) {
throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
}
if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) {
throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
}
}
if (isElementNode(node)) {
if (parentChildNodes.find(isElementNode) || !isElementInsertionPossible(parent, child)) {
if (find(parentChildNodes, isElementNode) || !isElementInsertionPossible(parent, child)) {
throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
}
}
if (isDocTypeNode(node)) {
if (parentChildNodes.find(isDocTypeNode)) {
if (find(parentChildNodes, isDocTypeNode)) {
throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
}
let parentElementChild = parentChildNodes.find(isElementNode);
var parentElementChild = find(parentChildNodes, isElementNode);
if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -34,8 +34,9 @@
"stryker": "stryker run",
"stryker:dry-run": "stryker run -m '' --reporters progress",
"test": "jest",
"testrelease": "npm test && npm run lint",
"version": "./changelog-has-version.sh",
"release": "np --no-yarn"
"release": "np --no-yarn --test-script testrelease"
},
"engines": {
"node": ">=10.0.0"
Expand Down
24 changes: 24 additions & 0 deletions test/conventions/find.test.js
@@ -0,0 +1,24 @@
'use strict';

const { find } = require('../../lib/conventions');

describe('find', () => {
it('should work in node without pasing an ArrayConstructor', () => {
const predicate = jest.fn((item) => item === 'b');
const list = ['a', 'b', 'c'];

expect(find(list, predicate)).toBe('b');
expect(predicate).toHaveBeenCalledTimes(2);
expect(predicate).toHaveBeenCalledWith('a', 0, list);
expect(predicate).toHaveBeenCalledWith('b', 1, list);
});
it('should work when ArrayConstructor does not provide find', () => {
const predicate = jest.fn((item) => item === 'b');
const list = ['a', 'b', 'c'];

expect(find(list, predicate, {})).toBe('b');
expect(predicate).toHaveBeenCalledTimes(2);
expect(predicate).toHaveBeenCalledWith('a', 0, list);
expect(predicate).toHaveBeenCalledWith('b', 1, list);
});
});

0 comments on commit 2debbf3

Please sign in to comment.