diff --git a/packages/js/expression-evaluation/.eslintrc.js b/packages/js/expression-evaluation/.eslintrc.js new file mode 100644 index 00000000000..e4d185d8cd1 --- /dev/null +++ b/packages/js/expression-evaluation/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + extends: [ 'plugin:@woocommerce/eslint-plugin/recommended' ], + root: true, +}; diff --git a/packages/js/expression-evaluation/.npmrc b/packages/js/expression-evaluation/.npmrc new file mode 100644 index 00000000000..43c97e719a5 --- /dev/null +++ b/packages/js/expression-evaluation/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/js/expression-evaluation/CHANGELOG.md b/packages/js/expression-evaluation/CHANGELOG.md new file mode 100644 index 00000000000..f7aac6be3b3 --- /dev/null +++ b/packages/js/expression-evaluation/CHANGELOG.md @@ -0,0 +1,3 @@ +# Changelog + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). diff --git a/packages/js/expression-evaluation/README.md b/packages/js/expression-evaluation/README.md new file mode 100644 index 00000000000..583c87b9b2b --- /dev/null +++ b/packages/js/expression-evaluation/README.md @@ -0,0 +1,314 @@ +# @woocommerce/expression-evaluation + +Evaluation of JavaScript-like expressions in an optional context. + +Examples of simple expressions: + +```js +1 + 2 +``` + +```js +foo === 'bar' +``` + +```js +foo ? 'bar' : 'baz' +``` + +Examples of complex expressions: + +```js +foo.bar.baz === 'qux' +``` + +```js +foo.bar + && ( foo.bar.baz === 'qux' || foo.baz === 'quux' ) +``` + +```js +foo.bar + && ( foo.baz === "qux" || foo.baz === "quux" ) + && ( foo.quux > 1 && foo.quux <= 5 ) +``` + +```js +foo.bar + && ( foo.baz === "qux" || foo.baz === "quux" ) + && ( foo.quux > 1 && foo.quux <= 5 ) + ? "boo" + : "baa" +``` + +```js +foo + + 5 + /* This is a comment */ + * ( bar ? baz : qux ) +``` + +## API + +### evaluate + +Evaluates an expression in an optional context. + +#### Usage + +```js +import { evaluate } from '@woocommerce/expression-evaluation'; + +const result = evaluate( '1 + foo', { foo: 2 } ); + +console.log( result ); // 3 +``` + +#### Parameters + +- _expression_ `string`: The expression to evaluate. +- _context_ `Object`: Optional. The context to evaluate the expression in. Variables in the expression will be looked up in this object. + +#### Returns + +- `any`: The result of the expression evaluation. + +## Expression syntax + +### Grammar and types + +The expression syntax is based on JavaScript. The formal grammar is defined in [parser.ts](./src/parser.ts). + +An expression consists of a single statement. + +Features like `if` statements, `for` loops, function calls, and variable assignments, are not supported. + +The following types are supported: + +- `null` +- Boolean: `true` and `false` +- Number: An integer or floating point number. +- String: A sequence of characters that represent text. + +### Literals + +Values in an expression can be written as literals. + +#### null + +```js +null +``` + +#### Boolean + +```js +true +false +``` + +#### Number + +```js +1 +5.23 +-9 +``` + +#### String + +String literals can be written with single or double quotes. This can be helpful if the string contains a single or double quote. + +```js +'foo' +"foo" +'foo "bar"' +"foo 'bar'" +``` + +Quotes can be escaped with a backslash. + +```js +'foo \'bar\'' +"foo \"bar\"" +``` + +### Context variables + +Variables can be used in an expression. The value of a variable is looked up in the context. + +```js +const result = evaluate( 'foo', { foo: 1 } ); + +console.log( result ); // 1 +``` + +Nested properties can be accessed with the dot operator. + +```js +const result = evaluate( 'foo.bar', { foo: { bar: 1 } } ); + +console.log( result ); // 1 +``` + +### Operators + +The following operators are supported. + +#### Comparison operators + +##### Equal (`==`) + +Returns `true` if the operands are equal. + +```js +1 == 1 +``` + +##### Not equal (`!=`) + +Returns `true` if the operands are not equal. + +```js +1 != 2 +``` + +##### Strict equal (`===`) + +Returns `true` if the operands are equal and of the same type. + +```js +1 === 1 +``` + +##### Strict not equal (`!==`) + +Returns `true` if the operands are not equal and/or not of the same type. + +```js +1 !== "1" +``` + +##### Greater than (`>`) + +Returns `true` if the left operand is greater than the right operand. + +```js +2 > 1 +``` + +##### Greater than or equal (`>=`) + +Returns `true` if the left operand is greater than or equal to the right operand. + +```js +2 >= 2 +``` + +##### Less than (`<`) + +Returns `true` if the left operand is less than the right operand. + +```js +1 < 2 +``` + +##### Less than or equal (`<=`) + +Returns `true` if the left operand is less than or equal to the right operand. + +```js +2 <= 2 +``` + +#### Arithmetic operators + +##### Addition (`+`) + +Returns the sum of two operands. + +```js +1 + 2 +``` + +##### Subtraction (`-`) + +Returns the difference of two operands. + +```js +2 - 1 +``` + +##### Multiplication (`*`) + +Returns the product of two operands. + +```js +2 * 3 +``` + +##### Division (`/`) + +Returns the quotient of two operands. + +```js +6 / 2 +``` + +##### Modulus (`%`) + +Returns the remainder of two operands. + +```js +5 % 2 +``` + +##### Negation (`-`) + +Returns the negation of an operand. + +```js +-1 +``` + +#### Logical operators + +##### Logical AND (`&&`) + +Returns `true` if both operands are `true`. + +```js +true && true +``` + +##### Logical OR (`||`) + +Returns `true` if either operand is `true`. + +```js +true || false +``` + +##### Logical NOT (`!`) + +Returns `true` if the operand is `false`. + +```js +!false +``` + +#### Conditional (ternary) operator + +Returns the first value if the condition is `true`, otherwise it returns the second value. + +```js +true ? 1 : 2 +``` + +### Comments + +Comments can be used to document an expression. Comments are treated as whitespace and are ignored by the parser. + +```js +/* This is a comment */ +``` diff --git a/packages/js/expression-evaluation/babel.config.js b/packages/js/expression-evaluation/babel.config.js new file mode 100644 index 00000000000..f73e04467aa --- /dev/null +++ b/packages/js/expression-evaluation/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + extends: '../internal-js-tests/babel.config.js', +}; diff --git a/packages/js/expression-evaluation/changelog/add-expression-evaluation-package b/packages/js/expression-evaluation/changelog/add-expression-evaluation-package new file mode 100644 index 00000000000..3ad65acf293 --- /dev/null +++ b/packages/js/expression-evaluation/changelog/add-expression-evaluation-package @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Initial @woocommerce/expression-evaluation package. diff --git a/packages/js/expression-evaluation/composer.json b/packages/js/expression-evaluation/composer.json new file mode 100644 index 00000000000..b7e276fcbea --- /dev/null +++ b/packages/js/expression-evaluation/composer.json @@ -0,0 +1,32 @@ +{ + "name": "woocommerce/expression-evaluation", + "description": "WooCommerce expression evaluation library", + "type": "library", + "license": "GPL-3.0-or-later", + "minimum-stability": "dev", + "require-dev": { + "automattic/jetpack-changelogger": "3.3.0" + }, + "config": { + "platform": { + "php": "7.2" + } + }, + "extra": { + "changelogger": { + "formatter": { + "filename": "../../../tools/changelogger/class-package-formatter.php" + }, + "types": { + "fix": "Fixes an existing bug", + "add": "Adds functionality", + "update": "Update existing functionality", + "dev": "Development related task", + "tweak": "A minor adjustment to the codebase", + "performance": "Address performance issues", + "enhancement": "Improve existing functionality" + }, + "changelog": "CHANGELOG.md" + } + } +} diff --git a/packages/js/expression-evaluation/composer.lock b/packages/js/expression-evaluation/composer.lock new file mode 100644 index 00000000000..0448aa63f09 --- /dev/null +++ b/packages/js/expression-evaluation/composer.lock @@ -0,0 +1,483 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "76226c5737d8666789d8a162710469da", + "packages": [], + "packages-dev": [ + { + "name": "automattic/jetpack-changelogger", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/Automattic/jetpack-changelogger.git", + "reference": "8f63c829b8d1b0d7b1d5de93510d78523ed18959" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Automattic/jetpack-changelogger/zipball/8f63c829b8d1b0d7b1d5de93510d78523ed18959", + "reference": "8f63c829b8d1b0d7b1d5de93510d78523ed18959", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "symfony/console": "^3.4 || ^5.2 || ^6.0", + "symfony/process": "^3.4 || ^5.2 || ^6.0", + "wikimedia/at-ease": "^1.2 || ^2.0" + }, + "require-dev": { + "wikimedia/testing-access-wrapper": "^1.0 || ^2.0", + "yoast/phpunit-polyfills": "1.0.4" + }, + "bin": [ + "bin/changelogger" + ], + "type": "project", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "3.3.x-dev" + }, + "mirror-repo": "Automattic/jetpack-changelogger", + "version-constants": { + "::VERSION": "src/Application.php" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-changelogger/compare/${old}...${new}" + } + }, + "autoload": { + "psr-4": { + "Automattic\\Jetpack\\Changelog\\": "lib", + "Automattic\\Jetpack\\Changelogger\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Jetpack Changelogger tool. Allows for managing changelogs by dropping change files into a changelog directory with each PR.", + "support": { + "source": "https://github.com/Automattic/jetpack-changelogger/tree/v3.3.0" + }, + "time": "2022-12-26T13:49:01+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "symfony/console", + "version": "3.4.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "a10b1da6fc93080c180bba7219b5ff5b7518fe81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/a10b1da6fc93080c180bba7219b5ff5b7518fe81", + "reference": "a10b1da6fc93080c180bba7219b5ff5b7518fe81", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/debug": "~2.8|~3.0|~4.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.3|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~2.8|~3.0|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.3|~4.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/console/tree/3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-24T10:57:07+00:00" + }, + { + "name": "symfony/debug", + "version": "4.4.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "1a692492190773c5310bc7877cb590c04c2f05be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/1a692492190773c5310bc7877cb590c04c2f05be", + "reference": "1a692492190773c5310bc7877cb590c04c2f05be", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "symfony/http-kernel": "<3.4" + }, + "require-dev": { + "symfony/http-kernel": "^3.4|^4.0|^5.0" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/debug/tree/v4.4.44" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "abandoned": "symfony/error-handler", + "time": "2022-07-28T16:29:46+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "42292d99c55abe617799667f454222c54c60e229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-28T09:04:16+00:00" + }, + { + "name": "symfony/process", + "version": "3.4.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "b8648cf1d5af12a44a51d07ef9bf980921f15fca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/b8648cf1d5af12a44a51d07ef9bf980921f15fca", + "reference": "b8648cf1d5af12a44a51d07ef9bf980921f15fca", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-24T10:57:07+00:00" + }, + { + "name": "wikimedia/at-ease", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/wikimedia/at-ease.git", + "reference": "013ac61929797839c80a111a3f1a4710d8248e7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wikimedia/at-ease/zipball/013ac61929797839c80a111a3f1a4710d8248e7a", + "reference": "013ac61929797839c80a111a3f1a4710d8248e7a", + "shasum": "" + }, + "require": { + "php": ">=5.6.99" + }, + "require-dev": { + "jakub-onderka/php-console-highlighter": "0.3.2", + "jakub-onderka/php-parallel-lint": "1.0.0", + "mediawiki/mediawiki-codesniffer": "22.0.0", + "mediawiki/minus-x": "0.3.1", + "ockcyp/covers-validator": "0.5.1 || 0.6.1", + "phpunit/phpunit": "4.8.36 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/Wikimedia/Functions.php" + ], + "psr-4": { + "Wikimedia\\AtEase\\": "src/Wikimedia/AtEase/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Tim Starling", + "email": "tstarling@wikimedia.org" + }, + { + "name": "MediaWiki developers", + "email": "wikitech-l@lists.wikimedia.org" + } + ], + "description": "Safe replacement to @ for suppressing warnings.", + "homepage": "https://www.mediawiki.org/wiki/at-ease", + "support": { + "source": "https://github.com/wikimedia/at-ease/tree/master" + }, + "time": "2018-10-10T15:39:06+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "platform-overrides": { + "php": "7.2" + }, + "plugin-api-version": "2.6.0" +} diff --git a/packages/js/expression-evaluation/jest.config.json b/packages/js/expression-evaluation/jest.config.json new file mode 100644 index 00000000000..3d8108048f6 --- /dev/null +++ b/packages/js/expression-evaluation/jest.config.json @@ -0,0 +1,4 @@ +{ + "rootDir": "./src", + "preset": "../node_modules/@woocommerce/internal-js-tests/jest-preset.js" +} diff --git a/packages/js/expression-evaluation/package.json b/packages/js/expression-evaluation/package.json new file mode 100644 index 00000000000..949a3b3cba7 --- /dev/null +++ b/packages/js/expression-evaluation/package.json @@ -0,0 +1,69 @@ +{ + "name": "@woocommerce/expression-evaluation", + "version": "0.0.1", + "description": "Library for evaluating expressions.", + "author": "Automattic", + "license": "GPL-3.0-or-later", + "keywords": [ + "wordpress", + "woocommerce", + "expression", + "evalution" + ], + "engines": { + "node": "^16.14.1", + "pnpm": "^8.6.7" + }, + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/expression-evaluation/README.md", + "repository": { + "type": "git", + "url": "https://github.com/woocommerce/woocommerce.git" + }, + "bugs": { + "url": "https://github.com/woocommerce/woocommerce/issues" + }, + "main": "build/index.js", + "module": "build-module/index.js", + "types": "build-types", + "react-native": "src/index", + "dependencies": { + "@wordpress/i18n": "wp-6.0", + "peggy": "^3.0.2" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "turbo:build": "tsc --project tsconfig.json && tsc --project tsconfig-cjs.json", + "turbo:test": "jest --config ./jest.config.json", + "prepare": "composer install", + "changelog": "composer exec -- changelogger", + "clean": "pnpm exec rimraf tsconfig.tsbuildinfo build build-*", + "build": "pnpm -w exec turbo run turbo:build --filter=$npm_package_name", + "test": "pnpm -w exec turbo run turbo:test --filter=$npm_package_name", + "lint": "eslint --output-file eslint_report.json --format json src", + "start": "concurrently \"tsc --project tsconfig.json --watch\" \"tsc --project tsconfig-cjs.json --watch\"", + "prepack": "pnpm run clean && pnpm run build", + "lint:fix": "eslint src --fix", + "test-staged": "jest --bail --config ./jest.config.json --findRelatedTests" + }, + "devDependencies": { + "@babel/core": "^7.17.5", + "@types/jest": "^27.4.1", + "@woocommerce/eslint-plugin": "workspace:*", + "@woocommerce/internal-js-tests": "workspace:*", + "concurrently": "^7.0.0", + "eslint": "^8.32.0", + "jest": "^27.5.1", + "jest-cli": "^27.5.1", + "rimraf": "^3.0.2", + "ts-jest": "^27.1.3", + "typescript": "^5.1.6" + }, + "lint-staged": { + "*.(t|j)s?(x)": [ + "pnpm lint:fix", + "pnpm test-staged" + ] + } +} diff --git a/packages/js/expression-evaluation/src/index.ts b/packages/js/expression-evaluation/src/index.ts new file mode 100644 index 00000000000..0d499c72029 --- /dev/null +++ b/packages/js/expression-evaluation/src/index.ts @@ -0,0 +1,9 @@ +/** + * Internal dependencies + */ + +import { parser } from './parser'; + +export function evaluate( expression: string, context = {} ) { + return parser.parse( expression, { context } ); +} diff --git a/packages/js/expression-evaluation/src/parser.ts b/packages/js/expression-evaluation/src/parser.ts new file mode 100644 index 00000000000..2fdb9470f0c --- /dev/null +++ b/packages/js/expression-evaluation/src/parser.ts @@ -0,0 +1,404 @@ +/** + * External dependencies + */ +import * as peggy from 'peggy'; + +const grammar = ` +{{ + function evaluateUnaryExpression( operator, operand ) { + switch ( operator ) { + case '!': + return !operand; + break; + case '-': + return -operand; + break; + case '+': + return +operand; + break; + default: + return undefined; + break; + } + } + + function evaluateBinaryExpression( head, tail ) { + return tail.reduce( ( leftOperand, tailElement ) => { + const operator = tailElement[ 1 ]; + const rightOperand = tailElement[ 3 ]; + + switch ( operator ) { + case '&&': + return leftOperand && rightOperand; + break; + case '||': + return leftOperand || rightOperand; + break; + case '===': + return leftOperand === rightOperand; + break; + case '!==': + return leftOperand !== rightOperand; + break; + case '==': + return leftOperand == rightOperand; + break; + case '!=': + return leftOperand != rightOperand; + break; + case '<=': + return leftOperand <= rightOperand; + break; + case '<': + return leftOperand < rightOperand; + break; + case '>=': + return leftOperand >= rightOperand; + break; + case '>': + return leftOperand > rightOperand; + break; + case '+': + return leftOperand + rightOperand; + break; + case '-': + return leftOperand - rightOperand; + break; + case '*': + return leftOperand * rightOperand; + break; + case '/': + return leftOperand / rightOperand; + break; + case '%': + return leftOperand % rightOperand; + break; + default: + return undefined; + break; + } + }, head ); + } +}} + +Start + = Expression + +SourceCharacter + = . + +WhiteSpace + = " " + / "\\t" + +LineTerminator + = "\\n" + / "\\r" + / "\\u2028" + / "\\u2029" + +LineTerminatorSequence + = "\\n" + / "\\r\\n" + / "\\r" + / "\\u2028" + / "\\u2029" + +Comment "comment" + = MultiLineComment + +MultiLineComment + = "/*" (!"*/" SourceCharacter)* "*/" + +__ "skipped" + = (WhiteSpace / LineTerminatorSequence / Comment)* + +IdentifierPath + = variable:Identifier accessor:(__ "." __ Identifier)* { + const path = variable.split( '.' ); + let result = path.reduce( ( nextObject, propertyName ) => nextObject[ propertyName ], options.context ); + + for ( let i = 0; i < accessor.length; i++ ) { + result = result[ accessor[ i ][ 3 ] ]; + } + + return result; + } + +Identifier + = !ReservedWord name:IdentifierName { + return name; + } + +IdentifierName + = first:IdentifierStart rest:IdentifierPart* { + return text(); + } + +IdentifierStart + = [a-zA-Z] + / "_" + / "$" + +IdentifierPart + = IdentifierStart + +ReservedWord + = NullLiteral + / BooleanLiteral + +// Literals + +Literal + = NullLiteral + / BooleanLiteral + / NumericLiteral + / StringLiteral + +NullLiteral + = NullToken { return null; } + +BooleanLiteral + = "true" { return true; } + / "false" { return false; } + +NumericLiteral + = literal:HexIntegerLiteral !(IdentifierStart / DecimalDigit) { + return literal; + } + / literal:DecimalLiteral !(IdentifierStart / DecimalDigit) { + return literal; + } + +HexIntegerLiteral + = "0x"i digits:$HexDigit+ { + return parseInt( digits, 16 ); + } + +HexDigit + = [0-9a-f]i + +DecimalLiteral + = DecimalIntegerLiteral "." DecimalDigit* ExponentPart? { + return parseFloat( text() ); + } + / "." DecimalDigit+ ExponentPart? { + return parseFloat( text() ); + } + / DecimalIntegerLiteral ExponentPart? { + return parseFloat( text() ); + } + +DecimalIntegerLiteral + = "0" + / NonZeroDigit DecimalDigit* + +DecimalDigit + = [0-9] + +NonZeroDigit + = [1-9] + +ExponentPart + = ExponentIndicator SignedInteger + +ExponentIndicator + = "e"i + +SignedInteger + = [+-]? DecimalDigit+ + +StringLiteral + = '"' chars:DoubleQuotedStringCharacter* '"' { + return chars.join( '' ); + } + / "'" chars:SingleQuotedStringCharacter* "'" { + return chars.join( '' ); + } + +DoubleQuotedStringCharacter + = !('"' / "\\\\" / LineTerminator) SourceCharacter { + return text(); + } + / "\\\\" escapeSequence:EscapeSequence { + return escapeSequence; + } + / LineContinuation + +SingleQuotedStringCharacter + = !("'" / "\\\\" / LineTerminator) SourceCharacter { + return text(); + } + / "\\\\" escapeSequence:EscapeSequence { + return escapeSequence; + } + / LineContinuation + +LineContinuation + = "\\\\" LineTerminatorSequence { + return ''; + } + +EscapeSequence + = CharacterEscapeSequence + / "0" !DecimalDigit { + return "\\0"; + } + / HexEscapeSequence + / UnicodeEscapeSequence + +CharacterEscapeSequence + = SingleEscapeCharacter + / NonEscapeCharacter + +SingleEscapeCharacter + = "'" + / '"' + / "\\\\" + / "b" { + return "\\b"; + } + / "f" { + return "\\f"; + } + / "n" { + return "\\n"; + } + / "r" { + return "\\r"; + } + / "t" { + return "\\t"; + } + / "v" { + return "\\v"; + } + +NonEscapeCharacter + = (!EscapeCharacter / LineTerminator) SourceCharacter { + return text(); + } + +EscapeCharacter + = SingleEscapeCharacter + / DecimalDigit + / "x" + / "u" + +HexEscapeSequence + = "x" digits:$(HexDigit HexDigit) { + return String.fromCharCode( parseInt( digits, 16 ) ); + } + +UnicodeEscapeSequence + = "u" digits:$(HexDigit HexDigit HexDigit HexDigit) { + return String.fromCharCode( parseInt( digits, 16 ) ); + } + +// Tokens + +NullToken + = "null" !IdentifierPart + +TrueToken + = "true" !IdentifierPart + +FalseToken + = "false" !IdentifierPart + +// Expressions + +PrimaryExpression + = IdentifierPath + / Literal + / "(" __ expression:Expression __ ")" { + return expression; + } + +UnaryExpression + = PrimaryExpression + / operator:UnaryOperator __ operand:UnaryExpression { + return evaluateUnaryExpression( operator, operand ); + } + +UnaryOperator + = "!" + / "-" + / "+" + +MultiplicativeExpression + = head:UnaryExpression tail:(__ MultiplicativeOperator __ UnaryExpression)* { + return evaluateBinaryExpression( head, tail ); + } + +MultiplicativeOperator + = "*" + / "/" + / "%" + +AdditiveExpression + = head:MultiplicativeExpression tail:(__ AdditiveOperator __ MultiplicativeExpression)* { + return evaluateBinaryExpression( head, tail ); + } + +AdditiveOperator + = "+" + / "-" + +RelationalExpression + = head:AdditiveExpression tail:(__ RelationalOperator __ AdditiveExpression)* { + return evaluateBinaryExpression( head, tail ); + } + +RelationalOperator + = "<=" + / "<" + / ">=" + / ">" + +EqualityExpression + = head:RelationalExpression tail:(__ EqualityOperator __ RelationalExpression)* { + return evaluateBinaryExpression( head, tail ); + } + +EqualityOperator + = "===" + / "!==" + / "==" + / "!=" + +LogicalAndExpression + = head:EqualityExpression tail:(__ LogicalAndOperator __ EqualityExpression)* { + return evaluateBinaryExpression( head, tail ); + } + +LogicalAndOperator + = "&&" + +LogicalOrExpression + = head:LogicalAndExpression tail:(__ LogicalOrOperator __ LogicalAndExpression)* { + return evaluateBinaryExpression( head, tail ); + } + +LogicalOrOperator + = "||" + +ConditionalExpression + = condition:LogicalOrExpression __ ConditionalTrueOperator __ expressionIfTrue:ConditionalExpression __ ConditionalFalseOperator __ expressionIfFalse:ConditionalExpression { + return condition ? expressionIfTrue : expressionIfFalse; + } + / LogicalOrExpression + +ConditionalTrueOperator + = "?" + +ConditionalFalseOperator + = ":" + +Expression + = __ expression:ConditionalExpression __ { + return expression; + } +`; + +export const parser = peggy.generate( grammar ); diff --git a/packages/js/expression-evaluation/src/test/index.test.ts b/packages/js/expression-evaluation/src/test/index.test.ts new file mode 100644 index 00000000000..7b5d3dc4284 --- /dev/null +++ b/packages/js/expression-evaluation/src/test/index.test.ts @@ -0,0 +1,482 @@ +/** + * Internal dependencies + */ + +import { evaluate } from '../'; + +describe( 'evaluate', () => { + it( 'should evaluate a null literal', () => { + const result = evaluate( 'null' ); + + expect( result ).toEqual( null ); + } ); + + it( 'should evaluate a boolean true literal', () => { + const result = evaluate( 'true' ); + + expect( result ).toEqual( true ); + } ); + + it( 'should evaluate a boolean false literal', () => { + const result = evaluate( 'false' ); + + expect( result ).toEqual( false ); + } ); + + it( 'should evaluate a numeric integer literal', () => { + const result = evaluate( '23' ); + + expect( result ).toEqual( 23 ); + } ); + + it( 'should evaluate a signed negative integer literal', () => { + const result = evaluate( '-1' ); + + expect( result ).toEqual( -1 ); + } ); + + it( 'should evaluate a signed positive integer literal', () => { + const result = evaluate( '+1' ); + + expect( result ).toEqual( 1 ); + } ); + + it( 'should evaluate a numeric floating point literal', () => { + const result = evaluate( '5.23' ); + + expect( result ).toEqual( 5.23 ); + } ); + + it( 'should evaluate a signed negative floating point literal', () => { + const result = evaluate( '-9.95' ); + + expect( result ).toEqual( -9.95 ); + } ); + + it( 'should evaluate a signed positive floating point literal', () => { + const result = evaluate( '+9.95' ); + + expect( result ).toEqual( 9.95 ); + } ); + + it( 'should evaluate a numeric hexadecimal literal', () => { + const result = evaluate( '0x23' ); + + expect( result ).toEqual( 35 ); + } ); + + it( 'should evaluate a string literal with double quotes', () => { + const result = evaluate( '"foo"' ); + + expect( result ).toEqual( 'foo' ); + } ); + + it( 'should evaluate a string literal with double quotes and single quotes', () => { + const result = evaluate( '"foo \'bar\'"' ); + + expect( result ).toEqual( "foo 'bar'" ); + } ); + + it( 'should evaluate a string literal with double quotes and escaped double quotes', () => { + const result = evaluate( '"foo \\"bar\\""' ); + + expect( result ).toEqual( 'foo "bar"' ); + } ); + + it( 'should evaluate a string literal with double quotes and escaped backslashes', () => { + // eslint-disable-next-line prettier/prettier + const result = evaluate( '"foo \\\\\\"bar\\\\\\""' ); + + expect( result ).toEqual( 'foo \\"bar\\"' ); + } ); + + it( 'should evaluate a string literal with single quotes', () => { + const result = evaluate( "'foo'" ); + + expect( result ).toEqual( 'foo' ); + } ); + + it( 'should evaluate a string literal with single quotes and double quotes', () => { + // eslint-disable-next-line prettier/prettier + const result = evaluate( "'foo \"bar\"'" ); + + expect( result ).toEqual( 'foo "bar"' ); + } ); + + it( 'should evaluate a string literal with single quotes and escaped single quotes', () => { + const result = evaluate( "'foo \\'bar\\''" ); + + expect( result ).toEqual( "foo 'bar'" ); + } ); + + it( 'should evaluate a string literal with single quotes and escaped backslashes', () => { + // eslint-disable-next-line prettier/prettier + const result = evaluate( "'foo \\\\\\'bar\\\\\\''" ); + + expect( result ).toEqual( "foo \\'bar\\'" ); + } ); + + it( 'should evaluate a literal with whitespace around it', () => { + const result = evaluate( ' 23 ' ); + + expect( result ).toEqual( 23 ); + } ); + + it( 'should evaluate a top-level context property', () => { + const result = evaluate( 'foo', { + foo: 'bar', + } ); + + expect( result ).toEqual( 'bar' ); + } ); + + it( 'should evaluate a top-level context property with whitespace', () => { + const result = evaluate( ' foo ', { + foo: 'bar', + } ); + + expect( result ).toEqual( 'bar' ); + } ); + + it( 'should evaluate a nested context property', () => { + const result = evaluate( 'foo.bar', { + foo: { + bar: 'baz', + }, + } ); + + expect( result ).toEqual( 'baz' ); + } ); + + it( 'should evaluate a nested context property with whitespace', () => { + const result = evaluate( 'foo. bar', { + foo: { + bar: 'baz', + }, + } ); + + expect( result ).toEqual( 'baz' ); + } ); + + it( 'should evaluate a nested context property with multiple lines', () => { + const result = evaluate( + `foo. + bar`, + { + foo: { + bar: 'baz', + }, + } + ); + + expect( result ).toEqual( 'baz' ); + } ); + + it( 'should evaluate a NOT expression', () => { + const result = evaluate( '!foo', { + foo: true, + } ); + + expect( result ).toEqual( false ); + } ); + + it( 'should evaluate a double NOT expression', () => { + const result = evaluate( '!!foo', { + foo: true, + } ); + + expect( result ).toEqual( true ); + } ); + + it( 'should evaluate a NOT expression with parentheses', () => { + const result = evaluate( '!( foo )', { + foo: true, + } ); + + expect( result ).toEqual( false ); + } ); + + it( 'should evaluate a NOT expression with parentheses and spaces', () => { + const result = evaluate( '! ( foo ) ', { + foo: true, + } ); + + expect( result ).toEqual( false ); + } ); + + it( 'should evaluate a multiplication expression', () => { + const result = evaluate( 'foo * 2', { + foo: 2, + } ); + + expect( result ).toEqual( 4 ); + } ); + + it( 'should evaluate a division expression', () => { + const result = evaluate( 'foo / 2', { + foo: 4, + } ); + + expect( result ).toEqual( 2 ); + } ); + + it( 'should evaluate a modulo expression', () => { + const result = evaluate( 'foo % 2', { + foo: 5, + } ); + + expect( result ).toEqual( 1 ); + } ); + + it( 'should evaluate an addition expression', () => { + const result = evaluate( 'foo + 2', { + foo: 3, + } ); + + expect( result ).toEqual( 5 ); + } ); + + it( 'should evaluate a subtraction expression', () => { + const result = evaluate( 'foo - 2', { + foo: 5, + } ); + + expect( result ).toEqual( 3 ); + } ); + + it( 'should evaluate a complex arithmetic expression', () => { + const result = evaluate( 'foo * 2 + 1', { + foo: 3, + } ); + + expect( result ).toEqual( 7 ); + } ); + + it( 'should evaluate a complex arithmetic expression with parenthesis', () => { + const result = evaluate( 'foo * (2 + 1)', { + foo: 3, + } ); + + expect( result ).toEqual( 9 ); + } ); + + it( 'should evaluate a less than or equal expression', () => { + const result = evaluate( 'foo <= 1', { + foo: 1, + } ); + + expect( result ).toEqual( true ); + } ); + + it( 'should evaluate a less than expression', () => { + const result = evaluate( 'foo < 1', { + foo: 1, + } ); + + expect( result ).toEqual( false ); + } ); + + it( 'should evaluate a greater than or equal expression', () => { + const result = evaluate( 'foo >= 1', { + foo: 1, + } ); + + expect( result ).toEqual( true ); + } ); + + it( 'should evaluate a greater than expression', () => { + const result = evaluate( 'foo > 1', { + foo: 1, + } ); + + expect( result ).toEqual( false ); + } ); + + it( 'should evaluate an strict equality expression', () => { + const result = evaluate( 'foo === "bar"', { + foo: 'bar', + } ); + + expect( result ).toEqual( true ); + } ); + + it( 'should evaluate an strict inequality expression', () => { + const result = evaluate( 'foo !== "bar"', { + foo: 'bar', + } ); + + expect( result ).toEqual( false ); + } ); + + it( 'should evaluate an equality expression', () => { + const result = evaluate( 'foo == "bar"', { + foo: 'bar', + } ); + + expect( result ).toEqual( true ); + } ); + + it( 'should evaluate an inequality expression', () => { + const result = evaluate( 'foo != "bar"', { + foo: 'bar', + } ); + + expect( result ).toEqual( false ); + } ); + + it( 'should evaluate a conditional expression that is true', () => { + const result = evaluate( 'foo ? "bar" : "baz"', { + foo: true, + } ); + + expect( result ).toEqual( 'bar' ); + } ); + + it( 'should evaluate a conditional expression that is false', () => { + const result = evaluate( 'foo ? "bar" : "baz"', { + foo: false, + } ); + + expect( result ).toEqual( 'baz' ); + } ); + + it( 'should evaluate a logical OR expression', () => { + const result = evaluate( 'foo || bar', { + foo: true, + bar: false, + } ); + + expect( result ).toEqual( true ); + } ); + + it( 'should evaluate a logical AND expression', () => { + const result = evaluate( 'foo && bar', { + foo: true, + bar: false, + } ); + + expect( result ).toEqual( false ); + } ); + + it( 'should evaluate a multiline expression', () => { + const result = evaluate( + `foo + || bar + || baz`, + { + foo: false, + bar: false, + baz: true, + } + ); + + expect( result ).toEqual( true ); + } ); + + it( 'should evaluate a complex expression', () => { + const result = evaluate( + `foo.bar + && ( foo.baz === "qux" || foo.baz === "quux" )`, + { + foo: { + bar: true, + baz: 'quux', + }, + } + ); + + expect( result ).toEqual( true ); + } ); + + it( 'should evaluate a complex expression with arithmetic, relational, and logical operators', () => { + const result = evaluate( + `foo.bar + && ( foo.baz === "qux" || foo.baz === "quux" ) + && ( foo.quux > 1 && foo.quux <= 5 )`, + { + foo: { + bar: true, + baz: 'quux', + quux: 10, + }, + } + ); + + expect( result ).toEqual( false ); + } ); + + it( 'should evaluate a complex expression with conditional, arithmetic, relational, and logical operators', () => { + const result = evaluate( + `foo.bar + && ( foo.baz === "qux" || foo.baz === "quux" ) + && ( foo.quux > 1 && foo.quux <= 5 ) + ? "boo" + : "baa"`, + { + foo: { + bar: true, + baz: 'quux', + quux: 10, + }, + } + ); + + expect( result ).toEqual( 'baa' ); + } ); + + it( 'should evaluate an expression with needless parentheses', () => { + const result = evaluate( '(((foo)))', { + foo: true, + } ); + + expect( result ).toEqual( true ); + } ); + + it( 'should evaluate an expression with a multiline comment at the end', () => { + const result = evaluate( 'foo /* + 23 */', { + foo: 5, + } ); + + expect( result ).toEqual( 5 ); + } ); + + it( 'should evaluate an expression with a multiline comment at the beginning', () => { + const result = evaluate( '/* 23 + */ foo', { + foo: 5, + } ); + + expect( result ).toEqual( 5 ); + } ); + + it( 'should evaluate an expression with a multiline comment in the middle', () => { + const result = evaluate( 'foo + /* 23 */ bar', { + foo: 5, + bar: 3, + } ); + + expect( result ).toEqual( 8 ); + } ); + + it( 'should evaluate a multiline expression with a multiline comment', () => { + const result = evaluate( + `foo + /* + + bar + + boo + */ + + baz`, + { + foo: 5, + bar: 23, + boo: 6, + baz: 3, + } + ); + + expect( result ).toEqual( 8 ); + } ); + + it( 'should throw an error if the expression is invalid', () => { + expect( () => evaluate( '= 1' ) ).toThrow(); + } ); +} ); diff --git a/packages/js/expression-evaluation/tsconfig-cjs.json b/packages/js/expression-evaluation/tsconfig-cjs.json new file mode 100644 index 00000000000..61782c90442 --- /dev/null +++ b/packages/js/expression-evaluation/tsconfig-cjs.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig-cjs", + "compilerOptions": { + "declaration": true, + "outDir": "build", + "typeRoots": [ + "./typings", + "./node_modules/@types" + ] + } +} diff --git a/packages/js/expression-evaluation/tsconfig.json b/packages/js/expression-evaluation/tsconfig.json new file mode 100644 index 00000000000..c5f351a60cc --- /dev/null +++ b/packages/js/expression-evaluation/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig", + "compilerOptions": { + "rootDir": "src", + "outDir": "build-module", + "declaration": true, + "declarationMap": true, + "declarationDir": "./build-types", + "typeRoots": [ + "./typings", + "./node_modules/@types" + ] + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7524e054847..e71c90fce8f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1825,6 +1825,49 @@ importers: specifier: ^5.1.6 version: 5.1.6 + packages/js/expression-evaluation: + dependencies: + '@wordpress/i18n': + specifier: wp-6.0 + version: 4.6.1 + peggy: + specifier: ^3.0.2 + version: 3.0.2 + devDependencies: + '@babel/core': + specifier: ^7.17.5 + version: 7.21.3 + '@types/jest': + specifier: ^27.4.1 + version: 27.4.1 + '@woocommerce/eslint-plugin': + specifier: workspace:* + version: link:../eslint-plugin + '@woocommerce/internal-js-tests': + specifier: workspace:* + version: link:../internal-js-tests + concurrently: + specifier: ^7.0.0 + version: 7.0.0 + eslint: + specifier: ^8.32.0 + version: 8.32.0 + jest: + specifier: ^27.5.1 + version: 27.5.1 + jest-cli: + specifier: ^27.5.1 + version: 27.5.1 + rimraf: + specifier: ^3.0.2 + version: 3.0.2 + ts-jest: + specifier: ^27.1.3 + version: 27.1.3(@babel/core@7.21.3)(@types/jest@27.4.1)(jest@27.5.1)(typescript@5.1.6) + typescript: + specifier: ^5.1.6 + version: 5.1.6 + packages/js/extend-cart-checkout-block: {} packages/js/integrate-plugin: @@ -4482,7 +4525,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.21.3 - '@jridgewell/trace-mapping': 0.3.19 + '@jridgewell/trace-mapping': 0.3.16 commander: 4.1.1 convert-source-map: 1.8.0 fs-readdir-recursive: 1.1.0 @@ -4726,6 +4769,32 @@ packages: browserslist: 4.19.3 semver: 6.3.0 + /@babel/helper-compilation-targets@7.17.7(@babel/core@7.12.9): + resolution: {integrity: sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/compat-data': 7.21.0 + '@babel/core': 7.12.9 + '@babel/helper-validator-option': 7.21.0 + browserslist: 4.19.3 + semver: 6.3.0 + dev: true + + /@babel/helper-compilation-targets@7.17.7(@babel/core@7.17.8): + resolution: {integrity: sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/compat-data': 7.21.0 + '@babel/core': 7.17.8 + '@babel/helper-validator-option': 7.21.0 + browserslist: 4.19.3 + semver: 6.3.0 + dev: true + /@babel/helper-compilation-targets@7.17.7(@babel/core@7.21.3): resolution: {integrity: sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==} engines: {node: '>=6.9.0'} @@ -5011,7 +5080,7 @@ packages: dependencies: '@babel/core': 7.17.8 '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.17.8) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.21.5 debug: 4.3.4(supports-color@9.2.2) lodash.debounce: 4.0.8 resolve: 1.22.1 @@ -5027,7 +5096,7 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.21.3) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.21.5 debug: 4.3.4(supports-color@9.2.2) lodash.debounce: 4.0.8 resolve: 1.22.1 @@ -6414,7 +6483,7 @@ packages: '@babel/compat-data': 7.21.0 '@babel/core': 7.12.9 '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.12.9) - '@babel/helper-plugin-utils': 7.21.5 + '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.12.9) '@babel/plugin-transform-parameters': 7.21.3(@babel/core@7.12.9) dev: true @@ -6428,7 +6497,7 @@ packages: '@babel/compat-data': 7.21.0 '@babel/core': 7.17.8 '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.17.8) - '@babel/helper-plugin-utils': 7.21.5 + '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.17.8) '@babel/plugin-transform-parameters': 7.21.3(@babel/core@7.17.8) dev: true @@ -6815,7 +6884,7 @@ packages: dependencies: '@babel/core': 7.12.9 '@babel/helper-create-regexp-features-plugin': 7.19.0(@babel/core@7.12.9) - '@babel/helper-plugin-utils': 7.21.5 + '@babel/helper-plugin-utils': 7.22.5 dev: true /@babel/plugin-proposal-unicode-property-regex@7.16.7(@babel/core@7.17.8): @@ -6826,7 +6895,7 @@ packages: dependencies: '@babel/core': 7.17.8 '@babel/helper-create-regexp-features-plugin': 7.19.0(@babel/core@7.17.8) - '@babel/helper-plugin-utils': 7.21.5 + '@babel/helper-plugin-utils': 7.22.5 dev: true /@babel/plugin-proposal-unicode-property-regex@7.16.7(@babel/core@7.21.3): @@ -8271,7 +8340,7 @@ packages: dependencies: '@babel/core': 7.17.8 '@babel/helper-builder-binary-assignment-operator-visitor': 7.18.9 - '@babel/helper-plugin-utils': 7.21.5 + '@babel/helper-plugin-utils': 7.22.5 dev: true /@babel/plugin-transform-exponentiation-operator@7.18.6(@babel/core@7.21.3): @@ -8282,7 +8351,7 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-builder-binary-assignment-operator-visitor': 7.18.9 - '@babel/helper-plugin-utils': 7.21.5 + '@babel/helper-plugin-utils': 7.22.5 /@babel/plugin-transform-flow-strip-types@7.16.7(@babel/core@7.12.9): resolution: {integrity: sha512-mzmCq3cNsDpZZu9FADYYyfZJIOrSONmHcop2XEKPdBNMa4PDC4eEvcOvzZaCNcjKu72v0XQlA5y1g58aLRXdYg==} @@ -8704,12 +8773,10 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.12.9 - '@babel/helper-module-transforms': 7.21.2 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-simple-access': 7.18.6 + '@babel/helper-module-transforms': 7.22.15(@babel/core@7.12.9) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-simple-access': 7.22.5 babel-plugin-dynamic-import-node: 2.3.3 - transitivePeerDependencies: - - supports-color dev: true /@babel/plugin-transform-modules-commonjs@7.17.7(@babel/core@7.17.8): @@ -8719,12 +8786,10 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.8 - '@babel/helper-module-transforms': 7.21.2 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-simple-access': 7.18.6 + '@babel/helper-module-transforms': 7.22.15(@babel/core@7.17.8) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-simple-access': 7.22.5 babel-plugin-dynamic-import-node: 2.3.3 - transitivePeerDependencies: - - supports-color dev: true /@babel/plugin-transform-modules-commonjs@7.17.7(@babel/core@7.21.3): @@ -8761,8 +8826,8 @@ packages: dependencies: '@babel/core': 7.17.8 '@babel/helper-module-transforms': 7.22.15(@babel/core@7.17.8) - '@babel/helper-plugin-utils': 7.21.5 - '@babel/helper-simple-access': 7.20.2 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-simple-access': 7.22.5 dev: true /@babel/plugin-transform-modules-commonjs@7.21.2(@babel/core@7.21.3): @@ -8773,8 +8838,8 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-module-transforms': 7.22.15(@babel/core@7.21.3) - '@babel/helper-plugin-utils': 7.21.5 - '@babel/helper-simple-access': 7.20.2 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-simple-access': 7.22.5 /@babel/plugin-transform-modules-commonjs@7.22.15(@babel/core@7.12.9): resolution: {integrity: sha512-jWL4eh90w0HQOTKP2MoXXUpVxilxsB2Vl4ji69rSjS3EcZ/v4sBmn+A3NpepuJzBhOaEBbR7udonlHHn5DWidg==} @@ -10390,7 +10455,7 @@ packages: dependencies: '@babel/compat-data': 7.17.7 '@babel/core': 7.12.9 - '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.12.9) + '@babel/helper-compilation-targets': 7.17.7(@babel/core@7.12.9) '@babel/helper-plugin-utils': 7.18.9 '@babel/helper-validator-option': 7.16.7 '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.16.7(@babel/core@7.12.9) @@ -10457,12 +10522,12 @@ packages: '@babel/plugin-transform-unicode-escapes': 7.16.7(@babel/core@7.12.9) '@babel/plugin-transform-unicode-regex': 7.16.7(@babel/core@7.12.9) '@babel/preset-modules': 0.1.5(@babel/core@7.12.9) - '@babel/types': 7.22.15 + '@babel/types': 7.17.0 babel-plugin-polyfill-corejs2: 0.3.0(@babel/core@7.12.9) babel-plugin-polyfill-corejs3: 0.5.2(@babel/core@7.12.9) babel-plugin-polyfill-regenerator: 0.3.0(@babel/core@7.12.9) core-js-compat: 3.21.1 - semver: 6.3.1 + semver: 6.3.0 transitivePeerDependencies: - supports-color dev: true @@ -10475,7 +10540,7 @@ packages: dependencies: '@babel/compat-data': 7.17.7 '@babel/core': 7.17.8 - '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.17.8) + '@babel/helper-compilation-targets': 7.17.7(@babel/core@7.17.8) '@babel/helper-plugin-utils': 7.18.9 '@babel/helper-validator-option': 7.16.7 '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.16.7(@babel/core@7.17.8) @@ -10542,12 +10607,12 @@ packages: '@babel/plugin-transform-unicode-escapes': 7.16.7(@babel/core@7.17.8) '@babel/plugin-transform-unicode-regex': 7.16.7(@babel/core@7.17.8) '@babel/preset-modules': 0.1.5(@babel/core@7.17.8) - '@babel/types': 7.22.15 + '@babel/types': 7.17.0 babel-plugin-polyfill-corejs2: 0.3.0(@babel/core@7.17.8) babel-plugin-polyfill-corejs3: 0.5.2(@babel/core@7.17.8) babel-plugin-polyfill-regenerator: 0.3.0(@babel/core@7.17.8) core-js-compat: 3.21.1 - semver: 6.3.1 + semver: 6.3.0 transitivePeerDependencies: - supports-color dev: true @@ -12165,6 +12230,49 @@ packages: - ts-node dev: true + /@jest/core@29.6.2: + resolution: {integrity: sha512-Oj+5B+sDMiMWLhPFF+4/DvHOf+U10rgvCLGPHP8Xlsy/7QxS51aU/eBngudHlJXnaWD5EohAgJ4js+T6pa+zOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/console': 29.6.2 + '@jest/reporters': 29.6.2 + '@jest/test-result': 29.6.2 + '@jest/transform': 29.6.2 + '@jest/types': 29.6.1 + '@types/node': 16.18.21 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.2.0 + exit: 0.1.2 + graceful-fs: 4.2.9 + jest-changed-files: 29.5.0 + jest-config: 29.6.2(@types/node@16.18.21) + jest-haste-map: 29.6.2 + jest-message-util: 29.6.2 + jest-regex-util: 29.4.3 + jest-resolve: 29.6.2 + jest-resolve-dependencies: 29.6.2 + jest-runner: 29.6.2 + jest-runtime: 29.6.2 + jest-snapshot: 29.6.2 + jest-util: 29.6.2 + jest-validate: 29.6.2 + jest-watcher: 29.6.2 + micromatch: 4.0.5 + pretty-format: 29.6.2 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + dev: false + /@jest/core@29.6.2(ts-node@10.9.1): resolution: {integrity: sha512-Oj+5B+sDMiMWLhPFF+4/DvHOf+U10rgvCLGPHP8Xlsy/7QxS51aU/eBngudHlJXnaWD5EohAgJ4js+T6pa+zOg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -12206,6 +12314,7 @@ packages: - babel-plugin-macros - supports-color - ts-node + dev: true /@jest/create-cache-key-function@27.5.1: resolution: {integrity: sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==} @@ -13040,7 +13149,6 @@ packages: dependencies: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 - dev: true /@jridgewell/trace-mapping@0.3.17: resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} @@ -21428,7 +21536,7 @@ packages: '@babel/runtime': 7.21.0 '@wordpress/keycodes': 2.19.3 '@wordpress/url': 2.22.2(react-native@0.70.0) - jest: 29.6.2(@types/node@16.18.21)(ts-node@10.9.1) + jest: 29.6.2(@types/node@16.18.21) lodash: 4.17.21 node-fetch: 2.6.7 puppeteer: 2.1.1 @@ -21449,7 +21557,7 @@ packages: '@wordpress/keycodes': 3.6.1 '@wordpress/url': 3.7.1 form-data: 4.0.0 - jest: 29.6.2(@types/node@16.18.21)(ts-node@10.9.1) + jest: 29.6.2(@types/node@16.18.21) lodash: 4.17.21 node-fetch: 2.6.7 puppeteer-core: 19.7.3(typescript@5.1.6) @@ -28204,12 +28312,8 @@ packages: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} - /decimal.js@10.3.1: - resolution: {integrity: sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==} - /decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} - dev: true /decode-uri-component@0.2.0: resolution: {integrity: sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==} @@ -28268,7 +28372,6 @@ packages: /deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - dev: true /deepsignal@1.3.6(@preact/signals@1.2.1)(preact@10.17.1): resolution: {integrity: sha512-yjd+vtiznL6YaMptOsKnEKkPr60OEApa+LRe+Qe6Ile/RfCOrELKk/YM3qVpXFZiyOI3Ng67GDEyjAlqVc697g==} @@ -34442,6 +34545,35 @@ packages: - ts-node dev: true + /jest-cli@29.6.2(@types/node@16.18.21): + resolution: {integrity: sha512-TT6O247v6dCEX2UGHGyflMpxhnrL0DNqP2fRTKYm3nJJpCTfXX3GCMQPGFjXDoj0i5/Blp3jriKXFgdfmbYB6Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.6.2 + '@jest/test-result': 29.6.2 + '@jest/types': 29.6.1 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.9 + import-local: 3.0.3 + jest-config: 29.6.2(@types/node@16.18.21) + jest-util: 29.6.2 + jest-validate: 29.6.2 + prompts: 2.4.2 + yargs: 17.5.1 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: false + /jest-cli@29.6.2(@types/node@16.18.21)(ts-node@10.9.1): resolution: {integrity: sha512-TT6O247v6dCEX2UGHGyflMpxhnrL0DNqP2fRTKYm3nJJpCTfXX3GCMQPGFjXDoj0i5/Blp3jriKXFgdfmbYB6Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -34469,6 +34601,7 @@ packages: - babel-plugin-macros - supports-color - ts-node + dev: true /jest-config@24.9.0: resolution: {integrity: sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==} @@ -34576,7 +34709,7 @@ packages: babel-jest: 27.5.1(@babel/core@7.21.3) chalk: 4.1.2 ci-info: 3.2.0 - deepmerge: 4.3.0 + deepmerge: 4.3.1 glob: 7.2.3 graceful-fs: 4.2.9 jest-circus: 27.5.1 @@ -34639,6 +34772,46 @@ packages: - supports-color dev: true + /jest-config@29.6.2(@types/node@16.18.21): + resolution: {integrity: sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.21.3 + '@jest/test-sequencer': 29.6.2 + '@jest/types': 29.6.1 + '@types/node': 16.18.21 + babel-jest: 29.6.2(@babel/core@7.21.3) + chalk: 4.1.2 + ci-info: 3.2.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.9 + jest-circus: 29.6.2 + jest-environment-node: 29.6.2 + jest-get-type: 29.4.3 + jest-regex-util: 29.4.3 + jest-resolve: 29.6.2 + jest-runner: 29.6.2 + jest-util: 29.6.2 + jest-validate: 29.6.2 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 29.6.2 + slash: 3.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + dev: false + /jest-config@29.6.2(@types/node@16.18.21)(ts-node@10.9.1): resolution: {integrity: sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -34678,6 +34851,7 @@ packages: transitivePeerDependencies: - babel-plugin-macros - supports-color + dev: true /jest-dev-server@4.4.0: resolution: {integrity: sha512-STEHJ3iPSC8HbrQ3TME0ozGX2KT28lbT4XopPxUm2WimsX3fcB3YOptRh12YphQisMhfqNSNTZUmWyT3HEXS2A==} @@ -36729,6 +36903,27 @@ packages: - ts-node dev: true + /jest@29.6.2(@types/node@16.18.21): + resolution: {integrity: sha512-8eQg2mqFbaP7CwfsTpCxQ+sHzw1WuNWL5UUvjnWP4hx2riGz9fPSzYOaU5q8/GqWn1TfgZIVTqYJygbGbWAANg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.6.2 + '@jest/types': 29.6.1 + import-local: 3.0.3 + jest-cli: 29.6.2(@types/node@16.18.21) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: false + /jest@29.6.2(@types/node@16.18.21)(ts-node@10.9.1): resolution: {integrity: sha512-8eQg2mqFbaP7CwfsTpCxQ+sHzw1WuNWL5UUvjnWP4hx2riGz9fPSzYOaU5q8/GqWn1TfgZIVTqYJygbGbWAANg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -36748,6 +36943,7 @@ packages: - babel-plugin-macros - supports-color - ts-node + dev: true /jmespath@0.16.0: resolution: {integrity: sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==} @@ -37000,7 +37196,7 @@ packages: cssom: 0.4.4 cssstyle: 2.3.0 data-urls: 2.0.0 - decimal.js: 10.3.1 + decimal.js: 10.4.3 domexception: 2.0.1 escodegen: 2.0.0 form-data: 3.0.1 @@ -40612,6 +40808,15 @@ packages: sha.js: 2.4.11 dev: true + /peggy@3.0.2: + resolution: {integrity: sha512-n7chtCbEoGYRwZZ0i/O3t1cPr6o+d9Xx4Zwy2LYfzv0vjchMBU0tO+qYYyvZloBPcgRgzYvALzGWHe609JjEpg==} + engines: {node: '>=14'} + hasBin: true + dependencies: + commander: 10.0.1 + source-map-generator: 0.8.0 + dev: false + /pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} @@ -44993,6 +45198,11 @@ packages: /source-list-map@2.0.1: resolution: {integrity: sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==} + /source-map-generator@0.8.0: + resolution: {integrity: sha512-psgxdGMwl5MZM9S3FWee4EgsEaIjahYV5AzGnwUvPhWeITz/j6rKpysQHlQ4USdxvINlb8lKfWGIXwfkrgtqkA==} + engines: {node: '>= 10'} + dev: false + /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} @@ -46786,7 +46996,7 @@ packages: json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.5.0 + semver: 7.5.3 typescript: 5.1.6 yargs-parser: 20.2.9 dev: true @@ -46821,7 +47031,7 @@ packages: json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.5.0 + semver: 7.5.3 typescript: 5.1.6 yargs-parser: 20.2.9 dev: true