diff --git a/.gitignore b/.gitignore index f5165c0..4187637 100644 --- a/.gitignore +++ b/.gitignore @@ -135,3 +135,6 @@ build/ # stryker temp files .stryker-tmp + +Mac +.DS_Store \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 71f0ff9..b1fd0d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-unused-imports": "^2.0.0", + "glob": "^10.3.10", "husky": "^8.0.3", "jest": "^29.4.3", "lint-staged": "^13.1.2", @@ -1221,6 +1222,50 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1688,6 +1733,26 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/@jest/reporters/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/@jest/reporters/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2046,6 +2111,16 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -4840,6 +4915,34 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fs-extra": { "version": "11.1.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", @@ -4997,20 +5100,22 @@ } }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", "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" + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5028,6 +5133,30 @@ "node": ">=10.13.0" } }, + "node_modules/glob/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/glob/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/global-dirs": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", @@ -5798,6 +5927,24 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -6136,6 +6283,26 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/jest-config/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/jest-config/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6906,6 +7073,26 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/jest-runtime/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/jest-runtime/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -8087,6 +8274,15 @@ "node": ">= 6" } }, + "node_modules/minipass": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz", + "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -8425,6 +8621,31 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", + "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -8982,6 +9203,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/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/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -9298,6 +9539,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width-cjs": { + "name": "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-width-cjs/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/string-width-cjs/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/string-width/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -9382,6 +9653,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "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-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -9462,6 +9746,26 @@ "node": ">=8" } }, + "node_modules/test-exclude/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/text-extensions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", @@ -9996,6 +10300,86 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", diff --git a/package.json b/package.json index bc66d86..107ae75 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-unused-imports": "^2.0.0", + "glob": "^10.3.10", "husky": "^8.0.3", "jest": "^29.4.3", "lint-staged": "^13.1.2", diff --git a/src/lexer.ts b/src/lexer.ts index d50139e..e8a6881 100644 --- a/src/lexer.ts +++ b/src/lexer.ts @@ -41,6 +41,16 @@ export class JsonTemplateLexer { return token.type === TokenType.PUNCT && token.value === value; } + matchAssignment(): boolean { + return ( + this.match('=') || + this.match('+=') || + this.match('-=') || + this.match('*=') || + this.match('/=') + ); + } + matchLiteral(): boolean { return JsonTemplateLexer.isLiteralToken(this.lookahead()); } @@ -77,6 +87,14 @@ export class JsonTemplateLexer { return this.match('...'); } + matchIncrement(): boolean { + return this.match('++'); + } + + matchDecrement(): boolean { + return this.match('--'); + } + matchPathPartSelector(): boolean { let token = this.lookahead(); if (token.type === TokenType.PUNCT) { @@ -488,7 +506,7 @@ export class JsonTemplateLexer { range: [start, this.idx], }; } - } else if ('=!^$*><'.indexOf(ch1) >= 0) { + } else if ('=!^$><'.indexOf(ch1) >= 0) { this.idx += 2; return { type: TokenType.PUNCT, @@ -581,14 +599,29 @@ export class JsonTemplateLexer { } } + private scanPunctuatorForArithmeticAssignment(): Token | undefined { + let start = this.idx, + ch1 = this.codeChars[this.idx], + ch2 = this.codeChars[this.idx + 1]; + if ('+-/*'.includes(ch1) && ch2 === '=') { + this.idx += 2; + return { + type: TokenType.PUNCT, + value: ch1 + ch2, + range: [start, this.idx], + }; + } + } + private scanPunctuator(): Token | undefined { return ( this.scanPunctuatorForDots() || this.scanPunctuatorForQuestionMarks() || + this.scanPunctuatorForArithmeticAssignment() || this.scanPunctuatorForEquality() || this.scanPunctuatorForPaths() || this.scanPunctuatorForRepeatedTokens('?', 3) || - this.scanPunctuatorForRepeatedTokens('|&*.=>?<', 2) || + this.scanPunctuatorForRepeatedTokens('|&*.=>?<+-', 2) || this.scanSingleCharPunctuators() ); } diff --git a/src/operators.ts b/src/operators.ts index 375bae5..f216ff9 100644 --- a/src/operators.ts +++ b/src/operators.ts @@ -97,8 +97,6 @@ export const binaryOperators = { return endsWith(val2, val1); }, - '*==': containsStrict, - '==*': function (val1, val2): string { return containsStrict(val2, val1); }, @@ -107,8 +105,6 @@ export const binaryOperators = { return contains(val2, val1); }, - '*=': contains, - '+': function (val1, val2): string { return `${val1}+${val2}`; }, diff --git a/src/parser.ts b/src/parser.ts index c0f4e01..d7e4eaf 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -30,9 +30,13 @@ import { PathType, ReturnExpression, ThrowExpression, + LoopControlExpression, + BlockExpressionOptions, + LoopExpression, + IncrementExpression, } from './types'; import { JsonTemplateParserError } from './errors'; -import { DATA_PARAM_KEY } from './constants'; +import { BINDINGS_PARAM_KEY, DATA_PARAM_KEY } from './constants'; import { JsonTemplateLexer } from './lexer'; import { CommonUtils } from './utils'; import { JsonTemplateEngine } from './engine'; @@ -41,6 +45,8 @@ const EMPTY_EXPR = { type: SyntaxType.EMPTY }; export class JsonTemplateParser { private lexer: JsonTemplateLexer; private options?: EngineOptions; + // indicates currently how many loops being parsed + private loopCount = 0; constructor(lexer: JsonTemplateLexer, options?: EngineOptions) { this.lexer = lexer; @@ -79,10 +85,40 @@ export class JsonTemplateParser { return statements; } - private parseStatementsExpr(blockEnd?: string): StatementsExpression { + private validateStatements(statements: Expression[], options?: BlockExpressionOptions): void { + if (!statements.length) { + if ( + options?.parentType === SyntaxType.CONDITIONAL_EXPR || + options?.parentType === SyntaxType.LOOP_EXPR + ) { + throw new JsonTemplateParserError( + 'Empty statements are not allowed in loop and condtional expressions', + ); + } + return; + } + for (let i = 0; i < statements.length; i++) { + const currStatement = statements[i]; + if ( + currStatement.type === SyntaxType.RETURN_EXPR || + currStatement.type === SyntaxType.THROW_EXPR || + currStatement.type === SyntaxType.LOOP_CONTROL_EXPR + ) { + if (options?.parentType !== SyntaxType.CONDITIONAL_EXPR || i !== statements.length - 1) { + throw new JsonTemplateParserError( + 'return, throw, continue and break statements are only allowed as last statements in conditional expressions', + ); + } + } + } + } + + private parseStatementsExpr(options?: BlockExpressionOptions): StatementsExpression { + const statements = this.parseStatements(options?.blockEnd); + this.validateStatements(statements, options); return { type: SyntaxType.STATEMENTS_EXPR, - statements: this.parseStatements(blockEnd), + statements, }; } @@ -92,8 +128,8 @@ export class JsonTemplateParser { private parseAssignmentExpr(): AssignmentExpression | Expression { const expr = this.parseNextExpr(OperatorType.ASSIGNMENT); - if (expr.type === SyntaxType.PATH && this.lexer.match('=')) { - this.lexer.ignoreTokens(1); + if (expr.type === SyntaxType.PATH && this.lexer.matchAssignment()) { + const op = this.lexer.value(); const path = expr as PathExpression; if (!path.root || typeof path.root === 'object' || path.root === DATA_PARAM_KEY) { throw new JsonTemplateParserError('Invalid assignment path'); @@ -105,6 +141,7 @@ export class JsonTemplateParser { return { type: SyntaxType.ASSIGNMENT_EXPR, value: this.parseBaseExpr(), + op, path, } as AssignmentExpression; } @@ -150,6 +187,10 @@ export class JsonTemplateParser { case OperatorType.POWER: return this.parseUnaryExpr(); case OperatorType.UNARY: + return this.parsePrefixIncreamentExpr(); + case OperatorType.PREFIX_INCREMENT: + return this.parsePostfixIncreamentExpr(); + case OperatorType.POSTFIX_INCREMENT: return this.parsePathAfterExpr(); default: return this.parseConditionalExpr(); @@ -260,6 +301,7 @@ export class JsonTemplateParser { pathType, }; if (!expr.parts.length) { + expr.pathType = PathType.SIMPLE; return expr; } return JsonTemplateParser.updatePathExpr(expr); @@ -410,36 +452,85 @@ export class JsonTemplateParser { return [objectFilter, ...indexFilters]; } + private parseLoopControlExpr(): LoopControlExpression { + const control = this.lexer.value(); + if (!this.loopCount) { + throw new JsonTemplateParserError(`encounted loop control outside loop: ${control}`); + } + return { + type: SyntaxType.LOOP_CONTROL_EXPR, + control, + }; + } + + private parseCurlyBlockExpr(options?: BlockExpressionOptions): StatementsExpression { + this.lexer.expect('{'); + const expr = this.parseStatementsExpr(options); + this.lexer.expect('}'); + return expr; + } + + private parseConditionalBodyExpr(): Expression { + if (this.lexer.match('{')) { + return this.parseCurlyBlockExpr({ blockEnd: '}', parentType: SyntaxType.CONDITIONAL_EXPR }); + } + return this.parseBaseExpr(); + } + private parseConditionalExpr(): ConditionalExpression | Expression { const ifExpr = this.parseNextExpr(OperatorType.CONDITIONAL); if (this.lexer.match('?')) { this.lexer.ignoreTokens(1); - const thenExpr = this.parseConditionalExpr(); + const thenExpr = this.parseConditionalBodyExpr(); + let elseExpr: Expression | undefined; if (this.lexer.match(':')) { this.lexer.ignoreTokens(1); - const elseExpr = this.parseConditionalExpr(); - return { - type: SyntaxType.CONDITIONAL_EXPR, - if: ifExpr, - then: thenExpr, - else: elseExpr, - }; + elseExpr = this.parseConditionalBodyExpr(); } return { type: SyntaxType.CONDITIONAL_EXPR, if: ifExpr, then: thenExpr, - else: { - type: SyntaxType.LITERAL, - tokenType: TokenType.UNDEFINED, - }, + else: elseExpr, }; } return ifExpr; } + private parseLoopExpr(): LoopExpression { + this.loopCount++; + this.lexer.ignoreTokens(1); + let init: Expression | undefined; + let test: Expression | undefined; + let update: Expression | undefined; + if (!this.lexer.match('{')) { + this.lexer.expect('('); + if (!this.lexer.match(';')) { + init = this.parseAssignmentExpr(); + } + this.lexer.expect(';'); + if (!this.lexer.match(';')) { + test = this.parseLogicalORExpr(); + } + this.lexer.expect(';'); + if (!this.lexer.match(')')) { + update = this.parseAssignmentExpr(); + } + this.lexer.expect(')'); + } + const body = this.parseCurlyBlockExpr({ blockEnd: '}', parentType: SyntaxType.LOOP_EXPR }); + this.loopCount--; + return { + type: SyntaxType.LOOP_EXPR, + init, + test, + update, + body, + }; + } + private parseArrayFilterExpr(): ArrayFilterExpression { this.lexer.expect('['); const filter = this.parseArrayFilter(); @@ -537,9 +628,7 @@ export class JsonTemplateParser { this.lexer.match('==$') || this.lexer.match('$=') || this.lexer.match('=$') || - this.lexer.match('*==') || this.lexer.match('==*') || - this.lexer.match('*=') || this.lexer.match('=*') ) { return { @@ -628,9 +717,59 @@ export class JsonTemplateParser { return expr; } + private parsePrefixIncreamentExpr(): IncrementExpression | Expression { + if (this.lexer.matchIncrement() || this.lexer.matchDecrement()) { + const op = this.lexer.value() as string; + if (!this.lexer.matchID()) { + throw new JsonTemplateParserError('Invalid prefix increment expression'); + } + const id = this.lexer.value(); + return { + type: SyntaxType.INCREMENT, + op, + id, + }; + } + + return this.parseNextExpr(OperatorType.PREFIX_INCREMENT); + } + + private convertToID(expr: Expression): string { + if (expr.type === SyntaxType.PATH) { + const path = expr as PathExpression; + if ( + !path.root || + typeof path.root !== 'string' || + path.parts.length !== 0 || + path.root === DATA_PARAM_KEY || + path.root === BINDINGS_PARAM_KEY + ) { + throw new JsonTemplateParserError('Invalid postfix increment expression'); + } + return path.root; + } + throw new JsonTemplateParserError('Invalid postfix increment expression'); + } + + private parsePostfixIncreamentExpr(): IncrementExpression | Expression { + let expr = this.parseNextExpr(OperatorType.POSTFIX_INCREMENT); + + if (this.lexer.matchIncrement() || this.lexer.matchDecrement()) { + return { + type: SyntaxType.INCREMENT, + op: this.lexer.value() as string, + id: this.convertToID(expr), + postfix: true, + }; + } + + return expr; + } + private parseUnaryExpr(): UnaryExpression | Expression { if ( this.lexer.match('!') || + this.lexer.match('+') || this.lexer.match('-') || this.lexer.matchTypeOf() || this.lexer.matchAwait() @@ -647,6 +786,7 @@ export class JsonTemplateParser { private shouldSkipPathParsing(expr: Expression): boolean { switch (expr.type) { + case SyntaxType.EMPTY: case SyntaxType.DEFINITION_EXPR: case SyntaxType.ASSIGNMENT_EXPR: case SyntaxType.SPREAD_EXPR: @@ -803,13 +943,10 @@ export class JsonTemplateParser { private parseFunctionExpr(asyncFn = false): FunctionExpression { this.lexer.ignoreTokens(1); const params = this.parseFunctionDefinitionParams(); - this.lexer.expect('{'); - const statements = this.parseStatementsExpr('}'); - this.lexer.expect('}'); return { type: SyntaxType.FUNCTION_EXPR, params, - body: statements, + body: this.parseCurlyBlockExpr({ blockEnd: '}' }), async: asyncFn, }; } @@ -1001,9 +1138,13 @@ export class JsonTemplateParser { private parseReturnExpr(): ReturnExpression { this.lexer.ignoreTokens(1); + let value: Expression | undefined; + if (!this.lexer.match(';')) { + value = this.parseBaseExpr(); + } return { type: SyntaxType.RETURN_EXPR, - value: this.parseBaseExpr(), + value, }; } @@ -1030,6 +1171,11 @@ export class JsonTemplateParser { return this.parseThrowExpr(); case Keyword.FUNCTION: return this.parseFunctionExpr(); + case Keyword.FOR: + return this.parseLoopExpr(); + case Keyword.CONTINUE: + case Keyword.BREAK: + return this.parseLoopControlExpr(); default: return this.parseDefinitionExpr(); } diff --git a/src/translator.ts b/src/translator.ts index 0c6152c..38c5ddd 100644 --- a/src/translator.ts +++ b/src/translator.ts @@ -36,6 +36,9 @@ import { PathType, ReturnExpression, ThrowExpression, + LoopExpression, + IncrementExpression, + LoopControlExpression, } from './types'; import { CommonUtils } from './utils'; @@ -121,6 +124,9 @@ export class JsonTemplateTranslator { case SyntaxType.SPREAD_EXPR: return this.translateSpreadExpr(expr as SpreadExpression, dest, ctx); + case SyntaxType.INCREMENT: + return this.translateIncrementExpr(expr as IncrementExpression, dest, ctx); + case SyntaxType.LITERAL: return this.translateLiteralExpr(expr as LiteralExpression, dest, ctx); @@ -133,6 +139,12 @@ export class JsonTemplateTranslator { case SyntaxType.BLOCK_EXPR: return this.translateBlockExpr(expr as BlockExpression, dest, ctx); + case SyntaxType.LOOP_EXPR: + return this.translateLoopExpr(expr as LoopExpression, dest, ctx); + + case SyntaxType.LOOP_CONTROL_EXPR: + return this.translateLoopControlExpr(expr as LoopControlExpression, dest, ctx); + case SyntaxType.FUNCTION_EXPR: return this.translateFunctionExpr(expr as FunctionExpression, dest, ctx); @@ -169,6 +181,46 @@ export class JsonTemplateTranslator { return ''; } } + translateLoopControlExpr(expr: LoopControlExpression, _dest: string, _ctx: string): string { + return `${expr.control};`; + } + + translateIncrementExpr(expr: IncrementExpression, dest: string, _ctx: string): string { + const code: string[] = []; + let incrementCode = `${expr.op}${expr.id};`; + if (expr.postfix) { + incrementCode = `${expr.id}${expr.op};`; + } + code.push(JsonTemplateTranslator.generateAssignmentCode(dest, incrementCode)); + return code.join(''); + } + + private translateLoopExpr(expr: LoopExpression, dest: string, ctx: string): string { + const code: string[] = []; + const init = this.acquireVar(); + const test = this.acquireVar(); + const update = this.acquireVar(); + const body = this.acquireVar(); + const iterator = this.acquireVar(); + if (expr.init) { + code.push(this.translateExpr(expr.init, init, ctx)); + } + code.push(`for(let ${iterator}=0;;${iterator}++){`); + if (expr.update) { + code.push(`if(${iterator} > 0) {`); + code.push(this.translateExpr(expr.update, update, ctx)); + code.push('}'); + } + if (expr.test) { + code.push(this.translateExpr(expr.test, test, ctx)); + code.push(`if(!${test}){break;}`); + } + code.push(this.translateExpr(expr.body, body, ctx)); + code.push(`}`); + JsonTemplateTranslator.generateAssignmentCode(dest, body); + this.releaseVars(iterator, body, update, test, init); + return code.join(''); + } private translateThrowExpr(expr: ThrowExpression, _dest: string, ctx: string): string { const code: string[] = []; @@ -181,29 +233,29 @@ export class JsonTemplateTranslator { private translateReturnExpr(expr: ReturnExpression, _dest: string, ctx: string): string { const code: string[] = []; - const value = this.acquireVar(); - code.push(this.translateExpr(expr.value, value, ctx)); - code.push(`return ${value};`); - this.releaseVars(value); + if (expr.value) { + const value = this.acquireVar(); + code.push(this.translateExpr(expr.value, value, ctx)); + code.push(`return ${value};`); + this.releaseVars(value); + } + code.push(`return ${ctx};`); return code.join(''); } private translateConditionalExpr(expr: ConditionalExpression, dest: string, ctx: string): string { const code: string[] = []; const ifVar = this.acquireVar(); - const thenVar = this.acquireVar(); - const elseVar = this.acquireVar(); code.push(this.translateExpr(expr.if, ifVar, ctx)); code.push(`if(${ifVar}){`); - code.push(this.translateExpr(expr.then, thenVar, ctx)); - - code.push(`${dest} = ${thenVar};`); + code.push(this.translateExpr(expr.then, dest, ctx)); code.push('} else {'); - code.push(this.translateExpr(expr.else, elseVar, ctx)); - code.push(`${dest} = ${elseVar};`); + if (expr.else) { + code.push(this.translateExpr(expr.else, dest, ctx)); + } else { + code.push(`${dest} = undefined;`); + } code.push('}'); - this.releaseVars(elseVar); - this.releaseVars(thenVar); this.releaseVars(ifVar); return code.join(''); } @@ -551,7 +603,7 @@ export class JsonTemplateTranslator { return simplePath.join(''); } - private translateAssignmentExpr(expr: AssignmentExpression, dest: string, ctx: string): string { + private translateAssignmentExpr(expr: AssignmentExpression, _dest: string, ctx: string): string { const code: string[] = []; const valueVar = this.acquireVar(); code.push(this.translateExpr(expr.value, valueVar, ctx)); @@ -562,8 +614,7 @@ export class JsonTemplateTranslator { true, ); JsonTemplateTranslator.ValidateAssignmentPath(assignmentPath); - code.push(JsonTemplateTranslator.generateAssignmentCode(assignmentPath, valueVar)); - code.push(JsonTemplateTranslator.generateAssignmentCode(dest, valueVar)); + code.push(JsonTemplateTranslator.generateAssignmentCode(assignmentPath, valueVar, expr.op)); this.releaseVars(valueVar); return code.join(''); } @@ -765,15 +816,7 @@ export class JsonTemplateTranslator { code.push(this.translateExpr(args[0], val1, ctx)); code.push(this.translateExpr(args[1], val2, ctx)); - code.push( - dest, - '=', - binaryOperators[expr.op]( - JsonTemplateTranslator.returnSingleValue(args[0], val1), - JsonTemplateTranslator.returnSingleValue(args[1], val2), - ), - ';', - ); + code.push(dest, '=', binaryOperators[expr.op](val1, val2), ';'); this.releaseVars(val1, val2); return code.join(''); @@ -809,14 +852,6 @@ export class JsonTemplateTranslator { return `Object.values(${varName}).filter(v => v !== null && v !== undefined)`; } - private static returnSingleValue(arg: Expression, varName: string): string { - if (arg.type === SyntaxType.LITERAL) { - return varName; - } - - return `(Array.isArray(${varName}) ? ${varName}[0] : ${varName})`; - } - private static convertToSingleValueIfSafe(varName: string): string { return `${varName} = ${varName}.length < 2 ? ${varName}[0] : ${varName};`; } @@ -829,7 +864,7 @@ export class JsonTemplateTranslator { return code.join(''); } - private static generateAssignmentCode(key: string, val: string): string { - return `${key}=${val};`; + private static generateAssignmentCode(key: string, val: string, op: string = '='): string { + return `${key}${op}${val};`; } } diff --git a/src/types.ts b/src/types.ts index b2490da..c2eb542 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,9 @@ export enum Keyword { NOT = 'not', RETURN = 'return', THROW = 'throw', + CONTINUE = 'continue', + BREAK = 'break', + FOR = 'for', } export enum TokenType { @@ -30,6 +33,7 @@ export enum TokenType { } // In the order of precedence + export enum OperatorType { BASE, CONDITIONAL, @@ -44,6 +48,8 @@ export enum OperatorType { MULTIPLICATION, POWER, UNARY, + PREFIX_INCREMENT, + POSTFIX_INCREMENT, } export enum SyntaxType { @@ -52,6 +58,7 @@ export enum SyntaxType { PATH_OPTIONS, SELECTOR, LAMBDA_ARG, + INCREMENT, LITERAL, LOGICAL_COALESCE_EXPR, LOGICAL_OR_EXPR, @@ -79,6 +86,8 @@ export enum SyntaxType { RETURN_EXPR, THROW_EXPR, STATEMENTS_EXPR, + LOOP_CONTROL_EXPR, + LOOP_EXPR, } export enum PathType { @@ -158,6 +167,7 @@ export interface ConcatExpression extends Expression { export interface AssignmentExpression extends Expression { path: PathExpression; value: Expression; + op: string; } export interface DefinitionExpression extends Expression { @@ -195,6 +205,12 @@ export interface PathExpression extends Expression { pathType: PathType; } +export interface IncrementExpression extends Expression { + id: string; + op: string; + postfix?: boolean; +} + export interface SelectorExpression extends Expression { selector: string; prop?: Token; @@ -213,11 +229,26 @@ export interface FunctionCallExpression extends Expression { export interface ConditionalExpression extends Expression { if: Expression; then: Expression; - else: Expression; + else?: Expression; } +export type BlockExpressionOptions = { + blockEnd?: string; + parentType?: SyntaxType; +}; + export interface ReturnExpression extends Expression { - value: Expression; + value?: Expression; +} + +export interface LoopControlExpression extends Expression { + control: string; +} +export interface LoopExpression extends Expression { + init?: Expression; + test?: Expression; + update?: Expression; + body: StatementsExpression; } export interface ThrowExpression extends Expression { diff --git a/test/e2e.test.ts b/test/e2e.test.ts index 2168559..672a265 100644 --- a/test/e2e.test.ts +++ b/test/e2e.test.ts @@ -1,5 +1,5 @@ -import { readdirSync } from 'fs'; -import { join } from 'path'; +import { glob } from 'glob'; +import path, { join } from 'path'; import { Command } from 'commander'; import { ScenarioUtils } from './utils'; import { Scenario } from './types'; @@ -12,13 +12,14 @@ const opts = command.opts(); let scenarios = opts.scenarios.split(/[, ]/); if (scenarios[0] === 'all') { - scenarios = readdirSync(join(__dirname, rootDirName)); + scenarios = glob.sync(join(__dirname, rootDirName, '**/data.ts')); } describe('Scenarios tests', () => { - scenarios.forEach((scenarioName) => { + scenarios.forEach((scenarioFileName) => { + const scenarioDir = path.dirname(scenarioFileName); + const scenarioName = path.basename(scenarioDir); describe(`${scenarioName}`, () => { - const scenarioDir = join(__dirname, rootDirName, scenarioName); const scenarios = ScenarioUtils.extractScenarios(scenarioDir); scenarios.forEach((scenario, index) => { it(`Scenario ${index}: ${Scenario.getTemplatePath(scenario)}`, async () => { diff --git a/test/scenario.test.ts b/test/scenario.test.ts index 32e9e62..1aeca0e 100644 --- a/test/scenario.test.ts +++ b/test/scenario.test.ts @@ -36,7 +36,7 @@ describe(`${scenarioName}:`, () => { } catch (error: any) { console.log('Actual result', JSON.stringify(result, null, 2)); console.log('Expected result', JSON.stringify(scenario.output, null, 2)); - expect(error.message).toEqual(scenario.error); + expect(error.message).toContain(scenario.error); } }); }); diff --git a/test/scenarios/arrays/template.jt b/test/scenarios/arrays/template.jt index 53340ec..3104f83 100644 --- a/test/scenarios/arrays/template.jt +++ b/test/scenarios/arrays/template.jt @@ -2,5 +2,5 @@ let a = [ "string1", `string2`, 'string3', "aa\"a", true, false, undefined, null, 20.02, .22, [1., 2, 3], {"b": [1, 2]} ]; -[...a[-2], a[0,8], a[1:6], a[-1].b[1]] +[...~r a[-2], a[0,8], a[1:6],~r a[-1].b[1]] diff --git a/test/scenarios/assignments/data.ts b/test/scenarios/assignments/data.ts index 9d712eb..9b7f1de 100644 --- a/test/scenarios/assignments/data.ts +++ b/test/scenarios/assignments/data.ts @@ -7,7 +7,7 @@ export const data: Scenario[] = [ }, output: { a: { - b: [12, 22], + b: [11, 13], 'c key': 4, }, }, diff --git a/test/scenarios/assignments/template.jt b/test/scenarios/assignments/template.jt index f16c553..2824683 100644 --- a/test/scenarios/assignments/template.jt +++ b/test/scenarios/assignments/template.jt @@ -1,5 +1,13 @@ -let a = 10 -let b = -a + 30 +let a = 3; +a*=3; +--a; +++a; +let b = -a + 30; +b-=1 +b/=2 +b+=1; +b--; +b++; let cKey = "c key"; let c = { a: { b: [a, b], [cKey]: 2 } } let {d, e, f} = {d: 2, e: 2, f: 1} diff --git a/test/scenarios/comparisons/data.ts b/test/scenarios/comparisons/data.ts index 68b4186..0974cfa 100644 --- a/test/scenarios/comparisons/data.ts +++ b/test/scenarios/comparisons/data.ts @@ -23,9 +23,6 @@ export const data: Scenario[] = [ true, true, true, - true, - true, - true, ], }, ]; diff --git a/test/scenarios/comparisons/template.jt b/test/scenarios/comparisons/template.jt index 567f744..945a949 100644 --- a/test/scenarios/comparisons/template.jt +++ b/test/scenarios/comparisons/template.jt @@ -6,10 +6,7 @@ 'IgnoreCase' == 'ignorecase', 'CompareWithCase' !== 'comparewithCase', 'CompareWithCase' === 'CompareWithCase', -'I contain' *= 'i', -'I contain' *== 'I', 'i' =* 'I contain', -'I contain' *== 'I', 'I' ==* 'I contain', 'I end with' $= 'With', 'I end with' $== 'with', diff --git a/test/scenarios/conditions/data.ts b/test/scenarios/conditions/data.ts index fec1cb4..ddfaa35 100644 --- a/test/scenarios/conditions/data.ts +++ b/test/scenarios/conditions/data.ts @@ -2,14 +2,57 @@ import { Scenario } from '../../types'; export const data: Scenario[] = [ { - templatePath: 'if-then.jt', + templatePath: 'empty_if.jt', + error: 'Empty statements are not allowed in loop and condtional expressions', + }, + { + templatePath: 'empty_then.jt', + error: 'Empty statements are not allowed in loop and condtional expressions', + }, + { + templatePath: 'if_block.jt', + input: { + a: -5, + }, + output: 'a <= 1', + }, + { + templatePath: 'if_block.jt', + input: { + a: 1, + }, + output: 'a <= 1', + }, + { + templatePath: 'if_block.jt', + input: { + a: 2, + }, + output: 'a > 1', + }, + { + templatePath: 'if_block.jt', + input: { + a: 3, + }, + output: 'a > 2', + }, + { + templatePath: 'if_block.jt', + input: { + a: 10, + }, + output: 'a > 3', + }, + { + templatePath: 'if_then.jt', input: { a: -5, }, output: 0, }, { - templatePath: 'if-then.jt', + templatePath: 'if_then.jt', input: { a: 5, }, @@ -40,21 +83,21 @@ export const data: Scenario[] = [ output: 15, }, { - templatePath: 'undefined-arr-cond.jt', + templatePath: 'undefined_arr_cond.jt', input: { products: [{ a: 1 }, { a: 2 }], }, output: 'no', }, { - templatePath: 'undefined-arr-cond.jt', + templatePath: 'undefined_arr_cond.jt', input: { products: [{ objectID: 1 }, { objectID: 2 }], }, output: 'yes', }, { - templatePath: 'undefined-arr-cond.jt', + templatePath: 'undefined_arr_cond.jt', input: { otherProperty: [{ objectID: 1 }, { objectID: 2 }], }, diff --git a/test/scenarios/conditions/empty_if.jt b/test/scenarios/conditions/empty_if.jt new file mode 100644 index 0000000..1fdbebc --- /dev/null +++ b/test/scenarios/conditions/empty_if.jt @@ -0,0 +1 @@ +.num > 0 ? {} \ No newline at end of file diff --git a/test/scenarios/conditions/empty_then.jt b/test/scenarios/conditions/empty_then.jt new file mode 100644 index 0000000..33ed3a4 --- /dev/null +++ b/test/scenarios/conditions/empty_then.jt @@ -0,0 +1 @@ +.num > 0 ? let a = .num : {} \ No newline at end of file diff --git a/test/scenarios/conditions/if_block.jt b/test/scenarios/conditions/if_block.jt new file mode 100644 index 0000000..384c1d0 --- /dev/null +++ b/test/scenarios/conditions/if_block.jt @@ -0,0 +1,10 @@ +(.a > 1) ? { + (.a > 2) ? { + (.a > 3) ? { + return "a > 3"; + } + return "a > 2"; + } + return "a > 1"; +} +"a <= 1" \ No newline at end of file diff --git a/test/scenarios/conditions/if-then.jt b/test/scenarios/conditions/if_then.jt similarity index 100% rename from test/scenarios/conditions/if-then.jt rename to test/scenarios/conditions/if_then.jt diff --git a/test/scenarios/conditions/undefined-arr-cond.jt b/test/scenarios/conditions/undefined_arr_cond.jt similarity index 100% rename from test/scenarios/conditions/undefined-arr-cond.jt rename to test/scenarios/conditions/undefined_arr_cond.jt diff --git a/test/scenarios/context_variables/filter.jt b/test/scenarios/context_variables/filter.jt index ee5a5db..5fb62bd 100644 --- a/test/scenarios/context_variables/filter.jt +++ b/test/scenarios/context_variables/filter.jt @@ -3,6 +3,6 @@ and then on the result rest of the path will be executed */ .{.[].length > 1}.().@item#idx.({ - a: item.a, + a: ~r item.a, idx: idx }) \ No newline at end of file diff --git a/test/scenarios/filters/array_filters.jt b/test/scenarios/filters/array_filters.jt index 6a2369d..0bcfce1 100644 --- a/test/scenarios/filters/array_filters.jt +++ b/test/scenarios/filters/array_filters.jt @@ -1,5 +1,5 @@ let a = [1, 2, 3, 4, 5, {"a": 1, "b": 2, "c": 3}]; [ a[2:], a[:3], a[3:5], a[...[1, 3]].[0, 1], - a[-2], a[1], a[:-2], a[-2:], a[-1]["a", "b"] + ~r a[-2], a[1], a[:-2], a[-2:], a[-1]["a", "b"] ] \ No newline at end of file diff --git a/test/scenarios/functions/data.ts b/test/scenarios/functions/data.ts index 8ec97ad..0aa3a1a 100644 --- a/test/scenarios/functions/data.ts +++ b/test/scenarios/functions/data.ts @@ -3,7 +3,7 @@ import { Scenario } from '../../types'; export const data: Scenario[] = [ { templatePath: 'function_calls.jt', - output: ['abc', undefined, undefined], + output: ['abc', null, undefined], }, { templatePath: 'js_date_function.jt', diff --git a/test/scenarios/increment_statements/data.ts b/test/scenarios/increment_statements/data.ts new file mode 100644 index 0000000..157386c --- /dev/null +++ b/test/scenarios/increment_statements/data.ts @@ -0,0 +1,28 @@ +import { Scenario } from '../../types'; + +export const data: Scenario[] = [ + { + templatePath: 'postfix_decrement_on_literal.jt', + error: 'Invalid postfix increment expression', + }, + { + templatePath: 'postfix_decrement_on_non_id.jt', + error: 'Invalid postfix increment expression', + }, + { + templatePath: 'postfix_increment_on_literal.jt', + error: 'Invalid postfix increment expression', + }, + { + templatePath: 'postfix_increment_on_non_id.jt', + error: 'Invalid postfix increment expression', + }, + { + templatePath: 'prefix_decrement_on_literal.jt', + error: 'Invalid prefix increment expression', + }, + { + templatePath: 'prefix_increment_on_literal.jt', + error: 'Invalid prefix increment expression', + }, +]; diff --git a/test/scenarios/increment_statements/postfix_decrement_on_literal.jt b/test/scenarios/increment_statements/postfix_decrement_on_literal.jt new file mode 100644 index 0000000..6a5ca8a --- /dev/null +++ b/test/scenarios/increment_statements/postfix_decrement_on_literal.jt @@ -0,0 +1 @@ +1-- \ No newline at end of file diff --git a/test/scenarios/increment_statements/postfix_decrement_on_non_id.jt b/test/scenarios/increment_statements/postfix_decrement_on_non_id.jt new file mode 100644 index 0000000..99e2dcb --- /dev/null +++ b/test/scenarios/increment_statements/postfix_decrement_on_non_id.jt @@ -0,0 +1 @@ +.a-- \ No newline at end of file diff --git a/test/scenarios/increment_statements/postfix_increment_on_literal.jt b/test/scenarios/increment_statements/postfix_increment_on_literal.jt new file mode 100644 index 0000000..1c8af92 --- /dev/null +++ b/test/scenarios/increment_statements/postfix_increment_on_literal.jt @@ -0,0 +1 @@ +1++ \ No newline at end of file diff --git a/test/scenarios/increment_statements/postfix_increment_on_non_id.jt b/test/scenarios/increment_statements/postfix_increment_on_non_id.jt new file mode 100644 index 0000000..d7118d4 --- /dev/null +++ b/test/scenarios/increment_statements/postfix_increment_on_non_id.jt @@ -0,0 +1 @@ +.a++ \ No newline at end of file diff --git a/test/scenarios/increment_statements/prefix_decrement_on_literal.jt b/test/scenarios/increment_statements/prefix_decrement_on_literal.jt new file mode 100644 index 0000000..bfbbe2b --- /dev/null +++ b/test/scenarios/increment_statements/prefix_decrement_on_literal.jt @@ -0,0 +1 @@ +--1 \ No newline at end of file diff --git a/test/scenarios/increment_statements/prefix_increment_on_literal.jt b/test/scenarios/increment_statements/prefix_increment_on_literal.jt new file mode 100644 index 0000000..e3bc4bd --- /dev/null +++ b/test/scenarios/increment_statements/prefix_increment_on_literal.jt @@ -0,0 +1 @@ +++1 \ No newline at end of file diff --git a/test/scenarios/loops/break_without_condition.jt b/test/scenarios/loops/break_without_condition.jt new file mode 100644 index 0000000..120ff93 --- /dev/null +++ b/test/scenarios/loops/break_without_condition.jt @@ -0,0 +1,3 @@ +for { + break; +} diff --git a/test/scenarios/loops/break_without_loop.jt b/test/scenarios/loops/break_without_loop.jt new file mode 100644 index 0000000..0fac5a1 --- /dev/null +++ b/test/scenarios/loops/break_without_loop.jt @@ -0,0 +1,2 @@ + +(.num < 0) ? break; \ No newline at end of file diff --git a/test/scenarios/loops/complex_loop.jt b/test/scenarios/loops/complex_loop.jt new file mode 100644 index 0000000..5ee5eb1 --- /dev/null +++ b/test/scenarios/loops/complex_loop.jt @@ -0,0 +1,10 @@ +let count = 0; +for (let i = 0; i < 5; i++) { + for (let j=0; j < 5; j++) { + i < j ? { + j > 4 ? continue; + count++; + } + } +} +count; \ No newline at end of file diff --git a/test/scenarios/loops/continue.jt b/test/scenarios/loops/continue.jt new file mode 100644 index 0000000..8f0bb42 --- /dev/null +++ b/test/scenarios/loops/continue.jt @@ -0,0 +1,7 @@ +let count = 0; +for (let i=0;i <= ^.num;i++) { + console.log(i); + i % 2 === 0 ? continue; + count+=i; +} +count \ No newline at end of file diff --git a/test/scenarios/loops/continue_without_condition.jt b/test/scenarios/loops/continue_without_condition.jt new file mode 100644 index 0000000..0d5d1b7 --- /dev/null +++ b/test/scenarios/loops/continue_without_condition.jt @@ -0,0 +1,3 @@ +for { + continue; +} \ No newline at end of file diff --git a/test/scenarios/loops/continue_without_loop.jt b/test/scenarios/loops/continue_without_loop.jt new file mode 100644 index 0000000..ff7c8c0 --- /dev/null +++ b/test/scenarios/loops/continue_without_loop.jt @@ -0,0 +1,2 @@ + +(.num < 0) ? continue; \ No newline at end of file diff --git a/test/scenarios/loops/data.ts b/test/scenarios/loops/data.ts new file mode 100644 index 0000000..706cbec --- /dev/null +++ b/test/scenarios/loops/data.ts @@ -0,0 +1,81 @@ +import { Scenario } from '../../types'; + +export const data: Scenario[] = [ + { + templatePath: 'break_without_condition.jt', + error: + 'return, throw, continue and break statements are only allowed as last statements in conditional expressions', + }, + { + templatePath: 'break_without_loop.jt', + error: 'encounted loop control outside loop', + }, + { + templatePath: 'complex_loop.jt', + output: 10, + }, + { + templatePath: 'continue_without_condition.jt', + error: + 'return, throw, continue and break statements are only allowed as last statements in conditional expressions', + }, + { + templatePath: 'continue_without_loop.jt', + error: 'encounted loop control outside loop', + }, + { + templatePath: 'continue.jt', + input: { + num: 10, + }, + output: 25, + }, + { + templatePath: 'empty_loop.jt', + error: 'Empty statements are not allowed in loop and condtional expressions', + }, + { + templatePath: 'just_for.jt', + input: { + num: 10, + }, + output: 55, + }, + { + input: { + num: 10, + }, + output: 55, + templatePath: 'no_init.jt', + }, + { + input: { + num: 10, + }, + output: 55, + templatePath: 'no_test.jt', + }, + { + input: { + num: 10, + }, + output: 55, + templatePath: 'no_update.jt', + }, + { + templatePath: 'statement_after_break.jt', + error: + 'return, throw, continue and break statements are only allowed as last statements in conditional expressions', + }, + { + templatePath: 'statement_after_continue.jt', + error: + 'return, throw, continue and break statements are only allowed as last statements in conditional expressions', + }, + { + input: { + num: 10, + }, + output: 55, + }, +]; diff --git a/test/scenarios/loops/empty_loop.jt b/test/scenarios/loops/empty_loop.jt new file mode 100644 index 0000000..e001913 --- /dev/null +++ b/test/scenarios/loops/empty_loop.jt @@ -0,0 +1 @@ +for {} \ No newline at end of file diff --git a/test/scenarios/loops/just_for.jt b/test/scenarios/loops/just_for.jt new file mode 100644 index 0000000..18f7caf --- /dev/null +++ b/test/scenarios/loops/just_for.jt @@ -0,0 +1,8 @@ +let count = 0; +let i = 0; +for { + i > ^.num ? break; + count+=i; + i++; +} +count \ No newline at end of file diff --git a/test/scenarios/loops/no_init.jt b/test/scenarios/loops/no_init.jt new file mode 100644 index 0000000..93dce73 --- /dev/null +++ b/test/scenarios/loops/no_init.jt @@ -0,0 +1,6 @@ +let count = 0; +let i = 0; +for (;i <= ^.num;i++) { + count+=i; +} +count \ No newline at end of file diff --git a/test/scenarios/loops/no_test.jt b/test/scenarios/loops/no_test.jt new file mode 100644 index 0000000..6546357 --- /dev/null +++ b/test/scenarios/loops/no_test.jt @@ -0,0 +1,6 @@ +let count = 0; +for (let i=0;;i++) { + i > ^.num ? break; + count+=i; +} +count \ No newline at end of file diff --git a/test/scenarios/loops/no_update.jt b/test/scenarios/loops/no_update.jt new file mode 100644 index 0000000..f4296fd --- /dev/null +++ b/test/scenarios/loops/no_update.jt @@ -0,0 +1,6 @@ +let count = 0; +for (let i=0;i <= ^.num;) { + count+=i; + i++; +} +count \ No newline at end of file diff --git a/test/scenarios/loops/statement_after_break.jt b/test/scenarios/loops/statement_after_break.jt new file mode 100644 index 0000000..1875720 --- /dev/null +++ b/test/scenarios/loops/statement_after_break.jt @@ -0,0 +1,6 @@ +for { + .num > 1 ? { + break; + let count = 0; + } +} \ No newline at end of file diff --git a/test/scenarios/loops/statement_after_continue.jt b/test/scenarios/loops/statement_after_continue.jt new file mode 100644 index 0000000..b14e7e3 --- /dev/null +++ b/test/scenarios/loops/statement_after_continue.jt @@ -0,0 +1,6 @@ +for { + .num > 1 ? { + continue; + let count = 0; + } +} \ No newline at end of file diff --git a/test/scenarios/loops/template.jt b/test/scenarios/loops/template.jt new file mode 100644 index 0000000..1ecfcce --- /dev/null +++ b/test/scenarios/loops/template.jt @@ -0,0 +1,5 @@ +let count = 0; +for (let i=0;i <= ^.num;i++) { + count+=i; +} +count \ No newline at end of file diff --git a/test/scenarios/paths/data.ts b/test/scenarios/paths/data.ts index 03ac6a0..6012d2a 100644 --- a/test/scenarios/paths/data.ts +++ b/test/scenarios/paths/data.ts @@ -164,5 +164,8 @@ export const data: Scenario[] = [ 4, 1, ], + options: { + defaultPathType: PathType.RICH, + }, }, ]; diff --git a/test/scenarios/return/data.ts b/test/scenarios/return/data.ts index a516931..5487785 100644 --- a/test/scenarios/return/data.ts +++ b/test/scenarios/return/data.ts @@ -1,6 +1,16 @@ import { Scenario } from '../../types'; export const data: Scenario[] = [ + { + templatePath: 'return_without_condition.jt', + error: + 'return, throw, continue and break statements are only allowed as last statements in conditional expressions', + }, + { + templatePath: 'statement_after_return.jt', + error: + 'return, throw, continue and break statements are only allowed as last statements in conditional expressions', + }, { input: 3, output: 1, diff --git a/test/scenarios/return/return_without_condition.jt b/test/scenarios/return/return_without_condition.jt new file mode 100644 index 0000000..699755f --- /dev/null +++ b/test/scenarios/return/return_without_condition.jt @@ -0,0 +1 @@ +return; \ No newline at end of file diff --git a/test/scenarios/return/statement_after_return.jt b/test/scenarios/return/statement_after_return.jt new file mode 100644 index 0000000..f12ef19 --- /dev/null +++ b/test/scenarios/return/statement_after_return.jt @@ -0,0 +1,4 @@ +.num > 1 ? { + return; + let count = 0; +} \ No newline at end of file diff --git a/test/scenarios/return/template.jt b/test/scenarios/return/template.jt index ca2e91b..7c63cf9 100644 --- a/test/scenarios/return/template.jt +++ b/test/scenarios/return/template.jt @@ -1,2 +1,4 @@ -(. % 2 === 0) ? return ./2; +(. % 2 === 0) ? { + return ./2; +} (. - 1)/2; \ No newline at end of file diff --git a/test/scenarios/selectors/template.jt b/test/scenarios/selectors/template.jt index bd2dd8e..baa88d0 100644 --- a/test/scenarios/selectors/template.jt +++ b/test/scenarios/selectors/template.jt @@ -1 +1 @@ -..d + .a * .b \ No newline at end of file +..d[0] + .a * .b \ No newline at end of file diff --git a/test/scenarios/throw/data.ts b/test/scenarios/throw/data.ts index 3faeeae..3a8db54 100644 --- a/test/scenarios/throw/data.ts +++ b/test/scenarios/throw/data.ts @@ -1,6 +1,11 @@ import { Scenario } from '../../types'; export const data: Scenario[] = [ + { + templatePath: 'statement_after_throw.jt', + error: + 'return, throw, continue and break statements are only allowed as last statements in conditional expressions', + }, { input: 3, error: 'num must be even', @@ -9,4 +14,9 @@ export const data: Scenario[] = [ input: 2, output: 1, }, + { + templatePath: 'throw_without_condition.jt', + error: + 'return, throw, continue and break statements are only allowed as last statements in conditional expressions', + }, ]; diff --git a/test/scenarios/throw/statement_after_throw.jt b/test/scenarios/throw/statement_after_throw.jt new file mode 100644 index 0000000..5b497ce --- /dev/null +++ b/test/scenarios/throw/statement_after_throw.jt @@ -0,0 +1,4 @@ +.num % 2 !== 0 ? { + throw new Error("num must be even"); + let count = 0; +} \ No newline at end of file diff --git a/test/scenarios/throw/throw_without_condition.jt b/test/scenarios/throw/throw_without_condition.jt new file mode 100644 index 0000000..566a389 --- /dev/null +++ b/test/scenarios/throw/throw_without_condition.jt @@ -0,0 +1 @@ +throw new Error("num must be even"); \ No newline at end of file diff --git a/test/utils/scenario.ts b/test/utils/scenario.ts index 4ee3a9f..6187fbc 100644 --- a/test/utils/scenario.ts +++ b/test/utils/scenario.ts @@ -1,12 +1,14 @@ import { readFileSync } from 'fs'; import { join } from 'path'; -import { JsonTemplateEngine } from '../../src'; +import { JsonTemplateEngine, PathType } from '../../src'; import { Scenario } from '../types'; export class ScenarioUtils { static createTemplateEngine(scenarioDir: string, scenario: Scenario): JsonTemplateEngine { const templatePath = join(scenarioDir, Scenario.getTemplatePath(scenario)); const template = readFileSync(templatePath, 'utf-8'); + scenario.options = scenario.options || {}; + scenario.options.defaultPathType = scenario.options.defaultPathType || PathType.SIMPLE; return JsonTemplateEngine.create(template, scenario.options); }