diff --git a/package-lock.json b/package-lock.json index 8f9fb90a..f16c11f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2482 +1,25 @@ { "name": "phpcore", "version": "8.0.3", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "phpcore", - "version": "8.0.3", - "license": "MIT", - "dependencies": { - "core-js-pure": "^3.30.1", - "es6-weak-map": "^2.0.3", - "is-promise": "^4.0.0", - "lie": "^3.3.0", - "microdash": "^1.4.2", - "pauser": "^1.1.1", - "phpcommon": "^2.0.2" - }, - "devDependencies": { - "chai": "^4.3.7", - "chai-as-promised": "^7.1.1", - "jshint": "^2.13.6", - "mocha": "^10.2.0", - "mocha-bootstrap": "^1.0.4", - "nowdoc": "^1.0.1", - "phptest": "^0.0.10", - "phptoast": "^9.1.1", - "phptojs": "^10.0.2", - "sinon": "^15.0.4", - "sinon-chai": "^3.7.0" - }, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", - "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^2.0.0" - } - }, - "node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", - "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^2.0.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chai-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", - "dev": true, - "dependencies": { - "check-error": "^1.0.2" - }, - "peerDependencies": { - "chai": ">= 2.1.2 < 5" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/cli": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", - "integrity": "sha512-41U72MB56TfUMGndAKK8vJ78eooOD4Z5NOL4xEfjc0c23s+6EYKXlXsmACBVclLP1yOfWCgEganVzddVrSNoTg==", - "dev": true, - "dependencies": { - "exit": "0.1.2", - "glob": "^7.1.1" - }, - "engines": { - "node": ">=0.2.5" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha512-duS7VP5pvfsNLDvL1O4VOEbw37AI3A4ZUQYemvDlnpGrNu9tprR7BYWpDYwC0Xia0Zxz5ZupdiIrUp0GH1aXfg==", - "dev": true, - "dependencies": { - "date-now": "^0.1.4" - } - }, - "node_modules/core-js-pure": { - "version": "3.30.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.30.1.tgz", - "integrity": "sha512-nXBEVpmUnNRhz83cHd9JRQC52cTMcuXAmR56+9dSMpRdpeA4I1PX6yjmhd71Eyc/wXNsdBdUDIj1QTIeZpU5Tg==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "node_modules/date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha512-AsElvov3LoNB7tf5k37H2jYSB+ZZPMT5sG2QjJCcdlV5chIv6htBUBUui2IKRjgtKAKtCBN7Zbwa+MtwLjSeNw==", - "dev": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "dev": true, - "dependencies": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - } - }, - "node_modules/dom-serializer/node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true - }, - "node_modules/domhandler": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", - "integrity": "sha512-q9bUwjfp7Eif8jWxxxPSykdRZAb6GkguBGSgvvCrhI9wB71W2K/Kvv4E61CF/mcCfnVJDeDWx/Vb/uAqbDj6UQ==", - "dev": true, - "dependencies": { - "domelementtype": "1" - } - }, - "node_modules/domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", - "dev": true, - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/entities": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", - "integrity": "sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ==", - "dev": true - }, - "node_modules/es-abstract": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", - "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "hasInstallScript": true, - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-set": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.6.tgz", - "integrity": "sha512-TE3LgGLDIBX332jq3ypv6bcOpkLO0AslAQo7p2VqX/1N46YNsvIWgvjojjSEnWEGWMhr1qUbYeTSir5J6mFHOw==", - "dev": true, - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "es6-iterator": "~2.0.3", - "es6-symbol": "^3.1.3", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/es6-set/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "dev": true - }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "node_modules/es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dependencies": { - "type": "^2.7.2" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/htmlparser2": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", - "integrity": "sha512-hBxEg3CYXe+rPIua8ETe7tmG3XDn9B0edOE/e9wH2nLczxzgdu0m0aNHY+5wFZiviLWLdANPJTssa92dMcXQ5Q==", - "dev": true, - "dependencies": { - "domelementtype": "1", - "domhandler": "2.3", - "domutils": "1.5", - "entities": "1.0", - "readable-stream": "1.1" - } - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jshint": { - "version": "2.13.6", - "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.6.tgz", - "integrity": "sha512-IVdB4G0NTTeQZrBoM8C5JFVLjV2KtZ9APgybDA1MK73xb09qFs0jCXyQLnCOp1cSZZZbvhq/6mfXHUTaDkffuQ==", - "dev": true, - "dependencies": { - "cli": "~1.0.0", - "console-browserify": "1.1.x", - "exit": "0.1.x", - "htmlparser2": "3.8.x", - "lodash": "~4.17.21", - "minimatch": "~3.0.2", - "strip-json-comments": "1.0.x" - }, - "bin": { - "jshint": "bin/jshint" - } - }, - "node_modules/just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", - "dev": true - }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "dev": true - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/microdash": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/microdash/-/microdash-1.4.2.tgz", - "integrity": "sha512-2R0iCltd/G5JKN3yCP9lgCbGLv+KpttUHIBW1Wfm7Mggbh3rsWEktHGsHHAGlGMjCYAvM+yL+eZgNxNGH6z+EQ==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", - "dev": true, - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha-bootstrap": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mocha-bootstrap/-/mocha-bootstrap-1.0.4.tgz", - "integrity": "sha512-d3snHM44rqR6TNnWyGvq1CBl5t9/0I6DcJ6ZjZjYlAtwI7itTBjuqAhIJKG+blsYrmHrADb5YM/mjryeIlCYww==", - "dev": true, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "chai": "^4.3.7", - "chai-as-promised": "^7.1.1", - "mocha": "^10.2.0", - "sinon": "^15.0.1", - "sinon-chai": "^3.7.0" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" - }, - "node_modules/nise": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", - "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^2.0.0", - "@sinonjs/fake-timers": "^10.0.2", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "path-to-regexp": "^1.7.0" - } - }, - "node_modules/nise/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nowdoc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/nowdoc/-/nowdoc-1.0.1.tgz", - "integrity": "sha512-/fYeEx57dUsMJy+Djb5fZalbsT/oyiI/qp7Icxos/sjgoeXRHiwHxFOnE3qWF8GNdz/ohqA2TK1OTIyKTgXUkg==", - "dev": true, - "dependencies": { - "microdash": "^1.3.0", - "template-string": "^1.1.0" - } - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parsing": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/parsing/-/parsing-2.3.0.tgz", - "integrity": "sha512-9KFnyd1tFJy1ocwgcd7aYlYo+huI1FKuxvpO48yTvjyweB6qm4oQDP9oUOaJaybY6iS9UY0tds9tTJgRmeyyPQ==", - "dev": true, - "dependencies": { - "microdash": "^1.4.2" - }, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/pauser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pauser/-/pauser-1.1.1.tgz", - "integrity": "sha512-cdXmv2MJ1jQEgs5/Yk576AKtJZkO4LdvfipIgqwggfRlGaDm2M1jJIoxkZCIiZ+BB79Uk9J7C0WZVyalL+rJGQ==", - "dependencies": { - "microdash": "~1" - }, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/phpcommon": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/phpcommon/-/phpcommon-2.0.2.tgz", - "integrity": "sha512-nA1y4ip3wbEvrM+jkRBD6kkEgHvLABo5SBrK15CrmBmTPhysb5aA0MUd6Fm2OpnL+yDW22zCdkPGgHXbqKDGkQ==", - "dependencies": { - "microdash": "^1.4.2", - "template-string": "~1" - }, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/phptest": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/phptest/-/phptest-0.0.10.tgz", - "integrity": "sha512-RvGI9c6/HMrpAtbpk6Lmy51Lmpj4HTpUHB/OSc3oYOchchbBTc3d7iyNVm3hSd/Bp7RTwW4qSZwMPa5Pg3ehhw==", - "dev": true, - "dependencies": { - "es6-weak-map": "^2.0.3", - "microdash": "^1.4.2", - "regexp.escape": "^1.1.0", - "source-map": "^0.8.0-beta.0" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "mocha": "^10.2.0", - "phptoast": "^9.1.0", - "phptojs": "^10.0.0" - } - }, - "node_modules/phptoast": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/phptoast/-/phptoast-9.1.1.tgz", - "integrity": "sha512-kHcH0dQjQLJ3EmmVJRVg0kBgMD274EZGX6A3s2Z8AbqrpQLCc4SmeA5rivSRcFIkNY3DQSWmc5y+IcojbbFi0w==", - "dev": true, - "dependencies": { - "microdash": "^1.4.2", - "parsing": "^2.3.0", - "phpcommon": "^2.0.2" - }, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/phptojs": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/phptojs/-/phptojs-10.0.2.tgz", - "integrity": "sha512-BzGci9CGHsWdbKHa//jdsuLIQ9YQygdUBn3TlD/F7sccZSF9fH9OzIZyLY+aWy9u7xGbHWsX0O1/HyNDZbhDOA==", - "dev": true, - "dependencies": { - "es6-set": "^0.1.6", - "microdash": "^1.4.2", - "phpcommon": "^2.0.2", - "source-map": "^0.8.0-beta.0", - "source-map-to-comment": "^2.0.0", - "transpiler": "~1.2" - }, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/regexp.escape": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/regexp.escape/-/regexp.escape-1.1.0.tgz", - "integrity": "sha512-KVeSRSGxG1oQn9Op7tkDaYo5MRacMciqL5cHzCsvChvFOCtyxLMplXe9p+Z3ItGMLAONUTtnHe+yEyYDDntdxA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sinon": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.4.tgz", - "integrity": "sha512-uzmfN6zx3GQaria1kwgWGeKiXSSbShBbue6Dcj0SI8fiCNFbiUDqKl57WFlY5lyhxZVUKmXvzgG2pilRQCBwWg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^10.0.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.1.0", - "nise": "^5.1.4", - "supports-color": "^7.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "node_modules/sinon-chai": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz", - "integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==", - "dev": true, - "peerDependencies": { - "chai": "^4.0.0", - "sinon": ">=4.0.0" - } - }, - "node_modules/sinon/node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/sinon/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "dev": true, - "dependencies": { - "whatwg-url": "^7.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/source-map-to-comment": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/source-map-to-comment/-/source-map-to-comment-2.0.0.tgz", - "integrity": "sha512-SJvZZ1t6mnLcxqV5w8JQoG8lFvp5ncMKMikYMxrUF87jaoqd+I1AaWMVX4Ot9kwsn68rfL2yu/0QcW60ZHeHgw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", - "integrity": "sha512-AOPG8EBc5wAikaG1/7uFCNFJwnKOuQwFTpYBdTW6OvWHeZBQBrAA/amefHGrEiOnCPcLFZK6FUPtWVKpQVIRgg==", - "dev": true, - "bin": { - "strip-json-comments": "cli.js" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/template-string": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/template-string/-/template-string-1.1.2.tgz", - "integrity": "sha512-FGMao6YSbzu+3jInp6VrMedIL65hxh3uGgjShV3X0NqnmXU9LX2mAjitUvWYFdEH4crnO8NQ+DsLhHE/rMZvXQ==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/transpiler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/transpiler/-/transpiler-1.2.0.tgz", - "integrity": "sha512-p3EJEoEUev56EXmb6/myG5OsuMRYaM8AGTau7IsOGbJ5gV6PeHf/NHkxyAdThzJQvH9obfalBiITJCfyN+spcw==", - "dev": true, - "dependencies": { - "microdash": "~1", - "template-string": "~1" - }, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, "dependencies": { "@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "requires": { "type-detect": "4.0.8" } }, "@sinonjs/fake-timers": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", - "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", "dev": true, "requires": { - "@sinonjs/commons": "^2.0.0" - }, - "dependencies": { - "@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - } + "@sinonjs/commons": "^3.0.0" } }, "@sinonjs/samsam": { @@ -2573,9 +116,9 @@ "dev": true }, "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true }, "brace-expansion": { @@ -2620,18 +163,29 @@ "dev": true }, "chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", "dev": true, "requires": { "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "type-detect": "^4.0.8" + }, + "dependencies": { + "check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "requires": { + "get-func-name": "^2.0.2" + } + } } }, "chai-as-promised": { @@ -2738,9 +292,9 @@ } }, "core-js-pure": { - "version": "3.30.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.30.1.tgz", - "integrity": "sha512-nXBEVpmUnNRhz83cHd9JRQC52cTMcuXAmR56+9dSMpRdpeA4I1PX6yjmhd71Eyc/wXNsdBdUDIj1QTIeZpU5Tg==" + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.0.tgz", + "integrity": "sha512-cN28qmhRNgbMZZMc/RFu5w8pK9VJzpb2rJVR/lHuZJKwmXnoWOpXmMkxqBB514igkp1Hu8WGROsiOAzUcKdHOQ==" }, "core-util-is": { "version": "1.0.3", @@ -2937,12 +491,13 @@ } }, "es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "requires": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, @@ -2999,9 +554,9 @@ } }, "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true }, "escape-string-regexp": { @@ -3010,11 +565,28 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, + "esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "dependencies": { + "type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + } + } + }, "event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "dev": true, "requires": { "d": "1", "es5-ext": "~0.10.14" @@ -3082,9 +654,9 @@ "dev": true }, "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "optional": true }, @@ -3119,9 +691,9 @@ "dev": true }, "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true }, "get-intrinsic": { @@ -3502,9 +1074,9 @@ } }, "just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", "dev": true }, "lie": { @@ -3553,12 +1125,12 @@ } }, "loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, "requires": { - "get-func-name": "^2.0.0" + "get-func-name": "^2.0.1" } }, "microdash": { @@ -3576,9 +1148,9 @@ } }, "mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz", + "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==", "dev": true, "requires": { "ansi-colors": "4.1.1", @@ -3588,13 +1160,12 @@ "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "8.1.0", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", @@ -3604,29 +1175,26 @@ "yargs-unparser": "2.0.0" }, "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "dependencies": { - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } + "minimatch": "^5.0.1", + "once": "^1.3.0" } }, "minimatch": { @@ -3636,17 +1204,6 @@ "dev": true, "requires": { "brace-expansion": "^2.0.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - } } }, "strip-json-comments": { @@ -3658,11 +1215,10 @@ } }, "mocha-bootstrap": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mocha-bootstrap/-/mocha-bootstrap-1.0.4.tgz", - "integrity": "sha512-d3snHM44rqR6TNnWyGvq1CBl5t9/0I6DcJ6ZjZjYlAtwI7itTBjuqAhIJKG+blsYrmHrADb5YM/mjryeIlCYww==", - "dev": true, - "requires": {} + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/mocha-bootstrap/-/mocha-bootstrap-1.0.6.tgz", + "integrity": "sha512-IcN6mZ6mN8A27/Nd6llFJJ6xcXZTq+1dFp40HBoCaMn+gsFCB246l6wLOJXUXRk3QzAk8m4PUnE/uyRTc7j4kQ==", + "dev": true }, "ms": { "version": "2.1.3", @@ -3670,39 +1226,22 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true - }, "next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "nise": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", - "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", "dev": true, "requires": { - "@sinonjs/commons": "^2.0.0", - "@sinonjs/fake-timers": "^10.0.2", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "path-to-regexp": "^1.7.0" - }, - "dependencies": { - "@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - } + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" } }, "normalize-path": { @@ -3794,13 +1333,10 @@ "dev": true }, "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "requires": { - "isarray": "0.0.1" - } + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", + "dev": true }, "pathval": { "version": "1.1.1", @@ -3838,9 +1374,9 @@ } }, "phptoast": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/phptoast/-/phptoast-9.1.1.tgz", - "integrity": "sha512-kHcH0dQjQLJ3EmmVJRVg0kBgMD274EZGX6A3s2Z8AbqrpQLCc4SmeA5rivSRcFIkNY3DQSWmc5y+IcojbbFi0w==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/phptoast/-/phptoast-9.2.0.tgz", + "integrity": "sha512-EgKrn+SgIIt40onD1UFa2fy8zevZvRWI/DTEoiq5PKxvfyMU3FYmfPmupQ41aQVR+7XlKCt11ny2aUKRyuRBKQ==", "dev": true, "requires": { "microdash": "^1.4.2", @@ -3849,9 +1385,9 @@ } }, "phptojs": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/phptojs/-/phptojs-10.0.2.tgz", - "integrity": "sha512-BzGci9CGHsWdbKHa//jdsuLIQ9YQygdUBn3TlD/F7sccZSF9fH9OzIZyLY+aWy9u7xGbHWsX0O1/HyNDZbhDOA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/phptojs/-/phptojs-10.1.0.tgz", + "integrity": "sha512-rmOFlu5JB9wriOuu3c5TzEp07YGPOxGnrpChvzOIk8Nd2+yCe2ia24gaHhlT2JpjKVKkVqz1984oa50jYvljvQ==", "dev": true, "requires": { "es6-set": "^0.1.6", @@ -3970,23 +1506,23 @@ } }, "sinon": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.4.tgz", - "integrity": "sha512-uzmfN6zx3GQaria1kwgWGeKiXSSbShBbue6Dcj0SI8fiCNFbiUDqKl57WFlY5lyhxZVUKmXvzgG2pilRQCBwWg==", + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", "dev": true, "requires": { "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/fake-timers": "^11.2.2", "@sinonjs/samsam": "^8.0.0", "diff": "^5.1.0", - "nise": "^5.1.4", + "nise": "^5.1.5", "supports-color": "^7.2.0" }, "dependencies": { "diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true }, "supports-color": { @@ -4004,8 +1540,7 @@ "version": "3.7.0", "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz", "integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==", - "dev": true, - "requires": {} + "dev": true }, "source-map": { "version": "0.8.0-beta.0", @@ -4022,12 +1557,6 @@ "integrity": "sha512-SJvZZ1t6mnLcxqV5w8JQoG8lFvp5ncMKMikYMxrUF87jaoqd+I1AaWMVX4Ot9kwsn68rfL2yu/0QcW60ZHeHgw==", "dev": true }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true - }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -4072,6 +1601,12 @@ "es-abstract": "^1.20.4" } }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", diff --git a/package.json b/package.json index 11a5a99f..8b2b4b13 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "test": "npm run jshint && npm run mocha" }, "dependencies": { - "core-js-pure": "^3.30.1", + "core-js-pure": "^3.36.0", "es6-weak-map": "^2.0.3", "is-promise": "^4.0.0", "lie": "^3.3.0", @@ -35,13 +35,13 @@ "chai": "^4.3.7", "chai-as-promised": "^7.1.1", "jshint": "^2.13.6", - "mocha": "^10.2.0", - "mocha-bootstrap": "^1.0.4", + "mocha": "^10.3.0", + "mocha-bootstrap": "^1.0.6", "nowdoc": "^1.0.1", "phptest": "^0.0.10", - "phptoast": "^9.1.1", - "phptojs": "^10.0.2", - "sinon": "^15.0.4", + "phptoast": "^9.2.0", + "phptojs": "^10.1.0", + "sinon": "^17.0.1", "sinon-chai": "^3.7.0" }, "engines": { diff --git a/src/Call.js b/src/Call.js index bae32253..1935706a 100644 --- a/src/Call.js +++ b/src/Call.js @@ -87,6 +87,13 @@ function Call( } _.extend(Call.prototype, { + /** + * Enables strict-types mode for the module this call was performed in. + */ + enableStrictTypes: function () { + this.originalNamespaceScope.enableStrictTypes(); + }, + /** * Enters an isolated call within this outer one, making the given NamespaceScope * and CallInstrumentation the current ones. @@ -315,6 +322,15 @@ _.extend(Call.prototype, { this.finder = finder; }, + /** + * Determines whether the module this call was performed in is in strict-types mode. + * + * @returns {boolean} + */ + isStrictTypesMode: function () { + return this.originalNamespaceScope.isStrictTypesMode(); + }, + /** * Determines whether this call is to a userland function (defined inside PHP-land) or not. * diff --git a/src/CallStack.js b/src/CallStack.js index bd863ce6..d7d2050a 100644 --- a/src/CallStack.js +++ b/src/CallStack.js @@ -48,6 +48,21 @@ _.extend(CallStack.prototype, { this.calls.length = 0; }, + /** + * Enables strict-types mode for the module of the current call. + */ + enableStrictTypes: function () { + // Note that when enabling, we enable strict-types mode for the callee's module and not the caller's, + // unlike when checking, where we check the caller instead. + var call = this.getUserlandCallee(); + + if (!call) { + throw new Exception('CallStack.enableStrictTypes() :: No userland callee'); + } + + call.enableStrictTypes(); + }, + /** * Fetches the previous Call near the top of the stack, or null if none * @@ -448,6 +463,23 @@ _.extend(CallStack.prototype, { this.getCurrent().instrument(finder); }, + /** + * Determines whether the module of the caller is in strict-types mode. + * + * @returns {boolean} + */ + isStrictTypesMode: function () { + // Note use of .getUserlandCallee() and not .getUserlandCaller() + // because at the point this is checked, the callee has not been pushed onto the stack. + var call = this.getUserlandCallee(); + + if (!call) { + throw new Exception('CallStack.isStrictTypesMode() :: No userland callee'); + } + + return call.isStrictTypesMode(); + }, + /** * Determines whether or not the current stack frame is a userland PHP function. * diff --git a/src/Class.js b/src/Class.js index 58b0ba18..4ccfcd0a 100644 --- a/src/Class.js +++ b/src/Class.js @@ -74,6 +74,7 @@ module.exports = require('pauser')([ * @param {Userland} userland * @param {string} name Fully-qualified class name (FQCN) * @param {string|null} constructorName + * @param {boolean} hasDestructor * @param {Function} InternalClass * @param {Object} rootInternalPrototype * @param {Object} instancePropertiesData @@ -87,6 +88,7 @@ module.exports = require('pauser')([ * @param {FFIFactory} ffiFactory * @param {Function|null} methodCaller Custom method call handler * @param {CallInstrumentation} instrumentation + * @param {DestructibleObjectRepository} destructibleObjectRepository * @constructor */ function Class( @@ -100,6 +102,7 @@ module.exports = require('pauser')([ userland, name, constructorName, + hasDestructor, InternalClass, rootInternalPrototype, instancePropertiesData, @@ -112,7 +115,8 @@ module.exports = require('pauser')([ valueCoercer, ffiFactory, methodCaller, - instrumentation + instrumentation, + destructibleObjectRepository ) { var classObject = this, staticProperties = {}; @@ -257,6 +261,17 @@ module.exports = require('pauser')([ data[VISIBILITY] ); }); + + /** + * @type {Function} + */ + this.internalInstantiator = hasDestructor ? + function (objectValue) { + destructibleObjectRepository.registerValue(objectValue); + } : + function () { + // No destructor defined, nothing special to do. + }; } _.extend(Class.prototype, { @@ -1029,6 +1044,10 @@ module.exports = require('pauser')([ _.forOwn(classObject.instancePropertyDefaults, function (propertyValue, name) { properties[name].initialise(propertyValue); }); + + // Use the optimised instantiator created inside this internal Class' constructor + // to handle destructors, if applicable. + classObject.internalInstantiator(objectValue); }, /** diff --git a/src/Class/ClassFactory.js b/src/Class/ClassFactory.js index dd7b2e2a..9c8a023d 100644 --- a/src/Class/ClassFactory.js +++ b/src/Class/ClassFactory.js @@ -23,6 +23,7 @@ var _ = require('microdash'), * @param {Userland} userland * @param {ExportRepository} exportRepository * @param {FFIFactory} ffiFactory + * @param {DestructibleObjectRepository} destructibleObjectRepository * @constructor */ function ClassFactory( @@ -35,12 +36,17 @@ function ClassFactory( futureFactory, userland, exportRepository, - ffiFactory + ffiFactory, + destructibleObjectRepository ) { /** * @type {CallStack} */ this.callStack = callStack; + /** + * @type {DestructibleObjectRepository} + */ + this.destructibleObjectRepository = destructibleObjectRepository; /** * @type {ExportRepository} */ @@ -87,6 +93,7 @@ _.extend(ClassFactory.prototype, { * @param {Namespace} namespace * @param {NamespaceScope} namespaceScope * @param {string} constructorName + * @param {boolean} hasDestructor * @param {Function} InternalClass * @param {Object} rootInternalPrototype * @param {Object} instanceProperties @@ -104,6 +111,7 @@ _.extend(ClassFactory.prototype, { namespace, namespaceScope, constructorName, + hasDestructor, InternalClass, rootInternalPrototype, instanceProperties, @@ -128,6 +136,7 @@ _.extend(ClassFactory.prototype, { factory.userland, name, constructorName, + hasDestructor, InternalClass, rootInternalPrototype, instanceProperties, @@ -140,7 +149,8 @@ _.extend(ClassFactory.prototype, { valueCoercer, factory.ffiFactory, methodCaller, - instrumentation + instrumentation, + factory.destructibleObjectRepository ); } }); diff --git a/src/Class/ClassPromoter.js b/src/Class/ClassPromoter.js index cc553052..101988c3 100644 --- a/src/Class/ClassPromoter.js +++ b/src/Class/ClassPromoter.js @@ -57,6 +57,7 @@ _.extend(ClassPromoter.prototype, { classDefinition.getNamespace(), namespaceScope, classDefinition.getConstructorName(), + classDefinition.hasDestructor(), InternalClass, classDefinition.getRootInternalPrototype(), classDefinition.getInstanceProperties(), diff --git a/src/Class/Definition/ClassDefinition.js b/src/Class/Definition/ClassDefinition.js index 1d753020..8cedbb9a 100644 --- a/src/Class/Definition/ClassDefinition.js +++ b/src/Class/Definition/ClassDefinition.js @@ -9,7 +9,9 @@ 'use strict'; -var _ = require('microdash'); +var _ = require('microdash'), + hasOwn = {}.hasOwnProperty, + MAGIC_DESTRUCT = '__destruct'; /** * @param {string} name @@ -262,6 +264,15 @@ _.extend(ClassDefinition.prototype, { */ getValueCoercer: function () { return this.valueCoercer; + }, + + /** + * Determines whether this class defines a destructor. + * + * @returns {boolean} + */ + hasDestructor: function () { + return hasOwn.call(this.methods, MAGIC_DESTRUCT); } }); diff --git a/src/Control/ControlScope.js b/src/Control/ControlScope.js index 0e269bd7..715cc249 100644 --- a/src/Control/ControlScope.js +++ b/src/Control/ControlScope.js @@ -39,12 +39,15 @@ _.extend(ControlScope.prototype, { /** * Enters a new coroutine, or continues the current one if we are nesting. * + * @param {{keepStack: boolean}} options * @returns {Coroutine} */ - enterCoroutine: function () { + enterCoroutine: function (options) { var scope = this, newCoroutine; + options = options || {}; + if (scope.nestNextCoroutine) { scope.nestNextCoroutine = false; @@ -58,7 +61,7 @@ _.extend(ControlScope.prototype, { newCoroutine = scope.coroutineFactory.createCoroutine(); if (scope.currentCoroutine !== null) { - scope.currentCoroutine.suspend(); + scope.currentCoroutine.suspend(Boolean(options.keepStack)); } scope.currentCoroutine = newCoroutine; diff --git a/src/Control/Coroutine.js b/src/Control/Coroutine.js index 9c68a57e..c2da3df6 100644 --- a/src/Control/Coroutine.js +++ b/src/Control/Coroutine.js @@ -58,8 +58,10 @@ _.extend(Coroutine.prototype, { /** * Suspends this coroutine, for later resumption. + * + * @param {boolean} keepStack */ - suspend: function () { + suspend: function (keepStack) { var coroutine = this; if (coroutine.suspended) { @@ -69,7 +71,9 @@ _.extend(Coroutine.prototype, { coroutine.savedCallStack = coroutine.callStack.save(); // Clear the call stack at this point, unlike .save(). - coroutine.callStack.clear(); + if (!keepStack) { + coroutine.callStack.clear(); + } coroutine.suspended = true; } diff --git a/src/Control/Flow.js b/src/Control/Flow.js index f2371d29..f766b70b 100644 --- a/src/Control/Flow.js +++ b/src/Control/Flow.js @@ -58,6 +58,12 @@ function Flow( * @type {string} */ this.mode = mode; + /** + * A pre-allocated Present that can be returned when a ChainableInterface is needed. + * + * @type {Present} + */ + this.noopPresent = futureFactory.createPresent(null); } _.extend(Flow.prototype, { @@ -329,6 +335,15 @@ _.extend(Flow.prototype, { }); }, + /** + * Returns a pre-allocated Present that can be returned when a ChainableInterface is needed. + * + * @returns {Present} + */ + getNoopPresent: function () { + return this.noopPresent; + }, + /** * Maps the given array of inputs to a Future via the given handler, settling any intermediate Futures. * @@ -443,7 +458,37 @@ _.extend(Flow.prototype, { } if (pauser) { - pauser(error); + pauser( + error, // TODO: Look into not passing error(pause) here now we have onResume(...)? + function onResume(reenter) { + error.next( + function (/* result */) { + /* + * Note that the result passed here for the opcode we are about to resume + * by re-calling the userland function has already been provided (see Pause), + * so the result argument passed to this callback may be ignored. + * + * If the pause resulted in an error, then we also want to re-call + * the function in order to resume with a throwInto at the correct opcode + * (see catch handler below). + */ + return reenter(); + }, + function (/* error */) { + /* + * Note that the error passed here for the opcode we are about to throwInto + * by re-calling the userland function has already been provided (see Pause), + * so the error argument passed to this callback may be ignored. + * + * Similar to the above, we want to re-call the function in order to resume + * with a throwInto at the correct opcode. + */ + + return reenter(); + } + ); + } + ); } // We have intercepted a pause - it must be marked as complete so that the future diff --git a/src/Control/Future.js b/src/Control/Future.js index e76b754a..d3a31f09 100644 --- a/src/Control/Future.js +++ b/src/Control/Future.js @@ -93,10 +93,13 @@ var _ = require('microdash'), }, nestCoroutine = function () { future.controlScope.nestCoroutine(); + }, + newCoroutine = function (options) { + future.controlScope.enterCoroutine(options); }; try { - executor(resolve, reject, nestCoroutine); + executor(resolve, reject, nestCoroutine, newCoroutine); } catch (error) { if (error instanceof Pause) { throw new Exception('Unexpected Pause raised by Future executor'); diff --git a/src/Control/HostScheduler.js b/src/Control/HostScheduler.js index 501e6dda..22d01111 100644 --- a/src/Control/HostScheduler.js +++ b/src/Control/HostScheduler.js @@ -12,7 +12,7 @@ var _ = require('microdash'), queueMacrotask = typeof requestIdleCallback !== 'undefined' ? function (callback) { - requestIdleCallback(callback); + requestIdleCallback(callback, {timeout: 0}); } : function (callback) { setTimeout(callback, 1); diff --git a/src/Control/Trace.js b/src/Control/Trace.js index b307c3c4..53babb6c 100644 --- a/src/Control/Trace.js +++ b/src/Control/Trace.js @@ -16,8 +16,24 @@ var _ = require('microdash'), RESUME_PLACEHOLDER_MARKER = '__isResumePlaceholder', createResumePlaceholder = function (trace) { function resumePlaceholder() { + var currentOpIndex = trace.currentOpIndex; + trace.advanceOpIndex(); + /* + * In this state, previous calculation op results will have been discarded, + * so there is no point in checking those, however control flow results + * and throws are kept for the entirety of the call trace. + */ + + if (hasOwn.call(trace.controlFlowResults, currentOpIndex)) { + return trace.controlFlowResults[currentOpIndex]; + } + + if (hasOwn.call(trace.opThrows, currentOpIndex)) { + throw trace.opThrows[currentOpIndex]; + } + return resumePlaceholder; } diff --git a/src/Control/Userland.js b/src/Control/Userland.js index 5f95022f..5e8d9845 100644 --- a/src/Control/Userland.js +++ b/src/Control/Userland.js @@ -99,32 +99,8 @@ _.extend(Userland.prototype, { function doCall() { return userland.flow.maybeFuturise( executor, - function (pause) { - pause.next( - function (/* result */) { - /* - * Note that the result passed here for the opcode we are about to resume - * by re-calling the userland function has already been provided (see Pause), - * so the result argument passed to this callback may be ignored. - * - * If the pause resulted in an error, then we also want to re-call - * the function in order to resume with a throwInto at the correct opcode - * (see catch handler below). - */ - return doCall(); - }, - function (/* error */) { - /* - * Note that the error passed here for the opcode we are about to throwInto - * by re-calling the userland function has already been provided (see Pause), - * so the error argument passed to this callback may be ignored. - * - * Similar to the above, we want to re-call the function in order to resume - * with a throwInto at the correct opcode. - */ - return doCall(); - } - ); + function (pause, onResume) { + onResume(doCall); } ); } @@ -154,32 +130,8 @@ _.extend(Userland.prototype, { function doCall() { return userland.flow.maybeFuturise( executor, - function (pause) { - pause.next( - function (/* result */) { - /* - * Note that the result passed here for the opcode we are about to resume - * by re-calling the userland function has already been provided (see Pause), - * so the result argument passed to this callback may be ignored. - * - * If the pause resulted in an error, then we also want to re-call - * the function in order to resume with a throwInto at the correct opcode - * (see catch handler below). - */ - return doCall(); - }, - function (/* error */) { - /* - * Note that the error passed here for the opcode we are about to throwInto - * by re-calling the userland function has already been provided (see Pause), - * so the error argument passed to this callback may be ignored. - * - * Similar to the above, we want to re-call the function in order to resume - * with a throwInto at the correct opcode. - */ - return doCall(); - } - ); + function (pause, onResume) { + onResume(doCall); } ); } diff --git a/src/Core/Internals/OpcodeHandlerTyper.js b/src/Core/Internals/OpcodeHandlerTyper.js index 08dcd175..49571f7f 100644 --- a/src/Core/Internals/OpcodeHandlerTyper.js +++ b/src/Core/Internals/OpcodeHandlerTyper.js @@ -35,14 +35,13 @@ _.extend(OpcodeHandlerTyper.prototype, { * * @param {string} signature * @param {Function} handler - * @param {string} opcodeFetcherType * @returns {Function} */ - typeHandler: function (signature, handler, opcodeFetcherType) { + typeHandler: function (signature, handler) { var typer = this, opcodeSignature = typer.signatureParser.parseSignature(signature); - return typer.typedOpcodeHandlerFactory.typeHandler(opcodeSignature, handler, opcodeFetcherType); + return typer.typedOpcodeHandlerFactory.typeHandler(opcodeSignature, handler); } }); diff --git a/src/Core/Internals/OpcodeInternalsClassFactory.js b/src/Core/Internals/OpcodeInternalsClassFactory.js index 9b9a488b..8eb45b42 100644 --- a/src/Core/Internals/OpcodeInternalsClassFactory.js +++ b/src/Core/Internals/OpcodeInternalsClassFactory.js @@ -214,7 +214,7 @@ _.extend(OpcodeInternalsClassFactory.prototype, { throw new Exception('Opcode fetcher has not been set'); } - return factory.opcodeHandlerTyper.typeHandler(signature, handler, internals.opcodeFetcher); + return factory.opcodeHandlerTyper.typeHandler(signature, handler); } }); diff --git a/src/Core/Opcode/Handler/TypedOpcodeHandlerFactory.js b/src/Core/Opcode/Handler/TypedOpcodeHandlerFactory.js index 3d0d2542..9fc77550 100644 --- a/src/Core/Opcode/Handler/TypedOpcodeHandlerFactory.js +++ b/src/Core/Opcode/Handler/TypedOpcodeHandlerFactory.js @@ -15,24 +15,17 @@ var _ = require('microdash'), Exception = phpCommon.Exception; /** - * Creates typed opcode handlers. + * Creates opcode handlers that support typing of their parameters. + * Arguments of typed parameters that coerce to Futures will automatically be awaited if needed. * * @param {ControlBridge} controlBridge - * @param {OpcodeHandlerFactory} opcodeHandlerFactory * @constructor */ -function TypedOpcodeHandlerFactory( - controlBridge, - opcodeHandlerFactory -) { +function TypedOpcodeHandlerFactory(controlBridge) { /** * @type {ControlBridge} */ this.controlBridge = controlBridge; - /** - * @type {OpcodeHandlerFactory} - */ - this.opcodeHandlerFactory = opcodeHandlerFactory; } _.extend(TypedOpcodeHandlerFactory.prototype, { @@ -41,265 +34,168 @@ _.extend(TypedOpcodeHandlerFactory.prototype, { * * @param {Signature} signature * @param {Function} handler - * @param {string} opcodeFetcherType * @returns {Function} */ - typeHandler: function (signature, handler, opcodeFetcherType) { + typeHandler: function (signature, handler) { var factory = this, - hasVariadicParameter = signature.hasVariadicParameter(), - initialParameterCount = signature.getInitialParameterCount(), parameterCount = signature.getParameterCount(), parameters = signature.getParameters(), - typedHandler; - - typedHandler = function () { - var coercedInitialArg, - coercedFormalArgs = [], - coercedVariadicArgs, - formalCaptureHandler, - initialArgs = slice.call(arguments), - initialArgCount = initialArgs.length, - initialArgIndex, - parameter, - variadicCaptureHandler; - - function coerceReturnValue(returnValue) { - return signature.coerceReturnValue(returnValue); - } - - function finishHandling() { - var returnValue, - coercedArgs = coercedFormalArgs; - - if (hasVariadicParameter) { - // Provide the variadic parameter's arguments as an array. - coercedArgs.push(coercedVariadicArgs); - } - - returnValue = handler.apply(null, coercedArgs); + typedHandler, + variadicParameter = signature.getVariadicParameter(); + + function coerceReturnValue(returnValue) { + return signature.coerceReturnValue(returnValue); + } + + typedHandler = variadicParameter ? + function withVariadicParameter() { + var argIndex = 0, + args = slice.call(arguments), + argCount = args.length, + coercedArgs = [], + variadicArg = []; + + function getParameter(argIndex) { + if (argIndex < parameterCount - 1) { + return parameters[argIndex]; + } - if (factory.controlBridge.isFuture(returnValue)) { - return returnValue - .next(coerceReturnValue); + return variadicParameter; } - return coerceReturnValue(returnValue); - } - - if (hasVariadicParameter) { - variadicCaptureHandler = factory.opcodeHandlerFactory.createTracedHandler( - function (partialArg) { - var coercedPartialArg, - partialArgs = arguments.length; - - if (partialArgs === 0) { - // Opcode has a variadic parameter, and we've received all its arguments. - return finishHandling(); - } - - if (partialArgs > 1) { - throw new Exception('Only one partial argument may be provided at a time'); - } - - coercedPartialArg = parameter.coerceArgument(partialArg); - - function finishArg(presentCoercedPartialArg) { - coercedVariadicArgs.push(presentCoercedPartialArg); + function finishHandling() { + var returnValue; - // Return the handler for capturing the next argument (or the end of the list). - return variadicCaptureHandler; - } + coercedArgs.push(variadicArg); - if (factory.controlBridge.isFuture(coercedPartialArg)) { - // Coerced partial arg is a future, so we need to await it before continuing. - return coercedPartialArg - .next(finishArg); - } + returnValue = handler.apply(null, coercedArgs); - // Coerced partial arg is not a future, capture it and return - // the handler for capturing the next argument (or the end of the list). - return finishArg(coercedPartialArg); - }, - opcodeFetcherType - ); - - coercedVariadicArgs = []; - } - - /** - * Handles parameters marked as initial that are unspecified in the initial arguments list. - * - * Uses default arguments if defined, otherwise raises an error due to missing required argument. - */ - function populateDefaultInitialArgs() { - var initialArgIndex; - - for ( - initialArgIndex = coercedFormalArgs.length; - initialArgIndex < initialParameterCount; - initialArgIndex++ - ) { - parameter = parameters[initialArgIndex]; - - if (parameter.isRequired()) { - throw new Exception( - 'Missing argument for required initial parameter "' + parameter.getName() + '"' - ); + if (factory.controlBridge.isFuture(returnValue)) { + return returnValue + .next(coerceReturnValue); } - coercedFormalArgs.push(parameter.getDefaultArgument()); + return coerceReturnValue(returnValue); } - } - - function finishInitialArgs() { - var nextInitialArgIndex; - populateDefaultInitialArgs(); + function coerceArgs() { + var coercedArg, + parameter, + parameterIndex; - nextInitialArgIndex = coercedFormalArgs.length; - - if (nextInitialArgIndex === parameterCount) { - // Fastest case: all parameters have received their arguments - // and there is no variadic final parameter. - return finishHandling(); - } + function pushCoercedArg(coercedArg) { + if (parameter === variadicParameter) { + variadicArg.push(coercedArg); + } else { + coercedArgs.push(coercedArg); + } + } - parameter = parameters[nextInitialArgIndex]; + function handlePresentArg(presentCoercedArg) { + pushCoercedArg(presentCoercedArg); - if (nextInitialArgIndex === parameterCount - 1) { - // Only exactly one parameter remains: check whether it is variadic. - if (parameter.isVariadic()) { - return variadicCaptureHandler; + return coerceArgs(); } - } - - // We have some remaining parameters that have not yet received their arguments: - // return a handler to capture them one at a time. - formalCaptureHandler = factory.opcodeHandlerFactory.createTracedHandler( - function (partialArg) { - var argCount = arguments.length, - coercedPartialArg; - if (argCount > 1) { - throw new Exception('Only one partial argument may be provided at a time'); - } + while (argIndex < argCount) { + parameter = getParameter(argIndex); + coercedArg = parameter.coerceArgument(args[argIndex]); - if (argCount === 0) { - // No argument provided - use default assuming parameter is optional. - if (parameter.isRequired()) { - throw new Exception( - 'Missing argument for required parameter "' + parameter.getName() + '"' - ); - } + argIndex++; - coercedPartialArg = parameter.getDefaultArgument(); - } else { - coercedPartialArg = parameter.coerceArgument(partialArg); + if (factory.controlBridge.isFuture(coercedArg)) { + // Coerced arg is a Future, so we need to await it before continuing. + return coercedArg.next(handlePresentArg); } - function finishArg(presentCoercedPartialArg) { - var argCount; - - coercedFormalArgs.push(presentCoercedPartialArg); - - argCount = coercedFormalArgs.length; - - if (argCount === parameterCount) { - // All parameters have received their arguments. - return finishHandling(); - } + pushCoercedArg(coercedArg); + } - // Return the handler for capturing the next argument based on the next parameter. - parameter = parameters[argCount]; + for (parameterIndex = argCount; parameterIndex < parameterCount; parameterIndex++) { + parameter = parameters[parameterIndex]; - return parameter.isVariadic() ? - variadicCaptureHandler : - formalCaptureHandler; + if (parameter !== variadicParameter && parameter.isRequired()) { + throw new Exception( + 'Missing argument for required parameter "' + parameter.getName() + '"' + ); } + } - if (factory.controlBridge.isFuture(coercedPartialArg)) { - // Coerced partial arg is a future, so we need to await it before continuing. - return coercedPartialArg - .next(finishArg); - } + return finishHandling(); + } - // Coerced partial arg is not a future, capture it and return - // the handler for capturing the next argument. - return finishArg(coercedPartialArg); - }, - opcodeFetcherType - ); + return coerceArgs(); + } : + function withoutVariadicParameter() { + var argIndex = 0, + args = slice.call(arguments), + argCount = args.length, + coercedArgs = []; + + function getParameter(argIndex) { + if (argIndex < parameterCount) { + return parameters[argIndex]; + } - return formalCaptureHandler; - } + throw new Exception( + 'Too many opcode arguments provided - expected ' + + parameterCount + ', got ' + argCount + ); + } - function finishFinalFutureArg(presentCoercedInitialArg) { - coercedFormalArgs.push(presentCoercedInitialArg); + function finishHandling() { + var returnValue = handler.apply(null, coercedArgs); - return finishInitialArgs(); - } + if (factory.controlBridge.isFuture(returnValue)) { + return returnValue + .next(coerceReturnValue); + } - if (parameterCount === 1 && initialArgCount <= 1 && parameters[0].isVariadic()) { - // Special case: first parameter is variadic, so an initial arg can contribute to it. - parameter = parameters[0]; + return coerceReturnValue(returnValue); + } - return initialArgCount > 0 ? variadicCaptureHandler(initialArgs[0]) : variadicCaptureHandler(); - } + function coerceArgs() { + var coercedArg, + parameter, + parameterIndex; - if (initialArgCount === 0) { - // Special case: no initial args passed, - // any parameters should have their default args used. - for (initialArgIndex = 0; initialArgIndex < parameterCount; initialArgIndex++) { - parameter = parameters[initialArgIndex]; + function handlePresentArg(presentCoercedArg) { + coercedArgs.push(presentCoercedArg); - if (parameter.isRequired()) { - throw new Exception( - 'Missing argument for required parameter "' + parameter.getName() + '"' - ); + return coerceArgs(); } - coercedFormalArgs.push(parameter.getDefaultArgument()); - } + while (argIndex < argCount) { + parameter = getParameter(argIndex); + coercedArg = parameter.coerceArgument(args[argIndex]); - return finishInitialArgs(); - } + argIndex++; - if (initialArgCount > parameterCount) { - throw new Exception( - 'Too many opcode arguments provided - expected ' + - parameterCount + ', got ' + initialArgCount - ); - } + if (factory.controlBridge.isFuture(coercedArg)) { + // Coerced arg is a Future, so we need to await it before continuing. + return coercedArg.next(handlePresentArg); + } - for (initialArgIndex = 0; initialArgIndex < initialArgCount; initialArgIndex++) { - parameter = parameters[initialArgIndex]; + coercedArgs.push(coercedArg); + } - if (parameter.isVariadic()) { - throw new Exception('Variadic opcode arguments should be provided separately'); - } + for (parameterIndex = argCount; parameterIndex < parameterCount; parameterIndex++) { + parameter = parameters[parameterIndex]; - coercedInitialArg = parameter.coerceArgument(initialArgs[initialArgIndex]); + if (parameter.isRequired()) { + throw new Exception( + 'Missing argument for required parameter "' + parameter.getName() + '"' + ); + } - if (factory.controlBridge.isFuture(coercedInitialArg)) { - if (initialArgIndex === initialArgCount - 1) { - // Final coerced initial arg is a future, so we need to await it before continuing. - return coercedInitialArg - .next(finishFinalFutureArg); + coercedArgs[parameterIndex] = parameter.getDefaultArgument(); } - throw new Exception( - 'Argument #' + initialArgIndex + ' for opcode parameter "' + - parameter.getName() + - '" is not the final one but has coerced to a Future - ' + - 'this should be handled by chained function calls' - ); + return finishHandling(); } - coercedFormalArgs.push(coercedInitialArg); - } - - return finishInitialArgs(); - }; + return coerceArgs(); + }; typedHandler.typedOpcodeHandler = handler; diff --git a/src/Core/Opcode/Parameter/Parameter.js b/src/Core/Opcode/Parameter/Parameter.js index 9c745696..7248f97e 100644 --- a/src/Core/Opcode/Parameter/Parameter.js +++ b/src/Core/Opcode/Parameter/Parameter.js @@ -16,7 +16,6 @@ var _ = require('microdash'); * * @param {string} name * @param {TypeInterface} type - * @param {boolean} isInitial * @param {boolean} isRequired * @param {boolean} isVariadic * @param {*} defaultArgument @@ -25,7 +24,6 @@ var _ = require('microdash'); function Parameter( name, type, - isInitial, isRequired, isVariadic, defaultArgument @@ -34,10 +32,6 @@ function Parameter( * @type {*} */ this.defaultArgument = defaultArgument; - /** - * @type {boolean} - */ - this.initial = isInitial; /** * @type {string} */ @@ -94,15 +88,6 @@ _.extend(Parameter.prototype, { return this.type; }, - /** - * Determines whether this parameter must have its argument specified initially. - * - * @returns {boolean} - */ - isInitial: function () { - return this.initial; - }, - /** * Determines whether this parameter is required. * diff --git a/src/Core/Opcode/Parameter/ParameterFactory.js b/src/Core/Opcode/Parameter/ParameterFactory.js index 70f1a09f..80948339 100644 --- a/src/Core/Opcode/Parameter/ParameterFactory.js +++ b/src/Core/Opcode/Parameter/ParameterFactory.js @@ -30,7 +30,6 @@ _.extend(ParameterFactory.prototype, { * * @param {string} name * @param {TypeInterface} type - * @param {boolean} isInitial * @param {boolean} isRequired * @param {boolean} isVariadic * @param {*} defaultArgument @@ -39,12 +38,11 @@ _.extend(ParameterFactory.prototype, { createParameter: function ( name, type, - isInitial, isRequired, isVariadic, defaultArgument ) { - return new this.Parameter(name, type, isInitial, isRequired, isVariadic, defaultArgument); + return new this.Parameter(name, type, isRequired, isVariadic, defaultArgument); } }); diff --git a/src/Core/Opcode/Signature/Signature.js b/src/Core/Opcode/Signature/Signature.js index 6a173cc6..164507be 100644 --- a/src/Core/Opcode/Signature/Signature.js +++ b/src/Core/Opcode/Signature/Signature.js @@ -40,24 +40,6 @@ _.extend(Signature.prototype, { return this.returnType.coerceValue(returnValue); }, - /** - * Fetches the number of parameters whose arguments (if specified) must be specified initially. - * - * @returns {number} - */ - getInitialParameterCount: function () { - var parameterIndex, - signature = this; - - for (parameterIndex = signature.parameters.length - 1; parameterIndex >= 0; parameterIndex--) { - if (signature.parameters[parameterIndex].isInitial()) { - return parameterIndex + 1; - } - } - - return 0; - }, - /** * Fetches the number of parameters in this opcode signature. * @@ -85,16 +67,33 @@ _.extend(Signature.prototype, { return this.returnType; }, + /** + * Fetches the final variadic parameter if this signature has one. + * + * @returns {Parameter|null} + */ + getVariadicParameter: function () { + var signature = this, + parameter; + + if (signature.parameters.length === 0) { + return null; + } + + parameter = signature.parameters[signature.parameters.length - 1]; + + return parameter.isVariadic() ? + parameter : + null; + }, + /** * Determines whether this signature has a final variadic parameter. * * @returns {boolean} */ hasVariadicParameter: function () { - var signature = this; - - return signature.parameters.length > 0 && - signature.parameters[signature.parameters.length - 1].isVariadic(); + return this.getVariadicParameter() !== null; } }); diff --git a/src/Core/Opcode/Signature/SignatureParser.js b/src/Core/Opcode/Signature/SignatureParser.js index f35653b0..40bc4497 100644 --- a/src/Core/Opcode/Signature/SignatureParser.js +++ b/src/Core/Opcode/Signature/SignatureParser.js @@ -55,18 +55,16 @@ _.extend(SignatureParser.prototype, { * @returns {Parameter} */ function buildParameter(match) { - var isRequired = typeof match[5] === 'undefined', - defaultArgument = isRequired ? null : JSON.parse(match[5]), - name = match[4], - isInitial = match[1] === 'initial', - isVariadic = Boolean(match[3]), - typeName = match[2], + var isRequired = typeof match[4] === 'undefined', + defaultArgument = isRequired ? null : JSON.parse(match[4]), + name = match[3], + isVariadic = Boolean(match[2]), + typeName = match[1], type = parser.typeProvider.provideType(typeName); return parser.parameterFactory.createParameter( name, type, - isInitial, isRequired, isVariadic, defaultArgument @@ -75,7 +73,7 @@ _.extend(SignatureParser.prototype, { while (remainingSignature.length > 0 && !/^\s*:/.test(remainingSignature)) { match = remainingSignature.match( - /^\s*(initial)?\s*(\w+(?:\|\w+)*)\s+(\.{3})?(\w+)(?:\s*=\s*([^,\s]+))?\s*(?:,\s*)?/i + /^\s*(\w+(?:\|\w+)*)\s+(\.{3})?(\w+)(?:\s*=\s*([^,\s]+))?\s*(?:,\s*)?/i ); if (!match) { diff --git a/src/Core/Opcode/Type/ElementType.js b/src/Core/Opcode/Type/ElementType.js new file mode 100644 index 00000000..74a8d72c --- /dev/null +++ b/src/Core/Opcode/Type/ElementType.js @@ -0,0 +1,80 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var _ = require('microdash'), + phpCommon = require('phpcommon'), + util = require('util'), + Exception = phpCommon.Exception, + KeyReferencePair = require('../../../KeyReferencePair'), + KeyValuePair = require('../../../KeyValuePair'), + Reference = require('../../../Reference/Reference'), + ReferenceElement = require('../../../Element/ReferenceElement'), + TypeInterface = require('./TypeInterface'), + Variable = require('../../../Variable').sync(); + +/** + * Represents a type of KeyReferencePair, KeyValuePair, Reference, ReferenceElement, Value or Variable + * that may be used as the elements of an array literal. + * + * @param {ValueFactory} valueFactory + * @constructor + * @implements {TypeInterface} + */ +function ElementType(valueFactory) { + /** + * @type {ValueFactory} + */ + this.valueFactory = valueFactory; +} + +util.inherits(ElementType, TypeInterface); + +_.extend(ElementType.prototype, { + /** + * {@inheritdoc} + */ + allowsValue: function (value) { + var type = this; + + return ( + type.valueFactory.isValue(value) || + value instanceof ReferenceElement || + value instanceof KeyReferencePair || + value instanceof KeyValuePair || + value instanceof Reference || // Including ReferenceSnapshot. + value instanceof Variable + ); + }, + + /** + * {@inheritdoc} + */ + coerceValue: function (value) { + var type = this; + + if (type.allowsValue(value)) { + // Fastest case: value is allowed. + + return value; + } + + throw new Exception('Unexpected value provided for ElementType'); + }, + + /** + * {@inheritdoc} + */ + getDisplayName: function () { + return 'element'; + } +}); + +module.exports = ElementType; diff --git a/src/Core/Opcode/Type/SlotType.js b/src/Core/Opcode/Type/SlotType.js new file mode 100644 index 00000000..e0185e1e --- /dev/null +++ b/src/Core/Opcode/Type/SlotType.js @@ -0,0 +1,73 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var _ = require('microdash'), + phpCommon = require('phpcommon'), + util = require('util'), + Exception = phpCommon.Exception, + Reference = require('../../../Reference/Reference'), + TypeInterface = require('./TypeInterface'), + Variable = require('../../../Variable').sync(); + +/** + * Represents a storage slot, which can be a Reference (including ReferenceSnapshot), Variable or Value. + * + * @param {ValueFactory} valueFactory + * @constructor + * @implements {TypeInterface} + */ +function SlotType(valueFactory) { + /** + * @type {ValueFactory} + */ + this.valueFactory = valueFactory; +} + +util.inherits(SlotType, TypeInterface); + +_.extend(SlotType.prototype, { + /** + * {@inheritdoc} + */ + allowsValue: function (value) { + var type = this; + + return ( + type.valueFactory.isValue(value) || + (value instanceof Reference) || // Including ReferenceSnapshot. + (value instanceof Variable) + ); + }, + + /** + * {@inheritdoc} + */ + coerceValue: function (value) { + var type = this; + + if (type.allowsValue(value)) { + // Fastest case: value is allowed. + + return value; + } + + throw new Exception('Unexpected value provided for SlotType'); + }, + + /** + * {@inheritdoc} + */ + getDisplayName: function () { + return 'slot'; + } +}); + +module.exports = SlotType; diff --git a/src/Core/Opcode/Type/SnapshotType.js b/src/Core/Opcode/Type/SnapshotType.js index 4cd930b9..2e3c5607 100644 --- a/src/Core/Opcode/Type/SnapshotType.js +++ b/src/Core/Opcode/Type/SnapshotType.js @@ -13,17 +13,14 @@ var _ = require('microdash'), phpCommon = require('phpcommon'), util = require('util'), Exception = phpCommon.Exception, - KeyReferencePair = require('../../../KeyReferencePair'), - KeyValuePair = require('../../../KeyValuePair'), Reference = require('../../../Reference/Reference'), - ReferenceElement = require('../../../Element/ReferenceElement'), ReferenceSnapshot = require('../../../Reference/ReferenceSnapshot'), TypeInterface = require('./TypeInterface'), Variable = require('../../../Variable').sync(); /** - * Represents a type where the given Reference, Variable or Value will be coerced to a Value - * and wrapped in a ReferenceSnapshot if required. + * Represents a type where the given value must be a ReferenceSnapshot + * or a reference that can be snapshotted. * * @param {ValueFactory} valueFactory * @param {ReferenceFactory} referenceFactory @@ -51,30 +48,8 @@ _.extend(SnapshotType.prototype, { * {@inheritdoc} */ allowsValue: function (value) { - var type = this; - - if (type.valueFactory.isValue(value)) { - // Fastest case: a Value was given, there is no reference or variable to fetch it from. - - return true; // No need to wrap a plain Value in a ReferenceSnapshot. - } - - if (value instanceof ReferenceSnapshot) { - // Value is already a snapshot: nothing to do. - return true; - } - - if ( - value instanceof ReferenceElement || - value instanceof KeyReferencePair || - value instanceof KeyValuePair - ) { - // Value is an array literal element. - return true; - } - - // Otherwise value must be a reference or variable to be snapshotted. - return (value instanceof Reference) || (value instanceof Variable); + return value instanceof Reference || // Including ReferenceSnapshot. + value instanceof Variable; }, /** @@ -84,24 +59,14 @@ _.extend(SnapshotType.prototype, { var resolvedValue, type = this; - if (type.valueFactory.isValue(value)) { - // Fastest case: a Value was given, there is no reference or variable to fetch it from. - - return value; // No need to wrap a plain Value in a ReferenceSnapshot. - } - if (value instanceof ReferenceSnapshot) { - // Value is already a snapshot: nothing to do. + // Fastest case: value is already a snapshot. + return value; } - if ( - value instanceof ReferenceElement || - value instanceof KeyReferencePair || - value instanceof KeyValuePair - ) { - // Value is an array literal element. - return value; + if (type.valueFactory.isValue(value)) { + throw new Exception('SnapshotType cannot accept Values'); } if ((value instanceof Reference) || (value instanceof Variable)) { @@ -112,6 +77,7 @@ _.extend(SnapshotType.prototype, { return type.referenceFactory.createSnapshot(value); } + // Resolve the value of the reference so that it is accessible synchronously within the opcode handler. return resolvedValue .next(function (presentValue) { /* diff --git a/src/Core/Opcode/Type/TypeFactory.js b/src/Core/Opcode/Type/TypeFactory.js index 0adb07ce..6d69eb5d 100644 --- a/src/Core/Opcode/Type/TypeFactory.js +++ b/src/Core/Opcode/Type/TypeFactory.js @@ -11,12 +11,15 @@ var _ = require('microdash'), AnyType = require('./AnyType'), + ElementType = require('./ElementType'), ListType = require('./ListType'), NativeType = require('./NativeType'), ReferenceType = require('./ReferenceType'), + SlotType = require('./SlotType'), SnapshotType = require('./SnapshotType'), UnionType = require('./UnionType'), - ValueType = require('./ValueType'); + ValueType = require('./ValueType'), + VoidType = require('./VoidType'); /** * Creates type objects for opcodes. @@ -49,6 +52,17 @@ _.extend(TypeFactory.prototype, { return new AnyType(); }, + /** + * Creates a new ElementType. + * + * @returns {ElementType} + */ + createElementType: function () { + var factory = this; + + return new ElementType(factory.valueFactory); + }, + /** * Creates a new ListType. * @@ -77,6 +91,17 @@ _.extend(TypeFactory.prototype, { return new ReferenceType(); }, + /** + * Creates a new SlotType. + * + * @returns {SlotType} + */ + createSlotType: function () { + var factory = this; + + return new SlotType(factory.valueFactory); + }, + /** * Creates a new SnapshotType. * @@ -105,6 +130,15 @@ _.extend(TypeFactory.prototype, { */ createValueType: function () { return new ValueType(this.valueFactory); + }, + + /** + * Creates a new VoidType. + * + * @returns {VoidType} + */ + createVoidType: function () { + return new VoidType(); } }); diff --git a/src/Core/Opcode/Type/TypeProvider.js b/src/Core/Opcode/Type/TypeProvider.js index 7becb185..a61e7970 100644 --- a/src/Core/Opcode/Type/TypeProvider.js +++ b/src/Core/Opcode/Type/TypeProvider.js @@ -16,10 +16,14 @@ var _ = require('microdash'), switch (typeName) { case 'any': return provider.typeFactory.createAnyType(); + case 'element': + return provider.typeFactory.createElementType(); case 'list': return provider.typeFactory.createListType(); case 'ref': return provider.typeFactory.createReferenceType(); + case 'slot': + return provider.typeFactory.createSlotType(); case 'snapshot': return provider.typeFactory.createSnapshotType(); case 'bool': @@ -27,9 +31,13 @@ var _ = require('microdash'), case 'null': case 'number': case 'string': + case 'undefined': return provider.typeFactory.createNativeType(typeName); case 'val': return provider.typeFactory.createValueType(); + // Provide "void" as a shorthand for "undefined" to shrink compiled bundle size. + case 'void': + return provider.typeFactory.createVoidType(); default: throw new Exception('Unsupported type "' + typeName + '"'); } diff --git a/src/Core/Opcode/Type/VoidType.js b/src/Core/Opcode/Type/VoidType.js new file mode 100644 index 00000000..532b8fb5 --- /dev/null +++ b/src/Core/Opcode/Type/VoidType.js @@ -0,0 +1,64 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var _ = require('microdash'), + phpCommon = require('phpcommon'), + util = require('util'), + Exception = phpCommon.Exception, + TypeInterface = require('./TypeInterface'); + +/** + * Represents a type where no value is to be returned. + * + * @constructor + * @implements {TypeInterface} + */ +function VoidType() { + +} + +util.inherits(VoidType, TypeInterface); + +_.extend(VoidType.prototype, { + /** + * {@inheritdoc} + */ + allowsValue: function (value) { + return typeof value === 'undefined'; + }, + + /** + * {@inheritdoc} + */ + coerceValue: function (value) { + var type = this, + givenType = typeof value; + + if (type.allowsValue(value)) { + return value; + } + + throw new Exception( + 'Unexpected value of type "' + + givenType + + '" provided for VoidType' + ); + }, + + /** + * {@inheritdoc} + */ + getDisplayName: function () { + return 'void'; + } +}); + +module.exports = VoidType; diff --git a/src/Environment.js b/src/Environment.js index 5bfec8d4..2afaa0fc 100644 --- a/src/Environment.js +++ b/src/Environment.js @@ -198,6 +198,15 @@ _.extend(Environment.prototype, { return this.state.getGlobal(name); }, + /** + * Fetches the configured synchronicity mode. + * + * @returns {'async'|'psync'|'sync'} + */ + getMode: function () { + return this.state.getMode(); + }, + getOptions: function () { return this.state.getOptions(); }, diff --git a/src/FFI/Call.js b/src/FFI/Call.js index eab4ca8c..acd86032 100644 --- a/src/FFI/Call.js +++ b/src/FFI/Call.js @@ -9,7 +9,9 @@ 'use strict'; -var _ = require('microdash'); +var _ = require('microdash'), + phpCommon = require('phpcommon'), + Exception = phpCommon.Exception; /** * @param {Value[]} args @@ -23,6 +25,13 @@ function Call(args) { } _.extend(Call.prototype, { + /** + * FFI calls are always in weak type-checking mode. + */ + enableStrictTypes: function () { + throw new Exception('FFI calls cannot be switched into strict-types mode'); + }, + /** * Fetches the current class for the call, if any * @@ -121,6 +130,15 @@ _.extend(Call.prototype, { throw new Error('Unable to instrument an FFI Call'); }, + /** + * FFI calls are always in weak type-checking mode. + * + * @returns {boolean} + */ + isStrictTypesMode: function () { + return false; + }, + /** * Determines whether this call is a userland call (from inside PHP-land) or not * diff --git a/src/FFI/Internals/Internals.js b/src/FFI/Internals/Internals.js index 068748b3..4b34eac9 100644 --- a/src/FFI/Internals/Internals.js +++ b/src/FFI/Internals/Internals.js @@ -32,6 +32,7 @@ var _ = require('microdash'), * @param {ErrorConfiguration} errorConfiguration * @param {ErrorPromoter} errorPromoter * @param {ErrorReporting} errorReporting + * @param {GarbageCollector} garbageCollector * @param {Namespace} globalNamespace * @param {Scope} globalScope * @param {INIState} iniState @@ -65,6 +66,7 @@ function Internals( errorConfiguration, errorPromoter, errorReporting, + garbageCollector, globalNamespace, globalScope, iniState, @@ -137,6 +139,11 @@ function Internals( * @type {FutureFactory} */ this.futureFactory = futureFactory; + /** + * @public + * @type {GarbageCollector} + */ + this.garbageCollector = garbageCollector; /** * @public * @type {Namespace} diff --git a/src/FFI/Value/ValueCoercer.js b/src/FFI/Value/ValueCoercer.js index f00633d2..5587df1a 100644 --- a/src/FFI/Value/ValueCoercer.js +++ b/src/FFI/Value/ValueCoercer.js @@ -42,7 +42,9 @@ _.extend(ValueCoercer.prototype, { } return coercer.flow.mapAsync(argumentReferences, function (argumentReference) { - return argumentReference.getValue() + // Note that if we called .getValue() at this point, any warnings/notices + // raised by FunctionSpec.coerceArguments() would be duplicated. + return argumentReference.getValueOrNull() .next(function (argumentValue) { return argumentValue.getNative(); }); diff --git a/src/Function/FunctionSpec.js b/src/Function/FunctionSpec.js index c4271625..2b671f57 100644 --- a/src/Function/FunctionSpec.js +++ b/src/Function/FunctionSpec.js @@ -105,7 +105,7 @@ function FunctionSpec( _.extend(FunctionSpec.prototype, { /** - * Coerces the given set of arguments for this function as needed + * Coerces the given set of arguments for this function as needed. * * @param {Reference[]|Value[]|Variable[]} argumentReferenceList * @returns {FutureInterface} Returns all arguments resolved to values @@ -114,25 +114,25 @@ _.extend(FunctionSpec.prototype, { var coercedArguments = argumentReferenceList.slice(), spec = this; - return spec.flow.eachAsync(spec.parameterList, function (parameter, index) { + return spec.flow.eachAsync(coercedArguments, function (argumentReference, index) { + var parameter = spec.parameterList[index]; + if (!parameter) { - // Parameter is omitted due to bundle-size optimisations or similar, ignore - return; - } + // Parameter is omitted due to bundle-size optimisations or similar, no coercion + // to perform, but do ensure we have a value. + coercedArguments[index] = argumentReference.getValue(); - if (argumentReferenceList.length <= index) { - // Argument is not provided: do not attempt to fetch it return; } /* - * Coerce the argument as the parameter requires (eg. for scalar types in PHP 7+ weak type-checking mode). + * Coerce the argument as the parameter requires (e.g. for scalar types in PHP 7+ weak type-checking mode). * * Note that it will be resolved to a value at this point if not already. * For by-reference parameters in weak-type checking mode, the coerced value will be written back * to the reference, ie. .setValue(). */ - return parameter.coerceArgument(argumentReferenceList[index]) + return parameter.coerceArgument(argumentReference) .next(function (coercedArgument) { coercedArguments[index] = coercedArgument; @@ -168,7 +168,11 @@ _.extend(FunctionSpec.prototype, { return value; } - // TODO: Don't perform this coercion in strict types mode when that is supported. + if (spec.callStack.isStrictTypesMode()) { + // No value coercion to perform in strict-types mode. + return value; + } + value = value.next(function (presentValue) { /* * Coerce the result to match the return type: for example, when the return type @@ -555,8 +559,9 @@ _.extend(FunctionSpec.prototype, { }, 'TypeError', false, - spec.filePath, - spec.lineNumber + // Use the current file & line context and not that of this function spec when userland. + spec.isUserland() ? undefined : spec.filePath, + spec.isUserland() ? undefined : spec.lineNumber ); }); } diff --git a/src/Function/Parameter.js b/src/Function/Parameter.js index 8883322e..e3425ef9 100644 --- a/src/Function/Parameter.js +++ b/src/Function/Parameter.js @@ -140,7 +140,11 @@ _.extend(Parameter.prototype, { // Otherwise use .getValue() to ensure a notice is raised on undefined variable or reference. argumentReference.getValue(); - // TODO: Don't perform this coercion in strict types mode when that is supported. + if (parameter.callStack.isStrictTypesMode()) { + // No value coercion to perform in strict-types mode. + return value; + } + value = value.next(function (presentValue) { /* * Coerce the argument to match the parameter's type: for example, when the parameter diff --git a/src/FunctionFactory.js b/src/FunctionFactory.js index 782e526c..e30e6bab 100644 --- a/src/FunctionFactory.js +++ b/src/FunctionFactory.js @@ -168,38 +168,14 @@ module.exports = require('pauser')([ // Native functions expect arguments to be provided natively as normal. return func.apply(scope, argReferences); }, - function (pause) { + function (pause, onResume) { if (!functionSpec.isUserland()) { throw new Exception( 'FunctionFactory :: A built-in function enacted a Pause, did you mean to return a Future instead?' ); } - pause.next( - function (/* result */) { - /* - * Note that the result passed here for the opcode we are about to resume - * by re-calling the userland function has already been provided (see Pause), - * so the result argument passed to this callback may be ignored. - * - * If the pause resulted in an error, then we also want to re-call - * the function in order to resume with a throwInto at the correct opcode - * (see catch handler below). - */ - return doCall(); - }, - function (/* error */) { - /* - * Note that the error passed here for the opcode we are about to throwInto - * by re-calling the userland function has already been provided (see Pause), - * so the error argument passed to this callback may be ignored. - * - * Similar to the above, we want to re-call the function in order to resume - * with a throwInto at the correct opcode. - */ - return doCall(); - } - ); + onResume(doCall); } ); } @@ -265,8 +241,9 @@ module.exports = require('pauser')([ functionSpec.loadArguments(argReferences, scope); } - return doCall().next(finishCall); + return doCall(); }) + .next(finishCall) .finally(function () { // Once the call completes, whether with a result or a thrown error/exception, // pop the call off of the stack diff --git a/src/Garbage/CacheInvalidator.js b/src/Garbage/CacheInvalidator.js new file mode 100644 index 00000000..f0ff9b0e --- /dev/null +++ b/src/Garbage/CacheInvalidator.js @@ -0,0 +1,68 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var _ = require('microdash'), + Set = require('core-js-pure/actual/set'); + +/** + * Invalidates PHP heap values that have been marked for invalidation due to being modified + * or because they were removed from a reference that referred to them. + * + * @param {ReferenceCache} referenceCache + * @constructor + */ +function CacheInvalidator(referenceCache) { + /** + * @type {Set} + */ + this.invalidatedSet = new Set(); + /** + * @type {ReferenceCache} + */ + this.referenceCache = referenceCache; +} + +_.extend(CacheInvalidator.prototype, { + /** + * Invalidates all values marked for invalidation. + */ + invalidate: function () { + var invalidator = this, + gcRootToMarkRootMap = invalidator.referenceCache.getGcRootToMarkRootMap(), + reachableValueToMarkRootMap = invalidator.referenceCache.getReachableValueToMarkRootMap(); + + invalidator.invalidatedSet.forEach(function (invalidatedValue) { + var markRoot = reachableValueToMarkRootMap.get(invalidatedValue); + + if (!markRoot || !markRoot.isValid()) { + // No cache entry for invalidated value: nothing to do. + return; + } + + gcRootToMarkRootMap.delete(markRoot.getGcRoot()); + markRoot.invalidate(); + reachableValueToMarkRootMap.delete(invalidatedValue); + }); + + invalidator.invalidatedSet.clear(); + }, + + /** + * Marks the given value for invalidation. + * + * @param {Value} value + */ + markValueForInvalidation: function (value) { + this.invalidatedSet.add(value); + } +}); + +module.exports = CacheInvalidator; diff --git a/src/Garbage/DestructibleObjectRepository.js b/src/Garbage/DestructibleObjectRepository.js new file mode 100644 index 00000000..725e9555 --- /dev/null +++ b/src/Garbage/DestructibleObjectRepository.js @@ -0,0 +1,58 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var _ = require('microdash'), + Set = require('core-js-pure/actual/set'); + +/** + * Keeps a strong reference to all known destructible objects (implementing ->__destruct()) + * so that we can call their destructor upon destruction. + * + * @constructor + */ +function DestructibleObjectRepository() { + /** + * @type {Set} + */ + this.objectValues = new Set(); +} + +_.extend(DestructibleObjectRepository.prototype, { + /** + * Fetches all known destructible ObjectValues that have not yet been garbage collected. + * + * @returns {ObjectValue[]} + */ + getObjectValues: function () { + // TODO: Look into how we can avoid needing this Array.from(...). + return Array.from(this.objectValues); + }, + + /** + * Registers a destructible ObjectValue. + * + * @param {ObjectValue} value + */ + registerValue: function (value) { + this.objectValues.add(value); + }, + + /** + * Unregisters a destructible ObjectValue once it has been destructed. + * + * @param {ObjectValue} value + */ + unregisterValue: function (value) { + this.objectValues.delete(value); + } +}); + +module.exports = DestructibleObjectRepository; diff --git a/src/Garbage/GarbageCollector.js b/src/Garbage/GarbageCollector.js new file mode 100644 index 00000000..31ae9777 --- /dev/null +++ b/src/Garbage/GarbageCollector.js @@ -0,0 +1,72 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var _ = require('microdash'); + +/** + * Top-level facade that performs garbage collection. + * + * Note that FinalizationRegistry cannot be used, because it invokes the finaliser + * _after_ the value has been destroyed, whereas PHP destructors must be called just before, + * as the object is accessible via `$this`. + * + * @param {RootDiscoverer} rootDiscoverer + * @param {ReferenceTreeWalker} referenceTreeWalker + * @param {CacheInvalidator} cacheInvalidator + * @param {ObjectDestructor} objectDestructor + * @constructor + */ +function GarbageCollector( + rootDiscoverer, + referenceTreeWalker, + cacheInvalidator, + objectDestructor +) { + /** + * @type {CacheInvalidator} + */ + this.cacheInvalidator = cacheInvalidator; + /** + * @type {ObjectDestructor} + */ + this.objectDestructor = objectDestructor; + /** + * @type {ReferenceTreeWalker} + */ + this.referenceTreeWalker = referenceTreeWalker; + /** + * @type {RootDiscoverer} + */ + this.rootDiscoverer = rootDiscoverer; +} + +_.extend(GarbageCollector.prototype, { + /** + * Performs a mark & sweep garbage collection. + * + * - Mark phase uses cached results of previous collections where possible. + * - Sweep phase will call ->__destruct() on any identified unreachable + * destructible ObjectValues (of classes implementing ->__destruct()). + * + * @returns {ChainableInterface} Returns the number of values collected + * (->__destruct()-ible objects destructed). + */ + collect: function () { + var collector = this; + + collector.cacheInvalidator.invalidate(); + collector.referenceTreeWalker.mark(collector.rootDiscoverer.discoverRoots()); + + return collector.objectDestructor.sweep(); + } +}); + +module.exports = GarbageCollector; diff --git a/src/Garbage/GarbageFactory.js b/src/Garbage/GarbageFactory.js new file mode 100644 index 00000000..cfe8d512 --- /dev/null +++ b/src/Garbage/GarbageFactory.js @@ -0,0 +1,45 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var _ = require('microdash'), + MarkRoot = require('./MarkRoot'), + MarkTree = require('./MarkTree'); + +/** + * @constructor + */ +function GarbageFactory() { + +} + +_.extend(GarbageFactory.prototype, { + /** + * Creates a new MarkRoot with an initial MarkTree. + * + * @param {Value} gcRoot + * @param {MarkTree} initialMarkTree + * @returns {MarkRoot} + */ + createMarkRoot: function (gcRoot, initialMarkTree) { + return new MarkRoot(gcRoot, initialMarkTree); + }, + + /** + * Creates a new MarkTree. + * + * @returns {MarkTree} + */ + createMarkTree: function () { + return new MarkTree(); + } +}); + +module.exports = GarbageFactory; diff --git a/src/Garbage/MarkRoot.js b/src/Garbage/MarkRoot.js new file mode 100644 index 00000000..99daf276 --- /dev/null +++ b/src/Garbage/MarkRoot.js @@ -0,0 +1,124 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var _ = require('microdash'), + phpCommon = require('phpcommon'), + Exception = phpCommon.Exception; + +/** + * Represents the cached result of the mark phase for a GC root. + * + * @param {Value} gcRoot + * @param {MarkTree} initialTree + * @constructor + */ +function MarkRoot(gcRoot, initialTree) { + /** + * @type {Value} + */ + this.gcRoot = gcRoot; + /** + * @type {MarkTree|null} + */ + this.tree = initialTree; +} + +_.extend(MarkRoot.prototype, { + /** + * Adopts a new MarkTree for this root and all its linked roots. + * + * @param {MarkTree} newTree + */ + adoptTree: function (newTree) { + var root = this; + + root.getLinkedMarkRoots() + .forEach(function (linkedMarkRoot) { + linkedMarkRoot.setTree(newTree); + }); + + root.setTree(newTree); + }, + + /** + * Fetches the GC root this MarkRoot is for. + * + * @returns {Value} + */ + getGcRoot: function () { + return this.gcRoot; + }, + + /** + * Fetches the set of other MarkRoots linked to this one via its MarkTree. + * + * @returns {Set} + */ + getLinkedMarkRoots: function () { + var root = this; + + if (!root.tree) { + throw new Exception('MarkRoot.getLinkedMarkRoots() :: Root is invalid'); + } + + return root.tree.getLinkedMarkRoots(); + }, + + /** + * Fetches the MarkTree for this root, if it is valid. + * + * @return {MarkTree} + */ + getTree: function () { + var root = this; + + if (!root.tree) { + throw new Exception('MarkRoot.getTree() :: Root is invalid'); + } + + return root.tree; + }, + + /** + * Invalidates this MarkRoot. + */ + invalidate: function () { + this.tree = null; + }, + + /** + * Determines whether this root is valid. + * + * @returns {boolean} + */ + isValid: function () { + return this.tree !== null; + }, + + /** + * Updates the MarkTree of this root. + * + * @param {MarkTree} tree + */ + setTree: function (tree) { + var root = this; + + if (!root.tree) { + // We cannot reuse MarkRoots, because there could still be stale entries + // in the ReachableValueToMarkRootMap cache that reference them. + throw new Exception('MarkRoot.setTree() :: Root is invalid'); + } + + root.tree = tree; + } +}); + +module.exports = MarkRoot; diff --git a/src/Garbage/MarkTree.js b/src/Garbage/MarkTree.js new file mode 100644 index 00000000..54f9a5cd --- /dev/null +++ b/src/Garbage/MarkTree.js @@ -0,0 +1,38 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var _ = require('microdash'), + Set = require('core-js-pure/actual/set'); + +/** + * Shared between one or more GC mark roots. + * + * @constructor + */ +function MarkTree() { + /** + * @type {Set} + */ + this.markRoots = new Set(); +} + +_.extend(MarkTree.prototype, { + /** + * Fetches the set of MarkRoots linked to this tree. + * + * @returns {Set} + */ + getLinkedMarkRoots: function () { + return this.markRoots; + } +}); + +module.exports = MarkTree; diff --git a/src/Garbage/ObjectDestructor.js b/src/Garbage/ObjectDestructor.js new file mode 100644 index 00000000..3e970e56 --- /dev/null +++ b/src/Garbage/ObjectDestructor.js @@ -0,0 +1,83 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var _ = require('microdash'), + MAGIC_DESTRUCT = '__destruct'; + +/** + * Calls the destructor of all destructible objects (implementing ->__destruct()) + * that have not been marked as reachable by the mark phase. + * + * @param {DestructibleObjectRepository} destructibleObjectRepository + * @param {ReferenceCache} referenceCache + * @param {Flow} flow + * @constructor + */ +function ObjectDestructor( + destructibleObjectRepository, + referenceCache, + flow +) { + /** + * @type {DestructibleObjectRepository} + */ + this.destructibleObjectRepository = destructibleObjectRepository; + /** + * @type {Flow} + */ + this.flow = flow; + /** + * @type {ReferenceCache} + */ + this.referenceCache = referenceCache; +} + +_.extend(ObjectDestructor.prototype, { + /** + * Calls the destructor for all destructible ObjectValues that are no longer reachable. + * + * @returns {ChainableInterface} Returns the number of values collected + * (->__destruct()-ible objects destructed). + */ + sweep: function () { + var destructor = this, + destructedObjectCount = 0, + reachableValueToMarkRootMap = destructor.referenceCache.getReachableValueToMarkRootMap(); + + // FIXME: Needs to handle different coroutines. Perhaps if a GC is already in progress + // in a different coroutine, this one should be skipped? + return destructor.flow + .eachAsync( + destructor.destructibleObjectRepository.getObjectValues(), + function (objectValue) { + var markRoot = reachableValueToMarkRootMap.get(objectValue); + + if (markRoot && markRoot.isValid()) { + // ObjectValue is reachable, so we must not yet call its destructor. + return; + } + + destructedObjectCount++; + + // FIXME: Remove ObjectValue from the repository so it is not destructed again + // assuming a reference hasn't been added somewhere in the destructor (?) + destructor.destructibleObjectRepository.unregisterValue(objectValue); + + return objectValue.callMethod(MAGIC_DESTRUCT); + } + ) + .next(function () { + return destructedObjectCount; + }); + } +}); + +module.exports = ObjectDestructor; diff --git a/src/Garbage/ReferenceCache.js b/src/Garbage/ReferenceCache.js new file mode 100644 index 00000000..28111099 --- /dev/null +++ b/src/Garbage/ReferenceCache.js @@ -0,0 +1,51 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var _ = require('microdash'), + WeakMap = require('es6-weak-map'); + +/** + * Holds cache structures for the garbage collection mechanism. + * + * @constructor + */ +function ReferenceCache() { + /** + * @type {WeakMap} + */ + this.gcRootToMarkRootMap = new WeakMap(); + /** + * @type {WeakMap} + */ + this.reachableValueToMarkRootMap = new WeakMap(); +} + +_.extend(ReferenceCache.prototype, { + /** + * Fetches the map from GC roots to MarkRoots. + * + * @return {WeakMap} + */ + getGcRootToMarkRootMap: function () { + return this.gcRootToMarkRootMap; + }, + + /** + * Fetches the map from reachable structured values to MarkRoots. + * + * @return {WeakMap} + */ + getReachableValueToMarkRootMap: function () { + return this.reachableValueToMarkRootMap; + } +}); + +module.exports = ReferenceCache; diff --git a/src/Garbage/ReferenceTreeWalker.js b/src/Garbage/ReferenceTreeWalker.js new file mode 100644 index 00000000..3530f9aa --- /dev/null +++ b/src/Garbage/ReferenceTreeWalker.js @@ -0,0 +1,135 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var _ = require('microdash'), + Set = require('core-js-pure/actual/set'); + +/** + * Walks the PHP value heap from the given GC roots, marking reachable values. + * + * @param {GarbageFactory} garbageFactory + * @param {ReferenceCache} referenceCache + * @constructor + */ +function ReferenceTreeWalker( + garbageFactory, + referenceCache +) { + /** + * @type {GarbageFactory} + */ + this.garbageFactory = garbageFactory; + /** + * @type {ReferenceCache} + */ + this.referenceCache = referenceCache; +} + +_.extend(ReferenceTreeWalker.prototype, { + /** + * Walks the given GC roots, updating the caches with reachable ArrayValues and ObjectValues + * and from those values back to all GC roots able to reach them. + * + * @param {Value[]} gcRoots + */ + mark: function (gcRoots) { + var walker = this, + gcRootToMarkRootMap = walker.referenceCache.getGcRootToMarkRootMap(), + reachableValueSetForGcRoot = new Set(), + reachableValueToMarkRootMap = walker.referenceCache.getReachableValueToMarkRootMap(); + + // As we populate the reachable value WeakMap, we can use it to ensure we don't recurse infinitely. + + _.each(gcRoots, function (gcRoot) { + var index, + linkedMarkRoot, + markRoot, + markTree = null, + outgoingValue, + outgoingValues, + length, + previousMarkRoot = gcRootToMarkRootMap.get(gcRoot), + subOutgoingValues; + + if (previousMarkRoot && previousMarkRoot.isValid()) { + // Fast case: GC root is already in cache, meaning nothing happened since the previous GC + // that would result in additional reachable values from this root, so there is nothing to do. + return; + } + + if (reachableValueToMarkRootMap.has(gcRoot)) { + markRoot = reachableValueToMarkRootMap.get(gcRoot); + + if (markRoot.isValid()) { + markTree = markRoot.getTree(); + } + } + + if (!markTree) { + markTree = walker.garbageFactory.createMarkTree(); + markRoot = walker.garbageFactory.createMarkRoot(gcRoot, markTree); + + reachableValueToMarkRootMap.set(gcRoot, markRoot); + } + + gcRootToMarkRootMap.set(gcRoot, markRoot); + + // Reuse this set for every GC root to reduce host heap/GC pressure. + reachableValueSetForGcRoot.clear(); + + outgoingValues = gcRoot.getOutgoingValues(); + length = outgoingValues.length; + + // Mark this GC root value itself first. + reachableValueSetForGcRoot.add(gcRoot); + + for (index = 0; index < length; index++) { + outgoingValue = outgoingValues[index]; + + if (reachableValueSetForGcRoot.has(outgoingValue)) { + /* + * We've already handled this value for this particular GC root, + * skip it to avoid recursing infinitely + * or unnecessarily performing the validation & adoption logic just below. + */ + continue; + } + + reachableValueSetForGcRoot.add(outgoingValue); + + if (reachableValueToMarkRootMap.has(outgoingValue)) { + linkedMarkRoot = reachableValueToMarkRootMap.get(outgoingValue); + + if (linkedMarkRoot.isValid()) { + // Merge the discovered MarkTree with this one. + linkedMarkRoot.adoptTree(markTree); + + continue; + } + } + + /** + * This map both limits recursion here and is used when invalidating + * the GC root->destructible object cache. + */ + reachableValueToMarkRootMap.set(outgoingValue, markRoot); + + subOutgoingValues = outgoingValue.getOutgoingValues(); + + // Unroll recursion so that we don't blow the host JS stack even for deeply nested heaps. + [].push.apply(outgoingValues, subOutgoingValues); + length = outgoingValues.length; + } + }); + } +}); + +module.exports = ReferenceTreeWalker; diff --git a/src/Garbage/RootDiscoverer.js b/src/Garbage/RootDiscoverer.js new file mode 100644 index 00000000..270abc55 --- /dev/null +++ b/src/Garbage/RootDiscoverer.js @@ -0,0 +1,62 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var _ = require('microdash'); + +/** + * Discovers all Garbage Collection roots (GC roots). + * + * This includes: + * - Global variables + * - Superglobal variables + * - Variables in all current call stack frame scopes + * - Static class properties + * - Static function/method variables + * - Variables in a paused Generator scope + * - Variables in all call stack frame scopes of a suspended Coroutine (including Fibers) + * + * TODO: Implement all of the above. + * + * @param {Scope} globalScope + * @constructor + */ +function RootDiscoverer(globalScope) { + /** + * @type {Scope} + */ + this.globalScope = globalScope; +} + +_.extend(RootDiscoverer.prototype, { + /** + * Discovers all Garbage Collection roots (GC roots). + * + * @returns {Value[]} + */ + discoverRoots: function () { + var discoverer = this, + gcRoots = []; + + // TODO: Fetch from all other sources! + + discoverer.globalScope.getVariables().forEach(function (reference) { + var outgoingValue = reference.getValueOrNativeNull(); + + if (outgoingValue && outgoingValue.isStructured()) { + gcRoots.push(outgoingValue); + } + }); + + return gcRoots; + } +}); + +module.exports = RootDiscoverer; diff --git a/src/Load/Includer.js b/src/Load/Includer.js index fe32978c..211823f0 100644 --- a/src/Load/Includer.js +++ b/src/Load/Includer.js @@ -11,14 +11,17 @@ module.exports = require('pauser')([ require('microdash'), + require('path'), require('phpcommon'), require('../Exception/LoadFailedException') ], function ( _, + path, phpCommon, LoadFailedException ) { var hasOwn = {}.hasOwnProperty, + resolvePath = path.resolve, Exception = phpCommon.Exception, INCLUDE_OPTION = 'include', PHPError = phpCommon.PHPError; @@ -80,7 +83,12 @@ module.exports = require('pauser')([ * @returns {boolean} */ hasModuleBeenIncluded: function (path) { - return hasOwn.call(this.includedPaths, path); + var includer = this, + // Resolve the path, as the eventual file is the one that must be unique. + // This will handle parent-directory ".." symbols, for example. + resolvedPath = resolvePath(path); + + return hasOwn.call(includer.includedPaths, resolvedPath); }, /** @@ -109,7 +117,9 @@ module.exports = require('pauser')([ var includer = this, includeFunction = includer.optionSet.getOption(INCLUDE_OPTION), includeScope, - previousError; + previousError, + // Resolve the path first - see .hasModuleBeenIncluded(...). + resolvedPath = resolvePath(includedPath); if (!includeFunction) { throw new Exception( @@ -125,8 +135,8 @@ module.exports = require('pauser')([ type ); - // Mark the module as included so we may avoid including it a second time - includer.includedPaths[includedPath] = true; + // Mark the module as included so we may avoid including it a second time. + includer.includedPaths[resolvedPath] = true; return includer.loader .load( diff --git a/src/Load/Loader.js b/src/Load/Loader.js index eb084096..b42f631f 100644 --- a/src/Load/Loader.js +++ b/src/Load/Loader.js @@ -67,7 +67,7 @@ module.exports = require('pauser')([ subOptions; // Always return a Future-wrapped Value, for a consistent interface regardless of synchronicity mode. - return loader.valueFactory.createFuture(function (resolveFuture, rejectFuture, nestCoroutine) { + return loader.valueFactory.createFuture(function (resolveFuture, rejectFuture, nestCoroutine, newCoroutine) { /** * Completes an unsuccessful module load * @@ -193,7 +193,8 @@ module.exports = require('pauser')([ load(filePath, { reject: reject, resolve: resolve, - nestCoroutine: nestCoroutine + nestCoroutine: nestCoroutine, + newCoroutine: newCoroutine }, module.getFilePath(), loader.valueFactory); } catch (error) { reject(error); diff --git a/src/Module.js b/src/Module.js index 9128199f..b940064b 100644 --- a/src/Module.js +++ b/src/Module.js @@ -23,6 +23,10 @@ function Module(scopeFactory, namespace, filePath, global) { * @type {string|null} */ this.filePath = filePath || null; + /** + * @type {boolean} + */ + this.strictTypesMode = false; /** * @type {NamespaceScope} */ @@ -30,6 +34,13 @@ function Module(scopeFactory, namespace, filePath, global) { } _.extend(Module.prototype, { + /** + * Enables strict-types mode for this module. + */ + enableStrictTypes: function () { + this.strictTypesMode = true; + }, + /** * Fetches the path to the file this module is defined in, or null if none. * @@ -46,6 +57,15 @@ _.extend(Module.prototype, { */ getTopLevelNamespaceScope: function () { return this.topLevelNamespaceScope; + }, + + /** + * Determines whether this module is in strict-types mode. + * + * @returns {boolean} + */ + isStrictTypesMode: function () { + return this.strictTypesMode; } }); diff --git a/src/NamespaceScope.js b/src/NamespaceScope.js index 4ef2d8aa..712bf208 100644 --- a/src/NamespaceScope.js +++ b/src/NamespaceScope.js @@ -137,6 +137,13 @@ module.exports = require('pauser')([ ); }, + /** + * Enables strict-types mode for this scope's module. + */ + enableStrictTypes: function () { + this.module.enableStrictTypes(); + }, + /** * Fetches a class with the given name relative to this namespace scope, * autoloading if necessary @@ -330,6 +337,15 @@ module.exports = require('pauser')([ return this.global; }, + /** + * Determines whether this scope's module is in strict-types mode. + * + * @returns {boolean} + */ + isStrictTypesMode: function () { + return this.module.isStrictTypesMode(); + }, + /** * Resolves a potentially relatively- or fully-qualified class path * to the Namespace instance it should be defined by and its name diff --git a/src/PHPState.js b/src/PHPState.js index bb9b7988..29a642de 100644 --- a/src/PHPState.js +++ b/src/PHPState.js @@ -652,6 +652,7 @@ module.exports = require('pauser')([ errorConfiguration, errorPromoter, errorReporting, + get('garbage.collector'), globalNamespace, globalScope, iniState, @@ -1352,6 +1353,15 @@ module.exports = require('pauser')([ return this.loader; }, + /** + * Fetches the configured synchronicity mode. + * + * @returns {'async'|'psync'|'sync'} + */ + getMode: function () { + return this.mode; + }, + getModuleFactory: function () { return this.moduleFactory; }, diff --git a/src/Reference/Reference.js b/src/Reference/Reference.js index ef57fcee..cec5fdcc 100644 --- a/src/Reference/Reference.js +++ b/src/Reference/Reference.js @@ -84,14 +84,6 @@ _.extend(Reference.prototype, { */ clearReference: throwUnimplemented('clearReference'), - /** - * Fetches the value of this reference when it is being assigned to a variable or another reference. - * This is used to implement the copy-on-assignment behaviour of PHP arrays - * - * @returns {Value} - */ - getForAssignment: throwUnimplemented('getForAssignment'), - /** * Fetches the native value of the PHP value being referred to * diff --git a/src/Reference/ReferenceSlot.js b/src/Reference/ReferenceSlot.js index cb2a0501..0f2b97cb 100644 --- a/src/Reference/ReferenceSlot.js +++ b/src/Reference/ReferenceSlot.js @@ -47,13 +47,6 @@ function ReferenceSlot( util.inherits(ReferenceSlot, Reference); _.extend(ReferenceSlot.prototype, { - /** - * {@inheritdoc} - */ - getForAssignment: function () { - return this.getValue(); - }, - /** * {@inheritdoc} */ diff --git a/src/Reference/ReferenceSnapshot.js b/src/Reference/ReferenceSnapshot.js index 7cf7fb81..ca68298b 100644 --- a/src/Reference/ReferenceSnapshot.js +++ b/src/Reference/ReferenceSnapshot.js @@ -113,11 +113,11 @@ _.extend(ReferenceSnapshot.prototype, { getValue: function () { var snapshot = this; - if (snapshot.value) { - return snapshot.value; + if (!snapshot.value) { + snapshot.value = snapshot.wrappedReference.raiseUndefined(); } - return snapshot.wrappedReference.raiseUndefined(); + return snapshot.value; }, /** @@ -154,7 +154,7 @@ _.extend(ReferenceSnapshot.prototype, { } if (snapshot.syntheticReference) { - // We've created a synthetic reference (see below), check it. + // We've created a synthetic reference (see `.getReference()`), check it. return snapshot.syntheticReference.isEmpty(); } @@ -172,7 +172,30 @@ _.extend(ReferenceSnapshot.prototype, { * {@inheritdoc} */ isSet: function () { - return this.futureFactory.createRejection(new Exception('ReferenceSnapshot.isSet(): Unsupported')); + var snapshot = this; + + if (snapshot.value) { + return snapshot.value.isSet(); + } + + if (snapshot.reference) { + // A reference was snapshotted, so check it. + return snapshot.reference.isSet(); + } + + if (snapshot.syntheticReference) { + // We've created a synthetic reference (see `.getReference()`), check it. + return snapshot.syntheticReference.isSet(); + } + + return this.futureFactory.createPresent(false); + }, + + /** + * {@inheritdoc} + */ + raiseUndefined: function () { + return this.wrappedReference.raiseUndefined(); }, /** diff --git a/src/Reference/UndeclaredStaticProperty.js b/src/Reference/UndeclaredStaticProperty.js index 26dadabf..298441aa 100644 --- a/src/Reference/UndeclaredStaticProperty.js +++ b/src/Reference/UndeclaredStaticProperty.js @@ -127,6 +127,15 @@ _.extend(UndeclaredStaticPropertyReference.prototype, { return this.futureFactory.createPresent(false); }, + /** + * Undeclared properties cannot be accessed, only checked for empty or set state. + * + * @throws (PHPFatalError} + */ + raiseUndefined: function () { + throwUndeclaredStaticPropertyAccessFatalError(this); + }, + /** * Undeclared properties cannot be accessed, only checked for empty or set state * diff --git a/src/Scope.js b/src/Scope.js index 73e7bc98..553d9a08 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -482,6 +482,15 @@ module.exports = require('pauser')([ return variable; }, + /** + * Fetches all variables defined for this scope. + * + * @return {Variable[]} + */ + getVariables: function () { + return Object.values(this.variables); + }, + /** * Determines whether this scope defines the specified variable or not * (not including the superglobal scope) diff --git a/src/Value.js b/src/Value.js index 7eb54e83..6e4ab954 100644 --- a/src/Value.js +++ b/src/Value.js @@ -102,10 +102,10 @@ module.exports = require('pauser')([ _.extend(Value.prototype, { /** - * Adds this value to another + * Adds this value to another. * * @param {Value} rightValue - * @returns {Value} + * @returns {ChainableInterface} */ add: function (rightValue) { var leftValue = this, @@ -697,6 +697,11 @@ module.exports = require('pauser')([ return createNullReference(this); }, + /** + * Handles this value being assigned to a storage slot. + * + * @returns {Value} + */ getForAssignment: function () { return this; }, @@ -740,6 +745,18 @@ module.exports = require('pauser')([ return this.value; }, + /** + * Fetches a list of all values that this value refers to. + * This does not include values that refer to this value (unless there is a cycle). + * + * This only applies to structured values, ArrayValue and ObjectValue. + * + * @returns {Value[]} + */ + getOutgoingValues: function () { + return []; + }, + /** * Exports a "proxying" version of the native value. For normal primitive values * (string, boolean, int, float) this will just be the native value, @@ -914,6 +931,15 @@ module.exports = require('pauser')([ return false; }, + /** + * Determines whether this value is structured (ArrayValue or ObjectValue, for now). + * + * @return {boolean} + */ + isStructured: function () { + return false; + }, + /** * Determines whether this value is the class of another value */ diff --git a/src/Value/Array.js b/src/Value/Array.js index 84568f75..6e7f0266 100644 --- a/src/Value/Array.js +++ b/src/Value/Array.js @@ -426,7 +426,9 @@ module.exports = require('pauser')([ /** * Fetches a copy of this array, as in PHP arrays are always passed by value - * and not by reference + * and not by reference. + * + * TODO: Implement copy-on-write to be more resource-efficient. * * @return {ArrayValue} */ @@ -576,6 +578,17 @@ module.exports = require('pauser')([ return this.value.length; }, + /** + * {@inheritdoc} + */ + getOutgoingValues: function () { + // Note that we ignore keys as they can only be scalar. + return this.getValues().filter(function (outgoingValue) { + // Property value is structured so can be marked for GC. + return outgoingValue.isStructured(); + }); + }, + getPointer: function () { return this.pointer; }, @@ -713,6 +726,13 @@ module.exports = require('pauser')([ return false; }, + /** + * {@inheritdoc} + */ + isStructured: function () { + return true; + }, + /** * Sets the internal array pointer. * diff --git a/src/Value/Object.js b/src/Value/Object.js index baa1a5b6..2ad417f2 100644 --- a/src/Value/Object.js +++ b/src/Value/Object.js @@ -853,7 +853,7 @@ module.exports = require('pauser')([ /** * Returns either the current value or one based on it as part of an assignment. - * Objects are passed around by reference so this should just return this + * Objects are passed around by reference so this should just return this. * * @returns {Value} */ @@ -880,6 +880,29 @@ module.exports = require('pauser')([ return this.getInstancePropertyNames(); }, + /** + * {@inheritdoc} + */ + getOutgoingValues: function () { + // NB: Don't include values via the class, e.g. static property values, + // as those will be captured separately during GC root discovery. + + var outgoingValues = [], + value = this; + + _.each(value.getInstancePropertyNames(), function (nameValue) { + var property = value.getInstancePropertyByName(nameValue), + propertyValue = property.getValueOrNativeNull(); + + if (propertyValue && propertyValue.isStructured()) { + // Property value is structured so can be marked for GC. + outgoingValues.push(propertyValue); + } + }); + + return outgoingValues; + }, + /** * Fetches the value of an instance property of this object * @@ -1445,6 +1468,13 @@ module.exports = require('pauser')([ return false; }, + /** + * {@inheritdoc} + */ + isStructured: function () { + return true; + }, + isTheClassOfArray: function () { return this.factory.createBoolean(false); }, diff --git a/src/ValueFactory.js b/src/ValueFactory.js index ec4376e9..7c576617 100644 --- a/src/ValueFactory.js +++ b/src/ValueFactory.js @@ -77,7 +77,7 @@ module.exports = require('pauser')([ }, queueMacrotask = typeof requestIdleCallback !== 'undefined' ? function (callback) { - requestIdleCallback(callback); + requestIdleCallback(callback, {timeout: 0}); } : function (callback) { setTimeout(callback, 1); @@ -771,7 +771,7 @@ module.exports = require('pauser')([ createFuture: function (executor) { var factory = this; - return factory.futureFactory.createFuture(function (resolveFuture, rejectFuture, nestCoroutine) { + return factory.futureFactory.createFuture(function (resolveFuture, rejectFuture, nestCoroutine, newCoroutine) { executor( function resolve(result) { // For Future-wrapped Values, we always want to coerce the eventual result to a Value. @@ -780,7 +780,8 @@ module.exports = require('pauser')([ function reject(error) { return rejectFuture(error); }, - nestCoroutine + nestCoroutine, + newCoroutine ); }); }, @@ -918,7 +919,7 @@ module.exports = require('pauser')([ }, /** - * Creates an ObjectValue for a given native value and class + * Creates an ObjectValue for a given native value and class. * * @param {object} nativeValue * @param {Class} classObject @@ -927,7 +928,25 @@ module.exports = require('pauser')([ createObject: function (nativeValue, classObject) { var factory = this; - // Object ID tracking is incomplete: ID should be freed when all references are lost + return factory.createObjectWithID( + nativeValue, + classObject, + factory.nextObjectID++ + ); + }, + + /** + * Creates an ObjectValue for a given native value, class and ID. + * + * @param {object} nativeValue + * @param {Class} classObject + * @param {number} id + * @returns {ObjectValue} + */ + createObjectWithID: function (nativeValue, classObject, id) { + var factory = this; + + // Object ID tracking is incomplete: ID should be freed when all references are lost. return new ObjectValue( factory, factory.referenceFactory, @@ -937,7 +956,7 @@ module.exports = require('pauser')([ factory.translator, nativeValue, classObject, - factory.nextObjectID++ + id ); }, diff --git a/src/Variable.js b/src/Variable.js index 276c56ff..257bccb7 100644 --- a/src/Variable.js +++ b/src/Variable.js @@ -30,6 +30,7 @@ module.exports = require('pauser')([ * @param {ReferenceFactory} referenceFactory * @param {FutureFactory} futureFactory * @param {Flow} flow + * @param {CacheInvalidator} garbageCacheInvalidator * @param {string} name * @constructor * @implements {ChainableInterface} @@ -40,6 +41,7 @@ module.exports = require('pauser')([ referenceFactory, futureFactory, flow, + garbageCacheInvalidator, name ) { /** @@ -54,6 +56,10 @@ module.exports = require('pauser')([ * @type {FutureFactory} */ this.futureFactory = futureFactory; + /** + * @type {CacheInvalidator} + */ + this.garbageCacheInvalidator = garbageCacheInvalidator; /** * @type {string} */ @@ -356,6 +362,19 @@ module.exports = require('pauser')([ var assignedValue, variable = this; + if (variable.value) { + variable.garbageCacheInvalidator.markValueForInvalidation(variable.value); + } + + /* + * Note that technically we should only do this if this variable does not have + * a reference assigned, as the ReferenceSlot should then handle the invalidation. + * + * However, that would introduce an additional branch in this hot code path, + * references are not used often, and invalidation is relatively quick for the moment. + */ + variable.garbageCacheInvalidator.markValueForInvalidation(value); + if (variable.name === 'this' && value.getType() === 'null') { // Normalise the value of $this to either be set to an ObjectValue // or be unset @@ -413,6 +432,10 @@ module.exports = require('pauser')([ unset: function () { var variable = this; + if (variable.value) { + variable.garbageCacheInvalidator.markValueForInvalidation(variable.value); + } + variable.value = variable.reference = null; return variable.futureFactory.createPresent(null); diff --git a/src/VariableFactory.js b/src/VariableFactory.js index ab47bdf0..0b542d92 100644 --- a/src/VariableFactory.js +++ b/src/VariableFactory.js @@ -21,6 +21,7 @@ module.exports = require('pauser')([ * @param {ReferenceFactory} referenceFactory * @param {FutureFactory} futureFactory * @param {Flow} flow + * @param {CacheInvalidator} garbageCacheInvalidator * @constructor */ function VariableFactory( @@ -29,7 +30,8 @@ module.exports = require('pauser')([ valueFactory, referenceFactory, futureFactory, - flow + flow, + garbageCacheInvalidator ) { /** * @type {CallStack} @@ -43,6 +45,10 @@ module.exports = require('pauser')([ * @type {FutureFactory} */ this.futureFactory = futureFactory; + /** + * @type {CacheInvalidator} + */ + this.garbageCacheInvalidator = garbageCacheInvalidator; /** * @type {ReferenceFactory} */ @@ -73,6 +79,7 @@ module.exports = require('pauser')([ factory.referenceFactory, factory.futureFactory, factory.flow, + factory.garbageCacheInvalidator, variableName ); } diff --git a/src/builtin/builtins.js b/src/builtin/builtins.js index a172cb4e..79ed5f4b 100644 --- a/src/builtin/builtins.js +++ b/src/builtin/builtins.js @@ -18,6 +18,8 @@ module.exports = require('pauser')([ require('./constants/errorHandling'), require('./ini/errorHandling'), require('./messages/error.en_GB'), + require('./functions/optionsAndInfo/garbage'), + require('./services/garbage'), require('./opcodes/instrumentation'), require('./opcodes/loopStructure'), require('./messages/misc.en_GB'), @@ -52,6 +54,8 @@ module.exports = require('pauser')([ errorHandlingConstants, errorHandlingDefaultINIOptions, errorMessages, + garbageOptionsAndInfoFunctions, + garbageServices, instrumentationOpcodeGroup, loopStructureOpcodeGroup, miscellaneousMessages, @@ -103,6 +107,7 @@ module.exports = require('pauser')([ ], functionGroups: [ configOptionsAndInfoFunctions, + garbageOptionsAndInfoFunctions, splFunctions ], defaultINIGroups: [ @@ -118,6 +123,7 @@ module.exports = require('pauser')([ ], serviceGroups: [ baseServiceGroup, + garbageServices, semanticsServiceGroup ], translationCatalogues: [ diff --git a/src/builtin/functions/optionsAndInfo/garbage.js b/src/builtin/functions/optionsAndInfo/garbage.js new file mode 100644 index 00000000..7e700f66 --- /dev/null +++ b/src/builtin/functions/optionsAndInfo/garbage.js @@ -0,0 +1,39 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +module.exports = function (internals) { + var garbageCollector = internals.garbageCollector, + optionSet = internals.optionSet; + + return { + /** + * Forces collection of any existing garbage cycles. + * + * @see {@link https://secure.php.net/manual/en/function.gc-collect-cycles.php} + */ + 'gc_collect_cycles': internals.typeFunction(': int', function () { + if (optionSet.getOption('garbageCollection') !== true) { + // Garbage collection is opt-in for now, especially while it is only partially implemented. + return 0; + } + + /* + * NB: Technically this should only return the number of collected cycles, + * as reference counting should collect all non-cyclical values. + * + * However, we have a mark & sweep garbage collector and no reference counting, + * so we return the total number of values collected instead. + */ + + return garbageCollector.collect(); + }) + }; +}; diff --git a/src/builtin/opcodes/calculation.js b/src/builtin/opcodes/calculation.js index fded757c..6d7e0d74 100644 --- a/src/builtin/opcodes/calculation.js +++ b/src/builtin/opcodes/calculation.js @@ -16,7 +16,10 @@ var _ = require('microdash'), KeyReferencePair = require('../../KeyReferencePair'), KeyValuePair = require('../../KeyValuePair'), List = require('../../List'), + Reference = require('../../Reference/Reference'), ReferenceElement = require('../../Element/ReferenceElement'), + ReferenceSnapshot = require('../../Reference/ReferenceSnapshot'), + Variable = require('../../Variable').sync(), NO_PARENT_CLASS = 'core.no_parent_class', TICK_OPTION = 'tick'; @@ -142,7 +145,7 @@ module.exports = function (internals) { * Used by "my_function(...)" syntax. */ callFunction: internals.typeHandler( - 'string name, snapshot ...argReferences : ref|val', + 'string name, slot ...argReferences : slot', function (name, argReferences) { var namespaceScope = callStack.getEffectiveNamespaceScope(), barewordString = valueFactory.createBarewordString(name, namespaceScope); @@ -158,7 +161,7 @@ module.exports = function (internals) { * Used by "$myObject->myMethod(...)" syntax. */ callInstanceMethod: internals.typeHandler( - 'val object, string method, snapshot ...argReferences : ref|val', + 'val object, string method, slot ...argReferences : slot', function (objectValue, methodName, argReferences) { return objectValue.callMethod(methodName, argReferences); } @@ -170,7 +173,7 @@ module.exports = function (internals) { * Used by "MyClass::myMethod(...)" syntax. */ callStaticMethod: internals.typeHandler( - 'val class, string method, bool isForwarding, snapshot ...argReferences : ref|val', + 'val class, string method, bool isForwarding, slot ...argReferences : slot', function (classValue, methodName, isForwarding, argReferences) { // TODO: Remove need for wrapping this as a *Value var methodValue = valueFactory.createString(methodName); @@ -186,7 +189,7 @@ module.exports = function (internals) { * Used by "$myObject->$myMethodName(...)" syntax. */ callVariableFunction: internals.typeHandler( - 'val name, snapshot ...argReferences : ref|val', + 'val name, slot ...argReferences : slot', function (nameValue, argReferences) { // NB: Make sure we do not coerce argument references to their values, // in case any of the parameters are passed by reference. @@ -201,7 +204,7 @@ module.exports = function (internals) { * Used by "$myObject->$myMethod(...)" syntax. */ callVariableInstanceMethod: internals.typeHandler( - 'val object, val methodName, snapshot ...argReferences : ref|val', + 'val object, val methodName, slot ...argReferences : slot', function (objectValue, methodNameValue, argReferences) { var methodName = methodNameValue.getNative(); @@ -216,7 +219,7 @@ module.exports = function (internals) { * Used by "MyClass::$myMethodName(...)" syntax. */ callVariableStaticMethod: internals.typeHandler( - 'val class, val methodName, bool isForwarding, snapshot ...argReferences : ref|val', + 'val class, val methodName, bool isForwarding, slot ...argReferences : slot', function (classNameValue, methodNameValue, isForwarding, argReferences) { return classNameValue.callStaticMethod(methodNameValue, argReferences, isForwarding); } @@ -318,7 +321,7 @@ module.exports = function (internals) { * Used by array literal syntax "[...]". */ createArray: internals.typeHandler( - 'snapshot ...elements : val', + 'element ...elements : val', function (elementSnapshots) { return valueProvider.createFutureArray(elementSnapshots); } @@ -413,7 +416,7 @@ module.exports = function (internals) { * @returns {ChainableInterface} */ createInstance: internals.typeHandler( - 'val className, snapshot ...args : val', + 'val className, slot ...args : val', function (classNameValue, args) { return classNameValue.instantiate(args); } @@ -463,7 +466,7 @@ module.exports = function (internals) { * Used by list syntax "list(...) = ...". */ createList: internals.typeHandler( - 'snapshot ...elements : list', + 'element ...elements : list', function (elementSnapshots) { return new List(valueFactory, flow, elementSnapshots); } @@ -679,7 +682,7 @@ module.exports = function (internals) { * @returns {ChainableInterface} */ getElement: internals.typeHandler( - 'ref|val array, any nativeKey : ref', + 'slot array, any nativeKey : ref', function (arrayReference, nativeKey) { // TODO: Remove need for this to be wrapped as a Value. var keyValue = valueFactory.coerce(nativeKey); @@ -880,7 +883,7 @@ module.exports = function (internals) { * @returns {ChainableInterface} */ getVariableElement: internals.typeHandler( - 'snapshot array, val key : ref', + 'slot array, val key : ref', function (arrayReference, keyValue) { return internals.implyArray(arrayReference) .next(function (arrayValue) { @@ -951,7 +954,7 @@ module.exports = function (internals) { * * Used by the `+$val` operator. */ - identity: internals.typeHandler('val value', function (value) { + identity: internals.typeHandler('val value : val', function (value) { return value.identity(); }), @@ -960,7 +963,7 @@ module.exports = function (internals) { * * Used by the "global $myVar;" statement. */ - importGlobal: internals.typeHandler('string name', function (name) { + importGlobal: internals.typeHandler('string name : void', function (name) { callStack.getCurrentScope().importGlobal(name); }), @@ -1077,7 +1080,7 @@ module.exports = function (internals) { // Fetch value from reference: return reference - // ... allowing for pauses (eg. AccessorReference) + // ... allowing for pauses (e.g. AccessorReference) .getValue() .next(function (value) { // ... and coerce to string values, allowing for pauses @@ -1354,7 +1357,7 @@ module.exports = function (internals) { * Used by the `%=` operator. */ moduloWith: internals.typeHandler( - 'snapshot target, val source', + 'snapshot target, val source : val', function (targetReference, sourceValue) { return targetReference.setValue(targetReference.getValue().modulo(sourceValue)); } @@ -1538,7 +1541,7 @@ module.exports = function (internals) { * @returns {ChainableInterface} */ pushElement: internals.typeHandler( - 'ref|val array : ref', + 'slot array : ref', function (arrayReference) { return internals.implyArray(arrayReference) .next(function (arrayValue) { @@ -1693,6 +1696,51 @@ module.exports = function (internals) { } ), + /** + * Snapshots the given reference if applicable. + * + * Snapshots are used to preserve evaluation order: if a variable is passed as an argument + * but later assigned to within a subsequent argument, the first argument should still pass + * the value that the reference had at that point, prior to the assignment. + * + * @param {Reference|Value|Variable} sourceReference + * @returns {ReferenceSnapshot|Value} + */ + snapshot: function (sourceReference) { + var resolvedValue; + + if (valueFactory.isValue(sourceReference)) { + // Fastest case: a Value was given, there is no reference or variable to fetch it from. + + return sourceReference; // No need to wrap a plain Value in a ReferenceSnapshot. + } + + if (sourceReference instanceof ReferenceSnapshot) { + // Value is already a snapshot: nothing to do. + return sourceReference; + } + + resolvedValue = sourceReference.getValueOrNativeNull(); + + if (resolvedValue === null) { + // Undefined: return a ReferenceSnapshot that indicates this. + return referenceFactory.createSnapshot(sourceReference); + } + + if ((sourceReference instanceof Reference) || (sourceReference instanceof Variable)) { + return resolvedValue + .next(function (presentValue) { + /* + * Wrap the result in a ReferenceSnapshot, so that we have access + * to the original Reference or Variable and the Value it resolved to at that point in time. + */ + return referenceFactory.createSnapshot(sourceReference, presentValue); + }); + } + + throw new Exception('Unexpected value provided to snapshot() opcode'); + }, + /** * Subtracts a Value from another, returning the result wrapped as a Value. * @@ -1782,7 +1830,7 @@ module.exports = function (internals) { * Used by "yield " expressions. */ yield_: internals.typeHandler( - 'snapshot value', + 'val value', function (valueSnapshot) { var generatorObjectValue = callStack.getGenerator(), iterator = generatorObjectValue.getInternalProperty('iterator'); @@ -1795,7 +1843,7 @@ module.exports = function (internals) { * Used by "yield => " expressions. */ yieldWithKey: internals.typeHandler( - 'snapshot key, snapshot value', + 'val key, val value', function (keySnapshot, valueSnapshot) { var generatorObjectValue = callStack.getGenerator(), iterator = generatorObjectValue.getInternalProperty('iterator'); diff --git a/src/builtin/opcodes/controlExpression.js b/src/builtin/opcodes/controlExpression.js index 34912e5e..80a183bb 100644 --- a/src/builtin/opcodes/controlExpression.js +++ b/src/builtin/opcodes/controlExpression.js @@ -82,6 +82,17 @@ module.exports = function (internals) { throw throwableValue; }), + /** + * Handles the operand of a return statement inside a try {...} block. + * Exists because we must preserve the result through pauses etc. + * if there is a finally clause that does not override it. + * + * Used by "try {...}" blocks that contain a return statement. + */ + tryReturn: internals.typeHandler('slot operand', function (operandReference) { + return operandReference; + }), + /** * Used by generator functions. */ diff --git a/src/builtin/opcodes/controlStructure.js b/src/builtin/opcodes/controlStructure.js index 632708ef..b6b74190 100644 --- a/src/builtin/opcodes/controlStructure.js +++ b/src/builtin/opcodes/controlStructure.js @@ -52,7 +52,7 @@ module.exports = function (internals) { * Defines a function with the given name for the current NamespaceScope. */ defineFunction: internals.typeHandler( - 'string name, any func, initial any parameters = null, initial any returnType = null, initial number line = null', + 'string name, any func, any parameters = null, any returnType = null, number line = null', function (name, func, parametersSpecData, returnTypeSpec, lineNumber) { var namespaceScope = callStack.getEffectiveNamespaceScope(); @@ -80,6 +80,14 @@ module.exports = function (internals) { } ), + /** + * Enables strict-types mode for the current module. The default is weak type-checking mode, + * so there is no opcode to reverse this behaviour. + */ + enableStrictTypes: function () { + callStack.enableStrictTypes(); + }, + /** * Immediately exits the currently executing PHP script. This is achieved * by throwing a JS error that cannot be caught by any PHP-land try..catch statement. @@ -156,7 +164,7 @@ module.exports = function (internals) { * Imports a class into the current namespace scope, eg. from a PHP `use ...` statement, * optionally with an alias. */ - useClass: internals.typeHandler('string name, initial string|null alias = null', function (name, alias) { + useClass: internals.typeHandler('string name, string|null alias = null', function (name, alias) { var namespaceScope = callStack.getEffectiveNamespaceScope(); return namespaceScope.use(name, alias); diff --git a/src/builtin/services/base.js b/src/builtin/services/base.js index e882eb24..1de01561 100644 --- a/src/builtin/services/base.js +++ b/src/builtin/services/base.js @@ -104,6 +104,7 @@ var phpCommon = require('phpcommon'), CLOSURE_FACTORY = 'closure_factory', CONTROL_BRIDGE = 'control_bridge', CONTROL_SCOPE = 'control_scope', + DESTRUCTIBLE_OBJECT_REPOSITORY = 'garbage.destructible_object_repository', ELEMENT_PROVIDER_FACTORY = 'element_provider_factory', ERROR_REPORTING = 'error_reporting', FFI_EXPORT_FACTORY = 'ffi_export_factory', @@ -117,6 +118,7 @@ var phpCommon = require('phpcommon'), FUNCTION_SIGNATURE_PARSER = 'function_signature_parser', FUNCTION_SPEC_FACTORY = 'function_spec_factory', FUTURE_FACTORY = 'future_factory', + GARBAGE_CACHE_INVALIDATOR = 'garbage.cache_invalidator', GLOBAL_SCOPE = 'global_scope', INSTRUMENTATION_FACTORY = 'instrumentation_factory', METHOD_PROMOTER = 'method_promoter', @@ -126,7 +128,6 @@ var phpCommon = require('phpcommon'), OPCODE_EXECUTOR = 'opcode_executor', OPCODE_FACTORY = 'opcode_factory', OPCODE_FETCHER_REPOSITORY = 'opcode_fetcher_repository', - OPCODE_HANDLER_FACTORY = 'opcode_handler_factory', OPCODE_PARAMETER_FACTORY = 'opcode_parameter_factory', OPCODE_POOL = 'opcode_pool', OPCODE_RESCUER = 'opcode_rescuer', @@ -209,7 +210,8 @@ module.exports = function (internals) { get(FUTURE_FACTORY), get(USERLAND), get(FFI_EXPORT_REPOSITORY), - get(FFI_FACTORY) + get(FFI_FACTORY), + get(DESTRUCTIBLE_OBJECT_REPOSITORY) ); }, @@ -460,8 +462,7 @@ module.exports = function (internals) { 'typed_opcode_handler_factory': function () { return new TypedOpcodeHandlerFactory( - get(CONTROL_BRIDGE), - get(OPCODE_HANDLER_FACTORY) + get(CONTROL_BRIDGE) ); }, @@ -492,7 +493,8 @@ module.exports = function (internals) { get(VALUE_FACTORY), get(REFERENCE_FACTORY), get(FUTURE_FACTORY), - get(FLOW) + get(FLOW), + get(GARBAGE_CACHE_INVALIDATOR) ); } }; diff --git a/src/builtin/services/garbage.js b/src/builtin/services/garbage.js new file mode 100644 index 00000000..bf7adc93 --- /dev/null +++ b/src/builtin/services/garbage.js @@ -0,0 +1,81 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var CacheInvalidator = require('../../Garbage/CacheInvalidator'), + DestructibleObjectRepository = require('../../Garbage/DestructibleObjectRepository'), + GarbageCollector = require('../../Garbage/GarbageCollector'), + GarbageFactory = require('../../Garbage/GarbageFactory'), + ObjectDestructor = require('../../Garbage/ObjectDestructor'), + ReferenceCache = require('../../Garbage/ReferenceCache'), + ReferenceTreeWalker = require('../../Garbage/ReferenceTreeWalker'), + RootDiscoverer = require('../../Garbage/RootDiscoverer'), + + CACHE_INVALIDATOR = 'garbage.cache_invalidator', + DESTRUCTIBLE_OBJECT_REPOSITORY = 'garbage.destructible_object_repository', + FLOW = 'flow', + GARBAGE_FACTORY = 'garbage.factory', + GLOBAL_SCOPE = 'global_scope', + OBJECT_DESTRUCTOR = 'garbage.object_destructor', + REFERENCE_CACHE = 'garbage.reference_cache', + REFERENCE_TREE_WALKER = 'garbage.reference_tree_walker', + ROOT_DISCOVERER = 'garbage.root_discoverer'; + +/** + * Provides services for handling of garbage collection. + * + * @param {ServiceInternals} internals + */ +module.exports = function (internals) { + var get = internals.getServiceFetcher(); + + return { + 'garbage.cache_invalidator': function () { + return new CacheInvalidator(get(REFERENCE_CACHE)); + }, + + 'garbage.destructible_object_repository': function () { + return new DestructibleObjectRepository(); + }, + + 'garbage.collector': function () { + return new GarbageCollector( + get(ROOT_DISCOVERER), + get(REFERENCE_TREE_WALKER), + get(CACHE_INVALIDATOR), + get(OBJECT_DESTRUCTOR) + ); + }, + + 'garbage.factory': function () { + return new GarbageFactory(); + }, + + 'garbage.object_destructor': function () { + return new ObjectDestructor( + get(DESTRUCTIBLE_OBJECT_REPOSITORY), + get(REFERENCE_CACHE), + get(FLOW) + ); + }, + + 'garbage.reference_cache': function () { + return new ReferenceCache(); + }, + + 'garbage.reference_tree_walker': function () { + return new ReferenceTreeWalker(get(GARBAGE_FACTORY), get(REFERENCE_CACHE)); + }, + + 'garbage.root_discoverer': function () { + return new RootDiscoverer(get(GLOBAL_SCOPE)); + } + }; +}; diff --git a/test/integration/addons/opcodeTest.js b/test/integration/addons/opcodeTest.js index 2fdf7059..2db41601 100644 --- a/test/integration/addons/opcodeTest.js +++ b/test/integration/addons/opcodeTest.js @@ -69,7 +69,7 @@ EOS expect(engine.getStdout().readAll()).to.equal('Logged: 242'); }); - it('should support installing an addon that hooks an opcode', async function () { + it('should support installing an addon that hooks an existing opcode', async function () { var php = nowdoc(function () {/*<<getIt()'] = $myObject->getIt($yourObject); + +return $result; +EOS +*/;}), //jshint ignore:line + module = tools.asyncTranspile('/path/to/my_module.php', php), + environment = tools.createAsyncEnvironment({ + include: function (path, promise) { + queueMicrotask(function () { + switch (path) { + case 'MyClass.php': + promise.resolve(tools.asyncTranspile(path, nowdoc(function () {/*<<getIt()': 21 + }); + }); }); diff --git a/test/integration/builtin/interfaces/ThrowableTest.js b/test/integration/builtin/interfaces/ThrowableTest.js index d30d6751..6d56ab03 100644 --- a/test/integration/builtin/interfaces/ThrowableTest.js +++ b/test/integration/builtin/interfaces/ThrowableTest.js @@ -51,8 +51,10 @@ EOS module = tools.asyncTranspile('/path/to/my/module.php', php), engine = module(), result; - engine.defineNonCoercingFunction('get_my_object_class', function (objectValue) { - return objectValue.getValue().getClassName(); + engine.defineNonCoercingFunction('get_my_object_class', function (objectReference) { + return objectReference.getValue().next(function (objectValue) { + return objectValue.getClassName(); + }); }); result = await engine.execute(); diff --git a/test/integration/constructs/emptyTest.js b/test/integration/constructs/emptyTest.js index 624dd6ba..ae4a4c52 100644 --- a/test/integration/constructs/emptyTest.js +++ b/test/integration/constructs/emptyTest.js @@ -278,11 +278,7 @@ EOS engine = module(); engine.defineFunction('get_async', function (internals) { return function (value) { - return internals.createFutureValue(function (resolve) { - setImmediate(function () { - resolve(value); - }); - }); + return internals.createAsyncPresentValue(value); }; }); diff --git a/test/integration/constructs/load/asyncIncludeOnceTest.js b/test/integration/constructs/load/asyncIncludeOnceTest.js index a1b1e6ed..82052f38 100644 --- a/test/integration/constructs/load/asyncIncludeOnceTest.js +++ b/test/integration/constructs/load/asyncIncludeOnceTest.js @@ -69,6 +69,37 @@ EOS }); }); + it('should correctly handle including the same file multiple times via different paths', async function () { + var php = nowdoc(function () {/*<<, which is a facade * that provides a minimal interface. - * - `CustomBuiltin/ObjectValue.callMethod(...)` then returns a Promise, resolved + * - `AsyncObjectValue.callMethod(...)` then returns a Promise, resolved * with the value returned from `get_async(...)` (see PHP snippet above) * - Using Promise chaining, we add 2 to the value and return the resulting Promise. * - As .add(...) returns a Promise (via the facade), operator overloading * is able to handle a sleep from inside a magic `+` operator method, for example. */ - return asyncObject.callMethod('getIt').then(function (value) { - return value.add(internals.valueFactory.createInteger(2)); - }); + var asyncObject = await objectArgReference.getValue() + .next(function (objectArg) { + return internals.valueHelper.toValueWithAsyncApi(objectArg); + }) + .toPromise(), + value = await asyncObject.callMethod('getIt'); + + return value.add(internals.valueFactory.createInteger(2)); }); }; }); diff --git a/test/integration/operators/arithmetic/additionTest.js b/test/integration/operators/arithmetic/additionTest.js index b0339a18..fe4fd64c 100644 --- a/test/integration/operators/arithmetic/additionTest.js +++ b/test/integration/operators/arithmetic/additionTest.js @@ -47,13 +47,33 @@ EOS }); }); - it('should correctly handle passing a variable as operand that is then re-assigned within a later operand', async function () { + it('should correctly handle passing a previously undefined variable as operand that is then re-assigned within a later operand', async function () { var php = nowdoc(function () {/*<<getMessage(); + } + + return [ + 'result' => $result, + 'throwable' => $throwable + ]; +} + +$result = []; + +$result['fully numeric string'] = tryCall(function () { + return myDoubler('21'); +}); +$result['leading numeric string'] = tryCall(function () { + return myDoubler('21abc'); +}); +$result['empty string'] = tryCall(function () { + return myDoubler(''); +}); + +return $result; +EOS +*/;}), //jshint ignore:line + module = tools.asyncTranspile('/path/to/my_module.php', php), + engine = module(); + + expect((await engine.execute()).getNative()).to.deep.equal({ + 'fully numeric string': { + 'result': null, + 'throwable': 'TypeError :: myDoubler(): Argument #1 ($myNumber) must be of type int, string given, called in /path/to/my_module.php on line 29' + }, + 'leading numeric string': { + 'result': null, + 'throwable': 'TypeError :: myDoubler(): Argument #1 ($myNumber) must be of type int, string given, called in /path/to/my_module.php on line 32' + }, + 'empty string': { + 'result': null, + 'throwable': 'TypeError :: myDoubler(): Argument #1 ($myNumber) must be of type int, string given, called in /path/to/my_module.php on line 35' + } + }); + expect(engine.getStderr().readAll()).to.equal(''); + }); + + it('should throw a TypeError when passing incorrect but coercible type when caller is in strict types mode', async function () { + var functionDefinitionPhp = nowdoc(function () {/*<<getMessage(); + } + + return [ + 'result' => $result, + 'throwable' => $throwable + ]; +} + +$result = []; + +$result['fully numeric string'] = tryCall(function () { + return myDoubler('21'); +}); +$result['leading numeric string'] = tryCall(function () { + return myDoubler('21abc'); +}); +$result['empty string'] = tryCall(function () { + return myDoubler(''); +}); + +return $result; +EOS +*/;}), //jshint ignore:line + mainModule = tools.asyncTranspile('/path/to/main.php', mainPhp), + environment = tools.createAsyncEnvironment(), + functionDefinitionEngine = functionDefinitionModule({}, environment), + mainEngine = mainModule({}, environment); + await functionDefinitionEngine.execute(); + + expect((await mainEngine.execute()).getNative()).to.deep.equal({ + 'fully numeric string': { + 'result': null, + 'throwable': 'TypeError :: myDoubler(): Argument #1 ($myNumber) must be of type int, string given, called in /path/to/main.php on line 25' + }, + 'leading numeric string': { + 'result': null, + 'throwable': 'TypeError :: myDoubler(): Argument #1 ($myNumber) must be of type int, string given, called in /path/to/main.php on line 28' + }, + 'empty string': { + 'result': null, + 'throwable': 'TypeError :: myDoubler(): Argument #1 ($myNumber) must be of type int, string given, called in /path/to/main.php on line 31' + } + }); + expect(functionDefinitionEngine.getStderr().readAll()).to.equal(''); + expect(mainEngine.getStderr().readAll()).to.equal(''); + }); +}); diff --git a/test/integration/statements/declare/strictTypes/returnTypeTest.js b/test/integration/statements/declare/strictTypes/returnTypeTest.js new file mode 100644 index 00000000..bb9ecfb9 --- /dev/null +++ b/test/integration/statements/declare/strictTypes/returnTypeTest.js @@ -0,0 +1,143 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + nowdoc = require('nowdoc'), + tools = require('../../../tools'); + +describe('PHP "declare" statement return type strict_types integration', function () { + it('should be able to return from a function with correct type in strict types mode', async function () { + var php = nowdoc(function () {/*<<getMessage() . + ' @ ' . + $caughtThrowable->getFile() . ':' . $caughtThrowable->getLine(); + } + + return [ + 'result' => $result, + 'throwable' => $throwable + ]; +} + +$result = []; + +$result['fully numeric string'] = tryCall(function () { + return getNumber('21'); +}); +$result['leading numeric string'] = tryCall(function () { + return getNumber('21abc'); +}); +$result['empty string'] = tryCall(function () { + return getNumber(''); +}); + +return $result; +EOS +*/;}), //jshint ignore:line + module = tools.asyncTranspile('/path/to/my_module.php', php), + engine = module(); + + expect((await engine.execute()).getNative()).to.deep.equal({ + 'fully numeric string': { + 'result': null, + 'throwable': 'TypeError :: getNumber(): Return value must be of type int, string returned @ /path/to/my_module.php:7' + }, + 'leading numeric string': { + 'result': null, + 'throwable': 'TypeError :: getNumber(): Return value must be of type int, string returned @ /path/to/my_module.php:7' + }, + 'empty string': { + 'result': null, + 'throwable': 'TypeError :: getNumber(): Return value must be of type int, string returned @ /path/to/my_module.php:7' + } + }); + expect(engine.getStderr().readAll()).to.equal(''); + }); +}); diff --git a/test/integration/statements/foreachTest.async.js b/test/integration/statements/foreachTest.async.js index 98d8408b..8dbea836 100644 --- a/test/integration/statements/foreachTest.async.js +++ b/test/integration/statements/foreachTest.async.js @@ -30,11 +30,7 @@ EOS engine = module(); engine.defineFunction('get_async', function (internals) { return function (value) { - return internals.createFutureValue(function (resolve) { - setImmediate(function () { - resolve(value); - }); - }); + return internals.createAsyncPresentValue(value); }; }); @@ -61,11 +57,7 @@ EOS engine = module(); engine.defineFunction('get_async', function (internals) { return function (value) { - return internals.createFutureValue(function (resolve) { - setImmediate(function () { - resolve(value); - }); - }); + return internals.createAsyncPresentValue(value); }; }); @@ -127,11 +119,7 @@ EOS engine = module(); engine.defineFunction('get_async', function (internals) { return function (value) { - return internals.createFutureValue(function (resolve) { - setImmediate(function () { - resolve(value); - }); - }); + return internals.createAsyncPresentValue(value); }; }); @@ -184,11 +172,7 @@ EOS engine = module(); engine.defineFunction('get_async', function (internals) { return function (value) { - return internals.createFutureValue(function (resolve) { - setImmediate(function () { - resolve(value); - }); - }); + return internals.createAsyncPresentValue(value); }; }); @@ -222,11 +206,7 @@ EOS function (internals) { return { 'get_async': function (value) { - return internals.createFutureValue(function (resolve) { - setImmediate(function () { - resolve(value); - }); - }); + return internals.createAsyncPresentValue(value); } }; } diff --git a/test/integration/statements/tryTest.js b/test/integration/statements/tryTest.js index e9bf3032..224e1f83 100644 --- a/test/integration/statements/tryTest.js +++ b/test/integration/statements/tryTest.js @@ -226,11 +226,7 @@ EOS module = tools.asyncTranspile('/path/to/my_module.php', php), engine = module(); engine.defineCoercingFunction('get_async', function (value) { - return this.createFutureValue(function (resolve) { - setImmediate(function () { - resolve(value); - }); - }); + return this.createAsyncPresentValue(value); }); expect((await engine.execute()).getNative()).to.deep.equal([ @@ -242,6 +238,101 @@ EOS ]); }); + it('should support a try clause with a return of function call that throws a caught exception', async function () { + var php = nowdoc(function () {/*<<(), see JS implementation of get_async() below + // This should resume with a throwInto(), see JS implementation of get_async() below. $result[] = get_async('fifth'); } catch (Throwable $t) { $result[] = 'caught: ' . @@ -285,7 +376,7 @@ EOS return internals.createFutureValue(function (resolve, reject) { setImmediate(function () { if (value === 'fifth') { - // Throw-into with a PHP Exception instance, so it can be caught by PHP-land + // Throw-into with a PHP Exception instance, so it can be caught by PHP-land. reject(internals.valueFactory.createErrorObject( 'Exception', 'Bang!', @@ -304,7 +395,7 @@ EOS expect((await engine.execute()).getNative()).to.deep.equal([ 'first', 'second', - // See the nested try..catch and the get_async() JS implementation above + // See the nested try...catch and the get_async() JS implementation above. 'caught: Bang! @ /some/fault.php:1234', 'sixth', 'seventh' diff --git a/test/integration/types/mixedTypeTest.js b/test/integration/types/mixedTypeTest.js new file mode 100644 index 00000000..9fe0c70f --- /dev/null +++ b/test/integration/types/mixedTypeTest.js @@ -0,0 +1,119 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + nowdoc = require('nowdoc'), + tools = require('../tools'); + +describe('PHP mixed type integration', function () { + it('should allow passing all value types for function parameters typed as "mixed"', async function () { + var php = nowdoc(function () {/*<<myProp = 21; +$result['stdClass instance'] = myChecker($value); + +$value = create_my_resource('my_resource_type'); +$result['valid resource'] = myChecker($value); + +$value = null; +$result['null'] = myChecker($value); + +// Skipping "unknown type" as we have no support yet (usually returned for closed file descriptors etc.) + +return $result; +EOS +*/;}),//jshint ignore:line + module = tools.asyncTranspile('/path/to/my_module.php', php), + engine = module(); + + engine.defineCoercingFunction('create_my_resource', function (type) { + return this.valueFactory.createResource(type, {}); + }); + engine.defineNonCoercingFunction('is_it_numeric', function (value) { + return value.isNumeric(); + }, 'mixed $value'); + + expect((await engine.execute()).getNative()).to.deep.equal({ + 'bool': true, + 'int': 21, + 'hexadecimal literal': 0x539, + 'octal literal': parseInt('02471', 8), + 'binary literal': parseInt('10100111001', 2), + 'integer literal with exponent': 1337e0, + 'hexadecimal literal as string': '0x539', + 'octal literal as string': '02471', + 'binary literal as string': '0b10100111001', + 'integer literal with exponent as string': '1337e0', + 'float': 101.222, + 'non-numeric string': 'hello world', + 'non-numeric string ending in number': 'hello world 987', + 'numeric string': '456', + 'array of numbers': [27, 31], + 'stdClass instance': {'myProp': 21}, + // Resources are coerced to their globally unique ID. + 'valid resource': 1, + 'null': null + }); + }); +}); diff --git a/test/integration/types/unionTypeTest.js b/test/integration/types/unionTypeTest.js new file mode 100644 index 00000000..27ea9505 --- /dev/null +++ b/test/integration/types/unionTypeTest.js @@ -0,0 +1,163 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + nowdoc = require('nowdoc'), + phpCommon = require('phpcommon'), + tools = require('../tools'), + PHPFatalError = phpCommon.PHPFatalError; + +describe('PHP union type integration', function () { + var doRun, + outputLog; + + beforeEach(function () { + outputLog = []; + doRun = function (engine) { + // Capture the standard streams, prefixing each write with its name + // so that we can ensure that what is written to each of them is in the correct order + // with respect to one another. + engine.getStdout().on('data', function (data) { + outputLog.push('[stdout]' + data); + }); + engine.getStderr().on('data', function (data) { + outputLog.push('[stderr]' + data); + }); + + return engine.execute(); + }; + }); + + it('should allow passing arrays and callables for function parameters typed as "array|callable"', async function () { + var php = nowdoc(function () {/*<<' + ); + }); + }); + describe('when "string"', function () { it('should return a value of correct native type', function () { createType('string'); @@ -125,6 +172,25 @@ describe('Opcode NativeType', function () { ); }); }); + + describe('when "undefined"', function () { + it('should return a value of correct native type', function () { + createType('undefined'); + + expect(type.coerceValue(undefined)).to.equal(undefined); + }); + + it('should throw when given an incorrect type', function () { + createType('undefined'); + + expect(function () { + type.coerceValue('not undefined'); + }).to.throw( + Exception, + 'Unexpected value of type "string" provided for NativeType' + ); + }); + }); }); describe('getDisplayName()', function () { @@ -152,5 +218,11 @@ describe('Opcode NativeType', function () { expect(type.getDisplayName()).to.equal('string'); }); + + it('should return the correct string for "undefined" native type', function () { + createType('undefined'); + + expect(type.getDisplayName()).to.equal('undefined'); + }); }); }); diff --git a/test/unit/Core/Opcode/Type/SlotTypeTest.js b/test/unit/Core/Opcode/Type/SlotTypeTest.js new file mode 100644 index 00000000..317efa58 --- /dev/null +++ b/test/unit/Core/Opcode/Type/SlotTypeTest.js @@ -0,0 +1,148 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + phpCommon = require('phpcommon'), + sinon = require('sinon'), + Exception = phpCommon.Exception, + KeyReferencePair = require('../../../../../src/KeyReferencePair'), + KeyValuePair = require('../../../../../src/KeyValuePair'), + Reference = require('../../../../../src/Reference/Reference'), + ReferenceElement = require('../../../../../src/Element/ReferenceElement'), + SlotType = require('../../../../../src/Core/Opcode/Type/SlotType'), + Value = require('../../../../../src/Value').sync(), + ValueFactory = require('../../../../../src/ValueFactory').sync(), + Variable = require('../../../../../src/Variable').sync(); + +describe('Opcode SlotType', function () { + var type, + valueFactory; + + beforeEach(function () { + valueFactory = sinon.createStubInstance(ValueFactory); + valueFactory.isValue + .withArgs(sinon.match.instanceOf(Value)) + .returns(true); + valueFactory.isValue.returns(false); + + type = new SlotType(valueFactory); + }); + + describe('allowsValue()', function () { + it('should return true for a Value instance', function () { + var value = sinon.createStubInstance(Value); + + expect(type.allowsValue(value)).to.be.true; + }); + + it('should return false for a ReferenceElement instance', function () { + var value = sinon.createStubInstance(ReferenceElement); + + expect(type.allowsValue(value)).to.be.false; + }); + + it('should return false for a KeyReferencePair instance', function () { + var value = sinon.createStubInstance(KeyReferencePair); + + expect(type.allowsValue(value)).to.be.false; + }); + + it('should return false for a KeyValuePair instance', function () { + var value = sinon.createStubInstance(KeyValuePair); + + expect(type.allowsValue(value)).to.be.false; + }); + + it('should return true for a Reference instance', function () { + var value = sinon.createStubInstance(Reference); + + expect(type.allowsValue(value)).to.be.true; + }); + + it('should return true for a Variable instance', function () { + var value = sinon.createStubInstance(Variable); + + expect(type.allowsValue(value)).to.be.true; + }); + + it('should return false for a native string', function () { + expect(type.allowsValue('my string')).to.be.false; + }); + }); + + describe('coerceValue()', function () { + it('should return the value when given a Value instance', function () { + var value = sinon.createStubInstance(Value); + + expect(type.coerceValue(value)).to.equal(value); + }); + + it('should throw when given a ReferenceElement instance', function () { + var value = sinon.createStubInstance(ReferenceElement); + + expect(function () { + type.coerceValue(value); + }).to.throw( + Exception, + 'Unexpected value provided for SlotType' + ); + }); + + it('should throw when given a KeyReferencePair instance', function () { + var value = sinon.createStubInstance(KeyReferencePair); + + expect(function () { + type.coerceValue(value); + }).to.throw( + Exception, + 'Unexpected value provided for SlotType' + ); + }); + + it('should throw when given a KeyValuePair instance', function () { + var value = sinon.createStubInstance(KeyValuePair); + + expect(function () { + type.coerceValue(value); + }).to.throw( + Exception, + 'Unexpected value provided for SlotType' + ); + }); + + it('should return the reference when given a Reference instance', function () { + var value = sinon.createStubInstance(Reference); + + expect(type.coerceValue(value)).to.equal(value); + }); + + it('should return the variable when given a Variable instance', function () { + var value = sinon.createStubInstance(Variable); + + expect(type.coerceValue(value)).to.equal(value); + }); + + it('should throw when given a native string', function () { + expect(function () { + type.coerceValue('my string'); + }).to.throw( + Exception, + 'Unexpected value provided for SlotType' + ); + }); + }); + + describe('getDisplayName()', function () { + it('should return "slot"', function () { + expect(type.getDisplayName()).to.equal('slot'); + }); + }); +}); diff --git a/test/unit/Core/Opcode/Type/SnapshotTypeTest.js b/test/unit/Core/Opcode/Type/SnapshotTypeTest.js index 8c234c4b..dfa4fdfb 100644 --- a/test/unit/Core/Opcode/Type/SnapshotTypeTest.js +++ b/test/unit/Core/Opcode/Type/SnapshotTypeTest.js @@ -53,40 +53,40 @@ describe('Opcode SnapshotType', function () { }); describe('allowsValue()', function () { - it('should return true for a Reference instance', function () { - var value = sinon.createStubInstance(Reference); + it('should return true for a ReferenceSnapshot instance', function () { + var value = sinon.createStubInstance(ReferenceSnapshot); expect(type.allowsValue(value)).to.be.true; }); - it('should return true for a ReferenceSnapshot instance', function () { - var value = sinon.createStubInstance(ReferenceSnapshot); + it('should return true for a non-ReferenceSnapshot Reference instance', function () { + var value = sinon.createStubInstance(Reference); expect(type.allowsValue(value)).to.be.true; }); - it('should return true for a ReferenceElement instance', function () { + it('should return false for a ReferenceElement instance', function () { var value = sinon.createStubInstance(ReferenceElement); - expect(type.allowsValue(value)).to.be.true; + expect(type.allowsValue(value)).to.be.false; }); - it('should return true for a KeyReferencePair instance', function () { + it('should return false for a KeyReferencePair instance', function () { var value = sinon.createStubInstance(KeyReferencePair); - expect(type.allowsValue(value)).to.be.true; + expect(type.allowsValue(value)).to.be.false; }); - it('should return true for a KeyValuePair instance', function () { + it('should return false for a KeyValuePair instance', function () { var value = sinon.createStubInstance(KeyValuePair); - expect(type.allowsValue(value)).to.be.true; + expect(type.allowsValue(value)).to.be.false; }); - it('should return true for a Value instance', function () { + it('should return false for a Value instance', function () { var value = sinon.createStubInstance(Value); - expect(type.allowsValue(value)).to.be.true; + expect(type.allowsValue(value)).to.be.false; }); it('should return true for a Variable instance', function () { @@ -101,34 +101,54 @@ describe('Opcode SnapshotType', function () { }); describe('coerceValue()', function () { - it('should return the value when given a Value', function () { - var value = valueFactory.createString('my string'); - - expect(type.coerceValue(value)).to.equal(value); - }); - it('should return the snapshot when given a ReferenceSnapshot', function () { var snapshot = sinon.createStubInstance(ReferenceSnapshot); expect(type.coerceValue(snapshot)).to.equal(snapshot); }); - it('should return the snapshot when given a ReferenceElement', function () { + it('should throw when given a ReferenceElement', function () { var element = sinon.createStubInstance(ReferenceElement); - expect(type.coerceValue(element)).to.equal(element); + expect(function () { + type.coerceValue(element); + }).to.throw( + Exception, + 'Unexpected value provided for SnapshotType' + ); }); - it('should return the pair when given a KeyReferencePair', function () { + it('should throw when given a KeyReferencePair', function () { var pair = sinon.createStubInstance(KeyReferencePair); - expect(type.coerceValue(pair)).to.equal(pair); + expect(function () { + type.coerceValue(pair); + }).to.throw( + Exception, + 'Unexpected value provided for SnapshotType' + ); }); - it('should return the pair when given a KeyValuePair', function () { + it('should throw when given a KeyValuePair', function () { var pair = sinon.createStubInstance(KeyValuePair); - expect(type.coerceValue(pair)).to.equal(pair); + expect(function () { + type.coerceValue(pair); + }).to.throw( + Exception, + 'Unexpected value provided for SnapshotType' + ); + }); + + it('should throw when given a Value', function () { + var value = valueFactory.createString('my string'); + + expect(function () { + type.coerceValue(value); + }).to.throw( + Exception, + 'SnapshotType cannot accept Values' + ); }); it('should throw when given a native string', function () { diff --git a/test/unit/Core/Opcode/Type/VoidTypeTest.js b/test/unit/Core/Opcode/Type/VoidTypeTest.js new file mode 100644 index 00000000..ef29b47a --- /dev/null +++ b/test/unit/Core/Opcode/Type/VoidTypeTest.js @@ -0,0 +1,163 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + phpCommon = require('phpcommon'), + sinon = require('sinon'), + Exception = phpCommon.Exception, + KeyReferencePair = require('../../../../../src/KeyReferencePair'), + KeyValuePair = require('../../../../../src/KeyValuePair'), + Reference = require('../../../../../src/Reference/Reference'), + ReferenceElement = require('../../../../../src/Element/ReferenceElement'), + Value = require('../../../../../src/Value').sync(), + Variable = require('../../../../../src/Variable').sync(), + VoidType = require('../../../../../src/Core/Opcode/Type/VoidType'); + +describe('Opcode VoidType', function () { + var type; + + beforeEach(function () { + type = new VoidType(); + }); + + describe('allowsValue()', function () { + it('should return true for native undefined', function () { + expect(type.allowsValue(undefined)).to.be.true; + }); + + it('should return false for a Value instance', function () { + var value = sinon.createStubInstance(Value); + + expect(type.allowsValue(value)).to.be.false; + }); + + it('should return false for a ReferenceElement instance', function () { + var value = sinon.createStubInstance(ReferenceElement); + + expect(type.allowsValue(value)).to.be.false; + }); + + it('should return false for a KeyReferencePair instance', function () { + var value = sinon.createStubInstance(KeyReferencePair); + + expect(type.allowsValue(value)).to.be.false; + }); + + it('should return false for a KeyValuePair instance', function () { + var value = sinon.createStubInstance(KeyValuePair); + + expect(type.allowsValue(value)).to.be.false; + }); + + it('should return false for a Reference instance', function () { + var value = sinon.createStubInstance(Reference); + + expect(type.allowsValue(value)).to.be.false; + }); + + it('should return false for a Variable instance', function () { + var value = sinon.createStubInstance(Variable); + + expect(type.allowsValue(value)).to.be.false; + }); + + it('should return false for a native string', function () { + expect(type.allowsValue('my string')).to.be.false; + }); + }); + + describe('coerceValue()', function () { + it('should return the value when given native undefined', function () { + expect(type.coerceValue(undefined)).to.be.undefined; + }); + + it('should throw when given a Value instance', function () { + var value = sinon.createStubInstance(Value); + + expect(function () { + type.coerceValue(value); + }).to.throw( + Exception, + 'Unexpected value of type "object" provided for VoidType' + ); + }); + + it('should throw when given a ReferenceElement instance', function () { + var value = sinon.createStubInstance(ReferenceElement); + + expect(function () { + type.coerceValue(value); + }).to.throw( + Exception, + 'Unexpected value of type "object" provided for VoidType' + ); + }); + + it('should throw when given a KeyReferencePair instance', function () { + var value = sinon.createStubInstance(KeyReferencePair); + + expect(function () { + type.coerceValue(value); + }).to.throw( + Exception, + 'Unexpected value of type "object" provided for VoidType' + ); + }); + + it('should throw when given a KeyValuePair instance', function () { + var value = sinon.createStubInstance(KeyValuePair); + + expect(function () { + type.coerceValue(value); + }).to.throw( + Exception, + 'Unexpected value of type "object" provided for VoidType' + ); + }); + + it('should throw when given a Reference instance', function () { + var value = sinon.createStubInstance(Reference); + + expect(function () { + type.coerceValue(value); + }).to.throw( + Exception, + 'Unexpected value of type "object" provided for VoidType' + ); + }); + + it('should throw when given a Variable instance', function () { + var value = sinon.createStubInstance(Variable); + + expect(function () { + type.coerceValue(value); + }).to.throw( + Exception, + 'Unexpected value of type "object" provided for VoidType' + ); + }); + + it('should throw when given a native string', function () { + expect(function () { + type.coerceValue('my string'); + }).to.throw( + Exception, + 'Unexpected value of type "string" provided for VoidType' + ); + }); + }); + + describe('getDisplayName()', function () { + it('should return "void"', function () { + expect(type.getDisplayName()).to.equal('void'); + }); + }); +}); diff --git a/test/unit/EnvironmentTest.js b/test/unit/EnvironmentTest.js index 8f0f458b..eff53598 100644 --- a/test/unit/EnvironmentTest.js +++ b/test/unit/EnvironmentTest.js @@ -251,6 +251,14 @@ describe('Environment', function () { }); }); + describe('getMode()', function () { + it('should return the mode from the state', function () { + state.getMode.returns('psync'); + + expect(environment.getMode()).to.equal('psync'); + }); + }); + describe('getOptions()', function () { it('should return the raw options object from the PHPState', function () { var options = {'my-option': 27}; diff --git a/test/unit/FFI/CallTest.js b/test/unit/FFI/CallTest.js new file mode 100644 index 00000000..efd107ca --- /dev/null +++ b/test/unit/FFI/CallTest.js @@ -0,0 +1,51 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + phpCommon = require('phpcommon'), + tools = require('../tools'), + Call = require('../../../src/FFI/Call'), + Exception = phpCommon.Exception; + +describe('FFI Call', function () { + var arg1, + arg2, + call, + state, + valueFactory; + + beforeEach(function () { + state = tools.createIsolatedState('async'); + valueFactory = state.getValueFactory(); + + arg1 = valueFactory.createString('first arg'); + arg2 = valueFactory.createString('second arg'); + + call = new Call([arg1, arg2]); + }); + + describe('enableStrictTypes()', function () { + it('should raise an exception', function () { + expect(function () { + call.enableStrictTypes(); + }).to.throw( + Exception, + 'FFI calls cannot be switched into strict-types mode' + ); + }); + }); + + describe('isStrictTypesMode()', function () { + it('should return false as FFI calls are always in weak type-checking mode', function () { + expect(call.isStrictTypesMode()).to.be.false; + }); + }); +}); diff --git a/test/unit/FFI/Internals/InternalsTest.js b/test/unit/FFI/Internals/InternalsTest.js index 1aca53f1..a48d0e2a 100644 --- a/test/unit/FFI/Internals/InternalsTest.js +++ b/test/unit/FFI/Internals/InternalsTest.js @@ -27,6 +27,7 @@ var expect = require('chai').expect, Flow = require('../../../../src/Control/Flow'), Future = require('../../../../src/Control/Future'), FutureFactory = require('../../../../src/Control/FutureFactory'), + GarbageCollector = require('../../../../src/Garbage/GarbageCollector'), Namespace = require('../../../../src/Namespace').sync(), Includer = require('../../../../src/Load/Includer').sync(), INIState = require('../../../../src/INIState'), @@ -63,6 +64,7 @@ describe('FFI Internals', function () { evaluator, flow, futureFactory, + garbageCollector, globalNamespace, globalScope, includer, @@ -103,6 +105,7 @@ describe('FFI Internals', function () { evaluator = sinon.createStubInstance(Evaluator); flow = sinon.createStubInstance(Flow); futureFactory = sinon.createStubInstance(FutureFactory); + garbageCollector = sinon.createStubInstance(GarbageCollector); globalNamespace = sinon.createStubInstance(Namespace); globalScope = sinon.createStubInstance(Scope); includer = sinon.createStubInstance(Includer); @@ -142,6 +145,7 @@ describe('FFI Internals', function () { errorConfiguration, errorPromoter, errorReporting, + garbageCollector, globalNamespace, globalScope, iniState, @@ -207,6 +211,10 @@ describe('FFI Internals', function () { expect(internals.futureFactory).to.equal(futureFactory); }); + it('should expose the GarbageCollector publicly', function () { + expect(internals.garbageCollector).to.equal(garbageCollector); + }); + it('should expose the global Namespace publicly', function () { expect(internals.globalNamespace).to.equal(globalNamespace); }); diff --git a/test/unit/FFI/Value/ValueCoercerTest.js b/test/unit/FFI/Value/ValueCoercerTest.js index d50c0405..6db3a763 100644 --- a/test/unit/FFI/Value/ValueCoercerTest.js +++ b/test/unit/FFI/Value/ValueCoercerTest.js @@ -10,8 +10,10 @@ 'use strict'; var expect = require('chai').expect, + sinon = require('sinon'), tools = require('../../tools'), - ValueCoercer = require('../../../../src/FFI/Value/ValueCoercer'); + ValueCoercer = require('../../../../src/FFI/Value/ValueCoercer'), + Variable = require('../../../../src/Variable').sync(); describe('FFI ValueCoercer', function () { var createCoercer, @@ -43,6 +45,20 @@ describe('FFI ValueCoercer', function () { expect(await valueCoercer.coerceArguments([argumentValue1, argumentValue2]).toPromise()) .to.deep.equal(['first arg', 'second arg']); }); + + it('should not raise notices/warnings on undefined references', async function () { + var variable1 = sinon.createStubInstance(Variable), + variable2 = sinon.createStubInstance(Variable), + argumentValue1 = valueFactory.createString('first arg'), + argumentValue2 = valueFactory.createString('second arg'); + variable1.getValueOrNull.returns(argumentValue1); + variable2.getValueOrNull.returns(argumentValue2); + + expect(await valueCoercer.coerceArguments([variable1, variable2]).toPromise()) + .to.deep.equal(['first arg', 'second arg']); + expect(variable1.getValueOrNull).to.have.been.calledOnce; + expect(variable2.getValueOrNull).to.have.been.calledOnce; + }); }); describe('in non-coercing mode', function () { diff --git a/test/unit/Function/FunctionSpecTest.js b/test/unit/Function/FunctionSpecTest.js index e7dad65b..17cb1689 100644 --- a/test/unit/Function/FunctionSpecTest.js +++ b/test/unit/Function/FunctionSpecTest.js @@ -72,6 +72,7 @@ describe('FunctionSpec', function () { callStack.getCurrent.returns(sinon.createStubInstance(Call)); callStack.getLastFilePath.returns('/path/to/my/module.php'); callStack.getLastLine.returns(123); + callStack.isStrictTypesMode.returns(false); callStack.raiseTranslatedError .withArgs(PHPError.E_ERROR) .callsFake(function (level, translationKey, placeholderVariables) { @@ -89,6 +90,7 @@ describe('FunctionSpec', function () { .callsFake(function (translationKey, placeholderVariables) { return '[Translated] ' + translationKey + ' ' + JSON.stringify(placeholderVariables || {}); }); + namespaceScope.isGlobal.returns(false); valueFactory.setGlobalNamespace(globalNamespace); createSpec = function (returnByReference) { @@ -210,13 +212,26 @@ describe('FunctionSpec', function () { expect(variable.setValue).to.have.been.calledWith(sinon.match.same(coercedValue)); }); - it('should return the result value when the function is return-by-value', function () { + it('should return the coerced value when the function is return-by-value', async function () { + var originalValue = valueFactory.createString('original value'), + coercedValue = valueFactory.createString('coerced value'), + variable = sinon.createStubInstance(Variable); + returnType.coerceValue + .withArgs(sinon.match.same(originalValue)) + .returns(coercedValue); + variable.getValue.returns(originalValue); + + expect(await spec.coerceReturnReference(variable).toPromise()).to.equal(coercedValue); + }); + + it('should not coerce the value when in strict-types mode', async function () { var value = valueFactory.createString('my value'), variable = sinon.createStubInstance(Variable); - returnType.coerceValue.returnsArg(0); + callStack.isStrictTypesMode.returns(true); variable.getValue.returns(value); - expect(spec.coerceReturnReference(variable)).to.equal(value); + expect(await spec.coerceReturnReference(variable).toPromise()).to.equal(value); + expect(returnType.coerceValue).not.to.have.been.called; }); }); @@ -504,16 +519,12 @@ describe('FunctionSpec', function () { }); it('should return false for a userland function', function () { - namespaceScope.isGlobal.returns(false); - expect(spec.isBuiltin()).to.be.false; }); }); describe('isUserland()', function () { it('should return true for a userland function', function () { - namespaceScope.isGlobal.returns(false); - expect(spec.isUserland()).to.be.true; }); @@ -558,6 +569,7 @@ describe('FunctionSpec', function () { var caughtError = null, errorClassObject = sinon.createStubInstance(Class), errorValue = sinon.createStubInstance(ObjectValue); + errorValue.next.yields(errorValue); callStack.isUserland.returns(false); globalNamespace.getClass .withArgs('ArgumentCountError') @@ -597,6 +609,7 @@ describe('FunctionSpec', function () { var caughtError = null, errorClassObject = sinon.createStubInstance(Class), errorValue = sinon.createStubInstance(ObjectValue); + errorValue.next.yields(errorValue); callStack.isUserland.returns(false); globalNamespace.getClass .withArgs('ArgumentCountError') @@ -636,6 +649,7 @@ describe('FunctionSpec', function () { errorClassObject = sinon.createStubInstance(Class), errorValue = sinon.createStubInstance(ObjectValue), parameter3 = sinon.createStubInstance(Parameter); + errorValue.next.yields(errorValue); callStack.isUserland.returns(false); globalNamespace.getClass .withArgs('ArgumentCountError') @@ -678,6 +692,7 @@ describe('FunctionSpec', function () { var caughtError = null, errorClassObject = sinon.createStubInstance(Class), errorValue = sinon.createStubInstance(ObjectValue); + errorValue.next.yields(errorValue); callStack.isUserland.returns(true); globalNamespace.getClass .withArgs('ArgumentCountError') @@ -717,6 +732,7 @@ describe('FunctionSpec', function () { var caughtError = null, errorClassObject = sinon.createStubInstance(Class), errorValue = sinon.createStubInstance(ObjectValue); + errorValue.next.yields(errorValue); callStack.isUserland.returns(true); callStack.getCallerFilePath.returns(null); callStack.getCallerLastLine.returns(null); @@ -788,6 +804,7 @@ describe('FunctionSpec', function () { beforeEach(function () { createSpec(true); returnReference.isReferenceable.returns(true); + returnReference.toPromise.returns(Promise.resolve(returnReference)); returnType.allowsValue .withArgs(sinon.match.same(returnValue)) .returns(futureFactory.createPresent(true)); @@ -821,7 +838,30 @@ describe('FunctionSpec', function () { ); }); - it('should raise an error', async function () { + it('should correctly raise an error when userland', async function () { + try { + await spec.validateReturnReference(returnReference, returnValue).toPromise(); + } catch (error) {} + + expect(callStack.raiseTranslatedError).to.have.been.calledOnce; + expect(callStack.raiseTranslatedError).to.have.been.calledWith( + PHPError.E_ERROR, + 'core.invalid_return_value_type', + { + func: 'myFunction', + expectedType: 'float', + actualType: 'string' + }, + 'TypeError', + false, + undefined, + undefined + ); + }); + + it('should correctly raise an error when builtin', async function () { + namespaceScope.isGlobal.returns(true); + try { await spec.validateReturnReference(returnReference, returnValue).toPromise(); } catch (error) {} @@ -860,7 +900,30 @@ describe('FunctionSpec', function () { ); }); - it('should raise an error', async function () { + it('should correctly raise an error when userland', async function () { + try { + await spec.validateReturnReference(returnReference, returnValue).toPromise(); + } catch (error) {} + + expect(callStack.raiseTranslatedError).to.have.been.calledOnce; + expect(callStack.raiseTranslatedError).to.have.been.calledWith( + PHPError.E_ERROR, + 'core.invalid_return_value_type', + { + func: 'myFunction', + expectedType: 'float', + actualType: 'string' + }, + 'TypeError', + false, + undefined, + undefined + ); + }); + + it('should correctly raise an error when builtin', async function () { + namespaceScope.isGlobal.returns(true); + try { await spec.validateReturnReference(returnReference, returnValue).toPromise(); } catch (error) {} @@ -886,6 +949,7 @@ describe('FunctionSpec', function () { beforeEach(function () { createSpec(true); returnReference.isReferenceable.returns(false); + returnReference.toPromise.returns(Promise.resolve(returnReference)); returnType.allowsValue .withArgs(sinon.match.same(returnValue)) .returns(futureFactory.createPresent(true)); diff --git a/test/unit/Function/ParameterTest.js b/test/unit/Function/ParameterTest.js index c6943da9..1e0494ff 100644 --- a/test/unit/Function/ParameterTest.js +++ b/test/unit/Function/ParameterTest.js @@ -61,6 +61,7 @@ describe('Parameter', function () { callStack.getCallerFilePath.returns(null); callStack.getCallerLastLine.returns(null); + callStack.isStrictTypesMode.returns(false); callStack.isUserland.returns(false); callStack.raiseTranslatedError.callsFake(function ( level, @@ -166,6 +167,21 @@ describe('Parameter', function () { expect(await parameter.coerceArgument(variable).toPromise()).to.equal(value); }); + + it('should not coerce in strict types mode', async function () { + var value = valueFactory.createString('my value'), + variable = sinon.createStubInstance(Variable); + callStack.isStrictTypesMode.returns(true); + // Stub anyway despite not being expected, so that test would fail gracefully. + typeObject.coerceValue.callsFake(function (value) { + return futureFactory.createPresent(value); + }); + variable.getValue.returns(value); + createParameter(false); + + expect(await parameter.coerceArgument(variable).toPromise()).to.equal(value); + expect(typeObject.coerceValue).not.to.have.been.called; + }); }); describe('getLineNumber()', function () { diff --git a/test/unit/Garbage/ReferenceTreeWalkerTest.js b/test/unit/Garbage/ReferenceTreeWalkerTest.js new file mode 100644 index 00000000..784d46a6 --- /dev/null +++ b/test/unit/Garbage/ReferenceTreeWalkerTest.js @@ -0,0 +1,274 @@ +/* + * PHPCore - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpcore/ + * + * Released under the MIT license + * https://github.com/uniter/phpcore/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + sinon = require('sinon'), + GarbageFactory = require('../../../src/Garbage/GarbageFactory'), + Map = require('core-js-pure/actual/map'), + MarkRoot = require('../../../src/Garbage/MarkRoot'), + ReferenceCache = require('../../../src/Garbage/ReferenceCache'), + ReferenceTreeWalker = require('../../../src/Garbage/ReferenceTreeWalker'), + Value = require('../../../src/Value').sync(); + +describe('Garbage ReferenceTreeWalker', function () { + var garbageFactory, + gcRootToMarkRootMap, + reachableValueToMarkRootMap, + referenceCache, + walker; + + beforeEach(function () { + garbageFactory = new GarbageFactory(); + // Use strong Maps rather than the WeakMaps used in the implementation + // so that we can inspect them during testing. + gcRootToMarkRootMap = new Map(); + reachableValueToMarkRootMap = new Map(); + referenceCache = sinon.createStubInstance(ReferenceCache); + + referenceCache.getGcRootToMarkRootMap.returns(gcRootToMarkRootMap); + referenceCache.getReachableValueToMarkRootMap.returns(reachableValueToMarkRootMap); + + walker = new ReferenceTreeWalker(garbageFactory, referenceCache); + }); + + describe('mark()', function () { + describe('when there are no GC roots', function () { + it('should add no entries to the maps', function () { + walker.mark([]); + + expect(gcRootToMarkRootMap.size).to.equal(0); + expect(reachableValueToMarkRootMap.size).to.equal(0); + }); + }); + + describe('when there is a single GC root with no outgoing structured values', function () { + it('should add the correct entries to the maps', function () { + var gcRoot = sinon.createStubInstance(Value), + markRoot; + gcRoot.getOutgoingValues.returns([]); + + walker.mark([gcRoot]); + + expect(gcRootToMarkRootMap.size).to.equal(1); + markRoot = gcRootToMarkRootMap.get(gcRoot); + expect(markRoot).to.be.an.instanceOf(MarkRoot); + expect(markRoot.getGcRoot()).to.equal(gcRoot); + expect(markRoot.isValid()).to.be.true; + expect(reachableValueToMarkRootMap.size).to.equal(1); + expect(reachableValueToMarkRootMap.get(gcRoot)).to.equal(markRoot); + }); + }); + + describe('when there is a single GC root with two outgoing structured values and one other', function () { + var gcRoot, + outgoingValue1, + outgoingValue2; + + beforeEach(function () { + gcRoot = sinon.createStubInstance(Value); + outgoingValue1 = sinon.createStubInstance(Value); + outgoingValue2 = sinon.createStubInstance(Value); + outgoingValue1.getOutgoingValues.returns([]); + outgoingValue2.getOutgoingValues.returns([]); + + gcRoot.getOutgoingValues.returns([ + outgoingValue1, + outgoingValue2 + ]); + }); + + it('should add the correct entries to the maps when cache is empty', function () { + var markRoot; + + walker.mark([gcRoot]); + + expect(gcRootToMarkRootMap.size).to.equal(1); + markRoot = gcRootToMarkRootMap.get(gcRoot); + expect(markRoot).to.be.an.instanceOf(MarkRoot); + expect(markRoot.getGcRoot()).to.equal(gcRoot); + expect(markRoot.isValid()).to.be.true; + expect(reachableValueToMarkRootMap.size).to.equal(3); + expect(reachableValueToMarkRootMap.get(gcRoot)).to.equal(markRoot); + expect(reachableValueToMarkRootMap.get(outgoingValue1)).to.equal(markRoot); + expect(reachableValueToMarkRootMap.get(outgoingValue2)).to.equal(markRoot); + }); + + it('should add the correct entries to the maps when cache already contains an invalidated MarkRoot', function () { + var invalidatedMarkRoot = sinon.createStubInstance(MarkRoot), + markRoot; + invalidatedMarkRoot.isValid.returns(false); + reachableValueToMarkRootMap.set(outgoingValue1, invalidatedMarkRoot); + + walker.mark([gcRoot]); + + expect(gcRootToMarkRootMap.size).to.equal(1); + markRoot = gcRootToMarkRootMap.get(gcRoot); + expect(markRoot).to.be.an.instanceOf(MarkRoot); + expect(markRoot).not.to.equal(invalidatedMarkRoot); + expect(markRoot.getGcRoot()).to.equal(gcRoot); + expect(markRoot.isValid()).to.be.true; + expect(reachableValueToMarkRootMap.size).to.equal(3); + expect(reachableValueToMarkRootMap.get(gcRoot)).to.equal(markRoot); + expect(reachableValueToMarkRootMap.get(outgoingValue1)).to.equal(markRoot); + expect(reachableValueToMarkRootMap.get(outgoingValue2)).to.equal(markRoot); + }); + }); + + describe('when there are four GC roots and the third one\'s tree eventually references the first two', function () { + var gcRoot1, + gcRoot2, + gcRoot3, + gcRoot4, + gcRoot1Value1, + gcRoot1Value2, + gcRoot1Value3, + gcRoot2Value1, + gcRoot2Value2, + gcRoot3Value1, + gcRoot3Value2, + gcRoot4Value1, + gcRoot4Value2, + markRoot1, + markRoot2, + markRoot3, + markRoot4, + markTree1; + + beforeEach(function () { + gcRoot1 = sinon.createStubInstance(Value); + gcRoot2 = sinon.createStubInstance(Value); + gcRoot3 = sinon.createStubInstance(Value); + gcRoot4 = sinon.createStubInstance(Value); + gcRoot1Value1 = sinon.createStubInstance(Value); + gcRoot1Value2 = sinon.createStubInstance(Value); + gcRoot1Value3 = sinon.createStubInstance(Value); + gcRoot2Value1 = sinon.createStubInstance(Value); + gcRoot2Value2 = sinon.createStubInstance(Value); + gcRoot3Value1 = sinon.createStubInstance(Value); + gcRoot3Value2 = sinon.createStubInstance(Value); + gcRoot4Value1 = sinon.createStubInstance(Value); + gcRoot4Value2 = sinon.createStubInstance(Value); + + gcRoot1.getOutgoingValues.returns([gcRoot1Value1, gcRoot1Value2]); + gcRoot1Value1.getOutgoingValues.returns([]); + gcRoot1Value2.getOutgoingValues.returns([gcRoot1Value3]); + gcRoot1Value3.getOutgoingValues.returns([]); + + gcRoot2.getOutgoingValues.returns([gcRoot2Value1]); + gcRoot2Value1.getOutgoingValues.returns([gcRoot2Value2]); + gcRoot2Value2.getOutgoingValues.returns([]); + + gcRoot3.getOutgoingValues.returns([gcRoot3Value1]); + gcRoot3Value1.getOutgoingValues.returns([gcRoot3Value2]); + // Third GC root's tree ends up referring to both of the first two root's trees. + gcRoot3Value2.getOutgoingValues.returns([gcRoot1Value3, gcRoot2Value1]); + + // Fourth root is left unlinked to the other three. + gcRoot4.getOutgoingValues.returns([gcRoot4Value1]); + gcRoot4Value1.getOutgoingValues.returns([gcRoot4Value2]); + gcRoot4Value2.getOutgoingValues.returns([]); + }); + + it('should add the correct entries to the maps when cache is empty', function () { + walker.mark([gcRoot1, gcRoot2, gcRoot3, gcRoot4]); + + expect(gcRootToMarkRootMap.size).to.equal(4); + markRoot1 = gcRootToMarkRootMap.get(gcRoot1); + expect(markRoot1).to.be.an.instanceOf(MarkRoot); + markTree1 = markRoot1.getTree(); + expect(markRoot1.getGcRoot()).to.equal(gcRoot1); + expect(markRoot1.isValid()).to.be.true; + markRoot2 = gcRootToMarkRootMap.get(gcRoot2); + expect(markRoot2).to.be.an.instanceOf(MarkRoot); + expect(markRoot2.getTree()).to.equal(markTree1); + expect(markRoot2.getGcRoot()).to.equal(gcRoot2); + expect(markRoot2.isValid()).to.be.true; + markRoot3 = gcRootToMarkRootMap.get(gcRoot3); + expect(markRoot3).to.be.an.instanceOf(MarkRoot); + expect(markRoot3.getTree()).to.equal(markTree1); + expect(markRoot3.getGcRoot()).to.equal(gcRoot3); + expect(markRoot3.isValid()).to.be.true; + markRoot4 = gcRootToMarkRootMap.get(gcRoot4); + expect(markRoot4).to.be.an.instanceOf(MarkRoot); + expect(markRoot4.getTree()).not.to.equal(markTree1); + expect(markRoot4.getGcRoot()).to.equal(gcRoot4); + expect(markRoot4.isValid()).to.be.true; + expect(reachableValueToMarkRootMap.size).to.equal(13); + + expect(reachableValueToMarkRootMap.get(gcRoot1)).to.equal(markRoot1); + expect(reachableValueToMarkRootMap.get(gcRoot1Value1)).to.equal(markRoot1); + expect(reachableValueToMarkRootMap.get(gcRoot1Value2)).to.equal(markRoot1); + expect(reachableValueToMarkRootMap.get(gcRoot1Value3)).to.equal(markRoot1); + + expect(reachableValueToMarkRootMap.get(gcRoot2)).to.equal(markRoot2); + expect(reachableValueToMarkRootMap.get(gcRoot2Value1)).to.equal(markRoot2); + expect(reachableValueToMarkRootMap.get(gcRoot2Value2)).to.equal(markRoot2); + + expect(reachableValueToMarkRootMap.get(gcRoot3)).to.equal(markRoot3); + expect(reachableValueToMarkRootMap.get(gcRoot3Value1)).to.equal(markRoot3); + expect(reachableValueToMarkRootMap.get(gcRoot3Value2)).to.equal(markRoot3); + + expect(reachableValueToMarkRootMap.get(gcRoot4)).to.equal(markRoot4); + expect(reachableValueToMarkRootMap.get(gcRoot4Value1)).to.equal(markRoot4); + expect(reachableValueToMarkRootMap.get(gcRoot4Value2)).to.equal(markRoot4); + }); + + it('should add the correct entries to the maps, leaving valid existing entries alone', function () { + var existingValidMarkRoot = sinon.createStubInstance(MarkRoot); + existingValidMarkRoot.isValid.returns(true); + gcRootToMarkRootMap.set(gcRoot4, existingValidMarkRoot); + reachableValueToMarkRootMap.set(gcRoot4, existingValidMarkRoot); + reachableValueToMarkRootMap.set(gcRoot4Value1, existingValidMarkRoot); + reachableValueToMarkRootMap.set(gcRoot4Value2, existingValidMarkRoot); + + walker.mark([gcRoot1, gcRoot2, gcRoot3, gcRoot4]); + + expect(gcRootToMarkRootMap.size).to.equal(4); + markRoot1 = gcRootToMarkRootMap.get(gcRoot1); + expect(markRoot1).to.be.an.instanceOf(MarkRoot); + markTree1 = markRoot1.getTree(); + expect(markRoot1.getGcRoot()).to.equal(gcRoot1); + expect(markRoot1.isValid()).to.be.true; + markRoot2 = gcRootToMarkRootMap.get(gcRoot2); + expect(markRoot2).to.be.an.instanceOf(MarkRoot); + expect(markRoot2.getTree()).to.equal(markTree1); + expect(markRoot2.getGcRoot()).to.equal(gcRoot2); + expect(markRoot2.isValid()).to.be.true; + markRoot3 = gcRootToMarkRootMap.get(gcRoot3); + expect(markRoot3).to.be.an.instanceOf(MarkRoot); + expect(markRoot3.getTree()).to.equal(markTree1); + expect(markRoot3.getGcRoot()).to.equal(gcRoot3); + expect(markRoot3.isValid()).to.be.true; + markRoot4 = gcRootToMarkRootMap.get(gcRoot4); + expect(markRoot4).to.equal(existingValidMarkRoot); + expect(reachableValueToMarkRootMap.size).to.equal(13); + + expect(reachableValueToMarkRootMap.get(gcRoot1)).to.equal(markRoot1); + expect(reachableValueToMarkRootMap.get(gcRoot1Value1)).to.equal(markRoot1); + expect(reachableValueToMarkRootMap.get(gcRoot1Value2)).to.equal(markRoot1); + expect(reachableValueToMarkRootMap.get(gcRoot1Value3)).to.equal(markRoot1); + + expect(reachableValueToMarkRootMap.get(gcRoot2)).to.equal(markRoot2); + expect(reachableValueToMarkRootMap.get(gcRoot2Value1)).to.equal(markRoot2); + expect(reachableValueToMarkRootMap.get(gcRoot2Value2)).to.equal(markRoot2); + + expect(reachableValueToMarkRootMap.get(gcRoot3)).to.equal(markRoot3); + expect(reachableValueToMarkRootMap.get(gcRoot3Value1)).to.equal(markRoot3); + expect(reachableValueToMarkRootMap.get(gcRoot3Value2)).to.equal(markRoot3); + + // Existing MarkRoot should be left alone. + expect(reachableValueToMarkRootMap.get(gcRoot4)).to.equal(existingValidMarkRoot); + expect(reachableValueToMarkRootMap.get(gcRoot4Value1)).to.equal(existingValidMarkRoot); + expect(reachableValueToMarkRootMap.get(gcRoot4Value2)).to.equal(existingValidMarkRoot); + }); + }); + }); +}); diff --git a/test/unit/Load/IncluderTest.js b/test/unit/Load/IncluderTest.js index 9716d502..b9f807cf 100644 --- a/test/unit/Load/IncluderTest.js +++ b/test/unit/Load/IncluderTest.js @@ -75,6 +75,59 @@ describe('Includer', function () { ); }); + describe('hasModuleBeenIncluded()', function () { + var callInclude; + + beforeEach(function () { + var includeOption = sinon.stub(); + + optionSet.getOption + .withArgs('include') + .returns(includeOption); + + callInclude = function (includedPath, type, errorLevel, options) { + return includer.include( + type || 'include', + errorLevel || PHPError.E_WARNING, + environment, + module, + includedPath, + enclosingScope, + options || {} + ).yieldSync(); + }; + }); + + it('should return true when a module has been included once', function () { + callInclude('/my/included_path.php'); + + expect(includer.hasModuleBeenIncluded('/my/included_path.php')).to.be.true; + }); + + it('should return true when a module has been included multiple times', function () { + callInclude('/my/included_path.php'); + callInclude('/my/included_path.php'); // Second include of the same module + + expect(includer.hasModuleBeenIncluded('/my/included_path.php')).to.be.true; + }); + + it('should resolve the include path', function () { + callInclude('/my/stuff/here/../../included_path.php'); + + expect(includer.hasModuleBeenIncluded('/my/included_path.php')).to.be.true; + }); + + it('should resolve the given path', function () { + callInclude('/my/included_path.php'); + + expect(includer.hasModuleBeenIncluded('/my/stuff/here/../../included_path.php')).to.be.true; + }); + + it('should return false when a module has not been included', function () { + expect(includer.hasModuleBeenIncluded('/my/included_path.php')).to.be.false; + }); + }); + describe('include()', function () { var callInclude; @@ -286,45 +339,4 @@ describe('Includer', function () { }); }); }); - - describe('hasModuleBeenIncluded()', function () { - var callInclude; - - beforeEach(function () { - var includeOption = sinon.stub(); - - optionSet.getOption - .withArgs('include') - .returns(includeOption); - - callInclude = function (includedPath, type, errorLevel, options) { - return includer.include( - type || 'include', - errorLevel || PHPError.E_WARNING, - environment, - module, - includedPath, - enclosingScope, - options || {} - ).yieldSync(); - }; - }); - - it('should return true when a module has been included once', function () { - callInclude('/my/included_path.php'); - - expect(includer.hasModuleBeenIncluded('/my/included_path.php')).to.be.true; - }); - - it('should return true when a module has been included multiple times', function () { - callInclude('/my/included_path.php'); - callInclude('/my/included_path.php'); // Second include of the same module - - expect(includer.hasModuleBeenIncluded('/my/included_path.php')).to.be.true; - }); - - it('should return false when a module has not been included', function () { - expect(includer.hasModuleBeenIncluded('/my/included_path.php')).to.be.false; - }); - }); }); diff --git a/test/unit/Load/LoaderTest.async.js b/test/unit/Load/LoaderTest.async.js index 5f659e91..1b8ccf1f 100644 --- a/test/unit/Load/LoaderTest.async.js +++ b/test/unit/Load/LoaderTest.async.js @@ -120,6 +120,40 @@ describe('Loader (async mode)', function () { expect(controlScope.isNestingCoroutine()).to.be.true; }); + it('should allow the load callback to enter a new Coroutine', async function () { + var enteredCoroutine = null; + + await loader.load( + 'include', + '/path/to/my/module.php', + {}, + environment, + module, + enclosingScope, + function (path, promise) { + var stubModule = sinon.stub(); + stubModule.returns(subEngine); + + // Pause before resolving, to test async behaviour. + state.queueMicrotask(function () { + var stubModule = sinon.stub(); + stubModule.returns(subEngine); + + // Enter a new coroutine. + promise.newCoroutine(); + + enteredCoroutine = controlScope.getCoroutine(); + + promise.resolve(stubModule); + }); + } + ) + .toPromise(); + + expect(controlScope.getCoroutine()).not.to.equal(enteredCoroutine); + expect(enteredCoroutine).not.to.be.null; + }); + it('should throw an error with the error when the load callback rejects with a normal JS error', function () { return expect( loader.load( @@ -149,6 +183,7 @@ describe('Loader (async mode)', function () { globalNamespace.getClass.withArgs('ParseError') .returns(futureFactory.createPresent(parseErrorClassObject)); parseErrorClassObject.instantiate.returns(parseErrorObjectValue); + parseErrorObjectValue.next.yields(parseErrorObjectValue); return loader.load( 'include', diff --git a/test/unit/Load/LoaderTest.sync.js b/test/unit/Load/LoaderTest.sync.js index 1a2203c9..f7b30b42 100644 --- a/test/unit/Load/LoaderTest.sync.js +++ b/test/unit/Load/LoaderTest.sync.js @@ -262,6 +262,7 @@ describe('Loader (sync mode)', function () { globalNamespace.getClass.withArgs('ParseError') .returns(futureFactory.createPresent(parseErrorClassObject)); parseErrorClassObject.instantiate.returns(parseErrorObjectValue); + parseErrorObjectValue.next.yields(parseErrorObjectValue); try { loader.load( diff --git a/test/unit/ModuleTest.js b/test/unit/ModuleTest.js index a71d173f..d79a3348 100644 --- a/test/unit/ModuleTest.js +++ b/test/unit/ModuleTest.js @@ -61,6 +61,16 @@ describe('Module', function () { }); }); + describe('enableStrictTypes()', function () { + it('should enable strict-types mode for the module', function () { + createModule('/my/module_here.php', true); + + module.enableStrictTypes(); + + expect(module.isStrictTypesMode()).to.be.true; + }); + }); + describe('getFilePath()', function () { it('should return the path to the file the module was defined in when specified', function () { createModule('/path/to/my/module.php'); @@ -82,4 +92,12 @@ describe('Module', function () { expect(module.getTopLevelNamespaceScope()).to.equal(topLevelNamespaceScope); }); }); + + describe('isStrictTypesMode()', function () { + it('should return false by default', function () { + createModule('/my/module_here.php', true); + + expect(module.isStrictTypesMode()).to.be.false; + }); + }); }); diff --git a/test/unit/NamespaceScopeTest.js b/test/unit/NamespaceScopeTest.js index 2dcbabc7..078c86e4 100644 --- a/test/unit/NamespaceScopeTest.js +++ b/test/unit/NamespaceScopeTest.js @@ -42,6 +42,7 @@ describe('NamespaceScope', function () { throw new Error('PHP Fatal error: [' + translationKey + '] ' + JSON.stringify(placeholderVariables || {})); }); globalNamespace.hasClass.returns(false); + module.isStrictTypesMode.returns(false); scope = new NamespaceScope( scopeFactory, @@ -81,6 +82,14 @@ describe('NamespaceScope', function () { }); }); + describe('enableStrictTypes()', function () { + it('should enable strict-types mode for the module', function () { + scope.enableStrictTypes(); + + expect(module.enableStrictTypes).to.have.been.calledOnce; + }); + }); + describe('getClass()', function () { it('should support fetching a class with no imports involved', async function () { var myClass = sinon.createStubInstance(Class); @@ -323,6 +332,18 @@ describe('NamespaceScope', function () { }); }); + describe('isStrictTypesMode()', function () { + it('should return true when the module is in strict-types mode', function () { + module.isStrictTypesMode.returns(true); + + expect(scope.isStrictTypesMode()).to.be.true; + }); + + it('should return false when the module is in weak type-checking mode', function () { + expect(scope.isStrictTypesMode()).to.be.false; + }); + }); + describe('resolveClass()', function () { it('should support resolving a class with no imports involved', function () { var result = scope.resolveClass('MyClass'); diff --git a/test/unit/PHPStateTest.js b/test/unit/PHPStateTest.js index 7a4f296a..de7d5e5f 100644 --- a/test/unit/PHPStateTest.js +++ b/test/unit/PHPStateTest.js @@ -1064,6 +1064,42 @@ describe('PHPState', function () { }); }); + describe('getMode()', function () { + it('should return "async" when expected', function () { + expect(state.getMode()).to.equal('async'); + }); + + it('should return "psync" when expected', function () { + state = new PHPState( + runtime, + environmentFactory, + globalStackHooker, + installedBuiltinTypes, + stdin, + stdout, + stderr, + 'psync' + ); + + expect(state.getMode()).to.equal('psync'); + }); + + it('should return "sync" when expected', function () { + state = new PHPState( + runtime, + environmentFactory, + globalStackHooker, + installedBuiltinTypes, + stdin, + stdout, + stderr, + 'sync' + ); + + expect(state.getMode()).to.equal('sync'); + }); + }); + describe('getOutput()', function () { it('should return an Output', function () { expect(state.getOutput()).to.be.an.instanceOf(Output); diff --git a/test/unit/Reference/PropertyTest.js b/test/unit/Reference/PropertyTest.js index b52a7c2d..a9d99baf 100644 --- a/test/unit/Reference/PropertyTest.js +++ b/test/unit/Reference/PropertyTest.js @@ -20,6 +20,7 @@ var expect = require('chai').expect, MethodSpec = require('../../../src/MethodSpec'), ObjectValue = require('../../../src/Value/Object').sync(), PHPError = phpCommon.PHPError, + Present = require('../../../src/Control/Present'), Reference = require('../../../src/Reference/Reference'), ReferenceSlot = require('../../../src/Reference/ReferenceSlot'), Variable = require('../../../src/Variable').sync(); @@ -622,14 +623,14 @@ describe('PropertyReference', function () { }); describe('when the class does not define magic __unset(...)', function () { - it('should return an unwrapped Future when defined', async function () { + it('should return an unwrapped Present when defined', async function () { property.initialise(propertyValue); - expect(property.unset()).to.be.an.instanceOf(Future); + expect(property.unset()).to.be.an.instanceOf(Present); }); - it('should return an unwrapped Future when undefined', async function () { - expect(property.unset()).to.be.an.instanceOf(Future); + it('should return an unwrapped Present when undefined', async function () { + expect(property.unset()).to.be.an.instanceOf(Present); }); }); diff --git a/test/unit/Reference/ReferenceSlotTest.js b/test/unit/Reference/ReferenceSlotTest.js index 02e235f3..e37317f5 100644 --- a/test/unit/Reference/ReferenceSlotTest.js +++ b/test/unit/Reference/ReferenceSlotTest.js @@ -65,15 +65,6 @@ describe('ReferenceSlot', function () { }); }); - describe('getForAssignment()', function () { - it('should return the value of the slot', function () { - var result = sinon.createStubInstance(Value); - reference.setValue(result); - - expect(reference.getForAssignment()).to.equal(result); - }); - }); - describe('getNative()', function () { it('should return the native value of the slot', function () { reference.setValue(valueFactory.createString('the native value of my var')); diff --git a/test/unit/Reference/ReferenceSnapshotTest.js b/test/unit/Reference/ReferenceSnapshotTest.js index b560c96c..27e2991a 100644 --- a/test/unit/Reference/ReferenceSnapshotTest.js +++ b/test/unit/Reference/ReferenceSnapshotTest.js @@ -161,6 +161,15 @@ describe('ReferenceSnapshot', function () { expect(wrappedReference.raiseUndefined).to.have.been.calledOnce; }); + + it('should not raise undefined for the snapshotted reference on subsequent calls', function () { + createSnapshot(); + + snapshot.getValue(); + snapshot.getValue(); + + expect(wrappedReference.raiseUndefined).to.have.been.calledOnce; + }); }); }); @@ -217,6 +226,78 @@ describe('ReferenceSnapshot', function () { }); }); + describe('isSet()', function () { + it('should return true when a set value was snapshotted', async function () { + createSnapshot(); + + expect(await snapshot.isSet().toPromise()).to.be.true; + }); + + it('should return false when an unset value was snapshotted', async function () { + value = valueFactory.createNull(); + createSnapshot(); + + expect(await snapshot.isSet().toPromise()).to.be.false; + }); + + it('should return true when a set reference was snapshotted', async function () { + reference = sinon.createStubInstance(Reference); + reference.isSet.returns(futureFactory.createPresent(true)); + value = null; + createSnapshot(); + + expect(await snapshot.isSet().toPromise()).to.be.true; + }); + + it('should return false when an unset reference was snapshotted', async function () { + reference = sinon.createStubInstance(Reference); + reference.isSet.returns(futureFactory.createPresent(false)); + value = null; + createSnapshot(); + + expect(await snapshot.isSet().toPromise()).to.be.false; + }); + + it('should return true when using a synthetic reference that was set', async function () { + var syntheticReference = sinon.createStubInstance(Reference); + syntheticReference.isSet.returns(futureFactory.createPresent(true)); + value = null; + wrappedReference.getReference.returns(syntheticReference); + createSnapshot(); + snapshot.getReference(); + + expect(await snapshot.isSet().toPromise()).to.be.true; + }); + + it('should return false when using a synthetic reference that was unset', async function () { + var syntheticReference = sinon.createStubInstance(Reference); + syntheticReference.isSet.returns(futureFactory.createPresent(false)); + value = null; + wrappedReference.getReference.returns(syntheticReference); + createSnapshot(); + snapshot.getReference(); + + expect(await snapshot.isSet().toPromise()).to.be.false; + }); + + it('should return false when not defined', async function () { + value = null; + createSnapshot(); + + expect(await snapshot.isSet().toPromise()).to.be.false; + }); + }); + + describe('raiseUndefined()', function () { + it('should raise undefined via the wrapped reference', function () { + createSnapshot(); + + snapshot.raiseUndefined(); + + expect(wrappedReference.raiseUndefined).to.have.been.calledOnce; + }); + }); + describe('setValue()', function () { it('should set the given value on the snapshotted reference', function () { var newValue = valueFactory.createString('my new value'); diff --git a/test/unit/Reference/UndeclaredStaticPropertyReferenceTest.js b/test/unit/Reference/UndeclaredStaticPropertyReferenceTest.js index 2a43752e..e024d85d 100644 --- a/test/unit/Reference/UndeclaredStaticPropertyReferenceTest.js +++ b/test/unit/Reference/UndeclaredStaticPropertyReferenceTest.js @@ -159,6 +159,16 @@ describe('UndeclaredStaticPropertyReference', function () { }); }); + describe('raiseUndefined()', function () { + it('should raise an error', function () { + expect(function () { + reference.raiseUndefined(); + }).to.throw( + 'Fake PHP Fatal error for #core.undeclared_static_property with {"propertyName":"myProperty"}' + ); + }); + }); + describe('toPromise()', function () { it('should return a Promise that resolves to the UndeclaredStaticPropertyReference', async function () { expect(await reference.toPromise()).to.equal(reference); diff --git a/test/unit/ScopeTest.js b/test/unit/ScopeTest.js index 626c04d3..6b1c1c3f 100644 --- a/test/unit/ScopeTest.js +++ b/test/unit/ScopeTest.js @@ -13,6 +13,7 @@ var expect = require('chai').expect, phpCommon = require('phpcommon'), sinon = require('sinon'), tools = require('./tools'), + CacheInvalidator = require('../../src/Garbage/CacheInvalidator'), CallStack = require('../../src/CallStack'), Class = require('../../src/Class').sync(), Closure = require('../../src/Closure').sync(), @@ -48,6 +49,7 @@ describe('Scope', function () { flow, functionSpecFactory, futureFactory, + garbageCacheInvalidator, globalNamespace, globalScope, parentClass, @@ -77,6 +79,7 @@ describe('Scope', function () { flow = state.getFlow(); functionSpecFactory = sinon.createStubInstance(FunctionSpecFactory); futureFactory = state.getFutureFactory(); + garbageCacheInvalidator = sinon.createStubInstance(CacheInvalidator); globalNamespace = sinon.createStubInstance(Namespace); globalScope = sinon.createStubInstance(Scope); parentClass = null; @@ -95,7 +98,15 @@ describe('Scope', function () { closureFactory.create.returns(closure); variableFactory.createVariable.callsFake(function (variableName) { - return new Variable(callStack, valueFactory, referenceFactory, futureFactory, flow, variableName); + return new Variable( + callStack, + valueFactory, + referenceFactory, + futureFactory, + flow, + garbageCacheInvalidator, + variableName + ); }); controlScope.enterCoroutine.resetHistory(); @@ -687,6 +698,24 @@ describe('Scope', function () { }); }); + describe('getVariables()', function () { + it('should return an array of all variables defined for this scope', function () { + var myVariable, + result, + yourVariable; + createScope(); + myVariable = scope.defineVariable('myVar'); + yourVariable = scope.defineVariable('yourVar'); + + result = scope.getVariables(); + + expect(result).to.have.length(3); + expect(result[0].getName()).to.equal('this', '$this is always defined first'); + expect(result[1]).to.equal(myVariable); + expect(result[2]).to.equal(yourVariable); + }); + }); + describe('hasVariable()', function () { it('should return true when the specified variable is defined', function () { createScope(); @@ -744,7 +773,7 @@ describe('Scope', function () { }); it('should define variable in current scope as reference to new static variable on first call', function () { - var staticVariable = new Variable(callStack, valueFactory, referenceFactory, futureFactory, 'myVar'), + var staticVariable = variableFactory.createVariable('myVar'), value = valueFactory.createString('my string'); variableFactory.createVariable.withArgs('myVar').returns(staticVariable); staticVariable.setValue(value); @@ -756,7 +785,7 @@ describe('Scope', function () { }); it('should define variable in current scope as reference to same static variable on second call', function () { - var existingStaticVariable = new Variable(callStack, valueFactory, referenceFactory, futureFactory, 'myVar'), + var existingStaticVariable = variableFactory.createVariable('myVar'), value = valueFactory.createString('my string'); existingStaticVariable.setValue(value); currentFunction.staticVariables = {myVar: existingStaticVariable}; @@ -803,6 +832,8 @@ describe('Scope', function () { var caughtError = null, errorClassObject = sinon.createStubInstance(Class), errorValue = sinon.createStubInstance(ObjectValue); + errorValue.next.yields(errorValue); + errorValue.toPromise.returns(Promise.resolve(errorValue)); globalNamespace.getClass .withArgs('MySubError') .returns(futureFactory.createPresent(errorClassObject)); diff --git a/test/unit/Value/ArrayTest.js b/test/unit/Value/ArrayTest.js index b3966106..92794dad 100644 --- a/test/unit/Value/ArrayTest.js +++ b/test/unit/Value/ArrayTest.js @@ -973,6 +973,22 @@ describe('ArrayValue', function () { }); }); + describe('getOutgoingValues()', function () { + it('should return an array of all structured element values', function () { + var structuredValue = factory.createArray([ + new KeyValuePair(factory.createString('myKey'), factory.createString('my value')) + ]), + values; + value.getElementByKey(factory.createString('structuredEl')).setValue(structuredValue); + + values = value.getOutgoingValues(); + + expect(values).to.have.length(1); + // Array will have been copied on assignment, so we cannot compare the value objects themselves. + expect(values[0].getNative()).to.deep.equal(structuredValue.getNative()); + }); + }); + describe('getProxy()', function () { it('should unwrap to a native array when the array has no non-numeric keys', function () { var result; @@ -1304,6 +1320,12 @@ describe('ArrayValue', function () { }); }); + describe('isStructured()', function () { + it('should return true', function () { + expect(value.isStructured()).to.be.true; + }); + }); + describe('isTheClassOfArray()', function () { it('should raise a fatal error', function () { var classValue = sinon.createStubInstance(ArrayValue); diff --git a/test/unit/Value/BarewordStringTest.js b/test/unit/Value/BarewordStringTest.js index aa22381b..a891d596 100644 --- a/test/unit/Value/BarewordStringTest.js +++ b/test/unit/Value/BarewordStringTest.js @@ -237,6 +237,8 @@ describe('BarewordStringValue', function () { it('should fetch the constant from the class', async function () { var classObject = sinon.createStubInstance(Class), resultValue = sinon.createStubInstance(Value); + resultValue.next.yields(resultValue); + resultValue.toPromise.returns(Promise.resolve(resultValue)); namespaceScope.getClass .withArgs('This\\SubSpace\\MyClass') .returns(futureFactory.createPresent(classObject)); @@ -275,6 +277,14 @@ describe('BarewordStringValue', function () { }); }); + describe('getOutgoingValues()', function () { + it('should return an empty array as scalars cannot refer to anything', function () { + createValue('mybarewordstring'); + + expect(value.getOutgoingValues()).to.deep.equal([]); + }); + }); + describe('getReference()', function () { it('should throw an error', function () { createValue('mybarewordstring'); @@ -289,6 +299,8 @@ describe('BarewordStringValue', function () { it('should fetch the property\'s value from the class', async function () { var classObject = sinon.createStubInstance(Class), resultValue = sinon.createStubInstance(Value); + resultValue.next.yields(resultValue); + resultValue.toPromise.returns(Promise.resolve(resultValue)); namespaceScope.getClass .withArgs('This\\SubSpace\\MyClass') .returns(futureFactory.createPresent(classObject)); @@ -341,6 +353,8 @@ describe('BarewordStringValue', function () { .withArgs('My\\Space\\MyClass') .returns(futureFactory.createPresent(classObject)); newObjectValue = sinon.createStubInstance(ObjectValue); + newObjectValue.next.yields(newObjectValue); + newObjectValue.toPromise.returns(Promise.resolve(newObjectValue)); classObject.instantiate.returns(newObjectValue); }); @@ -385,6 +399,14 @@ describe('BarewordStringValue', function () { }); }); + describe('isStructured()', function () { + it('should return false', function () { + createValue('mybarewordstring'); + + expect(value.isStructured()).to.be.false; + }); + }); + describe('isTheClassOfObject()', function () { var namespace; diff --git a/test/unit/Value/BooleanTest.js b/test/unit/Value/BooleanTest.js index 1c315365..a3ee02aa 100644 --- a/test/unit/Value/BooleanTest.js +++ b/test/unit/Value/BooleanTest.js @@ -648,6 +648,12 @@ describe('BooleanValue', function () { }); }); + describe('getOutgoingValues()', function () { + it('should return an empty array as scalars cannot refer to anything', function () { + expect(value.getOutgoingValues()).to.deep.equal([]); + }); + }); + describe('getProxy()', function () { it('should return true when true', function () { createValue(true); @@ -803,6 +809,12 @@ describe('BooleanValue', function () { }); }); + describe('isStructured()', function () { + it('should return false', function () { + expect(value.isStructured()).to.be.false; + }); + }); + describe('isTheClassOfArray()', function () { it('should raise a fatal error', function () { var classValue = sinon.createStubInstance(ArrayValue); diff --git a/test/unit/Value/FloatTest.js b/test/unit/Value/FloatTest.js index 53351ad1..e0217db6 100644 --- a/test/unit/Value/FloatTest.js +++ b/test/unit/Value/FloatTest.js @@ -692,6 +692,12 @@ describe('FloatValue', function () { }); }); + describe('getOutgoingValues()', function () { + it('should return an empty array as scalars cannot refer to anything', function () { + expect(value.getOutgoingValues()).to.deep.equal([]); + }); + }); + describe('getProxy()', function () { it('should return 21.5 when expected', function () { createValue(21.5); @@ -852,6 +858,12 @@ describe('FloatValue', function () { }); }); + describe('isStructured()', function () { + it('should return false', function () { + expect(value.isStructured()).to.be.false; + }); + }); + describe('isTheClassOfArray()', function () { it('should raise a fatal error', function () { var classValue = sinon.createStubInstance(ArrayValue); diff --git a/test/unit/Value/IntegerTest.js b/test/unit/Value/IntegerTest.js index 583e9b3c..8d356571 100644 --- a/test/unit/Value/IntegerTest.js +++ b/test/unit/Value/IntegerTest.js @@ -715,6 +715,12 @@ describe('IntegerValue', function () { }); }); + describe('getOutgoingValues()', function () { + it('should return an empty array as scalars cannot refer to anything', function () { + expect(value.getOutgoingValues()).to.deep.equal([]); + }); + }); + describe('getProxy()', function () { it('should return 27 when expected', function () { createValue(27); @@ -1069,6 +1075,12 @@ describe('IntegerValue', function () { }); }); + describe('isStructured()', function () { + it('should return false', function () { + expect(value.isStructured()).to.be.false; + }); + }); + describe('isTheClassOfArray()', function () { it('should raise a fatal error', function () { var classValue = sinon.createStubInstance(ArrayValue); diff --git a/test/unit/Value/MissingTest.js b/test/unit/Value/MissingTest.js index dd604506..b5a75de5 100644 --- a/test/unit/Value/MissingTest.js +++ b/test/unit/Value/MissingTest.js @@ -37,6 +37,12 @@ describe('MissingValue', function () { value = new MissingValue(factory, referenceFactory, futureFactory, callStack, flow); }); + describe('getOutgoingValues()', function () { + it('should return an empty array', function () { + expect(value.getOutgoingValues()).to.deep.equal([]); + }); + }); + describe('getType()', function () { it('should return "null"', function () { expect(value.getType()).to.equal('null'); @@ -48,4 +54,10 @@ describe('MissingValue', function () { expect(value.getUnderlyingType()).to.equal('missing'); }); }); + + describe('isStructured()', function () { + it('should return false', function () { + expect(value.isStructured()).to.be.false; + }); + }); }); diff --git a/test/unit/Value/NullTest.js b/test/unit/Value/NullTest.js index 90a736bd..b8d508c8 100644 --- a/test/unit/Value/NullTest.js +++ b/test/unit/Value/NullTest.js @@ -587,6 +587,12 @@ describe('NullValue', function () { }); }); + describe('getOutgoingValues()', function () { + it('should return an empty array as scalars cannot refer to anything', function () { + expect(value.getOutgoingValues()).to.deep.equal([]); + }); + }); + describe('getProxy()', function () { it('should return null', function () { expect(value.getProxy()).to.be.null; @@ -706,6 +712,12 @@ describe('NullValue', function () { }); }); + describe('isStructured()', function () { + it('should return false', function () { + expect(value.isStructured()).to.be.false; + }); + }); + describe('isTheClassOfArray()', function () { it('should raise a fatal error', function () { var classValue = sinon.createStubInstance(ArrayValue); diff --git a/test/unit/Value/ObjectTest.js b/test/unit/Value/ObjectTest.js index 947814fe..cf720e8a 100644 --- a/test/unit/Value/ObjectTest.js +++ b/test/unit/Value/ObjectTest.js @@ -23,6 +23,7 @@ var expect = require('chai').expect, FunctionSpec = require('../../../src/Function/FunctionSpec'), GeneratorIterator = require('../../../src/Iterator/GeneratorIterator'), IntegerValue = require('../../../src/Value/Integer').sync(), + KeyValuePair = require('../../../src/KeyValuePair'), MethodSpec = require('../../../src/MethodSpec'), Namespace = require('../../../src/Namespace').sync(), NamespaceScope = require('../../../src/NamespaceScope').sync(), @@ -1663,6 +1664,8 @@ describe('ObjectValue', function () { iteratorValue.classIs.withArgs('Iterator').returns(true); iteratorValue.classIs.returns(false); iteratorValue.getType.returns('object'); + iteratorValue.next.yields(iteratorValue); + iteratorValue.toPromise.returns(Promise.resolve(iteratorValue)); classObject.callMethod.withArgs('getIterator') .returns(futureFactory.createPresent(iteratorValue)); @@ -1678,6 +1681,8 @@ describe('ObjectValue', function () { iteratorValue.classIs.withArgs('Iterator').returns(true); iteratorValue.classIs.returns(false); iteratorValue.getType.returns('object'); + iteratorValue.next.yields(iteratorValue); + iteratorValue.toPromise.returns(Promise.resolve(iteratorValue)); classObject.callMethod.withArgs('getIterator') .returns(futureFactory.createPresent(iteratorValue)); @@ -1862,6 +1867,21 @@ describe('ObjectValue', function () { }); }); + describe('getOutgoingValues()', function () { + it('should return an array of all structured property values', function () { + var structuredValue = factory.createArray([ + new KeyValuePair(factory.createString('myKey'), factory.createString('my value')) + ]), + values; + value.getInstancePropertyByName(factory.createString('structuredProp')).setValue(structuredValue); + + values = value.getOutgoingValues(); + + expect(values).to.have.length(1); + expect(values[0].getNative()).to.deep.equal(structuredValue.getNative()); + }); + }); + describe('getPropertyNames()', function () { it('should return all instance property names as native strings', function () { expect(value.getPropertyNames()).to.deep.equal([ @@ -2244,6 +2264,12 @@ describe('ObjectValue', function () { }); }); + describe('isStructured()', function () { + it('should return true', function () { + expect(value.isStructured()).to.be.true; + }); + }); + describe('isTheClassOfArray()', function () { it('should return bool(false)', function () { var classValue = sinon.createStubInstance(ArrayValue), diff --git a/test/unit/Value/ResourceTest.js b/test/unit/Value/ResourceTest.js index 7da1d9af..16d1f4a9 100644 --- a/test/unit/Value/ResourceTest.js +++ b/test/unit/Value/ResourceTest.js @@ -296,6 +296,12 @@ describe('ResourceValue', function () { }); }); + describe('getOutgoingValues()', function () { + it('should return an empty array as resources cannot refer to anything', function () { + expect(value.getOutgoingValues()).to.deep.equal([]); + }); + }); + describe('getProxy()', function () { it('should return the resource ID', function () { expect(value.getProxy()).to.equal(1234); @@ -418,6 +424,12 @@ describe('ResourceValue', function () { }); }); + describe('isStructured()', function () { + it('should return false', function () { + expect(value.isStructured()).to.be.false; + }); + }); + describe('isTheClassOfArray()', function () { it('should raise a fatal error', function () { var classValue = sinon.createStubInstance(ArrayValue); diff --git a/test/unit/Value/StringTest.js b/test/unit/Value/StringTest.js index 2a7e6555..5b0793da 100644 --- a/test/unit/Value/StringTest.js +++ b/test/unit/Value/StringTest.js @@ -1246,6 +1246,14 @@ describe('StringValue', function () { }); }); + describe('getOutgoingValues()', function () { + it('should return an empty array as scalars cannot refer to anything', function () { + createValue('my string'); + + expect(value.getOutgoingValues()).to.deep.equal([]); + }); + }); + describe('getProxy()', function () { it('should return "hello" when expected', function () { createValue('hello'); @@ -1473,6 +1481,8 @@ describe('StringValue', function () { globalNamespace.getClass.withArgs('My\\Space\\MyClass') .returns(futureFactory.createPresent(classObject)); newObjectValue = sinon.createStubInstance(ObjectValue); + newObjectValue.next.yields(newObjectValue); + newObjectValue.toPromise.returns(Promise.resolve(newObjectValue)); classObject.instantiate.returns(newObjectValue); }); @@ -1660,6 +1670,14 @@ describe('StringValue', function () { }); }); + describe('isStructured()', function () { + it('should return false', function () { + createValue('my string'); + + expect(value.isStructured()).to.be.false; + }); + }); + describe('isTheClassOfArray()', function () { beforeEach(function () { createValue('a string'); diff --git a/test/unit/ValueFactoryTest.js b/test/unit/ValueFactoryTest.js index 159b3e9d..cb02a600 100644 --- a/test/unit/ValueFactoryTest.js +++ b/test/unit/ValueFactoryTest.js @@ -18,6 +18,7 @@ var expect = require('chai').expect, Call = require('../../src/Call'), CallFactory = require('../../src/CallFactory'), CallStack = require('../../src/CallStack'), + Chainifier = require('../../src/Control/Chain/Chainifier'), Class = require('../../src/Class').sync(), Closure = require('../../src/Closure').sync(), ArrayIterator = require('../../src/Iterator/ArrayIterator'), @@ -35,6 +36,7 @@ var expect = require('chai').expect, NumericStringParser = require('../../src/Semantics/NumericStringParser'), ObjectValue = require('../../src/Value/Object').sync(), PHPObject = require('../../src/FFI/Value/PHPObject').sync(), + Present = require('../../src/Control/Present'), Translator = phpCommon.Translator, Value = require('../../src/Value').sync(), ValueFactory = require('../../src/ValueFactory').sync(), @@ -43,6 +45,7 @@ var expect = require('chai').expect, describe('ValueFactory', function () { var callFactory, callStack, + chainifier, controlScope, elementProvider, errorPromoter, @@ -52,6 +55,8 @@ describe('ValueFactory', function () { futuresCreated, globalNamespace, numericStringParser, + presentsCreated, + realChainifier, referenceFactory, state, translator, @@ -59,10 +64,14 @@ describe('ValueFactory', function () { beforeEach(function () { callStack = sinon.createStubInstance(CallStack); + chainifier = sinon.createStubInstance(Chainifier); futuresCreated = 0; + presentsCreated = 0; state = tools.createIsolatedState('async', { 'call_stack': callStack, 'future_factory': function (set, get) { + var futureFactory; + function TrackedFuture() { Future.apply(this, arguments); @@ -71,13 +80,25 @@ describe('ValueFactory', function () { util.inherits(TrackedFuture, Future); - return new FutureFactory( + function TrackedPresent() { + Present.apply(this, arguments); + + presentsCreated++; + } + + util.inherits(TrackedPresent, Present); + + futureFactory = new FutureFactory( get('pause_factory'), get('value_factory'), get('control_bridge'), get('control_scope'), - TrackedFuture + TrackedFuture, + TrackedPresent ); + futureFactory.setChainifier(chainifier); + + return futureFactory; } }); callFactory = sinon.createStubInstance(CallFactory); @@ -91,6 +112,11 @@ describe('ValueFactory', function () { translator = sinon.createStubInstance(Translator); elementProvider = new ElementProvider(referenceFactory); valueStorage = new ValueStorage(); + realChainifier = state.getService('chainifier'); + + chainifier.chainify.callsFake(function (value) { + return realChainifier.chainify(value); + }); translator.translate .callsFake(function (translationKey, placeholderVariables) { @@ -453,6 +479,8 @@ describe('ValueFactory', function () { globalNamespace.getClass.withArgs('My\\Stuff\\MyErrorClass') .returns(futureFactory.createPresent(myClassObject)); + objectValue.next.yields(objectValue); + objectValue.toPromise.returns(Promise.resolve(objectValue)); objectValue.getInternalProperty .withArgs('reportsOwnContext') .returns(false); @@ -838,6 +866,23 @@ describe('ValueFactory', function () { expect(controlScope.isNestingCoroutine()).to.be.true; }); + + it('should allow a new Coroutine to be entered', async function () { + var enteredCoroutine = null, + value = factory.createFuture(function (resolve, reject, nestCoroutine, newCoroutine) { + state.queueMicrotask(function () { + newCoroutine(); + enteredCoroutine = controlScope.getCoroutine(); + + resolve(21); + }); + }); + + await value.toPromise(); + + expect(controlScope.getCoroutine()).not.to.equal(enteredCoroutine); + expect(enteredCoroutine).not.to.be.null; + }); }); describe('createFutureChain()', function () { @@ -1008,6 +1053,20 @@ describe('ValueFactory', function () { }); }); + describe('createObjectWithID()', function () { + it('should return a correctly constructed ObjectValue', function () { + var classObject = sinon.createStubInstance(Class), + nativeObject = {myProp: 'my value'}, + value; + + value = factory.createObjectWithID(nativeObject, classObject, 1234); + + expect(value.getType()).to.equal('object'); + expect(value.getObject()).to.equal(nativeObject); + expect(value.getID()).to.equal(1234); + }); + }); + describe('createResource()', function () { it('should return a correctly constructed ResourceValue', function () { var resource = {my: 'resource'}, @@ -1063,6 +1122,8 @@ describe('ValueFactory', function () { globalNamespace.getClass.withArgs('My\\Stuff\\MyErrorClass') .returns(futureFactory.createPresent(myClassObject)); + objectValue.next.yields(objectValue); + objectValue.toPromise.returns(Promise.resolve(objectValue)); objectValue.getInternalProperty .withArgs('reportsOwnContext') .returns(false); @@ -1176,6 +1237,8 @@ describe('ValueFactory', function () { globalNamespace.getClass.withArgs('My\\Stuff\\MyErrorClass') .returns(futureFactory.createPresent(myClassObject)); + objectValue.next.yields(objectValue); + objectValue.toPromise.returns(Promise.resolve(objectValue)); objectValue.getInternalProperty .withArgs('reportsOwnContext') .returns(false); @@ -1278,6 +1341,8 @@ describe('ValueFactory', function () { beforeEach(function () { myClassObject = sinon.createStubInstance(Class); objectValue = sinon.createStubInstance(ObjectValue); + objectValue.next.yields(objectValue); + objectValue.toPromise.returns(Promise.resolve(objectValue)); globalNamespace.getClass.withArgs('My\\Stuff\\MyClass') .returns(futureFactory.createPresent(myClassObject)); myClassObject.getSuperClass.returns(null); diff --git a/test/unit/VariableFactoryTest.js b/test/unit/VariableFactoryTest.js index 217897b3..606b907f 100644 --- a/test/unit/VariableFactoryTest.js +++ b/test/unit/VariableFactoryTest.js @@ -11,6 +11,7 @@ var expect = require('chai').expect, sinon = require('sinon'), + CacheInvalidator = require('../../src/Garbage/CacheInvalidator'), CallStack = require('../../src/CallStack'), Flow = require('../../src/Control/Flow'), FutureFactory = require('../../src/Control/FutureFactory'), @@ -23,6 +24,7 @@ describe('VariableFactory', function () { factory, flow, futureFactory, + garbageCacheInvalidator, referenceFactory, valueFactory, Variable; @@ -31,6 +33,7 @@ describe('VariableFactory', function () { callStack = sinon.createStubInstance(CallStack); flow = sinon.createStubInstance(Flow); futureFactory = sinon.createStubInstance(FutureFactory); + garbageCacheInvalidator = sinon.createStubInstance(CacheInvalidator); referenceFactory = sinon.createStubInstance(ReferenceFactory); valueFactory = sinon.createStubInstance(ValueFactory); Variable = sinon.stub(); @@ -41,7 +44,8 @@ describe('VariableFactory', function () { valueFactory, referenceFactory, futureFactory, - flow + flow, + garbageCacheInvalidator ); }); @@ -56,6 +60,7 @@ describe('VariableFactory', function () { sinon.match.same(referenceFactory), sinon.match.same(futureFactory), sinon.match.same(flow), + sinon.match.same(garbageCacheInvalidator), 'myVar' ); }); diff --git a/test/unit/VariableTest.js b/test/unit/VariableTest.js index 9ada5bd8..94c985fb 100644 --- a/test/unit/VariableTest.js +++ b/test/unit/VariableTest.js @@ -13,9 +13,10 @@ var expect = require('chai').expect, phpCommon = require('phpcommon'), sinon = require('sinon'), tools = require('./tools'), + CacheInvalidator = require('../../src/Garbage/CacheInvalidator'), CallStack = require('../../src/CallStack'), - Future = require('../../src/Control/Future'), PHPError = phpCommon.PHPError, + Present = require('../../src/Control/Present'), Reference = require('../../src/Reference/Reference'), ReferenceSlot = require('../../src/Reference/ReferenceSlot'), StringValue = require('../../src/Value/String').sync(), @@ -25,6 +26,7 @@ describe('Variable', function () { var callStack, flow, futureFactory, + garbageCacheInvalidator, referenceFactory, state, valueFactory, @@ -37,6 +39,7 @@ describe('Variable', function () { }); flow = state.getFlow(); futureFactory = state.getFutureFactory(); + garbageCacheInvalidator = sinon.createStubInstance(CacheInvalidator); referenceFactory = state.getReferenceFactory(); valueFactory = state.getValueFactory(); @@ -48,7 +51,15 @@ describe('Variable', function () { ); }); - variable = new Variable(callStack, valueFactory, referenceFactory, futureFactory, flow, 'myVar'); + variable = new Variable( + callStack, + valueFactory, + referenceFactory, + futureFactory, + flow, + garbageCacheInvalidator, + 'myVar' + ); }); describe('asArrayElement()', function () { @@ -188,7 +199,15 @@ describe('Variable', function () { }); it('should raise a "Using $this when not in object context" error when the variable is $this and the value is not set', function () { - variable = new Variable(callStack, valueFactory, referenceFactory, futureFactory, flow, 'this'); + variable = new Variable( + callStack, + valueFactory, + referenceFactory, + futureFactory, + flow, + garbageCacheInvalidator, + 'this' + ); expect(function () { variable.getValue(); @@ -361,7 +380,15 @@ describe('Variable', function () { describe('raiseUndefined()', function () { it('should raise a "Using $this when not in object context" error when the variable is $this', function () { - variable = new Variable(callStack, valueFactory, referenceFactory, futureFactory, flow, 'this'); + variable = new Variable( + callStack, + valueFactory, + referenceFactory, + futureFactory, + flow, + garbageCacheInvalidator, + 'this' + ); expect(function () { variable.raiseUndefined(); @@ -424,6 +451,32 @@ describe('Variable', function () { expect(resultValue.getType()).to.equal('string'); expect(resultValue.getNative()).to.equal('my assigned value'); }); + + it('should mark the previous and new values of the variable for garbage invalidation', async function () { + var previousValue = valueFactory.createInteger(1234), + newValue = valueFactory.createInteger(5678); + await variable.setValue(previousValue).toPromise(); + garbageCacheInvalidator.markValueForInvalidation.resetHistory(); + + await variable.setValue(newValue).toPromise(); + + expect(garbageCacheInvalidator.markValueForInvalidation).to.have.been.calledTwice; + expect(garbageCacheInvalidator.markValueForInvalidation) + .to.have.been.calledWith(sinon.match.same(previousValue)); + expect(garbageCacheInvalidator.markValueForInvalidation) + .to.have.been.calledWith(sinon.match.same(newValue)); + }); + + it('should mark only the new value for garbage invalidation when variable had none assigned', async function () { + var value = valueFactory.createInteger(5678); + garbageCacheInvalidator.markValueForInvalidation.resetHistory(); + + await variable.setValue(value).toPromise(); + + expect(garbageCacheInvalidator.markValueForInvalidation).to.have.been.calledOnce; + expect(garbageCacheInvalidator.markValueForInvalidation) + .to.have.been.calledWith(sinon.match.same(value)); + }); }); describe('when the variable has a reference assigned', function () { @@ -456,10 +509,29 @@ describe('Variable', function () { expect(reference.setValue.args[0][0].getType()).to.equal('string'); expect(reference.setValue.args[0][0].getNative()).to.equal('my assigned value'); }); + + it('should mark the new value for garbage invalidation', async function () { + var value = valueFactory.createInteger(5678); + garbageCacheInvalidator.markValueForInvalidation.resetHistory(); + + await variable.setValue(value).toPromise(); + + expect(garbageCacheInvalidator.markValueForInvalidation).to.have.been.calledOnce; + expect(garbageCacheInvalidator.markValueForInvalidation) + .to.have.been.calledWith(sinon.match.same(value)); + }); }); it('should unset $this when setting to null', async function () { - variable = new Variable(callStack, valueFactory, referenceFactory, futureFactory, flow, 'this'); + variable = new Variable( + callStack, + valueFactory, + referenceFactory, + futureFactory, + flow, + garbageCacheInvalidator, + 'this' + ); await variable.setValue(valueFactory.createNull()).toPromise(); @@ -468,7 +540,15 @@ describe('Variable', function () { it('should return the null value when setting $this to null', async function () { var value; - variable = new Variable(callStack, valueFactory, referenceFactory, futureFactory, flow, 'this'); + variable = new Variable( + callStack, + valueFactory, + referenceFactory, + futureFactory, + flow, + garbageCacheInvalidator, + 'this' + ); value = await variable.setValue(valueFactory.createNull()).toPromise(); @@ -509,10 +589,29 @@ describe('Variable', function () { expect(variable.isDefined()).to.be.false; }); - it('should return an unwrapped Future', async function () { + it('should mark the previous value of the variable for garbage invalidation', async function () { + var value = valueFactory.createInteger(1234); + variable.setValue(value); + garbageCacheInvalidator.markValueForInvalidation.resetHistory(); + + await variable.unset().toPromise(); + + expect(garbageCacheInvalidator.markValueForInvalidation).to.have.been.calledOnce; + expect(garbageCacheInvalidator.markValueForInvalidation).to.have.been.calledWith(sinon.match.same(value)); + }); + + it('should not mark any value for garbage invalidation when variable had none assigned', async function () { + garbageCacheInvalidator.markValueForInvalidation.resetHistory(); + + await variable.unset().toPromise(); + + expect(garbageCacheInvalidator.markValueForInvalidation).not.to.have.been.called; + }); + + it('should return an unwrapped Present', async function () { variable.setValue(valueFactory.createInteger(1234)); - expect(variable.unset()).to.be.an.instanceOf(Future); + expect(variable.unset()).to.be.an.instanceOf(Present); }); });