From f4f04e4ea2b96f703adaae69794c97cc294e6a73 Mon Sep 17 00:00:00 2001 From: Gili Tzabari Date: Thu, 29 Aug 2024 18:27:01 -0400 Subject: [PATCH 01/15] Synced implementation with Java implementation version 10.0 --- .eslintrc.mjs | 59 - .mocharc.cjs | 20 +- README.md | 45 +- build/LogFactory.mts | 2 +- build/Project.mts | 54 +- build/TestCompiler.mts | 2 +- build/mode.mts | 2 +- docs/Changelog.md | 41 +- docs/Colored_Diff.md | 45 +- docs/Features.md | 31 +- docs/String_Diff.md | 9 +- docs/Textual_Diff.md | 46 +- .eslintrc.d.mts => eslint.config.d.mts | 0 eslint.config.mjs | 62 + package.json | 82 +- pnpm-lock.yaml | 5880 ++++++++--------- src/ArrayValidator.mts | 142 - src/ArrayVerifier.mts | 154 - src/BooleanValidator.mts | 35 - src/BooleanVerifier.mts | 32 - src/ClassValidator.mts | 42 - src/ClassVerifier.mts | 46 - src/Configuration.mts | 234 - src/ContextLine.mts | 48 - src/DefaultJavascriptValidators.mts | 202 + src/DefaultRequirements.mts | 217 - src/GlobalConfiguration.mts | 120 +- src/GlobalRequirements.mts | 153 - src/InetAddressValidator.mts | 43 - src/InetAddressVerifier.mts | 49 - src/JavascriptAssertThat.mts | 130 + src/JavascriptCheckIf.mts | 132 + src/JavascriptRequireThat.mts | 117 + src/JavascriptValidators.mts | 90 + src/MapValidator.mts | 91 - src/MapVerifier.mts | 96 - src/MultipleFailuresError.mts | 56 + src/NumberValidator.mts | 26 - src/NumberVerifier.mts | 13 - src/ObjectValidator.mts | 17 - src/ObjectVerifier.mts | 12 - src/Requirements.mts | 313 - src/SetValidator.mts | 151 - src/SetVerifier.mts | 147 - src/StringValidator.mts | 107 - src/StringVerifier.mts | 121 - src/TerminalEncoding.mts | 55 +- src/Type.mts | 339 + src/ValidationFailure.mts | 193 +- src/ValidationFailures.mts | 130 + src/Validators.mts | 100 + src/extension/ExtensibleNumberValidator.mts | 138 - src/extension/ExtensibleNumberVerifier.mts | 154 - src/extension/ExtensibleObjectValidator.mts | 197 - src/extension/ExtensibleObjectVerifier.mts | 220 - src/index.mts | 38 +- src/internal/AbstractGlobalConfiguration.mts | 73 - src/internal/ArrayValidatorImpl.mts | 397 -- src/internal/ArrayVerifierImpl.mts | 105 - src/internal/BooleanValidatorImpl.mts | 55 - src/internal/BooleanVerifierImpl.mts | 49 - src/internal/ClassValidatorImpl.mts | 81 - src/internal/ClassVerifierImpl.mts | 39 - src/internal/Configuration.mts | 128 + src/internal/ConfigurationUpdater.mts | 94 + src/internal/InetAddressValidatorImpl.mts | 85 - src/internal/InetAddressVerifierImpl.mts | 43 - src/internal/MainGlobalConfiguration.mts | 64 - src/internal/MapValidatorImpl.mts | 151 - src/internal/MapVerifierImpl.mts | 99 - src/internal/Maps.mts | 33 - src/internal/MutableStringMappers.mts | 83 + src/internal/NumberValidatorImpl.mts | 30 - src/internal/NumberVerifierImpl.mts | 25 - src/internal/ObjectValidatorImpl.mts | 35 - src/internal/ObjectVerifierImpl.mts | 54 - src/internal/Objects.mts | 417 -- src/internal/SetValidatorImpl.mts | 356 - src/internal/SetVerifierImpl.mts | 99 - src/internal/SizeValidatorImpl.mts | 172 - src/internal/StringMapper.mts | 30 + src/internal/StringMappers.mts | 194 + src/internal/StringValidatorImpl.mts | 296 - src/internal/StringVerifierImpl.mts | 97 - src/internal/Strings.mts | 99 - src/internal/Terminal.mts | 166 - src/internal/VariableType.mts | 101 - src/internal/diff/AbstractColorWriter.mts | 171 - src/internal/diff/AbstractDiffWriter.mts | 241 - src/internal/diff/ContextGenerator.mts | 317 - src/internal/diff/DiffConstants.mts | 14 - src/internal/diff/DiffGenerator.mts | 440 -- src/internal/diff/DiffResult.mts | 67 - src/internal/diff/Node16Colors.mts | 25 - src/internal/diff/TextOnly.mts | 276 - .../extension/AbstractNumberValidator.mts | 282 - .../extension/AbstractNumberVerifier.mts | 101 - .../extension/AbstractObjectValidator.mts | 416 -- .../extension/AbstractObjectVerifier.mts | 209 - src/internal/internal.mts | 500 +- src/internal/message/BooleanMessages.mts | 31 + src/internal/message/ClassMessages.mts | 46 + src/internal/message/CollectionMessages.mts | 440 ++ src/internal/message/ComparableMessages.mts | 156 + src/internal/message/NumberMessages.mts | 154 + src/internal/message/ObjectMessages.mts | 34 + src/internal/message/StringMessages.mts | 198 + src/internal/message/UnquotedStringValue.mts | 28 + src/internal/message/ValidatorMessages.mts | 208 + .../message/diff/AbstractColorWriter.mts | 187 + .../message/diff/AbstractDiffWriter.mts | 225 + src/internal/message/diff/ColoredDiff.mts | 35 + .../message/diff/ContextGenerator.mts | 443 ++ src/internal/message/diff/DiffConstants.mts | 39 + src/internal/message/diff/DiffGenerator.mts | 541 ++ src/internal/message/diff/DiffResult.mts | 81 + src/internal/message/diff/DiffWriter.mts | 65 + src/internal/message/diff/Node16Colors.mts | 39 + .../diff/Node16MillionColors.mts | 22 +- .../{ => message}/diff/Node256Colors.mts | 6 +- src/internal/message/diff/TextOnly.mts | 131 + .../message/section/ContextSection.mts | 35 + .../message/section/MessageBuilder.mts | 188 + .../message/section/MessageSection.mts | 19 + .../message/section/StringSection.mts | 28 + .../scope/AbstractApplicationScope.mts | 55 + src/internal/scope/ApplicationScope.mts | 33 + src/internal/scope/DefaultProcessScope.mts | 36 + src/internal/scope/MainApplicationScope.mts | 38 + .../scope/MainGlobalConfiguration.mts | 55 + src/internal/scope/ProcessScope.mts | 23 + src/internal/util/AssertionError.mts | 24 + src/internal/util/Difference.mts | 102 + src/internal/{ => util}/IllegalStateError.mts | 5 + src/internal/util/ObjectAndSize.mts | 31 + src/internal/util/Strings.mts | 140 + src/internal/util/ValidationTarget.mts | 188 + .../validator/AbstractCollectionValidator.mts | 386 ++ src/internal/validator/AbstractValidator.mts | 413 ++ src/internal/validator/AbstractValidators.mts | 266 + src/internal/validator/ArrayValidatorImpl.mts | 70 + .../validator/BooleanValidatorImpl.mts | 61 + src/internal/validator/ClassValidatorImpl.mts | 58 + src/internal/validator/ErrorBuilder.mts | 21 + .../validator/JavascriptValidatorsImpl.mts | 239 + src/internal/validator/MapValidatorImpl.mts | 111 + src/internal/validator/Maps.mts | 31 + .../validator/MutableConfiguration.mts | 184 + .../validator/NumberValidatorImpl.mts | 370 ++ .../validator/ObjectSizeValidatorImpl.mts | 291 + .../validator/ObjectValidatorImpl.mts | 38 + src/internal/validator/Objects.mts | 545 ++ src/internal/{ => validator}/Pluralizer.mts | 0 src/internal/validator/SetValidatorImpl.mts | 48 + .../validator/StringValidatorImpl.mts | 178 + src/internal/validator/Terminal.mts | 125 + .../validator/ValidationFailureImpl.mts | 87 + src/validator/ArrayValidator.mts | 37 + src/validator/BooleanValidator.mts | 27 + src/validator/ClassValidator.mts | 41 + src/validator/MapValidator.mts | 66 + src/validator/NumberValidator.mts | 20 + src/validator/ObjectValidator.mts | 15 + src/validator/SetValidator.mts | 24 + src/validator/StringValidator.mts | 120 + src/validator/UnsignedNumberValidator.mts | 18 + .../component/CollectionComponent.mts | 515 ++ .../component/NegativeNumberComponent.mts | 36 + src/validator/component/NumberComponent.mts | 238 + .../component/PositiveNumberComponent.mts | 36 + .../component/ValidatorComponent.mts | 269 + .../component/ZeroNumberComponent.mts | 36 + test/ArrayTest.mts | 162 +- test/BooleanTest.mts | 45 +- test/ClassTest.mts | 86 - test/ConfigurationTest.mts | 60 +- test/DiffTest.mts | 930 +-- test/GenericTest.mts | 267 + test/InetAddressTest.mts | 249 - test/MapTest.mts | 144 +- test/NumberTest.mts | 193 +- test/ObjectTest.mts | 348 - test/ObjectsTest.mts | 88 +- test/RequirementsTest.mts | 102 +- test/SetTest.mts | 114 +- test/SizeTest.mts | 87 +- test/StringTest.mts | 105 +- test/TestApplicationScope.mts | 33 + test/TestGlobalConfiguration.mts | 60 +- test/ValidationFailureTest.mts | 75 +- tsconfig.json | 4 +- typedoc.json | 3 +- 192 files changed, 15882 insertions(+), 14291 deletions(-) delete mode 100644 .eslintrc.mjs rename .eslintrc.d.mts => eslint.config.d.mts (100%) create mode 100644 eslint.config.mjs delete mode 100644 src/ArrayValidator.mts delete mode 100644 src/ArrayVerifier.mts delete mode 100644 src/BooleanValidator.mts delete mode 100644 src/BooleanVerifier.mts delete mode 100644 src/ClassValidator.mts delete mode 100644 src/ClassVerifier.mts delete mode 100644 src/Configuration.mts delete mode 100644 src/ContextLine.mts create mode 100644 src/DefaultJavascriptValidators.mts delete mode 100644 src/DefaultRequirements.mts delete mode 100644 src/GlobalRequirements.mts delete mode 100644 src/InetAddressValidator.mts delete mode 100644 src/InetAddressVerifier.mts create mode 100644 src/JavascriptAssertThat.mts create mode 100644 src/JavascriptCheckIf.mts create mode 100644 src/JavascriptRequireThat.mts create mode 100644 src/JavascriptValidators.mts delete mode 100644 src/MapValidator.mts delete mode 100644 src/MapVerifier.mts create mode 100644 src/MultipleFailuresError.mts delete mode 100644 src/NumberValidator.mts delete mode 100644 src/NumberVerifier.mts delete mode 100644 src/ObjectValidator.mts delete mode 100644 src/ObjectVerifier.mts delete mode 100644 src/Requirements.mts delete mode 100644 src/SetValidator.mts delete mode 100644 src/SetVerifier.mts delete mode 100644 src/StringValidator.mts delete mode 100644 src/StringVerifier.mts create mode 100644 src/Type.mts create mode 100644 src/ValidationFailures.mts create mode 100644 src/Validators.mts delete mode 100644 src/extension/ExtensibleNumberValidator.mts delete mode 100644 src/extension/ExtensibleNumberVerifier.mts delete mode 100644 src/extension/ExtensibleObjectValidator.mts delete mode 100644 src/extension/ExtensibleObjectVerifier.mts delete mode 100644 src/internal/AbstractGlobalConfiguration.mts delete mode 100644 src/internal/ArrayValidatorImpl.mts delete mode 100644 src/internal/ArrayVerifierImpl.mts delete mode 100644 src/internal/BooleanValidatorImpl.mts delete mode 100644 src/internal/BooleanVerifierImpl.mts delete mode 100644 src/internal/ClassValidatorImpl.mts delete mode 100644 src/internal/ClassVerifierImpl.mts create mode 100644 src/internal/Configuration.mts create mode 100644 src/internal/ConfigurationUpdater.mts delete mode 100644 src/internal/InetAddressValidatorImpl.mts delete mode 100644 src/internal/InetAddressVerifierImpl.mts delete mode 100644 src/internal/MainGlobalConfiguration.mts delete mode 100644 src/internal/MapValidatorImpl.mts delete mode 100644 src/internal/MapVerifierImpl.mts delete mode 100644 src/internal/Maps.mts create mode 100644 src/internal/MutableStringMappers.mts delete mode 100644 src/internal/NumberValidatorImpl.mts delete mode 100644 src/internal/NumberVerifierImpl.mts delete mode 100644 src/internal/ObjectValidatorImpl.mts delete mode 100644 src/internal/ObjectVerifierImpl.mts delete mode 100644 src/internal/Objects.mts delete mode 100644 src/internal/SetValidatorImpl.mts delete mode 100644 src/internal/SetVerifierImpl.mts delete mode 100644 src/internal/SizeValidatorImpl.mts create mode 100644 src/internal/StringMapper.mts create mode 100644 src/internal/StringMappers.mts delete mode 100644 src/internal/StringValidatorImpl.mts delete mode 100644 src/internal/StringVerifierImpl.mts delete mode 100644 src/internal/Strings.mts delete mode 100644 src/internal/Terminal.mts delete mode 100644 src/internal/VariableType.mts delete mode 100644 src/internal/diff/AbstractColorWriter.mts delete mode 100644 src/internal/diff/AbstractDiffWriter.mts delete mode 100644 src/internal/diff/ContextGenerator.mts delete mode 100644 src/internal/diff/DiffConstants.mts delete mode 100644 src/internal/diff/DiffGenerator.mts delete mode 100644 src/internal/diff/DiffResult.mts delete mode 100644 src/internal/diff/Node16Colors.mts delete mode 100644 src/internal/diff/TextOnly.mts delete mode 100644 src/internal/extension/AbstractNumberValidator.mts delete mode 100644 src/internal/extension/AbstractNumberVerifier.mts delete mode 100644 src/internal/extension/AbstractObjectValidator.mts delete mode 100644 src/internal/extension/AbstractObjectVerifier.mts create mode 100644 src/internal/message/BooleanMessages.mts create mode 100644 src/internal/message/ClassMessages.mts create mode 100644 src/internal/message/CollectionMessages.mts create mode 100644 src/internal/message/ComparableMessages.mts create mode 100644 src/internal/message/NumberMessages.mts create mode 100644 src/internal/message/ObjectMessages.mts create mode 100644 src/internal/message/StringMessages.mts create mode 100644 src/internal/message/UnquotedStringValue.mts create mode 100644 src/internal/message/ValidatorMessages.mts create mode 100644 src/internal/message/diff/AbstractColorWriter.mts create mode 100644 src/internal/message/diff/AbstractDiffWriter.mts create mode 100644 src/internal/message/diff/ColoredDiff.mts create mode 100644 src/internal/message/diff/ContextGenerator.mts create mode 100644 src/internal/message/diff/DiffConstants.mts create mode 100644 src/internal/message/diff/DiffGenerator.mts create mode 100644 src/internal/message/diff/DiffResult.mts create mode 100644 src/internal/message/diff/DiffWriter.mts create mode 100644 src/internal/message/diff/Node16Colors.mts rename src/internal/{ => message}/diff/Node16MillionColors.mts (59%) rename src/internal/{ => message}/diff/Node256Colors.mts (60%) create mode 100644 src/internal/message/diff/TextOnly.mts create mode 100644 src/internal/message/section/ContextSection.mts create mode 100644 src/internal/message/section/MessageBuilder.mts create mode 100644 src/internal/message/section/MessageSection.mts create mode 100644 src/internal/message/section/StringSection.mts create mode 100644 src/internal/scope/AbstractApplicationScope.mts create mode 100644 src/internal/scope/ApplicationScope.mts create mode 100644 src/internal/scope/DefaultProcessScope.mts create mode 100644 src/internal/scope/MainApplicationScope.mts create mode 100644 src/internal/scope/MainGlobalConfiguration.mts create mode 100644 src/internal/scope/ProcessScope.mts create mode 100644 src/internal/util/AssertionError.mts create mode 100644 src/internal/util/Difference.mts rename src/internal/{ => util}/IllegalStateError.mts (71%) create mode 100644 src/internal/util/ObjectAndSize.mts create mode 100644 src/internal/util/Strings.mts create mode 100644 src/internal/util/ValidationTarget.mts create mode 100644 src/internal/validator/AbstractCollectionValidator.mts create mode 100644 src/internal/validator/AbstractValidator.mts create mode 100644 src/internal/validator/AbstractValidators.mts create mode 100644 src/internal/validator/ArrayValidatorImpl.mts create mode 100644 src/internal/validator/BooleanValidatorImpl.mts create mode 100644 src/internal/validator/ClassValidatorImpl.mts create mode 100644 src/internal/validator/ErrorBuilder.mts create mode 100644 src/internal/validator/JavascriptValidatorsImpl.mts create mode 100644 src/internal/validator/MapValidatorImpl.mts create mode 100644 src/internal/validator/Maps.mts create mode 100644 src/internal/validator/MutableConfiguration.mts create mode 100644 src/internal/validator/NumberValidatorImpl.mts create mode 100644 src/internal/validator/ObjectSizeValidatorImpl.mts create mode 100644 src/internal/validator/ObjectValidatorImpl.mts create mode 100644 src/internal/validator/Objects.mts rename src/internal/{ => validator}/Pluralizer.mts (100%) create mode 100644 src/internal/validator/SetValidatorImpl.mts create mode 100644 src/internal/validator/StringValidatorImpl.mts create mode 100644 src/internal/validator/Terminal.mts create mode 100644 src/internal/validator/ValidationFailureImpl.mts create mode 100644 src/validator/ArrayValidator.mts create mode 100644 src/validator/BooleanValidator.mts create mode 100644 src/validator/ClassValidator.mts create mode 100644 src/validator/MapValidator.mts create mode 100644 src/validator/NumberValidator.mts create mode 100644 src/validator/ObjectValidator.mts create mode 100644 src/validator/SetValidator.mts create mode 100644 src/validator/StringValidator.mts create mode 100644 src/validator/UnsignedNumberValidator.mts create mode 100644 src/validator/component/CollectionComponent.mts create mode 100644 src/validator/component/NegativeNumberComponent.mts create mode 100644 src/validator/component/NumberComponent.mts create mode 100644 src/validator/component/PositiveNumberComponent.mts create mode 100644 src/validator/component/ValidatorComponent.mts create mode 100644 src/validator/component/ZeroNumberComponent.mts delete mode 100644 test/ClassTest.mts create mode 100644 test/GenericTest.mts delete mode 100644 test/InetAddressTest.mts delete mode 100644 test/ObjectTest.mts create mode 100644 test/TestApplicationScope.mts diff --git a/.eslintrc.mjs b/.eslintrc.mjs deleted file mode 100644 index b825219..0000000 --- a/.eslintrc.mjs +++ /dev/null @@ -1,59 +0,0 @@ -export default { - "env": { - "browser": true, - "es2021": true, - "node": true - }, - "extends": [ - "eslint:recommended", - "plugin:import/recommended", - "plugin:import/typescript", - "plugin:@typescript-eslint/recommended", - // https://typescript-eslint.io/linting/typed-linting/ - "plugin:@typescript-eslint/recommended-type-checked" - ], - "settings": { - // https://github.com/import-js/eslint-plugin-import#typescript - "typescript": true, - "node": true, - "import/parsers": { - "@typescript-eslint/parser": [".mts"] - }, - // https://github.com/kriasoft/react-starter-kit/issues/1180#issuecomment-436753540 - "import/resolver": { - "typescript": { - "alwaysTryTypes": true - } - } - }, - "ignorePatterns": [ - "node_modules", - "/target/**", - "./*.mts" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module", - "project": "tsconfig.json" - }, - "plugins": [ - "@typescript-eslint", - "eslint-plugin-tsdoc" - ], - "rules": { - // "indent" is broken for Typescript: https://github.com/typescript-eslint/typescript-eslint/issues/1824 - "indent": "off", - "quotes": [ - "error", - "double" - ], - "semi": [ - "error", - "always" - ], - "tsdoc/syntax": "warn", - // Prevents "while(true)" - "no-constant-condition": "off" - } -}; \ No newline at end of file diff --git a/.mocharc.cjs b/.mocharc.cjs index d733ac2..2091edc 100644 --- a/.mocharc.cjs +++ b/.mocharc.cjs @@ -1,17 +1,9 @@ module.exports = { - // https://github.com/mochajs/mocha/issues/4726#issuecomment-903213780 - loader: ["ts-node/esm"], - require: ["ts-node/register", "source-map-support/register.js"], + // https://stackoverflow.com/a/78098005/14731 + "node-option": ["import=tsx"], + require: ["source-map-support/register.js"], reporter: "spec", - extensions: - [ - ".mts" - ], - include: - [ - "**/*.mts" - ], - exclude: [ - "**/*.d.mts" - ] + extension: ["mts"], + include: ["**/*.mts"], + exclude: ["**/*.d.mts"] }; \ No newline at end of file diff --git a/README.md b/README.md index a86f1ca..2d75734 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ [![npm version](https://badge.fury.io/js/%40cowwoc%2Frequirements.svg)](https://badge.fury.io/js/%40cowwoc%2Frequirements) [![build-status](https://github.com/cowwoc/requirements.js/workflows/Build/badge.svg)](https://github.com/cowwoc/requirements.js/actions?query=workflow%3ABuild) -# checklist Fluent API for Design Contracts +# checklist Fluent API for Design Contracts -[![API](https://img.shields.io/badge/api_docs-5B45D5.svg)](https://cowwoc.github.io/requirements.js/3.4.0/docs/api/) -[![Changelog](https://img.shields.io/badge/changelog-A345D5.svg)](wiki/Changelog.md) +[![API](https://img.shields.io/badge/api_docs-5B45D5.svg)](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/) +[![Changelog](https://img.shields.io/badge/changelog-A345D5.svg)](docs/Changelog.md) [![java](https://img.shields.io/badge/other%20languages-java-457FD5.svg)](../../../requirements.java) A [fluent API](https://en.wikipedia.org/wiki/Fluent_interface) for enforcing -[design contracts](https://en.wikipedia.org/wiki/Design_by_contract) with [automatic message generation](#usage). +[design contracts](https://en.wikipedia.org/wiki/Design_by_contract) +with [automatic message generation](#usage). ✔️ Easy to use ✔️ Fast @@ -17,17 +18,15 @@ A [fluent API](https://en.wikipedia.org/wiki/Fluent_interface) for enforcing To get started, add this dependency: ```shell -npm install --save @cowwoc/requirements@3.4.0 +npm install --save @cowwoc/requirements@4.0.0 ``` or [pnpm](https://pnpm.io/): ```shell -pnpm add @cowwoc/requirements@3.4.0 +pnpm add @cowwoc/requirements@4.0.0 ``` -The contents of the API classes depend on which [modules](wiki/Supported_Libraries.md) are enabled. - ## Sample Code ```typescript @@ -47,9 +46,9 @@ class PublicAPI requireThat(age as unknown, "age").isNumber().isBetween(18, 30); // Methods that conduct runtime type-checks, such as isString() or isNotNull(), update the - // compile-time type returned by getActual(). - const nameIsString: string = requireThat(name as unknown, "name").isString().getActual(); - const address: Address = requireThat(address as unknown, "address").isInstance(Address).getActual(); + // compile-time type returned by getValue(). + const nameIsString: string = requireThat(name as unknown, "name").isString().getValue(); + const address: Address = requireThat(address as unknown, "address").isInstance(Address).getValue(); } } @@ -79,24 +78,24 @@ Actual: 15 ## Features -* [Automatic message generation](wiki/Features.md#automatic-message-generation) -* [Diffs provided whenever possible](wiki/Features.md#diffs-provided-whenever-possible) -* [Assertion support](wiki/Features.md#assertion-support) -* [Grouping nested requirements](wiki/Features.md#grouping-nested-requirements) -* [String diff](wiki/Features.md#string-diff) +* [Automatic message generation](docs/Features.md#automatic-message-generation) +* [Diffs provided whenever possible](docs/Features.md#diffs-provided-whenever-possible) +* [Assertion support](docs/Features.md#assertion-support) +* [Grouping nested requirements](docs/Features.md#grouping-nested-requirements) +* [String diff](docs/Features.md#string-diff) ## Getting Started The best way to learn about the API is using your IDE's auto-complete engine. There are six entry points you can navigate from: -* [requireThat(value, name)](https://cowwoc.github.io/requirements.js/3.4.0/docs/api/module-DefaultRequirements.html#~requireThat) -* [validateThat(value, name)](https://cowwoc.github.io/requirements.js/3.4.0/docs/api/module-DefaultRequirements.html#~validateThat) -* [assertThat(Function)](https://cowwoc.github.io/requirements.js/3.4.0/docs/api/module-DefaultRequirements.html#~assertThat) -* [assertThatAndReturn(Function)](https://cowwoc.github.io/requirements.js/3.4.0/docs/api/module-DefaultRequirements.html#~assertThatAndReturn) +* [requireThat(value, name)](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/module-DefaultRequirements.html#~requireThat) +* [validateThat(value, name)](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/module-DefaultRequirements.html#~validateThat) +* [assertThat(Function)](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/module-DefaultRequirements.html#~assertThat) +* [assertThatAndReturn(Function)](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/module-DefaultRequirements.html#~assertThatAndReturn) -* [Requirements](https://cowwoc.github.io/requirements.js/3.4.0/docs/api/module-Requirements-Requirements.html) -* [GlobalRequirements](https://cowwoc.github.io/requirements.js/3.4.0/docs/api/module-GlobalRequirements-GlobalRequirements.html) +* [Requirements](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/module-Requirements-Requirements.html) +* [GlobalRequirements](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/module-GlobalRequirements-GlobalRequirements.html) ## Best practices @@ -104,6 +103,8 @@ There are six entry points you can navigate from: * Use `assertThat()` to verify object invariants and method post-conditions. This results in excellent performance when assertions are disabled. Have your cake and eat it too! +* Don't bother validating any constraints that are already enforced by the Typescript compiler (such as the + type of a variable) unless it will result in silent failures or security vulnerabilities when violated. ## Related Projects diff --git a/build/LogFactory.mts b/build/LogFactory.mts index 3f3ff88..01d29cb 100644 --- a/build/LogFactory.mts +++ b/build/LogFactory.mts @@ -13,7 +13,7 @@ const BgRed = "\x1b[41m"; class LogFactory { /** - * @param name the name of the logger + * @param name - the name of the logger */ public static getLogger(name: string): Logger { diff --git a/build/Project.mts b/build/Project.mts index 2f2b0f2..02dd714 100644 --- a/build/Project.mts +++ b/build/Project.mts @@ -17,11 +17,11 @@ import {minify} from "terser"; import {spawn} from "child_process"; import {LogFactory} from "./LogFactory.mjs"; import {default as chokidar} from "chokidar"; -import eslintConfig from "../.eslintrc.mjs"; +import eslintConfig from "../eslint.config.mjs"; import {Logger} from "winston"; import {mode} from "./mode.mjs"; import parseArgs from "minimist"; -import _ from "lodash"; +import debounce from "lodash.debounce"; class Project @@ -47,19 +47,19 @@ class Project console.time("lintTypescript"); const eslint = new ESLint({ baseConfig: eslintConfig, - cache: true, - "overrideConfig": { - parserOptions: { - debugLevel: false - } - } + cache: true }); let results; try { results = await eslint.lintFiles(sources); + const resultsMeta: ESLint.LintResultData = + { + cwd: process.cwd(), + rulesMeta: eslint.getRulesMetaForResults(results) + }; const formatter = await eslint.loadFormatter("stylish"); - const resultText = await formatter.format(results); + const resultText = await formatter.format(results, resultsMeta); if (resultText) this.log.info(resultText); } @@ -151,7 +151,8 @@ class Project console.time("bundleForBrowser"); // Need to cast plugins to Function due to bug in type definitions. - // WORKAROUND: https://github.com/algolia/algoliasearch-client-javascript/issues/1431#issuecomment-1568529321 + // WORKAROUND: + // https://github.com/algolia/algoliasearch-client-javascript/issues/1431#issuecomment-1568529321 const plugins: Plugin[] = [ rollupCommonjs, (rollupTypescript as unknown as Function)({ @@ -305,7 +306,7 @@ class Project const promise = new Promise(function(resolve, reject) { // https://stackoverflow.com/a/14231570/14731 - const process = spawn(c8Path, [mochaPath, "./test/**/*.mts", "--mode=" + mode], + const process = spawn(c8Path, [mochaPath, "--parallel", "./test/**/*.mts", "--mode=" + mode], { shell: true, stdio: "inherit" @@ -350,6 +351,22 @@ class Project console.timeEnd("build"); } + public async clean() + { + console.time("clean"); + const typescriptFiles = await this.getTypescriptFiles(); + for (const file of typescriptFiles) + { + let basePath = path.posix.basename(file); + basePath = basePath.substring(0, basePath.lastIndexOf(".")); + if (fs.existsSync(basePath + ".mjs")) + await fs.promises.unlink(basePath + ".mjs"); + if (fs.existsSync(basePath + ".mjs.map")) + await fs.promises.unlink(basePath + ".mjs.map"); + } + console.timeEnd("clean"); + } + /** * @param paths the paths to watch * @param callback the callback to notify after a path is updated. @@ -378,7 +395,7 @@ class Project await callback(posixPaths); } - const queueUpdate = _.debounce(processUpdate, 500); + const queueUpdate = debounce(processUpdate, 500); const onUpdate = async (changed: string, stats: fs.Stats) => { changes.add(changed); @@ -427,10 +444,23 @@ switch (command._[0]) await project.build(); break; } + case "clean": + { + await project.clean(); + break; + } case "watch": { await project.watch(); break; } + default: + { + // Use POSIX paths across all platforms + const posixPath = url.fileURLToPath(import.meta.url).split(path.sep).join(path.posix.sep); + const __filename = path.posix.basename(posixPath); + const log = LogFactory.getLogger(__filename); + log.error(`Unknown command: ${command._[0]}`); + } } console.timeEnd("Time elapsed"); \ No newline at end of file diff --git a/build/TestCompiler.mts b/build/TestCompiler.mts index 279ee61..f8fedad 100644 --- a/build/TestCompiler.mts +++ b/build/TestCompiler.mts @@ -33,7 +33,7 @@ class TestCompiler * Compiles a code snippet. * * @param snippet - the code to compile - * @return the compiler warnings and errors + * @returns the compiler warnings and errors */ public compile(snippet: string) { diff --git a/build/mode.mts b/build/mode.mts index 0785d70..f2ca708 100644 --- a/build/mode.mts +++ b/build/mode.mts @@ -2,7 +2,7 @@ import parseArgs from "minimist"; const env = parseArgs(process.argv.slice(2)); let mode = env.mode; -if (typeof (mode) === "undefined") +if (mode === undefined) mode = "DEBUG"; export {mode}; \ No newline at end of file diff --git a/docs/Changelog.md b/docs/Changelog.md index 5ae10c0..8cc74b1 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,9 +1,32 @@ Minor updates involving cosmetic changes have been omitted from this list. See https://github.com/cowwoc/requirements.java/commits/master for a full list. +## Version 4.0 - ? + +* Breaking changes: + * Replaced `validateThat()` with `checkIf()`. + * Use consistent parameter ordering across the entire API: `(value, name)`. + * Adding contextual information now looks like this: `requireThat().withContext(value, name)` + * Added `Validator.and(validation: (validator: S) => void)` to nest validations. + * Replaced `isBetweenClosed(min, max)` with `isBetween(min, true, max, true)`. + * Removed ability to configure validators in order to simplify API. + * Renamed `StringValidator.isInteger()` to `asPrimitiveInteger()` and `isCharacter()` + to `asPrimitiveCharacter()`. + * Replaced `Validator.elseGetMessages()` with `Validator.getFailures().getMessages()`. + * Replaced `Validator.elseGetException()` with `Validator.getFailures().getException()`. + * Dropped the `isOneOf()` and `isNotOneOf()` functionality yet again. I haven't figured out a good + design for this yet. +* New features: + * Added `validationFailed()` and `getValueOrDefault()` to all validators. + * Added `StringValidator.matches(regex)`. + * Replaced `ClassValidator.isSupertypeOf()`, `isSubtypeOf()` with `Type.isSupertypeOf()`. +* Improvements + * If `checkIf()` cannot run validations due to a null value, the expected conditions are still reported. + ## Version 3.4.0 - 2023/12/04 -* Breaking changes: The following `ObjectVerifier` methods now throw `TypeError` instead of `RangeError` if the actual +* Breaking changes: The following `ObjectVerifier` methods now throw `TypeError` instead of `RangeError` if + the actual value does not have the desired type: * `isNull()` * `isNotNull()` @@ -72,8 +95,9 @@ https://github.com/cowwoc/requirements.java/commits/master for a full list. The latter is used for assertions with a return value. This change improves the runtime performance of `assertThat()` and reduces code duplication across the library. - * Removed `Object.isActualAvailable()` in favor of `Requirements.assertionsAreEnabled()`. - * `Requirements` no longer returns copies on modification. Added `Requirements.copy()` to facilitate old behavior. + * Removed `Object.isActualAvailable()` in favor of `Requirements.assumeThatEnabled()`. + * `Requirements` no longer returns copies on modification. Added `Requirements.copy()` to facilitate old + behavior. * Documentation: Migrated from JSDoc to Typedoc. * Build: Migrated from yarn to pnpm. @@ -118,15 +142,16 @@ https://github.com/cowwoc/requirements.java/commits/master for a full list. ## Version 2.0.9 - 2020/06/17 * Bugfixes - * Documentation: `Configuration.getContext()` returns a `Map` not an `Array`. + * Documentation: `Configuration.getContext()` returns a `Map` not + an `Array`. * Improvements * Resolved CVE-2020-7662 security vulnerability by upgrading dependencies. ## Version 2.0.8 - 2020/05/07 * Bugfixes - * `ValidationFailure.mergeContext()` was throwing an exception if the global configuration contained a failure - context. + * `ValidationFailure.mergeContext()` was throwing an error if the global configuration contained a + failure context. ## Version 2.0.7 - 2020/04/20 @@ -136,8 +161,8 @@ https://github.com/cowwoc/requirements.java/commits/master for a full list. ## Version 2.0.5 - 2020/04/19 * Improvements - * If a failure message is longer than the terminal width, push the expected value from the failure message to the - exception context. This helps failure messages remain readable in the face of long values. + * If a failure message is longer than the terminal width, push the expected value from the failure message + to the error context. This helps failure messages remain readable in the face of long values. * Added `GlobalConfiguration.getTerminalWidth()`, `withDefaultTerminalWidth()`, `withTerminalWidth()`. ## Version 2.0.0 - 2020/04/15 diff --git a/docs/Colored_Diff.md b/docs/Colored_Diff.md index 34c31c8..46d14bb 100644 --- a/docs/Colored_Diff.md +++ b/docs/Colored_Diff.md @@ -7,8 +7,10 @@ A colored diff looks like this: * Red characters need to be deleted from `Actual`. * Uncolored characters are equal in `Actual` and `Expected`. * Green characters need to be inserted into `Actual`. -* Characters in the opposite direction of insertions and deletions are padded with `/` characters to line up the strings - vertically. This padding does not contribute any characters to the string it is found in. Read on for concrete +* Characters in the opposite direction of insertions and deletions are padded with `/` characters to line up + the strings + vertically. This padding does not contribute any characters to the string it is found in. Read on for + concrete examples. ## Example 1: insert @@ -51,7 +53,8 @@ results in the following diff: Meaning: * To go from `Actual` to `Expected` we need to insert three spaces at the beginning of `Actual`. -* There are no `///` characters in `Expected` in front of "foo". This padding is used to line up the strings vertically. +* There are no `///` characters in `Expected` in front of "foo". This padding is used to line up the strings + vertically. ## Example 4: delete, keep, insert @@ -69,17 +72,20 @@ Meaning, we need to: * Delete "foos". * Keep "ball". * Insert "room". -* There is no whitespace before "ballroom" or after "foosball". This padding is used to line up the strings vertically. +* There is no whitespace before "ballroom" or after "foosball". This padding is used to line up the strings + vertically. ## Example 5: Objects with the same toString() that are not equal * If objects are not equal, and their `toString()` values differ, we output their String representations. -* If the `toString()` values are equal, but their types differ, we output the string representation of `Actual` followed - by the two types (i.e. `Actual.class` vs `Expected.class`). -* If their classes are equal, but their `hashCode()` values differ, we output the string representation of `Actual` +* If the `toString()` values are equal, but their types differ, we output the string representation + of `Actual` followed + by the two types (i.e. `Type.of(Actual)` vs `Type.of(Expected)`). +* If their classes are equal, but their `hashCode()` values differ, we output the string representation + of `Actual` followed by the two hashcodes (i.e. `Actual.hashCode()` vs `Expected.hashCode()`). -For example: +For example, ```text Actual = "null" @@ -95,9 +101,9 @@ results in the following diff: When comparing multi-line strings: * `Actual` and `Expected` are followed by a line number. -* Lines that are identical (with the exception of the first and last line) are omitted. +* Lines that are identical (except the first and last line) are omitted. -For example: +For example, ```text Actual = "first\nsecond\nfoo\nforth\nfifth" @@ -116,12 +122,14 @@ Meaning: Lines always end with `\n` or `\0`. The former denotes a newline. The latter denotes the end of the string. -Lines ending with "\n\n" or "\0\0" represents the literal string "\n" followed by a newline character, or the literal +Lines ending with "\n\n" or "\0\0" represents the literal string "\n" followed by a newline character, or the +literal string "\0" followed by the end of string, respectively. ## Example 7: Missing Line Numbers -When `Actual` or `Expected` contain a line that does not have a corresponding line on the other side we omit the +When `Actual` or `Expected` contain a line that does not have a corresponding line on the other side we omit +the latter's line number. ```text @@ -144,13 +152,18 @@ Meaning: When comparing lists or arrays: * `Actual` and `Expected` are followed by the element's index number, followed by a line number. -* Indexes that are identical (with the exception of the first and last line) are omitted. +* Indexes that are identical (except the first and last line) are omitted. -For example: +For example, ```java -Actual = List.of("1", "foo\nbar", "3"); -Expected = List.of("1", "bar\nfoo", "3"); +Actual =List. + +of("1","foo\nbar","3"); + +Expected =List. + +of("1","bar\nfoo","3"); ``` results in the following diff: diff --git a/docs/Features.md b/docs/Features.md index bcf7620..60cd10d 100644 --- a/docs/Features.md +++ b/docs/Features.md @@ -29,18 +29,12 @@ Actual : [2, 3, 4, 6] Missing: [1, 5] ``` -## Clean stack-traces - -This API's classes do not show up in your stack-traces. - -```text -TypeError: actual may not be null -``` - ## Assertion support -All verifiers allocate memory which is especially hard to justify given that most checks are never going to fail. If -you need to run in a high-performance, zero allocation environment (to reduce latency and jitter) look no further than +All verifiers allocate memory which is especially hard to justify given that most checks are never going to +fail. If +you need to run in a high-performance, zero allocation environment (to reduce latency and jitter) look no +further than `DefaultRequirements.assertThat()`. `assertThat()` skips verification if assertions are disabled. `DefaultRequirements` might be less flexible @@ -49,9 +43,11 @@ performance and no allocations if assertions are disabled. ## Grouping nested requirements -Some classes provide a mechanism for grouping nested requirements. For example, `MapVerifier` has methods `keys()` and -`keys(consumer)`, `values()` and `values(consumer)`. This enables one to group requirements that share the same parent. -For example: +Some classes provide a mechanism for grouping nested requirements. For example, `MapVerifier` has +methods `keys()` and +`keys(consumer)`, `values()` and `values(consumer)`. This enables one to group requirements that share the +same parent. +For example, ```typescript const nameToAge = new Map(); @@ -66,13 +62,14 @@ can be rewritten as: ```typescript requireThat(nameToAge, "nameToAge").asMap(). - keys(k -> k.containsAll(["Leah", "Nathaniel"])). - values(v -> v.containsAll([3, 1])); + keys(k => k.containsAll(["Leah", "Nathaniel"])). + values(v => v.containsAll([3, 1])); ``` ## String diff -When a [String comparison](https://cowwoc.github.io/requirements.js/3.4.0/docs/api/ObjectVerifier.html#isEqualTo) +When +a [String comparison](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/ObjectVerifier.html#isEqualTo) fails, the library outputs a [diff](String_Diff.md) of the values being compared. ![colored-diff-example4.png](colored-diff-example4.png) @@ -90,7 +87,7 @@ class Player constructor(name) { - this.name = requireThat(name, "name").isNotEmpty().getActual(); + this.name = requireThat(name, "name").isNotEmpty().getValue(); } } ``` \ No newline at end of file diff --git a/docs/String_Diff.md b/docs/String_Diff.md index 502358c..5a425cb 100644 --- a/docs/String_Diff.md +++ b/docs/String_Diff.md @@ -1,11 +1,14 @@ -When a [String comparison](https://cowwoc.github.io/requirements.js/3.4.0/docs/api/ObjectVerifier.html#isEqualTo) +When +a [String comparison](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/ObjectVerifier.html#isEqualTo) fails, the library outputs a [diff](https://en.wikipedia.org/wiki/Diff) of the values being compared. -Depending on the terminal capability, you will see a [Textual](Textual_Diff.md) or [Colored](Colored_Diff.md) diff. +Depending on the terminal capability, you will see a [Textual](Textual_Diff.md) or [Colored](Colored_Diff.md) +diff. # Overriding Terminal Detection -We disable colors if stdout is redirected. This doesn't necessarily mean that ANSI codes are not supported, but we chose +We disable colors if stdout is redirected. This doesn't necessarily mean that ANSI codes are not supported, +but we chose to err on the side of caution. Users can override this behavior by invoking [GlobalRequirements.withTerminalEncoding()](https://cowwoc.github.io/requirements.js/3.2.3/docs/api/module-GlobalRequirements-GlobalRequirements.html#.withTerminalEncoding). \ No newline at end of file diff --git a/docs/Textual_Diff.md b/docs/Textual_Diff.md index 69550f0..2300df2 100644 --- a/docs/Textual_Diff.md +++ b/docs/Textual_Diff.md @@ -16,7 +16,8 @@ Expected: ballroom\0 * When `+` is present, `Expected` is padded to line up vertically with `Actual`. * The padding is not part of `Actual` and `Expected`'s value, respectively. Read on for concrete examples. * Lines always end with `\n` or `\0`. The former denotes a newline. The latter denotes the end of the string. -* Lines ending with "\n\n" or "\0\0" represents the literal string "\n" followed by a newline character, or the literal +* Lines ending with "\n\n" or "\0\0" represents the literal string "\n" followed by a newline character, or + the literal string "\0" followed by the end of string, respectively. ## Example 1: insert @@ -71,7 +72,8 @@ Expected: foo\0 Meaning: * To go from `Actual` to `Expected` we need to insert three spaces at the beginning of `Actual`. -* There is no whitespace in `Expected` in front of "foo". This padding is used to line up the strings vertically. +* There is no whitespace in `Expected` in front of "foo". This padding is used to line up the strings + vertically. ## Example 4: delete, keep, insert @@ -93,17 +95,17 @@ Meaning, we need to: * Delete "foos". * Keep "ball". * Insert "room". -* There is no whitespace before "ballroom" or after "foosball". This padding is used to line up the strings vertically. +* There is no whitespace before "ballroom" or after "foosball". This padding is used to line up the strings + vertically. ## Example 5: Objects with the same toString() that are not equal * If objects are not equal, and their `toString()` values differ, we output their String representations. -* If the `toString()` values are equal, but their types differ, we output the string representation of `Actual` followed - by the two types (i.e. `Actual.class` vs `Expected.class`). -* If their classes are equal, but their `hashCode()` values differ, we output the string representation of `Actual` - followed by the two hashcodes (i.e. `Actual.hashCode()` vs `Expected.hashCode()`). +* If the `toString()` values are equal, but their types differ, we output the string representation + of `Actual` followed + by the two types (i.e. `Type.of(Actual)` vs `Type.of(Expected)`). -For example: +For example, ```text Actual = "null" @@ -113,10 +115,10 @@ Expected = null results in the following diff: ```text -Actual : null -Actual.class : java.lang.String \0 -Diff : ----------------++++ -Expected.class: null\0 +Actual : null +Type.of(Actual) : string \0 +Diff : ------++++ +Type.of(Expected): null\0 ``` ## Example 6: Multi-line Strings @@ -125,9 +127,9 @@ When comparing multi-line strings: * We display the diff on a per-line basis. * `Actual` and `Expected` are followed by a 0-based line number. -* Lines that are identical (with the exception of the first and last line) are omitted. +* Lines that are identical (except the first and last line) are omitted. -For example: +For example, ```text Actual = "first\nsecond\nfoo\nforth\nfifth" @@ -160,7 +162,8 @@ Meaning: ## Example 7: Missing Line Numbers -When `Actual` or `Expected` contain a line that does not have a corresponding line on the other side we omit the +When `Actual` or `Expected` contain a line that does not have a corresponding line on the other side we omit +the latter's line number. ```text @@ -192,14 +195,19 @@ When comparing lists or arrays: * We display the diff on a per-element basis. * `Actual` and `Expected` are followed by the element's index number. -* Indexes that are identical (with the exception of the first and last line) are omitted. +* Indexes that are identical (except the first and last line) are omitted. -For example: +For example, ```java -Actual = List.of("1", "foo\nbar", "3"); -Expected = List.of("1", "bar\nfoo", "3"); +Actual =List. + +of("1","foo\nbar","3"); + +Expected =List. + +of("1","bar\nfoo","3"); ``` results in the following diff: diff --git a/.eslintrc.d.mts b/eslint.config.d.mts similarity index 100% rename from .eslintrc.d.mts rename to eslint.config.d.mts diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..2756ddc --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,62 @@ +import esLint from "@eslint/js"; +// https://typescript-eslint.io/getting-started#step-2-configuration +import tsLint from "typescript-eslint"; +import tsdoc from "eslint-plugin-tsdoc"; +import typescriptParser from "@typescript-eslint/parser"; + +export default [ + esLint.configs.recommended, + // https://typescript-eslint.io/getting-started/typed-linting/ + ...tsLint.configs.strict, + ...tsLint.configs.recommendedTypeChecked, + { + "settings": { + // https://github.com/import-js/eslint-plugin-import#typescript + "typescript": true, + "node": true, + "import/parsers": { + "@typescript-eslint/parser": [".mts"] + }, + // https://github.com/kriasoft/react-starter-kit/issues/1180#issuecomment-436753540 + "import/resolver": { + "typescript": { + "alwaysTryTypes": true + } + } + }, + ignores: [ + "**/node_modules", + "/target/**", + "./*.mts" + ], + languageOptions: { + parser: typescriptParser, + ecmaVersion: "latest", + sourceType: "module", + // https://typescript-eslint.io/getting-started/typed-linting/ + parserOptions: { + project: true, + // "project": "tsconfig.json" + tsconfigRootDir: import.meta.dirname + } + }, + plugins: { + tsdoc + }, + "rules": { + // "indent" is broken for Typescript: https://github.com/typescript-eslint/typescript-eslint/issues/1824 + "indent": "off", + "quotes": [ + "error", + "double" + ], + "semi": [ + "error", + "always" + ], + "tsdoc/syntax": "warn", + // Prevents "while(true)" + "no-constant-condition": "off" + } + } +]; \ No newline at end of file diff --git a/package.json b/package.json index 9e5b1be..66658c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cowwoc/requirements", - "version": "3.4.0", + "version": "4.0.0", "keywords": [ "preconditions", "postconditions", @@ -18,8 +18,9 @@ "url": "https://github.com/cowwoc/requirements.js/" }, "scripts": { - "build.debug": "node node_modules/tsx/dist/cli.mjs build/Project.mts build --mode=DEBUG", - "build.release": "node node_modules/tsx/dist/cli.mjs build/Project.mts build --mode=RELEASE" + "clean": "node node_modules/tsx/dist/cli.mjs build/Project.mts clean", + "build.debug": "node node_modules/tsx/dist/cli.mjs --enable-source-maps build/Project.mts build --mode=DEBUG", + "build.release": "node node_modules/tsx/dist/cli.mjs --enable-source-maps build/Project.mts build --mode=RELEASE" }, "browser": "browser/index.js", "module": "node/index.mjs", @@ -27,57 +28,58 @@ "sideEffects": false, "dependencies": { "chalk": "^5.3.0", - "diff": "^5.1.0", - "lodash": "^4.17.21" + "diff": "6.0.0-beta", + "lodash.isequal": "^4.5.0" }, "license": "Apache-2.0", "packageManager": "pnpm@8.6.9", "devDependencies": { - "@eslint/js": "^8.55.0", - "@rollup/plugin-commonjs": "^25.0.7", + "@eslint/js": "^9.9.1", + "@rollup/plugin-commonjs": "^26.0.1", "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-typescript": "^11.1.5", - "@types/chai": "^4.3.11", - "@types/diff": "^5.0.8", - "@types/eslint": "^8.44.8", - "@types/lodash": "^4.14.202", + "@rollup/plugin-typescript": "^11.1.6", + "@types/chai": "^4.3.18", + "@types/diff": "^5.2.1", + "@types/eslint": "^9.6.1", + "@types/lodash": "^4.17.7", + "@types/lodash.debounce": "^4.0.9", + "@types/lodash.isequal": "^4.5.8", "@types/minimist": "^1.2.5", - "@types/mocha": "^10.0.6", - "@types/node": "^20.10.3", + "@types/mocha": "^10.0.7", + "@types/node": "^22.5.0", "@types/rollup-plugin-node-globals": "^1.4.4", "@types/tmp": "^0.2.6", - "@typescript-eslint/eslint-plugin": "^6.13.2", - "@typescript-eslint/parser": "^6.13.2", - "browser-sync": "^2.29.3", - "c8": "^8.0.1", - "chai": "^4.3.10", - "chokidar": "^3.5.3", - "eslint": "^8.55.0", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.29.0", - "eslint-plugin-tsdoc": "^0.2.17", + "@typescript-eslint/eslint-plugin": "^8.3.0", + "@typescript-eslint/parser": "^8.3.0", + "browser-sync": "^3.0.2", + "c8": "^10.1.2", + "chai": "^5.1.1", + "chokidar": "^3.6.0", + "eslint": "^9.9.1", + "eslint-plugin-tsdoc": "^0.3.0", "fancy-log": "^2.0.0", - "glob": "^10.3.10", + "glob": "^11.0.0", "install": "^0.13.0", + "lodash.debounce": "^4.0.8", "minimist": "^1.2.8", - "mocha": "^10.2.0", - "pnpm": "^8.11.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "rollup": "^4.6.1", + "mocha": "^10.7.3", + "pnpm": "^9.9.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "rollup": "^4.21.1", "rollup-plugin-node-globals": "^1.4.0", "source-map-support": "^0.5.21", "strip-ansi": "^7.1.0", "taffydb": "^2.7.3", - "terser": "^5.25.0", - "tmp": "^0.2.1", - "ts-node": "^10.9.1", - "tslib": "^2.6.2", - "tsx": "^4.6.2", - "typedoc": "^0.25.4", - "typedoc-plugin-missing-exports": "^2.1.0", - "typescript": "^5.3.2", - "typescript-eslint": "0.0.1-alpha.0", - "winston": "^3.11.0" + "terser": "^5.31.6", + "tmp": "^0.2.3", + "ts-node": "^10.9.2", + "tslib": "^2.7.0", + "tsx": "^4.19.0", + "typedoc": "^0.26.6", + "typedoc-plugin-missing-exports": "^3.0.0", + "typescript": "^5.5.4", + "typescript-eslint": "^8.3.0", + "winston": "^3.14.2" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf49b50..ed1320a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,567 +1,423 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -dependencies: - chalk: - specifier: ^5.3.0 - version: 5.3.0 - diff: - specifier: ^5.1.0 - version: 5.1.0 - lodash: - specifier: ^4.17.21 - version: 4.17.21 - -devDependencies: - '@eslint/js': - specifier: ^8.55.0 - version: 8.55.0 - '@rollup/plugin-commonjs': - specifier: ^25.0.7 - version: 25.0.7(rollup@4.6.1) - '@rollup/plugin-node-resolve': - specifier: ^15.2.3 - version: 15.2.3(rollup@4.6.1) - '@rollup/plugin-typescript': - specifier: ^11.1.5 - version: 11.1.5(rollup@4.6.1)(tslib@2.6.2)(typescript@5.3.2) - '@types/chai': - specifier: ^4.3.11 - version: 4.3.11 - '@types/diff': - specifier: ^5.0.8 - version: 5.0.8 - '@types/eslint': - specifier: ^8.44.8 - version: 8.44.8 - '@types/lodash': - specifier: ^4.14.202 - version: 4.14.202 - '@types/minimist': - specifier: ^1.2.5 - version: 1.2.5 - '@types/mocha': - specifier: ^10.0.6 - version: 10.0.6 - '@types/node': - specifier: ^20.10.3 - version: 20.10.3 - '@types/rollup-plugin-node-globals': - specifier: ^1.4.4 - version: 1.4.4 - '@types/tmp': - specifier: ^0.2.6 - version: 0.2.6 - '@typescript-eslint/eslint-plugin': - specifier: ^6.13.2 - version: 6.13.2(@typescript-eslint/parser@6.13.2)(eslint@8.55.0)(typescript@5.3.2) - '@typescript-eslint/parser': - specifier: ^6.13.2 - version: 6.13.2(eslint@8.55.0)(typescript@5.3.2) - browser-sync: - specifier: ^2.29.3 - version: 2.29.3 - c8: - specifier: ^8.0.1 - version: 8.0.1 - chai: - specifier: ^4.3.10 - version: 4.3.10 - chokidar: - specifier: ^3.5.3 - version: 3.5.3 - eslint: - specifier: ^8.55.0 - version: 8.55.0 - eslint-import-resolver-typescript: - specifier: ^3.6.1 - version: 3.6.1(@typescript-eslint/parser@6.13.2)(eslint-plugin-import@2.29.0)(eslint@8.55.0) - eslint-plugin-import: - specifier: ^2.29.0 - version: 2.29.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) - eslint-plugin-tsdoc: - specifier: ^0.2.17 - version: 0.2.17 - fancy-log: - specifier: ^2.0.0 - version: 2.0.0 - glob: - specifier: ^10.3.10 - version: 10.3.10 - install: - specifier: ^0.13.0 - version: 0.13.0 - minimist: - specifier: ^1.2.8 - version: 1.2.8 - mocha: - specifier: ^10.2.0 - version: 10.2.0 - pnpm: - specifier: ^8.11.0 - version: 8.11.0 - react: - specifier: ^18.2.0 - version: 18.2.0 - react-dom: - specifier: ^18.2.0 - version: 18.2.0(react@18.2.0) - rollup: - specifier: ^4.6.1 - version: 4.6.1 - rollup-plugin-node-globals: - specifier: ^1.4.0 - version: 1.4.0 - source-map-support: - specifier: ^0.5.21 - version: 0.5.21 - strip-ansi: - specifier: ^7.1.0 - version: 7.1.0 - taffydb: - specifier: ^2.7.3 - version: 2.7.3 - terser: - specifier: ^5.25.0 - version: 5.25.0 - tmp: - specifier: ^0.2.1 - version: 0.2.1 - ts-node: - specifier: ^10.9.1 - version: 10.9.1(@types/node@20.10.3)(typescript@5.3.2) - tslib: - specifier: ^2.6.2 - version: 2.6.2 - tsx: - specifier: ^4.6.2 - version: 4.6.2 - typedoc: - specifier: ^0.25.4 - version: 0.25.4(typescript@5.3.2) - typedoc-plugin-missing-exports: - specifier: ^2.1.0 - version: 2.1.0(typedoc@0.25.4) - typescript: - specifier: ^5.3.2 - version: 5.3.2 - typescript-eslint: - specifier: 0.0.1-alpha.0 - version: 0.0.1-alpha.0 - winston: - specifier: ^3.11.0 - version: 3.11.0 +importers: + + .: + dependencies: + chalk: + specifier: ^5.3.0 + version: 5.3.0 + diff: + specifier: 6.0.0-beta + version: 6.0.0-beta + lodash.isequal: + specifier: ^4.5.0 + version: 4.5.0 + devDependencies: + '@eslint/js': + specifier: ^9.9.1 + version: 9.9.1 + '@rollup/plugin-commonjs': + specifier: ^26.0.1 + version: 26.0.1(rollup@4.21.1) + '@rollup/plugin-node-resolve': + specifier: ^15.2.3 + version: 15.2.3(rollup@4.21.1) + '@rollup/plugin-typescript': + specifier: ^11.1.6 + version: 11.1.6(rollup@4.21.1)(tslib@2.7.0)(typescript@5.5.4) + '@types/chai': + specifier: ^4.3.18 + version: 4.3.18 + '@types/diff': + specifier: ^5.2.1 + version: 5.2.1 + '@types/eslint': + specifier: ^9.6.1 + version: 9.6.1 + '@types/lodash': + specifier: ^4.17.7 + version: 4.17.7 + '@types/lodash.debounce': + specifier: ^4.0.9 + version: 4.0.9 + '@types/lodash.isequal': + specifier: ^4.5.8 + version: 4.5.8 + '@types/minimist': + specifier: ^1.2.5 + version: 1.2.5 + '@types/mocha': + specifier: ^10.0.7 + version: 10.0.7 + '@types/node': + specifier: ^22.5.0 + version: 22.5.0 + '@types/rollup-plugin-node-globals': + specifier: ^1.4.4 + version: 1.4.4 + '@types/tmp': + specifier: ^0.2.6 + version: 0.2.6 + '@typescript-eslint/eslint-plugin': + specifier: ^8.3.0 + version: 8.3.0(@typescript-eslint/parser@8.3.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/parser': + specifier: ^8.3.0 + version: 8.3.0(eslint@9.9.1)(typescript@5.5.4) + browser-sync: + specifier: ^3.0.2 + version: 3.0.2 + c8: + specifier: ^10.1.2 + version: 10.1.2 + chai: + specifier: ^5.1.1 + version: 5.1.1 + chokidar: + specifier: ^3.6.0 + version: 3.6.0 + eslint: + specifier: ^9.9.1 + version: 9.9.1 + eslint-plugin-tsdoc: + specifier: ^0.3.0 + version: 0.3.0 + fancy-log: + specifier: ^2.0.0 + version: 2.0.0 + glob: + specifier: ^11.0.0 + version: 11.0.0 + install: + specifier: ^0.13.0 + version: 0.13.0 + lodash.debounce: + specifier: ^4.0.8 + version: 4.0.8 + minimist: + specifier: ^1.2.8 + version: 1.2.8 + mocha: + specifier: ^10.7.3 + version: 10.7.3 + pnpm: + specifier: ^9.9.0 + version: 9.9.0 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + rollup: + specifier: ^4.21.1 + version: 4.21.1 + rollup-plugin-node-globals: + specifier: ^1.4.0 + version: 1.4.0 + source-map-support: + specifier: ^0.5.21 + version: 0.5.21 + strip-ansi: + specifier: ^7.1.0 + version: 7.1.0 + taffydb: + specifier: ^2.7.3 + version: 2.7.3 + terser: + specifier: ^5.31.6 + version: 5.31.6 + tmp: + specifier: ^0.2.3 + version: 0.2.3 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@22.5.0)(typescript@5.5.4) + tslib: + specifier: ^2.7.0 + version: 2.7.0 + tsx: + specifier: ^4.19.0 + version: 4.19.0 + typedoc: + specifier: ^0.26.6 + version: 0.26.6(typescript@5.5.4) + typedoc-plugin-missing-exports: + specifier: ^3.0.0 + version: 3.0.0(typedoc@0.26.6(typescript@5.5.4)) + typescript: + specifier: ^5.5.4 + version: 5.5.4 + typescript-eslint: + specifier: ^8.3.0 + version: 8.3.0(eslint@9.9.1)(typescript@5.5.4) + winston: + specifier: ^3.14.2 + version: 3.14.2 packages: - /@aashutoshrathi/word-wrap@1.2.6: - resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} - engines: {node: '>=0.10.0'} - dev: true - - /@bcoe/v8-coverage@0.2.3: + '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - dev: true - /@colors/colors@1.6.0: + '@colors/colors@1.6.0': resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} - dev: true - /@cspotcode/source-map-support@0.8.1: + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - dev: true - /@dabh/diagnostics@2.0.3: + '@dabh/diagnostics@2.0.3': resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} - dependencies: - colorspace: 1.1.4 - enabled: 2.0.0 - kuler: 2.0.0 - dev: true - /@esbuild/android-arm64@0.18.20: - resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} - engines: {node: '>=12'} + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm@0.18.20: - resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} - engines: {node: '>=12'} + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-x64@0.18.20: - resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} - engines: {node: '>=12'} + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} cpu: [x64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-arm64@0.18.20: - resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} - engines: {node: '>=12'} + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-x64@0.18.20: - resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} - engines: {node: '>=12'} + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-arm64@0.18.20: - resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} - engines: {node: '>=12'} + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-x64@0.18.20: - resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} - engines: {node: '>=12'} + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} cpu: [x64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm64@0.18.20: - resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} - engines: {node: '>=12'} + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm@0.18.20: - resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} - engines: {node: '>=12'} + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ia32@0.18.20: - resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} - engines: {node: '>=12'} + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} cpu: [ia32] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-loong64@0.18.20: - resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} - engines: {node: '>=12'} + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} cpu: [loong64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-mips64el@0.18.20: - resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} - engines: {node: '>=12'} + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} cpu: [mips64el] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ppc64@0.18.20: - resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} - engines: {node: '>=12'} + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-riscv64@0.18.20: - resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} - engines: {node: '>=12'} + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-s390x@0.18.20: - resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} - engines: {node: '>=12'} + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-x64@0.18.20: - resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} - engines: {node: '>=12'} + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/netbsd-x64@0.18.20: - resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} - engines: {node: '>=12'} + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} cpu: [x64] os: [netbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openbsd-x64@0.18.20: - resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} - engines: {node: '>=12'} + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} cpu: [x64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/sunos-x64@0.18.20: - resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} - engines: {node: '>=12'} + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} cpu: [x64] os: [sunos] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-arm64@0.18.20: - resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} - engines: {node: '>=12'} + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-ia32@0.18.20: - resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} - engines: {node: '>=12'} + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-x64@0.18.20: - resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} - engines: {node: '>=12'} + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.55.0): + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.55.0 - eslint-visitor-keys: 3.4.3 - dev: true - /@eslint-community/regexpp@4.10.0: - resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} + '@eslint-community/regexpp@4.11.0': + resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: true - /@eslint/eslintrc@2.1.4: - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - ajv: 6.12.6 - debug: 4.3.4(supports-color@8.1.1) - espree: 9.6.1 - globals: 13.23.0 - ignore: 5.3.0 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true + '@eslint/config-array@0.18.0': + resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - /@eslint/js@8.55.0: - resolution: {integrity: sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true + '@eslint/eslintrc@3.1.0': + resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - /@humanwhocodes/config-array@0.11.13: - resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} - engines: {node: '>=10.10.0'} - dependencies: - '@humanwhocodes/object-schema': 2.0.1 - debug: 4.3.4(supports-color@8.1.1) - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - dev: true + '@eslint/js@9.9.1': + resolution: {integrity: sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - /@humanwhocodes/module-importer@1.0.1: + '@eslint/object-schema@2.1.4': + resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - dev: true - /@humanwhocodes/object-schema@2.0.1: - resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} - dev: true + '@humanwhocodes/retry@0.3.0': + resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} + engines: {node: '>=18.18'} - /@isaacs/cliui@8.0.2: + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - dependencies: - string-width: 5.1.2 - string-width-cjs: /string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: /strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: /wrap-ansi@7.0.0 - dev: true - /@istanbuljs/schema@0.1.3: + '@istanbuljs/schema@0.1.3': resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} - dev: true - /@jridgewell/gen-mapping@0.3.3: - resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.20 - dev: true - /@jridgewell/resolve-uri@3.1.1: - resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - dev: true - /@jridgewell/set-array@1.1.2: - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} - dev: true - /@jridgewell/source-map@0.3.5: - resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==} - dependencies: - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.20 - dev: true + '@jridgewell/source-map@0.3.6': + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: true + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - /@jridgewell/trace-mapping@0.3.20: - resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==} - dependencies: - '@jridgewell/resolve-uri': 3.1.1 - '@jridgewell/sourcemap-codec': 1.4.15 - dev: true + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - /@jridgewell/trace-mapping@0.3.9: + '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - dependencies: - '@jridgewell/resolve-uri': 3.1.1 - '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - /@microsoft/tsdoc-config@0.16.2: - resolution: {integrity: sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==} - dependencies: - '@microsoft/tsdoc': 0.14.2 - ajv: 6.12.6 - jju: 1.4.0 - resolve: 1.19.0 - dev: true + '@microsoft/tsdoc-config@0.17.0': + resolution: {integrity: sha512-v/EYRXnCAIHxOHW+Plb6OWuUoMotxTN0GLatnpOb1xq0KuTNw/WI3pamJx/UbsoJP5k9MCw1QxvvhPcF9pH3Zg==} - /@microsoft/tsdoc@0.14.2: - resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==} - dev: true + '@microsoft/tsdoc@0.15.0': + resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==} - /@nodelib/fs.scandir@2.1.5: + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - dev: true - /@nodelib/fs.stat@2.0.5: + '@nodelib/fs.stat@2.0.5': resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} - dev: true - /@nodelib/fs.walk@1.2.8: + '@nodelib/fs.walk@1.2.8': resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.15.0 - dev: true - /@pkgjs/parseargs@0.11.0: + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - requiresBuild: true - dev: true - optional: true - /@rollup/plugin-commonjs@25.0.7(rollup@4.6.1): - resolution: {integrity: sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==} - engines: {node: '>=14.0.0'} + '@rollup/plugin-commonjs@26.0.1': + resolution: {integrity: sha512-UnsKoZK6/aGIH6AdkptXhNvhaqftcjq3zZdT+LY5Ftms6JR06nADcDsYp5hTU9E2lbJUEOhdlY5J4DNTneM+jQ==} + engines: {node: '>=16.0.0 || 14 >= 14.17'} peerDependencies: rollup: ^2.68.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true - dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.6.1) - commondir: 1.0.1 - estree-walker: 2.0.2 - glob: 8.1.0 - is-reference: 1.2.1 - magic-string: 0.30.5 - rollup: 4.6.1 - dev: true - /@rollup/plugin-node-resolve@15.2.3(rollup@4.6.1): + '@rollup/plugin-node-resolve@15.2.3': resolution: {integrity: sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==} engines: {node: '>=14.0.0'} peerDependencies: @@ -569,18 +425,9 @@ packages: peerDependenciesMeta: rollup: optional: true - dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.6.1) - '@types/resolve': 1.20.2 - deepmerge: 4.3.1 - is-builtin-module: 3.2.1 - is-module: 1.0.0 - resolve: 1.22.8 - rollup: 4.6.1 - dev: true - /@rollup/plugin-typescript@11.1.5(rollup@4.6.1)(tslib@2.6.2)(typescript@5.3.2): - resolution: {integrity: sha512-rnMHrGBB0IUEv69Q8/JGRD/n4/n6b3nfpufUu26axhUcboUzv/twfZU8fIBbTOphRAe0v8EyxzeDpKXqGHfyDA==} + '@rollup/plugin-typescript@11.1.6': + resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^2.14.0||^3.0.0||^4.0.0 @@ -591,15 +438,8 @@ packages: optional: true tslib: optional: true - dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.6.1) - resolve: 1.22.8 - rollup: 4.6.1 - tslib: 2.6.2 - typescript: 5.3.2 - dev: true - /@rollup/pluginutils@5.1.0(rollup@4.6.1): + '@rollup/pluginutils@5.1.0': resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} engines: {node: '>=14.0.0'} peerDependencies: @@ -607,2713 +447,3085 @@ packages: peerDependenciesMeta: rollup: optional: true - dependencies: - '@types/estree': 1.0.5 - estree-walker: 2.0.2 - picomatch: 2.3.1 - rollup: 4.6.1 - dev: true - /@rollup/rollup-android-arm-eabi@4.6.1: - resolution: {integrity: sha512-0WQ0ouLejaUCRsL93GD4uft3rOmB8qoQMU05Kb8CmMtMBe7XUDLAltxVZI1q6byNqEtU7N1ZX1Vw5lIpgulLQA==} + '@rollup/rollup-android-arm-eabi@4.21.1': + resolution: {integrity: sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-android-arm64@4.6.1: - resolution: {integrity: sha512-1TKm25Rn20vr5aTGGZqo6E4mzPicCUD79k17EgTLAsXc1zysyi4xXKACfUbwyANEPAEIxkzwue6JZ+stYzWUTA==} + '@rollup/rollup-android-arm64@4.21.1': + resolution: {integrity: sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-darwin-arm64@4.6.1: - resolution: {integrity: sha512-cEXJQY/ZqMACb+nxzDeX9IPLAg7S94xouJJCNVE5BJM8JUEP4HeTF+ti3cmxWeSJo+5D+o8Tc0UAWUkfENdeyw==} + '@rollup/rollup-darwin-arm64@4.21.1': + resolution: {integrity: sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-darwin-x64@4.6.1: - resolution: {integrity: sha512-LoSU9Xu56isrkV2jLldcKspJ7sSXmZWkAxg7sW/RfF7GS4F5/v4EiqKSMCFbZtDu2Nc1gxxFdQdKwkKS4rwxNg==} + '@rollup/rollup-darwin-x64@4.21.1': + resolution: {integrity: sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.6.1: - resolution: {integrity: sha512-EfI3hzYAy5vFNDqpXsNxXcgRDcFHUWSx5nnRSCKwXuQlI5J9dD84g2Usw81n3FLBNsGCegKGwwTVsSKK9cooSQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.21.1': + resolution: {integrity: sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.21.1': + resolution: {integrity: sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm64-gnu@4.6.1: - resolution: {integrity: sha512-9lhc4UZstsegbNLhH0Zu6TqvDfmhGzuCWtcTFXY10VjLLUe4Mr0Ye2L3rrtHaDd/J5+tFMEuo5LTCSCMXWfUKw==} + '@rollup/rollup-linux-arm64-gnu@4.21.1': + resolution: {integrity: sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm64-musl@4.6.1: - resolution: {integrity: sha512-FfoOK1yP5ksX3wwZ4Zk1NgyGHZyuRhf99j64I5oEmirV8EFT7+OhUZEnP+x17lcP/QHJNWGsoJwrz4PJ9fBEXw==} + '@rollup/rollup-linux-arm64-musl@4.21.1': + resolution: {integrity: sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-x64-gnu@4.6.1: - resolution: {integrity: sha512-DNGZvZDO5YF7jN5fX8ZqmGLjZEXIJRdJEdTFMhiyXqyXubBa0WVLDWSNlQ5JR2PNgDbEV1VQowhVRUh+74D+RA==} + '@rollup/rollup-linux-powerpc64le-gnu@4.21.1': + resolution: {integrity: sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.21.1': + resolution: {integrity: sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.21.1': + resolution: {integrity: sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.21.1': + resolution: {integrity: sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-x64-musl@4.6.1: - resolution: {integrity: sha512-RkJVNVRM+piYy87HrKmhbexCHg3A6Z6MU0W9GHnJwBQNBeyhCJG9KDce4SAMdicQnpURggSvtbGo9xAWOfSvIQ==} + '@rollup/rollup-linux-x64-musl@4.21.1': + resolution: {integrity: sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-arm64-msvc@4.6.1: - resolution: {integrity: sha512-v2FVT6xfnnmTe3W9bJXl6r5KwJglMK/iRlkKiIFfO6ysKs0rDgz7Cwwf3tjldxQUrHL9INT/1r4VA0n9L/F1vQ==} + '@rollup/rollup-win32-arm64-msvc@4.21.1': + resolution: {integrity: sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-ia32-msvc@4.6.1: - resolution: {integrity: sha512-YEeOjxRyEjqcWphH9dyLbzgkF8wZSKAKUkldRY6dgNR5oKs2LZazqGB41cWJ4Iqqcy9/zqYgmzBkRoVz3Q9MLw==} + '@rollup/rollup-win32-ia32-msvc@4.21.1': + resolution: {integrity: sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-x64-msvc@4.6.1: - resolution: {integrity: sha512-0zfTlFAIhgz8V2G8STq8toAjsYYA6eci1hnXuyOTUFnymrtJwnS6uGKiv3v5UrPZkBlamLvrLV2iiaeqCKzb0A==} + '@rollup/rollup-win32-x64-msvc@4.21.1': + resolution: {integrity: sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@socket.io/component-emitter@3.1.0: - resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==} - dev: true + '@shikijs/core@1.14.1': + resolution: {integrity: sha512-KyHIIpKNaT20FtFPFjCQB5WVSTpLR/n+jQXhWHWVUMm9MaOaG9BGOG0MSyt7yA4+Lm+4c9rTc03tt3nYzeYSfw==} - /@tsconfig/node10@1.0.9: - resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} - dev: true + '@socket.io/component-emitter@3.1.2': + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} - /@tsconfig/node12@1.0.11: + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - dev: true - /@tsconfig/node14@1.0.3: + '@tsconfig/node14@1.0.3': resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - dev: true - /@tsconfig/node16@1.0.4: + '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - dev: true - /@types/chai@4.3.11: - resolution: {integrity: sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==} - dev: true + '@types/chai@4.3.18': + resolution: {integrity: sha512-2UfJzigyNa8kYTKn7o4hNMPphkxtu4WTJyobK3m4FBpyj7EK5xgtPcOtxLm7Dznk/Qxr0QXn+gQbkg7mCZKdfg==} - /@types/cookie@0.4.1: + '@types/cookie@0.4.1': resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} - dev: true - /@types/cors@2.8.17: + '@types/cors@2.8.17': resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} - dependencies: - '@types/node': 20.10.3 - dev: true - /@types/diff@5.0.8: - resolution: {integrity: sha512-kR0gRf0wMwpxQq6ME5s+tWk9zVCfJUl98eRkD05HWWRbhPB/eu4V1IbyZAsvzC1Gn4znBJ0HN01M4DGXdBEV8Q==} - dev: true + '@types/diff@5.2.1': + resolution: {integrity: sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==} - /@types/eslint@8.44.8: - resolution: {integrity: sha512-4K8GavROwhrYl2QXDXm0Rv9epkA8GBFu0EI+XrrnnuCl7u8CWBRusX7fXJfanhZTDWSAL24gDI/UqXyUM0Injw==} - dependencies: - '@types/estree': 1.0.5 - '@types/json-schema': 7.0.15 - dev: true + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} - /@types/estree@0.0.39: + '@types/estree@0.0.39': resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} - dev: true - /@types/estree@1.0.5: + '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true - /@types/istanbul-lib-coverage@2.0.6: + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - dev: true - /@types/json-schema@7.0.15: + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - dev: true - /@types/json5@0.0.29: - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - dev: true + '@types/lodash.debounce@4.0.9': + resolution: {integrity: sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==} - /@types/lodash@4.14.202: - resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==} - dev: true + '@types/lodash.isequal@4.5.8': + resolution: {integrity: sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==} - /@types/minimist@1.2.5: + '@types/lodash@4.17.7': + resolution: {integrity: sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==} + + '@types/minimist@1.2.5': resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - dev: true - /@types/mocha@10.0.6: - resolution: {integrity: sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==} - dev: true + '@types/mocha@10.0.7': + resolution: {integrity: sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==} - /@types/node@20.10.3: - resolution: {integrity: sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==} - dependencies: - undici-types: 5.26.5 - dev: true + '@types/node@22.5.0': + resolution: {integrity: sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==} - /@types/resolve@1.20.2: + '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} - dev: true - /@types/rollup-plugin-node-globals@1.4.4: + '@types/rollup-plugin-node-globals@1.4.4': resolution: {integrity: sha512-T+eX0O6QFd7qEPzQik9m3DDRmtUusJV4KgN7S5Q6zOlQx7a+ZTDR61VL89a7sG0pCTpOHjQivPx5OOtkr3xGKA==} - dependencies: - '@types/node': 20.10.3 - rollup: 0.63.5 - dev: true - /@types/semver@7.5.6: - resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} - dev: true - - /@types/tmp@0.2.6: + '@types/tmp@0.2.6': resolution: {integrity: sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==} - dev: true - /@types/triple-beam@1.3.5: + '@types/triple-beam@1.3.5': resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} - dev: true - /@typescript-eslint/eslint-plugin@6.13.2(@typescript-eslint/parser@6.13.2)(eslint@8.55.0)(typescript@5.3.2): - resolution: {integrity: sha512-3+9OGAWHhk4O1LlcwLBONbdXsAhLjyCFogJY/cWy2lxdVJ2JrcTF2pTGMaLl2AE7U1l31n8Py4a8bx5DLf/0dQ==} - engines: {node: ^16.0.0 || >=18.0.0} + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@typescript-eslint/eslint-plugin@8.3.0': + resolution: {integrity: sha512-FLAIn63G5KH+adZosDYiutqkOkYEx0nvcwNNfJAf+c7Ae/H35qWwTYvPZUKFj5AS+WfHG/WJJfWnDnyNUlp8UA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true - dependencies: - '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 6.13.2(eslint@8.55.0)(typescript@5.3.2) - '@typescript-eslint/scope-manager': 6.13.2 - '@typescript-eslint/type-utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2) - '@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2) - '@typescript-eslint/visitor-keys': 6.13.2 - debug: 4.3.4(supports-color@8.1.1) - eslint: 8.55.0 - graphemer: 1.4.0 - ignore: 5.3.0 - natural-compare: 1.4.0 - semver: 7.5.4 - ts-api-utils: 1.0.3(typescript@5.3.2) - typescript: 5.3.2 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/parser@6.13.2(eslint@8.55.0)(typescript@5.3.2): - resolution: {integrity: sha512-MUkcC+7Wt/QOGeVlM8aGGJZy1XV5YKjTpq9jK6r6/iLsGXhBVaGP5N0UYvFsu9BFlSpwY9kMretzdBH01rkRXg==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/parser@8.3.0': + resolution: {integrity: sha512-h53RhVyLu6AtpUzVCYLPhZGL5jzTD9fZL+SYf/+hYOx2bDkyQXztXSc4tbvKYHzfMXExMLiL9CWqJmVz6+78IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^7.0.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true - dependencies: - '@typescript-eslint/scope-manager': 6.13.2 - '@typescript-eslint/types': 6.13.2 - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2) - '@typescript-eslint/visitor-keys': 6.13.2 - debug: 4.3.4(supports-color@8.1.1) - eslint: 8.55.0 - typescript: 5.3.2 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/scope-manager@6.13.2: - resolution: {integrity: sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==} - engines: {node: ^16.0.0 || >=18.0.0} - dependencies: - '@typescript-eslint/types': 6.13.2 - '@typescript-eslint/visitor-keys': 6.13.2 - dev: true + '@typescript-eslint/scope-manager@8.3.0': + resolution: {integrity: sha512-mz2X8WcN2nVu5Hodku+IR8GgCOl4C0G/Z1ruaWN4dgec64kDBabuXyPAr+/RgJtumv8EEkqIzf3X2U5DUKB2eg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - /@typescript-eslint/type-utils@6.13.2(eslint@8.55.0)(typescript@5.3.2): - resolution: {integrity: sha512-Qr6ssS1GFongzH2qfnWKkAQmMUyZSyOr0W54nZNU1MDfo+U4Mv3XveeLZzadc/yq8iYhQZHYT+eoXJqnACM1tw==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/type-utils@8.3.0': + resolution: {integrity: sha512-wrV6qh//nLbfXZQoj32EXKmwHf4b7L+xXLrP3FZ0GOUU72gSvLjeWUl5J5Ue5IwRxIV1TfF73j/eaBapxx99Lg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^7.0.0 || ^8.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true - dependencies: - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2) - '@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2) - debug: 4.3.4(supports-color@8.1.1) - eslint: 8.55.0 - ts-api-utils: 1.0.3(typescript@5.3.2) - typescript: 5.3.2 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/types@6.13.2: - resolution: {integrity: sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==} - engines: {node: ^16.0.0 || >=18.0.0} - dev: true + '@typescript-eslint/types@8.3.0': + resolution: {integrity: sha512-y6sSEeK+facMaAyixM36dQ5NVXTnKWunfD1Ft4xraYqxP0lC0POJmIaL/mw72CUMqjY9qfyVfXafMeaUj0noWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - /@typescript-eslint/typescript-estree@6.13.2(typescript@5.3.2): - resolution: {integrity: sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/typescript-estree@8.3.0': + resolution: {integrity: sha512-Mq7FTHl0R36EmWlCJWojIC1qn/ZWo2YiWYc1XVtasJ7FIgjo0MVv9rZWXEE7IK2CGrtwe1dVOxWwqXUdNgfRCA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true - dependencies: - '@typescript-eslint/types': 6.13.2 - '@typescript-eslint/visitor-keys': 6.13.2 - debug: 4.3.4(supports-color@8.1.1) - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.5.4 - ts-api-utils: 1.0.3(typescript@5.3.2) - typescript: 5.3.2 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/utils@6.13.2(eslint@8.55.0)(typescript@5.3.2): - resolution: {integrity: sha512-b9Ptq4eAZUym4idijCRzl61oPCwwREcfDI8xGk751Vhzig5fFZR9CyzDz4Sp/nxSLBYxUPyh4QdIDqWykFhNmQ==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/utils@8.3.0': + resolution: {integrity: sha512-F77WwqxIi/qGkIGOGXNBLV7nykwfjLsdauRB/DOFPdv6LTF3BHHkBpq81/b5iMPSF055oO2BiivDJV4ChvNtXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0) - '@types/json-schema': 7.0.15 - '@types/semver': 7.5.6 - '@typescript-eslint/scope-manager': 6.13.2 - '@typescript-eslint/types': 6.13.2 - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2) - eslint: 8.55.0 - semver: 7.5.4 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - - /@typescript-eslint/visitor-keys@6.13.2: - resolution: {integrity: sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==} - engines: {node: ^16.0.0 || >=18.0.0} - dependencies: - '@typescript-eslint/types': 6.13.2 - eslint-visitor-keys: 3.4.3 - dev: true + eslint: ^8.57.0 || ^9.0.0 - /@ungap/structured-clone@1.2.0: - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - dev: true + '@typescript-eslint/visitor-keys@8.3.0': + resolution: {integrity: sha512-RmZwrTbQ9QveF15m/Cl28n0LXD6ea2CjkhH5rQ55ewz3H24w+AMCJHPVYaZ8/0HoG8Z3cLLFFycRXxeO2tz9FA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - /accepts@1.3.8: + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} - dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 - dev: true - /acorn-jsx@5.3.2(acorn@8.11.2): + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 8.11.2 - dev: true - /acorn-walk@8.3.0: - resolution: {integrity: sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==} + acorn-walk@8.3.3: + resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} engines: {node: '>=0.4.0'} - dev: true - /acorn@5.7.4: + acorn@5.7.4: resolution: {integrity: sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==} engines: {node: '>=0.4.0'} hasBin: true - dev: true - /acorn@8.11.2: - resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} + acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} engines: {node: '>=0.4.0'} hasBin: true - dev: true - /ajv@6.12.6: + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - dev: true - /ansi-colors@4.1.1: - resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} + ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} - dev: true - /ansi-regex@5.0.1: + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - dev: true - /ansi-regex@6.0.1: + ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - dev: true - /ansi-sequence-parser@1.1.1: - resolution: {integrity: sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==} - dev: true - - /ansi-styles@4.3.0: + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 - dev: true - /ansi-styles@6.2.1: + ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - dev: true - /anymatch@3.1.3: + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - dev: true - /arg@4.1.3: + arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - dev: true - /argparse@2.0.1: + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true - - /array-buffer-byte-length@1.0.0: - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} - dependencies: - call-bind: 1.0.5 - is-array-buffer: 3.0.2 - dev: true - /array-includes@3.1.7: - resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - get-intrinsic: 1.2.2 - is-string: 1.0.7 - dev: true - - /array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - dev: true - - /array.prototype.findlastindex@1.2.3: - resolution: {integrity: sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - es-shim-unscopables: 1.0.2 - get-intrinsic: 1.2.2 - dev: true - - /array.prototype.flat@1.3.2: - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - es-shim-unscopables: 1.0.2 - dev: true - - /array.prototype.flatmap@1.3.2: - resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - es-shim-unscopables: 1.0.2 - dev: true - - /arraybuffer.prototype.slice@1.0.2: - resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} - engines: {node: '>= 0.4'} - dependencies: - array-buffer-byte-length: 1.0.0 - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - get-intrinsic: 1.2.2 - is-array-buffer: 3.0.2 - is-shared-array-buffer: 1.0.2 - dev: true - - /assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - dev: true + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} - /async-each-series@0.1.1: + async-each-series@0.1.1: resolution: {integrity: sha512-p4jj6Fws4Iy2m0iCmI2am2ZNZCgbdgE+P8F/8csmn2vx7ixXrO2zGcuNsD46X5uZSVecmkEy/M06X2vG8KD6dQ==} engines: {node: '>=0.8.0'} - dev: true - /async@2.6.4: + async@2.6.4: resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==} - dependencies: - lodash: 4.17.21 - dev: true - /async@3.2.5: - resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} - dev: true - - /available-typed-arrays@1.0.5: - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} - engines: {node: '>= 0.4'} - dev: true - - /axios@0.21.4(debug@4.3.2): - resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} - dependencies: - follow-redirects: 1.15.3(debug@4.3.2) - transitivePeerDependencies: - - debug - dev: true + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - /balanced-match@1.0.2: + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true - /base64id@2.0.0: + base64id@2.0.0: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - dev: true - /batch@0.6.1: + batch@0.6.1: resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} - dev: true - /binary-extensions@2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - dev: true - /brace-expansion@1.1.11: + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - dev: true - /brace-expansion@2.0.1: + brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - dependencies: - balanced-match: 1.0.2 - dev: true - /braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - dependencies: - fill-range: 7.0.1 - dev: true - /browser-stdout@1.3.1: + browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} - dev: true - /browser-sync-client@2.29.3: - resolution: {integrity: sha512-4tK5JKCl7v/3aLbmCBMzpufiYLsB1+UI+7tUXCCp5qF0AllHy/jAqYu6k7hUF3hYtlClKpxExWaR+rH+ny07wQ==} + browser-sync-client@3.0.2: + resolution: {integrity: sha512-tBWdfn9L0wd2Pjuz/NWHtNEKthVb1Y67vg8/qyGNtCqetNz5lkDkFnrsx5UhPNPYUO8vci50IWC/BhYaQskDiQ==} engines: {node: '>=8.0.0'} - dependencies: - etag: 1.8.1 - fresh: 0.5.2 - mitt: 1.2.0 - dev: true - /browser-sync-ui@2.29.3: - resolution: {integrity: sha512-kBYOIQjU/D/3kYtUIJtj82e797Egk1FB2broqItkr3i4eF1qiHbFCG6srksu9gWhfmuM/TNG76jMfzAdxEPakg==} - dependencies: - async-each-series: 0.1.1 - chalk: 4.1.2 - connect-history-api-fallback: 1.6.0 - immutable: 3.8.2 - server-destroy: 1.0.1 - socket.io-client: 4.7.2 - stream-throttle: 0.1.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: true + browser-sync-ui@3.0.2: + resolution: {integrity: sha512-V3FwWAI+abVbFLTyJjXJlCMBwjc3GXf/BPGfwO2fMFACWbIGW9/4SrBOFYEOOtqzCjQE0Di+U3VIb7eES4omNA==} - /browser-sync@2.29.3: - resolution: {integrity: sha512-NiM38O6XU84+MN+gzspVmXV2fTOoe+jBqIBx3IBdhZrdeURr6ZgznJr/p+hQ+KzkKEiGH/GcC4SQFSL0jV49bg==} + browser-sync@3.0.2: + resolution: {integrity: sha512-PC9c7aWJFVR4IFySrJxOqLwB9ENn3/TaXCXtAa0SzLwocLN3qMjN+IatbjvtCX92BjNXsY6YWg9Eb7F3Wy255g==} engines: {node: '>= 8.0.0'} hasBin: true - dependencies: - browser-sync-client: 2.29.3 - browser-sync-ui: 2.29.3 - bs-recipes: 1.3.4 - chalk: 4.1.2 - chokidar: 3.5.3 - connect: 3.6.6 - connect-history-api-fallback: 1.6.0 - dev-ip: 1.0.1 - easy-extender: 2.3.4 - eazy-logger: 4.0.1 - etag: 1.8.1 - fresh: 0.5.2 - fs-extra: 3.0.1 - http-proxy: 1.18.1 - immutable: 3.8.2 - localtunnel: 2.0.2 - micromatch: 4.0.5 - opn: 5.3.0 - portscanner: 2.2.0 - raw-body: 2.5.2 - resp-modifier: 6.0.2 - rx: 4.1.0 - send: 0.16.2 - serve-index: 1.9.1 - serve-static: 1.13.2 - server-destroy: 1.0.1 - socket.io: 4.7.2 - ua-parser-js: 1.0.37 - yargs: 17.7.2 - transitivePeerDependencies: - - bufferutil - - debug - - supports-color - - utf-8-validate - dev: true - /bs-recipes@1.3.4: + bs-recipes@1.3.4: resolution: {integrity: sha512-BXvDkqhDNxXEjeGM8LFkSbR+jzmP/CYpCiVKYn+soB1dDldeU15EBNDkwVXndKuX35wnNUaPd0qSoQEAkmQtMw==} - dev: true - /buffer-es6@4.9.3: + buffer-es6@4.9.3: resolution: {integrity: sha512-Ibt+oXxhmeYJSsCkODPqNpPmyegefiD8rfutH1NYGhMZQhSp95Rz7haemgnJ6dxa6LT+JLLbtgOMORRluwKktw==} - dev: true - /buffer-from@1.1.2: + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: true - /builtin-modules@3.3.0: + builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} - dev: true - /bytes@3.1.2: + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - dev: true - /c8@8.0.1: - resolution: {integrity: sha512-EINpopxZNH1mETuI0DzRA4MZpAUH+IFiRhnmFD3vFr3vdrgxqi3VfE3KL0AIL+zDq8rC9bZqwM/VDmmoe04y7w==} - engines: {node: '>=12'} + c8@10.1.2: + resolution: {integrity: sha512-Qr6rj76eSshu5CgRYvktW0uM0CFY0yi4Fd5D0duDXO6sYinyopmftUiJVuzBQxQcwQLor7JWDVRP+dUfCmzgJw==} + engines: {node: '>=18'} hasBin: true - dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@istanbuljs/schema': 0.1.3 - find-up: 5.0.0 - foreground-child: 2.0.0 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-reports: 3.1.6 - rimraf: 3.0.2 - test-exclude: 6.0.0 - v8-to-istanbul: 9.2.0 - yargs: 17.7.2 - yargs-parser: 21.1.1 - dev: true - - /call-bind@1.0.5: - resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} - dependencies: - function-bind: 1.1.2 - get-intrinsic: 1.2.2 - set-function-length: 1.1.1 - dev: true + peerDependencies: + monocart-coverage-reports: ^2 + peerDependenciesMeta: + monocart-coverage-reports: + optional: true - /callsites@3.1.0: + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - dev: true - /camelcase@6.3.0: + camelcase@6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - dev: true - /chai@4.3.10: - resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==} - engines: {node: '>=4'} - dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.3 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.0.8 - dev: true + chai@5.1.1: + resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + engines: {node: '>=12'} - /chalk@4.1.2: + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - dev: true - /chalk@5.3.0: + chalk@5.3.0: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - dev: false - /check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} - dependencies: - get-func-name: 2.0.2 - dev: true + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} - /chokidar@3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.3 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - dev: true - /cliui@7.0.4: + cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - dev: true - /cliui@8.0.1: + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - dev: true - /color-convert@1.9.3: + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - dependencies: - color-name: 1.1.3 - dev: true - /color-convert@2.0.1: + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 - dev: true - /color-name@1.1.3: + color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - dev: true - /color-name@1.1.4: + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true - /color-string@1.9.1: + color-string@1.9.1: resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} - dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.2 - dev: true - /color-support@1.1.3: + color-support@1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true - dev: true - /color@3.2.1: + color@3.2.1: resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} - dependencies: - color-convert: 1.9.3 - color-string: 1.9.1 - dev: true - /colorspace@1.1.4: + colorspace@1.1.4: resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} - dependencies: - color: 3.2.1 - text-hex: 1.0.0 - dev: true - /commander@2.20.3: + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - dev: true - /commondir@1.0.1: + commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - dev: true - /concat-map@0.0.1: + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: true - /connect-history-api-fallback@1.6.0: + connect-history-api-fallback@1.6.0: resolution: {integrity: sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==} engines: {node: '>=0.8'} - dev: true - /connect@3.6.6: + connect@3.6.6: resolution: {integrity: sha512-OO7axMmPpu/2XuX1+2Yrg0ddju31B6xLZMWkJ5rYBu4YRmRVlOjvlY6kw2FJKiAzyxGwnrDUAG4s1Pf0sbBMCQ==} engines: {node: '>= 0.10.0'} - dependencies: - debug: 2.6.9 - finalhandler: 1.1.0 - parseurl: 1.3.3 - utils-merge: 1.0.1 - transitivePeerDependencies: - - supports-color - dev: true - /convert-source-map@2.0.0: + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - dev: true - /cookie@0.4.2: + cookie@0.4.2: resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} engines: {node: '>= 0.6'} - dev: true - /cors@2.8.5: + cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - dev: true - /create-require@1.1.1: + create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - dev: true - /cross-spawn@7.0.3: + cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - dev: true - /debug@2.6.9: + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: supports-color: '*' peerDependenciesMeta: supports-color: optional: true - dependencies: - ms: 2.0.0 - dev: true - - /debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.3 - dev: true - - /debug@4.3.2: - resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - dev: true - /debug@4.3.4(supports-color@8.1.1): - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' peerDependenciesMeta: supports-color: optional: true - dependencies: - ms: 2.1.2 - supports-color: 8.1.1 - dev: true - /decamelize@4.0.0: + decamelize@4.0.0: resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} engines: {node: '>=10'} - dev: true - /deep-eql@4.1.3: - resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} - dependencies: - type-detect: 4.0.8 - dev: true - /deep-is@0.1.4: + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true - /deepmerge@4.3.1: + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - dev: true - - /define-data-property@1.1.1: - resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} - engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.2 - gopd: 1.0.1 - has-property-descriptors: 1.0.1 - dev: true - - /define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} - dependencies: - define-data-property: 1.1.1 - has-property-descriptors: 1.0.1 - object-keys: 1.1.1 - dev: true - /depd@1.1.2: + depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} - dev: true - /depd@2.0.0: + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} - dev: true - /destroy@1.0.4: + destroy@1.0.4: resolution: {integrity: sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==} - dev: true - /dev-ip@1.0.1: + dev-ip@1.0.1: resolution: {integrity: sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A==} engines: {node: '>= 0.8.0'} hasBin: true - dev: true - /diff@4.0.2: + diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} - dev: true - /diff@5.0.0: - resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} - dev: true - /diff@5.1.0: - resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} + diff@6.0.0-beta: + resolution: {integrity: sha512-ZLFOZi58WgvIcAre+bXFmKPx3j9rspGc1nR0pkJLFlbzY1FXPDhJu4sfBSvt4LHTNfPN/yoGy/46GMGxUyXyTQ==} engines: {node: '>=0.3.1'} - dev: false - - /dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - dependencies: - path-type: 4.0.0 - dev: true - /doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - dependencies: - esutils: 2.0.3 - dev: true - - /doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - dependencies: - esutils: 2.0.3 - dev: true - - /eastasianwidth@0.2.0: + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true - /easy-extender@2.3.4: + easy-extender@2.3.4: resolution: {integrity: sha512-8cAwm6md1YTiPpOvDULYJL4ZS6WfM5/cTeVVh4JsvyYZAoqlRVUpHL9Gr5Fy7HA6xcSZicUia3DeAgO3Us8E+Q==} engines: {node: '>= 4.0.0'} - dependencies: - lodash: 4.17.21 - dev: true - /eazy-logger@4.0.1: + eazy-logger@4.0.1: resolution: {integrity: sha512-2GSFtnnC6U4IEKhEI7+PvdxrmjJ04mdsj3wHZTFiw0tUtG4HCWzTr13ZYTk8XOGnA1xQMaDljoBOYlk3D/MMSw==} engines: {node: '>= 0.8.0'} - dependencies: - chalk: 4.1.2 - dev: true - /ee-first@1.1.1: + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - dev: true - /emoji-regex@8.0.0: + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true - /emoji-regex@9.2.2: + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - dev: true - /enabled@2.0.0: + enabled@2.0.0: resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} - dev: true - /encodeurl@1.0.2: + encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} - dev: true - /engine.io-client@6.5.3: - resolution: {integrity: sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==} - dependencies: - '@socket.io/component-emitter': 3.1.0 - debug: 4.3.4(supports-color@8.1.1) - engine.io-parser: 5.2.1 - ws: 8.11.0 - xmlhttprequest-ssl: 2.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: true + engine.io-client@6.5.4: + resolution: {integrity: sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==} - /engine.io-parser@5.2.1: - resolution: {integrity: sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==} + engine.io-parser@5.2.3: + resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} engines: {node: '>=10.0.0'} - dev: true - /engine.io@6.5.4: - resolution: {integrity: sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==} + engine.io@6.5.5: + resolution: {integrity: sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==} engines: {node: '>=10.2.0'} - dependencies: - '@types/cookie': 0.4.1 - '@types/cors': 2.8.17 - '@types/node': 20.10.3 - accepts: 1.3.8 - base64id: 2.0.0 - cookie: 0.4.2 - cors: 2.8.5 - debug: 4.3.4(supports-color@8.1.1) - engine.io-parser: 5.2.1 - ws: 8.11.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: true - - /enhanced-resolve@5.15.0: - resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==} - engines: {node: '>=10.13.0'} - dependencies: - graceful-fs: 4.2.11 - tapable: 2.2.1 - dev: true - - /es-abstract@1.22.3: - resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} - engines: {node: '>= 0.4'} - dependencies: - array-buffer-byte-length: 1.0.0 - arraybuffer.prototype.slice: 1.0.2 - available-typed-arrays: 1.0.5 - call-bind: 1.0.5 - es-set-tostringtag: 2.0.2 - es-to-primitive: 1.2.1 - function.prototype.name: 1.1.6 - get-intrinsic: 1.2.2 - get-symbol-description: 1.0.0 - globalthis: 1.0.3 - gopd: 1.0.1 - has-property-descriptors: 1.0.1 - has-proto: 1.0.1 - has-symbols: 1.0.3 - hasown: 2.0.0 - internal-slot: 1.0.6 - is-array-buffer: 3.0.2 - is-callable: 1.2.7 - is-negative-zero: 2.0.2 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 - is-string: 1.0.7 - is-typed-array: 1.1.12 - is-weakref: 1.0.2 - object-inspect: 1.13.1 - object-keys: 1.1.1 - object.assign: 4.1.5 - regexp.prototype.flags: 1.5.1 - safe-array-concat: 1.0.1 - safe-regex-test: 1.0.0 - string.prototype.trim: 1.2.8 - string.prototype.trimend: 1.0.7 - string.prototype.trimstart: 1.0.7 - typed-array-buffer: 1.0.0 - typed-array-byte-length: 1.0.0 - typed-array-byte-offset: 1.0.0 - typed-array-length: 1.0.4 - unbox-primitive: 1.0.2 - which-typed-array: 1.1.13 - dev: true - - /es-set-tostringtag@2.0.2: - resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==} - engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.2 - has-tostringtag: 1.0.0 - hasown: 2.0.0 - dev: true - - /es-shim-unscopables@1.0.2: - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} - dependencies: - hasown: 2.0.0 - dev: true - /es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} - engines: {node: '>= 0.4'} - dependencies: - is-callable: 1.2.7 - is-date-object: 1.0.5 - is-symbol: 1.0.4 - dev: true + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} - /esbuild@0.18.20: - resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} - engines: {node: '>=12'} + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/android-arm': 0.18.20 - '@esbuild/android-arm64': 0.18.20 - '@esbuild/android-x64': 0.18.20 - '@esbuild/darwin-arm64': 0.18.20 - '@esbuild/darwin-x64': 0.18.20 - '@esbuild/freebsd-arm64': 0.18.20 - '@esbuild/freebsd-x64': 0.18.20 - '@esbuild/linux-arm': 0.18.20 - '@esbuild/linux-arm64': 0.18.20 - '@esbuild/linux-ia32': 0.18.20 - '@esbuild/linux-loong64': 0.18.20 - '@esbuild/linux-mips64el': 0.18.20 - '@esbuild/linux-ppc64': 0.18.20 - '@esbuild/linux-riscv64': 0.18.20 - '@esbuild/linux-s390x': 0.18.20 - '@esbuild/linux-x64': 0.18.20 - '@esbuild/netbsd-x64': 0.18.20 - '@esbuild/openbsd-x64': 0.18.20 - '@esbuild/sunos-x64': 0.18.20 - '@esbuild/win32-arm64': 0.18.20 - '@esbuild/win32-ia32': 0.18.20 - '@esbuild/win32-x64': 0.18.20 - dev: true - - /escalade@3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + + escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} - dev: true - /escape-html@1.0.3: + escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - dev: true - /escape-string-regexp@4.0.0: + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - dev: true - - /eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - dependencies: - debug: 3.2.7 - is-core-module: 2.13.1 - resolve: 1.22.8 - transitivePeerDependencies: - - supports-color - dev: true - - /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.13.2)(eslint-plugin-import@2.29.0)(eslint@8.55.0): - resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - eslint: '*' - eslint-plugin-import: '*' - dependencies: - debug: 4.3.4(supports-color@8.1.1) - enhanced-resolve: 5.15.0 - eslint: 8.55.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) - eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) - fast-glob: 3.3.2 - get-tsconfig: 4.7.2 - is-core-module: 2.13.1 - is-glob: 4.0.3 - transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-node - - eslint-import-resolver-webpack - - supports-color - dev: true - - /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0): - resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - dependencies: - '@typescript-eslint/parser': 6.13.2(eslint@8.55.0)(typescript@5.3.2) - debug: 3.2.7 - eslint: 8.55.0 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.13.2)(eslint-plugin-import@2.29.0)(eslint@8.55.0) - transitivePeerDependencies: - - supports-color - dev: true - - /eslint-plugin-import@2.29.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0): - resolution: {integrity: sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - dependencies: - '@typescript-eslint/parser': 6.13.2(eslint@8.55.0)(typescript@5.3.2) - array-includes: 3.1.7 - array.prototype.findlastindex: 1.2.3 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.55.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) - hasown: 2.0.0 - is-core-module: 2.13.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.7 - object.groupby: 1.0.1 - object.values: 1.1.7 - semver: 6.3.1 - tsconfig-paths: 3.14.2 - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - dev: true - /eslint-plugin-tsdoc@0.2.17: - resolution: {integrity: sha512-xRmVi7Zx44lOBuYqG8vzTXuL6IdGOeF9nHX17bjJ8+VE6fsxpdGem0/SBTmAwgYMKYB1WBkqRJVQ+n8GK041pA==} - dependencies: - '@microsoft/tsdoc': 0.14.2 - '@microsoft/tsdoc-config': 0.16.2 - dev: true + eslint-plugin-tsdoc@0.3.0: + resolution: {integrity: sha512-0MuFdBrrJVBjT/gyhkP2BqpD0np1NxNLfQ38xXDlSs/KVVpKI2A6vN7jx2Rve/CyUsvOsMGwp9KKrinv7q9g3A==} - /eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - dev: true + eslint-scope@8.0.2: + resolution: {integrity: sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - /eslint-visitor-keys@3.4.3: + eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - /eslint@8.55.0: - resolution: {integrity: sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@4.0.0: + resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.9.1: + resolution: {integrity: sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0) - '@eslint-community/regexpp': 4.10.0 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.55.0 - '@humanwhocodes/config-array': 0.11.13 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.0 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@8.1.1) - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.5.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.23.0 - graphemer: 1.4.0 - ignore: 5.3.0 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.3 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - dev: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true - /espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - acorn: 8.11.2 - acorn-jsx: 5.3.2(acorn@8.11.2) - eslint-visitor-keys: 3.4.3 - dev: true + espree@10.1.0: + resolution: {integrity: sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - /esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} - dependencies: - estraverse: 5.3.0 - dev: true - /esrecurse@4.3.0: + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} - dependencies: - estraverse: 5.3.0 - dev: true - /estraverse@5.3.0: + estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} - dev: true - /estree-walker@0.5.2: + estree-walker@0.5.2: resolution: {integrity: sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig==} - dev: true - /estree-walker@0.6.1: + estree-walker@0.6.1: resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} - dev: true - /estree-walker@2.0.2: + estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - dev: true - /esutils@2.0.3: + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - dev: true - /etag@1.8.1: + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - dev: true - /eventemitter3@4.0.7: + eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - dev: true - /fancy-log@2.0.0: + fancy-log@2.0.0: resolution: {integrity: sha512-9CzxZbACXMUXW13tS0tI8XsGGmxWzO2DmYrGuBJOJ8k8q2K7hwfJA5qHjuPPe8wtsco33YR9wc+Rlr5wYFvhSA==} engines: {node: '>=10.13.0'} - dependencies: - color-support: 1.1.3 - dev: true - /fast-deep-equal@3.1.3: + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: true - /fast-glob@3.3.2: + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 - dev: true - /fast-json-stable-stringify@2.1.0: + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - dev: true - /fast-levenshtein@2.0.6: + fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true - /fastq@1.15.0: - resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} - dependencies: - reusify: 1.0.4 - dev: true + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - /fecha@4.2.3: + fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} - dev: true - /file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flat-cache: 3.2.0 - dev: true + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} - /fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - dev: true - /finalhandler@1.1.0: + finalhandler@1.1.0: resolution: {integrity: sha512-ejnvM9ZXYzp6PUPUyQBMBf0Co5VX2gr5H2VQe2Ui2jWXNlxv+PYZo8wpAymJNJdLsG1R4p+M4aynF8KuoUEwRw==} engines: {node: '>= 0.8'} - dependencies: - debug: 2.6.9 - encodeurl: 1.0.2 - escape-html: 1.0.3 - on-finished: 2.3.0 - parseurl: 1.3.3 - statuses: 1.3.1 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - dev: true - /find-up@5.0.0: + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - dev: true - /flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flatted: 3.2.9 - keyv: 4.5.4 - rimraf: 3.0.2 - dev: true + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} - /flat@5.0.2: + flat@5.0.2: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true - dev: true - /flatted@3.2.9: - resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} - dev: true + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - /fn.name@1.1.0: + fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} - dev: true - /follow-redirects@1.15.3(debug@4.3.2): - resolution: {integrity: sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==} + follow-redirects@1.15.6: + resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} engines: {node: '>=4.0'} peerDependencies: debug: '*' peerDependenciesMeta: debug: optional: true - dependencies: - debug: 4.3.2 - dev: true - /for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - dependencies: - is-callable: 1.2.7 - dev: true - - /foreground-child@2.0.0: - resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} - engines: {node: '>=8.0.0'} - dependencies: - cross-spawn: 7.0.3 - signal-exit: 3.0.7 - dev: true - - /foreground-child@3.1.1: - resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} - dependencies: - cross-spawn: 7.0.3 - signal-exit: 4.1.0 - dev: true - /fresh@0.5.2: + fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} - dev: true - /fs-extra@3.0.1: + fs-extra@3.0.1: resolution: {integrity: sha512-V3Z3WZWVUYd8hoCL5xfXJCaHWYzmtwW5XWYSlLgERi8PWd8bx1kUHUk8L1BT57e49oKnDDD180mjfrHc1yA9rg==} - dependencies: - graceful-fs: 4.2.11 - jsonfile: 3.0.1 - universalify: 0.1.2 - dev: true - /fs.realpath@1.0.0: + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true - /fsevents@2.3.3: + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - requiresBuild: true - dev: true - optional: true - /function-bind@1.1.2: + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - dev: true - - /function.prototype.name@1.1.6: - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - functions-have-names: 1.2.3 - dev: true - - /functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - dev: true - /get-caller-file@2.0.5: + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - dev: true - /get-func-name@2.0.2: + get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - dev: true - - /get-intrinsic@1.2.2: - resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} - dependencies: - function-bind: 1.1.2 - has-proto: 1.0.1 - has-symbols: 1.0.3 - hasown: 2.0.0 - dev: true - - /get-symbol-description@1.0.0: - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - dev: true - /get-tsconfig@4.7.2: - resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} - dependencies: - resolve-pkg-maps: 1.0.0 - dev: true + get-tsconfig@4.7.6: + resolution: {integrity: sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==} - /glob-parent@5.1.2: + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 - dev: true - /glob-parent@6.0.2: + glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - dependencies: - is-glob: 4.0.3 - dev: true - /glob@10.3.10: - resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} - engines: {node: '>=16 || 14 >=14.17'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true - dependencies: - foreground-child: 3.1.1 - jackspeak: 2.3.6 - minimatch: 9.0.3 - minipass: 7.0.4 - path-scurry: 1.10.1 - dev: true - /glob@7.2.0: - resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true - - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true + glob@11.0.0: + resolution: {integrity: sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==} + engines: {node: 20 || >=22} + hasBin: true - /glob@8.1.0: + glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 5.1.6 - once: 1.4.0 - dev: true + deprecated: Glob versions prior to v9 are no longer supported + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-errors@1.6.3: + resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} + engines: {node: '>= 0.6'} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + http-proxy@1.18.1: + resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} + engines: {node: '>=8.0.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + immutable@3.8.2: + resolution: {integrity: sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==} + engines: {node: '>=0.10.0'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + install@0.13.0: + resolution: {integrity: sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==} + engines: {node: '>= 0.10'} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-builtin-module@3.2.1: + resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} + engines: {node: '>=6'} + + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + + is-number-like@1.0.8: + resolution: {integrity: sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + + is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + is-wsl@1.1.0: + resolution: {integrity: sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==} + engines: {node: '>=4'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jackspeak@4.0.1: + resolution: {integrity: sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==} + engines: {node: 20 || >=22} + + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + jsonfile@3.0.1: + resolution: {integrity: sha512-oBko6ZHlubVB5mRFkur5vgYR1UyqX+S6Y/oCfLhqNdcc2fYFlDpIoNc7AfKS1KOGcnNAkvsr0grLck9ANM815w==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + limiter@1.1.5: + resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} + + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + + lodash.isfinite@3.3.2: + resolution: {integrity: sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + logform@2.6.1: + resolution: {integrity: sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==} + engines: {node: '>= 12.0.0'} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + loupe@3.1.1: + resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.0.0: + resolution: {integrity: sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==} + engines: {node: 20 || >=22} + + lunr@2.3.9: + resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + + magic-string@0.22.5: + resolution: {integrity: sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==} + + magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.4.1: + resolution: {integrity: sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==} + hasBin: true + + minimatch@10.0.1: + resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} + engines: {node: 20 || >=22} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mitt@1.2.0: + resolution: {integrity: sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==} + + mocha@10.7.3: + resolution: {integrity: sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==} + engines: {node: '>= 14.0.0'} + hasBin: true + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + + opn@5.3.0: + resolution: {integrity: sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==} + engines: {node: '>=4'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.0: + resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pnpm@9.9.0: + resolution: {integrity: sha512-YMGKzROL/2ldM5vmrRP36TbupnRWYNTMSndtUkfFQNDt7hpWNpXBg6ZuuRfviPK0/rH8JfMqetytx6rzQ46ZwQ==} + engines: {node: '>=18.12'} + hasBin: true + + portscanner@2.2.0: + resolution: {integrity: sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==} + engines: {node: '>=0.4', npm: '>=1.0.0'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + process-es6@0.11.6: + resolution: {integrity: sha512-GYBRQtL4v3wgigq10Pv58jmTbFXlIiTbSfgnNqZLY0ldUPqy1rRxDI5fCjoCpnM6TqmHQI8ydzTBXW86OYc0gA==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + resp-modifier@6.0.2: + resolution: {integrity: sha512-U1+0kWC/+4ncRFYqQWTx/3qkfE6a4B/h3XXgmXypfa0SPZ3t7cbbaFk297PjQS/yov24R18h6OZe6iZwj3NSLw==} + engines: {node: '>= 0.8.0'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup-plugin-node-globals@1.4.0: + resolution: {integrity: sha512-xRkB+W/m1KLIzPUmG0ofvR+CPNcvuCuNdjVBVS7ALKSxr3EDhnzNceGkGi1m8MToSli13AzKFYH4ie9w3I5L3g==} + + rollup-pluginutils@2.8.2: + resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} + + rollup@0.63.5: + resolution: {integrity: sha512-dFf8LpUNzIj3oE0vCvobX6rqOzHzLBoblyFp+3znPbjiSmSvOoK2kMKx+Fv9jYduG1rvcCfCveSgEaQHjWRF6g==} + hasBin: true + + rollup@4.21.1: + resolution: {integrity: sha512-ZnYyKvscThhgd3M5+Qt3pmhO4jIRR5RGzaSovB6Q7rGNrK5cUncrtLmcTTJVSdcKXyZjW8X8MB0JMSuH9bcAJg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rx@4.1.0: + resolution: {integrity: sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + send@0.16.2: + resolution: {integrity: sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==} + engines: {node: '>= 0.8.0'} + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + serve-index@1.9.1: + resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==} + engines: {node: '>= 0.8.0'} + + serve-static@1.13.2: + resolution: {integrity: sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==} + engines: {node: '>= 0.8.0'} + + server-destroy@1.0.1: + resolution: {integrity: sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==} + + setprototypeof@1.1.0: + resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shiki@1.14.1: + resolution: {integrity: sha512-FujAN40NEejeXdzPt+3sZ3F2dx1U24BY2XTY01+MG8mbxCiA2XukXdcbyMyLAHJ/1AUUnQd1tZlvIjefWWEJeA==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + + socket.io-adapter@2.5.5: + resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==} + + socket.io-client@4.7.5: + resolution: {integrity: sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==} + engines: {node: '>=10.0.0'} + + socket.io-parser@4.2.4: + resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + engines: {node: '>=10.0.0'} + + socket.io@4.7.5: + resolution: {integrity: sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==} + engines: {node: '>=10.2.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + + statuses@1.3.1: + resolution: {integrity: sha512-wuTCPGlJONk/a1kqZ4fQM2+908lC7fa7nPYpTC1EhnvqLX/IICbeP1OZGDtA374trpSq68YubKUMo8oRhN46yg==} + engines: {node: '>= 0.6'} + + statuses@1.4.0: + resolution: {integrity: sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==} + engines: {node: '>= 0.6'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + stream-throttle@0.1.3: + resolution: {integrity: sha512-889+B9vN9dq7/vLbGyuHeZ6/ctf5sNuGWsDy89uNxkFTAgzy0eK7+w5fL3KLNRTkLle7EgZGvHUphZW0Q26MnQ==} + engines: {node: '>= 0.10.0'} + hasBin: true + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + taffydb@2.7.3: + resolution: {integrity: sha512-GQ3gtYFSOAxSMN/apGtDKKkbJf+8izz5YfbGqIsUc7AMiQOapARZ76dhilRY2h39cynYxBFdafQo5HUL5vgkrg==} + + terser@5.31.6: + resolution: {integrity: sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==} + engines: {node: '>=10'} + hasBin: true + + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + tmp@0.2.3: + resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} + engines: {node: '>=14.14'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + + ts-api-utils@1.3.0: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + + tsx@4.19.0: + resolution: {integrity: sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typedoc-plugin-missing-exports@3.0.0: + resolution: {integrity: sha512-R7D8fYrK34mBFZSlF1EqJxfqiUSlQSmyrCiQgTQD52nNm6+kUtqwiaqaNkuJ2rA2wBgWFecUA8JzHT7x2r7ePg==} + peerDependencies: + typedoc: 0.26.x + + typedoc@0.26.6: + resolution: {integrity: sha512-SfEU3SH3wHNaxhFPjaZE2kNl/NFtLNW5c1oHsg7mti7GjmUj1Roq6osBQeMd+F4kL0BoRBBr8gQAuqBlfFu8LA==} + engines: {node: '>= 18'} + hasBin: true + peerDependencies: + typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x + + typescript-eslint@8.3.0: + resolution: {integrity: sha512-EvWjwWLwwKDIJuBjk2I6UkV8KEQcwZ0VM10nR1rIunRDIP67QJTZAHBXTX0HW/oI1H10YESF8yWie8fRQxjvFA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + engines: {node: '>=14.17'} + hasBin: true + + ua-parser-js@1.0.38: + resolution: {integrity: sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==} + + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vlq@0.2.3: + resolution: {integrity: sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + winston-transport@4.7.1: + resolution: {integrity: sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA==} + engines: {node: '>= 12.0.0'} + + winston@3.14.2: + resolution: {integrity: sha512-CO8cdpBB2yqzEf8v895L+GNKYJiEq8eKlHU38af3snQBQ+sdAIUepjMSguOIJC7ICbzm0ZI+Af2If4vIJrtmOg==} + engines: {node: '>= 12.0.0'} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + workerpool@6.5.1: + resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xmlhttprequest-ssl@2.0.0: + resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==} + engines: {node: '>=0.4.0'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yaml@2.5.0: + resolution: {integrity: sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==} + engines: {node: '>= 14'} + hasBin: true + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@bcoe/v8-coverage@0.2.3': {} + + '@colors/colors@1.6.0': {} + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@dabh/diagnostics@2.0.3': + dependencies: + colorspace: 1.1.4 + enabled: 2.0.0 + kuler: 2.0.0 + + '@esbuild/aix-ppc64@0.23.1': + optional: true + + '@esbuild/android-arm64@0.23.1': + optional: true + + '@esbuild/android-arm@0.23.1': + optional: true + + '@esbuild/android-x64@0.23.1': + optional: true + + '@esbuild/darwin-arm64@0.23.1': + optional: true + + '@esbuild/darwin-x64@0.23.1': + optional: true + + '@esbuild/freebsd-arm64@0.23.1': + optional: true + + '@esbuild/freebsd-x64@0.23.1': + optional: true + + '@esbuild/linux-arm64@0.23.1': + optional: true + + '@esbuild/linux-arm@0.23.1': + optional: true + + '@esbuild/linux-ia32@0.23.1': + optional: true + + '@esbuild/linux-loong64@0.23.1': + optional: true + + '@esbuild/linux-mips64el@0.23.1': + optional: true + + '@esbuild/linux-ppc64@0.23.1': + optional: true + + '@esbuild/linux-riscv64@0.23.1': + optional: true + + '@esbuild/linux-s390x@0.23.1': + optional: true + + '@esbuild/linux-x64@0.23.1': + optional: true + + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + + '@esbuild/openbsd-x64@0.23.1': + optional: true + + '@esbuild/sunos-x64@0.23.1': + optional: true + + '@esbuild/win32-arm64@0.23.1': + optional: true + + '@esbuild/win32-ia32@0.23.1': + optional: true + + '@esbuild/win32-x64@0.23.1': + optional: true + + '@eslint-community/eslint-utils@4.4.0(eslint@9.9.1)': + dependencies: + eslint: 9.9.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.11.0': {} + + '@eslint/config-array@0.18.0': + dependencies: + '@eslint/object-schema': 2.1.4 + debug: 4.3.6(supports-color@8.1.1) + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/eslintrc@3.1.0': + dependencies: + ajv: 6.12.6 + debug: 4.3.6(supports-color@8.1.1) + espree: 10.1.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.9.1': {} + + '@eslint/object-schema@2.1.4': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.0': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/source-map@0.3.6': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@microsoft/tsdoc-config@0.17.0': + dependencies: + '@microsoft/tsdoc': 0.15.0 + ajv: 8.12.0 + jju: 1.4.0 + resolve: 1.22.8 + + '@microsoft/tsdoc@0.15.0': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rollup/plugin-commonjs@26.0.1(rollup@4.21.1)': + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.21.1) + commondir: 1.0.1 + estree-walker: 2.0.2 + glob: 10.4.5 + is-reference: 1.2.1 + magic-string: 0.30.11 + optionalDependencies: + rollup: 4.21.1 + + '@rollup/plugin-node-resolve@15.2.3(rollup@4.21.1)': + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.21.1) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-builtin-module: 3.2.1 + is-module: 1.0.0 + resolve: 1.22.8 + optionalDependencies: + rollup: 4.21.1 + + '@rollup/plugin-typescript@11.1.6(rollup@4.21.1)(tslib@2.7.0)(typescript@5.5.4)': + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.21.1) + resolve: 1.22.8 + typescript: 5.5.4 + optionalDependencies: + rollup: 4.21.1 + tslib: 2.7.0 + + '@rollup/pluginutils@5.1.0(rollup@4.21.1)': + dependencies: + '@types/estree': 1.0.5 + estree-walker: 2.0.2 + picomatch: 2.3.1 + optionalDependencies: + rollup: 4.21.1 + + '@rollup/rollup-android-arm-eabi@4.21.1': + optional: true + + '@rollup/rollup-android-arm64@4.21.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.21.1': + optional: true + + '@rollup/rollup-darwin-x64@4.21.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.21.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.21.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.21.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.21.1': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.21.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.21.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.21.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.21.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.21.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.21.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.21.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.21.1': + optional: true + + '@shikijs/core@1.14.1': + dependencies: + '@types/hast': 3.0.4 + + '@socket.io/component-emitter@3.1.2': {} + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/chai@4.3.18': {} + + '@types/cookie@0.4.1': {} + + '@types/cors@2.8.17': + dependencies: + '@types/node': 22.5.0 + + '@types/diff@5.2.1': {} + + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.5 + '@types/json-schema': 7.0.15 + + '@types/estree@0.0.39': {} + + '@types/estree@1.0.5': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/json-schema@7.0.15': {} + + '@types/lodash.debounce@4.0.9': + dependencies: + '@types/lodash': 4.17.7 + + '@types/lodash.isequal@4.5.8': + dependencies: + '@types/lodash': 4.17.7 + + '@types/lodash@4.17.7': {} + + '@types/minimist@1.2.5': {} + + '@types/mocha@10.0.7': {} + + '@types/node@22.5.0': + dependencies: + undici-types: 6.19.8 + + '@types/resolve@1.20.2': {} + + '@types/rollup-plugin-node-globals@1.4.4': + dependencies: + '@types/node': 22.5.0 + rollup: 0.63.5 + + '@types/tmp@0.2.6': {} + + '@types/triple-beam@1.3.5': {} + + '@types/unist@3.0.3': {} + + '@typescript-eslint/eslint-plugin@8.3.0(@typescript-eslint/parser@8.3.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4)': + dependencies: + '@eslint-community/regexpp': 4.11.0 + '@typescript-eslint/parser': 8.3.0(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.3.0 + '@typescript-eslint/type-utils': 8.3.0(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/utils': 8.3.0(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.3.0 + eslint: 9.9.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.3.0(eslint@9.9.1)(typescript@5.5.4)': + dependencies: + '@typescript-eslint/scope-manager': 8.3.0 + '@typescript-eslint/types': 8.3.0 + '@typescript-eslint/typescript-estree': 8.3.0(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.3.0 + debug: 4.3.6(supports-color@8.1.1) + eslint: 9.9.1 + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.3.0': + dependencies: + '@typescript-eslint/types': 8.3.0 + '@typescript-eslint/visitor-keys': 8.3.0 + + '@typescript-eslint/type-utils@8.3.0(eslint@9.9.1)(typescript@5.5.4)': + dependencies: + '@typescript-eslint/typescript-estree': 8.3.0(typescript@5.5.4) + '@typescript-eslint/utils': 8.3.0(eslint@9.9.1)(typescript@5.5.4) + debug: 4.3.6(supports-color@8.1.1) + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - eslint + - supports-color + + '@typescript-eslint/types@8.3.0': {} + + '@typescript-eslint/typescript-estree@8.3.0(typescript@5.5.4)': + dependencies: + '@typescript-eslint/types': 8.3.0 + '@typescript-eslint/visitor-keys': 8.3.0 + debug: 4.3.6(supports-color@8.1.1) + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.3.0(eslint@9.9.1)(typescript@5.5.4)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1) + '@typescript-eslint/scope-manager': 8.3.0 + '@typescript-eslint/types': 8.3.0 + '@typescript-eslint/typescript-estree': 8.3.0(typescript@5.5.4) + eslint: 9.9.1 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@8.3.0': + dependencies: + '@typescript-eslint/types': 8.3.0 + eslint-visitor-keys: 3.4.3 + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn-jsx@5.3.2(acorn@8.12.1): + dependencies: + acorn: 8.12.1 + + acorn-walk@8.3.3: + dependencies: + acorn: 8.12.1 + + acorn@5.7.4: {} + + acorn@8.12.1: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.12.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@4.1.3: {} + + argparse@2.0.1: {} + + assertion-error@2.0.1: {} + + async-each-series@0.1.1: {} + + async@2.6.4: + dependencies: + lodash: 4.17.21 + + async@3.2.6: {} + + balanced-match@1.0.2: {} + + base64id@2.0.0: {} + + batch@0.6.1: {} + + binary-extensions@2.3.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browser-stdout@1.3.1: {} + + browser-sync-client@3.0.2: + dependencies: + etag: 1.8.1 + fresh: 0.5.2 + mitt: 1.2.0 + + browser-sync-ui@3.0.2: + dependencies: + async-each-series: 0.1.1 + chalk: 4.1.2 + connect-history-api-fallback: 1.6.0 + immutable: 3.8.2 + server-destroy: 1.0.1 + socket.io-client: 4.7.5 + stream-throttle: 0.1.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + browser-sync@3.0.2: + dependencies: + browser-sync-client: 3.0.2 + browser-sync-ui: 3.0.2 + bs-recipes: 1.3.4 + chalk: 4.1.2 + chokidar: 3.6.0 + connect: 3.6.6 + connect-history-api-fallback: 1.6.0 + dev-ip: 1.0.1 + easy-extender: 2.3.4 + eazy-logger: 4.0.1 + etag: 1.8.1 + fresh: 0.5.2 + fs-extra: 3.0.1 + http-proxy: 1.18.1 + immutable: 3.8.2 + micromatch: 4.0.8 + opn: 5.3.0 + portscanner: 2.2.0 + raw-body: 2.5.2 + resp-modifier: 6.0.2 + rx: 4.1.0 + send: 0.16.2 + serve-index: 1.9.1 + serve-static: 1.13.2 + server-destroy: 1.0.1 + socket.io: 4.7.5 + ua-parser-js: 1.0.38 + yargs: 17.7.2 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + bs-recipes@1.3.4: {} + + buffer-es6@4.9.3: {} + + buffer-from@1.1.2: {} + + builtin-modules@3.3.0: {} + + bytes@3.1.2: {} + + c8@10.1.2: + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@istanbuljs/schema': 0.1.3 + find-up: 5.0.0 + foreground-child: 3.3.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.1.7 + test-exclude: 7.0.1 + v8-to-istanbul: 9.3.0 + yargs: 17.7.2 + yargs-parser: 21.1.1 + + callsites@3.1.0: {} + + camelcase@6.3.0: {} + + chai@5.1.1: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.1 + pathval: 2.0.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.3.0: {} + + check-error@2.1.1: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color-support@1.1.3: {} + + color@3.2.1: + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + + colorspace@1.1.4: + dependencies: + color: 3.2.1 + text-hex: 1.0.0 + + commander@2.20.3: {} + + commondir@1.0.1: {} + + concat-map@0.0.1: {} + + connect-history-api-fallback@1.6.0: {} + + connect@3.6.6: + dependencies: + debug: 2.6.9 + finalhandler: 1.1.0 + parseurl: 1.3.3 + utils-merge: 1.0.1 + transitivePeerDependencies: + - supports-color + + convert-source-map@2.0.0: {} + + cookie@0.4.2: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + create-require@1.1.1: {} + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.3.6(supports-color@8.1.1): + dependencies: + ms: 2.1.2 + optionalDependencies: + supports-color: 8.1.1 + + decamelize@4.0.0: {} + + deep-eql@5.0.2: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + depd@1.1.2: {} + + depd@2.0.0: {} + + destroy@1.0.4: {} + + dev-ip@1.0.1: {} + + diff@4.0.2: {} + + diff@5.2.0: {} + + diff@6.0.0-beta: {} + + eastasianwidth@0.2.0: {} + + easy-extender@2.3.4: + dependencies: + lodash: 4.17.21 + + eazy-logger@4.0.1: + dependencies: + chalk: 4.1.2 + + ee-first@1.1.1: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + enabled@2.0.0: {} + + encodeurl@1.0.2: {} + + engine.io-client@6.5.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.6(supports-color@8.1.1) + engine.io-parser: 5.2.3 + ws: 8.17.1 + xmlhttprequest-ssl: 2.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + engine.io-parser@5.2.3: {} + + engine.io@6.5.5: + dependencies: + '@types/cookie': 0.4.1 + '@types/cors': 2.8.17 + '@types/node': 22.5.0 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.4.2 + cors: 2.8.5 + debug: 4.3.6(supports-color@8.1.1) + engine.io-parser: 5.2.3 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + entities@4.5.0: {} + + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + + escalade@3.1.2: {} + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-tsdoc@0.3.0: + dependencies: + '@microsoft/tsdoc': 0.15.0 + '@microsoft/tsdoc-config': 0.17.0 + + eslint-scope@8.0.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.0.0: {} + + eslint@9.9.1: + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1) + '@eslint-community/regexpp': 4.11.0 + '@eslint/config-array': 0.18.0 + '@eslint/eslintrc': 3.1.0 + '@eslint/js': 9.9.1 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.3.0 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.6(supports-color@8.1.1) + escape-string-regexp: 4.0.0 + eslint-scope: 8.0.2 + eslint-visitor-keys: 4.0.0 + espree: 10.1.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@10.1.0: + dependencies: + acorn: 8.12.1 + acorn-jsx: 5.3.2(acorn@8.12.1) + eslint-visitor-keys: 4.0.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@0.5.2: {} + + estree-walker@0.6.1: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + eventemitter3@4.0.7: {} + + fancy-log@2.0.0: + dependencies: + color-support: 1.1.3 + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fecha@4.2.3: {} + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.1.0: + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.3.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + + flat@5.0.2: {} + + flatted@3.3.1: {} + + fn.name@1.1.0: {} + + follow-redirects@1.15.6: {} + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + + fresh@0.5.2: {} + + fs-extra@3.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 3.0.1 + universalify: 0.1.2 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true - /globals@13.23.0: - resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==} - engines: {node: '>=8'} - dependencies: - type-fest: 0.20.2 - dev: true + function-bind@1.1.2: {} - /globalthis@1.0.3: - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} - engines: {node: '>= 0.4'} - dependencies: - define-properties: 1.2.1 - dev: true + get-caller-file@2.0.5: {} - /globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.0 - merge2: 1.4.1 - slash: 3.0.0 - dev: true + get-func-name@2.0.2: {} - /gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + get-tsconfig@4.7.6: dependencies: - get-intrinsic: 1.2.2 - dev: true + resolve-pkg-maps: 1.0.0 - /graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - dev: true + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 - /graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - dev: true + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 - /has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - dev: true + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.0 + path-scurry: 1.11.1 - /has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - dev: true + glob@11.0.0: + dependencies: + foreground-child: 3.3.0 + jackspeak: 4.0.1 + minimatch: 10.0.1 + minipass: 7.1.2 + package-json-from-dist: 1.0.0 + path-scurry: 2.0.0 - /has-property-descriptors@1.0.1: - resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} + glob@8.1.0: dependencies: - get-intrinsic: 1.2.2 - dev: true + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 - /has-proto@1.0.1: - resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} - engines: {node: '>= 0.4'} - dev: true + globals@14.0.0: {} - /has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} - dev: true + graceful-fs@4.2.11: {} - /has-tostringtag@1.0.0: - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} - engines: {node: '>= 0.4'} - dependencies: - has-symbols: 1.0.3 - dev: true + graphemer@1.4.0: {} - /hasown@2.0.0: - resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} - engines: {node: '>= 0.4'} + has-flag@4.0.0: {} + + hasown@2.0.2: dependencies: function-bind: 1.1.2 - dev: true - /he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - dev: true + he@1.2.0: {} - /html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - dev: true + html-escaper@2.0.2: {} - /http-errors@1.6.3: - resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} - engines: {node: '>= 0.6'} + http-errors@1.6.3: dependencies: depd: 1.1.2 inherits: 2.0.3 setprototypeof: 1.1.0 statuses: 1.4.0 - dev: true - /http-errors@2.0.0: - resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} - engines: {node: '>= 0.8'} + http-errors@2.0.0: dependencies: depd: 2.0.0 inherits: 2.0.4 setprototypeof: 1.2.0 statuses: 2.0.1 toidentifier: 1.0.1 - dev: true - /http-proxy@1.18.1: - resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} - engines: {node: '>=8.0.0'} + http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.3(debug@4.3.2) + follow-redirects: 1.15.6 requires-port: 1.0.0 transitivePeerDependencies: - debug - dev: true - /iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 - dev: true - /ignore@5.3.0: - resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} - engines: {node: '>= 4'} - dev: true + ignore@5.3.2: {} - /immutable@3.8.2: - resolution: {integrity: sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==} - engines: {node: '>=0.10.0'} - dev: true + immutable@3.8.2: {} - /import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} + import-fresh@3.3.0: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - dev: true - /imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - dev: true + imurmurhash@0.1.4: {} - /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + inflight@1.0.6: dependencies: once: 1.4.0 wrappy: 1.0.2 - dev: true - - /inherits@2.0.3: - resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} - dev: true - - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true - - /install@0.13.0: - resolution: {integrity: sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==} - engines: {node: '>= 0.10'} - dev: true - /internal-slot@1.0.6: - resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} - engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.2 - hasown: 2.0.0 - side-channel: 1.0.4 - dev: true - - /is-array-buffer@3.0.2: - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} - dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - is-typed-array: 1.1.12 - dev: true + inherits@2.0.3: {} - /is-arrayish@0.3.2: - resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - dev: true + inherits@2.0.4: {} - /is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} - dependencies: - has-bigints: 1.0.2 - dev: true + install@0.13.0: {} - /is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - dependencies: - binary-extensions: 2.2.0 - dev: true + is-arrayish@0.3.2: {} - /is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} - engines: {node: '>= 0.4'} + is-binary-path@2.1.0: dependencies: - call-bind: 1.0.5 - has-tostringtag: 1.0.0 - dev: true + binary-extensions: 2.3.0 - /is-builtin-module@3.2.1: - resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} - engines: {node: '>=6'} + is-builtin-module@3.2.1: dependencies: builtin-modules: 3.3.0 - dev: true - - /is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - dev: true - - /is-core-module@2.13.1: - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} - dependencies: - hasown: 2.0.0 - dev: true - /is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} - engines: {node: '>= 0.4'} + is-core-module@2.15.1: dependencies: - has-tostringtag: 1.0.0 - dev: true + hasown: 2.0.2 - /is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - dev: true + is-extglob@2.1.1: {} - /is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - dev: true + is-fullwidth-code-point@3.0.0: {} - /is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 - dev: true - - /is-module@1.0.0: - resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} - dev: true - /is-negative-zero@2.0.2: - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} - engines: {node: '>= 0.4'} - dev: true + is-module@1.0.0: {} - /is-number-like@1.0.8: - resolution: {integrity: sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==} + is-number-like@1.0.8: dependencies: lodash.isfinite: 3.3.2 - dev: true - - /is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - dev: true - /is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - dev: true + is-number@7.0.0: {} - /is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - dev: true + is-path-inside@3.0.3: {} - /is-plain-obj@2.1.0: - resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} - engines: {node: '>=8'} - dev: true + is-plain-obj@2.1.0: {} - /is-reference@1.2.1: - resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + is-reference@1.2.1: dependencies: '@types/estree': 1.0.5 - dev: true - - /is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - has-tostringtag: 1.0.0 - dev: true - /is-shared-array-buffer@1.0.2: - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} - dependencies: - call-bind: 1.0.5 - dev: true - - /is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - dev: true - - /is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - dev: true - - /is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} - engines: {node: '>= 0.4'} - dependencies: - has-symbols: 1.0.3 - dev: true - - /is-typed-array@1.1.12: - resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} - engines: {node: '>= 0.4'} - dependencies: - which-typed-array: 1.1.13 - dev: true - - /is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - dev: true + is-stream@2.0.1: {} - /is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} - dependencies: - call-bind: 1.0.5 - dev: true - - /is-wsl@1.1.0: - resolution: {integrity: sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==} - engines: {node: '>=4'} - dev: true + is-unicode-supported@0.1.0: {} - /isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - dev: true + is-wsl@1.1.0: {} - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true + isexe@2.0.0: {} - /istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - dev: true + istanbul-lib-coverage@3.2.2: {} - /istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} + istanbul-lib-report@3.0.1: dependencies: istanbul-lib-coverage: 3.2.2 make-dir: 4.0.0 supports-color: 7.2.0 - dev: true - /istanbul-reports@3.1.6: - resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} - engines: {node: '>=8'} + istanbul-reports@3.1.7: dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 - dev: true - /jackspeak@2.3.6: - resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} - engines: {node: '>=14'} + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - dev: true - /jju@1.4.0: - resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} - dev: true + jackspeak@4.0.1: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 - /js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true + jju@1.4.0: {} - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true + js-tokens@4.0.0: {} + + js-yaml@4.1.0: dependencies: argparse: 2.0.1 - dev: true - /json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - dev: true - - /json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true + json-buffer@3.0.1: {} - /json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: true + json-schema-traverse@0.4.1: {} - /json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true - dependencies: - minimist: 1.2.8 - dev: true + json-schema-traverse@1.0.0: {} - /jsonc-parser@3.2.0: - resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} - dev: true + json-stable-stringify-without-jsonify@1.0.1: {} - /jsonfile@3.0.1: - resolution: {integrity: sha512-oBko6ZHlubVB5mRFkur5vgYR1UyqX+S6Y/oCfLhqNdcc2fYFlDpIoNc7AfKS1KOGcnNAkvsr0grLck9ANM815w==} + jsonfile@3.0.1: optionalDependencies: graceful-fs: 4.2.11 - dev: true - /keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 - dev: true - /kuler@2.0.0: - resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} - dev: true + kuler@2.0.0: {} - /levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 - dev: true - /limiter@1.1.5: - resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} - dev: true + limiter@1.1.5: {} - /localtunnel@2.0.2: - resolution: {integrity: sha512-n418Cn5ynvJd7m/N1d9WVJISLJF/ellZnfsLnx8WBWGzxv/ntNcFkJ1o6se5quUhCplfLGBNL5tYHiq5WF3Nug==} - engines: {node: '>=8.3.0'} - hasBin: true + linkify-it@5.0.0: dependencies: - axios: 0.21.4(debug@4.3.2) - debug: 4.3.2 - openurl: 1.1.1 - yargs: 17.1.1 - transitivePeerDependencies: - - supports-color - dev: true + uc.micro: 2.1.0 - /locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 - dev: true - /lodash.isfinite@3.3.2: - resolution: {integrity: sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA==} - dev: true + lodash.debounce@4.0.8: {} - /lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true + lodash.isequal@4.5.0: {} - /lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash.isfinite@3.3.2: {} - /log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + log-symbols@4.1.0: dependencies: chalk: 4.1.2 is-unicode-supported: 0.1.0 - dev: true - /logform@2.6.0: - resolution: {integrity: sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==} - engines: {node: '>= 12.0.0'} + logform@2.6.1: dependencies: '@colors/colors': 1.6.0 '@types/triple-beam': 1.3.5 fecha: 4.2.3 ms: 2.1.3 - safe-stable-stringify: 2.4.3 + safe-stable-stringify: 2.5.0 triple-beam: 1.4.1 - dev: true - /loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 - dev: true - /loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + loupe@3.1.1: dependencies: get-func-name: 2.0.2 - dev: true - /lru-cache@10.1.0: - resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==} - engines: {node: 14 || >=16.14} - dev: true + lru-cache@10.4.3: {} - /lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - dependencies: - yallist: 4.0.0 - dev: true + lru-cache@11.0.0: {} - /lunr@2.3.9: - resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} - dev: true + lunr@2.3.9: {} - /magic-string@0.22.5: - resolution: {integrity: sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==} + magic-string@0.22.5: dependencies: vlq: 0.2.3 - dev: true - /magic-string@0.30.5: - resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} - engines: {node: '>=12'} + magic-string@0.30.11: dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 - dev: true + '@jridgewell/sourcemap-codec': 1.5.0 - /make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} + make-dir@4.0.0: dependencies: - semver: 7.5.4 - dev: true + semver: 7.6.3 - /make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - dev: true + make-error@1.3.6: {} - /marked@4.3.0: - resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} - engines: {node: '>= 12'} - hasBin: true - dev: true + markdown-it@14.1.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 - /merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - dev: true + mdurl@2.0.0: {} - /micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} + merge2@1.4.1: {} + + micromatch@4.0.8: dependencies: - braces: 3.0.2 + braces: 3.0.3 picomatch: 2.3.1 - dev: true - /mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - dev: true + mime-db@1.52.0: {} - /mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 - dev: true - /mime@1.4.1: - resolution: {integrity: sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==} - hasBin: true - dev: true + mime@1.4.1: {} - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@10.0.1: dependencies: - brace-expansion: 1.1.11 - dev: true + brace-expansion: 2.0.1 - /minimatch@5.0.1: - resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==} - engines: {node: '>=10'} + minimatch@3.1.2: dependencies: - brace-expansion: 2.0.1 - dev: true + brace-expansion: 1.1.11 - /minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} + minimatch@5.1.6: dependencies: brace-expansion: 2.0.1 - dev: true - /minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} + minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 - dev: true - /minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: true + minimist@1.2.8: {} - /minipass@7.0.4: - resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} - engines: {node: '>=16 || 14 >=14.17'} - dev: true + minipass@7.1.2: {} - /mitt@1.2.0: - resolution: {integrity: sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==} - dev: true + mitt@1.2.0: {} - /mocha@10.2.0: - resolution: {integrity: sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==} - engines: {node: '>= 14.0.0'} - hasBin: true + mocha@10.7.3: dependencies: - ansi-colors: 4.1.1 + ansi-colors: 4.1.3 browser-stdout: 1.3.1 - chokidar: 3.5.3 - debug: 4.3.4(supports-color@8.1.1) - diff: 5.0.0 + chokidar: 3.6.0 + debug: 4.3.6(supports-color@8.1.1) + diff: 5.2.0 escape-string-regexp: 4.0.0 find-up: 5.0.0 - glob: 7.2.0 + glob: 8.1.0 he: 1.2.0 js-yaml: 4.1.0 log-symbols: 4.1.0 - minimatch: 5.0.1 + minimatch: 5.1.6 ms: 2.1.3 - nanoid: 3.3.3 - serialize-javascript: 6.0.0 + serialize-javascript: 6.0.2 strip-json-comments: 3.1.1 supports-color: 8.1.1 - workerpool: 6.2.1 + workerpool: 6.5.1 yargs: 16.2.0 - yargs-parser: 20.2.4 + yargs-parser: 20.2.9 yargs-unparser: 2.0.0 - dev: true - - /ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - dev: true - - /ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true - - /ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: true - - /nanoid@3.3.3: - resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true - - /natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - dev: true - - /negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} - dev: true - - /normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - dev: true - /object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - dev: true + ms@2.0.0: {} - /object-inspect@1.13.1: - resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} - dev: true + ms@2.1.2: {} - /object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - dev: true + ms@2.1.3: {} - /object.assign@4.1.5: - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - has-symbols: 1.0.3 - object-keys: 1.1.1 - dev: true + natural-compare@1.4.0: {} - /object.fromentries@2.0.7: - resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - dev: true + negotiator@0.6.3: {} - /object.groupby@1.0.1: - resolution: {integrity: sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - get-intrinsic: 1.2.2 - dev: true + normalize-path@3.0.0: {} - /object.values@1.1.7: - resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - dev: true + object-assign@4.1.1: {} - /on-finished@2.3.0: - resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} - engines: {node: '>= 0.8'} + on-finished@2.3.0: dependencies: ee-first: 1.1.1 - dev: true - /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + once@1.4.0: dependencies: wrappy: 1.0.2 - dev: true - /one-time@1.0.0: - resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + one-time@1.0.0: dependencies: fn.name: 1.1.0 - dev: true - - /openurl@1.1.1: - resolution: {integrity: sha512-d/gTkTb1i1GKz5k3XE3XFV/PxQ1k45zDqGP2OA7YhgsaLoqm6qRvARAZOFer1fcXritWlGBRCu/UgeS4HAnXAA==} - dev: true - /opn@5.3.0: - resolution: {integrity: sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==} - engines: {node: '>=4'} + opn@5.3.0: dependencies: is-wsl: 1.1.0 - dev: true - /optionator@0.9.3: - resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} - engines: {node: '>= 0.8.0'} + optionator@0.9.4: dependencies: - '@aashutoshrathi/word-wrap': 1.2.6 deep-is: 0.1.4 fast-levenshtein: 2.0.6 levn: 0.4.1 prelude-ls: 1.2.1 type-check: 0.4.0 - dev: true + word-wrap: 1.2.5 - /p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 - dev: true - /p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + p-locate@5.0.0: dependencies: p-limit: 3.1.0 - dev: true - /parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} + package-json-from-dist@1.0.0: {} + + parent-module@1.0.1: dependencies: callsites: 3.1.0 - dev: true - - /parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - dev: true - /path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - dev: true + parseurl@1.3.3: {} - /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - dev: true + path-exists@4.0.0: {} - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - dev: true + path-key@3.1.1: {} - /path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: true + path-parse@1.0.7: {} - /path-scurry@1.10.1: - resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} - engines: {node: '>=16 || 14 >=14.17'} + path-scurry@1.11.1: dependencies: - lru-cache: 10.1.0 - minipass: 7.0.4 - dev: true + lru-cache: 10.4.3 + minipass: 7.1.2 - /path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - dev: true + path-scurry@2.0.0: + dependencies: + lru-cache: 11.0.0 + minipass: 7.1.2 - /pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - dev: true + pathval@2.0.0: {} - /picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - dev: true + picomatch@2.3.1: {} - /pnpm@8.11.0: - resolution: {integrity: sha512-nfh8FsmNsntOBR14fmfyIH7EfoCcywe/e17ErNzRYTNVg5o40LkAFEkj1qcFdwC3TSoMyxVYvrJBZHoSBqmnqw==} - engines: {node: '>=16.14'} - hasBin: true - dev: true + pnpm@9.9.0: {} - /portscanner@2.2.0: - resolution: {integrity: sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==} - engines: {node: '>=0.4', npm: '>=1.0.0'} + portscanner@2.2.0: dependencies: async: 2.6.4 is-number-like: 1.0.8 - dev: true - /prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - dev: true + prelude-ls@1.2.1: {} + + process-es6@0.11.6: {} - /process-es6@0.11.6: - resolution: {integrity: sha512-GYBRQtL4v3wgigq10Pv58jmTbFXlIiTbSfgnNqZLY0ldUPqy1rRxDI5fCjoCpnM6TqmHQI8ydzTBXW86OYc0gA==} - dev: true + punycode.js@2.3.1: {} - /punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - dev: true + punycode@2.3.1: {} - /queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true + queue-microtask@1.2.3: {} - /randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 - dev: true - /range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - dev: true + range-parser@1.2.1: {} - /raw-body@2.5.2: - resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} - engines: {node: '>= 0.8'} + raw-body@2.5.2: dependencies: bytes: 3.1.2 http-errors: 2.0.0 iconv-lite: 0.4.24 unpipe: 1.0.0 - dev: true - /react-dom@18.2.0(react@18.2.0): - resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} - peerDependencies: - react: ^18.2.0 + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 - react: 18.2.0 - scheduler: 0.23.0 - dev: true + react: 18.3.1 + scheduler: 0.23.2 - /react@18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} - engines: {node: '>=0.10.0'} + react@18.3.1: dependencies: loose-envify: 1.4.0 - dev: true - /readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} + readable-stream@3.6.2: dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 - dev: true - /readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + readdirp@3.6.0: dependencies: picomatch: 2.3.1 - dev: true - - /regexp.prototype.flags@1.5.1: - resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - set-function-name: 2.0.1 - dev: true - /require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - dev: true + require-directory@2.1.1: {} - /requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - dev: true + require-from-string@2.0.2: {} - /resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - dev: true + requires-port@1.0.0: {} - /resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - dev: true + resolve-from@4.0.0: {} - /resolve@1.19.0: - resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==} - dependencies: - is-core-module: 2.13.1 - path-parse: 1.0.7 - dev: true + resolve-pkg-maps@1.0.0: {} - /resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} - hasBin: true + resolve@1.22.8: dependencies: - is-core-module: 2.13.1 + is-core-module: 2.15.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: true - /resp-modifier@6.0.2: - resolution: {integrity: sha512-U1+0kWC/+4ncRFYqQWTx/3qkfE6a4B/h3XXgmXypfa0SPZ3t7cbbaFk297PjQS/yov24R18h6OZe6iZwj3NSLw==} - engines: {node: '>= 0.8.0'} + resp-modifier@6.0.2: dependencies: debug: 2.6.9 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - dev: true - /reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true - - /rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true - dependencies: - glob: 7.2.3 - dev: true + reusify@1.0.4: {} - /rollup-plugin-node-globals@1.4.0: - resolution: {integrity: sha512-xRkB+W/m1KLIzPUmG0ofvR+CPNcvuCuNdjVBVS7ALKSxr3EDhnzNceGkGi1m8MToSli13AzKFYH4ie9w3I5L3g==} + rollup-plugin-node-globals@1.4.0: dependencies: acorn: 5.7.4 buffer-es6: 4.9.3 @@ -3321,105 +3533,57 @@ packages: magic-string: 0.22.5 process-es6: 0.11.6 rollup-pluginutils: 2.8.2 - dev: true - /rollup-pluginutils@2.8.2: - resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} + rollup-pluginutils@2.8.2: dependencies: estree-walker: 0.6.1 - dev: true - /rollup@0.63.5: - resolution: {integrity: sha512-dFf8LpUNzIj3oE0vCvobX6rqOzHzLBoblyFp+3znPbjiSmSvOoK2kMKx+Fv9jYduG1rvcCfCveSgEaQHjWRF6g==} - hasBin: true + rollup@0.63.5: dependencies: '@types/estree': 0.0.39 - '@types/node': 20.10.3 - dev: true + '@types/node': 22.5.0 - /rollup@4.6.1: - resolution: {integrity: sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true + rollup@4.21.1: + dependencies: + '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.6.1 - '@rollup/rollup-android-arm64': 4.6.1 - '@rollup/rollup-darwin-arm64': 4.6.1 - '@rollup/rollup-darwin-x64': 4.6.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.6.1 - '@rollup/rollup-linux-arm64-gnu': 4.6.1 - '@rollup/rollup-linux-arm64-musl': 4.6.1 - '@rollup/rollup-linux-x64-gnu': 4.6.1 - '@rollup/rollup-linux-x64-musl': 4.6.1 - '@rollup/rollup-win32-arm64-msvc': 4.6.1 - '@rollup/rollup-win32-ia32-msvc': 4.6.1 - '@rollup/rollup-win32-x64-msvc': 4.6.1 + '@rollup/rollup-android-arm-eabi': 4.21.1 + '@rollup/rollup-android-arm64': 4.21.1 + '@rollup/rollup-darwin-arm64': 4.21.1 + '@rollup/rollup-darwin-x64': 4.21.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.21.1 + '@rollup/rollup-linux-arm-musleabihf': 4.21.1 + '@rollup/rollup-linux-arm64-gnu': 4.21.1 + '@rollup/rollup-linux-arm64-musl': 4.21.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.21.1 + '@rollup/rollup-linux-riscv64-gnu': 4.21.1 + '@rollup/rollup-linux-s390x-gnu': 4.21.1 + '@rollup/rollup-linux-x64-gnu': 4.21.1 + '@rollup/rollup-linux-x64-musl': 4.21.1 + '@rollup/rollup-win32-arm64-msvc': 4.21.1 + '@rollup/rollup-win32-ia32-msvc': 4.21.1 + '@rollup/rollup-win32-x64-msvc': 4.21.1 fsevents: 2.3.3 - dev: true - /run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - dev: true - - /rx@4.1.0: - resolution: {integrity: sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug==} - dev: true - /safe-array-concat@1.0.1: - resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==} - engines: {node: '>=0.4'} - dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - has-symbols: 1.0.3 - isarray: 2.0.5 - dev: true - - /safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: true + rx@4.1.0: {} - /safe-regex-test@1.0.0: - resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} - dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - is-regex: 1.1.4 - dev: true + safe-buffer@5.2.1: {} - /safe-stable-stringify@2.4.3: - resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} - engines: {node: '>=10'} - dev: true + safe-stable-stringify@2.5.0: {} - /safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - dev: true + safer-buffer@2.1.2: {} - /scheduler@0.23.0: - resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 - dev: true - - /semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - dev: true - /semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - dev: true + semver@7.6.3: {} - /send@0.16.2: - resolution: {integrity: sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==} - engines: {node: '>= 0.8.0'} + send@0.16.2: dependencies: debug: 2.6.9 depd: 1.1.2 @@ -3436,17 +3600,12 @@ packages: statuses: 1.4.0 transitivePeerDependencies: - supports-color - dev: true - /serialize-javascript@6.0.0: - resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} + serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 - dev: true - /serve-index@1.9.1: - resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==} - engines: {node: '>= 0.8.0'} + serve-index@1.9.1: dependencies: accepts: 1.3.8 batch: 0.6.1 @@ -3457,11 +3616,8 @@ packages: parseurl: 1.3.3 transitivePeerDependencies: - supports-color - dev: true - /serve-static@1.13.2: - resolution: {integrity: sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==} - engines: {node: '>= 0.8.0'} + serve-static@1.13.2: dependencies: encodeurl: 1.0.2 escape-html: 1.0.3 @@ -3469,732 +3625,328 @@ packages: send: 0.16.2 transitivePeerDependencies: - supports-color - dev: true - - /server-destroy@1.0.1: - resolution: {integrity: sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==} - dev: true - - /set-function-length@1.1.1: - resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} - engines: {node: '>= 0.4'} - dependencies: - define-data-property: 1.1.1 - get-intrinsic: 1.2.2 - gopd: 1.0.1 - has-property-descriptors: 1.0.1 - dev: true - /set-function-name@2.0.1: - resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} - engines: {node: '>= 0.4'} - dependencies: - define-data-property: 1.1.1 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.1 - dev: true + server-destroy@1.0.1: {} - /setprototypeof@1.1.0: - resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} - dev: true + setprototypeof@1.1.0: {} - /setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - dev: true + setprototypeof@1.2.0: {} - /shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - dev: true - /shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - dev: true - - /shiki@0.14.5: - resolution: {integrity: sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==} - dependencies: - ansi-sequence-parser: 1.1.1 - jsonc-parser: 3.2.0 - vscode-oniguruma: 1.7.0 - vscode-textmate: 8.0.0 - dev: true + shebang-regex@3.0.0: {} - /side-channel@1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + shiki@1.14.1: dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - object-inspect: 1.13.1 - dev: true + '@shikijs/core': 1.14.1 + '@types/hast': 3.0.4 - /signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: true + signal-exit@4.1.0: {} - /signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - dev: true - - /simple-swizzle@0.2.2: - resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 - dev: true - /slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - dev: true - - /socket.io-adapter@2.5.2: - resolution: {integrity: sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==} + socket.io-adapter@2.5.5: dependencies: - ws: 8.11.0 + debug: 4.3.6(supports-color@8.1.1) + ws: 8.17.1 transitivePeerDependencies: - bufferutil + - supports-color - utf-8-validate - dev: true - /socket.io-client@4.7.2: - resolution: {integrity: sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==} - engines: {node: '>=10.0.0'} + socket.io-client@4.7.5: dependencies: - '@socket.io/component-emitter': 3.1.0 - debug: 4.3.4(supports-color@8.1.1) - engine.io-client: 6.5.3 + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.6(supports-color@8.1.1) + engine.io-client: 6.5.4 socket.io-parser: 4.2.4 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - dev: true - /socket.io-parser@4.2.4: - resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} - engines: {node: '>=10.0.0'} + socket.io-parser@4.2.4: dependencies: - '@socket.io/component-emitter': 3.1.0 - debug: 4.3.4(supports-color@8.1.1) + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.6(supports-color@8.1.1) transitivePeerDependencies: - supports-color - dev: true - /socket.io@4.7.2: - resolution: {integrity: sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==} - engines: {node: '>=10.2.0'} + socket.io@4.7.5: dependencies: accepts: 1.3.8 base64id: 2.0.0 cors: 2.8.5 - debug: 4.3.4(supports-color@8.1.1) - engine.io: 6.5.4 - socket.io-adapter: 2.5.2 + debug: 4.3.6(supports-color@8.1.1) + engine.io: 6.5.5 + socket.io-adapter: 2.5.5 socket.io-parser: 4.2.4 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - dev: true - /source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 - dev: true - /source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - dev: true + source-map@0.6.1: {} - /stack-trace@0.0.10: - resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} - dev: true + stack-trace@0.0.10: {} - /statuses@1.3.1: - resolution: {integrity: sha512-wuTCPGlJONk/a1kqZ4fQM2+908lC7fa7nPYpTC1EhnvqLX/IICbeP1OZGDtA374trpSq68YubKUMo8oRhN46yg==} - engines: {node: '>= 0.6'} - dev: true + statuses@1.3.1: {} - /statuses@1.4.0: - resolution: {integrity: sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==} - engines: {node: '>= 0.6'} - dev: true + statuses@1.4.0: {} - /statuses@2.0.1: - resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} - engines: {node: '>= 0.8'} - dev: true + statuses@2.0.1: {} - /stream-throttle@0.1.3: - resolution: {integrity: sha512-889+B9vN9dq7/vLbGyuHeZ6/ctf5sNuGWsDy89uNxkFTAgzy0eK7+w5fL3KLNRTkLle7EgZGvHUphZW0Q26MnQ==} - engines: {node: '>= 0.10.0'} - hasBin: true + stream-throttle@0.1.3: dependencies: commander: 2.20.3 limiter: 1.1.5 - dev: true - /string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - dev: true - /string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} + string-width@5.1.2: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - dev: true - - /string.prototype.trim@1.2.8: - resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - dev: true - - /string.prototype.trimend@1.0.7: - resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - dev: true - - /string.prototype.trimstart@1.0.7: - resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - dev: true - /string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 - dev: true - /strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - dev: true - /strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} + strip-ansi@7.1.0: dependencies: ansi-regex: 6.0.1 - dev: true - /strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - dev: true - - /strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - dev: true + strip-json-comments@3.1.1: {} - /supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 - dev: true - /supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} + supports-color@8.1.1: dependencies: has-flag: 4.0.0 - dev: true - /supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - dev: true - - /taffydb@2.7.3: - resolution: {integrity: sha512-GQ3gtYFSOAxSMN/apGtDKKkbJf+8izz5YfbGqIsUc7AMiQOapARZ76dhilRY2h39cynYxBFdafQo5HUL5vgkrg==} - dev: true + supports-preserve-symlinks-flag@1.0.0: {} - /tapable@2.2.1: - resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} - engines: {node: '>=6'} - dev: true + taffydb@2.7.3: {} - /terser@5.25.0: - resolution: {integrity: sha512-we0I9SIsfvNUMP77zC9HG+MylwYYsGFSBG8qm+13oud2Yh+O104y614FRbyjpxys16jZwot72Fpi827YvGzuqg==} - engines: {node: '>=10'} - hasBin: true + terser@5.31.6: dependencies: - '@jridgewell/source-map': 0.3.5 - acorn: 8.11.2 + '@jridgewell/source-map': 0.3.6 + acorn: 8.12.1 commander: 2.20.3 source-map-support: 0.5.21 - dev: true - /test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} + test-exclude@7.0.1: dependencies: '@istanbuljs/schema': 0.1.3 - glob: 7.2.3 - minimatch: 3.1.2 - dev: true + glob: 10.4.5 + minimatch: 9.0.5 - /text-hex@1.0.0: - resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} - dev: true + text-hex@1.0.0: {} - /text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - dev: true + text-table@0.2.0: {} - /tmp@0.2.1: - resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} - engines: {node: '>=8.17.0'} - dependencies: - rimraf: 3.0.2 - dev: true + tmp@0.2.3: {} - /to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - dev: true - /toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - dev: true + toidentifier@1.0.1: {} - /triple-beam@1.4.1: - resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} - engines: {node: '>= 14.0.0'} - dev: true + triple-beam@1.4.1: {} - /ts-api-utils@1.0.3(typescript@5.3.2): - resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} - engines: {node: '>=16.13.0'} - peerDependencies: - typescript: '>=4.2.0' + ts-api-utils@1.3.0(typescript@5.5.4): dependencies: - typescript: 5.3.2 - dev: true + typescript: 5.5.4 - /ts-node@10.9.1(@types/node@20.10.3)(typescript@5.3.2): - resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true + ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.9 + '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.10.3 - acorn: 8.11.2 - acorn-walk: 8.3.0 + '@types/node': 22.5.0 + acorn: 8.12.1 + acorn-walk: 8.3.3 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.3.2 + typescript: 5.5.4 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - dev: true - - /tsconfig-paths@3.14.2: - resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} - dependencies: - '@types/json5': 0.0.29 - json5: 1.0.2 - minimist: 1.2.8 - strip-bom: 3.0.0 - dev: true - /tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - dev: true + tslib@2.7.0: {} - /tsx@4.6.2: - resolution: {integrity: sha512-QPpBdJo+ZDtqZgAnq86iY/PD2KYCUPSUGIunHdGwyII99GKH+f3z3FZ8XNFLSGQIA4I365ui8wnQpl8OKLqcsg==} - engines: {node: '>=18.0.0'} - hasBin: true + tsx@4.19.0: dependencies: - esbuild: 0.18.20 - get-tsconfig: 4.7.2 + esbuild: 0.23.1 + get-tsconfig: 4.7.6 optionalDependencies: fsevents: 2.3.3 - dev: true - /type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - dev: true - - /type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - dev: true - - /type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - dev: true - - /typed-array-buffer@1.0.0: - resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - is-typed-array: 1.1.12 - dev: true - /typed-array-byte-length@1.0.0: - resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - for-each: 0.3.3 - has-proto: 1.0.1 - is-typed-array: 1.1.12 - dev: true - - /typed-array-byte-offset@1.0.0: - resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} - engines: {node: '>= 0.4'} + typedoc-plugin-missing-exports@3.0.0(typedoc@0.26.6(typescript@5.5.4)): dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.5 - for-each: 0.3.3 - has-proto: 1.0.1 - is-typed-array: 1.1.12 - dev: true + typedoc: 0.26.6(typescript@5.5.4) - /typed-array-length@1.0.4: - resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + typedoc@0.26.6(typescript@5.5.4): dependencies: - call-bind: 1.0.5 - for-each: 0.3.3 - is-typed-array: 1.1.12 - dev: true + lunr: 2.3.9 + markdown-it: 14.1.0 + minimatch: 9.0.5 + shiki: 1.14.1 + typescript: 5.5.4 + yaml: 2.5.0 - /typedoc-plugin-missing-exports@2.1.0(typedoc@0.25.4): - resolution: {integrity: sha512-+1DhqZCEu7Vu5APnrqpPwl31D+hXpt1fV0Le9ycCRL1eLVdatdl6KVt4SEVwPxnEpKwgOn2dNX6I9+0F1aO2aA==} - peerDependencies: - typedoc: 0.24.x || 0.25.x + typescript-eslint@8.3.0(eslint@9.9.1)(typescript@5.5.4): dependencies: - typedoc: 0.25.4(typescript@5.3.2) - dev: true + '@typescript-eslint/eslint-plugin': 8.3.0(@typescript-eslint/parser@8.3.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/parser': 8.3.0(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/utils': 8.3.0(eslint@9.9.1)(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - eslint + - supports-color - /typedoc@0.25.4(typescript@5.3.2): - resolution: {integrity: sha512-Du9ImmpBCw54bX275yJrxPVnjdIyJO/84co0/L9mwe0R3G4FSR6rQ09AlXVRvZEGMUg09+z/usc8mgygQ1aidA==} - engines: {node: '>= 16'} - hasBin: true - peerDependencies: - typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x - dependencies: - lunr: 2.3.9 - marked: 4.3.0 - minimatch: 9.0.3 - shiki: 0.14.5 - typescript: 5.3.2 - dev: true - - /typescript-eslint@0.0.1-alpha.0: - resolution: {integrity: sha512-1hNKM37dAWML/2ltRXupOq2uqcdRQyDFphl+341NTPXFLLLiDhErXx8VtaSLh3xP7SyHZdcCgpt9boYYVb3fQg==} - dev: true - - /typescript@5.3.2: - resolution: {integrity: sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==} - engines: {node: '>=14.17'} - hasBin: true - dev: true + typescript@5.5.4: {} - /ua-parser-js@1.0.37: - resolution: {integrity: sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==} - dev: true + ua-parser-js@1.0.38: {} - /unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - dependencies: - call-bind: 1.0.5 - has-bigints: 1.0.2 - has-symbols: 1.0.3 - which-boxed-primitive: 1.0.2 - dev: true + uc.micro@2.1.0: {} - /undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - dev: true + undici-types@6.19.8: {} - /universalify@0.1.2: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} - dev: true + universalify@0.1.2: {} - /unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - dev: true + unpipe@1.0.0: {} - /uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + uri-js@4.4.1: dependencies: punycode: 2.3.1 - dev: true - /util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: true + util-deprecate@1.0.2: {} - /utils-merge@1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} - engines: {node: '>= 0.4.0'} - dev: true + utils-merge@1.0.1: {} - /v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - dev: true + v8-compile-cache-lib@3.0.1: {} - /v8-to-istanbul@9.2.0: - resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} - engines: {node: '>=10.12.0'} + v8-to-istanbul@9.3.0: dependencies: - '@jridgewell/trace-mapping': 0.3.20 + '@jridgewell/trace-mapping': 0.3.25 '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 - dev: true - - /vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - dev: true - - /vlq@0.2.3: - resolution: {integrity: sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==} - dev: true - /vscode-oniguruma@1.7.0: - resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} - dev: true + vary@1.1.2: {} - /vscode-textmate@8.0.0: - resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} - dev: true - - /which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} - dependencies: - is-bigint: 1.0.4 - is-boolean-object: 1.1.2 - is-number-object: 1.0.7 - is-string: 1.0.7 - is-symbol: 1.0.4 - dev: true - - /which-typed-array@1.1.13: - resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} - engines: {node: '>= 0.4'} - dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.5 - for-each: 0.3.3 - gopd: 1.0.1 - has-tostringtag: 1.0.0 - dev: true + vlq@0.2.3: {} - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true + which@2.0.2: dependencies: isexe: 2.0.0 - dev: true - /winston-transport@4.6.0: - resolution: {integrity: sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==} - engines: {node: '>= 12.0.0'} + winston-transport@4.7.1: dependencies: - logform: 2.6.0 + logform: 2.6.1 readable-stream: 3.6.2 triple-beam: 1.4.1 - dev: true - /winston@3.11.0: - resolution: {integrity: sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==} - engines: {node: '>= 12.0.0'} + winston@3.14.2: dependencies: '@colors/colors': 1.6.0 '@dabh/diagnostics': 2.0.3 - async: 3.2.5 + async: 3.2.6 is-stream: 2.0.1 - logform: 2.6.0 + logform: 2.6.1 one-time: 1.0.0 readable-stream: 3.6.2 - safe-stable-stringify: 2.4.3 + safe-stable-stringify: 2.5.0 stack-trace: 0.0.10 triple-beam: 1.4.1 - winston-transport: 4.6.0 - dev: true + winston-transport: 4.7.1 - /workerpool@6.2.1: - resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} - dev: true + word-wrap@1.2.5: {} - /wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + workerpool@6.5.1: {} + + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true - /wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} + wrap-ansi@8.1.0: dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - dev: true - - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true - /ws@8.11.0: - resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: true + wrappy@1.0.2: {} - /xmlhttprequest-ssl@2.0.0: - resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==} - engines: {node: '>=0.4.0'} - dev: true + ws@8.17.1: {} - /y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - dev: true + xmlhttprequest-ssl@2.0.0: {} - /yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: true + y18n@5.0.8: {} - /yargs-parser@20.2.4: - resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} - engines: {node: '>=10'} - dev: true + yaml@2.5.0: {} - /yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - dev: true + yargs-parser@20.2.9: {} - /yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - dev: true + yargs-parser@21.1.1: {} - /yargs-unparser@2.0.0: - resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} - engines: {node: '>=10'} + yargs-unparser@2.0.0: dependencies: camelcase: 6.3.0 decamelize: 4.0.0 flat: 5.0.2 is-plain-obj: 2.1.0 - dev: true - /yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} - dependencies: - cliui: 7.0.4 - escalade: 3.1.1 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 20.2.4 - dev: true - - /yargs@17.1.1: - resolution: {integrity: sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ==} - engines: {node: '>=12'} + yargs@16.2.0: dependencies: cliui: 7.0.4 - escalade: 3.1.1 + escalade: 3.1.2 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 20.2.9 - dev: true - /yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} + yargs@17.7.2: dependencies: cliui: 8.0.1 - escalade: 3.1.1 + escalade: 3.1.2 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 - dev: true - /yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - dev: true + yn@3.1.1: {} - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - dev: true + yocto-queue@0.1.0: {} diff --git a/src/ArrayValidator.mts b/src/ArrayValidator.mts deleted file mode 100644 index 5c7f5c2..0000000 --- a/src/ArrayValidator.mts +++ /dev/null @@ -1,142 +0,0 @@ -import type { - ExtensibleObjectValidator, - NumberValidator, - ObjectValidator -} from "./internal/internal.mjs"; - -const typedocWorkaround: null | ObjectValidator = null; -// noinspection PointlessBooleanExpressionJS -if (typedocWorkaround !== null) - console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); - -/** - * Validates the requirements of an array. - * - * Verifier and Validator methods are equivalent. - * Validators return validation failures through the - * {@link ExtensibleObjectValidator.getFailures | getFailures()} method, while Verifiers throw them as - * exceptions. - * - * All methods (except those found in {@link ObjectValidator}) assume that the actual value is not null. - * - * @typeParam E - the type the array elements - */ -interface ArrayValidator extends ExtensibleObjectValidator, E[]> -{ - /** - * Ensures that the actual value is empty. - * - * @returns the updated validator - */ - isEmpty(): ArrayValidator; - - /** - * Ensures that the actual value is not empty. - * - * @returns the updated validator - */ - isNotEmpty(): ArrayValidator; - - /** - * Ensures that the array contains an element. - * - * @param element - the element that must exist - * @param name - (optional) the name of the element - * @returns the updated validator - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - contains(element: E, name?: string): ArrayValidator; - - /** - * Ensures that the array contains exactly the specified elements; nothing less, nothing more. - * - * @param expected - the elements that must exist - * @param name - (optional) the name of the expected elements - * @returns the updated validator - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - containsExactly(expected: E[], name?: string): ArrayValidator; - - /** - * Ensures that the array contains any of the specified elements. - * - * @param expected - the elements that must exist - * @param name - (optional) the name of the expected elements - * @returns the updated validator - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - containsAny(expected: E[], name?: string): ArrayValidator; - - /** - * Ensures that the array contains all the specified elements. - * - * @param expected - the elements that must exist - * @param name - (optional) the name of the expected elements - * @returns the updated validator - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - containsAll(expected: E[], name?: string): ArrayValidator; - - /** - * Ensures that the array does not contain an element. - * - * @param element - the element that must not exist - * @param name - (optional) the name of the element - * @returns the updated validator - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - doesNotContain(element: E, name?: string): ArrayValidator; - - /** - * Ensures that the array does not contain any of the specified elements. - * - * @param elements - the elements that must not exist - * @param name - (optional) the name of the elements - * @returns the updated validator - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - doesNotContainAny(elements: E[], name?: string): ArrayValidator; - - /** - * Ensures that the array does not contain all the specified elements. - * - * @param elements - the elements that must not exist - * @param name - (optional) the name of the elements - * @returns the updated validator - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - doesNotContainAll(elements: E[], name?: string): ArrayValidator; - - /** - * Ensures that the array does not contain any duplicate elements. - * - * @returns the updated validator - */ - doesNotContainDuplicates(): ArrayValidator; - - /** - * @returns a validator for the length of the array - */ - length(): NumberValidator; - - /** - * @param consumer - a function that accepts a {@link NumberValidator} for the length of the array - * @returns the updated validator - * @throws TypeError if consumer is not set - */ - lengthConsumer(consumer: (length: NumberValidator) => void): ArrayValidator; - - /** - * {@inheritDoc} - */ - getActual(): E[] | undefined; -} - -export {type ArrayValidator}; \ No newline at end of file diff --git a/src/ArrayVerifier.mts b/src/ArrayVerifier.mts deleted file mode 100644 index 118263a..0000000 --- a/src/ArrayVerifier.mts +++ /dev/null @@ -1,154 +0,0 @@ -import type { - ExtensibleObjectVerifier, - NumberVerifier, - ObjectVerifier -} from "./internal/internal.mjs"; - -const typedocWorkaround: null | ObjectVerifier = null; -// noinspection PointlessBooleanExpressionJS -if (typedocWorkaround !== null) - console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); - -/** - * Verifies the requirements of an array. - * - * All methods (except those found in {@link ObjectVerifier}) assume that the actual value is not null. - * - * @typeParam E - the type the array elements - */ -interface ArrayVerifier extends ExtensibleObjectVerifier, E[]> -{ - /** - * Ensures that the actual value is empty. - * - * @returns the updated verifier - * @throws RangeError if the actual value is not empty - */ - isEmpty(): ArrayVerifier; - - /** - * Ensures that the actual value is not empty. - * - * @returns the updated verifier - * @throws RangeError if the actual value is empty - */ - isNotEmpty(): ArrayVerifier; - - /** - * Ensures that the array contains an element. - * - * @param element - the element that must exist - * @param name - (optional) the name of the element - * @returns the updated verifier - * @throws TypeError if name is null - * @throws RangeError if name is empty. - * If the array does not contain element. - */ - contains(element: E, name?: string): ArrayVerifier; - - /** - * Ensures that the array contains exactly the specified elements; nothing less, nothing more. - * - * @param expected - the elements that must exist - * @param name - (optional) the name of the expected elements - * @returns the updated verifier - * @throws TypeError if name is null. - * If expected is not an Array. - * @throws RangeError if name is empty. - * If the array is missing any elements in expected. - * If the array contains elements not found in expected. - */ - containsExactly(expected: E[], name?: string): ArrayVerifier; - - /** - * Ensures that the array contains any of the specified elements. - * - * @param expected - the elements that must exist - * @param name - (optional) the name of the expected elements - * @returns the updated verifier - * @throws TypeError if name is null. - * If expected is not an Array. - * @throws RangeError if name is empty. - * If the array is missing any elements in expected. - * If the array contains elements not found in expected. - */ - containsAny(expected: E[], name?: string): ArrayVerifier; - - /** - * Ensures that the array contains all the specified elements. - * - * @param expected - the elements that must exist - * @param name - (optional) the name of the expected elements - * @returns the updated verifier - * @throws TypeError if name is null. - * If expected is not an Array - * @throws RangeError if name is empty. - * If the array does not contain all of expected. - */ - containsAll(expected: E[], name?: string): ArrayVerifier; - - /** - * Ensures that the array does not contain an element. - * - * @param element - the element that must not exist - * @param name - (optional) the name of the element - * @returns the updated verifier - * @throws TypeError if name is null - * @throws RangeError if name is empty. - * If the array contains element. - */ - doesNotContain(element: E, name?: string): ArrayVerifier; - - /** - * Ensures that the array does not contain any of the specified elements. - * - * @param elements - the elements that must not exist - * @param name - (optional) the name of the elements - * @returns the updated verifier - * @throws TypeError if name is null. - * If elements is not an Array. - * @throws RangeError if name is empty. - * If the array contains any of elements. - */ - doesNotContainAny(elements: E[], name?: string): ArrayVerifier; - - /** - * Ensures that the array does not contain all the specified elements. - * - * @param elements - the elements that must not exist - * @param name - (optional) the name of the elements - * @returns the updated verifier - * @throws TypeError if name is null. - * If elements is not an Array. - * @throws RangeError if name is empty. - * If the array contains all of elements. - */ - doesNotContainAll(elements: E[], name?: string): ArrayVerifier; - - /** - * Ensures that the array does not contain any duplicate elements. - * - * @returns the updated verifier - * @throws RangeError if the array contains any duplicate elements - */ - doesNotContainDuplicates(): ArrayVerifier; - - /** - * @returns a verifier for the length of the array - */ - length(): NumberVerifier; - - /** - * @param consumer - a function that accepts a {@link NumberVerifier} for the length of the array - * @returns the updated verifier - * @throws TypeError if consumer is not set - */ - lengthConsumer(consumer: (actual: NumberVerifier) => void): ArrayVerifier; - - /** - * {@inheritDoc} - */ - getActual(): E[]; -} - -export {type ArrayVerifier}; \ No newline at end of file diff --git a/src/BooleanValidator.mts b/src/BooleanValidator.mts deleted file mode 100644 index abc9f80..0000000 --- a/src/BooleanValidator.mts +++ /dev/null @@ -1,35 +0,0 @@ -import type {ExtensibleObjectValidator} from "./internal/internal.mjs"; - -/** - * Validates the requirements of a boolean. - * - * Verifier and Validator methods are equivalent. - * Validators return validation failures through the - * {@link ExtensibleObjectValidator.getFailures | getFailures()} method, while Verifiers throw them as - * exceptions. - * - * All methods (except those found in {@link ObjectValidator}) assume that the actual value is not null. - */ -interface BooleanValidator extends ExtensibleObjectValidator -{ - /** - * Ensures that the actual value is true. - * - * @returns the updated validator - */ - isTrue(): BooleanValidator; - - /** - * Ensures that the actual value is false. - * - * @returns the updated validator - */ - isFalse(): BooleanValidator; - - /** - * {@inheritDoc} - */ - getActual(): boolean | undefined; -} - -export {type BooleanValidator}; \ No newline at end of file diff --git a/src/BooleanVerifier.mts b/src/BooleanVerifier.mts deleted file mode 100644 index 3cf40c5..0000000 --- a/src/BooleanVerifier.mts +++ /dev/null @@ -1,32 +0,0 @@ -import type {ExtensibleObjectVerifier} from "./internal/internal.mjs"; - -/** - * Verifies the requirements of a boolean. - *

- * All methods (except those found in {@link ObjectVerifier}) assume that the actual value is not null. - */ -interface BooleanVerifier extends ExtensibleObjectVerifier -{ - /** - * Ensures that the actual value is true. - * - * @returns the updated verifier - * @throws RangeError if the actual value is not true - */ - isTrue(): BooleanVerifier; - - /** - * Ensures that the actual value is false. - * - * @returns the updated verifier - * @throws RangeError if the actual value is not false - */ - isFalse(): BooleanVerifier; - - /** - * {@inheritDoc} - */ - getActual(): boolean; -} - -export {type BooleanVerifier}; \ No newline at end of file diff --git a/src/ClassValidator.mts b/src/ClassValidator.mts deleted file mode 100644 index c7f152d..0000000 --- a/src/ClassValidator.mts +++ /dev/null @@ -1,42 +0,0 @@ -import type { - ExtensibleObjectValidator, - ClassConstructor -} from "./internal/internal.mjs"; - -/** - * Validates the requirements of a type. - * - * Verifier and Validator methods are equivalent. - * Validators return validation failures through the - * {@link ExtensibleObjectValidator.getFailures | getFailures()} method, while Verifiers throw them as - * exceptions. - * - * All methods (except those found in {@link ObjectValidator}) assume that the actual value is not null. - * - * @typeParam T - the type of the class - */ -interface ClassValidator extends ExtensibleObjectValidator, ClassConstructor> -{ - /** - * Ensures that the actual value is the specified type, or a subtype. - * - * @param type -the type to compare to - * @returns the updated validator - */ - isSupertypeOf(type: ClassConstructor): ClassValidator; - - /** - * Ensures that the actual value is the specified type, or a subtype. - * - * @param type -the type to compare to - * @returns the updated validator - */ - isSubtypeOf(type: ClassConstructor): ClassValidator; - - /** - * {@inheritDoc} - */ - getActual(): ClassConstructor | undefined; -} - -export {type ClassValidator}; \ No newline at end of file diff --git a/src/ClassVerifier.mts b/src/ClassVerifier.mts deleted file mode 100644 index 0ca095d..0000000 --- a/src/ClassVerifier.mts +++ /dev/null @@ -1,46 +0,0 @@ -import type { - ObjectVerifier, - ExtensibleObjectVerifier, - ClassConstructor -} from "./internal/internal.mjs"; - - -const typedocWorkaround: null | ObjectVerifier = null; -// noinspection PointlessBooleanExpressionJS -if (typedocWorkaround !== null) - console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); - -/** - * Verifies the requirements of a class. - *

- * All methods (except those found in {@link ObjectVerifier}) assume that the actual value is not null. - * - * @typeParam T - the type of the class - */ -interface ClassVerifier extends ExtensibleObjectVerifier, ClassConstructor> -{ - /** - * Ensures that the actual value is the specified type, or a super-type. - * - * @param type -the type to compare to - * @returns the updated verifier - * @throws RangeError if the actual value is not a supertype of type - */ - isSupertypeOf(type: ClassConstructor): ClassVerifier; - - /** - * Ensures that the actual value is the specified type, or a subtype. - * - * @param type -the type to compare to - * @returns the updated verifier - * @throws RangeError if the actual value is not a subtype of type - */ - isSubtypeOf(type: ClassConstructor): ClassVerifier; - - /** - * {@inheritDoc} - */ - getActual(): ClassConstructor; -} - -export {type ClassVerifier}; \ No newline at end of file diff --git a/src/Configuration.mts b/src/Configuration.mts deleted file mode 100644 index 2a8a069..0000000 --- a/src/Configuration.mts +++ /dev/null @@ -1,234 +0,0 @@ -import type {GlobalConfiguration} from "./internal/internal.mjs"; -import {Objects} from "./internal/internal.mjs"; - -/** - * A verifier's configuration. - */ -class Configuration -{ - private readonly globalConfiguration: GlobalConfiguration; - private readonly context: Map; - private assertionsEnabled: boolean; - private diffEnabled: boolean; - private readonly typeToStringConverter: Map string>; - - /** - * Creates a new configuration without an empty context and without an exception override. - * - * @param globalConfiguration - the global configuration inherited by all verifiers - * @param context - (optional) a map of key-value pairs to append to the exception message - * @param assertionsEnabled - (optional) true if assertThat() should invoke - * requireThat(). false if assertThat() should do nothing. If - * undefined defaults to globalConfiguration.assertionsAreEnabled(). - * @param diffEnabled - (optional) true if exceptions should show the difference between the actual and - * expected values. If undefined defaults to globalConfiguration.diffEnabled(). - * @param typeToStringConverter - (optional) a map from the typeof of a value to a function - * that converts the value to a string - * @throws TypeError if context or one of its elements are not an array. If the nested array - * contains less or more than 2 elements. If the keys nested in the context array are not strings. - * @throws RangeError if the elements nested in the context array are undefined, null, or are empty - */ - constructor(globalConfiguration: GlobalConfiguration, - context = new Map(), assertionsEnabled?: boolean, diffEnabled = true, - typeToStringConverter = new Map string>()) - { - this.globalConfiguration = globalConfiguration; - Objects.assertThatInstanceOf(context, "context", Map); - this.context = context; - if (typeof (assertionsEnabled) === "undefined") - assertionsEnabled = globalConfiguration.assertionsAreEnabled(); - this.assertionsEnabled = assertionsEnabled; - if (typeof (diffEnabled) === "undefined") - diffEnabled = globalConfiguration.isDiffEnabled(); - this.diffEnabled = diffEnabled; - this.typeToStringConverter = typeToStringConverter; - } - - /** - * Returns a copy of this configuration. - * This method is thread-safe. - * Other methods may not be. - * - * @returns a copy of this configuration - */ - copy() - { - return new Configuration(this.globalConfiguration, new Map(this.context), - this.assertionsEnabled, this.diffEnabled, - new Map string>(this.typeToStringConverter)); - } - - /** - * Indicates whether assertThat() should invoke requireThat(). - * - * @returns true if assertThat() should delegate to requireThat(). - * false if it shouldn't do anything. - */ - assertionsAreEnabled() - { - return this.assertionsEnabled; - } - - /** - * Indicates that assertThat() should invoke requireThat(). - * - * @returns this - */ - withAssertionsEnabled() - { - if (!this.assertionsEnabled) - this.assertionsEnabled = true; - return this; - } - - /** - * Indicates that assertThat() shouldn't do anything. - * - * @returns this - */ - withAssertionsDisabled() - { - if (this.assertionsEnabled) - this.assertionsEnabled = false; - return this; - } - - /** - * Indicates if exceptions should show the difference between the actual and expected values. - * - * @returns true by default - */ - isDiffEnabled() - { - return this.diffEnabled; - } - - /** - * Indicates that exceptions should show the difference between the actual and expected values. - * - * @returns this - */ - withDiff() - { - if (!this.diffEnabled) - this.diffEnabled = true; - return this; - } - - /** - * Indicates that exceptions should not show the difference between the actual and expected values. - * - * @returns this - */ - withoutDiff() - { - if (this.diffEnabled) - this.diffEnabled = false; - return this; - } - - /** - * @returns a map of key-value pairs to append to the exception message - * @see #putContext - */ - getContext() - { - return this.context; - } - - /** - * Adds or updates contextual information associated with the exception message. - * - * @param name - the name of the parameter - * @param value - the value of the parameter - * @returns this - * @throws TypeError if key is not a string - * @see #getContext - */ - putContext(name: string, value: unknown) - { - Objects.requireThatStringIsNotEmpty(name, "name"); - this.context.set(name, value); - return this; - } - - /** - * Removes contextual information associated with the exception message. - * - * @param name - the name of the parameter - * @returns this - * @throws TypeError if name is null - */ - removeContext(name: string) - { - Objects.requireThatStringIsNotEmpty(name, "name"); - this.context.delete(name); - return this; - } - - /** - * Returns the String representation of an object. By default, custom handlers are provided for - * arrays, number and Set. - * - * @param value - a value - * @returns the String representation of the value - * @see #withStringConverter - */ - convertToString(value: unknown) - { - const valueInfo = Objects.getTypeInfo(value); - let converter = this.typeToStringConverter.get(valueInfo.type); - if (!converter) - converter = (value2: unknown) => Objects.toString(value2); - return converter(value); - } - - /** - * Indicates that a function should be used to convert a value to a string. - *

- * Please note that type must be an exact match, subclasses do not inherit converters from - * their superclass. - * - * @param type - the - * typeof - * of the type being converted - * @param converter - a function that converts an object of the specified type to a string - * @returns this - * @throws RangeError if any of the parameters are null - */ - withStringConverter(type: string, converter: (input: unknown) => string) - { - Objects.assertThatTypeOf(type, "type", "string"); - Objects.assertThatTypeOf(converter, "converter", "function"); - this.typeToStringConverter.set(type, converter); - return this; - } - - /** - * Indicates that the default converter should be used for type. - * - * @param type - the - * typeof - * of the type being converted - * @returns this - * @throws RangeError if type is null - */ - withoutStringConverter(type: string) - { - Objects.assertThatTypeOf(type, "type", "string"); - this.typeToStringConverter.delete(type); - return this; - } - - /** - * Returns the global configuration. - * - * @returns the global configuration associated with this object - */ - getGlobalConfiguration() - { - return this.globalConfiguration; - } -} - -export {Configuration}; \ No newline at end of file diff --git a/src/ContextLine.mts b/src/ContextLine.mts deleted file mode 100644 index 3b01c5f..0000000 --- a/src/ContextLine.mts +++ /dev/null @@ -1,48 +0,0 @@ -import type {Configuration} from "./internal/internal.mjs"; -import {Objects} from "./internal/internal.mjs"; - -/** - * A line item in an exception context. - */ -class ContextLine -{ - /** - * The instance configuration. - */ - public readonly config: Configuration; - /** - * The key associated with the value (empty string is absent). - */ - public readonly key: string; - /** - * A value. - */ - public readonly value: unknown; - - /** - * Creates a new line. - * - * @param configuration - the instance configuration - * @param key - the key associated with the value (empty string if absent) - * @param value - a value - * @throws TypeError if the key is not a string - */ - constructor(configuration: Configuration, key: string, value: unknown) - { - Objects.assertThatTypeOf(key, "key", "string"); - this.config = configuration; - this.key = key; - this.value = value; - } - - toString(): string - { - let result = ""; - if (this.key.length !== 0) - result += this.key + ":"; - result += this.config.convertToString(this.value); - return result; - } -} - -export {ContextLine}; \ No newline at end of file diff --git a/src/DefaultJavascriptValidators.mts b/src/DefaultJavascriptValidators.mts new file mode 100644 index 0000000..c1ef561 --- /dev/null +++ b/src/DefaultJavascriptValidators.mts @@ -0,0 +1,202 @@ +import { + type BooleanValidator, + type StringValidator, + type NumberValidator, + type ArrayValidator, + type SetValidator, + type MapValidator, + type ObjectValidator, + Configuration, + JavascriptValidatorsImpl, + MainApplicationScope, + type ConfigurationUpdater, + type ValidatorComponent, + JavascriptValidators +} from "./internal/internal.mjs"; + +const typedocWorkaround: null | ValidatorComponent | + JavascriptValidators = null; +// noinspection PointlessBooleanExpressionJS +if (typedocWorkaround !== null) + console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); + +/** + * Creates validators for the Javascript API. + *

+ * There are three kinds of validators: + *

    + *
  • `requireThat()` for method preconditions.
  • + *
  • `assertThat()` for class invariants, and method postconditions.
  • + *
  • `checkIf()` for returning multiple validation failures.
  • + *
+ *

+ */ +const DELEGATE = new JavascriptValidatorsImpl(MainApplicationScope.INSTANCE, Configuration.DEFAULT); + +/** + * Validates the state of a value. + *

+ * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the array or set + * @typeParam K - the type of keys in the map + * @typeParam V - the type of values in the map + * @param value - the value + * @param name - the name of the value + * @returns a verifier + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function requireThat(value: number, name: string): NumberValidator; +function requireThat(value: boolean, name: string): BooleanValidator; +function requireThat(value: E[], name: string): ArrayValidator; +function requireThat(value: Set, name: string): SetValidator; +function requireThat(value: Map, name: string): MapValidator; +function requireThat(value: string, name: string): StringValidator; +function requireThat(value: T, name: string): ObjectValidator; +function requireThat(value: T, name: string): NumberValidator | BooleanValidator | + ArrayValidator | SetValidator | MapValidator | StringValidator | ObjectValidator +{ + return DELEGATE.requireThat(value, name); +} + +/** + * Validates the state of a value. + *

+ * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the array or set + * @typeParam K - the type of keys in the map + * @typeParam V - the type of values in the map + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function assertThat(value: number, name: string): NumberValidator; +function assertThat(value: boolean, name: string): BooleanValidator; +function assertThat(value: E[], name: string): ArrayValidator; +function assertThat(value: Set, name: string): SetValidator; +function assertThat(value: Map, name: string): MapValidator; +function assertThat(value: string, name: string): StringValidator; +function assertThat(value: T, name: string): ObjectValidator; +function assertThat(value: T, name: string): NumberValidator | BooleanValidator | + ArrayValidator | SetValidator | MapValidator | StringValidator | ObjectValidator +{ + return DELEGATE.assertThat(value, name); +} + +/** + * Validates the state of a value. + *

+ * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the array or set + * @typeParam K - the type of keys in the map + * @typeParam V - the type of values in the map + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function checkIf(value: number, name: string): NumberValidator; +function checkIf(value: boolean, name: string): BooleanValidator; +function checkIf(value: E[], name: string): ArrayValidator; +function checkIf(value: Set, name: string): SetValidator; +function checkIf(value: Map, name: string): MapValidator; +function checkIf(value: string, name: string): StringValidator; +function checkIf(value: T, name: string): ObjectValidator; +function checkIf(value: T, name: string): NumberValidator | BooleanValidator | + ArrayValidator | SetValidator | MapValidator | StringValidator | ObjectValidator +{ + return DELEGATE.checkIf(value, name); +} + +/** + * Updates the configuration that will be used by new validators. + * + * @param updater - a function that updates the configuration + * @returns this + * @see {@link JavascriptValidators.newInstance|Creating an independent configuration} + */ +function updateConfiguration(updater: (configuration: ConfigurationUpdater) => void) +{ + return DELEGATE.updateConfiguration(updater); +} + +/** + * Returns the contextual information for validators created out by this factory. The contextual information + * is a map of key-value pairs that can provide more details about validation failures. For example, if the + * message is "Password may not be empty" and the map contains the key-value pair + * `{"username": "john.smith"}`, the error message would be: + *

+ * ```console + * Password may not be empty + * username: john.smith + * ``` + * + * @returns an unmodifiable map from each entry's name to its value + */ +function getContext() +{ + return DELEGATE.getContext(); +} + +/** + * Sets the contextual information for validators created by this factory. + *

+ * This method adds contextual information to error messages. The contextual information is stored as + * key-value pairs in a map. Values set by this method may be overridden by + * {@link ValidatorComponent.withContext}. + * + * @param value - the value of the entry + * @param name - the name of an entry + * @returns the underlying validator factory + * @throws NullPointerError if `name` is not a string + */ +function withContext(value: unknown, name: string) +{ + return DELEGATE.withContext(value, name); +} + +/** + * Removes the contextual information of validators created by this factory. + * + * @param name - the parameter name + * @returns the underlying validator factory + * @throws NullPointerError if `name` is not a string + * @throws IllegalArgumentError if `name` contains leading or trailing whitespace, or is + * empty + */ +function removeContext(name: string) +{ + return DELEGATE.removeContext(name); +} + +/** + * Returns the global configuration shared by all validators. + *

+ * NOTE: Updating this configuration affects existing and new validators. + * + * @returns the global configuration updater + */ +function globalConfiguration() +{ + return DELEGATE.getGlobalConfiguration(); +} + +export { + requireThat, + assertThat, + checkIf, + updateConfiguration, + getContext, + withContext, + removeContext, + globalConfiguration +}; \ No newline at end of file diff --git a/src/DefaultRequirements.mts b/src/DefaultRequirements.mts deleted file mode 100644 index 4041f66..0000000 --- a/src/DefaultRequirements.mts +++ /dev/null @@ -1,217 +0,0 @@ -import type { - GlobalConfiguration, - ObjectValidator, - ObjectVerifier, - StringValidator, - NumberValidator, - ClassValidator, - ArrayValidator, - SetValidator, - MapValidator, - StringVerifier, - NumberVerifier, - ClassVerifier, - ArrayVerifier, - SetVerifier, - MapVerifier, - ClassConstructor, - BooleanValidator, - BooleanVerifier, - AnythingButClassConstructor -} from "./internal/internal.mjs"; -import { - Requirements, - Objects, - ArrayValidatorImpl, - Pluralizer, - SetValidatorImpl, - ClassValidatorImpl, - ObjectValidatorImpl, - Configuration, - MainGlobalConfiguration, - ArrayVerifierImpl, - SetVerifierImpl, - MapVerifierImpl, - MapValidatorImpl, - ClassVerifierImpl, - ObjectVerifierImpl, - BooleanVerifierImpl, - BooleanValidatorImpl, - StringVerifierImpl, - StringValidatorImpl, - NumberVerifierImpl, - NumberValidatorImpl -} from "./internal/internal.mjs"; - -const typedocWorkaround: null | GlobalConfiguration = null; -// noinspection PointlessBooleanExpressionJS -if (typedocWorkaround !== null) - console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); - -/** - * Verifies the requirements of an object. - * - * @typeParam T - the type the actual value - * @param actual - the actual value - * @param name - the name of the value - * @returns a verifier - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ -function requireThat(actual: boolean, name: string): BooleanVerifier; -function requireThat(actual: string, name: string): StringVerifier; -function requireThat(actual: number, name: string): NumberVerifier; -function requireThat(actual: Array, name: string): ArrayVerifier; -function requireThat(actual: Set, name: string): SetVerifier; -function requireThat(actual: Map, name: string): MapVerifier; -function requireThat(actual: ClassConstructor, name: string): ClassVerifier; -function requireThat(actual: AnythingButClassConstructor, name: string): ObjectVerifier; -function requireThat -(actual: unknown, name: string): BooleanVerifier | StringVerifier | NumberVerifier | - ArrayVerifier | SetVerifier | MapVerifier | ClassVerifier | ObjectVerifier -{ - Objects.verifyName(name, "name"); - const config = new Configuration(MainGlobalConfiguration.INSTANCE); - const typeOfActual = Objects.getTypeInfo(actual); - switch (typeOfActual.type) - { - case "boolean": - return new BooleanVerifierImpl(new BooleanValidatorImpl(config, actual as boolean, name, [])); - case "string": - return new StringVerifierImpl(new StringValidatorImpl(config, actual as string, name, [])); - case "number": - return new NumberVerifierImpl(new NumberValidatorImpl(config, actual as number, name, [])); - case "array": - { - return new ArrayVerifierImpl(new ArrayValidatorImpl(config, actual as E[], name, - Pluralizer.ELEMENT, [])); - } - case "object": - { - switch (typeOfActual.name) - { - case "Set": - return new SetVerifierImpl(new SetValidatorImpl(config, actual as Set, name, [])); - case "Map": - { - return new MapVerifierImpl(new MapValidatorImpl(config, actual as Map, name, - [])); - } - } - break; - } - case "class": - { - return new ClassVerifierImpl(new ClassValidatorImpl(config, - actual as ClassConstructor | undefined, name, [])); - } - } - return new ObjectVerifierImpl, T>(new ObjectValidatorImpl(config, actual as T, name, - [])); -} - -/** - * Validates the requirements of an object. - * - * @typeParam T - the type the actual value - * @param actual - the actual value - * @param name - the name of the value - * @returns a validator - * @throws TypeError if name is null - * @throws RangeError if name is empty - * @see {@link GlobalConfiguration.assertionsAreEnabled | GlobalConfiguration.assertionsAreEnabled} - */ -function validateThat(actual: boolean, name: string): BooleanValidator; -function validateThat(actual: string, name: string): StringValidator; -function validateThat(actual: number, name: string): NumberValidator; -function validateThat(actual: Array, name: string): ArrayValidator; -function validateThat(actual: Set, name: string): SetValidator; -function validateThat(actual: Map, name: string): MapValidator; -function validateThat(actual: undefined, name: string): ObjectValidator; -function validateThat(actual: null, name: string): ObjectValidator; -function validateThat(actual: AnythingButClassConstructor, name: string): ObjectValidator; -function validateThat(actual: ClassConstructor, name: string): ClassValidator; -function validateThat(actual: unknown, name: string): ObjectValidator; -function validateThat -(actual: unknown, name: string): BooleanValidator | StringValidator | NumberValidator | ArrayValidator | - SetValidator | MapValidator | ObjectValidator | ClassValidator | ObjectValidator -{ - Objects.verifyName(name, "name"); - const config = new Configuration(MainGlobalConfiguration.INSTANCE); - const typeOfActual = Objects.getTypeInfo(actual); - switch (typeOfActual.type) - { - case "boolean": - return new BooleanValidatorImpl(config, actual as boolean, name, []); - case "string": - return new StringValidatorImpl(config, actual as string, name, []); - case "number": - return new NumberValidatorImpl(config, actual as number, name, []); - case "array": - return new ArrayValidatorImpl(config, actual as E[], name, Pluralizer.ELEMENT, []); - case "object": - { - switch (typeOfActual.name) - { - case "Set": - return new SetValidatorImpl(config, actual as Set, name, []); - case "Map": - return new MapValidatorImpl(config, actual as Map, name, []); - } - break; - } - case "class": - return new ClassValidatorImpl(config, actual as ClassConstructor | undefined, name, []); - } - return new ObjectValidatorImpl(config, actual as T | undefined, name, []); -} - -/** - * Verifies requirements only if assertions are enabled. - * - * By default, assertions are disabled. - * See {@link GlobalConfiguration.assertionsAreEnabled | GlobalConfiguration.assertionsAreEnabled} to change - * the default. - * - * @param requirements - the requirements to verify - * @throws TypeError if name is null - * @throws RangeError if name is empty - * @see {@link GlobalConfiguration.assertionsAreEnabled | GlobalConfiguration.assertionsAreEnabled} - */ -function assertThat(requirements: (requirements: Requirements) => void) -{ - Objects.requireThatValueIsDefinedAndNotNull(requirements, "requirements"); - const config = new Configuration(MainGlobalConfiguration.INSTANCE); - if (config.assertionsAreEnabled()) - requirements(new Requirements()); -} - -/** - * Verifies requirements only if assertions are enabled. - * - * By default, assertions are disabled. - * See {@link GlobalConfiguration.assertionsAreEnabled | GlobalConfiguration.assertionsAreEnabled} to change - * the default. - * - * @param requirements - the requirements to verify - * @returns the value returned by requirements - * @throws TypeError if name is null - * @throws RangeError if name is empty - * @see {@link GlobalConfiguration.assertionsAreEnabled | GlobalConfiguration.assertionsAreEnabled} - */ -function assertThatAndReturn(requirements: (requirements: Requirements) => void) -{ - Objects.requireThatValueIsDefinedAndNotNull(requirements, "requirements"); - const config = new Configuration(MainGlobalConfiguration.INSTANCE); - if (config.assertionsAreEnabled()) - return requirements(new Requirements()); - return undefined; -} - -export -{ - requireThat, - validateThat, - assertThat, - assertThatAndReturn -}; \ No newline at end of file diff --git a/src/GlobalConfiguration.mts b/src/GlobalConfiguration.mts index 91bbc88..3de165f 100644 --- a/src/GlobalConfiguration.mts +++ b/src/GlobalConfiguration.mts @@ -1,130 +1,40 @@ +/* + * Copyright (c) 2019 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ import type {TerminalEncoding} from "./internal/internal.mjs"; /** - * The global configuration inherited by all verifiers. - *

- * Note: Verifiers inherit from the global configuration at instantiation time. Their - * {@link Configuration | configuration} is not affected by subsequent changes to the global configuration. - *

- * However, updating settings not found in {@link Configuration} (such as - * {@link GlobalConfiguration.withTerminalEncoding | withTerminalEncoding(TerminalEncoding)}) will impact the - * behavior of existing verifiers. - * - * By default, {@link GlobalConfiguration.assertionsAreEnabled | assertionsAreEnabled} is false - * and {@link GlobalConfiguration.isDiffEnabled | isDiffEnabled} is true. + * The configuration shared by all validators. Changes apply to existing or new validators. */ interface GlobalConfiguration { /** - * Indicates whether assertThat() should invoke requireThat(). - * - * @returns true if assertThat() should delegate to requireThat(); false - * if it shouldn't do anything - */ - assertionsAreEnabled(): boolean; - - /** - * Indicates that assertThat() should invoke requireThat(). - * - * @returns this - */ - withAssertionsEnabled(): GlobalConfiguration; - - /** - * Indicates that assertThat() shouldn't do anything. - * - * @returns this - */ - withAssertionsDisabled(): GlobalConfiguration; - - /** - * Indicates if exceptions should show the difference between the actual and expected values. - * - * @returns true by default - * @see #withDiff() - * @see #withoutDiff() - */ - isDiffEnabled(): boolean; - - /** - * Indicates that exceptions should show the difference between the actual and expected values. - * - * @returns this - * @see #isDiffEnabled() - */ - withDiff(): GlobalConfiguration; - - /** - * Indicates that exceptions should not show the difference between the actual and expected values. + * Returns the encoding supported by the terminal. * - * @returns this - * @see #isDiffEnabled() - */ - withoutDiff(): GlobalConfiguration; - - /** - * Returns the color encodings supported by the terminal. - * - * @returns the encodings supported by the terminal (defaults to the auto-detected encoding) - * @see #withTerminalEncoding(TerminalEncoding) - * @see #withDefaultTerminalEncoding() + * @returns the encoding supported by the terminal */ - listTerminalEncodings(): TerminalEncoding[]; + supportedTerminalEncodings(): Set; /** * Returns the current terminal encoding. * * @returns the current terminal encoding (defaults to the auto-detected encoding) */ - getTerminalEncoding(): TerminalEncoding; - - /** - * Indicates that the terminal encoding should be auto-detected. - * - * @returns this - * @see #.withTerminalEncoding - */ - withDefaultTerminalEncoding(): GlobalConfiguration; + terminalEncoding(): TerminalEncoding; /** - * Indicates the type of encoding that the terminal supports. + * Sets the terminal encoding of the output. *

- * This feature can be used to force the use of colors even when their support is not detected. + * This can be used to force the use of ANSI colors when their support is not detected. * * @param encoding - the type of encoding that the terminal supports * @returns this - * @throws TypeError if encoding is null - * @see #.withDefaultTerminalEncoding - */ - withTerminalEncoding(encoding: TerminalEncoding): GlobalConfiguration; - - /** - * Returns the current terminal width. - * - * @returns the terminal width in characters (defaults to the auto-detected width) - */ - getTerminalWidth(): number; - - /** - * Indicates that the terminal width should be auto-detected. - * - * @returns this - * @see #.withTerminalWidth + * @throws TypeError if `encoding` is `undefined` or `null` */ - withDefaultTerminalWidth(): GlobalConfiguration; + terminalEncoding(encoding: TerminalEncoding): GlobalConfiguration; - /** - * Indicates the width that the terminal should use. - *

- * This feature can be used to override the default terminal width when it cannot be auto-detected. - * - * @param width - the terminal width in characters - * @returns this - * @throws TypeError if width is null - * @throws RangeError if width is zero or negative - * @see #.withDefaultTerminalWidth - */ - withTerminalWidth(width: number): GlobalConfiguration; + terminalEncoding(encoding?: TerminalEncoding): TerminalEncoding | GlobalConfiguration; } -export {type GlobalConfiguration}; \ No newline at end of file +export type {GlobalConfiguration}; \ No newline at end of file diff --git a/src/GlobalRequirements.mts b/src/GlobalRequirements.mts deleted file mode 100644 index c9c62fa..0000000 --- a/src/GlobalRequirements.mts +++ /dev/null @@ -1,153 +0,0 @@ -import type {TerminalEncoding} from "./internal/internal.mjs"; -import {MainGlobalConfiguration} from "./internal/internal.mjs"; - -const delegate = MainGlobalConfiguration.INSTANCE; - -/** - * The configuration shared by all verifiers. - */ -class GlobalRequirements -{ - /** - * Indicates whether assertThat() should invoke requireThat(). - * - * @returns true if assertThat() should delegate to requireThat(); false - * if it shouldn't do anything - */ - static assertionsAreEnabled() - { - return delegate.assertionsAreEnabled(); - } - - /** - * Indicates that assertThat() should invoke requireThat(). - */ - static withAssertionsEnabled() - { - delegate.withAssertionsEnabled(); - } - - /** - * Indicates that assertThat() shouldn't do anything. - */ - static withAssertionsDisabled() - { - delegate.withAssertionsDisabled(); - } - - /** - * Indicates if exceptions should show the difference between the actual and expected values. - * - * @returns true by default - * @see #withDiff() - * @see #withoutDiff() - */ - static isDiffEnabled() - { - return delegate.isDiffEnabled(); - } - - /** - * Indicates that exceptions should show the difference between the actual and expected values. - * - * @see #isDiffEnabled() - */ - static withDiff() - { - delegate.withDiff(); - } - - /** - * Indicates that exceptions should not show the difference between the actual and expected values. - * - * @see #isDiffEnabled() - */ - static withoutDiff() - { - delegate.withoutDiff(); - } - - /** - * Returns the color encodings supported by the terminal. - * - * @returns the encodings supported by the terminal (defaults to the auto-detected - * encoding) - * @see #withTerminalEncoding(TerminalEncoding) - * @see #withDefaultTerminalEncoding() - */ - static listTerminalEncodings() - { - return delegate.listTerminalEncodings(); - } - - /** - * Returns the current terminal encoding. - * - * @returns the current terminal encoding (defaults to the auto-detected encoding) - */ - static getTerminalEncoding() - { - return delegate.getTerminalEncoding(); - } - - /** - * Indicates that the terminal encoding should be auto-detected. - * - * @see #.withTerminalEncoding - */ - static withDefaultTerminalEncoding() - { - delegate.withDefaultTerminalWidth(); - } - - /** - * Indicates the type of encoding that the terminal supports. - *

- * This feature can be used to force the use of colors even when their support is not detected. - * - * @param encoding - the type of encoding that the terminal supports - * @throws TypeError if encoding is null - * @see #.withDefaultTerminalEncoding - */ - static withTerminalEncoding(encoding: TerminalEncoding) - { - delegate.withTerminalEncoding(encoding); - } - - /** - * Returns the current terminal width. - * - * @returns the terminal width in characters (defaults to the auto-detected width) - */ - static getTerminalWidth() - { - return delegate.getTerminalWidth(); - } - - /** - * Indicates that the terminal width should be auto-detected. - * - * @see #.withTerminalWidth - */ - static withDefaultTerminalWidth() - { - delegate.withDefaultTerminalWidth(); - } - - /** - * Indicates the width that the terminal should use. - *

- * This feature can be used to override the default terminal width when it cannot be auto-detected. - * - * @param width - the terminal width in characters - * @throws TypeError if width is null - * @throws RangeError if width is zero or negative - * @see #.withDefaultTerminalWidth - */ - withTerminalWidth(width: number) - { - delegate.withTerminalWidth(width); - } -} - -export {GlobalRequirements}; \ No newline at end of file diff --git a/src/InetAddressValidator.mts b/src/InetAddressValidator.mts deleted file mode 100644 index c8f69d1..0000000 --- a/src/InetAddressValidator.mts +++ /dev/null @@ -1,43 +0,0 @@ -import type {ExtensibleObjectValidator} from "./internal/internal.mjs"; - -/** - * Validates the requirements of an IP address or hostname. - * - * Verifier and Validator methods are equivalent. - * Validators return validation failures through the - * {@link ExtensibleObjectValidator.getFailures | getFailures()} method, while Verifiers throw them as - * exceptions. - * - * All methods (except those found in {@link ObjectValidator}) assume that the actual value is not null. - */ -interface InetAddressValidator extends ExtensibleObjectValidator -{ - /** - * Ensures that the actual value is an IP v4 address. - * - * @returns the updated validator - */ - isIpV4(): InetAddressValidator; - - /** - * Ensures that the actual value is an IP v6 address. - * - * @returns the updated validator - */ - isIpV6(): InetAddressValidator; - - /** - * Ensures that the actual value is an IP v6 address. - * - * @returns the updated validator - * @see rfc3696 - */ - isHostname(): InetAddressValidator; - - /** - * {@inheritDoc} - */ - getActual(): string | undefined; -} - -export {type InetAddressValidator}; \ No newline at end of file diff --git a/src/InetAddressVerifier.mts b/src/InetAddressVerifier.mts deleted file mode 100644 index 6fcb5a9..0000000 --- a/src/InetAddressVerifier.mts +++ /dev/null @@ -1,49 +0,0 @@ -import type { - ObjectVerifier, - ExtensibleObjectVerifier -} from "./internal/internal.mjs"; - -const typedocWorkaround: null | ObjectVerifier = null; -// noinspection PointlessBooleanExpressionJS -if (typedocWorkaround !== null) - console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); - -/** - * Verifies the requirements of an IP address or hostname. - *

- * All methods (except those found in {@link ObjectVerifier}) assume that the actual value is not null. - */ -interface InetAddressVerifier extends ExtensibleObjectVerifier -{ - /** - * Ensures that the actual value is an IP v4 address. - * - * @returns the updated verifier - * @throws RangeError if actual value is not an IP v4 address - */ - isIpV4(): InetAddressVerifier; - - /** - * Ensures that the actual value is an IP v6 address. - * - * @returns the updated verifier - * @throws RangeError if actual value is not an IP v6 address - */ - isIpV6(): InetAddressVerifier; - - /** - * Ensures that the actual value is an IP v6 address. - * - * @returns the updated verifier - * @throws RangeError if actual value is not a hostname - * @see rfc3696 - */ - isHostname(): InetAddressVerifier; - - /** - * {@inheritDoc} - */ - getActual(): string; -} - -export {type InetAddressVerifier}; \ No newline at end of file diff --git a/src/JavascriptAssertThat.mts b/src/JavascriptAssertThat.mts new file mode 100644 index 0000000..7fa55ca --- /dev/null +++ b/src/JavascriptAssertThat.mts @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2017 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ + +import { + type BooleanValidator, + type SetValidator, + type MapValidator, + type StringValidator, + type NumberValidator, + type ObjectValidator, + type ArrayValidator, + AssertionError +} from "./internal/internal.mjs"; + +const typedocWorkaround: null | AssertionError = null; +// noinspection PointlessBooleanExpressionJS +if (typedocWorkaround !== null) + console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); + +/** + * Creates validators for the Javascript API that throw `AssertionError` immediately on validation failure. + */ +interface JavascriptAssertThat +{ + /** + * Validates the state of a `number`. + *

+ * The returned validator throws an error immediately if a validation fails. This error is then + * converted into an {@link AssertionError}. Errors unrelated to validation failures are not converted. + * + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is `null` + * @throws RangeError if `name` contains whitespace or is empty + */ + assertThat(value: number, name?: string): NumberValidator; + + /** + * Validates the state of a `boolean`. + *

+ * The returned validator throws an error immediately if a validation fails. This error is then + * converted into an {@link AssertionError}. Errors unrelated to validation failures are not converted. + * + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is `null` + * @throws RangeError if `name` contains whitespace or is empty + */ + assertThat(value: boolean, name?: string): BooleanValidator; + + /** + * Validates the state of an `object`. + *

+ * The returned validator throws an error immediately if a validation fails. This error is then + * converted into an {@link AssertionError}. Errors unrelated to validation failures are not converted. + * + * @typeParam T - the type of the value + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is `null` + * @throws RangeError if `name` contains whitespace or is empty + */ + assertThat(value: T, name?: string): ObjectValidator; + + /** + * Validates the state of an array. + *

+ * The returned validator throws an error immediately if a validation fails. This error is then + * converted into an {@link AssertionError}. Errors unrelated to validation failures are not converted. + * + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is `null` + * @throws RangeError if `name` contains whitespace or is empty + */ + assertThat(value: E[], name?: string): ArrayValidator; + + /** + * Validates the state of a `Set`. + *

+ * The returned validator throws an error immediately if a validation fails. This error is then + * converted into an {@link AssertionError}. Errors unrelated to validation failures are not converted. + * + * @typeParam E - the type of elements in the set + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is `null` + * @throws RangeError if `name` contains whitespace or is empty + */ + assertThat(value: Set, name?: string): SetValidator; + + /** + * Validates the state of a `Map`. + *

+ * The returned validator throws an error immediately if a validation fails. This error is then + * converted into an {@link AssertionError}. Errors unrelated to validation failures are not converted. + * + * @typeParam K - the type of keys in the map + * @typeParam V - the type of values in the map + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is `null` + * @throws RangeError if `name` contains whitespace or is empty + */ + assertThat(value: Map, name?: string): MapValidator; + + /** + * Validates the state of a `string`. + *

+ * The returned validator throws an error immediately if a validation fails. This error is then + * converted into an {@link AssertionError}. Errors unrelated to validation failures are not converted. + * + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is `null` + * @throws RangeError if `name` contains whitespace or is empty + */ + assertThat(value: string, name?: string): StringValidator; +} + +export type {JavascriptAssertThat}; \ No newline at end of file diff --git a/src/JavascriptCheckIf.mts b/src/JavascriptCheckIf.mts new file mode 100644 index 0000000..e0d1064 --- /dev/null +++ b/src/JavascriptCheckIf.mts @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2017 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ + +import { + type BooleanValidator, + type StringValidator, + type NumberValidator, + type ObjectValidator, + type ArrayValidator, + type SetValidator, + type MapValidator, +} from "./internal/internal.mjs"; + +/** + * Creates validators for the Javascript API that capture errors on validation failure rather than throwing + * them immediately. + */ +interface JavascriptCheckIf +{ + /** + * Validates the state of a `number`. + *

+ * The returned validator captures errors on validation failure rather than throwing them immediately. + * These errors can be retrieved or thrown once the validation completes. Errors unrelated to + * validation failures are thrown immediately. + * + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is `null` + * @throws RangeError if `name` contains whitespace or is empty + */ + checkIf(value: number, name: string): NumberValidator; + + /** + * Validates the state of a `boolean`. + *

+ * The returned validator captures errors on validation failure rather than throwing them immediately. + * These errors can be retrieved or thrown once the validation completes. Errors unrelated to + * validation failures are thrown immediately. + * + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is `null` + * @throws RangeError if `name` contains whitespace or is empty + */ + checkIf(value: boolean, name: string): BooleanValidator; + + /** + * Validates the state of an `object`. + *

+ * The returned validator captures errors on validation failure rather than throwing them immediately. + * These errors can be retrieved or thrown once the validation completes. Errors unrelated to + * validation failures are thrown immediately. + * + * @typeParam T - the type of the value + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is `null` + * @throws RangeError if `name` contains whitespace or is empty + */ + checkIf(value: T, name: string): ObjectValidator; + + /** + * Validates the state of an array. + *

+ * The returned validator captures errors on validation failure rather than throwing them immediately. + * These errors can be retrieved or thrown once the validation completes. Errors unrelated to + * validation failures are thrown immediately. + * + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is `null` + * @throws RangeError if `name` contains whitespace or is empty + */ + checkIf(value: E[], name: string): ArrayValidator; + + /** + * Validates the state of a `Set`. + *

+ * The returned validator captures errors on validation failure rather than throwing them immediately. + * These errors can be retrieved or thrown once the validation completes. Errors unrelated to + * validation failures are thrown immediately. + * + * @typeParam E - the type of elements in the set + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace or is empty + */ + checkIf(value: Set, name: string): SetValidator; + + /** + * Validates the state of a `Map`. + *

+ * The returned validator captures errors on validation failure rather than throwing them immediately. + * These errors can be retrieved or thrown once the validation completes. Errors unrelated to + * validation failures are thrown immediately. + * + * @typeParam K - the type of keys in the map + * @typeParam V - the type of values in the map + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace or is empty + */ + checkIf(value: Map, name: string): MapValidator; + + /** + * Validates the state of a `string`. + *

+ * The returned validator captures errors on validation failure rather than throwing them immediately. + * These errors can be retrieved or thrown once the validation completes. Errors unrelated to + * validation failures are thrown immediately. + * + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace or is empty + */ + checkIf(value: string, name: string): StringValidator; +} + +export type {JavascriptCheckIf}; \ No newline at end of file diff --git a/src/JavascriptRequireThat.mts b/src/JavascriptRequireThat.mts new file mode 100644 index 0000000..a8c44b1 --- /dev/null +++ b/src/JavascriptRequireThat.mts @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2017 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ + +import { + type NumberValidator, + type BooleanValidator, + type ArrayValidator, + type SetValidator, + type MapValidator, + type StringValidator, + type ObjectValidator, +} from "./internal/internal.mjs"; + +/** + * Creates validators for the Javascript API that throw errors immediately on validation failure. + */ +interface JavascriptRequireThat +{ + /** + * Validates the state of a `number`. + *

+ * The returned validator throws an error immediately if a validation fails. + * + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace or is empty + */ + requireThat(value: number, name: string): NumberValidator; + + /** + * Validates the state of a `boolean`. + *

+ * The returned validator throws an error immediately if a validation fails. + * + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace or is empty + */ + requireThat(value: boolean, name: string): BooleanValidator; + + /** + * Validates the state of an `object`. + *

+ * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type of the value + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace or is empty + */ + requireThat(value: T, name: string): ObjectValidator; + + /** + * Validates the state of an array. + *

+ * The returned validator throws an error immediately if a validation fails. + * + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace or is empty + */ + requireThat(value: E[], name: string): ArrayValidator; + + /** + * Validates the state of a `Set`. + *

+ * The returned validator throws an error immediately if a validation fails. + * + * @typeParam E - the type of elements in the set + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace or is empty + */ + requireThat(value: Set, name: string): SetValidator; + + /** + * Validates the state of a `Map`. + *

+ * The returned validator throws an error immediately if a validation fails. + * + * @typeParam K - the type of keys in the map + * @typeParam V - the type of values in the map + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace or is empty + */ + requireThat(value: Map, name: string): MapValidator; + + /** + * Validates the state of a `string`. + *

+ * The returned validator throws an error immediately if a validation fails. + * + * @param value - the value + * @param name - the name of the value + * @returns a validator for the value + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace or is empty + */ + requireThat(value: string, name: string): StringValidator; +} + +export type {JavascriptRequireThat}; \ No newline at end of file diff --git a/src/JavascriptValidators.mts b/src/JavascriptValidators.mts new file mode 100644 index 0000000..1d62579 --- /dev/null +++ b/src/JavascriptValidators.mts @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2017 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ + +import { + MainApplicationScope, + type Validators, + JavascriptValidatorsImpl, + type NumberValidator, + type BooleanValidator, + type ArrayValidator, + type SetValidator, + type MapValidator, + type StringValidator, + type JavascriptRequireThat, + type JavascriptAssertThat, + type JavascriptCheckIf, + Configuration, + type GlobalConfiguration, + type ObjectValidator, + AssertionError +} from "./internal/internal.mjs"; + +const typedocWorkaround: null | AssertionError = null; +// noinspection PointlessBooleanExpressionJS +if (typedocWorkaround !== null) + console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); + +/** + * Creates validators for the Javascript API with an independent configuration. + *

+ * A factory that creates different types of validators. + *

+ * There are three kinds of validators: + *

    + *
  • `requireThat` for method preconditions.
  • + *
  • `assertThat` for class invariants, and method postconditions.
  • + *
  • `checkIf` for returning multiple validation failures.
  • + *
+ */ +abstract class JavascriptValidators + implements Validators, JavascriptRequireThat, JavascriptAssertThat, JavascriptCheckIf +{ + /** + * Creates a new instance using the default configuration. + * + * @returns an instance of this interface + */ + static newInstance(): JavascriptValidators + { + return new JavascriptValidatorsImpl(MainApplicationScope.INSTANCE, Configuration.DEFAULT); + } + + abstract copy(): JavascriptValidators; + + abstract getContext(): Map; + + abstract withContext(value: unknown, name: string): JavascriptValidators; + + abstract getGlobalConfiguration(): GlobalConfiguration; + + abstract removeContext(name: string): JavascriptValidators; + + abstract requireThat(value: number, name: string): NumberValidator; + abstract requireThat(value: boolean, name: string): BooleanValidator; + abstract requireThat(value: E[], name: string): ArrayValidator; + abstract requireThat(value: Set, name: string): SetValidator; + abstract requireThat(value: Map, name: string): MapValidator; + abstract requireThat(value: string, name: string): StringValidator; + abstract requireThat(value: T, name: string): ObjectValidator; + + abstract assertThat(value: number, name?: string): NumberValidator; + abstract assertThat(value: boolean, name?: string): BooleanValidator; + abstract assertThat(value: E[], name?: string): ArrayValidator; + abstract assertThat(value: Set, name?: string): SetValidator; + abstract assertThat(value: Map, name?: string): MapValidator; + abstract assertThat(value: string, name?: string): StringValidator; + abstract assertThat(value: T, name?: string): ObjectValidator; + + abstract checkIf(value: number, name?: string): NumberValidator; + abstract checkIf(value: boolean, name?: string): BooleanValidator; + abstract checkIf(value: E[], name?: string): ArrayValidator; + abstract checkIf(value: Set, name?: string): SetValidator; + abstract checkIf(value: Map, name?: string): MapValidator; + abstract checkIf(value: string, name?: string): StringValidator; + abstract checkIf(value: T, name?: string): ObjectValidator; +} + +export {JavascriptValidators}; \ No newline at end of file diff --git a/src/MapValidator.mts b/src/MapValidator.mts deleted file mode 100644 index c3bf815..0000000 --- a/src/MapValidator.mts +++ /dev/null @@ -1,91 +0,0 @@ -import type { - ArrayValidator, - ExtensibleObjectValidator, - NumberValidator -} from "./internal/internal.mjs"; - -/** - * Validates the requirements of a Map. - * - * Verifier and Validator methods are equivalent. - * Validators return validation failures through the - * {@link ExtensibleObjectValidator.getFailures | getFailures()} method, while Verifiers throw them as - * exceptions. - * - * @typeParam K - the type the map's keys - * @typeParam V - the type the map's values - */ -interface MapValidator extends ExtensibleObjectValidator, Map> -{ - /** - * Ensures that value does not contain any entries - * - * @returns the updated validator - */ - isEmpty(): MapValidator; - - /** - * Ensures that value contains at least one entry. - * - * @returns the updated validator - */ - isNotEmpty(): MapValidator; - - /** - * @returns a validator for the Map's keys - */ - keys(): ArrayValidator; - - /** - * @param consumer - a function that accepts an {@link ArrayValidator} for the Map's keys - * @returns the updated validator - * @throws TypeError if consumer is not set - */ - keysConsumer(consumer: (actual: ArrayValidator) => void): MapValidator; - - /** - * @returns a validator for the Map's values - */ - values(): ArrayValidator; - - /** - * @param consumer - a function that accepts an {@link ArrayValidator} for the Map's values - * @returns the updated validator - * @throws TypeError if consumer is not set - */ - valuesConsumer(consumer: (actual: ArrayValidator) => void): MapValidator; - - /** - * @returns validator for the Map's entries (an array of - * [key, value] for each element in the Map) - */ - entries(): ArrayValidator<[K, V]>; - - /** - * @param consumer - a function that accepts an {@link ArrayValidator} for the Map's entries (an - * array of [key, value] for each element in the Map) - * @returns the updated validator - * @throws TypeError if consumer is not set - */ - entriesConsumer(consumer: (actual: ArrayValidator<[K, V]>) => void): MapValidator; - - /** - * @returns a validator for the number of entries this Map contains - */ - size(): NumberValidator; - - /** - * @param consumer - a function that accepts a {@link NumberValidator} for the number of entries - * this Map contains - * @returns the updated validator - * @throws TypeError if consumer is not set - */ - sizeConsumer(consumer: (actual: NumberValidator) => void): MapValidator; - - /** - * {@inheritDoc} - */ - getActual(): Map | undefined; -} - -export {type MapValidator}; \ No newline at end of file diff --git a/src/MapVerifier.mts b/src/MapVerifier.mts deleted file mode 100644 index 373e857..0000000 --- a/src/MapVerifier.mts +++ /dev/null @@ -1,96 +0,0 @@ -import type { - ArrayVerifier, - NumberVerifier, - ObjectVerifier, - ExtensibleObjectVerifier -} from "./internal/internal.mjs"; - -const typedocWorkaround: null | ObjectVerifier = null; -// noinspection PointlessBooleanExpressionJS -if (typedocWorkaround !== null) - console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); - -/** - * Verifies the requirements of a Map. - *

- * All methods (except those found in {@link ObjectVerifier}) assume that the actual value is not null. - * - * @typeParam K - the type the map's keys - * @typeParam V - the type the map's values - */ -interface MapVerifier extends ExtensibleObjectVerifier, Map> -{ - /** - * Ensures that value does not contain any entries - * - * @returns the updated verifier - * @throws RangeError if the value contains any entries - */ - isEmpty(): MapVerifier; - - /** - * Ensures that value contains at least one entry. - * - * @returns the updated verifier - * @throws RangeError if the value does not contain any entries - */ - isNotEmpty(): MapVerifier; - - /** - * @returns a verifier for the Map's keys - */ - keys(): ArrayVerifier; - - /** - * @param consumer - a function that accepts an {@link ArrayVerifier} for the Map's keys - * @returns the updated verifier - * @throws TypeError if consumer is not set - */ - keysConsumer(consumer: (actual: ArrayVerifier) => void): MapVerifier; - - /** - * @returns a verifier for the Map's values - */ - values(): ArrayVerifier; - - /** - * @param consumer - a function that accepts an {@link ArrayVerifier} for the Map's values - * @returns the updated verifier - * @throws TypeError if consumer is not set - */ - valuesConsumer(consumer: (actual: ArrayVerifier) => void): MapVerifier; - - /** - * @returns a verifier for the Map's entries (an array of [key, value] for - * each element in the Map) - */ - entries(): ArrayVerifier<[K, V]>; - - /** - * @param consumer - a function that accepts an {@link ArrayVerifier} for the Map's entries (an - * array of [key, value] for each element in the Map) - * @returns the updated verifier - * @throws TypeError if consumer is not set - */ - entriesConsumer(consumer: (actual: ArrayVerifier<[K, V]>) => void): MapVerifier; - - /** - * @returns a verifier for the number of entries this Map contains - */ - size(): NumberVerifier; - - /** - * @param consumer - a function that accepts a {@link NumberVerifier} for the number of entries - * this Map contains - * @returns the updated verifier - * @throws TypeError if consumer is not set - */ - sizeConsumer(consumer: (actual: NumberVerifier) => void): MapVerifier; - - /** - * {@inheritDoc} - */ - getActual(): Map; -} - -export {type MapVerifier}; \ No newline at end of file diff --git a/src/MultipleFailuresError.mts b/src/MultipleFailuresError.mts new file mode 100644 index 0000000..1b88ebc --- /dev/null +++ b/src/MultipleFailuresError.mts @@ -0,0 +1,56 @@ +import { + type ValidationFailure, + requireThatTypeCategory, + TypeCategory +} from "./internal/internal.mjs"; + +/** + * Thrown if multiple validations have failed. + */ +class MultipleFailuresError extends Error +{ + private readonly failures: ValidationFailure[]; + + /** + * Creates a new error. + * + * @param failures - the list of validation failures + * @throws TypeError if `failures` is `undefined` or `null` + * @throws RangeError if `failures` contains less than two elements + */ + public constructor(failures: ValidationFailure[]) + { + super(MultipleFailuresError.createMessage(failures)); + this.failures = failures; + } + + private static createMessage(failures: ValidationFailure[]) + { + requireThatTypeCategory(failures, "failures", TypeCategory.ARRAY); + if (failures.length === 0) + throw new RangeError("failures must contain at least two elements"); + let result = `There are ${failures.length} nested errors.\n`; + let i = 1; + for (const failure of failures) + { + result += `${i}. ${failure.getError().name}`; + const message = failure.getMessage(); + if (message !== null) + result += `: ${message}\n`; + ++i; + } + return result.toString(); + } + + /** + * Returns the list of validation failures. + * + * @returns the list of validation failures + */ + public getFailures(): ValidationFailure[] + { + return this.failures; + } +} + +export {MultipleFailuresError}; \ No newline at end of file diff --git a/src/NumberValidator.mts b/src/NumberValidator.mts deleted file mode 100644 index 864cacb..0000000 --- a/src/NumberValidator.mts +++ /dev/null @@ -1,26 +0,0 @@ -import type { - ExtensibleNumberValidator, - ExtensibleObjectValidator -} from "./internal/internal.mjs"; - -const typedocWorkaround: null | ExtensibleObjectValidator = null; -// noinspection PointlessBooleanExpressionJS -if (typedocWorkaround !== null) - console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); - -/** - * Validates the requirements of a number. - * - * Verifier and Validator methods are equivalent. - * Validators return validation failures through the - * {@link ExtensibleObjectValidator.getFailures | getFailures()} method, while Verifiers throw them as - * exceptions. - * - * All methods (except those found in {@link ObjectValidator}) assume that the actual value is not null. - */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface NumberValidator extends ExtensibleNumberValidator -{ -} - -export {type NumberValidator}; \ No newline at end of file diff --git a/src/NumberVerifier.mts b/src/NumberVerifier.mts deleted file mode 100644 index 5b62b85..0000000 --- a/src/NumberVerifier.mts +++ /dev/null @@ -1,13 +0,0 @@ -import type {ExtensibleNumberVerifier} from "./internal/internal.mjs"; - -/** - * Verifies the requirements of a number. - *

- * All methods (except those found in {@link ObjectVerifier}) assume that the actual value is not null. - */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface NumberVerifier extends ExtensibleNumberVerifier -{ -} - -export {type NumberVerifier}; \ No newline at end of file diff --git a/src/ObjectValidator.mts b/src/ObjectValidator.mts deleted file mode 100644 index dc51f23..0000000 --- a/src/ObjectValidator.mts +++ /dev/null @@ -1,17 +0,0 @@ -import type {ExtensibleObjectValidator} from "./internal/internal.mjs"; - -/** - * Validates the requirements of an object. - * - * Verifier and Validator methods are equivalent. - * Validators return validation failures through the - * {@link ExtensibleObjectValidator.getFailures | getFailures()} method, while Verifiers throw them as - * exceptions. - * - * @typeParam T - the type the actual value - */ -interface ObjectValidator extends ExtensibleObjectValidator, T> -{ -} - -export {type ObjectValidator}; \ No newline at end of file diff --git a/src/ObjectVerifier.mts b/src/ObjectVerifier.mts deleted file mode 100644 index 2df14b0..0000000 --- a/src/ObjectVerifier.mts +++ /dev/null @@ -1,12 +0,0 @@ -import type {ExtensibleObjectVerifier} from "./internal/internal.mjs"; - -/** - * Verifies the requirements of an object. - * - * @typeParam T - the type the actual value - */ -interface ObjectVerifier extends ExtensibleObjectVerifier, T> -{ -} - -export {type ObjectVerifier}; \ No newline at end of file diff --git a/src/Requirements.mts b/src/Requirements.mts deleted file mode 100644 index 0869599..0000000 --- a/src/Requirements.mts +++ /dev/null @@ -1,313 +0,0 @@ -import type { - ObjectVerifier, - SetVerifier, - ArrayVerifier, - ClassVerifier, - ArrayValidator, - SetValidator, - ClassValidator, - ObjectValidator, - MapVerifier, - StringVerifier, - NumberVerifier, - StringValidator, - NumberValidator, - MapValidator, - BooleanVerifier, - BooleanValidator, - ClassConstructor, - AnythingButClassConstructor -} from "./internal/internal.mjs"; -import { - Configuration, - MainGlobalConfiguration, - Objects, - ObjectValidatorImpl, - ObjectVerifierImpl, - SetValidatorImpl, - SetVerifierImpl, - ArrayVerifierImpl, - ArrayValidatorImpl, - Pluralizer, - ClassVerifierImpl, - ClassValidatorImpl, - MapVerifierImpl, - MapValidatorImpl, - BooleanVerifierImpl, - BooleanValidatorImpl, - StringVerifierImpl, - StringValidatorImpl, - NumberVerifierImpl, - NumberValidatorImpl -} from "./internal/internal.mjs"; - -/** - * Verifies the requirements of types from the Javascript core API. - */ -class Requirements -{ - private readonly config: Configuration; - - /** - * Verifies a value. - *

- * Unlike {@link Requirements}, instances of this class can be configured prior to initiating verification. - * Doing so causes the same configuration to get reused across runs. - * - * @param configuration - the instance configuration - */ - constructor(configuration?: Configuration) - { - if (typeof (configuration) === "undefined") - configuration = new Configuration(MainGlobalConfiguration.INSTANCE); - this.config = configuration; - } - - /** - * Verifies the requirements of an object. - * - * @typeParam T - the type the actual value - * @typeParam E - the type elements in the array or set - * @param actual - the actual value - * @param name - the name of the value - * @returns a verifier - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - requireThat(actual: boolean, name: string): BooleanVerifier; - requireThat(actual: string, name: string): StringVerifier; - requireThat(actual: number, name: string): NumberVerifier; - requireThat(actual: Array, name: string): ArrayVerifier; - requireThat(actual: Set, name: string): SetVerifier; - requireThat(actual: Map, name: string): MapVerifier; - requireThat(actual: ClassConstructor, name: string): ClassVerifier; - requireThat(actual: T, name: string): ObjectVerifier; - requireThat - (actual: unknown, name: string): BooleanVerifier | StringVerifier | NumberVerifier | ArrayVerifier | - SetVerifier | MapVerifier | ClassVerifier | ObjectVerifier - { - Objects.verifyName(name, "name"); - const typeOfActual = Objects.getTypeInfo(actual); - switch (typeOfActual.type) - { - case "boolean": - return new BooleanVerifierImpl(new BooleanValidatorImpl(this.config, actual as boolean, name, [])); - case "string": - return new StringVerifierImpl(new StringValidatorImpl(this.config, actual as string, name, [])); - case "number": - return new NumberVerifierImpl(new NumberValidatorImpl(this.config, actual as number, name, [])); - case "array": - { - return new ArrayVerifierImpl(new ArrayValidatorImpl(this.config, actual as E[], name, - Pluralizer.ELEMENT, [])); - } - case "object": - { - switch (typeOfActual.name) - { - case "Set": - return new SetVerifierImpl(new SetValidatorImpl(this.config, actual as Set, name, [])); - case "Map": - { - return new MapVerifierImpl(new MapValidatorImpl(this.config, actual as Map, - name, [])); - } - } - break; - } - case "class": - { - return new ClassVerifierImpl(new ClassValidatorImpl(this.config, - actual as ClassConstructor | undefined, name, [])); - } - } - return new ObjectVerifierImpl, T>(new ObjectValidatorImpl(this.config, - actual as T | undefined, name, [])); - } - - /** - * Validates the requirements of an object. - * - * @typeParam T - the type the actual value - * @typeParam E - the type elements in the array or set - * @param actual - the actual value - * @param name - the name of the value - * @returns validator for the value - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - validateThat(actual: boolean, name: string): BooleanValidator; - validateThat(actual: string, name: string): StringValidator; - validateThat(actual: number, name: string): NumberValidator; - validateThat(actual: Array, name: string): ArrayValidator; - validateThat(actual: Set, name: string): SetValidator; - validateThat(actual: Map, name: string): MapValidator; - validateThat(actual: AnythingButClassConstructor, name: string): ObjectValidator; - validateThat(actual: ClassConstructor, name: string): ClassValidator; - validateThat(actual: unknown, name: string): ObjectValidator; - validateThat - (actual: unknown, name: string): BooleanValidator | StringValidator | NumberValidator | - ArrayValidator | SetValidator | MapValidator | ObjectValidator | ClassValidator | - ObjectValidator - { - Objects.verifyName(name, "name"); - const typeOfActual = Objects.getTypeInfo(actual); - switch (typeOfActual.type) - { - case "boolean": - return new BooleanValidatorImpl(this.config, actual as boolean, name, []); - case "string": - return new StringValidatorImpl(this.config, actual as string, name, []); - case "number": - return new NumberValidatorImpl(this.config, actual as number, name, []); - case "class": - return new ClassValidatorImpl(this.config, actual as ClassConstructor | undefined, name, []); - case "array": - return new ArrayValidatorImpl(this.config, actual as E[], name, Pluralizer.ELEMENT, []); - case "object": - { - switch (typeOfActual.name) - { - case "Set": - return new SetValidatorImpl(this.config, actual as Set, name, []); - case "Map": - return new MapValidatorImpl(this.config, actual as Map, name, []); - } - } - } - return new ObjectValidatorImpl(this.config, actual, name, []); - } - - /** - * Verifies requirements only if assertions are enabled. - * - * @param requirements - the requirements to verify - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - assertThat(requirements: (requirements: Requirements) => void) - { - Objects.requireThatValueIsDefinedAndNotNull(requirements, "requirements"); - if (this.config.assertionsAreEnabled()) - requirements(this.copy()); - } - - /** - * Verifies requirements only if assertions are enabled. - * - * @param requirements - the requirements to verify - * @returns the value returned by the operation, or undefined if assertions are disabled - * @throws TypeError if name is null - * @throws RangeError if name is empty - * @see #assertThat - */ - assertThatAndReturn(requirements: (requirements: Requirements) => V) - { - Objects.requireThatValueIsDefinedAndNotNull(requirements, "requirements"); - if (this.config.assertionsAreEnabled()) - return requirements(this.copy()); - return undefined; - } - - /** - * Returns a copy of this configuration. - * - * @returns a copy of this configuration - */ - copy() - { - return new Requirements(this.config.copy()); - } - - /** - * Indicates whether assertThat() should invoke requireThat(). - * - * @returns true if assertThat() should delegate to requireThat(); false if it - * shouldn't do anything - */ - assertionsAreEnabled() - { - return this.config.assertionsAreEnabled(); - } - - /** - * Indicates that assertThat() should invoke requireThat(). - * - * @returns this - */ - withAssertionsEnabled() - { - this.config.withAssertionsEnabled(); - return this; - } - - /** - * Indicates that assertThat() shouldn't do anything. - * - * @returns this - */ - withAssertionsDisabled() - { - this.config.withAssertionsDisabled(); - return this; - } - - /** - * Indicates if exceptions should show the difference between the actual and expected values. - * - * @returns true by default - */ - isDiffEnabled() - { - return this.config.isDiffEnabled(); - } - - /** - * Indicates that exceptions should show the difference between the actual and expected values. - * - * @returns this - */ - withDiff() - { - this.config.withDiff(); - return this; - } - - /** - * Indicates that exceptions should not show the difference between the actual and expected - * values. - * - * @returns this - */ - withoutDiff() - { - this.config.withoutDiff(); - return this; - } - - /** - * @returns a map of key-value pairs to append to the exception message - * @see #putContext - */ - getContext() - { - return this.config.getContext(); - } - - /** - * Appends contextual information to the exception message. - * - * @param key - a key - * @param value - a value - * @returns this - * @throws TypeError if key is not a string - * @see #getContext - */ - putContext(key: string, value: unknown) - { - this.config.putContext(key, value); - return this; - } -} - -export {Requirements}; \ No newline at end of file diff --git a/src/SetValidator.mts b/src/SetValidator.mts deleted file mode 100644 index dd0e1ea..0000000 --- a/src/SetValidator.mts +++ /dev/null @@ -1,151 +0,0 @@ -import type { - ArrayValidator, - ExtensibleObjectValidator, - NumberValidator -} from "./internal/internal.mjs"; - -/** - * Validates the requirements of a Set. - * - * Verifier and Validator methods are equivalent. - * Validators return validation failures through the - * {@link ExtensibleObjectValidator.getFailures | getFailures()} method, while Verifiers throw them as - * exceptions. - * - * All methods (except those found in {@link ObjectValidator}) assume that the actual value is not null. - * - * @typeParam E - the type the array elements - */ -interface SetValidator extends ExtensibleObjectValidator, Set> -{ - /** - * Ensures that value does not contain any elements. - * - * @returns the updated validator - */ - isEmpty(): SetValidator; - - /** - * Ensures that value contains at least one element. - * - * @returns the updated validator - */ - isNotEmpty(): SetValidator; - - /** - * Ensures that the actual value contains an entry. - * - * @param expected - the expected value - * @param name - (optional) the name of the expected value - * @returns the updated validator - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - contains(expected: E, name?: string): SetValidator; - - /** - * Ensures that the actual value contains exactly the same elements as the expected value; nothing less, - * nothing more. - * - * @param expected - the elements that must exist - * @param name - (optional) the name of the expected elements - * @returns the updated validator - * @throws TypeError if name is null. - * If expected is not an Array or Set. - * @throws RangeError if name is empty - */ - containsExactly(expected: E[] | Set, name?: string): SetValidator; - - /** - * Ensures that the actual value contains any of the elements in the expected value. - * - * @param expected - the elements that must exist - * @param name - (optional) the name of the expected elements - * @returns the updated validator - * @throws TypeError if name is null. - * If expected is not an Array or Set. - * @throws RangeError if name is empty - */ - containsAny(expected: E[] | Set, name?: string): SetValidator; - - /** - * Ensures that the actual value contains all the elements in the expected value. - * - * @param expected - the elements that must exist - * @param name - (optional) the name of the expected elements - * @returns the updated validator - * @throws TypeError if name is null. - * If expected is not an Array or Set. - * @throws RangeError if name is empty - */ - containsAll(expected: E[] | Set, name?: string): SetValidator; - - /** - * Ensures that the actual value does not contain an entry. - * - * @param entry - an entry - * @param name - (optional) the name of the entry - * @returns the updated validator - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - doesNotContain(entry: E, name?: string): SetValidator; - - /** - * Ensures that the actual value does not contain any of the specified elements. - * - * @param elements - the elements that must not exist - * @param name - (optional) the name of the elements - * @returns the updated validator - * @throws TypeError if name is null. - * If elements is not an Array or Set. - * @throws RangeError if name is empty - */ - doesNotContainAny(elements: E[] | Set, name?: string): SetValidator; - - /** - * Ensures that the array does not contain all the specified elements. - * - * @param elements - the elements that must not exist - * @param name - (optional) the name of the elements - * @returns the updated validator - * @throws TypeError if name is null. - * If elements is not an Array or Set. - * @throws RangeError if name is empty - */ - doesNotContainAll(elements: E[] | Set, name?: string): SetValidator; - - /** - * @returns a validator for the Set's size - */ - size(): NumberValidator; - - /** - * @param consumer - a function that accepts a {@link NumberValidator} for the Set's size - * @returns the updated validator - * @throws TypeError if consumer is not set - */ - sizeConsumer(consumer: (actual: NumberValidator) => void): SetValidator; - - /** - * @returns a validator for the Set's elements - */ - asArray(): ArrayValidator; - - asArray(): ArrayValidator; - - /** - * @returns a validator for the Set - * @deprecated the actual value is already a set - */ - asSet(): SetValidator; - - asSet(): SetValidator; - - /** - * {@inheritDoc} - */ - getActual(): Set | undefined; -} - -export {type SetValidator}; \ No newline at end of file diff --git a/src/SetVerifier.mts b/src/SetVerifier.mts deleted file mode 100644 index 2fc300e..0000000 --- a/src/SetVerifier.mts +++ /dev/null @@ -1,147 +0,0 @@ -import type { - NumberVerifier, - ObjectVerifier, - ExtensibleObjectVerifier -} from "./internal/internal.mjs"; - -const typedocWorkaround: null | ObjectVerifier = null; -// noinspection PointlessBooleanExpressionJS -if (typedocWorkaround !== null) - console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); - -/** - * Verifies the requirements of a Set. - *

- * All methods (except those found in {@link ObjectVerifier}) assume that the actual value is not null. - * - * @typeParam E - the type the array elements - */ -interface SetVerifier extends ExtensibleObjectVerifier, Set> -{ - /** - * Ensures that value does not contain any elements. - * - * @returns the updated verifier - * @throws RangeError if the value contains at least one element - */ - isEmpty(): SetVerifier; - - /** - * Ensures that value contains at least one element. - * - * @returns the updated verifier - * @throws RangeError if the value does not contain any elements - */ - isNotEmpty(): SetVerifier; - - /** - * Ensures that the actual value contains an entry. - * - * @param expected - the expected value - * @param name - (optional) the name of the expected value - * @returns the updated verifier - * @throws TypeError if name is null - * @throws RangeError if name is empty. - * If the Set does not contain expected. - */ - contains(expected: E, name?: string): SetVerifier; - - /** - * Ensures that the actual value contains exactly the same elements as the expected value; nothing less, - * nothing more. - * - * @param expected - the elements that must exist - * @param name - (optional) the name of the expected elements - * @returns the updated verifier - * @throws TypeError if name is null. - * If expected is not an Array or Set. - * @throws RangeError if name is empty. - * If the actual value is missing any elements in expected. - * If the actual value contains elements not found in expected. - */ - containsExactly(expected: E[] | Set, name?: string): SetVerifier; - - /** - * Ensures that the actual value contains any of the elements in the expected value. - * - * @param expected - the elements that must exist - * @param name - (optional) the name of the expected elements - * @returns the updated verifier - * @throws TypeError if name is null. - * If expected is not an Array or Set. - * @throws RangeError if name is empty. - * If the actual value is missing any elements in expected. - * If the actual value contains elements not found in expected. - */ - containsAny(expected: E[] | Set, name?: string): SetVerifier; - - /** - * Ensures that the actual value contains all the elements in the expected value. - * - * @param expected - the elements that must exist - * @param name - (optional) the name of the expected elements - * @returns the updated verifier - * @throws TypeError if name is null. - * If expected is not an Array or Set. - * @throws RangeError if name is empty. - * If the actual value does not contain all of expected. - */ - containsAll(expected: E[] | Set, name?: string): SetVerifier; - - /** - * Ensures that the actual value does not contain an entry. - * - * @param entry - an entry - * @param name - (optional) the name of the entry - * @returns the updated verifier - * @throws TypeError if name is null - * @throws RangeError if name is empty. - * If the actual value contains entry. - */ - doesNotContain(entry: E, name?: string): SetVerifier; - - /** - * Ensures that the actual value does not contain any of the specified elements. - * - * @param elements - the elements that must not exist - * @param name - (optional) the name of the elements - * @returns the updated verifier - * @throws TypeError if name is null. - * If elements is not an Array or Set. - * @throws RangeError if name is empty. - * If the array contains any of elements. - */ - doesNotContainAny(elements: E[] | Set, name?: string): SetVerifier; - - /** - * Ensures that the array does not contain all the specified elements. - * - * @param elements - the elements that must not exist - * @param name - (optional) the name of the elements - * @returns the updated verifier - * @throws TypeError if name is null. - * If elements is not an Array or Set. - * @throws RangeError if name is empty. - * If the actual value contains all of elements. - */ - doesNotContainAll(elements: E[] | Set, name?: string): SetVerifier; - - /** - * @returns a verifier for the Set's size - */ - size(): NumberVerifier; - - /** - * @param consumer - a function that accepts a {@link NumberVerifier} for the Set's size - * @returns the updated verifier - * @throws TypeError if consumer is not set - */ - sizeConsumer(consumer: (actual: NumberVerifier) => void): SetVerifier; - - /** - * {@inheritDoc} - */ - getActual(): Set; -} - -export {type SetVerifier}; \ No newline at end of file diff --git a/src/StringValidator.mts b/src/StringValidator.mts deleted file mode 100644 index 6fa766c..0000000 --- a/src/StringValidator.mts +++ /dev/null @@ -1,107 +0,0 @@ -import type { - ExtensibleObjectValidator, - NumberValidator -} from "./internal/internal.mjs"; - -/** - * Validates the requirements of a string. - * - * Verifier and Validator methods are equivalent. - * Validators return validation failures through the - * {@link ExtensibleObjectValidator.getFailures | getFailures()} method, while Verifiers throw them as - * exceptions. - * - * All methods (except those found in {@link ObjectValidator}) assume that the actual value is not null. - */ -interface StringValidator extends ExtensibleObjectValidator -{ - /** - * Ensures that the actual value starts with a value. - * - * @param prefix - the value that the string must start with - * @returns the updated validator - */ - startsWith(prefix: string): StringValidator; - - /** - * Ensures that the actual value does not start with a value. - * - * @param prefix - the value that the string may not start with - * @returns the updated validator - */ - doesNotStartWith(prefix: string): StringValidator; - - /** - * Ensures that the actual value contains a value. - * - * @param expected - the value that the string must contain - * @returns the updated validator - */ - contains(expected: string): StringValidator; - - /** - * Ensures that the actual value does not contain a value. - * - * @param value - the value that the string may not contain - * @returns the updated validator - */ - doesNotContain(value: string): StringValidator; - - /** - * Ensures that the actual value ends with a value. - * - * @param suffix - the value that the string must end with - * @returns the updated validator - */ - endsWith(suffix: string): StringValidator; - - /** - * Ensures that the actual value does not end with a value. - * - * @param suffix - the value that the string may not end with - * @returns the updated validator - */ - doesNotEndWith(suffix: string): StringValidator; - - /** - * Ensures that the value is an empty string. - * - * @returns the updated validator - */ - isEmpty(): StringValidator; - - /** - * Ensures that the value is not an empty string. - * - * @returns the updated validator - */ - isNotEmpty(): StringValidator; - - /** - * Ensures that the actual value does not contain leading or trailing whitespace. - * - * @returns the updated validator - * @see #trim - */ - isTrimmed(): StringValidator; - - /** - * @returns a validator for the length of the string - */ - length(): NumberValidator; - - /** - * @param consumer - a function that accepts a {@link NumberValidator} for the length of the - * string - * @returns the updated validator - * @throws TypeError if consumer is not set - */ - lengthConsumer(consumer: (actual: NumberValidator) => void): StringValidator; - - /** - * {@inheritDoc} - */ - getActual(): string | undefined; -} - -export {type StringValidator}; \ No newline at end of file diff --git a/src/StringVerifier.mts b/src/StringVerifier.mts deleted file mode 100644 index 6d1b918..0000000 --- a/src/StringVerifier.mts +++ /dev/null @@ -1,121 +0,0 @@ -import type { - ExtensibleObjectValidator, - NumberVerifier, - ExtensibleObjectVerifier -} from "./internal/internal.mjs"; - - -const typedocWorkaround: null | ExtensibleObjectValidator = null; -// noinspection PointlessBooleanExpressionJS -if (typedocWorkaround !== null) - console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); - -/** - * Verifies the requirements of a string. - *

- * Verifier and Validator methods are equivalent. - * Validators return validation failures through the - * {@link ExtensibleObjectValidator.getFailures | getFailures()} method, while Verifiers throw them as - * exceptions. - * - * All methods (except those found in {@link ObjectValidator}) assume that the actual value is not null. - */ -interface StringVerifier extends ExtensibleObjectVerifier -{ - /** - * Ensures that the actual value starts with a value. - * - * @param prefix - the value that the string must start with - * @returns the updated verifier - * @throws RangeError if the actual value does not start with prefix - */ - startsWith(prefix: string): StringVerifier; - - /** - * Ensures that the actual value does not start with a value. - * - * @param prefix - the value that the string may not start with - * @returns the updated verifier - * @throws RangeError if the actual value does not start with prefix - */ - doesNotStartWith(prefix: string): StringVerifier; - - /** - * Ensures that the actual value contains a value. - * - * @param expected - the value that the string must contain - * @returns the updated verifier - * @throws RangeError if the actual value does not contain expected - */ - contains(expected: string): StringVerifier; - - /** - * Ensures that the actual value does not contain a value. - * - * @param value - the value that the string may not contain - * @returns the updated verifier - * @throws RangeError if the actual value does not contain value - */ - doesNotContain(value: string): StringVerifier; - - /** - * Ensures that the actual value ends with a value. - * - * @param suffix - the value that the string must end with - * @returns the updated verifier - * @throws RangeError if the actual value does not end with suffix - */ - endsWith(suffix: string): StringVerifier; - - /** - * Ensures that the actual value does not end with a value. - * - * @param suffix - the value that the string may not end with - * @returns the updated verifier - * @throws RangeError if the actual value does not start with suffix - */ - doesNotEndWith(suffix: string): StringVerifier; - - /** - * Ensures that the value is an empty string. - * - * @returns the updated verifier - * @throws RangeError if the value is not an empty string - */ - isEmpty(): StringVerifier; - - /** - * Ensures that the value is not an empty string. - * - * @returns the updated verifier - * @throws RangeError if the value is an empty string - */ - isNotEmpty(): StringVerifier; - - /** - * Ensures that the actual value does not contain leading or trailing whitespace. - * - * @returns a verifier for the trimmed representation of the actual value - * @see #trim - */ - isTrimmed(): StringVerifier; - - /** - * @returns a verifier for the length of the string - */ - length(): NumberVerifier; - - /** - * @param consumer - a function that accepts a {@link NumberVerifier} for the length of the string - * @returns the updated verifier - * @throws TypeError if consumer is not set - */ - lengthConsumer(consumer: (actual: NumberVerifier) => void): StringVerifier; - - /** - * {@inheritDoc} - */ - getActual(): string; -} - -export {type StringVerifier}; \ No newline at end of file diff --git a/src/TerminalEncoding.mts b/src/TerminalEncoding.mts index 0f8648d..376f104 100644 --- a/src/TerminalEncoding.mts +++ b/src/TerminalEncoding.mts @@ -1,9 +1,3 @@ -import { - AbstractColorWriter, - Objects, - TextOnly -} from "./internal/internal.mjs"; - /** * The encodings supported by the terminal. */ @@ -28,48 +22,19 @@ enum TerminalEncoding } /** - * Helper functions for TerminalEncodings. + * Returns a comparator that sorts encodings based on the number of colors that they support, from the most + * to the least number of colors. + * + * @param first - the first encoding + * @param second - the second encoding + * @returns a negative number if `first` supports more colors than `second`. + * `0` if the encodings support the same number of colors. A positive number if + * `first` supports fewer colors than `second`. */ -class TerminalEncodings -{ - /** - * @param first - the first encoding - * @param second - the second encoding - * @returns a negative number if first supports more colors than second. - * 0 if the encodings support the same number of colors. A positive number if - * first supports fewer colors than second. - */ - static readonly sortByDecreasingRank = (first: TerminalEncoding, second: TerminalEncoding): number => - { - if (first < second) - return 1; - if (first > second) - return -1; - return 0; - }; - - /** - * @param terminalEncoding - the encoding to use for the terminal - * @returns the padding character used to align values vertically - */ - static getPaddingMarker(terminalEncoding: TerminalEncoding) - { - switch (terminalEncoding) - { - case TerminalEncoding.NONE: - return TextOnly.DIFF_PADDING; - case TerminalEncoding.NODE_16_COLORS: - case TerminalEncoding.NODE_256_COLORS: - case TerminalEncoding.NODE_16MILLION_COLORS: - return AbstractColorWriter.DIFF_PADDING; - default: - throw new RangeError(Objects.toString(terminalEncoding)); - } - } -} +const sortByDecreasingRank = (first: TerminalEncoding, second: TerminalEncoding): number => second - first; export { TerminalEncoding, - TerminalEncodings + sortByDecreasingRank }; \ No newline at end of file diff --git a/src/Type.mts b/src/Type.mts new file mode 100644 index 0000000..4f8a5ab --- /dev/null +++ b/src/Type.mts @@ -0,0 +1,339 @@ +import { + type ClassConstructor +} from "./internal/internal.mjs"; + +enum TypeCategory +{ + UNDEFINED, + NULL, + BOOLEAN, + NUMBER, + BIGINT, + STRING, + SYMBOL, + ARRAY, + FUNCTION, + CLASS +} + +const FUNCTION_NAME_REGEX = /^function\s+([^(]+)?\(/; +const CLASS_NAME_REGEX = /^class\s+([^{\s]+)?.*?{/; +const BUILT_IN_CLASS_NAME_REGEX = /^function\s+([^(]+)?\(\) { \[native code] }/; +const STARTS_WITH_VOWEL_REGEX = /^[aeiouAEIOU]/; + +/** + * Describes the type of a value. + */ +class Type +{ + public static readonly UNDEFINED = new Type(TypeCategory.UNDEFINED); + public static readonly NULL = new Type(TypeCategory.NULL); + public static readonly BOOLEAN = new Type(TypeCategory.BOOLEAN); + public static readonly NUMBER = new Type(TypeCategory.NUMBER); + public static readonly BIGINT = new Type(TypeCategory.BIGINT); + public static readonly STRING = new Type(TypeCategory.STRING); + public static readonly SYMBOL = new Type(TypeCategory.SYMBOL); + public static readonly ARRAY = new Type(TypeCategory.ARRAY); + /** + * An anonymous or arrow function. + */ + public static readonly ANONYMOUS_FUNCTION = new Type(TypeCategory.FUNCTION); + + public readonly category: TypeCategory; + public readonly name: string | null; + public readonly typeGuard?: (value: unknown) => boolean; + + /** + * Returns the type of a value. + * + * @param value - a value + * @returns the value's type + * @see http://stackoverflow.com/a/332429/14731 + * @see {@link Type.isPrimitive} + */ + public static of(value: unknown): Type + { + const primitive = Type.getPrimitive(value); + if (primitive !== null) + return primitive; + if (Array.isArray(value)) + return Type.ARRAY; + const valueAsFunction = value as ClassConstructor; + const objectToString = Object.prototype.toString.call(value).slice(8, -1); + if (objectToString === "Function") + { + // A function or a class + const valueAsString = valueAsFunction.toString(); + const indexOfArrow = valueAsString.indexOf("=>"); + const indexOfBody = valueAsString.indexOf("{"); + if (indexOfArrow !== -1 && (indexOfBody === -1 || indexOfArrow < indexOfBody)) + { + // Arrow function + return Type.ANONYMOUS_FUNCTION; + } + const className = CLASS_NAME_REGEX.exec(valueAsString); + if (className !== null && className[1] !== undefined) + { + // A class + const name = className[1]; + // Class constructors are returned as named functions + return Type.namedClass(name); + } + const builtInClassName = BUILT_IN_CLASS_NAME_REGEX.exec(valueAsString); + if (builtInClassName !== null && builtInClassName[1] !== undefined) + { + // A built-in class + const name = builtInClassName[1].trim(); + // Class constructors are returned as named functions + return Type.namedClass(name); + } + // Anonymous and named functions + const functionName = FUNCTION_NAME_REGEX.exec(valueAsString); + if (functionName !== null && functionName[1] !== undefined) + { + // A named function + const name = functionName[1].trim(); + return Type.namedFunction(name); + } + // Anonymous function + return Type.ANONYMOUS_FUNCTION; + } + + // Per https://stackoverflow.com/a/30560581/14731 the ES6 specification guarantees the following will + // work + return Type.namedClass(valueAsFunction.constructor.name); + } + + /** + * Returns the type of a named class. + * + * @param name - the name of the class, or `null` to represent "any object". + * @param typeGuard - (optional) for certain types, such as Typescript interfaces, runtime validation is + * not possible. In such a case, use a type guard to check if the value satisfies the type condition. + * @returns the type + */ + public static namedClass(name: string | null, typeGuard?: (value: unknown) => boolean): Type + { + return new Type(TypeCategory.CLASS, name, typeGuard); + } + + /** + * Returns the type of a named function. + * + * @param name - (optional) the name of the function. `name` represents "any named function". + * @returns the type + */ + public static namedFunction(name: string | null): Type + { + return new Type(TypeCategory.FUNCTION, name); + } + + /** + * Returns the type of an `undefined`, `null`, `boolean`, `number`, `bigint`, `string` or `symbol` + * value. + * + * @param value - a value + * @returns `null` if the value is not a primitive value + */ + public static getPrimitive(value: unknown) + { + if (value === undefined) + return Type.UNDEFINED; + if (value === null) + return Type.NULL; + switch (typeof (value)) + { + case "boolean" : + return Type.BOOLEAN; + case "number" : + return Type.NUMBER; + case "bigint": + return Type.BIGINT; + case "string": + return Type.STRING; + case "symbol": + return Type.SYMBOL; + } + return null; + } + + /** + * Creates a new Type. + * + * @param category - the category of the type + * @param name - (optional) the name of the function or class. `null` represents any instance of the type. + * @param typeGuard - (optional) for certain types, such as Typescript interfaces, runtime validation is + * not possible. In such a case, use a type guard to check if the value satisfies the type condition. + * @throws RangeError if neither `type` nor `name` are set. + * If `type` does not have a name (e.g. "number" or "array") but `name` is set. + */ + private constructor(category: TypeCategory, name: string | null = null, + typeGuard?: (value: unknown) => boolean) + { + if (!Object.values(TypeCategory).includes(category)) + { + throw new RangeError(`category must be an instance of TypeCategory. +Actual: ${Type.of(category).toString()}`); + } + this.category = category; + this.name = name; + this.typeGuard = typeGuard; + } + + /** + * @returns `true` if the type is an `undefined`, `null`, `boolean`, `number`, `bigint`, `string` or + * `symbol` value + */ + public isPrimitive() + { + switch (this.category) + { + case TypeCategory.UNDEFINED: + case TypeCategory.NULL: + case TypeCategory.BOOLEAN: + case TypeCategory.NUMBER: + case TypeCategory.BIGINT: + case TypeCategory.STRING: + case TypeCategory.SYMBOL: + return true; + default: + return false; + } + } + + /** + * Indicates if this type is equal to another type. + * + * @param other - another type + * @returns true if this type matches `other` + */ + public equals(other: Type): boolean + { + return other.category === this.category && + (other.name === this.name || this.name === null || other.name === null); + } + + /** + * Returns the type of this type. + * + * @returns the type of this type + */ + public getTypeOf(): Type + { + switch (this.category) + { + case TypeCategory.UNDEFINED: + case TypeCategory.NULL: + case TypeCategory.BOOLEAN: + case TypeCategory.NUMBER: + case TypeCategory.BIGINT: + case TypeCategory.STRING: + case TypeCategory.SYMBOL: + case TypeCategory.ARRAY: + case TypeCategory.FUNCTION: + return this; + case TypeCategory.CLASS: + return Type.namedClass(this.name); + } + } + + /** + * Indicates whether this type is a subtype of another type. Note that types are considered subtypes of + * themselves. + * + * @param parent - the parent type + * @returns + *

    + *
  • `true` if `child` extends `parent`
  • + *
  • `false` if `parent` or `child` are `undefined`, `null` or an object
  • + *
  • `false` if `child` does not extend `parent`
  • + *
+ */ + public isSubtypeOf(parent: Type): boolean + { + // To convert a type to an object, use `prototype` such as `Error.prototype`. + // To convert an object to a type, use `constructor` such as `instance.constructor`. + switch (this.category) + { + case TypeCategory.UNDEFINED: + case TypeCategory.NULL: + return false; + case TypeCategory.BOOLEAN: + case TypeCategory.NUMBER: + case TypeCategory.BIGINT: + case TypeCategory.STRING: + case TypeCategory.SYMBOL: + case TypeCategory.ARRAY: + case TypeCategory.FUNCTION: + return this.equals(parent); + case TypeCategory.CLASS: + { + if (parent === undefined || parent === null) + return false; + if (parent.name === null) + { + // null represents any class + return true; + } + // There is no way to provide type-casting for a dynamic lookup of an unknown type + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const parentClass = (globalThis as unknown as any)[parent.name]; + if (this.name == null) + { + // null represents any class + return true; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const childClass = (globalThis as unknown as any)[this.name]; + // https://stackoverflow.com/a/14486171/14731 + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + return childClass.prototype instanceof parentClass; + } + } + } + + /** + * @returns the string representation of this object + */ + public toString(): string + { + switch (this.category) + { + case TypeCategory.UNDEFINED: + return "undefined"; + case TypeCategory.NULL: + return "null"; + case TypeCategory.BOOLEAN: + return "a boolean"; + case TypeCategory.NUMBER: + return "a number"; + case TypeCategory.BIGINT: + return "a bigint"; + case TypeCategory.STRING: + return "a string"; + case TypeCategory.SYMBOL: + return "a symbol"; + case TypeCategory.ARRAY: + return "an array"; + case TypeCategory.FUNCTION: + { + if (this.name === null) + return "a function"; + return `a function named ${this.name}`; + } + case TypeCategory.CLASS: + { + if (this.name === null) + return "an object"; + if (STARTS_WITH_VOWEL_REGEX.test(this.name)) + return `an ${this.name}`; + return `a ${this.name}`; + } + } + } +} + +export { + Type, + TypeCategory +}; \ No newline at end of file diff --git a/src/ValidationFailure.mts b/src/ValidationFailure.mts index c930e03..462277e 100644 --- a/src/ValidationFailure.mts +++ b/src/ValidationFailure.mts @@ -1,190 +1,41 @@ -import { - ContextLine, - Objects, - Configuration -} from "./internal/internal.mjs"; - /** * A failed validation. */ -class ValidationFailure +interface ValidationFailure { - private readonly config: Configuration; - private readonly exceptionType: new (message: string) => Error; - private readonly message: string; - private messageWithContext: string | null = null; - private readonly context: ContextLine[] = []; - - /** - * Creates a new validation failure. - * - * @param configuration - the instance configuration - * @param exceptionType - the type of exception associated with the failure - * @param message - the message associated with the failure - * @throws TypeError if exceptionType is not a Function or message - * is not a string - * @throws RangeError if message is empty - */ - constructor(configuration: Configuration, exceptionType: new (message: string) => Error, message: string) - { - Objects.assertThatInstanceOf(configuration, "configuration", Configuration); - Objects.requireThatInstanceOf(exceptionType, "exceptionType", Function); - Objects.requireThatStringIsNotEmpty(message, "message"); - - this.config = configuration; - this.exceptionType = exceptionType; - this.message = message; - } - - /** - * Returns the message associated with the failure. - * - * @returns the message associated with the failure - */ - getMessage() - { - if (this.messageWithContext === null) - this.messageWithContext = this.createMessageWithContext(); - return this.messageWithContext; - } - - /** - * Pads a string with space on the right to reach the desired length. - * - * @param text - a string - * @param length - the maximum length of the string - * @returns the result - */ - private static justifyLeft(text: string, length: number) - { - // See http://stackoverflow.com/a/36247412/14731 - const needed = length - text.length; - if (needed === 0) - return text; - return text + " ".repeat(needed); - } - - /** - * Returns the failure message with contextual information. - * - * @returns the failure message with contextual information - */ - private createMessageWithContext() - { - const mergedContext = this.mergeContext(); - - let maxKeyLength = 0; - for (const entry of mergedContext) - { - if (entry.key === "") - continue; - const keyLength = entry.key.length; - if (keyLength > maxKeyLength) - maxKeyLength = keyLength; - } - - const contextToAdd = [this.message]; - for (const entry of mergedContext) - { - const key = entry.key; - const value = entry.value; - if (key === "") - contextToAdd.push(this.config.convertToString(value)); - else - { - contextToAdd.push(ValidationFailure.justifyLeft(key, maxKeyLength) + ": " + - this.config.convertToString(value)); - } - } - return contextToAdd.join("\n"); - } - /** - * Merges the failure context from the ValidationFailure and Configuration object, - * where the former may override values set by the latter. + * Returns the message corresponding to the validation failure. * - * @returns the merged failure context + * @returns the message corresponding to the validation failure */ - private mergeContext() - { - const mergedContext: ContextLine[] = []; - const existingKeys = new Set(); - for (const entry of this.context) - { - Objects.requireThatInstanceOf(entry, "entry", ContextLine); - mergedContext.push(entry); - if (entry.key !== "") - existingKeys.add(entry.key); - } - - for (const entry of this.config.getContext()) - { - const key = entry[0]; - if (!existingKeys.has(key)) - { - existingKeys.add(key); - const value = entry[1]; - mergedContext.push(new ContextLine(this.config, key, value)); - } - } - return mergedContext; - } + getMessage(): string; - /** - * Adds contextual information associated with the failure. - * - * @param name - the name of a variable - * @param value - the value of the variable - * @returns this - * @throws TypeError if name is null - */ - addContext(name: string, value: unknown) - { - Objects.requireThatStringIsNotEmpty(name, "name"); - this.context.push(new ContextLine(this.config, name, value)); - this.messageWithContext = null; - return this; - } /** - * Adds contextual information to append to the exception message. + * Returns the type of error that is associated with this failure. * - * @param context - the list of name-value pairs to append to the exception message - * @returns this - * @throws TypeError if context is not an Array - * @see ConsumerToContext + * @returns the type of error that is associated with this failure */ - addContextList(context: ContextLine[]) - { - Objects.requireThatTypeOf(context, "context", "array"); - this.context.push(...context); - this.messageWithContext = null; - return this; - } + getType(): string; /** - * Creates an exception containing the failure message. + * Returns the error corresponding to the validation failure. * - * @typeParam E - the type of the exception - * @returns the exception corresponding to the validation failure + * @returns the error corresponding to the validation failure */ - createException() - { - // eslint-disable-next-line new-cap - return new this.exceptionType(this.getMessage()) as E; - } + getError(): Error; +} - /** - * Returns the String representation of the failure. - * - * @returns the String representation of the failure - */ - toString() - { - return "exception: " + this.exceptionType.name + "\n" + - "message: " + this.message + "\n" + - "context: " + this.config.convertToString(this.context); - } +/** + * @param value - a value + * @returns true if the value is an instance of `ValidationFailure` + */ +function isValidationFailure(value: unknown): value is ValidationFailure +{ + const validationFailure = value as ValidationFailure; + return validationFailure.getMessage !== undefined && + validationFailure.getType !== undefined && + validationFailure.getError !== undefined; } -export {ValidationFailure}; \ No newline at end of file +export {type ValidationFailure, isValidationFailure}; \ No newline at end of file diff --git a/src/ValidationFailures.mts b/src/ValidationFailures.mts new file mode 100644 index 0000000..672f632 --- /dev/null +++ b/src/ValidationFailures.mts @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2024 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +import { + requireThatInstanceOf, + MultipleFailuresError, + type ValidationFailure, + requireThatTypeCategory, + TypeCategory, + isValidationFailure +} from "./internal/internal.mjs"; + +/** + * A collection of validation failures. + */ +class ValidationFailures +{ + /** + * A collection that does not contain any failures. + */ + public static readonly EMPTY = new ValidationFailures([]); + private readonly failures: ValidationFailure[]; + + /** + * Creates a new instance. + * + * @param failures - the validation failures + * @throws TypeError if `failures` is `undefined` or `null` + */ + public constructor(failures: ValidationFailure[]) + { + requireThatTypeCategory(failures, "failures", TypeCategory.ARRAY, + v => Array.isArray(v) && v.every(element => isValidationFailure(element))); + this.failures = [...failures]; + } + + /** + * Checks if any validation has failed. + * + * @returns `false` if at least one validation has failed + */ + public isEmpty() + { + return this.failures.length === 0; + } + + /** + * Returns the list of failed validations. + * + * @returns an unmodifiable list of failed validations + */ + public getFailures() + { + return [...this.failures]; + } + + /** + * Throws an error if a validation failed; otherwise, returns `true`. + * + * @returns true if the validation passed + * @throws RangeError if a method precondition was violated + * @throws AssertionError if a class invariant or method postcondition was violated + * @throws MultipleFailuresError if more than one validation failed. This error contains a list of the + * failures + */ + public throwOnFailure() + { + if (this.failures.length === 0) + return true; + if (this.failures.length === 1) + { + const failure = this.failures[0]; + throw failure.getError(); + } + throw new MultipleFailuresError(this.failures); + } + + /** + * Returns the validation failure messages. + * + * @returns an empty list if the validation was successful + */ + public getMessages() + { + const messages = []; + for (const failure of this.failures) + messages.push(failure.getMessage()); + return messages; + } + + /** + * Returns the error for the validation failures, if any. + * + *
    + *
  1. Returns `null` if no validation has failed.
  2. + *
  3. Returns `MultipleFailuresError` if there were multiple failures.
  4. + *
  5. Returns `Throwable` if there was one failure.
  6. + *
+ * + * @returns the error or `null` if no validation has failed + */ + public getError() + { + if (this.failures.length === 0) + return null; + if (this.failures.length === 1) + { + const failure = this.failures[0]; + return failure.getError(); + } + return new MultipleFailuresError(this.failures); + } + + /** + * Adds validation failures into this collection. + * + * @param failures - the failures to add + * @returns this + * @throws TypeError if `failures` is `undefined` or `null` + */ + public addAll(failures: ValidationFailures) + { + requireThatInstanceOf(failures, "failures", ValidationFailures); + this.failures.push(...failures.failures); + return this; + } +} + +export {ValidationFailures}; \ No newline at end of file diff --git a/src/Validators.mts b/src/Validators.mts new file mode 100644 index 0000000..e75ef16 --- /dev/null +++ b/src/Validators.mts @@ -0,0 +1,100 @@ +import type { + ValidatorComponent, + GlobalConfiguration +} from "./internal/internal.mjs"; + +const typedocWorkaround: null | ValidatorComponent = null; +// noinspection PointlessBooleanExpressionJS +if (typedocWorkaround !== null) + console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); + +/** + * A factory that creates different types of validators. + *

+ * There are three kinds of validators: + *

    + *
  • `requireThat()` for method preconditions.
  • + *
  • `assertThat()` for class invariants, and method postconditions.
  • + *
  • `checkIf()` for returning multiple validation failures.
  • + *
+ * + * @typeParam S - the type of the validator factory + */ +interface Validators +{ + /** + * Returns a new factory instance with an independent configuration. This method is commonly used to inherit + * and update contextual information from the original factory before passing it into a nested operation. + * For example, + * + * ```console + * JavascriptValidators copy = validators.copy(); + * copy.getContext().put(json.toString(), "json"); + * nestedOperation(copy); + * ``` + * + * @returns a copy of this factory + */ + copy(): S; + + /** + * Returns the contextual information inherited by validators created out by this factory. The contextual + * information is a map of key-value pairs that can provide more details about validation failures. For + * example, if the message is "Password may not be empty" and the map contains the key-value pair + * `{"username": "john.smith"}`, the error message would be: + * + * ```console + * Password may not be empty + * username: john.smith + * ``` + * + * @returns an unmodifiable map from each entry's name to its value + */ + getContext(): Map; + + /** + * Sets the contextual information for validators created by this factory. + *

+ * This method adds contextual information to error messages. The contextual information is stored as + * key-value pairs in a map. Values set by this method may be overridden by + * {@link ValidatorComponent.withContext}. + * + * @param value - the value of the entry + * @param name - the name of an entry + * @returns this + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if: + *

    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
+ */ + withContext(value: unknown, name: string): S; + + /** + * Removes the contextual information of validators created by this factory. + * + * @param name - the parameter name + * @returns this + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name`: + *
    + *
  • contains whitespace
  • + *
  • is empty
  • + *
+ */ + removeContext(name: string): S; + + /** + * Returns the global configuration shared by all validators. + *

+ * NOTE: Updating this configuration affects existing and new validators. + * + * @returns the global configuration updater + */ + getGlobalConfiguration(): GlobalConfiguration; +} + +export type {Validators}; \ No newline at end of file diff --git a/src/extension/ExtensibleNumberValidator.mts b/src/extension/ExtensibleNumberValidator.mts deleted file mode 100644 index 80e7c47..0000000 --- a/src/extension/ExtensibleNumberValidator.mts +++ /dev/null @@ -1,138 +0,0 @@ -import type {ExtensibleObjectValidator} from "../internal/internal.mjs"; - -/** - * Validates the requirements of a number. - *

- * All methods (except those found in {@link ObjectValidator}) assume that the actual value is not null. - * - * @typeParam S - the type of validator returned by the methods - */ -interface ExtensibleNumberValidator extends ExtensibleObjectValidator -{ - /** - * Ensures that the actual value is negative. - * - * @returns the updated validator - */ - isNegative(): S; - - /** - * Ensures that the actual value is not negative. - * - * @returns the updated validator - */ - isNotNegative(): S; - - /** - * Ensures that the actual value is zero. - * - * @returns the updated validator - */ - isZero(): S; - - /** - * Ensures that the actual value is not zero. - * - * @returns the updated validator - */ - isNotZero(): S; - - /** - * Ensures that the actual value is positive. - * - * @returns the updated validator - */ - isPositive(): S; - - /** - * Ensures that the actual value is not positive. - * - * @returns the updated validator - */ - isNotPositive(): S; - - /** - * Ensures that the actual value is greater than a value. - * - * @param value - the lower bound - * @param name - (optional) the name of the lower bound - * @returns the updated validator - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - isGreaterThan(value: number, name?: string): S; - - /** - * Ensures that the actual value is greater than or equal to a value. - * - * @param value - the minimum value - * @param name - the name of the minimum value - * @returns the updated validator - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - isGreaterThanOrEqualTo(value: number, name?: string): S; - - /** - * Ensures that the actual value is less than a value. - * - * @param value - the upper bound - * @param name - (optional) the name of the upper bound - * @returns the updated validator - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - isLessThan(value: number, name?: string): S; - - /** - * Ensures that the actual value is less or equal to a value. - * - * @param value - the maximum value - * @param name - (optional) the name of the maximum value - * @returns the updated validator - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - isLessThanOrEqualTo(value: number, name?: string): S; - - /** - * Ensures that the actual value is within range. - * - * @param startInclusive - the minimum value (inclusive) - * @param endExclusive - the maximum value (exclusive) - * @returns the updated validator - * @throws TypeError if any of the arguments are null or not a number - */ - isBetween(startInclusive: number, endExclusive: number): S; - - /** - * Ensures that the actual value is within range. - * - * @param startInclusive - the minimum value (inclusive) - * @param endInclusive - the maximum value (inclusive) - * @returns the updated validator - * @throws TypeError if any of the arguments are null or not a number - */ - isBetweenClosed(startInclusive: number, endInclusive: number): S; - - /** - * Ensures that the actual value is a finite number. - * - * @returns the updated validator - */ - isFinite(): S; - - /** - * Ensures that the actual value is not a finite number. - * - * @returns the updated validator - */ - isInfinite(): S; - - /** - * {@inheritDoc} - */ - getActual(): number | undefined; -} - -export {type ExtensibleNumberValidator}; \ No newline at end of file diff --git a/src/extension/ExtensibleNumberVerifier.mts b/src/extension/ExtensibleNumberVerifier.mts deleted file mode 100644 index 0f1edd5..0000000 --- a/src/extension/ExtensibleNumberVerifier.mts +++ /dev/null @@ -1,154 +0,0 @@ -import type {ExtensibleObjectVerifier} from "../internal/internal.mjs"; - -/** - * Verifies the requirements of a number. - *

- * All methods (except those found in {@link ObjectVerifier}) assume that the actual value is not null. - * - * @typeParam S - the type of validator returned by the methods - */ -interface ExtensibleNumberVerifier extends ExtensibleObjectVerifier -{ - /** - * Ensures that the actual value is negative. - * - * @returns the updated verifier - * @throws RangeError if the actual value is not negative - */ - isNegative(): S; - - /** - * Ensures that the actual value is not negative. - * - * @returns the updated verifier - * @throws RangeError if the actual value is negative - */ - isNotNegative(): S; - - /** - * Ensures that the actual value is zero. - * - * @returns the updated verifier - * @throws RangeError if the actual value is not zero - */ - isZero(): S; - - /** - * Ensures that the actual value is not zero. - * - * @returns the updated verifier - * @throws RangeError if the actual value is zero - */ - isNotZero(): S; - - /** - * Ensures that the actual value is positive. - * - * @returns the updated verifier - * @throws RangeError if the actual value is not positive - */ - isPositive(): S; - - /** - * Ensures that the actual value is not positive. - * - * @returns the updated verifier - * @throws RangeError if the actual value is positive - */ - isNotPositive(): S; - - /** - * Ensures that the actual value is greater than a value. - * - * @param value - the lower bound - * @param name - the name of the lower bound - * @returns the updated verifier - * @throws TypeError if value or name are null - * @throws RangeError if the actual value is less than or equal to value. - * If name is empty. - */ - isGreaterThan(value: number, name?: string): S; - - /** - * Ensures that the actual value is greater than or equal to a value. - * - * @param value - the minimum value - * @param name - (optional) the name of the minimum value - * @returns the updated verifier - * @throws TypeError if value or name are null - * @throws RangeError if the actual value is less than value. - * If name is empty. - */ - isGreaterThanOrEqualTo(value: number, name?: string): S; - - /** - * Ensures that the actual value is less than a value. - * - * @param value - the upper bound - * @param name - (optional) the name of the upper bound - * @returns the updated verifier - * @throws TypeError if value or name are null - * @throws RangeError if the actual value is greater than or equal to value. - * If name is empty. - */ - isLessThan(value: number, name?: string): S; - - /** - * Ensures that the actual value is less or equal to a value. - * - * @param value - the maximum value - * @param name - the name of the maximum value - * @returns the updated verifier - * @throws TypeError if value or name are null - * @throws RangeError if the actual value is greater than value. - * If name is empty. - */ - isLessThanOrEqualTo(value: number, name?: string): S; - - /** - * Ensures that the actual value is within range. - * - * @param startInclusive - the minimum value (inclusive) - * @param endExclusive - the maximum value (exclusive) - * @returns the updated verifier - * @throws TypeError if any of the arguments are null or not a number - * @throws RangeError if endExclusive is less than startInclusive. - * If the actual value is not in range. - */ - isBetween(startInclusive: number, endExclusive: number): S; - - /** - * Ensures that the actual value is within range. - * - * @param startInclusive - the minimum value (inclusive) - * @param endInclusive - the maximum value (inclusive) - * @returns the updated verifier - * @throws TypeError if any of the arguments are null or not a number - * @throws RangeError if endInclusive is less than startInclusive. - * If the actual value is not in range. - */ - isBetweenClosed(startInclusive: number, endInclusive: number): S; - - /** - * Ensures that the actual value is a finite number. - * - * @returns the updated verifier - * @throws RangeError if actual value is not a finite number - */ - isFinite(): S; - - /** - * Ensures that the actual value is not a finite number. - * - * @returns the updated verifier - * @throws RangeError if actual value is a finite number - */ - isInfinite(): S; - - /** - * {@inheritDoc} - */ - getActual(): number; -} - -export {type ExtensibleNumberVerifier}; \ No newline at end of file diff --git a/src/extension/ExtensibleObjectValidator.mts b/src/extension/ExtensibleObjectValidator.mts deleted file mode 100644 index 6a11ed6..0000000 --- a/src/extension/ExtensibleObjectValidator.mts +++ /dev/null @@ -1,197 +0,0 @@ -import type { - BooleanValidator, - NumberValidator, - StringValidator, - InetAddressValidator, - ClassConstructor, - ClassValidator, - ArrayValidator, - SetValidator, - MapKey, - MapValue, - MapValidator, - ObjectValidator, - ValidationFailure -} from "../internal/internal.mjs"; - -/** - * Validates the requirements of an object. - * - * @typeParam S - the type of validator returned by the methods - * @typeParam T - the type the actual value - */ -interface ExtensibleObjectValidator -{ - /** - * Ensures that the actual value is null. - * - * @returns the updated validator - */ - isNull(): ObjectValidator; - - /** - * Ensures that the actual value is not null. - * - * @returns the updated validator - */ - isNotNull(): ObjectValidator>; - - /** - * Ensures that the actual value is defined. - * - * @returns the updated validator - */ - isDefined>(): ObjectValidator; - - /** - * Ensures that the actual value is undefined. - * - * @returns the updated validator - */ - isUndefined(): ObjectValidator; - - /** - * Ensures that the actual value is not undefined or null. - * - * @returns the updated validator - */ - isDefinedAndNotNull>(): ObjectValidator; - - /** - * Ensures that the actual value is not undefined or null. - * - * @returns the updated validator - */ - isUndefinedOrNull(): ObjectValidator; - - /** - * Ensures that the actual value is a boolean. - * - * @returns a validator for the boolean - */ - isBoolean(): BooleanValidator; - - /** - * Ensures that the actual value is a number. - * - * @returns a validator for the number - */ - isNumber(): NumberValidator; - - /** - * Ensures that the actual value is a string. - * - * @returns a validator for the string - */ - isString(): StringValidator; - - /** - * Ensures that the actual value is an internet address. - * - * @returns a validator for the internet address - */ - isInetAddress(): InetAddressValidator; - - /** - * Ensures that the actual value is a class constructor. - * - * @typeParam T2 - the type of the class - * @param type - the type of class to check for - * @returns a validator for the class - */ - isClass(type: ClassConstructor): ClassValidator; - - /** - * Ensures that the actual value is an array. - * - * @typeParam E - the type of elements in the array - * @returns a validator for the Array - */ - isArray(): ArrayValidator; - - /** - * Ensures that the actual value is a set. - * - * @typeParam E - the type of elements in the set - * @returns a validator for the Set - */ - isSet(): SetValidator; - - /** - * Ensures that the actual value is a map. - * - * @typeParam K - the type of keys in the map - * @typeParam K - the type of values in the map - * @returns a validator for the Map - */ - isMap, V = MapValue>(): MapValidator; - - /** - * Ensures that the actual value is a primitive type (string, - * number, bigint, boolean, null, - * undefined, or symbol). - * To check if the actual value is an object, use isInstanceOf(Object). - * - * @returns the updated validator - */ - isPrimitive(): - ObjectValidator; - - /** - * Ensures that typeof(actual) is equal to type. - * - * @param type - the expected - * typeof - * of the actual value - * @returns the updated validator - */ - isTypeOf(type: string): S; - - /** - * Ensures that the actual value is an instance of an array or class. - * - * @typeParam T2 - the type of the actual value - * @param type - the array or class constructor - * @returns the updated validator - * @throws TypeError if type is not a class - */ - isInstanceOf(type: ClassConstructor): ObjectValidator; - - /** - * Ensures that the actual value is equal to a value. - * - * @param expected - the expected value - * @param name - the name of the expected value - * @returns the updated validator - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - isEqualTo(expected: T, name?: string): S; - - /** - * Ensures that the actual value is not equal to a value. - * - * @param value - the value to compare to - * @param name - the name of the expected value - * @returns the updated validator - * @throws TypeError if name is null - * @throws RangeError if name is empty - */ - isNotEqualTo(value: T, name?: string): S; - - /** - * Returns the actual value. - * - * @returns undefined if the validation failed - */ - getActual(): T | undefined; - - /** - * Returns the list of failed validations. Modifying the returned list results in an undefined behavior. - * - * @returns the list of failed validations - */ - getFailures(): ValidationFailure[]; -} - -export {type ExtensibleObjectValidator}; \ No newline at end of file diff --git a/src/extension/ExtensibleObjectVerifier.mts b/src/extension/ExtensibleObjectVerifier.mts deleted file mode 100644 index ae63cd1..0000000 --- a/src/extension/ExtensibleObjectVerifier.mts +++ /dev/null @@ -1,220 +0,0 @@ -import type { - ClassConstructor, - ClassVerifier, - ArrayVerifier, - SetVerifier, - MapKey, - MapValue, - MapVerifier, - ObjectVerifier, - BooleanVerifier, - NumberVerifier, - StringVerifier, - InetAddressVerifier, - ElementOf -} from "../internal/internal.mjs"; - -/** - * Verifies the requirements of an object. - * - * @typeParam S - the type of verifier returned by the methods - * @typeParam T - the type the actual value - */ -interface ExtensibleObjectVerifier -{ - /** - * Ensures that the actual value is null. - * - * @returns the updated verifier - * @throws TypeError if the actual value is not null - */ - isNull(): ObjectVerifier; - - /** - * Ensures that the actual value is not null. - * - * @returns the updated verifier - * @throws TypeError if the actual value is null - */ - isNotNull(): ObjectVerifier>; - - /** - * Ensures that the actual value is defined. - * - * @returns the updated verifier - * @throws TypeError if the actual value is undefined - */ - isDefined>(): ObjectVerifier; - - /** - * Ensures that the actual value is undefined. - * - * @returns the updated verifier - * @throws TypeError if the actual value is not undefined - */ - isUndefined(): ObjectVerifier; - - /** - * Ensures that the actual value is not undefined or null. - * - * @returns the updated verifier - * @throws TypeError if the value is undefined or null - */ - isDefinedAndNotNull>(): ObjectVerifier; - - /** - * Ensures that the actual value is not undefined or null. - * - * @returns the updated verifier - * @throws TypeError if the value is not undefined or null - */ - isUndefinedOrNull(): ObjectVerifier; - - /** - * Ensures that the actual value is a boolean. - * - * @returns a verifier for the boolean - * @throws TypeError if the actual value is not a boolean - */ - isBoolean(): BooleanVerifier; - - /** - * Ensures that the actual value is a number. - * - * @returns a verifier for the number - * @throws TypeError if the actual value is not a number - */ - isNumber(): NumberVerifier; - - /** - * Ensures that the actual value is a string. - * - * @returns a verifier for the string - * @throws TypeError if the actual value is not a string - */ - isString(): StringVerifier; - - /** - * Ensures that the actual value is an internet address. - * - * @returns a verifier for the internet address - * @throws TypeError if the actual value is not an internet address - */ - isInetAddress(): InetAddressVerifier; - - /** - * Ensures that the actual value is class-like. - * - * @typeParam T2 - the type of the class - * @param type - the type of class to check for - * @returns a verifier for the object's class representation - * @throws TypeError if the actual value is not a class constructor - */ - isClass(type: ClassConstructor): ClassVerifier; - - /** - * Ensures that the actual value is an array. - * - * @typeParam E - the type of elements in the array - * @returns a verifier for the Array - * @throws TypeError if the actual value is not an Array - */ - isArray>(): ArrayVerifier; - - /** - * Ensures that the actual value is a set. - * - * @typeParam E - the type of elements in the set - * @returns a verifier for the Set - * @throws TypeError if the actual value is not a Set - */ - isSet>(): SetVerifier; - - /** - * Ensures that the actual value is a map. - * - * @typeParam K - the type of keys in the map - * @typeParam K - the type of values in the map - * @returns a verifier for the Map - * @throws TypeError if the actual value is not a Map - */ - isMap, V = MapValue>(): MapVerifier; - - /** - * Ensures that the actual value is a primitive type (string, - * number, bigint, boolean, null, - * undefined, or symbol). - * To check if the actual value is an object, use isInstanceOf(Object). - * - * @returns the updated verifier - * @throws TypeError if the actual value is not a primitive type - */ - isPrimitive(): - ObjectVerifier; - - /** - * Ensures that typeof(actual) is equal to type. - * - * @param type - the expected - * typeof - * of the actual value - * @returns the updated verifier - * @throws TypeError if type is not a string or if the actual value does not have the specified type - */ - isTypeOf(type: string): S; - - /** - * Ensures that the actual value is an instance of a class. - * - * @typeParam T2 - the type of the actual value - * @param type - a class constructor - * @returns the updated validator - * @throws TypeError if type is not a class, or if the actual value is not an instance of the specified - * class. - */ - isInstanceOf(type: ClassConstructor): ObjectVerifier; - - /** - * Ensures that the actual value is equal to a value. - * - * @param expected - the expected value - * @param name - the name of the expected value - * @returns the updated verifier - * @throws TypeError if name is null - * @throws RangeError if name is empty. - * If the actual value is not equal to expected. - */ - isEqualTo(expected: T, name?: string): S; - - /** - * Ensures that the actual value is not equal to a value. - * - * @param value - the value to compare to - * @param name - (optional) the name of the expected value - * @returns the updated verifier - * @throws TypeError if name is null - * @throws RangeError if name is empty. - * If the actual value is equal to value. - */ - isNotEqualTo(value: T, name?: string): S; - - /** - * Returns the actual value. - * - * @returns the actual value - */ - getActual(): T; - - /** - * Throws an exception if the validation failed. - * - * @typeParam T2 - the type of the updated verifier - * @param result - (optional) a no-arg function that returns the value to return on success. - * By default, this function returns "this". - * @returns the updated verifier - * @throws Error if the validation failed - */ - validationResult(result: () => T2): T2; -} - -export {type ExtensibleObjectVerifier}; \ No newline at end of file diff --git a/src/index.mts b/src/index.mts index 57ab880..262c84a 100644 --- a/src/index.mts +++ b/src/index.mts @@ -1,13 +1,31 @@ -export -{ +export { requireThat, assertThat, - assertThatAndReturn, - validateThat -} from "./DefaultRequirements.mjs"; -export { - Requirements, - GlobalRequirements, + checkIf, + updateConfiguration, + getContext, + withContext, + removeContext, Configuration, - TerminalEncoding -} from "./internal/internal.mjs"; \ No newline at end of file + TerminalEncoding, + JavascriptValidators, + Type, + TypeCategory, + AssertionError, + type SetValidator, + type StringValidator, + type MapValidator, + type NumberValidator, + type BooleanValidator, + type ObjectValidator, + type ElementOf, + type MapKey, + type MapValue, + type ClassConstructor, + type ArrayValidator, + type ConfigurationUpdater, + type Validators, + type UnsignedNumberValidator, + type StringMapper +} from "./internal/internal.mjs"; +export type {GlobalConfiguration} from "./internal/internal.mjs"; diff --git a/src/internal/AbstractGlobalConfiguration.mts b/src/internal/AbstractGlobalConfiguration.mts deleted file mode 100644 index 078d98f..0000000 --- a/src/internal/AbstractGlobalConfiguration.mts +++ /dev/null @@ -1,73 +0,0 @@ -import type { - GlobalConfiguration, - TerminalEncoding -} from "./internal.mjs"; - -abstract class AbstractGlobalConfiguration implements GlobalConfiguration -{ - abstract getTerminalEncoding(): TerminalEncoding; - - abstract getTerminalWidth(): number; - - abstract listTerminalEncodings(): TerminalEncoding[]; - - abstract withDefaultTerminalEncoding(): GlobalConfiguration; - - abstract withDefaultTerminalWidth(): GlobalConfiguration; - - abstract withTerminalEncoding(encoding: TerminalEncoding): GlobalConfiguration; - - abstract withTerminalWidth(width: number): GlobalConfiguration; - - protected assertionsEnabled: boolean; - protected diffEnabled: boolean; - - /** - * Creates a new global configuration. - * - * @param assertionsEnabled - true if assertThat() should invoke requireThat() - * @param diffEnabled - true if exceptions should show the difference between the actual and expected - * values - */ - protected constructor(assertionsEnabled: boolean, diffEnabled: boolean) - { - this.assertionsEnabled = assertionsEnabled; - this.diffEnabled = diffEnabled; - } - - assertionsAreEnabled(): boolean - { - return this.assertionsEnabled; - } - - withAssertionsEnabled(): this - { - this.assertionsEnabled = true; - return this; - } - - withAssertionsDisabled(): this - { - this.assertionsEnabled = false; - return this; - } - - isDiffEnabled(): boolean - { - return this.diffEnabled; - } - - withDiff(): this - { - this.diffEnabled = true; - return this; - } - - withoutDiff(): this - { - this.diffEnabled = false; - return this; - } -} - -export {AbstractGlobalConfiguration}; \ No newline at end of file diff --git a/src/internal/ArrayValidatorImpl.mts b/src/internal/ArrayValidatorImpl.mts deleted file mode 100644 index 11548d6..0000000 --- a/src/internal/ArrayValidatorImpl.mts +++ /dev/null @@ -1,397 +0,0 @@ -import isEqual from "lodash/isEqual.js"; -import type { - ArrayValidator, - Configuration, - NumberValidator, - Pluralizer -} from "./internal.mjs"; -import { - AbstractObjectValidator, - Objects, - SizeValidatorImpl, - ValidationFailure -} from "./internal.mjs"; - -/** - * Default implementation of ArrayValidator. - * - * @typeParam T - the type the actual value - */ -class ArrayValidatorImpl extends AbstractObjectValidator, E[]> - implements ArrayValidator -{ - private readonly pluralizer: Pluralizer; - - /** - * Creates a new ArrayValidatorImpl. - * - * @param configuration - the instance configuration - * @param actual - the actual value - * @param name - (optional) the name of the value - * @param pluralizer - the plural form of the array elements - * @param failures - the list of validation failures - * @throws TypeError if configuration or name are null or undefined - * @throws RangeError if name is empty - */ - constructor(configuration: Configuration, actual: E[] | undefined, name: string, pluralizer: Pluralizer, - failures: ValidationFailure[]) - { - super(configuration, actual, name, failures); - this.pluralizer = pluralizer; - } - - isEmpty() - { - if (this.actual === undefined || this.actual.length > 0) - { - const failure = new ValidationFailure(this.config, RangeError, this.name + " must be empty"). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this; - } - - isNotEmpty() - { - if (this.actual === undefined || this.actual.length === 0) - { - const failure = new ValidationFailure(this.config, RangeError, this.name + " may not be empty."); - this.failures.push(failure); - } - return this; - } - - /** - * Indicates if an array contains at least one element of another array. - * - * @param array - an array of arrays - * @param element - an element - * @returns true if arrays contains the element - */ - private arrayContainsElement(array: E[], element: E): boolean - { - // indexOf(), includes() do not work for multidimensional arrays: http://stackoverflow.com/a/24943461/14731 - for (let i = 0; i < array.length; ++i) - { - if (isEqual(array[i], element)) - return true; - } - return false; - } - - /** - * @param array - an array - * @param expected - an array of expected values - * @returns true if actual contains any of the expected elements - */ - private arrayContainsAny(array: E[], expected: E[]): boolean - { - for (const element of expected) - { - if (this.arrayContainsElement(array, element)) - return true; - } - return false; - } - - /** - * Indicates if an array contains all elements of another array. - * - * @param array - an array - * @param expected - an array of expected elements - * @returns true if actual contains all the expected elements - */ - private arrayContainsAll(array: E[], expected: E[]): boolean - { - for (const element of expected) - { - if (!this.arrayContainsElement(array, element)) - return false; - } - return true; - } - - contains(element: E, name?: string): ArrayValidator - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - - if (this.actual === undefined || !this.arrayContainsElement(this.actual, element)) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must contain " + name + "."). - addContext("Actual", this.actual). - addContext("Expected", element); - } - else - { - failure = new ValidationFailure(this.config, RangeError, this.name + " must contain " + - this.config.convertToString(element)). - addContext("Actual", this.actual); - } - this.failures.push(failure); - } - return this; - } - - containsExactly(expected: E[], name?: string): ArrayValidator - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - Objects.requireThatTypeOf(expected, "expected", "array"); - - const expectedAsSet = new Set(expected); - let missing; - let unwanted; - if (this.actual === undefined) - { - missing = undefined; - unwanted = undefined; - } - else - { - const actualAsSet = new Set(this.actual); - missing = new Set([...expectedAsSet].filter(x => !actualAsSet.has(x))); - unwanted = new Set([...actualAsSet].filter(x => !expectedAsSet.has(x))); - } - if (missing === undefined || unwanted === undefined || missing.size !== 0 || unwanted.size !== 0) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, this.name + - " must contain exactly the same elements as " + name). - addContext("Actual", this.actual). - addContext("Expected", expected). - addContext("Missing", missing). - addContext("Unwanted", unwanted); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must contain exactly: " + this.config.convertToString(expected)). - addContext("Actual", this.actual). - addContext("Missing", missing). - addContext("Unwanted", unwanted); - } - this.failures.push(failure); - } - return this; - } - - containsAny(expected: E[], name?: string): ArrayValidator - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - - Objects.requireThatTypeOf(expected, "expected", "array"); - - if (this.actual === undefined || !this.arrayContainsAny(this.actual, expected)) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must contain any element in " + name). - addContext("Actual", this.actual). - addContext("Expected", expected); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must contain any element in: " + this.config.convertToString(expected)). - addContext("Actual", this.actual); - } - this.failures.push(failure); - } - return this; - } - - containsAll(expected: E[], name?: string): ArrayValidator - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - - Objects.requireThatTypeOf(expected, "expected", "array"); - - let missing; - if (this.actual === undefined) - missing = undefined; - else - { - const expectedAsSet = new Set(expected); - const actualAsSet = new Set(this.actual); - missing = new Set([...expectedAsSet].filter(x => !actualAsSet.has(x))); - } - if (this.actual === undefined || !this.arrayContainsAll(this.actual, expected)) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must contain all elements in " + name). - addContext("Actual", this.actual). - addContext("Expected", expected). - addContext("Missing", missing); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must contain all elements in: " + this.config.convertToString(expected)). - addContext("Actual", this.actual). - addContext("Missing", missing); - } - this.failures.push(failure); - } - return this; - } - - doesNotContain(element: E, name?: string): ArrayValidator - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - - if (this.actual === undefined || this.arrayContainsElement(this.actual, element)) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " may not contain " + name + "."). - addContext("Actual", this.actual). - addContext("Unwanted", element); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " may not contain " + this.config.convertToString(element)). - addContext("Actual", this.actual); - } - this.failures.push(failure); - } - return this; - } - - doesNotContainAny(elements: E[], name?: string): ArrayValidator - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - - Objects.requireThatTypeOf(elements, "elements", "array"); - - if (this.actual === undefined || this.arrayContainsAny(this.actual, elements)) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must not contain any element in " + name). - addContext("Actual", this.actual). - addContext("Unwanted", elements); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must not contain any element in: " + this.config.convertToString(elements)). - addContext("Actual", this.actual); - } - this.failures.push(failure); - } - return this; - } - - doesNotContainAll(elements: E[], name?: string): ArrayValidator - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - - Objects.requireThatTypeOf(elements, "elements", "array"); - - if (this.actual === undefined || this.arrayContainsAll(this.actual, elements)) - { - let missing; - if (this.actual === undefined) - missing = undefined; - else - { - const elementsAsSet = new Set(elements); - const actualAsSet = new Set(this.actual); - missing = new Set([...elementsAsSet].filter(x => !actualAsSet.has(x))); - } - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " may not contain all elements in " + name). - addContext("Actual", this.actual). - addContext("Missing", missing); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " may not contain all elements in: " + this.config.convertToString(elements)). - addContext("Actual", this.actual). - addContext("Unwanted", elements). - addContext("Missing", missing); - } - this.failures.push(failure); - } - return this; - } - - doesNotContainDuplicates(): ArrayValidator - { - const unique = new Set(); - const duplicates = new Set(); - if (this.actual !== undefined) - { - for (const element of this.actual) - { - if (unique.has(element)) - duplicates.add(element); - else - unique.add(element); - } - } - if (this.actual === undefined || duplicates.size !== 0) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " may not contain duplicate elements"). - addContext("Actual", this.actual). - addContext("Duplicates", duplicates); - this.failures.push(failure); - } - return this; - } - - length(): NumberValidator - { - let value: E[] | undefined; - let length; - if (this.actual === undefined) - { - value = undefined; - length = 0; - } - else - { - value = this.actual; - length = value.length; - } - return new SizeValidatorImpl(this.config, value, this.name, length, this.name + ".length", - this.pluralizer, this.failures); - } - - lengthConsumer(consumer: (length: NumberValidator) => void): ArrayValidator - { - Objects.requireThatValueIsDefinedAndNotNull(consumer, "consumer"); - if (this.failures.length === 0) - consumer(this.length()); - return this; - } -} - -export {ArrayValidatorImpl}; \ No newline at end of file diff --git a/src/internal/ArrayVerifierImpl.mts b/src/internal/ArrayVerifierImpl.mts deleted file mode 100644 index 32219c7..0000000 --- a/src/internal/ArrayVerifierImpl.mts +++ /dev/null @@ -1,105 +0,0 @@ -import type { - ArrayValidator, - ArrayVerifier, - NumberVerifier -} from "./internal.mjs"; -import { - AbstractObjectVerifier, - NumberVerifierImpl, - Objects -} from "./internal.mjs"; - -/** - * Default implementation of ArrayVerifier. - * - * @typeParam E - the type the array elements - */ -class ArrayVerifierImpl extends AbstractObjectVerifier, ArrayValidator, E[]> - implements ArrayVerifier -{ - /** - * Creates a new ArrayVerifierImpl. - * - * @param validator - the validator to delegate to - * @throws TypeError if validator is null or undefined - */ - constructor(validator: ArrayValidator) - { - super(validator); - } - - isEmpty() - { - this.validator.isEmpty(); - return this.validationResult(() => this.getThis()); - } - - isNotEmpty() - { - this.validator.isNotEmpty(); - return this.validationResult(() => this.getThis()); - } - - contains(element: E, name?: string) - { - this.validator.contains(element, name); - return this.validationResult(() => this.getThis()); - } - - containsExactly(expected: E[], name?: string) - { - this.validator.containsExactly(expected, name); - return this.validationResult(() => this.getThis()); - } - - containsAny(expected: E[], name?: string) - { - this.validator.containsAny(expected, name); - return this.validationResult(() => this.getThis()); - } - - containsAll(expected: E[], name?: string) - { - this.validator.containsAll(expected, name); - return this.validationResult(() => this.getThis()); - } - - doesNotContain(element: E, name?: string) - { - this.validator.doesNotContain(element, name); - return this.validationResult(() => this.getThis()); - } - - doesNotContainAny(elements: E[], name?: string): ArrayVerifier - { - this.validator.doesNotContainAny(elements, name); - return this.validationResult(() => this.getThis()); - } - - doesNotContainAll(elements: E[], name?: string): ArrayVerifier - { - this.validator.doesNotContainAll(elements, name); - return this.validationResult(() => this.getThis()); - } - - doesNotContainDuplicates(): ArrayVerifier - { - this.validator.doesNotContainDuplicates(); - return this.validationResult(() => this.getThis()); - } - - length(): NumberVerifier - { - const newValidator = this.validator.length(); - return this.validationResult(() => new NumberVerifierImpl(newValidator)) as NumberVerifier; - } - - lengthConsumer(consumer: (actual: NumberVerifier) => void): ArrayVerifier - { - Objects.requireThatValueIsDefinedAndNotNull(consumer, "consumer"); - consumer(this.length()); - return this; - } -} - -export {ArrayVerifierImpl}; \ No newline at end of file diff --git a/src/internal/BooleanValidatorImpl.mts b/src/internal/BooleanValidatorImpl.mts deleted file mode 100644 index 4583fe1..0000000 --- a/src/internal/BooleanValidatorImpl.mts +++ /dev/null @@ -1,55 +0,0 @@ -import type { - BooleanValidator, - Configuration -} from "./internal.mjs"; -import { - AbstractObjectValidator, - ValidationFailure -} from "./internal.mjs"; - -/** - * Default implementation of BooleanValidator. - */ -class BooleanValidatorImpl extends AbstractObjectValidator - implements BooleanValidator -{ - /** - * Creates a new BooleanValidator. - * - * @param configuration - the instance configuration - * @param actual - the actual value - * @param name - the name of the value - * @param failures - the list of validation failures - * @throws TypeError if configuration or name are null or undefined - * @throws RangeError if name is empty - */ - constructor(configuration: Configuration, actual: boolean | undefined, name: string, - failures: ValidationFailure[]) - { - super(configuration, actual, name, failures); - } - - isTrue(): BooleanValidator - { - if (this.actual === undefined || !this.actual) - { - const failure = new ValidationFailure(this.config, RangeError, this.name + " must be true."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this; - } - - isFalse(): BooleanValidator - { - if (this.actual === undefined || this.actual) - { - const failure = new ValidationFailure(this.config, RangeError, this.name + " must be false."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this; - } -} - -export {BooleanValidatorImpl}; \ No newline at end of file diff --git a/src/internal/BooleanVerifierImpl.mts b/src/internal/BooleanVerifierImpl.mts deleted file mode 100644 index c14b419..0000000 --- a/src/internal/BooleanVerifierImpl.mts +++ /dev/null @@ -1,49 +0,0 @@ -import type { - BooleanValidator, - BooleanVerifier -} from "./internal.mjs"; -import {AbstractObjectVerifier} from "./internal.mjs"; - -/** - * Default implementation of BooleanVerifier. - */ -class BooleanVerifierImpl extends AbstractObjectVerifier - implements BooleanVerifier -{ - /** - * Creates a new BooleanVerifierImpl. - * - * @param validator - the validator to delegate to - * @throws TypeError if validator is null or undefined - */ - constructor(validator: BooleanValidator) - { - super(validator); - } - - /** - * Ensures that the actual value is true. - * - * @returns the updated verifier - * @throws RangeError if the actual value is not true - */ - isTrue(): BooleanVerifier - { - this.validator.isTrue(); - return this.validationResult(() => this.getThis()); - } - - /** - * Ensures that the actual value is false. - * - * @returns the updated verifier - * @throws RangeError if the actual value is not false - */ - isFalse(): BooleanVerifier - { - this.validator.isFalse(); - return this.validationResult(() => this.getThis()); - } -} - -export {BooleanVerifierImpl}; \ No newline at end of file diff --git a/src/internal/ClassValidatorImpl.mts b/src/internal/ClassValidatorImpl.mts deleted file mode 100644 index 1d9a427..0000000 --- a/src/internal/ClassValidatorImpl.mts +++ /dev/null @@ -1,81 +0,0 @@ -import type { - ClassValidator, - Configuration, - ClassConstructor -} from "./internal.mjs"; -import { - AbstractObjectValidator, - Objects, - ValidationFailure -} from "./internal.mjs"; - -/** - * Default implementation of ClassValidator. - */ -class ClassValidatorImpl extends AbstractObjectValidator, ClassConstructor> - implements ClassValidator -{ - /** - * Creates a new ClassValidator. - * - * @param configuration - the instance configuration - * @param actual - the actual value - * @param name - the name of the value - * @param failures - the list of validation failures - * @throws TypeError if configuration, name, isIpV4, - * isIpV6, isHostname are null or undefined - * @throws RangeError if name is empty - */ - constructor(configuration: Configuration, actual: ClassConstructor | undefined, name: string, - failures: ValidationFailure[]) - { - super(configuration, actual, name, failures); - } - - isSupertypeOf(type: ClassConstructor): ClassValidator - { - Objects.requireThatValueIsDefinedAndNotNull(type, "type"); - const typeOfType = Objects.getTypeInfo(type); - let failure; - if (typeOfType.type === "class") - { - const typeOfActual = Objects.getTypeInfo(this.actual); - if (typeOfActual.type === "class" && Objects.extends(type, this.actual as ClassConstructor)) - return this; - failure = new ValidationFailure(this.config, RangeError, - this.name + " must be a super-type of " + typeOfType.toString()). - addContext("Actual", typeOfActual); - } - else - { - failure = new ValidationFailure(this.config, TypeError, "type must be a class."). - addContext("Actual", typeOfType); - } - this.failures.push(failure); - return this; - } - - isSubtypeOf(type: ClassConstructor): ClassValidator - { - const typeInfo = Objects.getTypeInfo(type); - let failure; - if (typeInfo.type === "class") - { - const typeOfActual = Objects.getTypeInfo(this.actual); - if (typeOfActual.type === "class" && Objects.extends(this.actual as ClassConstructor, type)) - return this; - failure = new ValidationFailure(this.config, RangeError, - this.name + " must be a sub-type of " + typeInfo.toString()). - addContext("Actual", typeOfActual); - } - else - { - failure = new ValidationFailure(this.config, TypeError, "type must be a class."). - addContext("Actual", typeInfo); - } - this.failures.push(failure); - return this; - } -} - -export {ClassValidatorImpl}; \ No newline at end of file diff --git a/src/internal/ClassVerifierImpl.mts b/src/internal/ClassVerifierImpl.mts deleted file mode 100644 index 6547f15..0000000 --- a/src/internal/ClassVerifierImpl.mts +++ /dev/null @@ -1,39 +0,0 @@ -import type { - ClassValidator, - ClassVerifier, - ClassConstructor -} from "./internal.mjs"; -import {AbstractObjectVerifier} from "./internal.mjs"; - -/** - * Default implementation of ClassVerifier. - */ -class ClassVerifierImpl - extends AbstractObjectVerifier, ClassValidator, ClassConstructor> - implements ClassVerifier -{ - /** - * Creates a new ClassVerifierImpl. - * - * @param validator - the validator to delegate to - * @throws TypeError if validator is null or undefined - */ - constructor(validator: ClassValidator) - { - super(validator); - } - - isSupertypeOf(type: ClassConstructor): ClassVerifier - { - this.validator.isSupertypeOf(type); - return this.validationResult(() => this.getThis()); - } - - isSubtypeOf(type: ClassConstructor): ClassVerifier - { - this.validator.isSubtypeOf(type); - return this.validationResult(() => this.getThis()); - } -} - -export {ClassVerifierImpl}; \ No newline at end of file diff --git a/src/internal/Configuration.mts b/src/internal/Configuration.mts new file mode 100644 index 0000000..575fac4 --- /dev/null +++ b/src/internal/Configuration.mts @@ -0,0 +1,128 @@ +/* +* Copyright (c) 2019 Gili Tzabari +* Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 +*/ +import { + StringMappers, + requireThatValueIsNotNull, + ValidationFailures +} from "./internal.mjs"; + +const typedocWorkaround: null | ValidationFailures = null; +// noinspection PointlessBooleanExpressionJS +if (typedocWorkaround !== null) + console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); + +/** + * Determines the behavior of a validator. + */ +class Configuration +{ + /** + * The default configuration. + */ + public static readonly DEFAULT: Configuration = new Configuration(); + private readonly _stringMappers: StringMappers; + private readonly _allowDiff: boolean; + private readonly _recordStacktrace: boolean; + private readonly _throwOnFailure: boolean; + private readonly _errorTransformer: (error: Error) => Error; + + /** + * Creates a new configuration that: + *

    + *
  • Has an empty context.
  • + *
  • Throws an error on failure.
  • + *
  • Records the error stack trace when a validation failure occurs.
  • + *
  • May include a diff that compares the actual and expected values.
  • + *
+ * + * @param allowDiff - `true` if error messages may include a diff that compares actual and expected values + * @param stringMappers - the configuration used to map contextual values to a string + * @param recordStacktrace - `true` if the error stack trace must be recorded when a validation failure + * occurs. If `false`, the error type remains the same, but the stack trace points to the invocation + * of `elseGetError()`. Users who only plan to + * {@link ValidationFailures.getMessages|list of failure messages} instead of retrieving an error + * may see a performance improvement if this value is set to `false`. + * @param throwOnFailure - `true` if an error is thrown on validation failure + * @param errorTransformer - a function that transforms the validation error before it is thrown or + * returned + * @throws TypeError if any of the arguments are `undefined` or `null` + */ + constructor(allowDiff = true, stringMappers = StringMappers.DEFAULT, recordStacktrace = true, + throwOnFailure = true, errorTransformer: (error: Error) => Error = e => e) + { + requireThatValueIsNotNull(stringMappers, "stripMappers"); + requireThatValueIsNotNull(errorTransformer, "errorTransformer"); + this._allowDiff = allowDiff; + this._stringMappers = stringMappers; + this._recordStacktrace = recordStacktrace; + this._throwOnFailure = throwOnFailure; + this._errorTransformer = errorTransformer; + } + + /** + * Returns `true` if error messages may include a diff that compares actual and expected values. + * + * @returns `true` by default + */ + public allowDiff() + { + return this._allowDiff; + } + + /** + * Returns the configuration used to map contextual values to a String. Supports common types such as + * arrays, numbers, collections, maps, paths and errors. + * + * @returns a function that takes an object and returns the `string` representation of the object + */ + public stringMappers() + { + return this._stringMappers; + } + + /** + * Returns `true` if the error stack trace must be recorded when a validation failure occurs. If `false`, + * the error type remains the same, but the stack trace points to the invocation of + * `elseGetError()`. Users who only plan to + * {@link ValidationFailures.getMessages|list of failure messages} instead of retrieving an error + * may see a performance improvement if this value is set to `false`. + * + * @returns `true` if error stack traces must be recorded when a validation failure occurs + */ + public recordStacktrace() + { + return this._recordStacktrace; + } + + /** + * Returns `true` if an error is thrown on validation failure. + * + * @returns `true` if an error is thrown on validation failure + */ + public throwOnFailure() + { + return this._throwOnFailure; + } + + /** + * Returns a function that transforms validation errors before they are thrown or recorded. + * + * If the function returns `undefined` or `null`, it’s treated as if it returned the input error. + * + * @returns a function that transforms the validation error + */ + public errorTransformer() + { + return this._errorTransformer; + } + + public toString() + { + return `Configuration[allowDiff=${this._allowDiff}, , stringMappers=${this._stringMappers.toString()},\ +recordStacktrace: ${this._recordStacktrace}, throwOnFailure: ${this._throwOnFailure}`; + } +} + +export {Configuration}; \ No newline at end of file diff --git a/src/internal/ConfigurationUpdater.mts b/src/internal/ConfigurationUpdater.mts new file mode 100644 index 0000000..cd98042 --- /dev/null +++ b/src/internal/ConfigurationUpdater.mts @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2019 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ + +import { + type MutableStringMappers, + ValidationFailures +} from "./internal.mjs"; + +const typedocWorkaround: null | ValidationFailures = null; +// noinspection PointlessBooleanExpressionJS +if (typedocWorkaround !== null) + console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); + +/** + * Updates the configuration used by new validators. + */ +interface ConfigurationUpdater +{ + /** + * Returns `true` if error messages may include a diff that compares actual and expected values. + * + * @returns `true` by default + */ + allowDiff(): boolean; + + /** + * Specifies that error messages may include a diff that compares actual and expected values. + * + * @param allowDiff - `true` by default + * @returns this + */ + allowDiff(allowDiff: boolean): ConfigurationUpdater; + + allowDiff(mayDiff?: boolean): boolean | ConfigurationUpdater; + + /** + * Returns the configuration used to map contextual values to a String. Supports common types such as + * arrays or numbers. + * + * @returns a function that takes an object and returns the `string` representation of the object + */ + stringMappers(): MutableStringMappers; + + /** + * Returns `true` if error stack traces should reference the code that triggers a validation + * failure. When set to `false`, the error type remains unchanged, but the stack trace location is + * undefined. Users who only plan to {@link ValidationFailures.getMessages|list of failure messages} + * instead of errors may experience a performance improvement if this value is set to `false`. + * + * @returns `true` if errors must be recorded when a validation failure occurs + */ + recordStacktrace(): boolean; + + /** + * Specifies whether error stack traces should reference the code that triggers a validation failure. + * When set to `false`, the error type remains unchanged, but the stack trace location is + * undefined. Users who only plan to {@link ValidationFailures.getMessages|list of failure messages} + * instead of errors may experience a performance improvement if this value is set to `false`. + * + * @param recordStacktrace - `true` if errors must be recorded when a validation failure occurs + * @returns this + */ + recordStacktrace(recordStacktrace: boolean): ConfigurationUpdater; + + recordStacktrace(recordStacktrace?: boolean): boolean | ConfigurationUpdater; + + /** + * Returns a function that transforms the validation error before it is thrown or returned. If the function + * returns `undefined` or `null`, it’s treated as if it returned the input error. + * + * @returns a function that transforms the validation error + */ + errorTransformer(): (error: Error) => Error; + + /** + * Sets a function that transforms the validation error before it is thrown or returned. If the function + * returns `undefined` or `null`, it’s treated as if it returned the input error. + * + * @param errorTransformer - a function that transforms the validation error before it is thrown or returned + * @returns this + */ + errorTransformer(errorTransformer: (error: Error) => Error): ConfigurationUpdater; + + errorTransformer(errorTransformer?: (error: Error) => Error): ((error: Error) => Error) | ConfigurationUpdater; + + /** + * Applies the changes to the configuration. + */ + close(): void; +} + +export type {ConfigurationUpdater}; \ No newline at end of file diff --git a/src/internal/InetAddressValidatorImpl.mts b/src/internal/InetAddressValidatorImpl.mts deleted file mode 100644 index 06332c1..0000000 --- a/src/internal/InetAddressValidatorImpl.mts +++ /dev/null @@ -1,85 +0,0 @@ -import type { - Configuration, - InetAddressValidator -} from "./internal.mjs"; -import { - AbstractObjectValidator, - Objects, - ValidationFailure -} from "./internal.mjs"; - -/** - * Default implementation of InetAddressValidator. - */ -class InetAddressValidatorImpl extends AbstractObjectValidator - implements InetAddressValidator -{ - private readonly addressIsIpV4: boolean; - private readonly addressIsIpV6: boolean; - private readonly addressIsHostname: boolean; - - /** - * Creates a new InetAddressValidator. - * - * @param configuration - the instance configuration - * @param actual - the actual value - * @param name - the name of the value - * @param isIpV4 - true if the actual value is an IP v4 address - * @param isIpV6 - true if the actual value is an IP v6 address - * @param isHostname - true if the actual value is a hostname - * @param failures - the list of validation failures - * @throws TypeError if configuration, name, isIpV4, - * isIpV6, isHostname are null or undefined - * @throws RangeError if name is empty - */ - constructor(configuration: Configuration, actual: string | undefined, name: string, isIpV4: boolean, isIpV6: boolean, - isHostname: boolean, failures: ValidationFailure[]) - { - super(configuration, actual, name, failures); - - Objects.requireThatValueIsDefinedAndNotNull(isIpV4, "isIpV4"); - Objects.requireThatValueIsDefinedAndNotNull(isIpV6, "isIpV6"); - Objects.requireThatValueIsDefinedAndNotNull(isHostname, "isHostname"); - this.addressIsIpV4 = isIpV4; - this.addressIsIpV6 = isIpV6; - this.addressIsHostname = isHostname; - } - - isIpV4(): InetAddressValidator - { - if (this.actual === undefined || !this.addressIsIpV4) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " must be an IP v4 address."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this; - } - - isIpV6(): InetAddressValidator - { - if (this.actual === undefined || !this.addressIsIpV6) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " must be an IP v6 address."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this; - } - - isHostname(): InetAddressValidator - { - if (this.actual === undefined || !this.addressIsHostname) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " must be a hostname."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this; - } -} - -export {InetAddressValidatorImpl}; \ No newline at end of file diff --git a/src/internal/InetAddressVerifierImpl.mts b/src/internal/InetAddressVerifierImpl.mts deleted file mode 100644 index 66a6048..0000000 --- a/src/internal/InetAddressVerifierImpl.mts +++ /dev/null @@ -1,43 +0,0 @@ -import type { - InetAddressValidator, - InetAddressVerifier -} from "./internal.mjs"; -import {AbstractObjectVerifier} from "./internal.mjs"; - -/** - * Default implementation of InetAddressVerifier. - */ -class InetAddressVerifierImpl extends AbstractObjectVerifier - implements InetAddressVerifier -{ - /** - * Creates a new InetAddressVerifierImpl. - * - * @param validator - the validator to delegate to - * @throws TypeError if validator is null or undefined - */ - constructor(validator: InetAddressValidator) - { - super(validator); - } - - isIpV4(): InetAddressVerifier - { - this.validator.isIpV4(); - return this.validationResult(() => this.getThis()); - } - - isIpV6(): InetAddressVerifier - { - this.validator.isIpV6(); - return this.validationResult(() => this.getThis()); - } - - isHostname(): InetAddressVerifier - { - this.validator.isHostname(); - return this.validationResult(() => this.getThis()); - } -} - -export {InetAddressVerifierImpl}; \ No newline at end of file diff --git a/src/internal/MainGlobalConfiguration.mts b/src/internal/MainGlobalConfiguration.mts deleted file mode 100644 index 6844b11..0000000 --- a/src/internal/MainGlobalConfiguration.mts +++ /dev/null @@ -1,64 +0,0 @@ -import type {TerminalEncoding} from "./internal.mjs"; -import { - AbstractGlobalConfiguration, - Terminal -} from "./internal.mjs"; - -const terminal = new Terminal(); - -class MainGlobalConfiguration extends AbstractGlobalConfiguration -{ - static readonly INSTANCE: MainGlobalConfiguration = new MainGlobalConfiguration(false, true); - - /** - * Creates a new global configuration. - * - * @param assertionsEnabled - true if assertThat() should invoke requireThat() - * @param diffEnabled - true if exceptions should show the difference between the actual and expected values - */ - private constructor(assertionsEnabled: boolean, diffEnabled: boolean) - { - super(assertionsEnabled, diffEnabled); - } - - listTerminalEncodings(): TerminalEncoding[] - { - return terminal.listSupportedTypes(); - } - - getTerminalEncoding(): TerminalEncoding - { - return terminal.getEncoding(); - } - - withDefaultTerminalEncoding(): this - { - terminal.useBestEncoding(); - return this; - } - - withTerminalEncoding(encoding: TerminalEncoding): this - { - terminal.setEncoding(encoding); - return this; - } - - getTerminalWidth(): number - { - return terminal.getWidth(); - } - - withDefaultTerminalWidth(): this - { - terminal.useBestWidth(); - return this; - } - - withTerminalWidth(width: number): this - { - terminal.setWidth(width); - return this; - } -} - -export {MainGlobalConfiguration}; \ No newline at end of file diff --git a/src/internal/MapValidatorImpl.mts b/src/internal/MapValidatorImpl.mts deleted file mode 100644 index f6d8ce8..0000000 --- a/src/internal/MapValidatorImpl.mts +++ /dev/null @@ -1,151 +0,0 @@ -import type { - ArrayValidator, - Configuration, - MapValidator, - NumberValidator -} from "./internal.mjs"; -import { - AbstractObjectValidator, - ArrayValidatorImpl, - Objects, - Pluralizer, - SizeValidatorImpl, - ValidationFailure -} from "./internal.mjs"; - -/** - * Default implementation of MapValidator. - * - * @typeParam K - the type the map's keys - * @typeParam V - the type the map's values - */ -class MapValidatorImpl extends AbstractObjectValidator, Map> - implements MapValidator -{ - /** - * Creates a new MapValidatorImpl. - * - * @param configuration - the instance configuration - * @param actual - the actual value - * @param name - the name of the value - * @param failures - the list of validation failures - * @throws TypeError if configuration or name are null or undefined - * @throws RangeError if name is empty - */ - constructor(configuration: Configuration, actual: Map | undefined, name: string, failures: ValidationFailure[]) - { - super(configuration, actual, name, failures); - } - - isEmpty() - { - if (this.actual === undefined || this.actual.size !== 0) - { - const failure = new ValidationFailure(this.config, RangeError, this.name + " must be empty."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this; - } - - isNotEmpty() - { - if (this.actual === undefined || this.actual.size === 0) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " may not be empty"); - this.failures.push(failure); - } - return this; - } - - keys() - { - let value: K[] | undefined; - if (this.actual === undefined) - value = undefined; - else - value = Array.from(this.actual.keys()); - return new ArrayValidatorImpl(this.config, value, this.name + ".keys()", Pluralizer.KEY, this.failures); - } - - keysConsumer(consumer: (actual: ArrayValidator) => void) - { - Objects.requireThatValueIsDefinedAndNotNull(consumer, "consumer"); - if (this.failures.length === 0) - consumer(this.keys()); - return this; - } - - values() - { - let value: V[] | undefined; - if (this.actual === undefined) - value = undefined; - else - value = Array.from(this.actual.values()); - return new ArrayValidatorImpl(this.config, value, this.name + ".values()", Pluralizer.VALUE, - this.failures); - } - - valuesConsumer(consumer: (actual: ArrayValidator) => void) - { - Objects.requireThatValueIsDefinedAndNotNull(consumer, "consumer"); - if (this.failures.length === 0) - consumer(this.values()); - return this; - } - - entries() - { - let value: [K, V][] | undefined; - if (this.actual === undefined) - value = undefined; - else - value = Array.from(this.actual.entries()); - return new ArrayValidatorImpl(this.config, value, this.name + ".entries()", Pluralizer.ENTRY, - this.failures); - } - - entriesConsumer(consumer: (actual: ArrayValidator<[K, V]>) => void): MapValidator - { - Objects.requireThatValueIsDefinedAndNotNull(consumer, "consumer"); - if (this.failures.length === 0) - consumer(this.entries()); - return this; - } - - size(): NumberValidator - { - let value: unknown; - let size: number | undefined; - if (this.actual === undefined) - { - value = undefined; - size = undefined; - } - else - { - value = this.actual; - size = this.actual.size; - } - return new SizeValidatorImpl(this.config, value, this.name, size, this.name + ".size", - Pluralizer.ENTRY, this.failures); - } - - sizeConsumer(consumer: (actual: NumberValidator) => void): MapValidator - { - Objects.requireThatValueIsDefinedAndNotNull(consumer, "consumer"); - if (this.failures.length === 0) - consumer(this.size()); - return this; - } - - asMap(): MapValidator; - asMap(): MapValidator - { - return this; - } -} - -export {MapValidatorImpl}; \ No newline at end of file diff --git a/src/internal/MapVerifierImpl.mts b/src/internal/MapVerifierImpl.mts deleted file mode 100644 index 1eb4823..0000000 --- a/src/internal/MapVerifierImpl.mts +++ /dev/null @@ -1,99 +0,0 @@ -import type { - ArrayVerifier, - MapValidator, - MapVerifier, - NumberVerifier -} from "./internal.mjs"; -import { - AbstractObjectVerifier, - ArrayVerifierImpl, - NumberVerifierImpl, - Objects -} from "./internal.mjs"; - -/** - * Default implementation of MapVerifier. - * - * @typeParam K - the type the map's keys - * @typeParam V - the type the map's values - */ -class MapVerifierImpl extends AbstractObjectVerifier, MapValidator, Map> - implements MapVerifier -{ - /** - * Creates a new MapVerifierImpl. - * - * @param validator - the validator to delegate to - * @throws TypeError if validator is null or undefined - */ - constructor(validator: MapValidator) - { - super(validator); - } - - isEmpty() - { - this.validator.isEmpty(); - return this.validationResult(() => this.getThis()); - } - - isNotEmpty() - { - this.validator.isNotEmpty(); - return this.validationResult(() => this.getThis()); - } - - keys() - { - const newValidator = this.validator.keys(); - return this.validationResult(() => new ArrayVerifierImpl(newValidator)) as ArrayVerifier; - } - - keysConsumer(consumer: (actual: ArrayVerifier) => void) - { - Objects.requireThatValueIsDefinedAndNotNull(consumer, "consumer"); - consumer(this.keys()); - return this; - } - - values() - { - const newValidator = this.validator.values(); - return this.validationResult(() => new ArrayVerifierImpl(newValidator)) as ArrayVerifier; - } - - valuesConsumer(consumer: (actual: ArrayVerifier) => void) - { - Objects.requireThatValueIsDefinedAndNotNull(consumer, "consumer"); - consumer(this.values()); - return this; - } - - entries() - { - const newValidator = this.validator.entries(); - return this.validationResult(() => new ArrayVerifierImpl(newValidator)) as ArrayVerifier<[K, V]>; - } - - entriesConsumer(consumer: (actual: ArrayVerifier<[K, V]>) => void) - { - Objects.requireThatValueIsDefinedAndNotNull(consumer, "consumer"); - consumer(this.entries()); - return this; - } - - size() - { - const newValidator = this.validator.size(); - return this.validationResult(() => new NumberVerifierImpl(newValidator)) as NumberVerifier; - } - - sizeConsumer(consumer: (actual: NumberVerifier) => void): MapVerifier - { - Objects.requireThatValueIsDefinedAndNotNull(consumer, "consumer"); - consumer(this.size()); - return this; - } -} - -export {MapVerifierImpl}; \ No newline at end of file diff --git a/src/internal/Maps.mts b/src/internal/Maps.mts deleted file mode 100644 index 0450434..0000000 --- a/src/internal/Maps.mts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Map helper functions. - */ -class Maps -{ - /** - * Appends to a string map value. - * - * @param map - a map - * @param key - the map key - * @param value - the value to append - */ - static appendToValue(map: Map, key: K, value: string) - { - const oldValue = map.get(key); - if (typeof (oldValue) === "undefined") - map.set(key, value); - else - map.set(key, oldValue + value); - } - - /** - * @param map - a map - * @returns the map sorted by its keys - */ - static sortByKeys(map: Map) - { - // https://stackoverflow.com/a/31159284/14731 - return new Map([...map.entries()].sort()); - } -} - -export {Maps}; \ No newline at end of file diff --git a/src/internal/MutableStringMappers.mts b/src/internal/MutableStringMappers.mts new file mode 100644 index 0000000..4ef57ec --- /dev/null +++ b/src/internal/MutableStringMappers.mts @@ -0,0 +1,83 @@ +import { + Type, + type StringMapper, + StringMappers, + internalValueToString +} from "./internal.mjs"; + +/** + * Returns the String representation of + */ +class MutableStringMappers +{ + private readonly typeToMapper: Map; + + /** + * Creates a new instance. + * + * @param typeToMapper - a mapping from the name of a type to the string representation of its values + */ + public constructor(typeToMapper: Map) + { + this.typeToMapper = new Map(typeToMapper); + } + + /** + * Returns a mutable copy of the StringMappers. + * + * @param mappers - a `StringMappers` object + * @returns a mutable copy of the StringMappers + */ + public static from(mappers: StringMappers) + { + return new MutableStringMappers(mappers.typeToMapper); + } + + /** + * Returns an immutable copy of the mapper configuration. + * + * @returns an immutable copy of the mapper configuration + */ + public toImmutable(): StringMappers + { + return new StringMappers(this.typeToMapper); + } + + /** + * Sets the function that maps a value of the given type to a string. This method is useful for customizing + * the formatting of validation failure messages. + * + * @param type - a type + * @param mapper - a function that returns the String representation of the type's instances + * @returns this + */ + public put(type: Type, mapper: StringMapper) + { + this.typeToMapper.set(type, mapper); + return this; + } + + /** + * Removes a mapper for a type. + * + * @param type - the type + * @returns this + */ + public remove(type: Type) + { + this.typeToMapper.delete(type); + return this; + } + + /** + * Returns the string representation of this instance + * + * @returns the string representation of this instance + */ + public toString(): string + { + return internalValueToString(this.typeToMapper); + } +} + +export {MutableStringMappers}; \ No newline at end of file diff --git a/src/internal/NumberValidatorImpl.mts b/src/internal/NumberValidatorImpl.mts deleted file mode 100644 index 38d8153..0000000 --- a/src/internal/NumberValidatorImpl.mts +++ /dev/null @@ -1,30 +0,0 @@ -import type { - Configuration, - NumberValidator, - ValidationFailure -} from "./internal.mjs"; -import {AbstractNumberValidator} from "./internal.mjs"; - -/** - * Default implementation of NumberValidator. - */ -class NumberValidatorImpl extends AbstractNumberValidator - implements NumberValidator -{ - /** - * Creates a new NumberValidator. - * - * @param configuration - the instance configuration - * @param actual - the actual value - * @param name - (optional) the name of the value - * @param failures - the list of validation failures - * @throws TypeError if configuration or name are null or undefined - * @throws RangeError if name is empty - */ - constructor(configuration: Configuration, actual: number | undefined, name: string, failures: ValidationFailure[]) - { - super(configuration, actual, name, failures); - } -} - -export {NumberValidatorImpl}; \ No newline at end of file diff --git a/src/internal/NumberVerifierImpl.mts b/src/internal/NumberVerifierImpl.mts deleted file mode 100644 index bee5db4..0000000 --- a/src/internal/NumberVerifierImpl.mts +++ /dev/null @@ -1,25 +0,0 @@ -import type { - NumberValidator, - NumberVerifier -} from "./internal.mjs"; -import {AbstractNumberVerifier} from "./internal.mjs"; - -/** - * Default implementation of NumberVerifier. - */ -class NumberVerifierImpl extends AbstractNumberVerifier - implements NumberVerifier -{ - /** - * Creates a new NumberVerifierImpl. - * - * @param validator - the validator to delegate to - * @throws TypeError if validator is null or undefined - */ - constructor(validator: NumberValidator) - { - super(validator); - } -} - -export {NumberVerifierImpl}; \ No newline at end of file diff --git a/src/internal/ObjectValidatorImpl.mts b/src/internal/ObjectValidatorImpl.mts deleted file mode 100644 index 37a84c0..0000000 --- a/src/internal/ObjectValidatorImpl.mts +++ /dev/null @@ -1,35 +0,0 @@ -import type { - Configuration, - ObjectValidator -} from "./internal.mjs"; -import { - AbstractObjectValidator, - ValidationFailure -} from "./internal.mjs"; - -/** - * Default implementation of ObjectValidator. - * - * @typeParam T - the type the actual value - */ -class ObjectValidatorImpl extends AbstractObjectValidator, T> - implements ObjectValidator -{ - /** - * Creates a new ObjectValidator. - * - * @param configuration - the instance configuration - * @param actual - the actual value - * @param name - the name of the value - * @param failures - the list of validation failures - * @throws TypeError if configuration or name are null or undefined - * @throws RangeError if name is empty - */ - constructor(configuration: Configuration, actual: T | undefined, name: string, - failures: ValidationFailure[]) - { - super(configuration, actual, name, failures); - } -} - -export {ObjectValidatorImpl}; \ No newline at end of file diff --git a/src/internal/ObjectVerifierImpl.mts b/src/internal/ObjectVerifierImpl.mts deleted file mode 100644 index a205224..0000000 --- a/src/internal/ObjectVerifierImpl.mts +++ /dev/null @@ -1,54 +0,0 @@ -import type { - ObjectValidator, - ObjectVerifier -} from "./internal.mjs"; -import {AbstractObjectVerifier} from "./internal.mjs"; - -/** - * Default implementation of ObjectVerifier. - * - * @typeParam T - the type the actual value - */ -class ObjectVerifierImpl, T> extends AbstractObjectVerifier, ObjectValidator, T> - implements ObjectVerifier -{ - /** - * Creates a new ObjectVerifier. - * - * @param validator - the validator to delegate to - * @throws TypeError if validator is null or undefined - */ - constructor(validator: V) - { - super(validator); - } - - isEqualTo(expected: T, name?: string): ObjectVerifier - { - this.validator.isEqualTo(expected, name); - return this.validationResult(() => this.getThis()); - } - - isNotEqualTo(value: T, name?: string): ObjectVerifier - { - this.validator.isNotEqualTo(value, name); - return this.validationResult(() => this.getThis()); - } - - validationResult(result: () => T2): T2 - { - if (result === null) - throw new TypeError("result may not be null"); - - const failures = this.validator.getFailures(); - if (failures.length === 0) - { - // eslint-disable-next-line no-undefined - return result.apply(undefined); - } - const failure = failures[0]; - throw failure.createException(); - } -} - -export {ObjectVerifierImpl}; \ No newline at end of file diff --git a/src/internal/Objects.mts b/src/internal/Objects.mts deleted file mode 100644 index 5f37063..0000000 --- a/src/internal/Objects.mts +++ /dev/null @@ -1,417 +0,0 @@ -import { - VariableType, - type ClassConstructor -} from "./internal.mjs"; - -/** - * Object helper functions. - */ -class Objects -{ - static readonly functionNamePattern = /^function\s+([^(]+)?\(/; - static readonly classNamePattern = /^class(\s+[^{]+)?{/; - static readonly builtInClassNamePattern = /^function\s+([^(]+)?\(\) { \[native code] }/; - - /** - * @param value - a value - * @returns true if value is a primitive type (string, - * number, bigint, boolean, null, - * undefined, or symbol) - */ - static isPrimitive(value: unknown) - { - // Per https://jsperf.com/testing-value-is-primitive/7 the following is the fastest - if (value === null) - return true; - const type = typeof (value); - return type !== "function" && type !== "object"; - } - - /** - * Indicates if an object is an instance of a type. To convert a type to an object, use - * prototype such as Error.prototype. To convert an object to a type, use - * constructor such as instance.constructor. - * - * @param child - the child class - * @param parent - the parent class - * @returns true if child extends parent; false if - * parent or child are null or undefined; false if child does not - * extend parent - */ - static extends(child: ClassConstructor, parent: ClassConstructor) - { - if (typeof (child) === "undefined" || child === null || typeof (parent) === "undefined" || - parent === null) - { - return false; - } - // https://stackoverflow.com/a/14486171/14731 - return child.prototype instanceof parent; - } - - /** - * Throws an Error if condition is false. - * - * @param condition - a condition - * @param error - the type of error to throw (Default: Error) - * @param message - the error message to use on failure - * @throws Error if condition is false - */ - static assert(condition: boolean, error: (message?: string) => Error = Error, message?: string): - asserts condition - { - // Will be stripped out using uglify.js option "pure_funcs" - if (!condition) - throw error(message); - } - - /** - * Returns the type information of a value. - * - *
    - *
  • If the input is undefined, returns (type="undefined", name=null).
  • - *
  • If the input is null, returns (type="null", name=null).
  • - *
  • If the input is a primitive boolean, returns (type="boolean", name=null).
  • - *
  • If the input is a primitive number, returns (type="number", name=null).
  • - *
  • If the input is a primitive or wrapper bigint, returns - * (type="bigint", name=null).
  • - *
  • If the input is an array, returns (type="array", name=null).
  • - *
  • If the input is a primitive string, returns (type="string", name=null).
  • - *
  • If the input is a primitive symbol, returns (type="symbol", null).
  • - *
  • If the input is a function, returns (type="function", name=the function name). If the - * input is an arrow or anonymous function, its name is null.
  • - *
  • If the input is a function, returns (type="function", name=the function name).
  • - *
  • If the input is a class, returns (type="class", name=the name of the class). - *
  • If the input is an object, returns - * (type="object", name=the name of the object's class). - *
  • - *
- * - * Please note that built-in types (such as Object, String or Number) - * may return a function instead of class. - * - * @param value - a value - * @returns value's type - * @see http://stackoverflow.com/a/332429/14731 - * @see isPrimitive - */ - static getTypeInfo(value: unknown) - { - if (value === undefined) - return VariableType.UNDEFINED; - if (value === null) - return VariableType.NULL; - if (Array.isArray(value)) - return VariableType.ARRAY; - const typeOfValue = typeof (value); - const isPrimitive = typeOfValue !== "function" && typeOfValue !== "object"; - if (isPrimitive) - return VariableType.of(typeOfValue); - const valueAsFunction = value as ClassConstructor; - const objectToString = Object.prototype.toString.call(value).slice(8, -1); - if (objectToString === "Function") - { - // A function or a class - const valueToString = valueAsFunction.toString(); - const indexOfArrow = valueToString.indexOf("=>"); - const indexOfBody = valueToString.indexOf("{"); - if (indexOfArrow !== -1 && (indexOfBody === -1 || indexOfArrow < indexOfBody)) - { - // Arrow function - return VariableType.ANONYMOUS_FUNCTION; - } - const className = this.classNamePattern.exec(valueToString); - if (className !== null && typeof (className[1]) !== "undefined") - { - // A class - const name = className[1].trim(); - return VariableType.of("class", name); - } - const builtInClassName = this.builtInClassNamePattern.exec(valueToString); - if (builtInClassName !== null && typeof (builtInClassName[1]) !== "undefined") - { - // A built-in class - const name = builtInClassName[1].trim(); - return VariableType.of("class", name); - } - // Anonymous and named functions - const functionName = this.functionNamePattern.exec(valueToString); - if (functionName !== null && typeof (functionName[1]) !== "undefined") - { - // A named function - const name = functionName[1].trim(); - return VariableType.of("function", name); - } - // Anonymous function - return VariableType.ANONYMOUS_FUNCTION; - } - - // Per https://stackoverflow.com/a/30560581/14731 the ES6 specification guarantees the following will - // work - return VariableType.of("object", valueAsFunction.constructor.name); - } - - /** - * Ensures that an object is defined and not null. - * - * @param value - the value of a parameter - * @param name - the name of the parameter - * @returns true - * @throws TypeError if name is not a string - * If value is undefined or null. - */ - static requireThatValueIsDefinedAndNotNull(value: unknown, name: string) - { - const nameInfo = Objects.getTypeInfo(name); - if (nameInfo.type !== "string") - { - throw new TypeError("name must be a string.\n" + - "Actual: " + Objects.toString(name) + "\n" + - "Type : " + nameInfo.toString()); - } - if (typeof (value) === "undefined") - throw new TypeError(name + " must be defined"); - if (value === null) - throw new TypeError(name + " may not be null"); - return true; - } - - /** - * Requires that an object has the expected type. - * - * @param value - the value of a parameter - * @param name - the name of the parameter - * @param type - the expected - * typeof - * of value - * @returns true - * @throws TypeError if typeof(value) is not equal to type's type. - * If name is not a string. - */ - static requireThatTypeOf(value: unknown, name: string, type: string) - { - const nameInfo = Objects.getTypeInfo(name); - if (nameInfo.type !== "string") - { - throw new TypeError("name must be a string.\n" + - "Actual: " + Objects.toString(name) + "\n" + - "Type : " + nameInfo.toString()); - } - const valueInfo = Objects.getTypeInfo(value); - if (valueInfo.type !== type) - { - throw new TypeError(name + " must be a " + type + ".\n" + - "Actual: " + Objects.toString(value) + "\n" + - "Type : " + valueInfo.toString()); - } - return true; - } - - /** - * Requires that an object is an instance of type. - * - * @param value - the value of a parameter - * @param name - the name of the parameter - * @param type - the class that value is expected to be an instance of - * @returns true - * @throws TypeError if value is not an instance of type. - * If name is not a string. - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - static requireThatInstanceOf(value: unknown, name: string, type: ClassConstructor): boolean - { - const nameInfo = Objects.getTypeInfo(name); - if (nameInfo.type !== "string") - { - throw new TypeError("name must be a string.\n" + - "Actual : " + Objects.toString(name) + "\n" + - "Actual.type: " + nameInfo.toString()); - } - const typeInfo = Objects.getTypeInfo(type); - if (!(value instanceof type)) - { - throw new TypeError(name + " must be an instance of " + typeInfo.toString() + ".\n" + - "Actual: " + Objects.toString(type) + "\n" + - "Type : " + typeInfo.toString()); - } - return true; - } - - /** - * Requires that a value has the expected type if assertions are enabled. We assume that - * Objects.assert() will be stripped out at build-time if assertions are disabled. - * - * @param value - the value of a parameter - * @param name - the name of the parameter - * @param type - the expected - * typeof - * of value - * @throws TypeError if value is not of type type. If name is not - * a string. - */ - static assertThatTypeOf(value: unknown, name: string, type: string): void - { - Objects.assert(this.requireThatTypeOf(value, name, type)); - } - - /** - * Requires that an object is an instance of the expected type. - * - * @param value - the value of a parameter - * @param name - the name of the parameter - * @param type - the class the value is expected to be an instance of - * @throws TypeError if value is not an instance of type. - * If name is not a string. - */ - static assertThatInstanceOf(value: T, name: string, type: ClassConstructor): void - { - Objects.assert(this.requireThatInstanceOf(value, name, type)); - } - - /** - * Requires that a string is not empty. - * - * @param value - the value of a parameter - * @param name - the name of the parameter - * @returns true - * @throws TypeError if name or value are empty. - * If name is not a string. - */ - static requireThatStringIsNotEmpty(value: string, name: string): boolean - { - this.requireThatTypeOf(name, "name", "string"); - name = name.trim(); - if (name.length === 0) - throw new RangeError("name may not be empty"); - this.requireThatTypeOf(value, "value", "string"); - value = value.trim(); - if (value.length === 0) - throw new RangeError(name + " may not be empty"); - return true; - } - - /** - * Requires that a string is not empty if assertions are enabled. We assume that - * Objects.assert() will be stripped out at build-time if assertions are disabled. - * - * @param value - the value of a parameter - * @param name - the name of the parameter - * @throws TypeError if name or value are empty. If name is not a - * string. - */ - static assertThatStringNotEmpty(value: string, name: string): void - { - Objects.assert(this.requireThatStringIsNotEmpty(value, name)); - } - - /** - * @param object - an object - * @returns the string representation of the object - */ - static toString(object: unknown): string - { - let currentInfo = Objects.getTypeInfo(object); - switch (currentInfo.type) - { - case "undefined": - case "null": - return currentInfo.type; - case "array": - return Objects.arrayToString(object as unknown[]); - case "string": - return object as string; - case "boolean" : - case "number" : - case "bigint": - case "symbol": - case "function": - case "class": - break; - case "object": - { - switch (currentInfo.name) - { - case "Set": - { - const set = object as Set; - return Objects.arrayToString(Array.from(set.values())); - } - case "Map": - { - const result: { [key: string]: unknown } = {}; - const map = object as Map; - for (const entry of map.entries()) - { - const key = Objects.toString(entry[0]); - result[key] = entry[1]; - } - return JSON.stringify(result, null, 2); - } - } - // Other kind of classes - break; - } - default: - throw new Error("Unexpected type: " + currentInfo.toString()); - } - - let current = object as boolean | number | bigint | symbol | ClassConstructor | object; - while (true) - { - // See http://stackoverflow.com/a/22445303/14731, - // Invoke toString() if it was defined - // https://stackoverflow.com/a/57214796/14731: invoke toString() on safeTypes - if (Object.prototype.hasOwnProperty.call(current.constructor.prototype, "toString")) - { - // eslint-disable-next-line @typescript-eslint/no-base-to-string - return current.toString(); - } - - // Get the superclass and try again - current = Object.getPrototypeOf(current.constructor.prototype) as object; - currentInfo = Objects.getTypeInfo(current); - if (currentInfo.type === "object") - { - // Prefer JSON.stringify() to Object.toString(). - return JSON.stringify(current, null, 2); - } - } - } - - /** - * @param array - an array - * @returns the string representation of the array, using Objects.toString() to convert nested values - */ - private static arrayToString(array: unknown[]): string - { - let result = "["; - // Can't use Array.join() because it doesn't handle nested arrays well - const size = array.length; - for (let i = 0; i < size; ++i) - { - result += Objects.toString(array[i]); - if (i < size - 1) - result += ", "; - } - result += "]"; - return result; - } - - /** - * @param value - a name - * @param name - the name of the name - * @throws TypeError if name or value are not a string - * @throws RangeError if value is empty - */ - static verifyName(value: string, name: string): void - { - if (typeof (name) !== "undefined") - this.requireThatTypeOf(name, "name", "string"); - this.requireThatTypeOf(value, "value", "string"); - const trimmed = value.trim(); - if (trimmed.length === 0) - throw new RangeError(name + " may not be empty"); - } -} - -export {Objects}; \ No newline at end of file diff --git a/src/internal/SetValidatorImpl.mts b/src/internal/SetValidatorImpl.mts deleted file mode 100644 index 8a9ea2f..0000000 --- a/src/internal/SetValidatorImpl.mts +++ /dev/null @@ -1,356 +0,0 @@ -import type { - ArrayValidator, - Configuration, - NumberValidator, - SetValidator -} from "./internal.mjs"; -import { - AbstractObjectValidator, - ArrayValidatorImpl, - Objects, - Pluralizer, - SizeValidatorImpl, - ValidationFailure -} from "./internal.mjs"; - -/** - * Default implementation of SetValidator. - */ -class SetValidatorImpl extends AbstractObjectValidator, Set> - implements SetValidator -{ - /** - * Creates a new SetValidatorImpl. - * - * @param configuration - the instance configuration - * @param actual - the actual value - * @param name - the name of the value - * @param failures - the list of validation failures - * @throws TypeError if configuration or name are null or undefined - * @throws RangeError if name is empty - */ - constructor(configuration: Configuration, actual: Set | undefined, name: string, - failures: ValidationFailure[]) - { - super(configuration, actual, name, failures); - } - - isEmpty() - { - if (this.actual === undefined || this.actual.size !== 0) - { - const failure = new ValidationFailure(this.config, RangeError, this.name + " must be empty."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this; - } - - isNotEmpty() - { - if (this.actual === undefined || this.actual.size === 0) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " may not be empty"); - this.failures.push(failure); - } - return this; - } - - contains(expected: E, name?: string) - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - - if (this.actual === undefined || !this.actual.has(expected)) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must contain " + name). - addContext("Actual", this.actual). - addContext("Expected", expected); - } - else - { - failure = new ValidationFailure(this.config, RangeError, this.name + " must contain " + - this.config.convertToString(expected)). - addContext("Actual", this.actual); - } - this.failures.push(failure); - } - return this; - } - - containsExactly(expected: E[] | Set, name?: string): SetValidator - { - const expectedAsSet = new Set(expected); - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - - let missing; - let unwanted; - if (this.actual === undefined) - { - missing = undefined; - unwanted = undefined; - } - else - { - missing = new Set([...expectedAsSet].filter(x => !this.actual!.has(x))); - unwanted = new Set([...this.actual].filter(x => !expectedAsSet.has(x))); - } - if (missing === undefined || unwanted === undefined || missing.size !== 0 || unwanted.size !== 0) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, this.name + - " must contain exactly the same elements as " + name). - addContext("Actual", this.actual). - addContext("Expected", expectedAsSet). - addContext("Missing", missing). - addContext("Unwanted", unwanted); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must contain exactly: " + this.config.convertToString(expectedAsSet)). - addContext("Actual", this.actual). - addContext("Missing", missing). - addContext("Unwanted", unwanted); - } - this.failures.push(failure); - } - return this; - } - - containsAny(expected: E[] | Set, name?: string): SetValidator - { - const expectedAsSet = new Set(expected); - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - - if (this.actual === undefined || !this.actualContainsAny(this.actual, expectedAsSet)) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must contain any entry in " + name). - addContext("Actual", this.actual). - addContext("Expected", expectedAsSet); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must contain any entry in: " + this.config.convertToString(expectedAsSet)). - addContext("Actual", this.actual); - } - this.failures.push(failure); - } - return this; - } - - containsAll(expected: E[] | Set, name?: string): SetValidator - { - const expectedAsSet = new Set(expected); - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - - if (this.actual === undefined || !this.actualContainsAll(this.actual, expectedAsSet)) - { - let missing; - if (this.actual === undefined) - missing = undefined; - else - missing = new Set([...expectedAsSet].filter(x => !this.actual!.has(x))); - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must contain all elements in " + name). - addContext("Actual", this.actual). - addContext("Missing", missing); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must contain all elements in: " + this.config.convertToString(expectedAsSet)). - addContext("Actual", this.actual). - addContext("Expected", expectedAsSet). - addContext("Missing", missing); - } - this.failures.push(failure); - } - return this; - } - - doesNotContain(entry: E, name?: string): SetValidator - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - - if (this.actual === undefined || this.actual.has(entry)) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " may not contain " + name + "."). - addContext("Actual", this.actual). - addContext("Unwanted", entry); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " may not contain " + this.config.convertToString(entry)). - addContext("Actual", this.actual); - } - this.failures.push(failure); - } - return this; - } - - doesNotContainAny(elements: E[] | Set, name?: string): SetValidator - { - const elementsAsSet = new Set(elements); - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - - if (this.actual === undefined || this.actualContainsAny(this.actual, elementsAsSet)) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must not contain any element in " + name). - addContext("Actual", this.actual). - addContext("Unwanted", elementsAsSet); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must not contain any element in: " + this.config.convertToString(elementsAsSet)). - addContext("Actual", this.actual); - } - this.failures.push(failure); - } - return this; - } - - doesNotContainAll(elements: E[] | Set, name?: string): SetValidator - { - const elementsAsSet = new Set(elements); - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - - if (this.actual === undefined || this.actualContainsAll(this.actual, elementsAsSet)) - { - let missing; - if (this.actual === undefined) - missing = undefined; - else - missing = new Set([...elementsAsSet].filter(x => !this.actual!.has(x))); - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " may not contain all elements in " + name). - addContext("Actual", this.actual). - addContext("Missing", missing); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " may not contain all elements in: " + this.config.convertToString(elementsAsSet)). - addContext("Actual", this.actual). - addContext("Unwanted", elementsAsSet). - addContext("Missing", missing); - } - this.failures.push(failure); - } - return this; - } - - size(): NumberValidator - { - let value: Set | undefined; - let size: number; - if (!this.requireThatActualIsDefinedAndNotNull()) - { - value = undefined; - size = 0; - } - else - { - value = this.actual as Set; - size = value.size; - } - return new SizeValidatorImpl(this.config, value, this.name, size, this.name + ".size", - Pluralizer.ELEMENT, this.failures); - } - - sizeConsumer(consumer: (actual: NumberValidator) => void): SetValidator - { - Objects.requireThatValueIsDefinedAndNotNull(consumer, "consumer"); - if (this.failures.length === 0) - consumer(this.size()); - return this; - } - - asArray>(): C; - asArray(): ArrayValidator - { - let value: E[] | undefined; - if (!this.requireThatActualIsDefinedAndNotNull()) - value = undefined; - else - { - const actualAsDefined = this.actual as Set; - value = Array.from(actualAsDefined.values()); - } - - return new ArrayValidatorImpl(this.config, value, this.name + ".asArray()", - Pluralizer.ELEMENT, this.failures); - } - - /** - * @param actual - a Set - * @param expected - a set of expected elements - * @returns true if actual contains any of the expected elements - */ - private actualContainsAny(actual: Set, expected: Set): boolean - { - for (const entry of expected) - { - if (actual.has(entry)) - return true; - } - return false; - } - - /** - * @param actual - a Set - * @param expected - a Set of expected values - * @returns true if actual contains all the expected elements - */ - private actualContainsAll(actual: Set, expected: Set): boolean - { - for (const entry of expected) - { - if (!actual.has(entry)) - return false; - } - return true; - } - - asSet(): SetValidator; - asSet(): SetValidator - { - return this; - } -} - -export {SetValidatorImpl}; \ No newline at end of file diff --git a/src/internal/SetVerifierImpl.mts b/src/internal/SetVerifierImpl.mts deleted file mode 100644 index 125b758..0000000 --- a/src/internal/SetVerifierImpl.mts +++ /dev/null @@ -1,99 +0,0 @@ -import type { - NumberVerifier, - SetValidator, - SetVerifier -} from "./internal.mjs"; -import { - AbstractObjectVerifier, - NumberVerifierImpl, - Objects -} from "./internal.mjs"; - -/** - * Default implementation of SetVerifier. - * - * @typeParam E - the type the array elements - */ -class SetVerifierImpl extends AbstractObjectVerifier, SetValidator, Set> - implements SetVerifier -{ - /** - * Creates a new SetVerifierImpl. - * - * @param validator - the validator to delegate to - * @throws TypeError if validator is null or undefined - */ - constructor(validator: SetValidator) - { - super(validator); - } - - isEmpty() - { - this.validator.isEmpty(); - return this.validationResult(() => this.getThis()); - } - - isNotEmpty() - { - this.validator.isNotEmpty(); - return this.validationResult(() => this.getThis()); - } - - contains(expected: E, name?: string) - { - this.validator.contains(expected, name); - return this.validationResult(() => this.getThis()); - } - - containsExactly(expected: E[] | Set, name?: string) - { - this.validator.containsExactly(expected, name); - return this.validationResult(() => this.getThis()); - } - - containsAny(expected: E[] | Set, name?: string) - { - this.validator.containsAny(expected, name); - return this.validationResult(() => this.getThis()); - } - - containsAll(expected: E[] | Set, name?: string) - { - this.validator.containsAll(expected, name); - return this.validationResult(() => this.getThis()); - } - - doesNotContain(entry: E, name?: string) - { - this.validator.doesNotContain(entry, name); - return this.validationResult(() => this.getThis()); - } - - doesNotContainAny(elements: E[] | Set, name?: string): SetVerifier - { - this.validator.doesNotContainAny(elements, name); - return this.validationResult(() => this.getThis()); - } - - doesNotContainAll(elements: E[] | Set, name?: string): SetVerifier - { - this.validator.doesNotContainAll(elements, name); - return this.validationResult(() => this.getThis()); - } - - size(): NumberVerifier - { - const newValidator = this.validator.size(); - return this.validationResult(() => new NumberVerifierImpl(newValidator)) as NumberVerifier; - } - - sizeConsumer(consumer: (actual: NumberVerifier) => void): SetVerifier - { - Objects.requireThatValueIsDefinedAndNotNull(consumer, "consumer"); - consumer(this.size()); - return this; - } -} - -export {SetVerifierImpl}; \ No newline at end of file diff --git a/src/internal/SizeValidatorImpl.mts b/src/internal/SizeValidatorImpl.mts deleted file mode 100644 index f0cc72a..0000000 --- a/src/internal/SizeValidatorImpl.mts +++ /dev/null @@ -1,172 +0,0 @@ -import type { - Configuration, - NumberValidator, - Pluralizer -} from "./internal.mjs"; -import { - NumberValidatorImpl, - Objects, - ValidationFailure -} from "./internal.mjs"; - -/** - * Default implementation of SetVerifier. - */ -class SizeValidatorImpl extends NumberValidatorImpl - implements NumberValidator -{ - private readonly collection: unknown; - private readonly collectionName: string; - private readonly pluralizer: Pluralizer; - - /** - * Creates a new SizeValidatorImpl. - * - * @param configuration - the instance configuration - * @param collection - the collection - * @param collectionName - the name of the collection - * @param size - the size of the collection - * @param sizeName - the name of the container size - * @param pluralizer - returns the singular or plural form of the container's element type - * @param failures - the list of validation failures - * @throws TypeError if containerName, sizeName, configuration are - * undefined or null. If containerName or sizeName are not a string. - * @throws RangeError if containerName or sizeName are empty - */ - constructor(configuration: Configuration, collection: unknown, - collectionName: string, size: number | undefined, sizeName: string, pluralizer: Pluralizer, - failures: ValidationFailure[]) - { - super(configuration, size, sizeName, failures); - Objects.verifyName(collectionName, "containerName"); - this.collection = collection; - this.collectionName = collectionName; - this.pluralizer = pluralizer; - } - - isNotNegative() - { - // Always true - return this; - } - - isNegative() - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " may not be negative"); - this.failures.push(failure); - return this; - } - - isEqualTo(expected: unknown, name?: string) - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - Objects.requireThatTypeOf(expected, "expected", "number"); - - if (this.actual === undefined || this.actual !== expected) - { - const expectedIsNumber = expected as number; - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.collectionName + " must contain " + name + "(" + expectedIsNumber + ") " + - this.pluralizer.nameOf(expectedIsNumber) + "."); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.collectionName + " must contain " + expectedIsNumber + " " + - this.pluralizer.nameOf(expectedIsNumber) + "."); - } - failure.addContext("Actual", this.actual); - - if (this.actual !== undefined && this.actual > 0) - failure.addContext(this.collectionName, this.collection); - this.failures.push(failure); - } - return this; - } - - isNotEqualTo(value: unknown, name?: string) - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - Objects.requireThatTypeOf(value, "value", "number"); - - if (this.actual === undefined || this.actual === value) - { - const valueIsNumber = value as number; - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.collectionName + " may not contain " + name + " (" + valueIsNumber + ") " + - this.pluralizer.nameOf(valueIsNumber)). - addContext(this.collectionName, this.collection); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.collectionName + " may not contain " + valueIsNumber + " " + - this.pluralizer.nameOf(valueIsNumber)). - addContext(this.collectionName, this.collection); - } - this.failures.push(failure); - } - return this; - } - - isBetween(startInclusive: number, endExclusive: number) - { - Objects.requireThatTypeOf(startInclusive, "startInclusive", "number"); - Objects.requireThatTypeOf(endExclusive, "endExclusive", "number"); - if (endExclusive < startInclusive) - { - throw new RangeError("endExclusive must be greater than or equal to startInclusive.\n" + - "Actual: " + endExclusive + "\n" + - "Min : " + startInclusive); - } - - if (this.actual === undefined || this.actual < startInclusive || this.actual >= endExclusive) - { - const failure = new ValidationFailure(this.config, RangeError, - this.collectionName + " must contain [" + startInclusive + ", " + endExclusive + ") " + - this.pluralizer.nameOf(2) + "."). - addContext("Actual", this.actual); - - if (this.actual !== undefined && this.actual > 0) - failure.addContext(this.collectionName, this.collection); - this.failures.push(failure); - } - return this; - } - - isBetweenClosed(startInclusive: number, endInclusive: number): NumberValidator - { - Objects.requireThatTypeOf(startInclusive, "startInclusive", "number"); - Objects.requireThatTypeOf(endInclusive, "endInclusive", "number"); - if (endInclusive < startInclusive) - { - throw new RangeError("endInclusive must be greater than or equal to startInclusive.\n" + - "Actual: " + endInclusive + "\n" + - "Min : " + startInclusive); - } - - if (this.actual === undefined || this.actual < startInclusive || this.actual > endInclusive) - { - const failure = new ValidationFailure(this.config, RangeError, - this.collectionName + " must contain [" + startInclusive + ", " + endInclusive + "] " + - this.pluralizer.nameOf(2) + "."). - addContext("Actual", this.actual); - - if (this.actual !== undefined && this.actual > 0) - failure.addContext(this.collectionName, this.collection); - this.failures.push(failure); - } - return this; - } -} - -export {SizeValidatorImpl}; \ No newline at end of file diff --git a/src/internal/StringMapper.mts b/src/internal/StringMapper.mts new file mode 100644 index 0000000..cf09ae0 --- /dev/null +++ b/src/internal/StringMapper.mts @@ -0,0 +1,30 @@ +import {internalValueToString} from "./internal.mjs"; + +/** + * Returns the string representation of a value. + * + * @param value - a value + * @param seen - the objects that we've seen before + * @returns the string representation of the value + */ +type StringMapper = (value: unknown, seen?: Set) => string; + +/** + * @param value - a value + * @returns true if the value has the number of parameters expected by `StringMapper` + */ +function isStringMapper(value: unknown): value is StringMapper +{ + return typeof (value) === "function" && value.length >= 1 && value.length <= 2; +} + +/** + * Uses {@link internalValueToString} to convert objects to a string. + */ +const INTERNAL_VALUE_TO_STRING = (value: unknown) => internalValueToString(value); + +export { + type StringMapper, + isStringMapper, + INTERNAL_VALUE_TO_STRING +}; \ No newline at end of file diff --git a/src/internal/StringMappers.mts b/src/internal/StringMappers.mts new file mode 100644 index 0000000..26ec9b2 --- /dev/null +++ b/src/internal/StringMappers.mts @@ -0,0 +1,194 @@ +import { + Type, + internalValueToString, + type StringMapper, + getMapper, + quoteString +} from "./internal.mjs"; + +/** + * Returns the String representation of an object. + */ +class StringMappers +{ + /** + * The default mapper configuration. + */ + public static readonly DEFAULT = new StringMappers(); + public readonly typeToMapper: Map; + + /** + * Creates a new instance. If `typeToMapper` is `undefined` the new instance uses the + * default mappings. Otherwise, it contains a copy of the `typeToMapper` mappings. + * + * @param typeToMapper - a mapping from the name of a type to the string representation of its values + * @throws TypeError if `typeToMapper` is `undefined` or `null` + */ + public constructor(typeToMapper?: Map) + { + if (typeToMapper === undefined) + { + this.typeToMapper = new Map(); + this.typeToMapper.set(Type.STRING, (value: unknown) => quoteString(value as string)); + this.typeToMapper.set(Type.ARRAY, + (value: unknown, seen?: Set) => this.arrayToString(value as boolean[], seen)); + this.typeToMapper.set(Type.namedClass("Set"), + (value: unknown, seen?: Set) => this.setToString(value as Set, seen)); + this.typeToMapper.set(Type.namedClass("Map"), + (value: unknown, seen?: Set) => this.mapToString(value as Map, seen)); + this.typeToMapper.set(Type.namedClass("Error"), + (value: unknown) => this.errorToString(value as Error)); + this.typeToMapper.set(Type.namedClass("Type"), (value: unknown) => (value as Type).toString()); + } + else + this.typeToMapper = new Map(typeToMapper); + } + + /** + * @param array - an array + * @param seen - the objects that we've seen before + * @returns the "deep" String representation of the array + */ + private arrayToString(array: unknown[], seen?: Set) + { + if (seen === undefined) + seen = new Set(); + // We cannot use Arrays.deepToString(array) because it does not delegate to StringMappers.toString() + const elements: string[] = []; + for (const element of array) + { + if (element !== null && Array.isArray(element)) + { + if (seen.add(element)) + elements.push(this.valueToString(element, seen)); + else + elements.push("..."); + } + else + elements.push(this.valueToString(element, seen)); + } + return `[${elements.join(", ")}]`; + } + + /** + * Returns the string representation of a value using the mappers. + * + * @param value - a value + * @param seen - the objects that we've seen before + * @returns the string representation of the value + */ + private valueToString(value: unknown, seen: Set) + { + const mapper = getMapper(value, this.typeToMapper); + return mapper(value, seen); + } + + /** + * @param set - a Set of elements + * @param seen - the objects that we've seen before + * @returns the string representation of the set + */ + private setToString(set: Set, seen?: Set) + { + if (seen === undefined) + seen = new Set(); + return this.orderedToString([...set], seen); + } + + /** + * @param list - an ordered list of values + * @param seen - the objects that we've seen before + * @returns the string representation of `list` + */ + private orderedToString(list: unknown[], seen: Set) + { + let joiner = "["; + for (const element of list) + { + if (element === list) + joiner += "(this Collection)"; + else + { + const elementToString = getMapper(element, this.typeToMapper); + joiner += elementToString(element, seen); + } + } + return joiner + "]"; + } + + /** + * @param map - a Map of elements + * @param seen - the objects that we've seen before + * @returns the String representation of the map + */ + private mapToString(map: Map, seen?: Set) + { + if (seen === undefined) + seen = new Set(); + return this.mapEntriesToString([...map.entries()], seen); + } + + /** + * @param entries - map entries + * @param seen - the objects that we've seen before + * @returns the String representation of the entries + */ + private mapEntriesToString(entries: [unknown, unknown][], seen: Set) + { + let joiner = "{"; + for (const entry of entries) + { + const key = entry[0]; + const value = entry[1]; + let keyAsString: string; + if (key === entries) + keyAsString = "(this Map)"; + else + keyAsString = getMapper(key, this.typeToMapper)(key, seen); + let valueAsString; + if (value === entries) + valueAsString = "(this Map)"; + else + valueAsString = getMapper(value, this.typeToMapper)(value, seen); + joiner += `${keyAsString} = ${valueAsString}`; + } + return joiner + "}"; + } + + /** + * @param error - an `Error` + * @returns the string representation of the error + */ + private errorToString(error: Error) + { + if (error.stack === undefined) + return ""; + return error.stack; + } + + /** + * Returns the string representation of the mappers. + * + * @returns the string representation of the mappers + */ + public toString(): string + /** + * Returns the string representation of a value using the mappers. + * + * @param value - a value + * @returns the string representation of the value + */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + public toString(value: unknown): string + public toString(value?: unknown) + { + const mapper = getMapper(value, this.typeToMapper); + if (mapper === undefined) + return internalValueToString(this.typeToMapper); + else + return mapper(value); + } +} + +export {StringMappers}; \ No newline at end of file diff --git a/src/internal/StringValidatorImpl.mts b/src/internal/StringValidatorImpl.mts deleted file mode 100644 index 5f86397..0000000 --- a/src/internal/StringValidatorImpl.mts +++ /dev/null @@ -1,296 +0,0 @@ -import type { - Configuration, - NumberValidator, - StringValidator, - InetAddressValidator -} from "./internal.mjs"; -import { - AbstractObjectValidator, - Objects, - Pluralizer, - SizeValidatorImpl, - ValidationFailure, - InetAddressValidatorImpl -} from "./internal.mjs"; - -/** - * Default implementation of StringValidator. - */ -class StringValidatorImpl extends AbstractObjectValidator - implements StringValidator -{ - /** - * Creates a new StringValidatorImpl. - * - * @param configuration - the instance configuration - * @param actual - the actual value - * @param name - the name of the value - * @param failures - the list of validation failures - * @throws TypeError if configuration or name are null or undefined - * @throws RangeError if name is empty - */ - constructor(configuration: Configuration, actual: string | undefined, name: string, failures: ValidationFailure[]) - { - super(configuration, actual, name, failures); - } - - /** - * @param value - a String - * @returns true if the String is a valid IPv4 address; false otherwise - */ - private static isIpV4Impl(value: string): boolean - { - // See https://devblogs.microsoft.com/oldnewthing/20060522-08/?p=31113 - const match = (/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/).exec(value); - return match !== null && Number(match[1]) <= 255 && Number(match[2]) <= 255 && - Number(match[3]) <= 255 && Number(match[4]) <= 255; - } - - /** - * @param value - a string - * @returns true if the String is a valid IPv6 address; false otherwise - */ - private static isIpV6Impl(value: string): boolean - { - // See https://blogs.msdn.microsoft.com/oldnewthing/20060522-08/?p=31113 and - // https://4sysops.com/archives/ipv6-tutorial-part-4-ipv6-address-syntax/ - const components = value.split(":"); - if (components.length < 2 || components.length > 8) - return false; - if (components[0] !== "" || components[1] !== "") - { - // Address does not begin with a zero compression ("::") - if (!(/^[\da-f]{1,4}/i).exec(components[0])) - { - // Component must contain 1-4 hex characters - return false; - } - } - - let numberOfColons = 0; - let numberOfZeroCompressions = 0; - for (let i = 1; i < components.length; ++i) - { - ++numberOfColons; - const component = components[i]; - if (component === "") - continue; - if (numberOfColons === 2) - ++numberOfZeroCompressions; - if (numberOfZeroCompressions > 1 || numberOfColons > 2) - { - // Zero compression can only occur once in an address - return false; - } - numberOfColons = 0; - if (!(/^[\da-f]{1,4}/i).exec(component)) - { - // Component must contain 1-4 hex characters - return false; - } - } - if (numberOfColons === 2) - { - ++numberOfZeroCompressions; - numberOfColons = 0; - } - // Lines may not end with a colon. If they end with a zero compression, it must have been the first one. - return numberOfColons === 0 && numberOfZeroCompressions <= 1; - } - - /** - * @param value - a String - * @returns true if the String is a valid hostname; false otherwise - */ - private static isHostnameImpl(value: string): boolean - { - // See http://serverfault.com/a/638270/15584 and - // https://blogs.msdn.microsoft.com/oldnewthing/20120412-00/?p=7873 - const components = value.split("."); - - // Top-level domain names may not be empty or all-numeric - const topLevelDomain = components[components.length - 1]; - if ((/^[a-zA-Z-]+$/).exec(topLevelDomain) === null) - return false; - - let sum = 0; - for (let i = 0; i < components.length; ++i) - { - const label = components[i]; - // label may not be empty. It must consist of only the ASCII alphabetic and numeric characters, plus the - // hyphen. - if ((/^[a-zA-Z0-9-]+$/).exec(label) === null) - return false; - const length = label.length; - if (length > 63) - return false; - if (label.startsWith("-") || label.endsWith("-")) - { - // If the hyphen is used, it is not permitted to appear at either the beginning or end of a label. - return false; - } - // Each label is prefixed by a byte denoting its length - sum += 1 + length; - } - // The last label is terminated by a byte denoting a length of zero - ++sum; - return sum <= 255; - } - - isInetAddress(): InetAddressValidator - { - if (this.actual !== undefined) - { - const actualString = this.actual; - if (StringValidatorImpl.isIpV4Impl(actualString)) - { - return new InetAddressValidatorImpl(this.config, actualString, this.name, true, false, false, - this.failures); - } - if (StringValidatorImpl.isIpV6Impl(actualString)) - { - return new InetAddressValidatorImpl(this.config, actualString, this.name, false, true, false, - this.failures); - } - if (StringValidatorImpl.isHostnameImpl(actualString)) - { - return new InetAddressValidatorImpl(this.config, actualString, this.name, false, false, true, - this.failures); - } - } - const typeOfActual = Objects.getTypeInfo(this.actual); - const failure = new ValidationFailure(this.config, RangeError, - this.name + " must contain a valid IP address or hostname."). - addContext("Actual", this.actual). - addContext("Type", typeOfActual); - this.failures.push(failure); - return new InetAddressValidatorImpl(this.config, undefined, this.name, false, false, false, this.failures); - } - - startsWith(prefix: string): StringValidator - { - if (this.actual === undefined || !this.actual.startsWith(prefix)) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " must start with \"" + this.config.convertToString(prefix) + "\"."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this; - } - - doesNotStartWith(prefix: string): StringValidator - { - if (this.actual === undefined || this.actual.startsWith(prefix)) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " may not start with \"" + this.config.convertToString(prefix) + "\"."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this; - } - - contains(expected: string): StringValidator - { - if (this.actual === undefined || !this.actual.includes(expected)) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " must contain \"" + this.config.convertToString(expected) + "\"."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this; - } - - doesNotContain(value: string): StringValidator - { - if (this.actual === undefined || this.actual.includes(value)) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " may not contain \"" + this.config.convertToString(value) + "\"."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this; - } - - endsWith(suffix: string): StringValidator - { - if (this.actual === undefined || !this.actual.endsWith(suffix)) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " must end with \"" + this.config.convertToString(suffix) + "\"."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this; - } - - doesNotEndWith(suffix: string): StringValidator - { - if (this.actual === undefined || this.actual.endsWith(suffix)) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " may not end with \"" + this.config.convertToString(suffix) + "\"."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this; - } - - isEmpty(): StringValidator - { - if (this.actual === undefined || this.actual.length !== 0) - { - const failure = new ValidationFailure(this.config, RangeError, this.name + " must be empty."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this; - } - - isNotEmpty(): StringValidator - { - if (this.actual === undefined || this.actual.length <= 0) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " may not be empty"); - this.failures.push(failure); - } - return this; - } - - isTrimmed(): StringValidator - { - if (this.actual === undefined || /^\s|\s$/.test(this.actual)) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " may not contain leading or trailing whitespace"). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this; - } - - length(): NumberValidator - { - let value: number | undefined; - if (this.actual === undefined) - value = undefined; - else - value = this.actual.length; - return new SizeValidatorImpl(this.config, this.actual, this.name, value, this.name + ".length", - Pluralizer.CHARACTER, this.failures); - } - - lengthConsumer(consumer: (actual: NumberValidator) => void): StringValidator - { - Objects.requireThatValueIsDefinedAndNotNull(consumer, "consumer"); - if (this.failures.length === 0) - consumer(this.length()); - return this; - } -} - -export {StringValidatorImpl}; \ No newline at end of file diff --git a/src/internal/StringVerifierImpl.mts b/src/internal/StringVerifierImpl.mts deleted file mode 100644 index 1035d1e..0000000 --- a/src/internal/StringVerifierImpl.mts +++ /dev/null @@ -1,97 +0,0 @@ -import type { - NumberVerifier, - StringValidator, - StringVerifier -} from "./internal.mjs"; -import { - AbstractObjectVerifier, - NumberVerifierImpl, - Objects -} from "./internal.mjs"; - -/** - * Default implementation of StringVerifier. - */ -class StringVerifierImpl extends AbstractObjectVerifier - implements StringVerifier -{ - /** - * Creates a new StringVerifierImpl. - * - * @param validator - the validator to delegate to - * @throws TypeError if validator is null or undefined - */ - constructor(validator: StringValidator) - { - super(validator); - } - - startsWith(prefix: string): StringVerifier - { - this.validator.startsWith(prefix); - return this.validationResult(() => this.getThis()); - } - - doesNotStartWith(prefix: string): StringVerifier - { - this.validator.doesNotStartWith(prefix); - return this.validationResult(() => this.getThis()); - } - - contains(expected: string): StringVerifier - { - this.validator.contains(expected); - return this.validationResult(() => this.getThis()); - } - - doesNotContain(value: string): StringVerifier - { - this.validator.doesNotContain(value); - return this.validationResult(() => this.getThis()); - } - - endsWith(suffix: string): StringVerifier - { - this.validator.endsWith(suffix); - return this.validationResult(() => this.getThis()); - } - - doesNotEndWith(suffix: string): StringVerifier - { - this.validator.doesNotEndWith(suffix); - return this.validationResult(() => this.getThis()); - } - - isEmpty(): StringVerifier - { - this.validator.isEmpty(); - return this.validationResult(() => this.getThis()); - } - - isNotEmpty(): StringVerifier - { - this.validator.isNotEmpty(); - return this.validationResult(() => this.getThis()); - } - - isTrimmed(): StringVerifier - { - this.validator.isTrimmed(); - return this.validationResult(() => this.getThis()); - } - - length(): NumberVerifier - { - const newValidator = this.validator.length(); - return this.validationResult(() => new NumberVerifierImpl(newValidator)) as NumberVerifier; - } - - lengthConsumer(consumer: (actual: NumberVerifier) => void): StringVerifier - { - Objects.requireThatValueIsDefinedAndNotNull(consumer, "consumer"); - consumer(this.length()); - return this; - } -} - -export {StringVerifierImpl}; \ No newline at end of file diff --git a/src/internal/Strings.mts b/src/internal/Strings.mts deleted file mode 100644 index eb426f2..0000000 --- a/src/internal/Strings.mts +++ /dev/null @@ -1,99 +0,0 @@ -import {Objects} from "./internal.mjs"; - -class SearchResult -{ - /** - * The start index (inclusive) of the matched text. - */ - readonly start: number; - /** - * The end index (exclusive) of the matched text. - */ - readonly end: number; - - constructor(start: number, end: number) - { - this.start = start; - this.end = end; - } -} - -/** - * String helper functions. - */ -class Strings -{ - /** - * Returns the last consecutive occurrence of target within source. - * The last occurrence of the empty string "" is considered to occur at the index value - * source.length(). - *

- * The returned index is the largest value k for which - * source.startsWith(target, k) consecutively. If no such value of k exists, then - * -1 is returned. - * - * @param source - the string to search within - * @param target - the string to search for - * @returns the index of the last consecutive occurrence of target in source, - * or -1 if there is no such occurrence. - */ - static lastConsecutiveIndexOf(source: string, target: string) - { - Objects.assertThatTypeOf(source, "source", "string"); - Objects.assertThatTypeOf(target, "target", "string"); - const lengthOfTarget = target.length; - let result = -1; - if (lengthOfTarget === 0) - return result; - - for (let i = source.length - lengthOfTarget; i >= 0; i -= lengthOfTarget) - { - if (!source.startsWith(target, i)) - return result; - result = i; - } - return result; - } - - /** - * Returns the last occurrence of target in source. - * - * @param source - the string to search within - * @param target - the regular expression to search for - * @returns null if no match was found. - */ - static lastIndexOf(source: string, target: RegExp): SearchResult | null - { - Objects.assertThatTypeOf(source, "source", "string"); - Objects.assertThatInstanceOf(target, "target", RegExp); - - // RegExp is stateful: https://stackoverflow.com/a/11477448/14731 - let flags = target.flags; - if (!flags.includes("g")) - flags += "g"; - const matcher = new RegExp(target.source, flags); - let match; - let result: SearchResult | null = null; - while (true) - { - match = matcher.exec(source); - if (!match) - break; - result = new SearchResult(match.index, match.index + match[0].length); - } - return result; - } - - /** - * @param source - the string to search within - * @param target - the string to search for - * @returns true if source only contains (potentially multiple) occurrences of - * target or if source is empty - */ - static containsOnly(source: string, target: string) - { - return source.length === 0 || this.lastConsecutiveIndexOf(source, target) === 0; - } -} - -export {Strings}; \ No newline at end of file diff --git a/src/internal/Terminal.mts b/src/internal/Terminal.mts deleted file mode 100644 index 9627766..0000000 --- a/src/internal/Terminal.mts +++ /dev/null @@ -1,166 +0,0 @@ -import chalk from "chalk"; -import { - Objects, - TerminalEncoding, - TerminalEncodings -} from "./internal.mjs"; - - -/** - * The terminal associated with the JVM. - */ -class Terminal -{ - private supportedTypes: TerminalEncoding[] | undefined; - private encoding: TerminalEncoding | undefined; - private width: number | undefined; - - /** - * @returns the encodings supported by the terminal - */ - listSupportedTypes() - { - if (typeof (this.supportedTypes) === "undefined") - { - this.supportedTypes = [TerminalEncoding.NONE]; - // https://stackoverflow.com/a/4224668/14731 - if (typeof (window) === "undefined") - { - // Node - switch (chalk.level) - { - case 3: - this.supportedTypes.push(TerminalEncoding.NODE_16MILLION_COLORS); - // fallthrough - case 2: - this.supportedTypes.push(TerminalEncoding.NODE_256_COLORS); - // fallthrough - case 1: - this.supportedTypes.push(TerminalEncoding.NODE_16_COLORS); - // fallthrough - case 0: - break; - default: - { - throw new RangeError("chalk.level had an unexpected value.\n" + - "Actual: " + String(chalk.level)); - } - } - } - else - { - // Browsers support colors using console.log() but exception messages do not support any colors. - } - this.supportedTypes.sort(TerminalEncodings.sortByDecreasingRank); - } - return this.supportedTypes; - } - - /** - * Indicates the type of encoding that the terminal should use. - *

- * This feature can be used to force the use of colors even when their support is not detected. - * - * @param encoding - the type of encoding that the terminal should use - * @param force - true if the encoding should be forced regardless of what the system supports - * @throws TypeError if encoding is not a TerminalEncoding. - * If force is not a boolean. - * @see #useBestEncoding - */ - private setEncodingImpl(encoding: TerminalEncoding, force: boolean) - { - Objects.assertThatTypeOf(force, "force", "boolean"); - console.debug("setEncodingImpl(%s, %s)", encoding, force); - - if (!this.listSupportedTypes().includes(encoding)) - { - console.debug("User forced the use of an unsupported encoding: %s", encoding); - this.encoding = encoding; - return; - } - this.encoding = encoding; - console.debug("Setting encoding to %s", encoding); - } - - /** - * Indicates the type of encoding that the terminal should use. - *

- * This feature can be used to force the use of colors even when their support is not detected. - * - * @param encoding - the type of encoding that the terminal should use - * @throws TypeError if encoding is not a TerminalEncoding - * @see #useBestEncoding - */ - setEncoding(encoding: TerminalEncoding) - { - this.setEncodingImpl(encoding, true); - } - - /** - * Indicates that verifiers should output the best encoding supported by the terminal. - * - * @see #setEncoding - */ - useBestEncoding() - { - const supportedTypes = this.listSupportedTypes(); - const sortedTypes = supportedTypes.sort(TerminalEncodings.sortByDecreasingRank); - this.setEncodingImpl(sortedTypes[0], false); - } - - /** - * @returns the encoding that the terminal should use (defaults to the best available encoding) - */ - getEncoding() - { - let result = this.encoding; - if (typeof (result) === "undefined") - { - this.useBestEncoding(); - result = this.encoding; - } - return result as TerminalEncoding; - } - - /** - * Indicates the width that the terminal should use. - *

- * This feature can be used to override the default terminal width when it cannot be auto-detected. - * - * @param width - the width that the terminal should use - * @throws TypeError if width is not a number - * @throws RangeError if width is zero or negative - * @see #useBestWidth - */ - setWidth(width: number) - { - Objects.assertThatTypeOf(width, "width", "number"); - if (width <= 0) - throw new RangeError("width must be positive"); - this.width = width; - } - - /** - * Indicates that verifiers should use the best width supported by the terminal. If the width cannot be - * auto-detected, a value of 80 is used. - * - * @see #setWidth(int) - */ - useBestWidth() - { - // Node: https://stackoverflow.com/a/30335724/14731 - this.width = process.stdout.columns || 80; - } - - /** - * @returns the width of the terminal in characters (defaults to the auto-detected width) - */ - getWidth() - { - if (typeof (this.width) === "undefined") - this.useBestWidth(); - return this.width as number; - } -} - -export {Terminal}; \ No newline at end of file diff --git a/src/internal/VariableType.mts b/src/internal/VariableType.mts deleted file mode 100644 index 0789005..0000000 --- a/src/internal/VariableType.mts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Describes the type of a variable. - */ -class VariableType -{ - public static readonly UNDEFINED = new VariableType("undefined"); - public static readonly NULL = new VariableType("null"); - public static readonly BOOLEAN = new VariableType("boolean"); - public static readonly NUMBER = new VariableType("number"); - public static readonly BIGINT = new VariableType("bigint"); - public static readonly STRING = new VariableType("string"); - public static readonly SYMBOL = new VariableType("symbol"); - public static readonly ARRAY = new VariableType("array"); - public static readonly ANONYMOUS_FUNCTION = new VariableType("function"); - public readonly type: "undefined" | "null" | "boolean" | "number" | "bigint" | "string" | "symbol" | - "array" | "function" | "class" | "object"; - public readonly name: null | string; - - public static of(type: "undefined" | "null" | "boolean" | "number" | "bigint" | "string" | "symbol" | - "array" | "function" | "class" | "object", name: null | string = null): VariableType - { - switch (type) - { - case "undefined": - return VariableType.UNDEFINED; - case "null": - return VariableType.NULL; - case "boolean" : - return VariableType.BOOLEAN; - case "number" : - return VariableType.NUMBER; - case "bigint": - return VariableType.BIGINT; - case "string": - return VariableType.STRING; - case "symbol": - return VariableType.SYMBOL; - case "array": - return VariableType.ARRAY; - } - return new VariableType(type, name); - } - - /** - * Creates a new VariableType. - * - * @param type - the name of the type - * @param name - the name of the function or class (Default: null) - * @throws RangeError if neither type or name are set. - * If type does not have a name (e.g. "number" or "array") but name is set. - */ - constructor(type: "undefined" | "null" | "boolean" | "number" | "bigint" | "string" | "symbol" | "array" | - "function" | "class" | "object", name: null | string = null) - { - switch (type) - { - case "undefined": - case "null": - case "boolean" : - case "number" : - case "bigint": - case "string": - case "symbol": - case "array": - if (name !== null) - throw new RangeError(type + " may not have a name"); - } - this.type = type; - this.name = name; - } - - /** - * @returns the string representation of this object - */ - toString() - { - let result; - switch (this.type) - { - case "function": - case "class": - { - result = "a "; - break; - } - case "object": - { - result = "an "; - break; - } - default: - return this.type; - } - result += this.type; - if (this.name !== null) - result += " named " + this.name; - return result; - } -} - -export {VariableType}; \ No newline at end of file diff --git a/src/internal/diff/AbstractColorWriter.mts b/src/internal/diff/AbstractColorWriter.mts deleted file mode 100644 index 157b999..0000000 --- a/src/internal/diff/AbstractColorWriter.mts +++ /dev/null @@ -1,171 +0,0 @@ -import chalk from "chalk"; -import { - AbstractDiffWriter, - IllegalStateError, - Maps -} from "../internal.mjs"; - -/** - * Possible types of decorations. - */ -enum DecorationType -{ - UNDECORATED, - DELETE, - INSERT, - EQUAL -} - -/** - * A node terminal that supports colors. - */ -abstract class AbstractColorWriter extends AbstractDiffWriter -{ - /** - * A padding character used to align values vertically. - */ - static readonly DIFF_PADDING = "/"; - /** - * Maps from a line number in the actual value to the decoration at the end of the line. - */ - private lineToActualDecoration: Map = new Map(); - /** - * Maps from a line number in the expected value to the decoration at the end of the line. - */ - private lineToExpectedDecoration: Map = new Map(); - - protected constructor() - { - super(AbstractColorWriter.DIFF_PADDING); - this.initActualLine(0); - this.initExpectedLine(0); - } - - /** - * @param length - the length of the padding - * @returns the (possibly decorated) padding - */ - decoratePadding(length: number): string - { - return chalk.bgBlack(super.decoratePadding(length)); - } - - initActualLine(number: number): void - { - super.initActualLine(number); - if (!this.lineToActualDecoration.get(number)) - this.lineToActualDecoration.set(number, DecorationType.UNDECORATED); - } - - initExpectedLine(number: number): void - { - super.initExpectedLine(number); - if (!this.lineToExpectedDecoration.get(number)) - this.lineToExpectedDecoration.set(number, DecorationType.UNDECORATED); - } - - writeEqual(text: string): void - { - if (this.closed) - throw new IllegalStateError("Writer must be open"); - if (text.length === 0) - return; - this.splitLines(text, (line: string) => - { - const actualDecoration = this.lineToActualDecoration.get(this.actualLineNumber); - if (actualDecoration === DecorationType.EQUAL) - Maps.appendToValue(this.lineToActualLine, this.actualLineNumber, line); - else - { - Maps.appendToValue(this.lineToActualLine, this.actualLineNumber, this.decorateEqualText(line)); - this.lineToActualDecoration.set(this.actualLineNumber, DecorationType.EQUAL); - } - - if (this.expectedLineNumber !== this.actualLineNumber) - { - const length = line.length; - const padding = this.decoratePadding(length); - Maps.appendToValue(this.lineToExpectedLine, this.actualLineNumber, padding); - this.lineToExpectedDecoration.set(this.actualLineNumber, DecorationType.EQUAL); - - Maps.appendToValue(this.lineToActualLine, this.expectedLineNumber, padding); - this.lineToActualDecoration.set(this.expectedLineNumber, DecorationType.EQUAL); - } - - const expectedDecoration = this.lineToExpectedDecoration.get(this.expectedLineNumber); - if (expectedDecoration === DecorationType.EQUAL) - Maps.appendToValue(this.lineToExpectedLine, this.expectedLineNumber, line); - else - { - Maps.appendToValue(this.lineToExpectedLine, this.expectedLineNumber, this.decorateEqualText(line)); - this.lineToExpectedDecoration.set(this.expectedLineNumber, DecorationType.EQUAL); - } - }, () => - { - this.writeActualNewline(); - this.writeExpectedNewline(); - }); - } - - writeDeleted(text: string): void - { - if (this.closed) - throw new IllegalStateError("Writer must be open"); - if (text.length === 0) - return; - this.splitLines(text, (line: string) => - { - const actualDecoration = this.lineToActualDecoration.get(this.actualLineNumber); - if (actualDecoration === DecorationType.DELETE) - Maps.appendToValue(this.lineToActualLine, this.actualLineNumber, line); - else - { - Maps.appendToValue(this.lineToActualLine, this.actualLineNumber, this.decorateDeletedText(line)); - this.lineToActualDecoration.set(this.actualLineNumber, DecorationType.DELETE); - } - - const expectedDecoration = this.lineToExpectedDecoration.get(this.expectedLineNumber); - if (expectedDecoration === DecorationType.DELETE) - { - Maps.appendToValue(this.lineToExpectedLine, this.expectedLineNumber, - super.decoratePadding(line.length)); - } - else - { - Maps.appendToValue(this.lineToExpectedLine, this.expectedLineNumber, - this.decoratePadding(line.length)); - this.lineToExpectedDecoration.set(this.expectedLineNumber, DecorationType.DELETE); - } - }, this.writeActualNewline.bind(this)); - } - - writeInserted(text: string): void - { - if (this.closed) - throw new IllegalStateError("Writer must be open"); - if (text.length === 0) - return; - this.splitLines(text, (line: string) => - { - const actualDecoration = this.lineToActualDecoration.get(this.actualLineNumber); - if (actualDecoration === DecorationType.INSERT) - Maps.appendToValue(this.lineToActualLine, this.actualLineNumber, super.decoratePadding(line.length)); - else - { - Maps.appendToValue(this.lineToActualLine, this.actualLineNumber, this.decoratePadding(line.length)); - this.lineToActualDecoration.set(this.actualLineNumber, DecorationType.INSERT); - } - - const expectedDecoration = this.lineToExpectedDecoration.get(this.expectedLineNumber); - if (expectedDecoration === DecorationType.INSERT) - Maps.appendToValue(this.lineToExpectedLine, this.expectedLineNumber, line); - else - { - Maps.appendToValue(this.lineToExpectedLine, this.expectedLineNumber, this.decorateInsertedText(line)); - this.lineToExpectedDecoration.set(this.expectedLineNumber, DecorationType.INSERT); - } - }, this.writeExpectedNewline.bind(this)); - } -} - -export {AbstractColorWriter}; \ No newline at end of file diff --git a/src/internal/diff/AbstractDiffWriter.mts b/src/internal/diff/AbstractDiffWriter.mts deleted file mode 100644 index acacfeb..0000000 --- a/src/internal/diff/AbstractDiffWriter.mts +++ /dev/null @@ -1,241 +0,0 @@ -import { - IllegalStateError, - Maps, - NEWLINE_MARKER, - NEWLINE_PATTERN, - Objects -} from "../internal.mjs"; - -/** - * Base implementation for all diff writers. - */ -abstract class AbstractDiffWriter -{ - protected lineToActualLine: Map = new Map(); - protected lineToExpectedLine: Map = new Map(); - protected actualLineNumber = 0; - protected expectedLineNumber = 0; - private readonly paddingMarker: string; - private actualLines: string[] = []; - private expectedLines: string[] = []; - protected closed = false; - - /** - * Adds text that did not change. - * - * @param text - the text - * @throws IllegalStateError if the writer is closed - */ - abstract writeEqual(text: string): void; - - /** - * Deletes text that is present in actual but not expected. - * - * @param text - the text - * @throws IllegalStateError if the writer is closed - */ - abstract writeDeleted(text: string): void; - - /** - * Adds text that is present in expected but not actual. - * - * @param text - the text - * @throws IllegalStateError if the writer is closed - */ - abstract writeInserted(text: string): void; - - /** - * @param paddingMarker - a padding character used to align values vertically - * @throws TypeError if any of the arguments are null - */ - protected constructor(paddingMarker: string) - { - Objects.assertThatTypeOf(paddingMarker, "paddingMarker", "string"); - this.paddingMarker = paddingMarker; - } - - /** - * Splits text into one or more lines, invoking writeNewline() in place of each newline - * character. - * - * @param text - some text - * @param lineConsumer - consumes one line at a time - * @param writeNewLine - ends the current line - */ - splitLines(text: string, lineConsumer: (line: string) => void, writeNewLine: () => void): void - { - const lines = text.split(NEWLINE_PATTERN); - let line; - for (let i = 0; i < lines.length; ++i) - { - const isLastLine = i === lines.length - 1; - line = ""; - line += lines[i]; - if (!isLastLine) - line += NEWLINE_MARKER; - if (line.length > 0) - lineConsumer(line); - if (!isLastLine) - writeNewLine(); - } - } - - /** - * @param length - the length of the padding - * @returns the (possibly decorated) padding - */ - decoratePadding(length: number): string - { - return this.paddingMarker.repeat(length); - } - - /** - * @param text - the text that did not change - * @returns the (possibly decorated) text - */ - decorateEqualText(text: string): string - { - return text; - } - - /** - * @param text - the text that was inserted - * @returns the (possibly decorated) text - */ - decorateInsertedText(text: string): string - { - return text; - } - - /** - * @param text - the text that was deleted - * @returns the (possibly decorated) text - */ - decorateDeletedText(text: string): string - { - return text; - } - - /** - * Invoked before closing the writer. - */ - beforeClose(): void - { - // do nothing - } - - /** - * Invoked after closing the writer. - */ - afterClose(): void - { - // do nothing - } - - /** - * @returns a padding character used to align values vertically - */ - getPaddingMarker(): string - { - return this.paddingMarker; - } - - /** - * Populates the state of lineTo* variables for a new actual line. - * - * @param number - the line number to initialize - */ - initActualLine(number: number): void - { - if (!this.lineToActualLine.get(number)) - this.lineToActualLine.set(number, ""); - } - - /** - * Populates the state of lineTo* variables for a new expected line. - * - * @param number - the line number to initialize - */ - initExpectedLine(number: number): void - { - if (!this.lineToExpectedLine.get(number)) - this.lineToExpectedLine.set(number, ""); - } - - /** - * Ends the current line. - */ - writeActualNewline(): void - { - ++this.actualLineNumber; - this.initActualLine(this.actualLineNumber); - if (!this.lineToExpectedLine.get(this.actualLineNumber)) - this.initExpectedLine(this.actualLineNumber); - } - - /** - * Ends the current line. - */ - writeExpectedNewline(): void - { - ++this.expectedLineNumber; - this.initExpectedLine(this.expectedLineNumber); - if (!this.lineToActualLine.get(this.expectedLineNumber)) - this.initActualLine(this.expectedLineNumber); - } - - /** - * Releases any resources associated with this object. - */ - close(): void - { - if (this.closed) - return; - this.closed = true; - this.beforeClose(); - - for (const actualLine of Maps.sortByKeys(this.lineToActualLine).values()) - this.actualLines.push(actualLine); - Object.freeze(this.actualLines); - - for (const expectedLine of Maps.sortByKeys(this.lineToExpectedLine).values()) - this.expectedLines.push(expectedLine); - Object.freeze(this.expectedLines); - this.afterClose(); - } - - /** - * @returns the lines of the actual value - * @throws IllegalStateError if the writer was closed - */ - getActualLines(): string[] - { - if (!this.closed) - throw new IllegalStateError("Writer must be closed"); - return this.actualLines; - } - - /** - * @returns the lines to display after "actual" and before "expected" (empty lines should not be displayed) - * @throws RangeError if the writer is open - */ - getDiffLines(): string[] - { - if (!this.closed) - throw new RangeError("Writer must be closed"); - return []; - } - - /** - * @returns the lines of the expected value - * @throws IllegalStateError if the writer was closed - */ - getExpectedLines(): string[] - { - if (!this.closed) - throw new IllegalStateError("Writer must be closed"); - return this.expectedLines; - } -} - -export {AbstractDiffWriter}; \ No newline at end of file diff --git a/src/internal/diff/ContextGenerator.mts b/src/internal/diff/ContextGenerator.mts deleted file mode 100644 index 8c7ef5b..0000000 --- a/src/internal/diff/ContextGenerator.mts +++ /dev/null @@ -1,317 +0,0 @@ -import isEqual from "lodash/isEqual.js"; -import { - Configuration, - ContextLine, - DiffGenerator, - Objects, - TextOnly -} from "../internal.mjs"; - -/** - * Returns the difference between two values as an exception context. - */ -class ContextGenerator -{ - /** - * A regular expression that matches lines that are not equal. - */ - private static readonly LINES_NOT_EQUAL = new RegExp("[^" + TextOnly.DIFF_EQUAL + "]+"); - /** - * A pattern matching the end of a line or stream. - */ - private static readonly EOL_PATTERN = /\\n|\\0$/; - private readonly config: Configuration; - private readonly diffGenerator: DiffGenerator; - - /** - * @param configuration - the instance configuration - * @throws TypeError if configuration is null - */ - constructor(configuration: Configuration) - { - Objects.assertThatInstanceOf(configuration, "configuration", Configuration); - - this.config = configuration; - this.diffGenerator = new DiffGenerator(configuration.getGlobalConfiguration().getTerminalEncoding()); - } - - /** - * Updates the last context entry to indicate that duplicate lines were skipped. - * - * @param entries - the exception context - */ - private skipDuplicateLines(entries: ContextLine[]) - { - entries.push(new ContextLine(this.config, "", "")); - entries.push(new ContextLine(this.config, "", "[...]")); - } - - /** - * @param actualLine - the actual lines - * @param expectedLine - the expected lines - * @param diffLine - the diff associated with the line - * @returns true if the lines being compared are equal to each other - */ - private static linesAreEqual(actualLine: string, expectedLine: string, diffLine: string): boolean - { - if (diffLine.length !== 0) - return !ContextGenerator.LINES_NOT_EQUAL.test(diffLine); - return actualLine === expectedLine; - } - - /** - * @param actualLines - the lines of the actual value - * @param expectedLines - the lines of the expected value - * @returns true - * @throws RangeError if the number of lines does not match - */ - private static requireThatNumberOfLinesAreEqual(actualLines: string[], expectedLines: string[]) - { - if (actualLines.length !== expectedLines.length) - { - throw new RangeError("actualLines.size() !== expectedLines\n" + - "actualLines: " + actualLines.length + "\n" + - "expectedLines: " + expectedLines.length); - } - return true; - } - - /** - * @param actualName - the name of the actual value - * @param actualValue - the actual value - * @param expectedName - the name of the expected value - * @param expectedValue - the expected value - * @param expectedInMessage - true if the expected value is already mentioned in the failure message - * @param compareTypes - true if the actual and expected types (classes) should be compared if their values - * are equal (Default: true) - * @returns the list of name-value pairs to append to the exception message - * @throws TypeError if actualName or expectedName are not a string - * @throws RangeError if actualName or expectedName are empty; if - * expectedInMessage is not a boolean - */ - // eslint-disable-next-line max-statements - getContext(actualName: string, actualValue: unknown, expectedName: string, - expectedValue: unknown, expectedInMessage: boolean, compareTypes = true): ContextLine[] - { - Objects.assertThatStringNotEmpty(actualName, "actualName"); - Objects.assertThatStringNotEmpty(expectedName, "expectedName"); - Objects.assertThatTypeOf(expectedInMessage, "expectedInMessage", "boolean"); - - const actualInfo = Objects.getTypeInfo(actualValue); - const expectedInfo = Objects.getTypeInfo(expectedValue); - if (actualInfo.type === "array" && expectedInfo.type === "array") - { - return this.getContextForArrays(actualName, actualValue as unknown[], expectedName, - expectedValue as unknown[], expectedInMessage); - } - // Don't diff booleans - const typeIsDiffable = (actualInfo.type !== "boolean"); - if (!typeIsDiffable || !this.config.isDiffEnabled()) - { - const result: ContextLine[] = []; - result.push(new ContextLine(this.config, actualName, actualValue)); - if (!expectedInMessage) - result.push(new ContextLine(this.config, expectedName, expectedValue)); - return result; - } - const actualIsString = this.config.convertToString(actualValue); - const expectedIsString = this.config.convertToString(expectedValue); - const lines = this.diffGenerator.diff(actualIsString, expectedIsString); - const actualLines = lines.getActualLines(); - const expectedLines = lines.getExpectedLines(); - const diffLines = lines.getDiffLines(); - Objects.assert(ContextGenerator.requireThatNumberOfLinesAreEqual(actualLines, expectedLines)); - const numberOfLines = actualLines.length; - const result: ContextLine[] = []; - if (numberOfLines === 1) - { - const actualLine = actualLines[0]; - const expectedLine = expectedLines[0]; - let diffLine; - if (diffLines.length === 0) - diffLine = ""; - else - diffLine = diffLines[0]; - const stringsAreEqual = ContextGenerator.linesAreEqual(actualLine, expectedLine, diffLine); - result.push(new ContextLine(this.config, "", "")); - result.push(new ContextLine(this.config, actualName, actualLine)); - if (diffLine.length !== 0 && !stringsAreEqual) - result.push(new ContextLine(this.config, "Diff", diffLine)); - result.push(new ContextLine(this.config, expectedName, expectedLine)); - if (compareTypes && ContextGenerator.linesAreEqual(actualLine, expectedLine, diffLine)) - { - // If the String representation of the values is equal, output getClass(), hashCode(), - // or System.identityHashCode()] that differ. - result.push(...this.compareTypes(actualName, actualValue, expectedName, expectedValue)); - } - return result; - } - let actualLineNumber = 0; - let expectedLineNumber = 0; - // Indicates if the previous line was identical - let skippedDuplicates = false; - for (let i = 0; i < numberOfLines; ++i) - { - const actualLine = actualLines[i]; - let expectedLine; - if (expectedLines.length > i) - expectedLine = expectedLines[i]; - else - expectedLine = ""; - let diffLine; - if (diffLines.length === 0) - diffLine = ""; - else - diffLine = diffLines[i]; - const currentLineIsEqual = ContextGenerator.linesAreEqual(actualLine, expectedLine, diffLine); - if (i !== 0 && i !== numberOfLines - 1 && currentLineIsEqual) - { - // Skip identical lines, unless they are the first or last line. - skippedDuplicates = true; - ++actualLineNumber; - ++expectedLineNumber; - continue; - } - let actualNameForLine; - if (this.diffGenerator.isEmpty(actualLine)) - actualNameForLine = actualName; - else - { - actualNameForLine = actualName + "@" + actualLineNumber; - if (ContextGenerator.EOL_PATTERN.test(actualLine)) - ++actualLineNumber; - } - if (skippedDuplicates) - { - skippedDuplicates = false; - this.skipDuplicateLines(result); - } - result.push(new ContextLine(this.config, "", "")); - result.push(new ContextLine(this.config, actualNameForLine, actualLine)); - if (diffLine.length !== 0 && !currentLineIsEqual) - result.push(new ContextLine(this.config, "Diff", diffLine)); - let expectedNameForLine; - if (this.diffGenerator.isEmpty(expectedLine)) - expectedNameForLine = expectedName; - else - { - expectedNameForLine = expectedName + "@" + expectedLineNumber; - if (ContextGenerator.EOL_PATTERN.test(expectedLine)) - ++expectedLineNumber; - } - result.push(new ContextLine(this.config, expectedNameForLine, expectedLine)); - } - return result; - } - - /** - * @param actualName -the name of the actual value - * @param actualValue - the actual value - * @param expectedName - the name of the expected value - * @param expectedValue - the expected value - * @param expectedInMessage - true if the expected value is already mentioned in the failure message - * @returns the list of name-value pairs to append to the exception message - * @throws TypeError if actualName or expectedName are not a string - * @throws RangeError if actualName or expectedName are empty; if - * expectedInMessage is not a boolean - */ - // eslint-disable-next-line max-statements - private getContextForArrays(actualName: string, actualValue: unknown[], expectedName: string, - expectedValue: unknown[], expectedInMessage: boolean) - { - Objects.assertThatStringNotEmpty(actualName, "actualName"); - Objects.assertThatTypeOf(actualValue, "actualValue", "array"); - Objects.assertThatStringNotEmpty(expectedName, "expectedName"); - Objects.assertThatTypeOf(expectedValue, "expectedValue", "array"); - Objects.assertThatTypeOf(expectedInMessage, "expectedInMessage", "boolean"); - - if (!this.config.isDiffEnabled()) - { - const result: ContextLine[] = []; - result.push(new ContextLine(this.config, actualName, actualValue)); - if (!expectedInMessage) - result.push(new ContextLine(this.config, expectedName, expectedValue)); - return result; - } - const actualSize = actualValue.length; - const expectedSize = expectedValue.length; - const maxSize = Math.max(actualSize, expectedSize); - - const result: ContextLine[] = []; - // Indicates if the previous index was equal - let skippedDuplicates = false; - let actualIndex = 0; - let expectedIndex = 0; - for (let i = 0; i < maxSize; ++i) - { - let elementsAreEqual = true; - let actualValueIsString; - let actualNameForElement; - if (i < actualSize) - { - actualValueIsString = this.config.convertToString(actualValue[i]); - actualNameForElement = actualName + "[" + actualIndex + "]"; - ++actualIndex; - } - else - { - actualValueIsString = ""; - actualNameForElement = actualName; - elementsAreEqual = false; - } - let expectedValueIsString; - let expectedNameForElement; - if (i < expectedSize) - { - expectedValueIsString = this.config.convertToString(expectedValue[i]); - expectedNameForElement = expectedName + "[" + expectedIndex + "]"; - ++expectedIndex; - } - else - { - expectedValueIsString = ""; - expectedNameForElement = expectedName; - elementsAreEqual = false; - } - if (elementsAreEqual) - elementsAreEqual = actualValue[i] === expectedValue[i]; - if (i !== 0 && i !== maxSize - 1 && elementsAreEqual) - { - // Skip identical elements, unless they are the first or last element. - skippedDuplicates = true; - continue; - } - if (skippedDuplicates) - { - skippedDuplicates = false; - this.skipDuplicateLines(result); - } - result.push(...this.getContext(actualNameForElement, actualValueIsString, - expectedNameForElement, expectedValueIsString, false, !elementsAreEqual)); - } - return result; - } - - /** - * @param actualName - the name of the actual value - * @param actualValue - the actual value - * @param expectedName - the name of the expected value - * @param expectedValue - the expected value - * @returns the list of name-value pairs to append to the exception message - * @throws TypeError if actualName or expectedName are null - */ - private compareTypes(actualName: string, actualValue: unknown, expectedName: string, - expectedValue: unknown): ContextLine[] - { - const actualType = Objects.getTypeInfo(actualValue); - const expectedType = Objects.getTypeInfo(expectedValue); - if (!isEqual(actualType, expectedType)) - { - return this.getContext(actualName + ".class", actualType, expectedName + ".class", expectedType, false, - false); - } - return []; - } -} - -export {ContextGenerator}; \ No newline at end of file diff --git a/src/internal/diff/DiffConstants.mts b/src/internal/diff/DiffConstants.mts deleted file mode 100644 index d582e03..0000000 --- a/src/internal/diff/DiffConstants.mts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * String denoting the end of a line. - */ -const NEWLINE_MARKER = "\\n"; -/** - * A pattern matching newline characters anywhere in a string. - */ -const NEWLINE_PATTERN = /\r?\n/; - -export -{ - NEWLINE_MARKER, - NEWLINE_PATTERN -}; \ No newline at end of file diff --git a/src/internal/diff/DiffGenerator.mts b/src/internal/diff/DiffGenerator.mts deleted file mode 100644 index 3a701e9..0000000 --- a/src/internal/diff/DiffGenerator.mts +++ /dev/null @@ -1,440 +0,0 @@ -import type {Change} from "diff"; -import {diffChars} from "diff"; -import stripAnsi from "strip-ansi"; -import { - DiffResult, - Node16Colors, - Node16MillionColors, - Node256Colors, - Objects, - Strings, - TerminalEncoding, - TerminalEncodings, - TextOnly -} from "../internal.mjs"; - -/** - * Character denoting the end of string. - */ -const EOS_MARKER = "\\0"; -// See https://www.regular-expressions.info/unicode.html for an explanation of \p{Zs}. -// https://stackoverflow.com/a/12002085/14731: Surrounding the regex with parenthesis causes the delimited to -// be returned. -const WORDS = /(\p{Zs}+|\r?\n|[.[\](){}/\\*+\-#])/u; - -/** - * @param deltas - a list of deltas - * @returns the number of deltas that indicate that text was not equal - */ -function numberOfUnequalDeltas(deltas: Change[]): number -{ - let result = 0; - for (const delta of deltas) - { - if (delta.removed || delta.added) - ++result; - } - return result; -} - -/** - * Write a single word. - * - * @param delta - a delta - * @param writer - the writer to write into - */ -function writeDelta(delta: Change, writer: TextOnly | Node16Colors | Node256Colors | Node16MillionColors): - void -{ - if (delta.added) - writer.writeInserted(delta.value); - else if (delta.removed) - writer.writeDeleted(delta.value); - else - writer.writeEqual(delta.value); -} - -/** - * For every word that is associated with 2 or more unequal deltas, replace the deltas with a single - * [DELETE actual, INSERT expected] pair. - */ -class ReduceDeltasPerWord -{ - /** - * The deltas to process. - * - * Syntax: [optional] (mandatory) - * - * word: (start-delta) (end-delta) - * start-delta: [pre-word] [delimiter] (word-in-start-delta) - * end-delta: (word-in-end-delta) [delimiter] [post-word] - * delimiter: whitespace found in EQUAL deltas - */ - private deltas: Change[] = []; - /** - * The length of deltas. - */ - private numberOfDeltas = 0; - /** - * The index of the start delta. - */ - private indexOfStartDelta = 0; - /** - * The index of the word in the start delta. - */ - private indexOfWordInStartDelta = 0; - /** - * The index of the end delta. - */ - private indexOfEndDelta = 0; - /** - * The index of the delimiter in the end delta. If there is no delimiter, points to the end of the string. - */ - private indexOfDelimiterInEndDelta = 0; - /** - * the start of the next word in the end delta. - * If there are no followup words, points to the end of the string. - */ - private indexOfNextWordInEndDelta = 0; - private actualBuilder = ""; - private expectedBuilder = ""; - - /** - * @param deltas - the deltas to update - */ - accept(deltas: Change[]): void - { - this.deltas = deltas; - this.numberOfDeltas = deltas.length; - // We are looking for words that span multiple deltas. If the current delta contains multiple - // words, we are interested in the latest one. - this.findFirstWord(); - if (this.indexOfStartDelta === this.numberOfDeltas) - return; - do - { - this.findEndOfWord(); - this.updateDeltas(); - } while (this.findNextWord()); - } - - /** - * Finds the first word. - */ - findFirstWord(): void - { - // Words start after a whitespace delimiter within an EQUAL delta. If none is found, the start - // of the first delta acts as a word boundary. - const delta = this.deltas[0]; - this.indexOfStartDelta = 0; - const result = Strings.lastIndexOf(delta.value, WORDS); - if (result === null) - this.indexOfWordInStartDelta = 0; - else - this.indexOfWordInStartDelta = result.end; - } - - /** - * Finds the end of the word. - */ - findEndOfWord(): void - { - // Words end at a whitespace delimiter found within an EQUAL delta. If none is found, the end of the - // last delta acts as a word boundary. - for (let i = this.indexOfStartDelta + 1; i < this.numberOfDeltas; ++i) - { - const delta = this.deltas[i]; - if (!delta.removed && !delta.added) - { - const match = WORDS.exec(delta.value); - if (match) - { - this.indexOfDelimiterInEndDelta = match.index; - this.indexOfNextWordInEndDelta = match.index + match[0].length; - this.indexOfEndDelta = i; - return; - } - } - } - this.indexOfEndDelta = this.numberOfDeltas - 1; - } - - /** - * Update the deltas if necessary. - */ - updateDeltas(): void - { - const deltasInWord = this.deltas.slice(this.indexOfStartDelta, this.indexOfEndDelta + 1); - if (numberOfUnequalDeltas(deltasInWord) <= 2) - { - // If the word contains 2 or less unequal deltas then provide character-level granularity. - // - // Good: - // -----=====----- - // -----+++++===== - // =====-----+++++ - // - // Bad: - // =====-----=====----- - // +++++=====+++++===== - // -----++++++----===== - return; - } - // Otherwise, replace the deltas with a single [DELETE, INSERT] pair - const updatedDeltas: Change[] = []; - this.actualBuilder = ""; - this.expectedBuilder = ""; - this.processStartDelta(updatedDeltas); - this.processMiddleDeltas(); - this.processEndDelta(updatedDeltas); - - const deltasRemoved = deltasInWord.length - updatedDeltas.length; - // https://stackoverflow.com/a/17511398/14731 - this.deltas.splice(this.indexOfStartDelta, deltasInWord.length, ...updatedDeltas); - this.numberOfDeltas -= deltasRemoved; - this.indexOfEndDelta -= deltasRemoved; - this.indexOfNextWordInEndDelta -= this.indexOfDelimiterInEndDelta; - } - - /** - * Processes the start delta. - * - * @param updatedDeltas - a list to insert updated deltas into - */ - processStartDelta(updatedDeltas: Change[]): void - { - const delta = this.deltas[this.indexOfStartDelta]; - let actualWord; - let expectedWord; - let beforeWord; - - if (!delta.added && !delta.removed) - { - // Equal - const actual = delta.value; - actualWord = actual.substring(this.indexOfWordInStartDelta); - expectedWord = actualWord; - beforeWord = actual.substring(0, this.indexOfWordInStartDelta); - } - else if (delta.added) - { - // Insert - actualWord = ""; - expectedWord = delta.value; - beforeWord = ""; - } - else - { - // Delete - const actual = delta.value; - actualWord = actual.substring(this.indexOfWordInStartDelta); - expectedWord = ""; - beforeWord = actual.substring(0, this.indexOfWordInStartDelta); - } - this.actualBuilder += actualWord; - this.expectedBuilder += expectedWord; - - if (this.indexOfWordInStartDelta > 0) - { - updatedDeltas.push( - { - added: delta.added, - removed: delta.removed, - value: beforeWord - }); - } - } - - /** - * Processes the middle deltas. - */ - processMiddleDeltas(): void - { - for (let i = this.indexOfStartDelta + 1; i < this.indexOfEndDelta; ++i) - { - const delta = this.deltas[i]; - if (!delta.added) - { - // Deleted or equal - this.actualBuilder += delta.value; - } - if (!delta.removed) - { - // Inserted or equal - this.expectedBuilder += delta.value; - } - } - } - - /** - * Processes the end delta. - * - * @param updatedDeltas - a list to insert updated deltas into - */ - processEndDelta(updatedDeltas: Change[]): void - { - const delta = this.deltas[this.indexOfEndDelta]; - const actual = delta.value; - const actualWord = actual.substring(0, this.indexOfDelimiterInEndDelta); - - // Word before delimiter - this.actualBuilder += actualWord; - if (!delta.removed) - { - // Insert or equal - this.expectedBuilder += delta.value.substring(0, this.indexOfDelimiterInEndDelta); - } - - const deleteActual: Change = { - value: this.actualBuilder, - added: false, - removed: true - }; - const insertExpected: Change = { - value: this.expectedBuilder, - added: true, - removed: false - }; - updatedDeltas.push(deleteActual); - updatedDeltas.push(insertExpected); - - // Word after delimiter - if (this.indexOfDelimiterInEndDelta < actual.length) - { - updatedDeltas.push( - { - added: delta.added, - removed: delta.removed, - value: delta.value.substring(this.indexOfDelimiterInEndDelta) - }); - } - } - - /** - * Finds the next word. - * - * @returns false if there are no more words to be found - */ - findNextWord(): boolean - { - this.indexOfStartDelta = this.indexOfEndDelta; - if (this.indexOfStartDelta === this.numberOfDeltas - 1) - return false; - - // Similar logic as findFirstWord() - const delta = this.deltas[this.indexOfStartDelta]; - if (!delta.added && !delta.removed) - { - // Equal - const result = Strings.lastIndexOf(delta.value, WORDS); - if (result === null) - { - throw new Error("Expecting result to be equal to indexOfNextWordInEndDelta (" + - this.indexOfNextWordInEndDelta + ") or later.\n" + - "delta.value: " + delta.value); - } - this.indexOfWordInStartDelta = result.end; - } - return true; - } -} - -/** - * Generates a diff of two strings. - */ -class DiffGenerator -{ - private readonly terminalEncoding: TerminalEncoding; - private readonly paddingMarker: string; - private readonly reduceDeltasPerWord: ReduceDeltasPerWord; - - /** - * @param terminalEncoding - the terminal encoding - */ - constructor(terminalEncoding: TerminalEncoding) - { - this.terminalEncoding = terminalEncoding; - this.paddingMarker = TerminalEncodings.getPaddingMarker(terminalEncoding); - this.reduceDeltasPerWord = new ReduceDeltasPerWord(); - } - - /** - * Generates the diff of two strings. - *

- * NOTE: Colors may be disabled when stdin or stdout are redirected. - * To override this behavior, use {@link GlobalRequirements.withTerminalEncoding}. - * - * @param actual - the actual value - * @param expected - the expected value - * @returns the calculated diff - * @throws TypeError if any of the arguments are null - */ - diff(actual: string, expected: string): DiffResult - { - Objects.assertThatTypeOf(actual, "actual", "string"); - Objects.assertThatTypeOf(expected, "expected", "string"); - - // Mark the end of the string to guard against cases that end with whitespace - const actualWithEos = actual + EOS_MARKER; - const expectedWithEos = expected + EOS_MARKER; - const writer = this.createDiffWriter(this.terminalEncoding); - const deltas = diffChars(actualWithEos, expectedWithEos); - this.reduceDeltasPerWord.accept(deltas); - for (const delta of deltas) - writeDelta(delta, writer); - writer.close(); - return new DiffResult(writer.getActualLines(), writer.getDiffLines(), writer.getExpectedLines(), - writer.getPaddingMarker()); - } - - /** - * @param terminalEncoding - the encoding to use for the terminal - * @returns a writer that generates a diff - */ - createDiffWriter(terminalEncoding: TerminalEncoding): - TextOnly | Node16Colors | Node256Colors | Node16MillionColors - { - switch (terminalEncoding) - { - case TerminalEncoding.NONE: - return new TextOnly(); - case TerminalEncoding.NODE_16_COLORS: - return new Node16Colors(); - case TerminalEncoding.NODE_256_COLORS: - return new Node256Colors(); - case TerminalEncoding.NODE_16MILLION_COLORS: - return new Node16MillionColors(); - default: - throw new RangeError(Objects.toString(terminalEncoding)); - } - } - - /** - * @param line - a line - * @returns if line only contains padding characters - */ - isEmpty(line: string): boolean - { - switch (this.terminalEncoding) - { - case TerminalEncoding.NONE: - break; - case TerminalEncoding.NODE_16_COLORS: - case TerminalEncoding.NODE_256_COLORS: - case TerminalEncoding.NODE_16MILLION_COLORS: - { - line = stripAnsi(line); - break; - } - default: - throw new RangeError(Objects.toString(this.terminalEncoding)); - } - return Strings.containsOnly(line, this.paddingMarker); - } -} - -export -{ - DiffGenerator, - EOS_MARKER -}; \ No newline at end of file diff --git a/src/internal/diff/DiffResult.mts b/src/internal/diff/DiffResult.mts deleted file mode 100644 index 55fc72b..0000000 --- a/src/internal/diff/DiffResult.mts +++ /dev/null @@ -1,67 +0,0 @@ -import {Objects} from "../internal.mjs"; - -/** - * The result of calculating the difference between two strings. - */ -class DiffResult -{ - private readonly actualLines: string[]; - private readonly diffLines: string[]; - private readonly expectedLines: string[]; - private readonly paddingMarker: string; - - /** - * @param actualLines - the lines of the actual string - * @param diffLines - optional lines denoting the difference between "actual" and "expected" - * @param expectedLines - the lines of the expected string - * @param paddingMarker - a padding character used to align values vertically - * @throws TypeError if any of the arguments are null - */ - constructor(actualLines: string[], diffLines: string[], expectedLines: string[], paddingMarker: string) - { - Objects.assertThatTypeOf(actualLines, "actualLines", "array"); - Objects.assertThatTypeOf(diffLines, "diffLines", "array"); - Objects.assertThatTypeOf(expectedLines, "expectedLines", "array"); - Objects.assertThatTypeOf(paddingMarker, "paddingMarker", "string"); - - this.actualLines = actualLines; - this.diffLines = diffLines; - this.expectedLines = expectedLines; - this.paddingMarker = paddingMarker; - } - - /** - * @returns the lines of the actual string - */ - getActualLines(): string[] - { - return this.actualLines; - } - - /** - * @returns the lines to display between "actual" and "expected". If the list is empty, no lines should be - * displayed. - */ - getDiffLines(): string[] - { - return this.diffLines; - } - - /** - * @returns the lines of the expected string - */ - getExpectedLines(): string[] - { - return this.expectedLines; - } - - /** - * @returns a padding character used to align values vertically - */ - getPaddingMarker(): string - { - return this.paddingMarker; - } -} - -export {DiffResult}; \ No newline at end of file diff --git a/src/internal/diff/Node16Colors.mts b/src/internal/diff/Node16Colors.mts deleted file mode 100644 index 78abba9..0000000 --- a/src/internal/diff/Node16Colors.mts +++ /dev/null @@ -1,25 +0,0 @@ -import chalk from "chalk"; -import {AbstractColorWriter} from "../internal.mjs"; - -/** - * A node terminal that supports 16 colors. - */ -class Node16Colors extends AbstractColorWriter -{ - constructor() - { - super(); - } - - decorateInsertedText(text: string): string - { - return chalk.bgGreen(chalk.whiteBright(text)); - } - - decorateDeletedText(text: string): string - { - return chalk.bgRed(chalk.whiteBright(text)); - } -} - -export {Node16Colors}; \ No newline at end of file diff --git a/src/internal/diff/TextOnly.mts b/src/internal/diff/TextOnly.mts deleted file mode 100644 index b77fa9f..0000000 --- a/src/internal/diff/TextOnly.mts +++ /dev/null @@ -1,276 +0,0 @@ -import { - AbstractDiffWriter, - IllegalStateError, - Maps -} from "../internal.mjs"; - -// WORKAROUND: https://github.com/microsoft/tsdoc/issues/362 -/* eslint-disable tsdoc/syntax */ -/** - * A diff representation that does not use colors. - *

Basic Rules

- *
    - *
  • Minus (-) denotes a character that needs to be removed from Actual.
  • - *
  • Space ( ) denotes a character that is equal in Actual and Expected.
  • - *
  • Plus (+) denotes a character that needs to be added to Actual.
  • - *
  • "Diff" is omitted for lines that are identical.
  • - *
  • When '-' is present, Actual is padded to line up vertically with - * Expected.
  • - *
  • When '+' is present, Expected is padded to line up vertically with - * Actual.
  • - *
  • The padding is not part of Actual and Expected's value, respectively. Read - * on for concrete examples. - *
  • Lines always end with \n or \0. The former denotes a newline. The latter - * denotes the end of the string.
  • - *
  • Lines ending with "\n\n" or "\0\0" represents the literal string "\n" followed by a newline - * character, or the literal string "\0" followed by the end of string, respectively.
  • - *
- *

Example 1: insert

- *

- * Actual   = ""
- * Expected = "text"
- * 
results in the following diff: - *

- *
- * Actual  :     \0
- * Diff    : ++++
- * Expected: text\0
- * 
- * Meaning, to go from Actual to Expected we need to insert "text". - *

Example 2: delete

- *

- * Actual   = "text"
- * Expected = ""
- * 
- * results in the following diff: - *

- *
- * Actual  : text\0
- * Diff    : ----
- * Expected:     \0
- * 
- * Meaning, to go from Actual to Expected we need to delete "text". - *

Example 3: padding

- *

- * Actual   = "foo"
- * Expected = "   foo"
- * 
- * results in the following diff: - *

- *
- * Actual  :    foo\0
- * Diff    : +++
- * Expected:    foo\0
- * 
- * 
- * Meaning: - *
    - *
  • To go from Actual to Expected we need to insert three spaces at the - * beginning - * of Actual.
  • - *
  • There is no whitespace in Expected in front of "foo". This padding is used to line up - * the strings vertically.
  • - *
- *

Example 4: delete, keep, insert

- *

- * Actual   = "foosball"
- * Expected = "ballroom"
- * 
- * results in the following diff: - *

- *
- * Actual  : foosball    \0
- * Diff    :     ====++++
- * Expected:     ballroom\0
- * 
- * Meaning, we need to: - *
    - *
  • Delete "foos".
  • - *
  • Keep "ball".
  • - *
  • Insert "room".
  • - *
  • There is no whitespace before "ballroom" or after "foosball". This padding is used to line up - * the strings vertically.
  • - *
- *

Example 5: Multi-line Strings

- * When comparing multi-line strings: - *
    - *
  • We display the diff on a per-line basis.
  • - *
  • Actual and Expected are followed by a line number.
  • - *
  • Lines that are identical (with the exception of the first and last line) are omitted.
  • - *
- * For example: - *

- *
- * Actual   = "first\nsecond\nfoo\nforth\nfifth"
- * Expected = "first\nsecond\nbar\nforth\nfifth"
- * 
- * results in the following diff: - *

- *
- * Actual@1  : first\n
- * Expected@1: first\n
- *
- * [...]
- *
- * Actual@3  : foo   \n
- * Diff      : ---+++
- * Expected@3:    bar\n
- *
- * [...]
- *
- * Actual@5  : fifth\0
- * Expected@5: fifth\0
- * 
- * Meaning: - *
    - *
  • Lines 1-2 were equal.
  • - *
  • On line 3, we need to delete "foo" and insert "bar".
  • - *
  • Lines 4-5 were equal.
  • - *
- *

Example 6: Missing Line Numbers

- * When Actual or Expected contain a line that does not have a corresponding line - * on - * the other side we omit the latter's line number. - *

- * Actual   = "Foo\nBar"
- * Expected = "Bar"
- * 
- * results in the following diff: - *

- *
- * Actual@1  : Foo\n
- * Diff      : -----
- * Expected  :
- *
- * Actual@2  : Bar\0
- * Expected@1: Bar\0
- * 
- * Meaning: - *
    - *
  • Actual contained more lines than Expected.
  • - *
  • Expected did not have a line that corresponded to Actual line 1.
  • - *
  • We need to delete line 1 and retain line 2 unchanged.
  • - *
- */ - -/* eslint-enable tsdoc/syntax */ -class TextOnly extends AbstractDiffWriter -{ - /** - * A padding character used to align values vertically. - */ - static readonly DIFF_PADDING = " "; - /** - * Indicates a character is equal in the actual and expected values. - */ - static readonly DIFF_EQUAL = " "; - /** - * Indicates a character to delete from the actual value. - */ - static readonly DIFF_DELETE = "-"; - /** - * Indicates a character to insert into the actual value. - */ - static readonly DIFF_INSERT = "+"; - private lineToDiffLine: Map = new Map(); - private diffLines: string[] = []; - - constructor() - { - super(TextOnly.DIFF_PADDING); - this.initActualLine(0); - this.initExpectedLine(0); - } - - initActualLine(number: number): void - { - super.initActualLine(number); - if (!this.lineToDiffLine.get(number)) - this.lineToDiffLine.set(number, ""); - } - - initExpectedLine(number: number): void - { - super.initExpectedLine(number); - if (!this.lineToDiffLine.get(number)) - this.lineToDiffLine.set(number, ""); - } - - writeEqual(text: string): void - { - if (this.closed) - throw new IllegalStateError("Writer must be open"); - if (text.length === 0) - return; - this.splitLines(text, (line: string) => - { - Maps.appendToValue(this.lineToActualLine, this.actualLineNumber, line); - - const length = line.length; - if (this.expectedLineNumber === this.actualLineNumber) - Maps.appendToValue(this.lineToDiffLine, this.actualLineNumber, TextOnly.DIFF_EQUAL.repeat(length)); - else - { - const paddingMarker = this.getPaddingMarker(); - Maps.appendToValue(this.lineToExpectedLine, this.actualLineNumber, paddingMarker.repeat(length)); - Maps.appendToValue(this.lineToDiffLine, this.actualLineNumber, TextOnly.DIFF_EQUAL.repeat(length)); - - Maps.appendToValue(this.lineToActualLine, this.expectedLineNumber, paddingMarker.repeat(length)); - Maps.appendToValue(this.lineToDiffLine, this.expectedLineNumber, TextOnly.DIFF_EQUAL.repeat(length)); - } - Maps.appendToValue(this.lineToExpectedLine, this.expectedLineNumber, line); - }, () => - { - this.writeActualNewline(); - this.writeExpectedNewline(); - }); - } - - writeDeleted(text: string): void - { - if (this.closed) - throw new IllegalStateError("Writer must be open"); - if (text.length === 0) - return; - this.splitLines(text, (line: string) => - { - Maps.appendToValue(this.lineToActualLine, this.actualLineNumber, line); - const length = line.length; - Maps.appendToValue(this.lineToDiffLine, this.actualLineNumber, TextOnly.DIFF_DELETE.repeat(length)); - Maps.appendToValue(this.lineToExpectedLine, this.actualLineNumber, - this.getPaddingMarker().repeat(length)); - }, this.writeActualNewline.bind(this)); - } - - writeInserted(text: string): void - { - if (this.closed) - throw new IllegalStateError("Writer must be open"); - if (text.length === 0) - return; - this.splitLines(text, (line: string) => - { - const length = line.length; - Maps.appendToValue(this.lineToActualLine, this.expectedLineNumber, - this.getPaddingMarker().repeat(length)); - Maps.appendToValue(this.lineToDiffLine, this.expectedLineNumber, TextOnly.DIFF_INSERT.repeat(length)); - Maps.appendToValue(this.lineToExpectedLine, this.expectedLineNumber, line); - }, this.writeExpectedNewline.bind(this)); - } - - afterClose(): void - { - for (const diffLine of Maps.sortByKeys(this.lineToDiffLine).values()) - this.diffLines.push(diffLine); - Object.freeze(this.diffLines); - } - - getDiffLines(): string[] - { - if (!this.closed) - throw new RangeError("Writer must be closed"); - return this.diffLines; - } -} - -export {TextOnly}; \ No newline at end of file diff --git a/src/internal/extension/AbstractNumberValidator.mts b/src/internal/extension/AbstractNumberValidator.mts deleted file mode 100644 index 92a97d9..0000000 --- a/src/internal/extension/AbstractNumberValidator.mts +++ /dev/null @@ -1,282 +0,0 @@ -import type { - Configuration, - ExtensibleNumberValidator -} from "../internal.mjs"; -import { - AbstractObjectValidator, - Objects, - ValidationFailure -} from "../internal.mjs"; - -/** - * Extensible implementation of ExtensibleNumberValidator. - * - * @typeParam S - the type of validator returned by the methods - */ -abstract class AbstractNumberValidator extends AbstractObjectValidator - implements ExtensibleNumberValidator -{ - /** - * Creates a new NumberValidator. - * - * @param configuration - the instance configuration - * @param actual - the actual value - * @param name - (optional) the name of the value - * @param failures - the list of validation failures - * @throws TypeError if configuration or name are null or undefined - * @throws RangeError if name is empty - */ - protected constructor(configuration: Configuration, actual: number | undefined, name: string, - failures: ValidationFailure[]) - { - super(configuration, actual, name, failures); - } - - isNegative(): S - { - if (this.actual === undefined || this.actual >= 0) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " must be negative."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this.getThis(); - } - - isNotNegative(): S - { - if (this.actual === undefined || this.actual < 0) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " may not be negative."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this.getThis(); - } - - isZero(): S - { - if (this.actual === undefined || this.actual !== 0) - { - const failure = new ValidationFailure(this.config, RangeError, this.name + " must be zero."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this.getThis(); - } - - isNotZero(): S - { - if (this.actual === undefined || this.actual === 0) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " may not be zero"); - this.failures.push(failure); - } - return this.getThis(); - } - - isPositive(): S - { - if (this.actual === undefined || this.actual <= 0) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " must be positive."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this.getThis(); - } - - isNotPositive(): S - { - if (this.actual === undefined || this.actual > 0) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " may not be positive."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this.getThis(); - } - - isGreaterThan(value: number, name?: string): S - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - Objects.requireThatTypeOf(value, "value", "number"); - - if (this.actual === undefined || this.actual <= value) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must be greater than " + name). - addContext("Actual", this.actual). - addContext("Min", value); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must be greater than: " + this.config.convertToString(value)). - addContext("Actual", this.actual); - } - this.failures.push(failure); - } - return this.getThis(); - } - - isGreaterThanOrEqualTo(value: number, name?: string): S - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - Objects.requireThatTypeOf(value, "value", "number"); - - if (this.actual === undefined || this.actual < value) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must be greater than or equal to " + name + "."). - addContext("Actual", this.actual). - addContext("Min", value); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must be greater than or equal to: " + this.config.convertToString(value)). - addContext("Actual", this.actual); - } - this.failures.push(failure); - } - return this.getThis(); - } - - isLessThan(value: number, name?: string): S - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - Objects.requireThatTypeOf(value, "value", "number"); - - if (this.actual === undefined || this.actual >= value) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must be less than " + name). - addContext("Actual", this.actual). - addContext("Max", value); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must be less than: " + this.config.convertToString(value)). - addContext("Actual", this.actual); - } - this.failures.push(failure); - } - return this.getThis(); - } - - isLessThanOrEqualTo(value: number, name?: string): S - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - Objects.requireThatTypeOf(value, "value", "number"); - - if (this.actual === undefined || this.actual > value) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must be less than or equal to " + name). - addContext("Actual", this.actual). - addContext("Max", value); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must be less than or equal to: " + this.config.convertToString(value)). - addContext("Actual", this.actual); - } - this.failures.push(failure); - } - return this.getThis(); - } - - isBetween(startInclusive: number, endExclusive: number): S - { - Objects.requireThatTypeOf(startInclusive, "startInclusive", "number"); - Objects.requireThatTypeOf(endExclusive, "endExclusive", "number"); - if (endExclusive < startInclusive) - { - throw new RangeError("endExclusive must be greater than or equal to startInclusive.\n" + - "Actual: " + endExclusive + "\n" + - "Min : " + startInclusive); - } - - if (this.actual === undefined || this.actual < startInclusive || this.actual >= endExclusive) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " must be in range [" + startInclusive + ", " + endExclusive + ")."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this.getThis(); - } - - isBetweenClosed(startInclusive: number, endInclusive: number): S - { - Objects.requireThatTypeOf(startInclusive, "startInclusive", "number"); - Objects.requireThatTypeOf(endInclusive, "endInclusive", "number"); - if (endInclusive < startInclusive) - { - throw new RangeError("endInclusive must be greater than or equal to startInclusive.\n" + - "Actual: " + endInclusive + "\n" + - "Min : " + startInclusive); - } - - if (this.actual === undefined || this.actual < startInclusive || this.actual > endInclusive) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " must be in range [" + startInclusive + ", " + endInclusive + "]."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this.getThis(); - } - - isFinite(): S - { - // See http://stackoverflow.com/a/1830844/14731 - if (this.actual === undefined || !Number.isFinite(this.actual)) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " must be finite."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this.getThis(); - } - - isInfinite(): S - { - // See http://stackoverflow.com/a/1830844/14731 - if (this.actual === undefined || Number.isFinite(this.actual)) - { - const failure = new ValidationFailure(this.config, RangeError, - this.name + " may not be finite."). - addContext("Actual", this.actual); - this.failures.push(failure); - } - return this.getThis(); - } -} - -export {AbstractNumberValidator}; \ No newline at end of file diff --git a/src/internal/extension/AbstractNumberVerifier.mts b/src/internal/extension/AbstractNumberVerifier.mts deleted file mode 100644 index 1ef98c4..0000000 --- a/src/internal/extension/AbstractNumberVerifier.mts +++ /dev/null @@ -1,101 +0,0 @@ -import type { - ExtensibleNumberValidator, - ExtensibleNumberVerifier -} from "../internal.mjs"; -import {AbstractObjectVerifier} from "../internal.mjs"; - -/** - * Extensible implementation of ExtensibleNumberVerifier. - * - * @typeParam S - the type of validator returned by the methods - */ -abstract class AbstractNumberVerifier> - extends AbstractObjectVerifier - implements ExtensibleNumberVerifier -{ - isNegative(): S - { - this.validator.isNegative(); - return this.validationResult(() => this.getThis()); - } - - isNotNegative(): S - { - this.validator.isNotNegative(); - return this.validationResult(() => this.getThis()); - } - - isZero(): S - { - this.validator.isZero(); - return this.validationResult(() => this.getThis()); - } - - isNotZero(): S - { - this.validator.isNotZero(); - return this.validationResult(() => this.getThis()); - } - - isPositive(): S - { - this.validator.isPositive(); - return this.validationResult(() => this.getThis()); - } - - isNotPositive(): S - { - this.validator.isNotPositive(); - return this.validationResult(() => this.getThis()); - } - - isGreaterThan(value: number, name?: string): S - { - this.validator.isGreaterThan(value, name); - return this.validationResult(() => this.getThis()); - } - - isGreaterThanOrEqualTo(value: number, name?: string): S - { - this.validator.isGreaterThanOrEqualTo(value, name); - return this.validationResult(() => this.getThis()); - } - - isLessThan(value: number, name?: string): S - { - this.validator.isLessThan(value, name); - return this.validationResult(() => this.getThis()); - } - - isLessThanOrEqualTo(value: number, name?: string): S - { - this.validator.isLessThanOrEqualTo(value, name); - return this.validationResult(() => this.getThis()); - } - - isBetween(startInclusive: number, endExclusive: number): S - { - this.validator.isBetween(startInclusive, endExclusive); - return this.validationResult(() => this.getThis()); - } - - isBetweenClosed(startInclusive: number, endInclusive: number): S - { - this.validator.isBetweenClosed(startInclusive, endInclusive); - return this.validationResult(() => this.getThis()); - } - - isFinite(): S - { - this.validator.isFinite(); - return this.validationResult(() => this.getThis()); - } - - isInfinite(): S - { - this.validator.isInfinite(); - return this.validationResult(() => this.getThis()); - } -} - -export {AbstractNumberVerifier}; \ No newline at end of file diff --git a/src/internal/extension/AbstractObjectValidator.mts b/src/internal/extension/AbstractObjectValidator.mts deleted file mode 100644 index 213b790..0000000 --- a/src/internal/extension/AbstractObjectValidator.mts +++ /dev/null @@ -1,416 +0,0 @@ -import isEqual from "lodash/isEqual.js"; -import type { - ContextLine, - ObjectValidator, - ClassConstructor, - BooleanValidator, - NumberValidator, - ElementOf, - ArrayValidator, - StringValidator, - SetValidator, - MapValidator, - InetAddressValidator, - ClassValidator, - ExtensibleObjectValidator -} from "../internal.mjs"; -import { - Objects, - Configuration, - ValidationFailure, - ObjectValidatorImpl, - BooleanValidatorImpl, - NumberValidatorImpl, - ArrayValidatorImpl, - StringValidatorImpl, - SetValidatorImpl, - ClassValidatorImpl, - Pluralizer, - MapValidatorImpl, - ContextGenerator -} from "../internal.mjs"; - -/** - * Extensible implementation of ExtensibleObjectValidator. - * - * @typeParam S - the type of validator returned by the methods - * @typeParam T - the type the actual value - */ -abstract class AbstractObjectValidator implements ExtensibleObjectValidator -{ - protected readonly config: Configuration; - protected actual: T | undefined; - protected readonly name: string; - protected readonly failures: ValidationFailure[]; - - /** - * @returns this - */ - protected getThis(): S - { - return this as unknown as S; - } - - /** - * Creates a new AbstractObjectValidator. - * - * @param configuration - the instance configuration - * @param actual - the actual value - * @param name - the name of the value - * @param failures - the list of validation failures - * @throws TypeError if configuration or name are null or undefined - * @throws RangeError if name is empty - */ - protected constructor(configuration: Configuration, actual: T | undefined, name: string, - failures: ValidationFailure[]) - { - Objects.assertThatInstanceOf(configuration, "configuration", Configuration); - Objects.verifyName(name, "name"); - this.config = configuration; - this.actual = actual; - this.name = name; - this.failures = failures; - } - - isNull(): ObjectValidator - { - if (this.actual !== null) - { - const failure = new ValidationFailure(this.config, TypeError, this.name + " must be null."). - addContextList(this.getContext(undefined, true)); - this.failures.push(failure); - } - return this.getThis() as ObjectValidator; - } - - isNotNull(): ObjectValidator> - { - if (this.actual === null) - { - const failure = new ValidationFailure(this.config, TypeError, this.name + " may not be null"); - this.failures.push(failure); - } - return this.getThis() as ObjectValidator>; - } - - isDefined>(): ObjectValidator - { - if (typeof (this.actual) === "undefined") - { - const failure = new ValidationFailure(this.config, TypeError, this.name + " must be defined"); - this.failures.push(failure); - } - return this.getThis() as unknown as ObjectValidator; - } - - isUndefined(): ObjectValidator - { - if (typeof (this.actual) !== "undefined") - { - const failure = new ValidationFailure(this.config, TypeError, - this.name + " must be undefined.").addContext("Actual", this.actual); - this.failures.push(failure); - } - return this.getThis() as ObjectValidator; - } - - isDefinedAndNotNull>(): ObjectValidator - { - if (typeof (this.actual) === "undefined" || this.actual === null) - { - const failure = new ValidationFailure(this.config, TypeError, this.name + - " may not be undefined or null"); - this.failures.push(failure); - } - return this.getThis() as unknown as ObjectValidator; - } - - isUndefinedOrNull(): ObjectValidator - { - if (typeof (this.actual) !== "undefined" && this.actual !== null) - { - const failure = new ValidationFailure(this.config, TypeError, this.name + - " must be undefined or null"); - this.failures.push(failure); - } - return this.getThis() as ObjectValidator; - } - - isBoolean(): BooleanValidator - { - const typeOfActual = Objects.getTypeInfo(this.actual); - if (typeOfActual.type === "boolean") - return new BooleanValidatorImpl(this.config, Boolean(this.actual), this.name, this.failures); - const failure = new ValidationFailure(this.config, TypeError, - this.name + " must be a boolean."). - addContext("Actual", this.actual). - addContext("Type", typeOfActual); - this.failures.push(failure); - return new BooleanValidatorImpl(this.config, undefined, this.name, this.failures); - } - - isNumber(): NumberValidator - { - const typeOfActual = Objects.getTypeInfo(this.actual); - if (typeOfActual.type === "number") - { - // https://stackoverflow.com/a/23440948 - return new NumberValidatorImpl(this.config, Number(this.actual), this.name, this.failures); - } - - const failure = new ValidationFailure(this.config, TypeError, - this.name + " must be a number."). - addContext("Actual", this.actual). - addContext("Type", typeOfActual); - this.failures.push(failure); - return new NumberValidatorImpl(this.config, undefined, this.name, this.failures); - } - - isString(): StringValidator - { - const typeOfActual = Objects.getTypeInfo(this.actual); - let value: string | undefined; - if (typeOfActual.type === "string") - value = this.actual as string; - else - { - value = undefined; - const failure = new ValidationFailure(this.config, TypeError, this.name + " must be a string."). - addContext("Actual", this.config.convertToString(this.actual)). - addContext("Type", typeOfActual); - this.failures.push(failure); - } - return new StringValidatorImpl(this.config, value, this.name, this.failures); - } - - isInetAddress(): InetAddressValidator - { - return this.isString().isInetAddress(); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - isClass(type: ClassConstructor): ClassValidator - { - const typeOfActual = Objects.getTypeInfo(this.actual); - if (typeOfActual.type === "class") - { - return new ClassValidatorImpl(this.config, this.actual as ClassConstructor | undefined, - this.name, this.failures); - } - - const failure = new ValidationFailure(this.config, TypeError, - this.name + " must contain a class."). - addContext("Actual", this.actual). - addContext("Type", typeOfActual); - this.failures.push(failure); - return new ClassValidatorImpl(this.config, undefined, this.name, this.failures); - } - - isArray>(): ArrayValidator - { - const typeOfActual = Objects.getTypeInfo(this.actual); - if (typeOfActual.type === "array") - { - return new ArrayValidatorImpl(this.config, this.actual as E[], this.name, Pluralizer.ELEMENT, - this.failures) as ArrayValidator; - } - - const failure = new ValidationFailure(this.config, TypeError, - this.name + " must be an Array."). - addContext("Actual", this.actual). - addContext("Type", typeOfActual); - this.failures.push(failure); - return new ArrayValidatorImpl(this.config, undefined, this.name, Pluralizer.ELEMENT, - this.failures) as ArrayValidator; - } - - isSet(): SetValidator - { - const typeOfActual = Objects.getTypeInfo(this.actual); - if (typeOfActual.type === "object" && typeOfActual.name === "Set") - return new SetValidatorImpl(this.config, this.actual as Set, this.name, this.failures); - - const failure = new ValidationFailure(this.config, TypeError, this.name + " must be a Set."). - addContext("Actual", this.config.convertToString(this.actual)). - addContext("Type", typeOfActual); - this.failures.push(failure); - return new SetValidatorImpl(this.config, undefined, this.name, this.failures); - } - - isMap(): MapValidator - { - const typeOfActual = Objects.getTypeInfo(this.actual); - if (typeOfActual.type === "object" && typeOfActual.name === "Map") - return new MapValidatorImpl(this.config, this.actual as Map, this.name, this.failures); - - const failure = new ValidationFailure(this.config, TypeError, this.name + " must be a Map."). - addContext("Actual", this.config.convertToString(this.actual)). - addContext("Type", typeOfActual); - this.failures.push(failure); - return new MapValidatorImpl(this.config, undefined, this.name, this.failures); - } - - isPrimitive(): ObjectValidator - { - if (!this.requireThatActualIsDefinedAndNotNull()) - return this.getThis() as ObjectValidator; - if (!Objects.isPrimitive(this.actual)) - { - const typeOfActual = Objects.getTypeInfo(this.actual); - const failure = new ValidationFailure(this.config, TypeError, - this.name + " must be a primitive"). - addContext("Actual", this.actual). - addContext("Type", typeOfActual); - this.failures.push(failure); - } - return this.getThis() as ObjectValidator; - } - - isTypeOf(type: string): S - { - Objects.requireThatValueIsDefinedAndNotNull(type, "type"); - const typeOfActual = typeof (this.actual); - if (type !== typeOfActual) - { - const failure = new ValidationFailure(this.config, TypeError, - "typeof(" + this.name + ") must be equal to " + type). - addContext("Actual", Objects.getTypeInfo(this.actual)); - this.failures.push(failure); - } - return this.getThis(); - } - - isInstanceOf(type: ClassConstructor): ObjectValidator - { - const typeOfType = Objects.getTypeInfo(type); - const typeOfActual = Objects.getTypeInfo(this.actual); - if (typeOfType.type === "class") - { - if (typeOfActual.type === "object" && this.actual instanceof type) - { - return new ObjectValidatorImpl(this.config, this.actual as T2 | undefined, this.name, - this.failures); - } - const failure = new ValidationFailure(this.config, TypeError, - this.name + " must be an instance of " + typeOfType.toString()). - addContext("Actual", typeOfActual); - this.failures.push(failure); - return new ObjectValidatorImpl(this.config, undefined, this.name, this.failures); - } - else - { - throw new TypeError("type must be a class\n" + - "Actual: " + typeOfType.toString()); - } - } - - isEqualTo(expected: T, name?: string): S - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - if (!isEqual(this.actual, expected)) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " must be equal to " + name). - addContextList(this.getContext(expected, false)); - } - else - { - const expectedIsString = this.config.convertToString(expected); - const terminalWidth = this.config.getGlobalConfiguration().getTerminalWidth(); - const message = this.name + " must be equal to " + expectedIsString + "."; - if (message.length < terminalWidth) - { - failure = new ValidationFailure(this.config, RangeError, message). - addContextList(this.getContext(expected, true)); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " had an unexpected value."). - addContextList(this.getContext(expected, false)); - } - } - this.failures.push(failure); - } - return this.getThis(); - } - - isNotEqualTo(value: T, name?: string): S - { - if (typeof (name) !== "undefined") - Objects.requireThatStringIsNotEmpty(name, "name"); - if (isEqual(this.actual, value)) - { - let failure; - if (name) - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " may not be equal to " + name). - addContext("Actual", this.actual); - } - else - { - failure = new ValidationFailure(this.config, RangeError, - this.name + " may not be equal to " + this.config.convertToString(value)); - } - this.failures.push(failure); - } - return this.getThis(); - } - - /** - * {@inheritDoc} - */ - getActual(): T | undefined - { - return this.actual; - } - - getFailures(): ValidationFailure[] - { - return this.failures; - } - - /** - * @param expected - the expected value - * @param expectedInMessage - true if the expected value is already mentioned in the failure message - * @returns the list of name-value pairs to append to the exception message - */ - protected getContext(expected: T | undefined, expectedInMessage: boolean): ContextLine[] - { - const contextGenerator = new ContextGenerator(this.config); - return contextGenerator.getContext("Actual", this.actual, "Expected", expected, - expectedInMessage); - } - - /** - * Ensures that actual is defined and not null; otherwise, an entry is added to - * failures. - * - * @returns false if the actual value is undefined or null - */ - protected requireThatActualIsDefinedAndNotNull(): boolean - { - if (typeof (this.actual) === "undefined") - { - const failure = new ValidationFailure(this.config, TypeError, this.name + " must be defined."). - addContextList(this.getContext(undefined, true)); - this.failures.push(failure); - return false; - } - if (this.actual === null) - { - const failure = new ValidationFailure(this.config, TypeError, this.name + " may not be null."). - addContextList(this.getContext(undefined, true)); - this.failures.push(failure); - return false; - } - return true; - } -} - -export {AbstractObjectValidator}; \ No newline at end of file diff --git a/src/internal/extension/AbstractObjectVerifier.mts b/src/internal/extension/AbstractObjectVerifier.mts deleted file mode 100644 index 8cc86de..0000000 --- a/src/internal/extension/AbstractObjectVerifier.mts +++ /dev/null @@ -1,209 +0,0 @@ -import type { - ExtensibleObjectVerifier, - ExtensibleObjectValidator, - ObjectVerifier, - ClassConstructor, - ObjectValidator, - BooleanVerifier, - NumberVerifier, - StringVerifier, - ElementOf, - ArrayVerifier, - SetVerifier, - MapKey, - MapVerifier, - InetAddressVerifier, - ClassVerifier, - MapValue -} from "../internal.mjs"; -import { - Objects, - BooleanVerifierImpl, - NumberVerifierImpl, - StringVerifierImpl, - ArrayVerifierImpl, - SetVerifierImpl, - MapVerifierImpl, - InetAddressVerifierImpl, - ClassVerifierImpl, - ObjectVerifierImpl -} from "../internal.mjs"; - -/** - * Extensible implementation of ExtensibleObjectVerifier. - * - * @typeParam S - the type of validator returned by the methods - * @typeParam T - the type the actual value - */ -abstract class AbstractObjectVerifier, T> - implements ExtensibleObjectVerifier -{ - protected readonly validator: V; - - /** - * Creates a new AbstractObjectVerifier. - * - * @param validator - the validator to delegate to - * @throws TypeError if validator is null or undefined - */ - protected constructor(validator: V) - { - Objects.requireThatValueIsDefinedAndNotNull(validator, "validator"); - this.validator = validator; - } - - /** - * @returns this - */ - protected getThis(): S - { - return this as unknown as S; - } - - isNull(): ObjectVerifier - { - this.validator.isNull(); - return this.validationResult(() => this.getThis()) as unknown as ObjectVerifier; - } - - isNotNull(): ObjectVerifier> - { - this.validator.isNotNull(); - return this.validationResult(() => this.getThis()) as ObjectVerifier>; - } - - isDefined>(): ObjectVerifier - { - this.validator.isDefined(); - return this.validationResult(() => this.getThis()) as unknown as ObjectVerifier; - } - - isUndefined(): ObjectVerifier - { - this.validator.isUndefined(); - return this.validationResult(() => this.getThis()) as ObjectVerifier; - } - - isDefinedAndNotNull>(): ObjectVerifier - { - this.validator.isDefinedAndNotNull(); - return this.validationResult(() => this.getThis()) as unknown as ObjectVerifier; - } - - isUndefinedOrNull(): ObjectVerifier - { - this.validator.isUndefinedOrNull(); - return this.validationResult(() => this.getThis()) as ObjectVerifier; - } - - isBoolean(): BooleanVerifier - { - const newValidator = this.validator.isBoolean(); - return this.validationResult(() => new BooleanVerifierImpl(newValidator)) as BooleanVerifier; - } - - isNumber(): NumberVerifier - { - const newValidator = this.validator.isNumber(); - return this.validationResult(() => new NumberVerifierImpl(newValidator)) as NumberVerifier; - } - - isString(): StringVerifier - { - const newValidator = this.validator.isString(); - return this.validationResult(() => new StringVerifierImpl(newValidator)) as StringVerifier; - } - - isInetAddress(): InetAddressVerifier - { - const newValidator = this.validator.isInetAddress(); - return this.validationResult(() => new InetAddressVerifierImpl(newValidator)) as InetAddressVerifier; - } - - isClass(type: ClassConstructor): ClassVerifier - { - const newValidator = this.validator.isClass(type); - return this.validationResult(() => new ClassVerifierImpl(newValidator)); - } - - isArray>(): ArrayVerifier - { - const newValidator = this.validator.isArray(); - return this.validationResult(() => new ArrayVerifierImpl(newValidator)) as ArrayVerifier; - } - - isSet>(): SetVerifier - { - const newValidator = this.validator.isSet(); - return this.validationResult(() => new SetVerifierImpl(newValidator)) as SetVerifier; - } - - isMap, V = MapValue>(): MapVerifier - { - const newValidator = this.validator.isMap(); - return this.validationResult(() => new MapVerifierImpl(newValidator)) as unknown as MapVerifier; - } - - isPrimitive(): ObjectVerifier - { - this.validator.isPrimitive(); - return this.validationResult(() => this.getThis()) as unknown as ObjectVerifier; - } - - isTypeOf(type: string): S - { - this.validator.isTypeOf(type); - return this.validationResult(() => this.getThis()); - } - - isInstanceOf(type: ClassConstructor): ObjectVerifier - { - const typeOfType = Objects.getTypeInfo(type); - if (typeOfType.type === "class") - { - const newValidator = this.validator.isInstanceOf(type); - const newVerifier = new ObjectVerifierImpl, T2>(newValidator); - return newVerifier.validationResult(() => this.getThis()) as unknown as ObjectVerifier; - } - else - { - throw new TypeError("type must be a class\n" + - "Actual: " + typeOfType.toString()); - } - } - - isEqualTo(expected: T, name?: string): S - { - this.validator.isEqualTo(expected, name); - return this.validationResult(() => this.getThis()); - } - - isNotEqualTo(value: T, name?: string): S - { - this.validator.isNotEqualTo(value, name); - return this.validationResult(() => this.getThis()); - } - - validationResult(result: () => T2): T2 - { - if (result === null) - throw new TypeError("result may not be null"); - - const failures = this.validator.getFailures(); - if (failures.length === 0) - { - // eslint-disable-next-line no-undefined - return result.apply(undefined); - } - const failure = failures[0]; - throw failure.createException(); - } - - getActual(): T - { - // The verifier is guaranteed to throw an exception if validation fails - return this.validator.getActual() as T; - } -} - -export {AbstractObjectVerifier}; \ No newline at end of file diff --git a/src/internal/internal.mts b/src/internal/internal.mts index d27a988..f3460b5 100644 --- a/src/internal/internal.mts +++ b/src/internal/internal.mts @@ -9,179 +9,419 @@ // internal.js determines the library-wide loading order. // Dependencies must be loaded before dependents. -import type {ExtensibleObjectValidator} from "../extension/ExtensibleObjectValidator.mjs"; -import type {ExtensibleObjectVerifier} from "../extension/ExtensibleObjectVerifier.mjs"; -import type {ObjectValidator} from "../ObjectValidator.mjs"; -import type {ObjectVerifier} from "../ObjectVerifier.mjs"; -import {AbstractObjectValidator} from "./extension/AbstractObjectValidator.mjs"; -import {AbstractObjectVerifier} from "./extension/AbstractObjectVerifier.mjs"; -import {ObjectValidatorImpl} from "./ObjectValidatorImpl.mjs"; -import {ObjectVerifierImpl} from "./ObjectVerifierImpl.mjs"; -import type {ArrayValidator} from "../ArrayValidator.mjs"; -import type {ArrayVerifier} from "../ArrayVerifier.mjs"; -import {ArrayValidatorImpl} from "./ArrayValidatorImpl.mjs"; -import {ArrayVerifierImpl} from "./ArrayVerifierImpl.mjs"; -import type {ClassValidator} from "../ClassValidator.mjs"; -import type {ClassVerifier} from "../ClassVerifier.mjs"; -import {ClassValidatorImpl} from "./ClassValidatorImpl.mjs"; -import {ClassVerifierImpl} from "./ClassVerifierImpl.mjs"; -import {Configuration} from "../Configuration.mjs"; -import type {InetAddressValidator} from "../InetAddressValidator.mjs"; -import type {InetAddressVerifier} from "../InetAddressVerifier.mjs"; -import {InetAddressValidatorImpl} from "./InetAddressValidatorImpl.mjs"; -import {InetAddressVerifierImpl} from "./InetAddressVerifierImpl.mjs"; -import type {MapValidator} from "../MapValidator.mjs"; -import type {MapVerifier} from "../MapVerifier.mjs"; -import {MapValidatorImpl} from "./MapValidatorImpl.mjs"; -import {MapVerifierImpl} from "./MapVerifierImpl.mjs"; -import type {ExtensibleNumberValidator} from "../extension/ExtensibleNumberValidator.mjs"; -import type {ExtensibleNumberVerifier} from "../extension/ExtensibleNumberVerifier.mjs"; -import type {NumberValidator} from "../NumberValidator.mjs"; -import type {NumberVerifier} from "../NumberVerifier.mjs"; -import {AbstractNumberValidator} from "./extension/AbstractNumberValidator.mjs"; -import {NumberValidatorImpl} from "./NumberValidatorImpl.mjs"; -import {AbstractNumberVerifier} from "./extension/AbstractNumberVerifier.mjs"; -import {NumberVerifierImpl} from "./NumberVerifierImpl.mjs"; -import type {BooleanValidator} from "../BooleanValidator.mjs"; -import type {BooleanVerifier} from "../BooleanVerifier.mjs"; -import {BooleanValidatorImpl} from "./BooleanValidatorImpl.mjs"; -import {BooleanVerifierImpl} from "./BooleanVerifierImpl.mjs"; -import type {SetValidator} from "../SetValidator.mjs"; -import type {SetVerifier} from "../SetVerifier.mjs"; -import {SetValidatorImpl} from "./SetValidatorImpl.mjs"; -import {SetVerifierImpl} from "./SetVerifierImpl.mjs"; -import type {StringValidator} from "../StringValidator.mjs"; -import type {StringVerifier} from "../StringVerifier.mjs"; -import {StringValidatorImpl} from "./StringValidatorImpl.mjs"; -import {StringVerifierImpl} from "./StringVerifierImpl.mjs"; +import type {ObjectValidator} from "../validator/ObjectValidator.mjs"; +import { + Type, + TypeCategory +} from "../Type.mjs"; +import { + classExtends, + assert, + requireThatValueIsNotNull, + assertThatValueIsNotNull, + requireThatType, + assertThatType, + requireThatTypeCategory, + assertThatTypeCategory, + requireThatInstanceOf, + assertThatInstanceOf, + requireThatStringIsNotEmpty, + assertThatStringIsNotEmpty, + internalValueToString, + getSuperclass, + verifyName, + quoteString, + type ElementOf, + type MapKey, + type MapValue, + type ClassConstructor, + type Comparable, + type NonUndefinable +} from "./validator/Objects.mjs"; +import type {ArrayValidator} from "../validator/ArrayValidator.mjs"; +import { + type ErrorBuilder, + isErrorBuilder +} from "./validator/ErrorBuilder.mjs"; +import type {CollectionComponent} from "../validator/component/CollectionComponent.mjs"; +import type {NegativeNumberComponent} from "../validator/component/NegativeNumberComponent.mjs"; +import type {PositiveNumberComponent} from "../validator/component/PositiveNumberComponent.mjs"; +import type {ZeroNumberComponent} from "../validator/component/ZeroNumberComponent.mjs"; +import type {NumberComponent} from "../validator/component/NumberComponent.mjs"; +import {AbstractValidator} from "./validator/AbstractValidator.mjs"; +import {AbstractCollectionValidator} from "./validator/AbstractCollectionValidator.mjs"; +import {ArrayValidatorImpl} from "./validator/ArrayValidatorImpl.mjs"; +import {Terminal} from "./validator/Terminal.mjs"; +import {type ProcessScope} from "./scope/ProcessScope.mjs"; +import {DefaultProcessScope} from "./scope/DefaultProcessScope.mjs"; +import type {GlobalConfiguration} from "../GlobalConfiguration.mjs"; +import {MainGlobalConfiguration} from "./scope/MainGlobalConfiguration.mjs"; +import {StringMappers} from "./StringMappers.mjs"; +import {MutableStringMappers} from "./MutableStringMappers.mjs"; +import {MutableConfiguration} from "./validator/MutableConfiguration.mjs"; +import {AbstractValidators} from "./validator/AbstractValidators.mjs"; +import { + type ApplicationScope, + isApplicationScope +} from "./scope/ApplicationScope.mjs"; +import {AbstractApplicationScope} from "./scope/AbstractApplicationScope.mjs"; +import {MainApplicationScope} from "./scope/MainApplicationScope.mjs"; +import {Configuration} from "./Configuration.mjs"; +import {JavascriptValidatorsImpl} from "./validator/JavascriptValidatorsImpl.mjs"; +import {ObjectValidatorImpl} from "./validator/ObjectValidatorImpl.mjs"; +import type {MapValidator} from "../validator/MapValidator.mjs"; +import {MapValidatorImpl} from "./validator/MapValidatorImpl.mjs"; +import type {NumberValidator} from "../validator/NumberValidator.mjs"; +import {NumberValidatorImpl} from "./validator/NumberValidatorImpl.mjs"; +import type {BooleanValidator} from "../validator/BooleanValidator.mjs"; +import {BooleanValidatorImpl} from "./validator/BooleanValidatorImpl.mjs"; +import type {SetValidator} from "../validator/SetValidator.mjs"; +import {SetValidatorImpl} from "./validator/SetValidatorImpl.mjs"; +import type {StringValidator} from "../validator/StringValidator.mjs"; +import {StringValidatorImpl} from "./validator/StringValidatorImpl.mjs"; import { TerminalEncoding, - TerminalEncodings + sortByDecreasingRank } from "../TerminalEncoding.mjs"; -import {ValidationFailure} from "../ValidationFailure.mjs"; -import {Requirements} from "../Requirements.mjs"; -import {AbstractGlobalConfiguration} from "./AbstractGlobalConfiguration.mjs"; -import {AbstractDiffWriter} from "./diff/AbstractDiffWriter.mjs"; -import {AbstractColorWriter} from "./diff/AbstractColorWriter.mjs"; -import {TextOnly} from "./diff/TextOnly.mjs"; -import {ContextGenerator} from "./diff/ContextGenerator.mjs"; -import {ContextLine} from "../ContextLine.mjs"; +import {type ValidationFailure, isValidationFailure} from "../ValidationFailure.mjs"; +import {ValidationFailures} from "../ValidationFailures.mjs"; +import {ValidationFailureImpl} from "./validator/ValidationFailureImpl.mjs"; +import {MultipleFailuresError} from "../MultipleFailuresError.mjs"; +import {JavascriptValidators} from "../JavascriptValidators.mjs"; +import type {JavascriptRequireThat} from "../JavascriptRequireThat.mjs"; +import type {JavascriptAssertThat} from "../JavascriptAssertThat.mjs"; +import type {JavascriptCheckIf} from "../JavascriptCheckIf.mjs"; +import { + requireThat, + assertThat, + checkIf, + updateConfiguration, + getContext, + withContext, + removeContext +} from "../DefaultJavascriptValidators.mjs"; +import {AbstractDiffWriter} from "./message/diff/AbstractDiffWriter.mjs"; +import {AbstractColorWriter} from "./message/diff/AbstractColorWriter.mjs"; +import {TextOnly} from "./message/diff/TextOnly.mjs"; +import {ContextGenerator} from "./message/diff/ContextGenerator.mjs"; import { NEWLINE_MARKER, - NEWLINE_PATTERN -} from "./diff/DiffConstants.mjs"; + EOS_MARKER, + NEWLINE_PATTERN, + EOL_PATTERN, + DIFF_EQUAL, + DIFF_DELETE, + DIFF_INSERT +} from "./message/diff/DiffConstants.mjs"; +import {DiffGenerator} from "./message/diff/DiffGenerator.mjs"; +import {DiffResult} from "./message/diff/DiffResult.mjs"; import { - DiffGenerator, - EOS_MARKER -} from "./diff/DiffGenerator.mjs"; -import {DiffResult} from "./diff/DiffResult.mjs"; -import {Terminal} from "./Terminal.mjs"; -import {VariableType} from "./VariableType.mjs"; -import {Objects} from "./Objects.mjs"; -import {MainGlobalConfiguration} from "./MainGlobalConfiguration.mjs"; -import {GlobalRequirements} from "../GlobalRequirements.mjs"; -import {Node16Colors} from "./diff/Node16Colors.mjs"; -import {Node16MillionColors} from "./diff/Node16MillionColors.mjs"; -import {Node256Colors} from "./diff/Node256Colors.mjs"; -import type {GlobalConfiguration} from "../GlobalConfiguration.mjs"; -import {IllegalStateError} from "./IllegalStateError.mjs"; -import {Maps} from "./Maps.mjs"; -import {SizeValidatorImpl} from "./SizeValidatorImpl.mjs"; -import {Pluralizer} from "./Pluralizer.mjs"; -import {Strings} from "./Strings.mjs"; - -type ElementOf = T extends readonly (infer E)[] ? E : (T extends Set ? E : never); -type MapKey = T extends Map ? K : never; -type MapValue = T extends Map ? V : never; - -// Object and all its subclasses, excluding Function which is its superclass. -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type ClassConstructor = new (...args: any[]) => NonNullable; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type AnythingButClassConstructor = T extends ClassConstructor ? never : T; + isTrueFailed, + isFalseFailed +} from "./message/BooleanMessages.mjs"; +import {Node16Colors} from "./message/diff/Node16Colors.mjs"; +import {Node16MillionColors} from "./message/diff/Node16MillionColors.mjs"; +import {Node256Colors} from "./message/diff/Node256Colors.mjs"; +import {IllegalStateError} from "./util/IllegalStateError.mjs"; +import { + appendToValue, + sortByKeys +} from "./validator/Maps.mjs"; +import {ObjectSizeValidatorImpl} from "./validator/ObjectSizeValidatorImpl.mjs"; +import {Pluralizer} from "./validator/Pluralizer.mjs"; +import { + lastConsecutiveIndexOf, + lastIndexOf, + containsOnly, + getMapper, + valueIsStripped +} from "./util/Strings.mjs"; +import type {ConfigurationUpdater} from "./ConfigurationUpdater.mjs"; +import { + type StringMapper, + isStringMapper, + INTERNAL_VALUE_TO_STRING +} from "./StringMapper.mjs"; +import {AssertionError} from "./util/AssertionError.mjs"; +import type {Validators} from "../Validators.mjs"; +import type {DiffWriter} from "./message/diff/DiffWriter.mjs"; +import type {ColoredDiff} from "./message/diff/ColoredDiff.mjs"; +import type {ValidatorComponent} from "../validator/component/ValidatorComponent.mjs"; +import {MessageBuilder} from "./message/section/MessageBuilder.mjs"; +import { + objectIsEmpty, + objectIsNotEmpty +} from "./message/ObjectMessages.mjs"; +import { + numberIsNegative, + numberIsNotNegative, + numberIsZero, + numberIsNotZero, + numberIsPositive, + numberIsNotPositive, + numberIsMultipleOf, + numberIsNotMultipleOf, + numberIsWholeNumber, + numberIsNotWholeNumber, + numberIsNumber, + numberIsNotNumber, + numberIsFinite, + numberIsInfinite +} from "./message/NumberMessages.mjs"; +import { + comparableIsEqualTo, + comparableIsLessThan, + comparableIsLessThanOrEqualTo, + comparableIsGreaterThanOrEqualTo, + comparableIsGreaterThan, + comparableCompareValues, + isBetweenFailed, + comparableGetBounds +} from "./message/ComparableMessages.mjs"; +import { + stringIsBlank, + stringIsNotBlank, + stringIsTrimmed, + stringIsStripped, + stringStartsWith, + stringDoesNotStartWith, + stringEndsWith, + stringDoesNotEndWith, + stringContains, + stringDoesNotContain, + stringMatches +} from "./message/StringMessages.mjs"; +import { + messagesIsUndefined, + messagesIsNotUndefined, + messagesIsNull, + messagesIsNotNull, + messagesConstraint, + messagesIsEqualTo, + messagesIsInstanceOf, + messagesIsNotInstanceOf, + messagesIsNotEqualTo, + MINIMUM_LENGTH_FOR_DIFF +} from "./message/ValidatorMessages.mjs"; +import type {UnsignedNumberValidator} from "../validator/UnsignedNumberValidator.mjs"; +import type {MessageSection} from "./message/section/MessageSection.mjs"; +import {ContextSection} from "./message/section/ContextSection.mjs"; +import {StringSection} from "./message/section/StringSection.mjs"; +import {ObjectAndSize} from "./util/ObjectAndSize.mjs"; +import { ValidationTarget} from "./util/ValidationTarget.mjs"; +import {Difference} from "./util/Difference.mjs"; +import { + collectionContainsSize, + collectionSizeIsBetween, + collectionContains, + collectionDoesNotContain, + collectionContainsExactly, + collectionDoesNotContainExactly, + collectionContainsAny, + collectionDoesNotContainAny, + collectionContainsAll, + collectionDoesNotContainAll, + collectionDoesNotContainDuplicates, + collectionIsSorted, + collectionContainsSameNullity +} from "./message/CollectionMessages.mjs"; +import { + classIsPrimitive, + classIsSupertypeOf, + classIsSubtypeOf +} from "./message/ClassMessages.mjs"; +import type {ClassValidator} from "../validator/ClassValidator.mts"; export { - ArrayValidatorImpl, - ArrayVerifierImpl, - ClassValidatorImpl, - ClassVerifierImpl, + AbstractCollectionValidator, SetValidatorImpl, - SetVerifierImpl, StringValidatorImpl, - StringVerifierImpl, TerminalEncoding, - TerminalEncodings, - ValidationFailure, - AbstractGlobalConfiguration, + sortByDecreasingRank, Configuration, ContextGenerator, - ContextLine, NEWLINE_MARKER, + EOS_MARKER, NEWLINE_PATTERN, + EOL_PATTERN, + DIFF_EQUAL, + DIFF_DELETE, + DIFF_INSERT, AbstractColorWriter, AbstractDiffWriter, DiffGenerator, - EOS_MARKER, DiffResult, - GlobalRequirements, IllegalStateError, - InetAddressValidatorImpl, - InetAddressVerifierImpl, MainGlobalConfiguration, - Maps, + appendToValue, + sortByKeys, MapValidatorImpl, - MapVerifierImpl, - AbstractNumberValidator, NumberValidatorImpl, - AbstractNumberVerifier, - NumberVerifierImpl, BooleanValidatorImpl, - BooleanVerifierImpl, Node16Colors, Node256Colors, Node16MillionColors, - SizeValidatorImpl, - Objects, - AbstractObjectValidator, + ObjectSizeValidatorImpl, + classExtends, + assert, + requireThatValueIsNotNull, + assertThatValueIsNotNull, + requireThatType, + assertThatType, + requireThatTypeCategory, + assertThatTypeCategory, + requireThatInstanceOf, + assertThatInstanceOf, + requireThatStringIsNotEmpty, + assertThatStringIsNotEmpty, + internalValueToString, + getSuperclass, + verifyName, + quoteString, + AbstractValidators, + AbstractValidator, ObjectValidatorImpl, - AbstractObjectVerifier, - ObjectVerifierImpl, Pluralizer, - Requirements, - Strings, + requireThat, + assertThat, + checkIf, + updateConfiguration, + getContext, + withContext, + removeContext, + lastConsecutiveIndexOf, + lastIndexOf, + containsOnly, + getMapper, + valueIsStripped, TextOnly, Terminal, - VariableType + Type, + TypeCategory, + ArrayValidatorImpl, + MainApplicationScope, + JavascriptValidatorsImpl, + DefaultProcessScope, + MutableConfiguration, + AssertionError, + MutableStringMappers, + StringMappers, + MessageBuilder, + messagesIsInstanceOf, + messagesIsNotInstanceOf, + messagesIsNotEqualTo, + numberIsNegative, + numberIsNotNegative, + numberIsZero, + numberIsNotZero, + numberIsPositive, + numberIsNotPositive, + numberIsMultipleOf, + numberIsNotMultipleOf, + numberIsWholeNumber, + numberIsNotWholeNumber, + numberIsNumber, + numberIsNotNumber, + numberIsFinite, + numberIsInfinite, + comparableIsEqualTo, + comparableIsLessThan, + comparableIsLessThanOrEqualTo, + comparableIsGreaterThanOrEqualTo, + comparableIsGreaterThan, + comparableCompareValues, + isBetweenFailed, + comparableGetBounds, + isTrueFailed, + isFalseFailed, + JavascriptValidators, + ValidationFailureImpl, + MultipleFailuresError, + AbstractApplicationScope, + isApplicationScope, + ContextSection, + StringSection, + INTERNAL_VALUE_TO_STRING, + isStringMapper, + isErrorBuilder, + ValidationTarget, + stringIsBlank, + stringIsNotBlank, + stringIsTrimmed, + stringIsStripped, + stringStartsWith, + stringDoesNotStartWith, + stringEndsWith, + stringDoesNotEndWith, + stringContains, + stringDoesNotContain, + stringMatches, + Difference, + ObjectAndSize, + collectionContainsSize, + collectionSizeIsBetween, + collectionContains, + collectionDoesNotContain, + collectionContainsExactly, + collectionDoesNotContainExactly, + collectionContainsAny, + collectionDoesNotContainAny, + collectionContainsAll, + collectionDoesNotContainAll, + collectionDoesNotContainDuplicates, + collectionIsSorted, + collectionContainsSameNullity, + classIsPrimitive, + classIsSupertypeOf, + classIsSubtypeOf, + messagesIsUndefined, + messagesIsNotUndefined, + messagesIsNull, + messagesIsNotNull, + objectIsEmpty, + objectIsNotEmpty, + messagesConstraint, + messagesIsEqualTo, + isValidationFailure, + ValidationFailures, + MINIMUM_LENGTH_FOR_DIFF }; export type { - GlobalConfiguration, - ArrayValidator, - ArrayVerifier, - ClassValidator, - ClassVerifier, SetValidator, - SetVerifier, StringValidator, - StringVerifier, - InetAddressValidator, - InetAddressVerifier, MapValidator, - MapVerifier, - ExtensibleNumberValidator, - ExtensibleNumberVerifier, NumberValidator, - NumberVerifier, BooleanValidator, - BooleanVerifier, - ExtensibleObjectValidator, - ExtensibleObjectVerifier, ObjectValidator, - ObjectVerifier, ElementOf, MapKey, MapValue, ClassConstructor, - AnythingButClassConstructor + Comparable, + NonUndefinable, + ArrayValidator, + ConfigurationUpdater, + Validators, + DiffWriter, + ColoredDiff, + ValidatorComponent, + UnsignedNumberValidator, + StringMapper, + JavascriptRequireThat, + JavascriptAssertThat, + JavascriptCheckIf, + CollectionComponent, + ErrorBuilder, + NegativeNumberComponent, + NumberComponent, + ZeroNumberComponent, + PositiveNumberComponent, + ValidationFailure, + ProcessScope, + ApplicationScope, + GlobalConfiguration, + MessageSection, + ClassValidator }; \ No newline at end of file diff --git a/src/internal/message/BooleanMessages.mts b/src/internal/message/BooleanMessages.mts new file mode 100644 index 0000000..39abcc6 --- /dev/null +++ b/src/internal/message/BooleanMessages.mts @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +import { + AbstractValidator, + messagesConstraint +} from "../internal.mjs"; + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function isTrueFailed(validator: AbstractValidator) +{ + return messagesConstraint(validator, "must be true"); +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function isFalseFailed(validator: AbstractValidator) +{ + return messagesConstraint(validator, "must be false"); +} + +export { + isTrueFailed, + isFalseFailed +}; \ No newline at end of file diff --git a/src/internal/message/ClassMessages.mts b/src/internal/message/ClassMessages.mts new file mode 100644 index 0000000..9bee246 --- /dev/null +++ b/src/internal/message/ClassMessages.mts @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +import { + AbstractValidator, + messagesConstraint, + type ClassConstructor +} from "../internal.mjs"; + + + /** + * @param validator - the validator + * @returns a message for the validation failure + */ + function classIsPrimitive(validator: AbstractValidator) + { + return messagesConstraint(validator, "must be a primitive type"); + } + +/** + * @param subtype - the subtype + * @param validator - the validator + * @returns a message for the validation failure + */ +function classIsSupertypeOf(validator: AbstractValidator, subtype: ClassConstructor) +{ + return messagesConstraint(validator, " must be a supertype of " + subtype.toString()); +} + +/** + * @param supertype - the supertype + * @param validator - the validator + * @returns a message for the validation failure + */ +function classIsSubtypeOf(validator: AbstractValidator, supertype: ClassConstructor) +{ + return messagesConstraint(validator, "must be a subtype of " + supertype.toString()); +} + +export +{ + classIsPrimitive, + classIsSupertypeOf, + classIsSubtypeOf +}; \ No newline at end of file diff --git a/src/internal/message/CollectionMessages.mts b/src/internal/message/CollectionMessages.mts new file mode 100644 index 0000000..f71e267 --- /dev/null +++ b/src/internal/message/CollectionMessages.mts @@ -0,0 +1,440 @@ +import { + AbstractValidator, + MessageBuilder, + Pluralizer, + assert, + AssertionError, + Difference, + objectIsNotEmpty, + objectIsEmpty, + comparableGetBounds +} from "../internal.mjs"; + + +/** + * @param validator - the collection's validator + * @param actualSizeName - the name of the collection's size + * @param actualSize - the collection's size + * @param relationship - the relationship between the actual and expected sizes (e.g. "must contain less + * than") + * @param expectedSizeName - an expression representing the expected size of the collection + * @param expectedSize - the number of elements that should be in the collection + * @param pluralizer - the type of items in the collection + * @returns a message for the validation failure + */ +function collectionContainsSize(validator: AbstractValidator, actualSizeName: string, + actualSize: number | null, relationship: string, + expectedSizeName: string | null, expectedSize: number, pluralizer: Pluralizer) +{ + // "actual" must contain exactly expected.size() characters. + // actual : "hello world" + // actual.size() : 11 + // expected.size(): 15 + const expectedNameOrSize = validator.getNameOrValue("", expectedSizeName, "", expectedSize); + + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} ${relationship} ${expectedNameOrSize} ${pluralizer.nameOf( + expectedSize)}.`); + + validator.value.undefinedOrNullToInvalid().ifValid(v => messageBuilder.withContext(v, name)); + if (actualSize !== null) + messageBuilder.withContext(actualSize, actualSizeName); + if (expectedSizeName !== null) + messageBuilder.withContext(expectedSize, expectedSizeName); + return messageBuilder; +} + +/** + * @param validator - the collection's validator + * @param actualSizeName - the name of the collection's size + * @param actualSize - the collection's size + * @param minimum - the collection's minimum size + * @param minimumInclusive - `true` if minimum size is inclusive + * @param maximum - the collection's maximum size + * @param maximumInclusive - `true` if maximum size is inclusive + * @param pluralizer - the type of items in the collection + */ +function collectionSizeIsBetween(validator: AbstractValidator, actualSizeName: string, + actualSize: number | null, minimum: number, minimumInclusive: boolean, + maximum: number, maximumInclusive: boolean, pluralizer: Pluralizer) +{ + assert(maximum >= minimum, undefined, `"minimum: ${minimum}, maximum: ${maximum}`); + + const bounds = comparableGetBounds(minimum, minimumInclusive, maximum, maximumInclusive, + validator.configuration().stringMappers()); + + const name = validator.getName(); + let message = MessageBuilder.quoteName(name); + + if (actualSize === null) + { + // The size is null (e.g. the collection is null) + // + // "actual" must contain [1, 3] elements + message += ` must contain ${bounds} ${pluralizer.nameOf(2)} .`; + return new MessageBuilder(validator, message); + } + + // actual must contain at least 4 characters. + // actual : "hey" + // actual.length(): 3 + // Bounds : [4, 6] + let inclusiveMinimum; + if (minimumInclusive) + inclusiveMinimum = minimum; + else + inclusiveMinimum = minimum + 1; + + let exclusiveMaximum; + if (maximumInclusive) + exclusiveMaximum = maximum - 1; + else + exclusiveMaximum = maximum; + + message += " must contain "; + if (actualSize < inclusiveMinimum) + message += "at least "; + else if (actualSize >= exclusiveMaximum) + message += "at most "; + else + { + throw new AssertionError(`Value should have been out of bounds. +actual: ${actualSize.toString()} +bounds: ${bounds}`); + } + message += `${pluralizer.nameOf(2)}.`; + return new MessageBuilder(validator, message). + withContext(validator.getValue(), name). + withContext(actualSize, actualSizeName). + withContext(bounds, "bounds"); +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function collectionIsEmpty(validator: AbstractValidator) +{ + return objectIsEmpty(validator); +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function collectionIsNotEmpty(validator: AbstractValidator) +{ + return objectIsNotEmpty(validator); +} + + +/** + * @param validator - the validator + * @param expectedName - the name of the expected value + * @param expected - the expected value + * @returns a message for the validation failure + */ +function collectionContains(validator: AbstractValidator, expectedName: string | null, + expected: unknown) +{ + // "actual" must contain the same value as "expected". + // actual : 5 + // expected: 2 + return collectionContainsImpl(validator, "must contain", expectedName, expected); +} + + +/** + * @param validator - the validator + * @param relationship - the relationship between the actual and other value (e.g. "must contain") + * @param otherName - the name of the other value + * @param other - the other value + * @returns a message for the validation failure + */ +function collectionContainsImpl(validator: AbstractValidator, relationship: string, + otherName: string | null, other: unknown) +{ + // "actual" must contain the same value as "expected". + // actual: 5 + // factor: 2 + const otherNameOrValue = validator.getNameOrValue("the same value as ", otherName, "", other); + + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(validator.getName())} ${relationship} ${otherNameOrValue}.`); + if (otherName != null) + messageBuilder.withContext(other, otherName); + return messageBuilder; +} + +/** + * @param validator - the validator + * @param unwantedName - the name of the unwanted value + * @param unwanted - the unwanted value + * @returns a message for the validation failure + */ +function collectionDoesNotContain(validator: AbstractValidator, + unwantedName: string | null, unwanted: unknown) +{ + // "actual" may not contain the same value as "unwanted". + // actual : 5 + // unwanted: 2 + return collectionContainsImpl(validator, "may not contain", unwantedName, unwanted); +} + +/** + * @param validator - the validator + * @param expectedName - the name of the expected collection + * @param expected - the collection of expected values + * @param pluralizer - the type of items in the collections + * @returns a message for the validation failure + */ +function collectionContainsAny(validator: AbstractValidator, + expectedName: string | null, expected: unknown, pluralizer: Pluralizer) +{ + // "actual" must contain any of the elements present in "expected". + // actual : [1, 2, 3] + // expected: [2, 3, 4] + const expectedNameOrValue = validator.getNameOrValue("", expectedName, "the set ", expected); + + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} must contain any of the ${pluralizer.nameOf(2)} \ +present in ${expectedNameOrValue}.`); + + validator.value.undefinedOrNullToInvalid().ifValid(v => messageBuilder.withContext(v, name)); + if (expectedName !== null) + messageBuilder.withContext(expected, expectedName); + return messageBuilder; +} + +/** + * @typeParam E - the type of elements in the value + * @param validator - the validator + * @param difference - the difference between the actual and unwanted values + * @param unwantedName - the name of the unwanted collection + * @param unwanted - the collection of unwanted elements + * @param pluralizer - the type of items in the collections + * @returns a message for the validation failure + */ +function collectionDoesNotContainAny(validator: AbstractValidator, + difference: Difference | null, unwantedName: string | null, + unwanted: unknown, pluralizer: Pluralizer) +{ + // "actual" may not contain any of the elements present in "unwanted". + // actual : [1, 2, 3] + // unwanted: [2, 3, 4] + // elementsToRemove: [2, 3, 4] + const unwantedNameOrValue = validator.getNameOrValue("", unwantedName, "the set ", unwanted); + + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} may not contain any of the ${pluralizer.nameOf(2)} \ +present in ${unwantedNameOrValue}.`); + + validator.value.undefinedOrNullToInvalid().ifValid(v => messageBuilder.withContext(v, name)); + if (unwantedName != null) + messageBuilder.withContext(unwanted, unwantedName); + if (difference != null) + messageBuilder.withContext(difference.common, "elementsToRemove"); + return messageBuilder; +} + +/** + * @typeParam E - the type of elements in the value + * @param validator - the validator + * @param difference - the difference between the actual and expected values + * @param expectedName - the name of the collection + * @param expected - the collection + * @param pluralizer - the type of items in the value + * @returns a message for the validation failure + */ +function collectionContainsExactly(validator: AbstractValidator, + difference: Difference | null, expectedName: string | null, + expected: unknown, pluralizer: Pluralizer) +{ + // "actual" must consist of the elements [2, 3, 4], regardless of their order. + // + // or + // + // "actual" must consist of the same elements as "expected", regardless of their order. + // actual : [1, 2, 3] + // expected: [2, 3, 4] + // missing : [4] + // unwanted: [1] + + const name = validator.getName(); + let message = `${MessageBuilder.quoteName(name)} must consist of the `; + if (expectedName !== null) + message += "same "; + message += `${pluralizer.nameOf(2)} `; + const expectedNameOrValue = validator.getNameOrValue("as ", expectedName, "", expected); + message += `${expectedNameOrValue}, regardless of their order.`; + + const messageBuilder = new MessageBuilder(validator, message); + validator.value.undefinedOrNullToInvalid().ifValid(v => messageBuilder.withContext(v, name)); + if (expectedName !== null) + messageBuilder.withContext(expected, expectedName); + if (difference !== null) + { + messageBuilder.withContext(difference.onlyInOther, "missing"). + withContext(difference.onlyInActual, "unwanted"); + } + return messageBuilder; +} + +/** + * @param validator - the validator + * @param unwantedName - the name of the collection + * @param unwanted - the collection + * @param pluralizer - the type of items in the value + * @returns a message for the validation failure + */ +function collectionDoesNotContainExactly(validator: AbstractValidator, + unwantedName: string | null, unwanted: unknown, + pluralizer: Pluralizer) +{ + // "actual" may not consist of the elements [2, 3, 4], regardless of their order. + // + // or + // + // "actual" may not consist of the same elements as "expected", regardless of their order. + // unwanted : [1, 2, 3] + let message = `${MessageBuilder.quoteName(validator.getName())} may not consist of the `; + if (unwantedName !== null) + message += "same "; + message += `${pluralizer.nameOf(2)} `; + const unwantedStringNameOrValue = validator.getNameOrValue("as ", unwantedName, "", unwanted); + message += `${unwantedStringNameOrValue}, regardless of their order.`; + + const messageBuilder = new MessageBuilder(validator, message); + if (unwantedName !== null) + messageBuilder.withContext(unwanted, unwantedName); + return messageBuilder; +} + +/** + * @typeParam E - the type of elements in the value + * @param validator - the validator + * @param difference - the difference between the actual and expected values + * @param expectedName - the name of the expected collection + * @param expected - the collection of expected values + * @param pluralizer - the type of items in the value + * @returns a message for the validation failure + */ +function collectionContainsAll(validator: AbstractValidator, + difference: Difference | null, expectedName: string | null, + expected: unknown, pluralizer: Pluralizer) +{ + // "actual" must contain all the elements present in "expected". + // actual : [1, 2, 3] + // expected: [2, 3, 4] + // missing : [4] + const expectedNameOrValue = validator.getNameOrValue("", expectedName, "the set ", expected); + + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} must contain all the ${pluralizer.nameOf(2)} \ +present in ${expectedNameOrValue}.`); + + validator.value.undefinedOrNullToInvalid().ifValid(v => messageBuilder.withContext(v, name)); + if (expectedName !== null) + messageBuilder.withContext(expected, expectedName); + if (difference !== null) + messageBuilder.withContext(difference.onlyInOther, "missing"); + return messageBuilder; +} + +/** + * @param validator - the validator + * @param unwantedName - the name of the unwanted collection + * @param unwanted - the collection of unwanted values + * @param pluralizer - the type of items in the value + * @returns a message for the validation failure + */ +function collectionDoesNotContainAll(validator: AbstractValidator, + unwantedName: string | null, unwanted: unknown, + pluralizer: Pluralizer) +{ + // "actual" may not contain some, but not all, the elements present in "unwanted". + // actual : [1, 2, 3] + // unwanted: [2, 3, 4] + const unwantedNameOrValue = validator.getNameOrValue("", unwantedName, "the set ", unwanted); + + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} may contain some, but not all, the \ +${pluralizer.nameOf(2)} present in ${unwantedNameOrValue}.`); + + validator.value.undefinedOrNullToInvalid().ifValid(v => messageBuilder.withContext(v, name)); + if (unwantedName !== null) + messageBuilder.withContext(unwanted, unwantedName); + return messageBuilder; +} + +/** + * @typeParam E - the type of elements in the value + * @param validator - the validator + * @param duplicates - the duplicate values in the value being validated + * @param pluralizer - the type of items in the value + * @returns a message for the validation failure + */ +function collectionDoesNotContainDuplicates(validator: AbstractValidator, + duplicates: Set | null, pluralizer: Pluralizer) +{ + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} may not contain any duplicate ${pluralizer.nameOf(2)}.`); + if (duplicates !== null) + messageBuilder.withContext(duplicates, "duplicates"); + validator.value.undefinedOrNullToInvalid().ifValid(v => messageBuilder.withContext(v, name)); + return messageBuilder; +} + +/** + * @param validator - the validator + * @param sorted - the sorted representation of the value being validated + * @returns a message for the validation failure + */ +function collectionIsSorted(validator: AbstractValidator, + sorted: unknown[] | null) +{ + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} must be sorted.`); + validator.value.undefinedOrNullToInvalid().ifValid(v => messageBuilder.withContext(v, name)); + if (sorted !== null) + messageBuilder.withContext(sorted, "expected"); + return messageBuilder; +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function collectionContainsSameNullity(validator: AbstractValidator) +{ + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} must contain all nulls, or no nulls.`); + validator.value.undefinedOrNullToInvalid().ifValid(v => messageBuilder.withContext(v, name)); + return messageBuilder; +} + +export { + collectionContainsSize, + collectionSizeIsBetween, + collectionIsEmpty, + collectionIsNotEmpty, + collectionContains, + collectionDoesNotContain, + collectionContainsExactly, + collectionDoesNotContainExactly, + collectionContainsAny, + collectionDoesNotContainAny, + collectionContainsAll, + collectionDoesNotContainAll, + collectionDoesNotContainDuplicates, + collectionIsSorted, + collectionContainsSameNullity +}; \ No newline at end of file diff --git a/src/internal/message/ComparableMessages.mts b/src/internal/message/ComparableMessages.mts new file mode 100644 index 0000000..637608d --- /dev/null +++ b/src/internal/message/ComparableMessages.mts @@ -0,0 +1,156 @@ +import { + AbstractValidator, + MessageBuilder, + StringMappers +} from "../internal.mjs"; + +/** + * @param validator - the validator + * @param expectedName - the name of the expected value + * @param expected - the expected value + * @returns a message for the validation failure + */ +function comparableIsEqualTo(validator: AbstractValidator, expectedName: string | null, + expected: unknown) +{ + return comparableCompareValues(validator, "must be equal to", expectedName, expected); +} + +/** + * @param validator - the validator + * @param limitName - the name of the value's bound + * @param maximumExclusive - the exclusive upper bound + * @returns a message for the validation failure + */ +function comparableIsLessThan(validator: AbstractValidator, limitName: string | null, + maximumExclusive: unknown) +{ + return comparableCompareValues(validator, "must be less than", limitName, maximumExclusive); +} + +/** + * @param validator - the validator + * @param limitName - the name of the value's bound + * @param maximumInclusive - the inclusive upper bound + * @returns a message for the validation failure + */ +function comparableIsLessThanOrEqualTo(validator: AbstractValidator, + limitName: string | null, maximumInclusive: unknown) +{ + return comparableCompareValues(validator, "must be less than or equal to", limitName, maximumInclusive); +} + +/** + * @param validator - the validator + * @param limitName - the name of the value's bound + * @param minimumInclusive - the inclusive lower bound + * @returns a message for the validation failure + */ +function comparableIsGreaterThanOrEqualTo(validator: AbstractValidator, + limitName: string | null, minimumInclusive: unknown) +{ + return comparableCompareValues(validator, "must be greater than or equal to", limitName, minimumInclusive); +} + +/** + * @param validator - the validator + * @param limitName - the name of the value's bound + * @param minimumExclusive - the exclusive lower bound + * @returns a message for the validation failure + */ +function comparableIsGreaterThan(validator: AbstractValidator, + limitName: string | null, minimumExclusive: unknown) +{ + return comparableCompareValues(validator, "must be greater than", limitName, minimumExclusive); +} + +/** + * @param validator - the validator + * @param relationship - a description of the relationship between the actual and expected value (e.g. "must + * be equal to") + * @param expectedName - the name of the expected value + * @param expected - the expected value + * @returns a message for the validation failure + */ +function comparableCompareValues(validator: AbstractValidator, relationship: string, + expectedName: string | null, expected: unknown) +{ + const actualName = validator.getName(); + + // "actual" must be equal to "expected". + // actual : 123 + // expected: 456 + const expectedNameOrValue = validator.getNameOrValue("", expectedName, "", expected); + + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(actualName)} ${relationship} ${expectedNameOrValue}.`); + + const invalidToNull = validator.getValueOrDefault(null); + if (invalidToNull != null) + messageBuilder.withContext(invalidToNull, actualName); + if (expectedName != null) + messageBuilder.withContext(expected, expectedName); + return messageBuilder; +} + +/** + * @param validator - the validator + * @param minimum - the object representation of the lower limit + * @param minimumInclusive - `true` if the lower bound of the range is inclusive + * @param maximum - the object representation of the upper limit + * @param maximumInclusive - `true` if the upper bound of the range is inclusive + * @returns a message for the validation failure + */ +function isBetweenFailed(validator: AbstractValidator, minimum: unknown, + minimumInclusive: boolean, maximum: unknown, maximumInclusive: boolean) +{ + const name = validator.getName(); + const builder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} is out of bounds.`); + const value = validator.getValueOrDefault(null); + if (value != null) + builder.withContext(value, name); + + const bounds = comparableGetBounds(minimum, minimumInclusive, maximum, maximumInclusive, + validator.configuration().stringMappers()); + builder.withContext(bounds, "bounds"); + return builder; +} + +/** + * @param minimum - the Object representation of the lower limit + * @param minimumInclusive - `true` if the lower bound of the range is inclusive + * @param maximum - the Object representation of the upper limit + * @param maximumInclusive - `true` if the upper bound of the range is inclusive + * @param stringMappers - the configuration used to map contextual values to a String + * @returns a message for the validation failure + */ +function comparableGetBounds(minimum: unknown, minimumInclusive: boolean, maximum: unknown, + maximumInclusive: boolean, stringMappers: StringMappers) +{ + let bounds = ""; + if (minimumInclusive) + bounds += "["; + else + bounds += "("; + const minimumAsString = stringMappers.toString(minimum); + const maximumAsString = stringMappers.toString(maximum); + bounds += `${minimumAsString}, ${maximumAsString}`; + if (maximumInclusive) + bounds += "]"; + else + bounds += ")"; + return bounds; +} + + +export { + comparableIsEqualTo, + comparableIsLessThan, + comparableIsLessThanOrEqualTo, + comparableIsGreaterThanOrEqualTo, + comparableIsGreaterThan, + comparableCompareValues, + isBetweenFailed, + comparableGetBounds +}; \ No newline at end of file diff --git a/src/internal/message/NumberMessages.mts b/src/internal/message/NumberMessages.mts new file mode 100644 index 0000000..7f8a4e4 --- /dev/null +++ b/src/internal/message/NumberMessages.mts @@ -0,0 +1,154 @@ +import { + AbstractValidator, + messagesConstraint, + comparableCompareValues +} from "../internal.mjs"; + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function numberIsNegative(validator: AbstractValidator) +{ + return messagesConstraint(validator, "must be negative"); +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function numberIsNotNegative(validator: AbstractValidator) +{ + return messagesConstraint(validator, "may not be negative"); +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function numberIsZero(validator: AbstractValidator) +{ + return messagesConstraint(validator, "must be zero"); +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function numberIsNotZero(validator: AbstractValidator) +{ + return messagesConstraint(validator, "may not be zero"); +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function numberIsPositive(validator: AbstractValidator) +{ + return messagesConstraint(validator, "must be positive"); +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function numberIsNotPositive(validator: AbstractValidator) +{ + return messagesConstraint(validator, "may not be positive"); +} + +/** + * @param validator - the validator + * @param factorName - the name of the factor + * @param factor - the value being multiplied by + * @returns a message for the validation failure + */ +function numberIsMultipleOf(validator: AbstractValidator, + factorName: string | null, factor: number) +{ + return comparableCompareValues(validator, "must be a multiple of", factorName, factor); +} + +/** + * @param validator - the validator + * @param factorName - the name of the factor + * @param factor - the value being multiplied by + * @returns a message for the validation failure + */ +function numberIsNotMultipleOf(validator: AbstractValidator, + factorName: string | null, factor: number) +{ + return comparableCompareValues(validator, "may not be a multiple of", factorName, factor); +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function numberIsWholeNumber(validator: AbstractValidator) +{ + return messagesConstraint(validator, "must be a whole number"); +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function numberIsNotWholeNumber(validator: AbstractValidator) +{ + return messagesConstraint(validator, "may not be a whole number"); +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function numberIsNumber(validator: AbstractValidator) +{ + return messagesConstraint(validator, "must be a well-defined number"); +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function numberIsNotNumber(validator: AbstractValidator) +{ + return messagesConstraint(validator, "may not be a well-defined number"); +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function numberIsFinite(validator: AbstractValidator) +{ + return messagesConstraint(validator, "must be a finite number"); +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function numberIsInfinite(validator: AbstractValidator) +{ + return messagesConstraint(validator, "must be an infinite number"); +} + +export { + numberIsNegative, + numberIsNotNegative, + numberIsZero, + numberIsNotZero, + numberIsPositive, + numberIsNotPositive, + numberIsMultipleOf, + numberIsNotMultipleOf, + numberIsWholeNumber, + numberIsNotWholeNumber, + numberIsNumber, + numberIsNotNumber, + numberIsFinite, + numberIsInfinite +}; \ No newline at end of file diff --git a/src/internal/message/ObjectMessages.mts b/src/internal/message/ObjectMessages.mts new file mode 100644 index 0000000..5d70823 --- /dev/null +++ b/src/internal/message/ObjectMessages.mts @@ -0,0 +1,34 @@ +import { + AbstractValidator, + MessageBuilder +} from "../internal.mjs"; + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function objectIsEmpty(validator: AbstractValidator) +{ + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} must be empty.`); + const value = validator.getValueOrDefault(null); + if (value !== null) + messageBuilder.withContext(value, name); + return messageBuilder; +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function objectIsNotEmpty(validator: AbstractValidator) +{ + const name = validator.getName(); + return new MessageBuilder(validator, MessageBuilder.quoteName(name) + " may not be empty."); +} + +export { + objectIsEmpty, + objectIsNotEmpty +}; \ No newline at end of file diff --git a/src/internal/message/StringMessages.mts b/src/internal/message/StringMessages.mts new file mode 100644 index 0000000..4732fcb --- /dev/null +++ b/src/internal/message/StringMessages.mts @@ -0,0 +1,198 @@ +import { + MessageBuilder, + AbstractValidator +} from "../internal.mjs"; + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function stringIsBlank(validator: AbstractValidator) +{ + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} must be empty or contain only whitespace codepoints.`); + const value = validator.getValueOrDefault(null); + if (value !== null) + messageBuilder.withContext(value, name); + return messageBuilder; +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function stringIsNotBlank(validator: AbstractValidator) +{ + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} may not be empty or contain only whitespace codepoints.`); + const value = validator.getValueOrDefault(null); + if (value !== null) + messageBuilder.withContext(value, name); + return messageBuilder; +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function stringIsTrimmed(validator: AbstractValidator) +{ + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} may not contain leading or trailing whitespace.`); + const value = validator.getValueOrDefault(null); + if (value !== null) + messageBuilder.withContext(value, name); + return messageBuilder; +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function stringIsStripped(validator: AbstractValidator) +{ + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} may not contain leading or trailing whitespace.`); + const value = validator.getValueOrDefault(null); + if (value !== null) + messageBuilder.withContext(value, name); + return messageBuilder; +} + +/** + * @param validator - the validator + * @param prefix - the value that the string must start with + * @returns a message for the validation failure + */ +function stringStartsWith(validator: AbstractValidator, prefix: string) +{ + const name = validator.getName(); + const stringMappers = validator.configuration().stringMappers(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} must start with ${stringMappers.toString(prefix)}.`); + const value = validator.getValueOrDefault(null); + if (value !== null) + messageBuilder.withContext(value, name); + return messageBuilder; +} + +/** + * @param validator - the validator + * @param prefix - the value that the string must start with + * @returns a message for the validation failure + */ +function stringDoesNotStartWith(validator: AbstractValidator, prefix: string) +{ + const name = validator.getName(); + const stringMappers = validator.configuration().stringMappers(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} may not start with ${stringMappers.toString(prefix)}.`); + const value = validator.getValueOrDefault(null); + if (value !== null) + messageBuilder.withContext(value, name); + return messageBuilder; +} + +/** + * @param validator - the validator + * @param suffix - the value that the string must end with + * @returns a message for the validation failure + */ +function stringEndsWith(validator: AbstractValidator, suffix: string) +{ + const name = validator.getName(); + const stringMappers = validator.configuration().stringMappers(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} must end with ${stringMappers.toString(suffix)}.`); + const value = validator.getValueOrDefault(null); + if (value !== null) + messageBuilder.withContext(value, name); + return messageBuilder; +} + +/** + * @param validator - the validator + * @param suffix - the value that the string must end with + * @returns a message for the validation failure + */ +function stringDoesNotEndWith(validator: AbstractValidator, suffix: string) +{ + const name = validator.getName(); + const stringMappers = validator.configuration().stringMappers(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} may not end with ${stringMappers.toString(suffix)}.`); + const value = validator.getValueOrDefault(null); + if (value !== null) + messageBuilder.withContext(value, name); + return messageBuilder; +} + +/** + * @param validator - the validator + * @param expected - the expected value + * @returns a message for the validation failure + */ +function stringContains(validator: AbstractValidator, expected: string) +{ + const name = validator.getName(); + const stringMappers = validator.configuration().stringMappers(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} must contain ${stringMappers.toString(expected)}.`); + const value = validator.getValueOrDefault(null); + if (value !== null) + messageBuilder.withContext(value, name); + return messageBuilder; +} + +/** + * @param validator - the validator + * @param unwanted - the unwanted value + * @returns a message for the validation failure + */ +function stringDoesNotContain(validator: AbstractValidator, unwanted: string) +{ + const name = validator.getName(); + const stringMappers = validator.configuration().stringMappers(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} may not contain ${stringMappers.toString(unwanted)}.`); + const value = validator.getValueOrDefault(null); + if (value !== null) + messageBuilder.withContext(value, name); + return messageBuilder; +} + +/** + * @param validator - the validator + * @param regex - the regular expression + * @returns a message for the validation failure + */ +function stringMatches(validator: AbstractValidator, regex: string) +{ + const name = validator.getName(); + const stringMappers = validator.configuration().stringMappers(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} must match the regular expression ${stringMappers.toString(regex)}.`); + const value = validator.getValueOrDefault(null); + if (value !== null) + messageBuilder.withContext(value, name); + return messageBuilder; +} + +export +{ + stringIsBlank, + stringIsNotBlank, + stringIsTrimmed, + stringIsStripped, + stringStartsWith, + stringDoesNotStartWith, + stringEndsWith, + stringDoesNotEndWith, + stringContains, + stringDoesNotContain, + stringMatches +}; \ No newline at end of file diff --git a/src/internal/message/UnquotedStringValue.mts b/src/internal/message/UnquotedStringValue.mts new file mode 100644 index 0000000..186aa77 --- /dev/null +++ b/src/internal/message/UnquotedStringValue.mts @@ -0,0 +1,28 @@ +import {assertThatValueIsNotNull} from "../validator/Objects.mjs"; + +/** + * Wraps a String to prevent it from being quoted when used as a context value. + */ +class UnquotedStringValue +{ + private readonly value: string; + + /** + * Creates a new instance. + * + * @param value - the string + * @throws TypeError if `value` is null + */ + public constructor(value: string) + { + assertThatValueIsNotNull(value, "value"); + this.value = value; + } + + public toString(): string + { + return this.value; + } +} + +export {UnquotedStringValue}; \ No newline at end of file diff --git a/src/internal/message/ValidatorMessages.mts b/src/internal/message/ValidatorMessages.mts new file mode 100644 index 0000000..54147c6 --- /dev/null +++ b/src/internal/message/ValidatorMessages.mts @@ -0,0 +1,208 @@ +import { + AbstractValidator, + MessageBuilder, + StringMappers, + Type +} from "../internal.mjs"; + +/** + * The minimum length of a value that triggers a diff. + */ +const MINIMUM_LENGTH_FOR_DIFF = 10; + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function messagesIsUndefined(validator: AbstractValidator) +{ + const messageBuilder = new MessageBuilder(validator, + MessageBuilder.quoteName(validator.getName()) + " must be undefined."); + if (validator.value.isValid()) + messageBuilder.withContext(validator.getValue(), validator.getName()); + return messageBuilder; +} + +/** + * @param validator - the validator + * @param name - the name of the value + * @returns a message for the validation failure + */ +function messagesIsNotUndefined(validator: AbstractValidator, name: string) +{ + return new MessageBuilder(validator, MessageBuilder.quoteName(name) + " may not be undefined."); +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function messagesIsNull(validator: AbstractValidator) +{ + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} must be null.`); + const value = validator.getValueOrDefault(null); + if (value !== null) + messageBuilder.withContext(value, name); + return messageBuilder; +} + +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function messagesIsNotNull(validator: AbstractValidator) +{ + return new MessageBuilder(validator, MessageBuilder.quoteName(validator.getName()) + " may not be null."); +} + +/** + * @typeParam T - the type of the value + * @param validator - the validator + * @param constraint - the constraint that the value must adhere to (e.g. "must be negative") + * @returns a message for the validation failure + */ +function messagesConstraint(validator: AbstractValidator, constraint: string) +{ + // "actual" must be negative. + // actual: 5 + const actualName = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(actualName)} ${constraint}.`); + const invalidToNull = validator.getValueOrDefault(null); + if (invalidToNull !== null) + messageBuilder.withContext(invalidToNull, validator.getName()); + return messageBuilder; +} + +/** + * @param validator - the validator + * @param expectedName - the name of the expected value + * @param expected - the expected value + * @returns a message for the validation failure + */ +function messagesIsEqualTo(validator: AbstractValidator, expectedName: string | null, + expected: unknown) +{ + const stringMappers = validator.configuration().stringMappers(); + const name = validator.getName(); + const invalidToNull = validator.getValueOrDefault(null); + if (invalidToNull == null || unnecessaryDiff(invalidToNull, stringMappers) || + unnecessaryDiff(expected, stringMappers)) + { + // 1. One of the values is short and simple enough to make a diff unnecessary. + // + // "actual" must be equal to "expected". + // actual : 123 + // expected: 456 + const expectedNameOrValue = validator.getNameOrValue("", expectedName, "", expected); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} must be equal to ${expectedNameOrValue}.`); + + validator.value.ifValid(v => messageBuilder.withContext(v, name)); + if (expectedName != null) + messageBuilder.withContext(expected, expectedName); + return messageBuilder; + } + + // 2. Both values are long and/or complex. + // + // "actual" had an unexpected value. + // + // actual : 456 + // diff : ---+++ + // expected: 123 + const resolvedExpectedName = expectedName ?? "expected"; + return new MessageBuilder(validator, `${MessageBuilder.quoteName(name)} had an unexpected value.`). + addDiff(name, invalidToNull, resolvedExpectedName, expected); +} + +/** + * @param value - a value + * @param stringMappers - the configuration used to map contextual values to a String + * @returns true if the value is short and simple enough to forego a diff + */ +function unnecessaryDiff(value: unknown, stringMappers: StringMappers) +{ + const valueForDiff = stringMappers.toString(value); + return valueForDiff.length < MINIMUM_LENGTH_FOR_DIFF && + !valueForDiff.includes("\n"); +} + +/** + * @param validator - the validator + * @param expected - the expected type + * @returns a message for the validation failure + */ +function messagesIsInstanceOf(validator: AbstractValidator, expected: Type) +{ + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} must be ${expected.toString()}.`); + const value = validator.getValueOrDefault(null); + if (value !== null || validator.value.isValid()) + { + messageBuilder.withContext(value, name); + if (value !== null) + messageBuilder.withContext(Type.of(value), `${name}.type`); + } + return messageBuilder; +} + +/** + * @param validator - the validator + * @param unwanted - the unwanted type + * @returns a message for the validation failure + */ +function messagesIsNotInstanceOf(validator: AbstractValidator, unwanted: Type) +{ + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} may not be ${unwanted.toString()}.`); + + const value = validator.getValueOrDefault(null); + if (value !== null || validator.value.isValid()) + { + messageBuilder.withContext(value, name). + withContext(Type.of(value), `${name}.type`); + } + return messageBuilder; +} + + +/** + * @param validator - the validator + * @param unwantedName - the name of the unwanted element + * @param unwanted - the unwanted element + * @returns a message for the validation failure + */ +function messagesIsNotEqualTo(validator: AbstractValidator, + unwantedName: string | null, unwanted: unknown) +{ + // "actual" may not be equal to "expected". + // actual : 123 + // expected: 456 + const unwantedNameOrValue = validator.getNameOrValue("", unwantedName, "", unwanted); + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} may not be equal to ${unwantedNameOrValue}.`); + validator.value.ifValid(v => messageBuilder.withContext(v, name)); + if (unwantedName != null) + messageBuilder.withContext(unwanted, unwantedName); + return messageBuilder; +} + +export +{ + messagesIsUndefined, + messagesIsNotUndefined, + messagesIsNull, + messagesIsNotNull, + messagesConstraint, + messagesIsEqualTo, + messagesIsInstanceOf, + messagesIsNotInstanceOf, + messagesIsNotEqualTo, + MINIMUM_LENGTH_FOR_DIFF +}; \ No newline at end of file diff --git a/src/internal/message/diff/AbstractColorWriter.mts b/src/internal/message/diff/AbstractColorWriter.mts new file mode 100644 index 0000000..c291c7a --- /dev/null +++ b/src/internal/message/diff/AbstractColorWriter.mts @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2016 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +import { + AbstractDiffWriter, + IllegalStateError, + type ColoredDiff, + appendToValue +} from "../../internal.mjs"; + +/** + * Base implementation for all color diff writers. + */ +abstract class AbstractColorWriter extends AbstractDiffWriter + implements ColoredDiff +{ + /** + * A padding character used to align values vertically. + */ + static readonly DIFF_PADDING = "/"; + /** + * Maps from a line number in the actual value to the decoration at the end of the line. + */ + private lineToActualDecoration: Map = new Map(); + /** + * Maps from a line number in the expected value to the decoration at the end of the line. + */ + private lineToExpectedDecoration: Map = new Map(); + + protected constructor() + { + super(AbstractColorWriter.DIFF_PADDING); + this.addActualLine(0); + this.addExpectedLine(0); + } + + public addActualLine(number: number): void + { + super.addActualLine(number); + this.lineToActualDecoration.set(number, DecorationType.UNDECORATED); + } + + public addExpectedLine(number: number): void + { + super.addExpectedLine(number); + this.lineToExpectedDecoration.set(number, DecorationType.UNDECORATED); + } + + public writeEqual(text: string): void + { + if (this.flushed) + throw new IllegalStateError("Writer was already flushed"); + if (text.length === 0) + return; + this.splitLines(text, (line: string) => + { + const actualDecoration = this.lineToActualDecoration.get(this.actualLineNumber); + if (actualDecoration === DecorationType.EQUAL) + appendToValue(this.lineToActualLine, this.actualLineNumber, line); + else + { + appendToValue(this.lineToActualLine, this.actualLineNumber, this.decorateEqualText(line)); + this.lineToActualDecoration.set(this.actualLineNumber, DecorationType.EQUAL); + } + + if (this.expectedLineNumber !== this.actualLineNumber) + { + const length = line.length; + const padding = this.decoratePadding(this.getPaddingMarker().repeat(length)); + appendToValue(this.lineToExpectedLine, this.actualLineNumber, padding); + this.lineToExpectedDecoration.set(this.actualLineNumber, DecorationType.EQUAL); + + appendToValue(this.lineToActualLine, this.expectedLineNumber, padding); + this.lineToActualDecoration.set(this.expectedLineNumber, DecorationType.EQUAL); + } + + const expectedDecoration = this.lineToExpectedDecoration.get(this.expectedLineNumber); + if (expectedDecoration === DecorationType.EQUAL) + appendToValue(this.lineToExpectedLine, this.expectedLineNumber, line); + else + { + appendToValue(this.lineToExpectedLine, this.expectedLineNumber, this.decorateEqualText(line)); + this.lineToExpectedDecoration.set(this.expectedLineNumber, DecorationType.EQUAL); + } + }); + } + + public writeDeleted(text: string): void + { + if (this.flushed) + throw new IllegalStateError("Writer was already flushed"); + if (text.length === 0) + return; + this.splitLines(text, (line: string) => + { + const actualDecoration = this.lineToActualDecoration.get(this.actualLineNumber); + if (actualDecoration === DecorationType.DELETE) + appendToValue(this.lineToActualLine, this.actualLineNumber, line); + else + { + appendToValue(this.lineToActualLine, this.actualLineNumber, this.decorateDeletedText(line)); + this.lineToActualDecoration.set(this.actualLineNumber, DecorationType.DELETE); + } + + const expectedDecoration = this.lineToExpectedDecoration.get(this.actualLineNumber); + const padding = this.getPaddingMarker().repeat(line.length); + if (expectedDecoration === DecorationType.DELETE) + appendToValue(this.lineToExpectedLine, this.actualLineNumber, padding); + else + { + appendToValue(this.lineToExpectedLine, this.actualLineNumber, + this.decoratePadding(padding)); + this.lineToExpectedDecoration.set(this.actualLineNumber, DecorationType.DELETE); + } + this.lineToEqualLine.set(this.actualLineNumber, false); + }); + } + + public writeInserted(text: string): void + { + if (this.flushed) + throw new IllegalStateError("Writer was already flushed"); + if (text.length === 0) + return; + this.splitLines(text, (line: string) => + { + const actualDecoration = this.lineToActualDecoration.get(this.expectedLineNumber); + const padding = this.getPaddingMarker().repeat(line.length); + if (actualDecoration === DecorationType.INSERT) + appendToValue(this.lineToActualLine, this.expectedLineNumber, this.decoratePadding(padding)); + else + { + appendToValue(this.lineToActualLine, this.expectedLineNumber, this.decoratePadding(padding)); + this.lineToActualDecoration.set(this.expectedLineNumber, DecorationType.INSERT); + } + + const expectedDecoration = this.lineToExpectedDecoration.get(this.expectedLineNumber); + if (expectedDecoration === DecorationType.INSERT) + appendToValue(this.lineToExpectedLine, this.expectedLineNumber, line); + else + { + appendToValue(this.lineToExpectedLine, this.expectedLineNumber, this.decorateInsertedText(line)); + this.lineToExpectedDecoration.set(this.expectedLineNumber, DecorationType.INSERT); + } + this.lineToEqualLine.set(this.expectedLineNumber, false); + }); + } + + protected beforeFlush() + { + for (const entry of this.lineToActualDecoration.entries()) + this.lineToActualDecoration.set(entry[0], DecorationType.UNDECORATED); + for (const entry of this.lineToExpectedDecoration.entries()) + this.lineToExpectedDecoration.set(entry[0], DecorationType.UNDECORATED); + } + + public getDiffLines(): string[] + { + if (!this.flushed) + throw new IllegalStateError("Writer must be flushed"); + return []; + } + + protected abstract afterFlush(): void; + + public abstract decorateDeletedText(text: string): string; + + public abstract decorateEqualText(text: string): string; + + public abstract decorateInsertedText(text: string): string; + + public abstract decoratePadding(text: string): string; +} + +/** + * Possible types of decorations. + */ +enum DecorationType +{ + UNDECORATED, + DELETE, + INSERT, + EQUAL +} + +export {AbstractColorWriter}; \ No newline at end of file diff --git a/src/internal/message/diff/AbstractDiffWriter.mts b/src/internal/message/diff/AbstractDiffWriter.mts new file mode 100644 index 0000000..33b0b8f --- /dev/null +++ b/src/internal/message/diff/AbstractDiffWriter.mts @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2016 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +import { + IllegalStateError, + NEWLINE_MARKER, + NEWLINE_PATTERN, + type DiffWriter, + sortByKeys, + assertThatType, + Type, + assert +} from "../../internal.mjs"; + +/** + * Base implementation for all diff writers. + */ +abstract class AbstractDiffWriter implements DiffWriter +{ + /** + * Maps each line number to its associated actual value. + */ + protected readonly lineToActualLine = new Map(); + /** + * Maps each line number to its associated expected value. + */ + protected readonly lineToExpectedLine = new Map(); + /** + * Maps each line number to an indication of whether the actual and expected values are equal. + */ + protected readonly lineToEqualLine = new Map(); + /** + * A padding character used to align values vertically. + */ + private readonly paddingMarker: string; + /** + * The final list of lines in the actual value. + */ + private readonly actualLines: string[] = []; + /** + * The final list of lines in the expected value. + */ + private readonly expectedLines: string[] = []; + /** + * The final list that indicates which lines contain actual and expected values that are equal. + */ + private readonly equalLines: boolean[] = []; + /** + * The current line number of the actual value. + */ + protected actualLineNumber = 0; + /** + * The current line number of the expected value. + */ + protected expectedLineNumber = 0; + /** + * `true` if the writer has been flushed. + */ + protected flushed = false; + + /** + * @param paddingMarker - a padding character used to align values vertically + * @throws TypeError if `paddingMarker` is `undefined` or `null` + * @throws RangeError if `paddingMarker` is empty + */ + protected constructor(paddingMarker: string) + { + assertThatType(paddingMarker, "paddingMarker", Type.STRING); + assert(paddingMarker.length !== 0, undefined, "paddingMarker may not be empty"); + this.paddingMarker = paddingMarker; + } + + /** + * Invoked before flushing the writer. + */ + protected abstract beforeFlush(): void; + + /** + * Invoked after flushing the writer. + */ + protected abstract afterFlush(): void; + + public getPaddingMarker(): string + { + return this.paddingMarker; + } + + /** + * Adds a new line for the actual value. + * + * @param number - the line number to add + */ + protected addActualLine(number: number): void + { + this.lineToActualLine.set(number, ""); + if (!this.lineToEqualLine.has(this.actualLineNumber)) + this.lineToEqualLine.set(this.actualLineNumber, true); + } + + /** + * Adds a new line for the expected value. + * + * @param number - the line number to initialize + */ + protected addExpectedLine(number: number): void + { + this.lineToExpectedLine.set(number, ""); + if (!this.lineToEqualLine.has(this.expectedLineNumber)) + this.lineToEqualLine.set(this.expectedLineNumber, true); + } + + /** + * Splits text into one or more lines. + * + * @param text - some text + * @param lineConsumer - consumes one line at a time + * @throws IllegalStateError if the writer has already been flushed + */ + public splitLines(text: string, lineConsumer: (line: string) => void): void + { + if (this.flushed) + throw new IllegalStateError("Writer has already been flushed"); + const lines = text.split(NEWLINE_PATTERN); + let line; + for (let i = 0; i < lines.length; ++i) + { + const isLastLine = i === lines.length - 1; + line = ""; + line += lines[i]; + if (!isLastLine) + line += NEWLINE_MARKER; + if (line.length !== 0) + lineConsumer(line); + if (!isLastLine) + { + this.writeActualNewline(); + this.writeExpectedNewline(); + } + } + } + + /** + * Ends the current line. + * + * @throws IllegalStateError if the writer has already been flushed + */ + protected writeActualNewline(): void + { + if (this.flushed) + throw new IllegalStateError("Writer has already been flushed"); + ++this.actualLineNumber; + this.addActualLine(this.actualLineNumber); + if (!this.lineToExpectedLine.get(this.actualLineNumber)) + this.addExpectedLine(this.actualLineNumber); + } + + /** + * Ends the current line. + * + * @throws IllegalStateError if the writer has already been flushed + */ + public writeExpectedNewline(): void + { + if (this.flushed) + throw new IllegalStateError("Writer has already been flushed"); + ++this.expectedLineNumber; + this.addExpectedLine(this.expectedLineNumber); + if (!this.lineToActualLine.get(this.expectedLineNumber)) + this.addActualLine(this.expectedLineNumber); + } + + public flush(): void + { + if (this.flushed) + return; + this.flushed = true; + this.beforeFlush(); + + for (const actualLine of sortByKeys(this.lineToActualLine).values()) + this.actualLines.push(actualLine); + Object.freeze(this.actualLines); + + for (const expectedLine of sortByKeys(this.lineToExpectedLine).values()) + this.expectedLines.push(expectedLine); + Object.freeze(this.expectedLines); + + for (const equalLine of sortByKeys(this.lineToEqualLine).values()) + this.equalLines.push(equalLine); + Object.freeze(this.equalLines); + + this.afterFlush(); + } + + public getActualLines(): string[] + { + if (!this.flushed) + throw new IllegalStateError("Writer must be flushed"); + return this.actualLines; + } + + public getExpectedLines(): string[] + { + if (!this.flushed) + throw new IllegalStateError("Writer must be flushed"); + return this.expectedLines; + } + + public getEqualLines(): boolean[] + { + if (!this.flushed) + throw new IllegalStateError("Writer must be flushed"); + return this.equalLines; + } + + public abstract getDiffLines(): string[]; + + public abstract writeEqual(text: string): void; + + public abstract writeDeleted(text: string): void; + + public abstract writeInserted(text: string): void; +} + +export {AbstractDiffWriter}; \ No newline at end of file diff --git a/src/internal/message/diff/ColoredDiff.mts b/src/internal/message/diff/ColoredDiff.mts new file mode 100644 index 0000000..61bb6ff --- /dev/null +++ b/src/internal/message/diff/ColoredDiff.mts @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +/** + * A terminal that supports ANSI color codes. + */ +interface ColoredDiff +{ + /** + * @param text - the text that did not change + * @returns the (possibly decorated) text + */ + decorateEqualText(text: string): string; + + /** + * @param text - the text that was deleted + * @returns the (possibly decorated) text + */ + decorateDeletedText(text: string): string; + + /** + * @param text - the text that was inserted + * @returns the (possibly decorated) text + */ + decorateInsertedText(text: string): string; + + /** + * @param text - the padding + * @returns the (possibly decorated) text + */ + decoratePadding(text: string): string; +} + +export type {ColoredDiff}; \ No newline at end of file diff --git a/src/internal/message/diff/ContextGenerator.mts b/src/internal/message/diff/ContextGenerator.mts new file mode 100644 index 0000000..77795d8 --- /dev/null +++ b/src/internal/message/diff/ContextGenerator.mts @@ -0,0 +1,443 @@ +import { + Configuration, + DiffGenerator, + DiffResult, + Type, + type ApplicationScope, + EOL_PATTERN, + assertThatStringIsNotEmpty, + assert, + type MessageSection, + ContextSection, + StringSection, + assertThatType, + isApplicationScope, + assertThatInstanceOf, + ValidationTarget, + AssertionError +} from "../../internal.mjs"; +import isEqual from "lodash.isequal"; + +/** + * Returns the difference between two values as an error context. + */ +class ContextGenerator +{ + private readonly scope: ApplicationScope; + private readonly configuration: Configuration; + private readonly diffGenerator: DiffGenerator; + /** + * The name of the actual value. + */ + private readonly _actualName; + /** + * The actual value. + */ + private _actualValue: ValidationTarget = ValidationTarget.invalid(); + /** + * The name of the expected value. + */ + private readonly _expectedName; + /** + * The expected value. + */ + private _expectedValue: ValidationTarget = ValidationTarget.invalid(); + /** + * `true` if error messages may include a diff that compares actual and expected values. + */ + private _allowDiff: boolean; + + /** + * Creates a ContextGenerator. + * + * @param scope - the application configuration + * @param configuration - the validator configuration + * @param actualName - the name of the actual value + * @param expectedName - the name of the expected value + * @throws AssertionError if: + *
    + *
  • any of the arguments is null
  • + *
  • `actualName` or `expectedName` are blank
  • + *
  • `actualName` or `expectedName` contains a colon
  • + *
+ */ + constructor(scope: ApplicationScope, configuration: Configuration, actualName: string, expectedName: string) + { + assertThatInstanceOf(configuration, "configuration", Configuration); + assertThatType(scope, "scope", Type.namedClass("ApplicationScope", () => isApplicationScope(scope))); + assertThatStringIsNotEmpty(actualName, "actualName"); + assert(!actualName.includes(":"), undefined, `actualName may not contain a colon. +actualName: ${actualName}`); + assertThatStringIsNotEmpty(expectedName, "expectedName"); + assert(!expectedName.includes(":"), undefined, `expectedName may not contain a colon. +expectedName: ${expectedName}`); + + this.scope = scope; + this.configuration = configuration; + this.diffGenerator = new DiffGenerator(scope.getGlobalConfiguration().terminalEncoding()); + this._allowDiff = configuration.allowDiff(); + this._actualName = actualName; + this._expectedName = expectedName; + } + + /** + * Sets the actual value. + * + * @param value - the object representation of the actual value + * @returns this + */ + public actualValue(value: unknown): ContextGenerator + { + this._actualValue = ValidationTarget.valid(value); + return this; + } + + /** + * Sets the expected value. + * + * @param value - the object representation of the expected value + * @returns this + */ + public expectedValue(value: unknown): ContextGenerator + { + this._expectedValue = ValidationTarget.valid(value); + return this; + } + + /** + * Overrides the value of {@link Configuration.allowDiff}. + * + * @param allowDiff - `true` if error messages may include a diff that compares actual and expected + * values + * @returns this + */ + public allowDiff(allowDiff: boolean): ContextGenerator + { + this._allowDiff = allowDiff; + return this; + } + + /** + * @returns the diff to append to the error message + */ + public build(): MessageSection[] + { + assert(this._actualValue.isValid() || this._expectedValue.isValid(), undefined, + "actualValue and expectedValue were both invalid"); + + if (this._actualValue.map(v => Array.isArray(v)).or(false) && + this._expectedValue.map(v => Array.isArray(v)).or(false)) + { + return this.getContextOfList(); + } + return this.getContextOfObjects(); + } + + /** + * @param actualName - the name of the actual value + * @param actualValue - the value of the actual value + * @param diff - the difference between the two values (empty if absent) + * @param expectedName - the name of the expected value + * @param expectedValue - the value of the expected value + * @returns the difference between the expected and actual values + */ + private getDiffSection(actualName: string, actualValue: string, diff: string, + expectedName: string, expectedValue: string): MessageSection + { + const value = new Map(); + value.set(actualName, actualValue); + if (diff.length !== 0) + value.set("diff", diff); + value.set(expectedName, expectedValue); + return new ContextSection(value); + } + + /** + * Generates a List-specific error context from the actual and expected values. + * + * @returns the difference between the expected and actual values + * @throws AssertionError if the actual or expected values do not exist + */ + private getContextOfList(): MessageSection[] + { + const actualAsArray = this._actualValue.orThrow( + () => new AssertionError("actualValue was invalid")) as unknown[]; + const expectedAsArray = this._expectedValue.orThrow( + () => new AssertionError("actualValue was invalid")) as unknown[]; + const actualSize = actualAsArray.length; + const expectedSize = expectedAsArray.length; + const maxSize = Math.max(actualSize, expectedSize); + + const components: MessageSection[] = []; + // Indicates if the previous index was equal + let skippedEqualElements = false; + let actualIndex = 0; + let expectedIndex = 0; + for (let i = 0; i < maxSize; ++i) + { + let elementsAreEqual = true; + const actualLineExists = i < actualSize; + + let actualNameLine; + let actualValueLine; + if (actualLineExists) + { + actualNameLine = `${this._actualName}[${actualIndex}]`; + actualValueLine = ValidationTarget.valid(actualAsArray[i]); + ++actualIndex; + } + else + { + actualNameLine = this._actualName; + actualValueLine = ValidationTarget.invalid(); + elementsAreEqual = false; + } + + const expectedLineExists = i < expectedSize; + let expectedNameLine; + let expectedValueLine; + if (expectedLineExists) + { + expectedNameLine = `${this._expectedName}[${expectedIndex}]`; + expectedValueLine = ValidationTarget.valid(expectedAsArray[i]); + ++expectedIndex; + } + else + { + expectedNameLine = this._expectedName; + expectedValueLine = ValidationTarget.invalid(); + elementsAreEqual = false; + } + + const elementGenerator = new ContextGenerator(this.scope, this.configuration, + actualNameLine, expectedNameLine); + actualValueLine.ifValid(value => elementGenerator.actualValue(value)); + expectedValueLine.ifValid(value => elementGenerator.expectedValue(value)); + + elementsAreEqual &&= isEqual(actualValueLine, expectedValueLine); + if (i !== 0 && i !== maxSize - 1 && elementsAreEqual) + { + // Skip identical elements, unless they are the first or last element. + skippedEqualElements = true; + continue; + } + if (skippedEqualElements) + { + skippedEqualElements = false; + components.push(ContextGenerator.skipEqualLines()); + } + if (components.length !== 0) + { + // Insert an empty line between each diff section + components.push(new StringSection("")); + } + components.push(...elementGenerator.build()); + } + return components; + } + + /** + * Returns context entries to indicate that duplicate lines were skipped. + * + * @returns the context entries to append + */ + private static skipEqualLines(): MessageSection + { + return new StringSection(` +[...]`); + } + + /** + * Generates an error context from the actual and expected values. + * + * @returns the difference between the expected and actual values + */ + private getContextOfObjects(): MessageSection[] + { + assert(this._actualValue.isValid() || this._expectedValue.isValid(), undefined, + "actualValue and expectedValue were both invalid"); + + const stringMappers = this.configuration.stringMappers(); + const actualAsString = this._actualValue.map(v => stringMappers.toString(v)).or(""); + const expectedAsString = this._expectedValue.map(v => stringMappers.toString(v)).or(""); + const lines = this.diffGenerator.diff(actualAsString, expectedAsString); + const diffLinesExist = lines.getDiffLines().length !== 0; + + // When comparing multiline strings, this method is invoked one line at a time. If the actual or expected + // value is invalid, it indicates that one of the values contains more lines than the other. The value + // with fewer lines will be considered invalid on a per-line basis. + const numberOfLines = lines.getActualLines().length; + // Don't diff boolean values + if (!this._allowDiff || numberOfLines == 1 || + this._actualValue.map(v => v instanceof Boolean).or(false) || + this._expectedValue.map(v => v instanceof Boolean).or(false)) + { + return this.getContextForSingleLine(lines); + } + + let actualLineNumber = 0; + let expectedLineNumber = 0; + const actualLines = lines.getActualLines(); + const expectedLines = lines.getExpectedLines(); + const equalLines = lines.getEqualLines(); + + // Indicates if the previous line was equal + let skippedEqualLines = false; + const context: MessageSection[] = []; + for (let i = 0; i < numberOfLines; ++i) + { + const valuesAreEqual = equalLines[i]; + if (i !== 0 && i !== numberOfLines - 1 && valuesAreEqual) + { + // Skip equal lines, unless they are the first or last line. + skippedEqualLines = true; + ++actualLineNumber; + ++expectedLineNumber; + continue; + } + + const actualValueLine = ContextGenerator.getElementOrEmptyString(actualLines, i); + let actualNameLine: string; + if (this.diffGenerator.isEmpty(actualValueLine)) + actualNameLine = this._actualName; + else + { + actualNameLine = `${this._actualName}@${actualLineNumber}`; + if (EOL_PATTERN.test(actualValueLine)) + ++actualLineNumber; + } + + let diffLine: string; + if (diffLinesExist && !valuesAreEqual) + diffLine = lines.getDiffLines()[i]; + else + diffLine = ""; + + const expectedValueLine = ContextGenerator.getElementOrEmptyString(expectedLines, i); + let expectedNameLine: string; + if (this.diffGenerator.isEmpty(expectedValueLine)) + expectedNameLine = this._expectedName; + else + { + expectedNameLine = `${this._expectedName}@${expectedLineNumber}`; + if (EOL_PATTERN.test(expectedValueLine)) + ++expectedLineNumber; + } + if (skippedEqualLines) + { + skippedEqualLines = false; + context.push(ContextGenerator.skipEqualLines()); + } + + if (context.length !== 0) + context.push(new StringSection("")); + const elementGenerator = new ContextGenerator(this.scope, this.configuration, + actualNameLine, expectedNameLine). + actualValue(actualValueLine). + expectedValue(expectedValueLine); + context.push(elementGenerator.getDiffSection(actualNameLine, actualValueLine, diffLine, + expectedNameLine, expectedValueLine)); + } + return context; + } + + /** + * @param list - a list + * @param i - an index + * @returns the element at the specified index, or `""` if the index is out of bounds + */ + private static getElementOrEmptyString(list: string[], i: number) + { + if (list.length > i) + return list[i]; + return ""; + } + + private getContextForSingleLine(lines: DiffResult): MessageSection[] + { + let actualAsString: string; + let expectedAsString: string; + if (lines.getActualLines().length > 1 || lines.getExpectedLines().length > 1) + { + const stringMappers = this.configuration.stringMappers(); + actualAsString = this._actualValue.map(v => stringMappers.toString(v)).or(""); + expectedAsString = this._expectedValue.map(v => stringMappers.toString(v)).or(""); + } + else + { + actualAsString = lines.getActualLines()[0]; + expectedAsString = lines.getExpectedLines()[0]; + } + + const diffLinesExist = lines.getDiffLines().length !== 0; + const valuesAreEqual = lines.getEqualLines()[0]; + + let diffLine: string; + if (diffLinesExist && !valuesAreEqual) + diffLine = lines.getDiffLines()[0]; + else + diffLine = ""; + + const context: MessageSection[] = []; + context.push(this.getDiffSection(this._actualName, actualAsString, diffLine, this._expectedName, + expectedAsString)); + + if (this._actualValue !== this._expectedValue && this.stringRepresentationsAreEqual(lines)) + { + // If the String representation of the values is equal, output getClass(), hashCode(), + // or System.identityHashCode() to figure out why they differ. + const optionalContext = this.compareTypes(); + if (optionalContext.length !== 0) + { + context.push(new StringSection("")); + context.push(...optionalContext); + } + } + return context; + } + + /** + * @param lines - the result of comparing the actual and expected values + * @returns `true` if the string representation of the values is equal + */ + private stringRepresentationsAreEqual(lines: DiffResult) + { + return lines.getEqualLines().every((value) => value); + } + + /** + * @returns the difference between the expected and actual values + * @throws TypeError if `actualName` or `expectedName` are `null` + */ + private compareTypes(): MessageSection[] + { + assert(this._actualValue.isValid() || this._expectedValue.isValid(), undefined, + "actualValue and expectedValue were both invalid"); + + const actualTypeName = Type.of(this._actualValue); + const expectedTypeName = Type.of(this._expectedValue); + if (!isEqual(actualTypeName, expectedTypeName)) + { + return new ContextGenerator(this.scope, this.configuration, `${this._actualName}.type`, + `${this._expectedName}.type`). + actualValue(actualTypeName). + expectedValue(expectedTypeName). + allowDiff(false). + build(); + } + return []; + } + + public toString(): string + { + const stringMappers = this.configuration.stringMappers(); + + let result = `actualName: ${this._actualName}`; + this._actualValue.ifValid(v => result += `, actualValue: ${stringMappers.toString(v)}`); + result += `, expectedName: ${this._expectedName}`; + this._expectedValue.ifValid(v => result += `, expectedValue: ${stringMappers.toString(v)}`); + return result; + } +} + +export {ContextGenerator}; \ No newline at end of file diff --git a/src/internal/message/diff/DiffConstants.mts b/src/internal/message/diff/DiffConstants.mts new file mode 100644 index 0000000..bbe21ec --- /dev/null +++ b/src/internal/message/diff/DiffConstants.mts @@ -0,0 +1,39 @@ +/** + * A string denoting the end of a line. + */ +const NEWLINE_MARKER = "\\n"; +/** + * Character denoting the end of string. + */ +const EOS_MARKER = "\\0"; +/** + * A pattern matching newline characters anywhere in a string. + */ +const NEWLINE_PATTERN = /\r?\n/; +/** + * A pattern matching the end of a line or stream. + */ +const EOL_PATTERN = /\\n|\\0$/; +/** + * Indicates a character is equal in the actual and expected values. + */ +const DIFF_EQUAL = " "; +/** + * Indicates a character to delete from the actual value. + */ +const DIFF_DELETE = "-"; +/** + * Indicates a character to insert into the actual value. + */ +const DIFF_INSERT = "+"; + +export +{ + NEWLINE_MARKER, + EOS_MARKER, + NEWLINE_PATTERN, + EOL_PATTERN, + DIFF_EQUAL, + DIFF_DELETE, + DIFF_INSERT +}; \ No newline at end of file diff --git a/src/internal/message/diff/DiffGenerator.mts b/src/internal/message/diff/DiffGenerator.mts new file mode 100644 index 0000000..d01e023 --- /dev/null +++ b/src/internal/message/diff/DiffGenerator.mts @@ -0,0 +1,541 @@ +/* + * Copyright (c) 2016 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +import type {Change} from "diff"; +import {diffChars} from "diff"; +import stripAnsi from "strip-ansi"; +import { + DiffResult, + Node16Colors, + Node16MillionColors, + Node256Colors, + TerminalEncoding, + TextOnly, + AbstractColorWriter, + AssertionError, + EOS_MARKER, + type DiffWriter, + assert, + internalValueToString, + containsOnly, + lastIndexOf, + requireThatValueIsNotNull +} from "../../internal.mjs"; + +/** + * Improve the readability of diff by avoiding many diffs per word or short diffs in a short word. + * + * ```stdout + * Good: + * -----=====----- + * -----+++++===== + * =====-----+++++ + * + * Bad: + * =====-----=====----- + * +++++=====+++++===== + * -----++++++----===== + * + * Good: + * football + * ----====++++ + * ballroom + * + * Bad: + * 123 + * -+= + * 133 + * ``` + *

+ * Bad deltas are replaced with a single `[DELETE actual, INSERT expected]` pair. + */ +class SimplifyDeltas +{ + // A "word" is defined as one or more characters that are surrounded by word delimiters. + // + // \p{Zs} matches any Unicode whitespace: https://www.regular-expressions.info/unicode.html + private static readonly WORD_DELIMITER = SimplifyDeltas.getWordDelimiter(); + + private static getWordDelimiter(): RegExp + { + const whitespace = "\\p{Zs}+"; + const newline = "\r\n|[\r\n]"; + const specialCharacters = "[\\[\\](){}/\\\\*+\\-#:;.]"; + return new RegExp(whitespace + "|" + + newline + "|" + + specialCharacters, "u"); + } + + /** + * The deltas to process. + *

    + *
  • A word may span one or more deltas.
  • + *
  • The first delta it appears in is called the "start delta".
  • + *
  • The last delta it appears in is called the "end delta".
  • + *
  • Any deltas in between are called the "middle deltas".
  • + *
  • If a word is fully contained within a single delta, its start and end deltas are the same, and + * it has no middle deltas.
  • + *
+ */ + private deltas: Change[] = []; + /** + * The index of the start delta in the list of all deltas. + */ + private indexOfStartDelta = 0; + /** + * The index of the end delta in the list of all deltas. + */ + private indexOfEndDelta = 0; + /** + * The index of the word in the start delta. + */ + private startOfWord = 0; + /** + * The index right after the last character of the word in the end delta. + */ + private endOfWord = 0; + /** + * The index of the next word in the end delta. If there are no more words, points to the end of the + * string. + */ + private startOfNextWord = 0; + + /** + * @param deltas - the deltas to update + */ + accept(deltas: Change[]) + { + this.deltas = deltas; + // We are looking for words that span multiple deltas. If the first delta contains multiple + // words, we are interested in the last one. + this.findFirstWord(); + if (this.indexOfStartDelta === this.deltas.length) + return; + do + { + this.findEndOfWord(); + this.updateDeltas(); + } while (this.findNextWord()); + } + + /** + * Finds the first word. + */ + findFirstWord() + { + // Words start after a whitespace delimiter within an EQUAL delta. If none is found, the start + // of the first delta acts as a word boundary. + const delta = this.deltas[0]; + this.indexOfStartDelta = 0; + const match = lastIndexOf(delta.value, SimplifyDeltas.WORD_DELIMITER); + if (match === null) + this.startOfWord = 0; + else + this.startOfWord = match.end; + } + + /** + * Finds the end of the word. + */ + findEndOfWord() + { + // Words end at a whitespace delimiter found within an EQUAL delta. If none is found, the end of the + // last delta acts as a word boundary. + for (let i = this.indexOfStartDelta + 1; i < this.deltas.length; ++i) + { + const delta = this.deltas[i]; + const isEqual = !delta.removed && !delta.added; + if (isEqual) + { + const match = SimplifyDeltas.WORD_DELIMITER.exec(delta.value); + if (match) + { + this.endOfWord = match.index; + this.startOfNextWord = match.index + match[0].length; + this.indexOfEndDelta = i; + return; + } + } + } + this.indexOfEndDelta = this.deltas.length - 1; + } + + /** + * Update the deltas if necessary. + */ + updateDeltas() + { + assert(this.deltas.length !== 0, undefined, JSON.stringify(this.deltas, null, 2)); + const deltasInWord = this.deltas.slice(this.indexOfStartDelta, this.indexOfEndDelta + 1); + if (deltasInWord.length < 2) + return; + if (this.numberOfUnequalDeltas(deltasInWord) <= 2 && + (this.shortestDelta(deltasInWord) >= 3 || this.longestWord(deltasInWord) >= 5)) + { + // Diff is already good + return; + } + // Otherwise, replace the deltas with a single [DELETE, INSERT] pair + const updatedDeltas: Change[] = []; + let actualBuilder = ""; + let expectedBuilder = ""; + [actualBuilder, expectedBuilder] = this.processStartDelta(actualBuilder, expectedBuilder, updatedDeltas); + [actualBuilder, expectedBuilder] = this.processMiddleDeltas(actualBuilder, expectedBuilder); + this.processEndDelta(actualBuilder, expectedBuilder, updatedDeltas); + + const deltasRemoved = deltasInWord.length - updatedDeltas.length; + // Remove deltasInWord and insert updatedDeltas in its place: + // https://stackoverflow.com/a/17511398/14731 + this.deltas.splice(this.indexOfStartDelta, deltasInWord.length, ...updatedDeltas); + this.indexOfEndDelta -= deltasRemoved; + this.startOfNextWord -= this.endOfWord; + } + + /** + * @param deltas - a list of deltas + * @returns the number of deltas whose type is not EQUAL + */ + numberOfUnequalDeltas(deltas: Change[]): number + { + let result = 0; + for (const delta of deltas) + { + if (delta.removed || delta.added) + ++result; + } + return result; + } + + /** + * Processes the start delta. + * + * @param actualBuilder - a buffer to insert the actual value of the word into + * @param expectedBuilder - a buffer to insert the expected value of the word into + * @param updatedDeltas - a list to insert updated deltas into + * @returns the updated values of `actualBuilder` and `expectedBuilder` + */ + processStartDelta(actualBuilder: string, expectedBuilder: string, updatedDeltas: Change[]): + [actualBuilder: string, expectedBuilder: string] + { + const delta = this.deltas[this.indexOfStartDelta]; + let actualWord; + let expectedWord; + let beforeWord; + + if (delta.added) + { + actualWord = ""; + expectedWord = delta.value; + beforeWord = ""; + } + else if (delta.removed) + { + const actual = delta.value; + actualWord = actual.substring(this.startOfWord); + expectedWord = ""; + beforeWord = actual.substring(0, this.startOfWord); + } + else + { + const actual = delta.value; + actualWord = actual.substring(this.startOfWord); + expectedWord = actualWord; + beforeWord = actual.substring(0, this.startOfWord); + } + + actualBuilder += actualWord; + expectedBuilder += expectedWord; + + if (this.startOfWord > 0) + { + updatedDeltas.push( + { + added: delta.added, + removed: delta.removed, + value: beforeWord + }); + } + return [actualBuilder, expectedBuilder]; + } + + /** + * Processes the middle deltas. + * @param actualBuilder - a buffer to insert the actual value of the word into + * @param expectedBuilder - a buffer to insert the expected value of the word into + * @returns the updated values of `actualBuilder` and `expectedBuilder` + */ + processMiddleDeltas(actualBuilder: string, expectedBuilder: string, ) + { + for (let i = this.indexOfStartDelta + 1; i < this.indexOfEndDelta; ++i) + { + const delta = this.deltas[i]; + if (!delta.added) + { + // Deleted or equal + actualBuilder += delta.value; + } + if (!delta.removed) + { + // Inserted or equal + expectedBuilder += delta.value; + } + } + return [actualBuilder, expectedBuilder]; + } + + /** + * Processes the end delta. + * + * @param actualBuilder - a buffer to insert the actual value of the word into + * @param expectedBuilder - a buffer to insert the expected value of the word into + * @param updatedDeltas - a list to insert updated deltas into + */ + processEndDelta(actualBuilder: string, expectedBuilder: string, updatedDeltas: Change[]) + { + const delta = this.deltas[this.indexOfEndDelta]; + + // Extract the first word in the delta + let actualWord: string; + let expectedWord: string; + if (delta.added) + { + actualWord = delta.value.substring(0, this.endOfWord); + expectedWord = ""; + } + else if (delta.removed) + { + actualWord = ""; + expectedWord = delta.value.substring(0, this.endOfWord); + } + else + { + // Equal + actualWord = expectedWord = delta.value.substring(0, this.endOfWord); + } + actualBuilder += actualWord; + expectedBuilder += expectedWord; + + const deleteActual: Change = { + value: actualBuilder, + added: false, + removed: true + }; + const insertExpected: Change = { + value: expectedBuilder, + added: true, + removed: false + }; + updatedDeltas.push(deleteActual); + updatedDeltas.push(insertExpected); + + // Add the remaining part of the delta + if (this.endOfWord < delta.value.length) + { + updatedDeltas.push( + { + added: delta.added, + removed: delta.removed, + value: delta.value.substring(this.endOfWord) + }); + } + } + + /** + * Finds the next word. + * + * @returns `false` if there are no more words to be found + */ + findNextWord(): boolean + { + this.indexOfStartDelta = this.indexOfEndDelta; + if (this.indexOfStartDelta === this.deltas.length - 1) + return false; + + // Similar logic as findFirstWord() + const delta = this.deltas[this.indexOfStartDelta]; + if (!delta.added && !delta.removed) + { + // Equal + const result = lastIndexOf(delta.value, SimplifyDeltas.WORD_DELIMITER); + if (result === null) + { + throw new Error("Expecting result to be equal to indexOfNextWordInEndDelta (" + + this.startOfNextWord + ") or later.\n" + + "delta.value: " + delta.value); + } + this.startOfWord = result.end; + } + return true; + } + + /** + * @param deltas - a list of deltas + * @returns the length of the shortest delta + */ + private shortestDelta(deltas: Change[]): number + { + assert(this.deltas.length !== 0, undefined, JSON.stringify(this.deltas, null, 2)); + let result = Number.MAX_VALUE; + for (const delta of deltas) + result = Math.min(result, delta.value.length); + return result; + } + + /** + * @param deltas - a list of deltas + * @returns the length of the longest word (source or target) spanned by the deltas + */ + private longestWord(deltas: Change[]): number + { + let lengthOfSource = 0; + let lengthOfTarget = 0; + for (const delta of deltas) + { + const length = delta.value.length; + if (delta.added) + lengthOfTarget += length; + else if (delta.removed) + lengthOfSource += length; + else + { + lengthOfSource += length; + lengthOfTarget += length; + } + } + let result = Math.max(lengthOfSource, lengthOfTarget); + // Trim text before the first delta and after the last delta + result -= this.startOfWord; + const lastDelta = deltas[deltas.length - 1]; + const actual = lastDelta.value; + result -= actual.length - this.startOfNextWord; + return Math.max(0, result); + } +} + +/** + * Generates a diff of two Strings. + */ +class DiffGenerator +{ + private readonly encoding: TerminalEncoding; + private readonly paddingMarker: string; + private readonly simplifyDeltas = new SimplifyDeltas(); + + /** + * @param encoding - the terminal encoding + * @throws AssertionError if `encoding` is `undefined` or `null` + */ + constructor(encoding: TerminalEncoding) + { + requireThatValueIsNotNull(encoding, "encoding"); + + this.encoding = encoding; + this.paddingMarker = this.getPaddingMarker(); + } + + /** + * @returns the padding character used to align values vertically + */ + private getPaddingMarker() + { + switch (this.encoding) + { + case TerminalEncoding.NONE: + return TextOnly.DIFF_PADDING; + case TerminalEncoding.NODE_16_COLORS: + case TerminalEncoding.NODE_256_COLORS: + case TerminalEncoding.NODE_16MILLION_COLORS: + return AbstractColorWriter.DIFF_PADDING; + default: + throw new AssertionError(internalValueToString(this.encoding)); + } + } + + /** + * Generates the diff of two strings. + *

+ * NOTE: Colors may be disabled when stdin or stdout are redirected. To override this + * behavior, use {@link GlobalConfiguration.terminalEncoding}. + * + * @param actual - the actual value + * @param expected - the expected value + * @returns the calculated diff + */ + diff(actual: string, expected: string) + { + // Mark the end of the string to guard against cases that end with whitespace + const actualWithEos = actual + EOS_MARKER; + const expectedWithEos = expected + EOS_MARKER; + const writer = this.createDiffWriter(); + // diffChars() returns a list of deltas, where each delta is associated with a list of characters. + const deltas = diffChars(actualWithEos, expectedWithEos); + this.simplifyDeltas.accept(deltas); + for (const delta of deltas) + this.writeDelta(delta, writer); + writer.flush(); + return new DiffResult(writer.getActualLines(), writer.getDiffLines(), writer.getExpectedLines(), + writer.getEqualLines()); + } + + /** + * Write a single delta. + * + * @param delta - a delta + * @param writer - the writer to write into + */ + private writeDelta(delta: Change, writer: DiffWriter) + { + if (delta.added) + writer.writeInserted(delta.value); + else if (delta.removed) + writer.writeDeleted(delta.value); + else + writer.writeEqual(delta.value); + } + + /** + * @returns a new writer + */ + private createDiffWriter() + { + switch (this.encoding) + { + case TerminalEncoding.NONE: + return new TextOnly(); + case TerminalEncoding.NODE_16_COLORS: + return new Node16Colors(); + case TerminalEncoding.NODE_256_COLORS: + return new Node256Colors(); + case TerminalEncoding.NODE_16MILLION_COLORS: + return new Node16MillionColors(); + default: + throw new AssertionError(internalValueToString(this.encoding)); + } + } + + /** + * @param line - a line + * @returns true if `line` is empty once all colors and padding characters are removed + */ + public isEmpty(line: string) + { + switch (this.encoding) + { + case TerminalEncoding.NONE: + break; + case TerminalEncoding.NODE_16_COLORS: + case TerminalEncoding.NODE_256_COLORS: + case TerminalEncoding.NODE_16MILLION_COLORS: + { + line = stripAnsi(line); + break; + } + default: + throw new AssertionError(internalValueToString(this.encoding)); + } + return containsOnly(line, this.paddingMarker); + } +} + +export {DiffGenerator}; \ No newline at end of file diff --git a/src/internal/message/diff/DiffResult.mts b/src/internal/message/diff/DiffResult.mts new file mode 100644 index 0000000..f905188 --- /dev/null +++ b/src/internal/message/diff/DiffResult.mts @@ -0,0 +1,81 @@ +import { + Type, + assertThatType +} from "../../internal.mjs"; + +/** + * The result of calculating the difference between two strings. + */ +class DiffResult +{ + private readonly actualLines: string[]; + private readonly diffLines: string[]; + private readonly expectedLines: string[]; + private readonly equalLines: boolean[]; + + /** + * @param actualLines - the lines of the actual string + * @param diffLines - the difference between the actual and expected values (empty list if omitted) + * @param expectedLines - the lines of the expected string + * @param equalLines - indicates if the actual and expected values are equal for each line + * @throws TypeError if any of the arguments are `null` + */ + constructor(actualLines: string[], diffLines: string[], expectedLines: string[], equalLines: boolean[]) + { + assertThatType(actualLines, "actualLines", Type.ARRAY); + assertThatType(diffLines, "diffLines", Type.ARRAY); + assertThatType(expectedLines, "expectedLines", Type.ARRAY); + assertThatType(equalLines, "equalLines", Type.ARRAY); + + this.actualLines = actualLines; + this.diffLines = diffLines; + this.expectedLines = expectedLines; + this.equalLines = equalLines; + } + + /** + * @returns the lines of the actual string + */ + getActualLines(): string[] + { + return this.actualLines; + } + + /** + * @returns the difference between "Actual" and "Expected". If the list is empty, no lines should be + * displayed. + */ + getDiffLines(): string[] + { + return this.diffLines; + } + + /** + * Returns the lines of the expected string. + * + * @returns the lines of the expected string + */ + getExpectedLines(): string[] + { + return this.expectedLines; + } + + /** + * @returns a list that indicates whether the actual and expected values are equal on each line + */ + public getEqualLines(): boolean[] + { + return this.equalLines; + } + + public toString(): string + { + return `\ +actual : ${this.actualLines.toString()} +diff : ${this.diffLines.toString()} +expected: ${this.expectedLines.toString()} +equal : ${this.equalLines.toString()}`; + } +} + +export {DiffResult}; \ No newline at end of file diff --git a/src/internal/message/diff/DiffWriter.mts b/src/internal/message/diff/DiffWriter.mts new file mode 100644 index 0000000..1bf11e6 --- /dev/null +++ b/src/internal/message/diff/DiffWriter.mts @@ -0,0 +1,65 @@ +/** + * Generates the String representation of a diff between `actual` and `expected` values. + */ +interface DiffWriter +{ + /** + * Adds text that is equal in `expected` and `actual`. + * + * @param text - the text to keep in `actual` + * @throws RangeError if the writer was already flushed + */ + writeEqual(text: string): void; + + /** + * Deletes text that is present in `actual` but not `expected`. + * + * @param text - the text that needs to be deleted from `actual` + * @throws RangeError if the writer was already flushed + */ + writeDeleted(text: string): void; + + /** + * Adds text that is present in `expected` but not `actual`. + * + * @param text - the text that needs to be inserted into `actual` + * @throws RangeError if the writer was already flushed + */ + writeInserted(text: string): void; + + /** + * @returns the lines of the actual value + * @throws RangeError if the writer was already flushed + */ + getActualLines(): string[]; + + /** + * @returns the lines to display after "actual" and before "expected" (empty lines should not be displayed) + * @throws RangeError if the writer was already flushed + */ + getDiffLines(): string[]; + + /** + * @returns the lines of the expected value + * @throws RangeError if the writer was already flushed + */ + getExpectedLines(): string[]; + + /** + * @returns an array that indicates whether the actual and expected values are equal on each line + * @throws RangeError if the writer was already flushed + */ + getEqualLines(): boolean[]; + + /** + * @returns a padding character used to align values vertically + */ + getPaddingMarker(): string; + + /** + * Flushes the writer's output. + */ + flush(): void; +} + +export type {DiffWriter}; \ No newline at end of file diff --git a/src/internal/message/diff/Node16Colors.mts b/src/internal/message/diff/Node16Colors.mts new file mode 100644 index 0000000..3ec0127 --- /dev/null +++ b/src/internal/message/diff/Node16Colors.mts @@ -0,0 +1,39 @@ +import chalk from "chalk"; +import {AbstractColorWriter} from "../../internal.mjs"; + +/** + * A node terminal that supports 16 colors. + */ +class Node16Colors extends AbstractColorWriter +{ + public constructor() + { + super(); + } + + public decorateInsertedText(text: string): string + { + return chalk.bgGreen(chalk.whiteBright(text)); + } + + public decorateDeletedText(text: string): string + { + return chalk.bgRed(chalk.whiteBright(text)); + } + + protected afterFlush() + { + } + + public decorateEqualText(text: string): string + { + return text; + } + + public decoratePadding(text: string): string + { + return text; + } +} + +export {Node16Colors}; \ No newline at end of file diff --git a/src/internal/diff/Node16MillionColors.mts b/src/internal/message/diff/Node16MillionColors.mts similarity index 59% rename from src/internal/diff/Node16MillionColors.mts rename to src/internal/message/diff/Node16MillionColors.mts index 9d67316..10c0fb9 100644 --- a/src/internal/diff/Node16MillionColors.mts +++ b/src/internal/message/diff/Node16MillionColors.mts @@ -1,5 +1,5 @@ import chalk from "chalk"; -import {AbstractColorWriter} from "../internal.mjs"; +import {AbstractColorWriter} from "../../internal.mjs"; const greenBackground = chalk.bgRgb(0, 135, 0); const redBackground = chalk.bgRgb(175, 0, 0); @@ -11,20 +11,34 @@ const redBackground = chalk.bgRgb(175, 0, 0); */ class Node16MillionColors extends AbstractColorWriter { - constructor() + public constructor() { super(); } - decorateInsertedText(text: string): string + protected afterFlush() + { + } + + public decorateInsertedText(text: string): string { return greenBackground(chalk.whiteBright(text)); } - decorateDeletedText(text: string): string + public decorateDeletedText(text: string): string { return redBackground(chalk.whiteBright(text)); } + + public decorateEqualText(text: string): string + { + return text; + } + + public decoratePadding(text: string): string + { + return text; + } } export {Node16MillionColors}; \ No newline at end of file diff --git a/src/internal/diff/Node256Colors.mts b/src/internal/message/diff/Node256Colors.mts similarity index 60% rename from src/internal/diff/Node256Colors.mts rename to src/internal/message/diff/Node256Colors.mts index c530c82..add9882 100644 --- a/src/internal/diff/Node256Colors.mts +++ b/src/internal/message/diff/Node256Colors.mts @@ -1,14 +1,10 @@ -import {Node16MillionColors} from "../internal.mjs"; +import {Node16MillionColors} from "../../internal.mjs"; /** * A node terminal that supports 256 colors. */ class Node256Colors extends Node16MillionColors { - constructor() - { - super(); - } } export {Node256Colors}; \ No newline at end of file diff --git a/src/internal/message/diff/TextOnly.mts b/src/internal/message/diff/TextOnly.mts new file mode 100644 index 0000000..aa26298 --- /dev/null +++ b/src/internal/message/diff/TextOnly.mts @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2016 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +import { + AbstractDiffWriter, + IllegalStateError, + DIFF_EQUAL, + DIFF_DELETE, + DIFF_INSERT, + appendToValue, + sortByKeys +} from "../../internal.mjs"; + +/** + * A diff writer that does not use ANSI escape codes. + */ +class TextOnly extends AbstractDiffWriter +{ + /** + * A padding character used to align values vertically. + */ + static readonly DIFF_PADDING = " "; + private lineToDiffLine: Map = new Map(); + private diffLines: string[] = []; + + constructor() + { + super(TextOnly.DIFF_PADDING); + this.addActualLine(0); + this.addExpectedLine(0); + } + + addActualLine(number: number) + { + super.addActualLine(number); + this.addDiffLine(number); + } + + addExpectedLine(number: number) + { + super.addExpectedLine(number); + this.addDiffLine(number); + } + + private addDiffLine(number: number) + { + if (this.lineToDiffLine.get(number) === undefined) + this.lineToDiffLine.set(number, ""); + } + + public writeEqual(text: string) + { + if (this.flushed) + throw new IllegalStateError("Writer was already flushed"); + if (text.length === 0) + return; + this.splitLines(text, (line: string) => + { + appendToValue(this.lineToActualLine, this.actualLineNumber, line); + + const length = line.length; + if (this.expectedLineNumber === this.actualLineNumber) + appendToValue(this.lineToDiffLine, this.actualLineNumber, DIFF_EQUAL.repeat(length)); + else + { + const paddingMarker = this.getPaddingMarker(); + appendToValue(this.lineToExpectedLine, this.actualLineNumber, paddingMarker.repeat(length)); + appendToValue(this.lineToDiffLine, this.actualLineNumber, DIFF_EQUAL.repeat(length)); + + appendToValue(this.lineToActualLine, this.expectedLineNumber, paddingMarker.repeat(length)); + appendToValue(this.lineToDiffLine, this.expectedLineNumber, DIFF_EQUAL.repeat(length)); + } + appendToValue(this.lineToExpectedLine, this.expectedLineNumber, line); + }); + } + + public writeDeleted(text: string) + { + if (this.flushed) + throw new IllegalStateError("Writer was already flushed"); + if (text.length === 0) + return; + this.splitLines(text, (line: string) => + { + appendToValue(this.lineToActualLine, this.actualLineNumber, line); + const length = line.length; + appendToValue(this.lineToDiffLine, this.actualLineNumber, DIFF_DELETE.repeat(length)); + appendToValue(this.lineToExpectedLine, this.actualLineNumber, + this.getPaddingMarker().repeat(length)); + this.lineToEqualLine.set(this.actualLineNumber, false); + }); + } + + public writeInserted(text: string) + { + if (this.flushed) + throw new IllegalStateError("Writer was already flushed"); + if (text.length === 0) + return; + this.splitLines(text, (line: string) => + { + const length = line.length; + appendToValue(this.lineToActualLine, this.expectedLineNumber, + this.getPaddingMarker().repeat(length)); + appendToValue(this.lineToDiffLine, this.expectedLineNumber, DIFF_INSERT.repeat(length)); + appendToValue(this.lineToExpectedLine, this.expectedLineNumber, line); + this.lineToEqualLine.set(this.expectedLineNumber, false); + }); + } + + protected beforeFlush() + { + } + + public afterFlush() + { + for (const diffLine of sortByKeys(this.lineToDiffLine).values()) + this.diffLines.push(diffLine); + Object.freeze(this.diffLines); + } + + public getDiffLines() + { + if (!this.flushed) + throw new RangeError("Writer must be flushed"); + return this.diffLines; + } +} + +export {TextOnly}; \ No newline at end of file diff --git a/src/internal/message/section/ContextSection.mts b/src/internal/message/section/ContextSection.mts new file mode 100644 index 0000000..292b06c --- /dev/null +++ b/src/internal/message/section/ContextSection.mts @@ -0,0 +1,35 @@ +import type {MessageSection} from "./MessageSection.mjs"; +import {requireThatValueIsNotNull} from "../../internal.mjs"; + +/** + * A section of key-pair pairs that contain contextual information related to a validation failure. + */ +class ContextSection implements MessageSection +{ + public readonly value: Map; + + constructor(value: Map) + { + requireThatValueIsNotNull(value, "value"); + this.value = value; + } + + getMaxKeyLength(): number + { + let maxKeyLength = 0; + for (const key of this.value.keys()) + maxKeyLength = Math.max(maxKeyLength, key.length); + return maxKeyLength; + } + + getLines(maxKeyLength: number): string[] + { + // Align the colons vertically + const lines = []; + for (const [key, value] of this.value.entries()) + lines.push(key.padEnd(maxKeyLength) + ": " + value); + return lines; + } +} + +export {ContextSection}; \ No newline at end of file diff --git a/src/internal/message/section/MessageBuilder.mts b/src/internal/message/section/MessageBuilder.mts new file mode 100644 index 0000000..47ec56b --- /dev/null +++ b/src/internal/message/section/MessageBuilder.mts @@ -0,0 +1,188 @@ +import { + ContextGenerator, + AbstractValidator, + assert, + StringSection, + ContextSection, + type MessageSection, + requireThatStringIsNotEmpty, + AssertionError, + requireThatValueIsNotNull, + assertThatValueIsNotNull +} from "../../internal.mjs"; + +/** + * Builds an error message. + */ +class MessageBuilder +{ + public static readonly DIFF_LEGEND = `\ + +Legend +------ ++ : Add this character to the value +- : Remove this character from the value +[index] : Refers to the index of a collection element +@line-number: Refers to the line number of a multiline string +`; + private readonly validator: AbstractValidator; + private readonly message: string; + private readonly failureContext = new Map(); + /** + * A string that describes the difference between the expected and actual values. + */ + private readonly diff: MessageSection[] = []; + + /** + * @param validator - the validator + * @param message - (optional) the error message (empty string when absent) + * @throws AssertionError if: + *

    + *
  • any of the arguments are null
  • + *
  • `message` is blank or does not end with a dot
  • + *
+ */ + public constructor(validator: AbstractValidator, message: string) + { + assertThatValueIsNotNull(validator, "validator"); + if (!message.endsWith(".")) + throw new AssertionError(`Message must end with a dot: ${message}`); + this.validator = validator; + this.message = message; + } + + /** + * Appends context to the error message. If the context previously contained a mapping for the name, the + * old value is replaced. + * + * @param value - the value of the context + * @param name - (optional) the name of the context (empty string if absent) + * @returns this + * @throws AssertionError if `name`: + *
    + *
  • is `undefined` or `null`
  • + *
  • is empty
  • + *
  • contains whitespace or a colon
  • + *
+ */ + public withContext(value: unknown, name: string) + { + requireThatValueIsNotNull(name, "name"); + this.failureContext.set(name, value); + return this; + } + + /** + * Adds a DIFF to the context that compares the value to an expected value + * + * @param actualName - the name of the value + * @param actualValue - the object representation of the value + * @param expectedName - the name of the expected value + * @param expectedValue - the object representation of the expected value + * @returns this + */ + public addDiff(actualName: string, actualValue: unknown, expectedName: string, expectedValue: unknown) + { + const contextGenerator = new ContextGenerator(this.validator.getScope(), this.validator.configuration(), + actualName, expectedName). + actualValue(actualValue). + expectedValue(expectedValue); + this.diff.push(...contextGenerator.build()); + this.diff.push(new StringSection(MessageBuilder.DIFF_LEGEND)); + return this; + } + + /** + * @returns the contextual information associated with a validation failure + */ + private getValidatorContext() + { + const mergedContext = new Map(this.failureContext); + for (const entry of this.validator.getContext()) + mergedContext.set(entry[0], entry[1]); + + const stringMappers = this.validator.configuration().stringMappers(); + const contextAsString = new Map(); + for (const [key, value] of mergedContext.entries()) + contextAsString.set(key, stringMappers.toString(value)); + return new ContextSection(contextAsString); + } + + /** + * Quotes the name of a parameter, unless it references a method call. + * + * @param name - the name of a parameter + * @returns the updated name + */ + public static quoteName(name: string): string + { + if (name.includes(".")) + return name; + return "\"" + name + "\""; + } + + public toString() + { + const context: MessageSection[] = []; + this.addValidatorContextToContext(context); + this.addDiffToContext(context); + this.addErrorMessageToContext(context); + return this.contextToString(context); + } + + private addDiffToContext(context: MessageSection[]) + { + if (this.diff.length === 0) + return; + if (context.length !== 0 || this.message.length !== 0) + { + // Add an extra newline in front of the diff + context.push(new StringSection("")); + } + context.push(...this.diff); + } + + private addValidatorContextToContext(context: MessageSection[]) + { + const validatorContext = this.getValidatorContext(); + if (validatorContext.value.size !== 0) + context.push(validatorContext); + } + + private contextToString(context: MessageSection[]) + { + let maxKeyLength = 0; + for (const section of context) + maxKeyLength = Math.max(maxKeyLength, section.getMaxKeyLength()); + + let result = ""; + const lines: string[] = []; + for (const section of context) + lines.push(...section.getLines(maxKeyLength)); + result += lines.join("\n"); + return result.toString(); + } + + private addErrorMessageToContext(context: MessageSection[]) + { + if (this.message === null) + return; + let updatedMessage; + if (context.length === 0 && !this.message.includes("\n")) + { + requireThatStringIsNotEmpty(this.message, "message"); + assert(this.message.endsWith("."), undefined, this.message); + + // Strip the period from the end of single-line messages, unless it contains a comma. + if (this.message.includes(",")) + updatedMessage = this.message; + else + updatedMessage = this.message.substring(0, this.message.length - 1); + } + else + updatedMessage = this.message; + context.unshift(new StringSection(updatedMessage)); + } +} + +export {MessageBuilder}; \ No newline at end of file diff --git a/src/internal/message/section/MessageSection.mts b/src/internal/message/section/MessageSection.mts new file mode 100644 index 0000000..92afd12 --- /dev/null +++ b/src/internal/message/section/MessageSection.mts @@ -0,0 +1,19 @@ +/** + * A section of a text that contains contextual information related to a validation failure. + */ +interface MessageSection +{ + /** + * @returns if the section contains key-value pairs, returns the maximum length of all keys; otherwise + * returns 0 + */ + getMaxKeyLength(): number; + + /** + * @param maxKeyLength - the maximum key length across all sections + * @returns an array of this section's lines + */ + getLines(maxKeyLength: number): string[]; +} + +export type {MessageSection}; \ No newline at end of file diff --git a/src/internal/message/section/StringSection.mts b/src/internal/message/section/StringSection.mts new file mode 100644 index 0000000..16e8382 --- /dev/null +++ b/src/internal/message/section/StringSection.mts @@ -0,0 +1,28 @@ +import type {MessageSection} from "./MessageSection.mjs"; +import {assertThatValueIsNotNull} from "../../validator/Objects.mjs"; + +/** + * A string that is added to the error context. + */ +class StringSection implements MessageSection +{ + public readonly value: string; + + constructor(value: string) + { + assertThatValueIsNotNull(value, "value"); + this.value = value; + } + + getMaxKeyLength(): number + { + return 0; + } + + getLines(): string[] + { + return [this.value]; + } +} + +export {StringSection}; \ No newline at end of file diff --git a/src/internal/scope/AbstractApplicationScope.mts b/src/internal/scope/AbstractApplicationScope.mts new file mode 100644 index 0000000..dbb31ee --- /dev/null +++ b/src/internal/scope/AbstractApplicationScope.mts @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +/* + * Copyright (c) 2016 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +import { + type GlobalConfiguration, + type ProcessScope, + type ApplicationScope, + assertThatValueIsNotNull +} from "../internal.mjs"; + +/** + * ApplicationScope for the main and test codebases. + */ +abstract class AbstractApplicationScope implements ApplicationScope +{ + public abstract close(): void; + + private readonly parent: ProcessScope; + /** + * The global configuration. + */ + private readonly globalConfiguration: GlobalConfiguration; + + /** + * Creates a new instance. + * + * @param parent - the parent scope + * @param globalConfiguration - the global configuration + * @throws TypeError if any of the arguments are `null` + */ + protected constructor(parent: ProcessScope, globalConfiguration: GlobalConfiguration) + { + assertThatValueIsNotNull(parent, "parent"); + assertThatValueIsNotNull(globalConfiguration, "globalConfiguration"); + this.parent = parent; + this.globalConfiguration = globalConfiguration; + } + + public getGlobalConfiguration() + { + return this.globalConfiguration; + } + + public getTerminal() + { + return this.parent.getTerminal(); + } +} + +export {AbstractApplicationScope}; \ No newline at end of file diff --git a/src/internal/scope/ApplicationScope.mts b/src/internal/scope/ApplicationScope.mts new file mode 100644 index 0000000..498c6f0 --- /dev/null +++ b/src/internal/scope/ApplicationScope.mts @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +import { + type GlobalConfiguration, + type ProcessScope +} from "../../internal/internal.mjs"; + +/** + * The configuration of an application. A process may contain multiple applications. + */ +interface ApplicationScope extends ProcessScope +{ + /** + * @returns the global configuration inherited by all validators + */ + getGlobalConfiguration(): GlobalConfiguration; +} + +/** + * @param value - a value + * @returns true if the value is an instance of `ApplicationScope` + */ +function isApplicationScope(value: unknown): value is ApplicationScope +{ + return (value as ApplicationScope).getGlobalConfiguration !== undefined; +} + +export { + type ApplicationScope, + isApplicationScope +}; \ No newline at end of file diff --git a/src/internal/scope/DefaultProcessScope.mts b/src/internal/scope/DefaultProcessScope.mts new file mode 100644 index 0000000..91fff38 --- /dev/null +++ b/src/internal/scope/DefaultProcessScope.mts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2016 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ + +import { + Terminal, + type ProcessScope +} from "../internal.mjs"; + +/** + * The default implementation of ProcessScope. + */ +class DefaultProcessScope implements ProcessScope +{ + /** + * The singleton instance. + */ + public static readonly INSTANCE = new DefaultProcessScope(); + private readonly terminal = new Terminal(); + + private constructor() + { + } + + public getTerminal() + { + return this.terminal; + } + + public close() + { + } +} + +export {DefaultProcessScope}; \ No newline at end of file diff --git a/src/internal/scope/MainApplicationScope.mts b/src/internal/scope/MainApplicationScope.mts new file mode 100644 index 0000000..c01d69e --- /dev/null +++ b/src/internal/scope/MainApplicationScope.mts @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +import { + MainGlobalConfiguration, + type ProcessScope, + DefaultProcessScope, + AbstractApplicationScope +} from "../internal.mjs"; + +/** + * ApplicationScope for the main codebase. + */ +class MainApplicationScope extends AbstractApplicationScope +{ + /** + * The singleton instance. + */ + public static readonly INSTANCE = new MainApplicationScope(DefaultProcessScope.INSTANCE); + + /** + * Creates a new application scope. + * + * @param parent - the parent scope + * @throws TypeError if `parent` is null + */ + private constructor(parent: ProcessScope) + { + super(parent, new MainGlobalConfiguration(parent.getTerminal())); + } + + public close() + { + } +} + +export {MainApplicationScope}; \ No newline at end of file diff --git a/src/internal/scope/MainGlobalConfiguration.mts b/src/internal/scope/MainGlobalConfiguration.mts new file mode 100644 index 0000000..6bd2efb --- /dev/null +++ b/src/internal/scope/MainGlobalConfiguration.mts @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +import { + type GlobalConfiguration, + TerminalEncoding, + Terminal, + internalValueToString, + assertThatValueIsNotNull +} from "../internal.mjs"; + +/** + * Default global configuration. + */ +class MainGlobalConfiguration implements GlobalConfiguration +{ + private readonly terminal: Terminal; + + /** + * @param terminal - the system configuration + * @throws TypeError if `terminal` is not a `Terminal` + */ + public constructor(terminal: Terminal) + { + assertThatValueIsNotNull(terminal, "terminal"); + this.terminal = terminal; + } + + public supportedTerminalEncodings(): Set + { + return this.terminal.getSupportedTypes(); + } + + public terminalEncoding(): TerminalEncoding; + public terminalEncoding(encoding: TerminalEncoding): GlobalConfiguration; + public terminalEncoding(encoding?: TerminalEncoding): TerminalEncoding | GlobalConfiguration + { + if (encoding === undefined) + return this.terminal.getEncoding(); + if (encoding === null) + throw new TypeError("encoding may not be null"); + this.terminal.setEncoding(encoding); + return this; + } + + public toString() + { + return `MainGlobalConfiguration[supportedTerminalEncodings= +${internalValueToString(this.supportedTerminalEncodings())}, terminalEncoding= +${internalValueToString(this.terminalEncoding())}]`; + } +} + +export {MainGlobalConfiguration}; \ No newline at end of file diff --git a/src/internal/scope/ProcessScope.mts b/src/internal/scope/ProcessScope.mts new file mode 100644 index 0000000..7d5287b --- /dev/null +++ b/src/internal/scope/ProcessScope.mts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2016 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +import type {Terminal} from "../internal.mjs"; + +/** + * The process configuration. + */ +interface ProcessScope +{ + /** + * @returns the terminal attached to the process + */ + getTerminal(): Terminal; + + /** + * Closes the scope. + */ + close(): void; +} + +export type {ProcessScope}; \ No newline at end of file diff --git a/src/internal/util/AssertionError.mts b/src/internal/util/AssertionError.mts new file mode 100644 index 0000000..454a636 --- /dev/null +++ b/src/internal/util/AssertionError.mts @@ -0,0 +1,24 @@ +/** + * Thrown when assertion has failed or a condition thought to be impossible has occurred. + */ +class AssertionError extends Error +{ + /** + * Creates a new error. + * + * @param message - an explanation of what went wrong + * @param options - configuration options + */ + constructor(message?: string, options?: { cause: Error }) + { + super(message, options); + this.name = this.constructor.name; + if (options !== undefined && options.cause !== undefined) + { + const originalError = options.cause; + this.stack = originalError.stack?.replace(originalError.name, this.name); + } + } +} + +export {AssertionError}; \ No newline at end of file diff --git a/src/internal/util/Difference.mts b/src/internal/util/Difference.mts new file mode 100644 index 0000000..6ffcc57 --- /dev/null +++ b/src/internal/util/Difference.mts @@ -0,0 +1,102 @@ +import {requireThatValueIsDefined} from "../validator/Objects.mjs"; + +/** + * The difference between two collections, irrespective of element ordering. + * + * @typeParam E - the type of elements in the actual and other collections + * @param common - elements that were present in both collections + * @param onlyInActual - elements that were only present in the value + * @param onlyInOther - elements that were only present in the other collection + */ +class Difference +{ + public readonly common: Set; + public readonly onlyInActual: Set; + public readonly onlyInOther: Set; + + /** + * Creates a new instance. + * + * @param common - the elements that are common to both arrays + * @param onlyInActual - the elements that are only found in the actual value + * @param onlyInOther - the elements that are only found in the other value + */ + private constructor(common: Set, onlyInActual: Set, onlyInOther: Set) + { + requireThatValueIsDefined(common, "common"); + requireThatValueIsDefined(onlyInActual, "onlyInActual"); + requireThatValueIsDefined(onlyInOther, "onlyInOther"); + this.common = common; + this.onlyInActual = onlyInActual; + this.onlyInOther = onlyInOther; + } + + /** + * Compares the elements in two collections. + * + * @typeParam E - the type of elements in the collections + * @param value - the value's elements + * @param other - the other collection's elements + * @returns the elements that were common to both collections, or were only present in the value, or were + * only present in the other collection + */ + public static actualVsOther(value: E[] | Set, other: E[] | Set) + { + const valueAsSet = this.asSet(value); + const otherAsSet = this.asSet(other); + + const common = this.intersection(valueAsSet, otherAsSet); + const onlyInValue = this.firstMinusSecond(valueAsSet, otherAsSet); + const onlyInOther = this.firstMinusSecond(otherAsSet, valueAsSet); + return new Difference(common, onlyInValue, onlyInOther); + } + + /** + * @param value - an array or set + * @returns the Set representation of the value + */ + private static asSet(value: E[] | Set): Set + { + if (value instanceof Set) + return value; + return new Set(value); + } + + /** + * @param first - a set + * @param second - a set + * @returns the elements found in both sets + */ + private static intersection(first: Set, second: Set) + { + return new Set([...first].filter(x => second.has(x))); + } + + /** + * @param first - a set + * @param second - a set + * @returns the elements found in the first set but not the second set + */ + private static firstMinusSecond(first: Set, second: Set) + { + return new Set([...first].filter(x => !second.has(x))); + } + + /** + * @returns `true` if both collections contain the same elements, irrespective of ordering + */ + public areTheSame() + { + return this.onlyInActual.size === 0 && this.onlyInOther.size === 0; + } + + /** + * @returns `true` if the collections contain different elements + */ + public areDifferent() + { + return this.onlyInActual.size > 0 || this.onlyInOther.size > 0; + } +} + +export {Difference}; \ No newline at end of file diff --git a/src/internal/IllegalStateError.mts b/src/internal/util/IllegalStateError.mts similarity index 71% rename from src/internal/IllegalStateError.mts rename to src/internal/util/IllegalStateError.mts index 0ad7e64..0bce17a 100644 --- a/src/internal/IllegalStateError.mts +++ b/src/internal/util/IllegalStateError.mts @@ -3,6 +3,11 @@ */ class IllegalStateError extends Error { + /** + * Creates a new error. + * + * @param message - an explanation of what went wrong + */ constructor(message: string) { super(message); diff --git a/src/internal/util/ObjectAndSize.mts b/src/internal/util/ObjectAndSize.mts new file mode 100644 index 0000000..36f1e0e --- /dev/null +++ b/src/internal/util/ObjectAndSize.mts @@ -0,0 +1,31 @@ +/** + * An object and its size: If the object represents a collection, the size refers to the number of elements it + * contains. If the object represents a string, the size corresponds to its length. + * + * @param value - the object + * @param size - the value's size + */ +class ObjectAndSize +{ + public object: Map | Set | unknown[] | string; + public size: number; + + /** + * Creates a new instance. + * + * @param object - the object + * @param size - the object's size + */ + constructor(object: Map | Set | unknown[] | string, size: number) + { + this.object = object; + this.size = size; + } + + toString() + { + return `value: ${JSON.stringify(this.object, null, 2)}, size: ${this.size}`; + } +} + +export {ObjectAndSize}; \ No newline at end of file diff --git a/src/internal/util/Strings.mts b/src/internal/util/Strings.mts new file mode 100644 index 0000000..b14c375 --- /dev/null +++ b/src/internal/util/Strings.mts @@ -0,0 +1,140 @@ +import { + Type, + type ClassConstructor, + assertThatType, + getSuperclass, + type StringMapper, + internalValueToString +} from "../internal.mjs"; + +class SearchResult +{ + /** + * The start index (inclusive) of the matched text. + */ + readonly start: number; + /** + * The end index (exclusive) of the matched text. + */ + readonly end: number; + + constructor(start: number, end: number) + { + this.start = start; + this.end = end; + } +} + +/** + * Returns the last consecutive occurrence of `target` within `source`. + * The last occurrence of the empty string `""` is considered to occur at the index value + * `source.length()`. + *

+ * The returned index is the largest value `k` for which + * `source.startsWith(target, k)` consecutively. If no such value of `k` exists, then + * `-1` is returned. + * + * @param source - the string to search within + * @param target - the string to search for + * @returns the index of the last consecutive occurrence of `target` in `source`, + * or `-1` if there is no such occurrence. + */ +function lastConsecutiveIndexOf(source: string, target: string) +{ + assertThatType(source, "source", Type.STRING); + assertThatType(target, "target", Type.STRING); + const lengthOfTarget = target.length; + let result = -1; + if (lengthOfTarget === 0) + return result; + + for (let i = source.length - lengthOfTarget; i >= 0; i -= lengthOfTarget) + { + if (!source.startsWith(target, i)) + return result; + result = i; + } + return result; +} + +/** + * Returns the last occurrence of `target` in `source`. + * + * @param source - the string to search within + * @param target - the regular expression to search for + * @returns null if no match was found + */ +function lastIndexOf(source: string, target: RegExp): SearchResult | null +{ + // RegExp is stateful: https://stackoverflow.com/a/11477448/14731 + let flags = target.flags; + if (!flags.includes("g")) + flags += "g"; + const matcher = new RegExp(target.source, flags); + let match; + let searchResult: SearchResult | null = null; + while (true) + { + match = matcher.exec(source); + if (!match) + break; + searchResult = new SearchResult(match.index, match.index + match[0].length); + } + return searchResult; +} + +/** + * @param source - the string to search within + * @param target - the string to search for + * @returns true if `source` only contains (potentially multiple) occurrences of + * `target` or if `source` is empty + */ +function containsOnly(source: string, target: string) +{ + return source.length === 0 || lastConsecutiveIndexOf(source, target) === 0; +} + +const builtInMapper = (v: unknown) => `${internalValueToString(v)}`; + +/** + * Returns a StringMapper for a value or the closest ancestor that has an associated mapper. + * + * @param value - a value + * @param typeToMapper - a mapping from the name of a type to the string representation of its values + * @returns the StringMapper for the value + */ +function getMapper(value: unknown, typeToMapper: Map): StringMapper +{ + const type = Type.of(value); + let mapper = typeToMapper.get(type); + if (mapper !== undefined) + return mapper; + if (type.isPrimitive()) + return builtInMapper; + + const superclass = getSuperclass(value as ClassConstructor); + if (superclass === null) + return builtInMapper; + const superclassType = Type.of(superclass); + mapper = typeToMapper.get(superclassType); + if (mapper !== undefined) + return mapper; + return getMapper(superclass, typeToMapper); +} + +/** + * @param value - a string + * @returns true if the string does not contain any leading or trailing whitespace + */ +function valueIsStripped(value: string): boolean +{ + return /^\S+.*\S+$/.test(value); +} + +export { + lastConsecutiveIndexOf, + lastIndexOf, + containsOnly, + getMapper, + valueIsStripped +}; \ No newline at end of file diff --git a/src/internal/util/ValidationTarget.mts b/src/internal/util/ValidationTarget.mts new file mode 100644 index 0000000..917e811 --- /dev/null +++ b/src/internal/util/ValidationTarget.mts @@ -0,0 +1,188 @@ +import {Type} from "../internal.mjs"; + +/** + * Represents a value that is being validated. + * + * This class is not intended to replace `undefined` or `null` references but to record additional + * information alongside them. + * + * Instead of throwing an error when an `undefined` or `null` value is accessed, the system + * marks it as invalid and continues to record validation failures. + * + * @typeParam T - the type of the value + */ +class ValidationTarget +{ + private static readonly INVALID: ValidationTarget = new ValidationTarget(false, + undefined); + private readonly valid: boolean; + private readonly value: T; + + /** + * Creates a value that may be invalid. + * + * @param valid - `true` if the value is valid, or `false` if invalid + * @param value - the value + */ + public constructor(valid: boolean, value: T) + { + this.valid = valid; + this.value = value; + } + + /** + * Returns an invalid value. + * + * @typeParam T - the type of the value + * @returns an invalid value + */ + public static invalid() + { + return ValidationTarget.INVALID as ValidationTarget; + } + + /** + * Returns a valid value. + * + * @typeParam T - the type of the value + * @param value - a value + * @returns a valid value + */ + public static valid(value: T) + { + return new ValidationTarget(true, value); + } + + /** + * Returns the valid value, or a default value if invalid. A value of `null` does not hold any + * special significance. It does not imply that the value is invalid. + * + * @param defaultValue - a value + * @returns the valid value, or `defaultValue` if the value is invalid + */ + public or(defaultValue: T): T; + public or(defaultValue: T | null): T | null; + public or(defaultValue: T | null): T | null + { + if (this.valid) + return this.value; + return defaultValue; + } + + /** + * Returns the valid value, or a default value if invalid. A value of `null` does not hold any + * special significance. It does not imply that the value is invalid. + * + * @param defaultValue - a supplier that returns the default value + * @returns the valid value, or `defaultValue` if the value is invalid + */ + public orGet(defaultValue: () => T) + { + if (this.valid) + return this.value; + return defaultValue(); + } + + /** + * Consumes the value if it is valid. If the value is invalid, no action is taken. + * + * @param consumer - consumes the value if it is valid + */ + public ifValid(consumer: (value: T) => void) + { + if (this.valid) + consumer(this.value); + } + + /** + * Applies a function to the value if it is valid. If the value is invalid, no action is taken. + * + * @typeParam U - the type of value returned by the mapper + * @param mapper - the function to apply to the value if it is valid + * @returns `this` if the value is invalid; otherwise, a `MaybeInvalid` instance wrapping the + * result of applying the mapper to the value + */ + public map(mapper: (value: T) => U): ValidationTarget + { + if (!this.valid) + return this as unknown as ValidationTarget; + const newValue = mapper(this.value); + if (this.value as unknown as U === newValue) + return this as unknown as ValidationTarget; + return ValidationTarget.valid(newValue); + } + + /** + * Converts an `undefined` or `null` value to an invalid value. If the value is invalid or non-`null`, no + * action is taken. + * + * @returns an invalid value if the original value was `undefined` or `null`; otherwise, returns `this` + */ + public undefinedOrNullToInvalid(): ValidationTarget + { + if (this.valid && (this.value === undefined || this.value === null)) + return ValidationTarget.invalid(); + return this; + } + + /** + * Returns the value or throws an error if invalid. + * + * @typeParam U - the type of error to throw + * @param errorSupplier - returns the error to throw if the value is invalid + * @returns the value + * @throws U if the value is invalid + */ + public orThrow(errorSupplier: () => U): T + { + if (this.valid) + return this.value; + // WORKAROUND: https://github.com/typescript-eslint/typescript-eslint/issues/9882 + throw errorSupplier() as Error; + } + + /** + * Checks if the value is valid. + * + * @returns `true` if the value is valid; `false` otherwise + */ + public isValid() + { + return this.valid; + } + + /** + * Checks if the value is null. + * + * @returns `true` if the value is null; `false` otherwise + */ + public isNull() + { + return this.valid && this.value === null; + } + + /** + * Evaluates a condition on the value. + * + * @param condition - the condition to evaluate + * @returns `true` if the value is invalid or if the `condition` returns `false`; + * otherwise, returns `false` + */ + public validationFailed(condition: (value: T) => boolean) + { + return !this.valid || !condition(this.value); + } + + public toString() + { + if (!this.valid) + return "invalid"; + if (this.value === null) + return "null"; + return `${JSON.stringify(Type.of(this.value), null, 2)}: ${JSON.stringify(this.value, undefined, 2)}`; + } +} + +export { + ValidationTarget +}; \ No newline at end of file diff --git a/src/internal/validator/AbstractCollectionValidator.mts b/src/internal/validator/AbstractCollectionValidator.mts new file mode 100644 index 0000000..f9e4f55 --- /dev/null +++ b/src/internal/validator/AbstractCollectionValidator.mts @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2019 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +import isEqual from "lodash.isequal"; +import { + type ApplicationScope, + Type, + assertThatType, + Pluralizer, + Configuration, + type ValidationFailure, + AbstractValidator, + ValidationTarget, + Difference, + requireThatValueIsNotNull, + objectIsEmpty, + objectIsNotEmpty, + type UnsignedNumberValidator, + ObjectSizeValidatorImpl, + collectionContains, + collectionDoesNotContainExactly, + collectionContainsAny, + collectionDoesNotContainAny, + collectionContainsAll, + collectionDoesNotContainAll, + collectionDoesNotContainDuplicates, + collectionDoesNotContain, + collectionContainsExactly, + collectionContainsSameNullity +} from "../internal.mjs"; + +/** + * Validates the state of a collection. + * + * @typeParam S - the type of validator returned by the methods + * @typeParam T - the type the collection + * @typeParam E - the type of elements in the array + */ +abstract class AbstractCollectionValidator, E> + extends AbstractValidator +{ + protected readonly pluralizer: Pluralizer; + + /** + * @param scope - the application configuration + * @param configuration - the validator configuration + * @param name - the name of the value + * @param value - the value + * @param pluralizer - the type of items in the array + * @param context - the contextual information set by a parent validator or the user + * @param failures - the list of validation failures + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace, or is empty + * @throws AssertionError if `scope`, `configuration`, `value`, `context` or `failures` are null + */ + public constructor(scope: ApplicationScope, configuration: Configuration, name: string, + value: ValidationTarget, pluralizer: Pluralizer, context: Map, + failures: ValidationFailure[]) + { + super(scope, configuration, name, value, context, failures); + + requireThatValueIsNotNull(pluralizer, "pluralizer"); + this.pluralizer = pluralizer; + } + + public isEmpty(): S + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && this.getLength(v) === 0)) + { + this.addRangeError( + objectIsEmpty(this).toString()); + } + return this.self(); + } + + /** + * @param value - the collection + * @returns the length of the collection + */ + protected getLength(value: T) + { + return this.collectionAsArray(value).length; + } + + /** + * @param value - the value + * @returns the array representation of the value + */ + protected collectionAsArray(value: E[] | Set): E[] + { + if (Array.isArray(value)) + return value; + assertThatType(value, "value", Type.namedClass("Set")); + return Array.from(value); + } + + /** + * @param value - the value + * @returns the array representation of the value + */ + protected collectionAsSet(value: E[] | Set): Set + { + if (value instanceof Set) + return value; + assertThatType(value, "value", Type.ARRAY); + return new Set(value); + } + + public isNotEmpty(): S + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && this.getLength(v) !== 0)) + { + this.addRangeError( + objectIsNotEmpty(this).toString()); + } + return this.self(); + } + + contains(expected: E): S; + contains(expected: E, name?: string): S + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && this.collectionContainsElement(v, expected))) + { + this.addRangeError( + collectionContains(this, name ?? null, expected).toString()); + } + return this.self(); + } + + /** + * Indicates if an array contains at least one element of another array. + * + * @param value - a collection + * @param element - an element + * @returns true if `value` contains the element + */ + protected collectionContainsElement(value: T, element: E): boolean + { + // Set.has(), indexOf(), includes() do not work for multidimensional arrays: + // http://stackoverflow.com/a/24943461/14731 + const valueAsArray = this.collectionAsArray(value); + for (let i = 0; i < valueAsArray.length; ++i) + { + if (isEqual(valueAsArray[i], element)) + return true; + } + return false; + } + + doesNotContain(unwanted: E): S; + doesNotContain(unwanted: E, name?: string): S + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && !this.collectionContainsElement(v, unwanted))) + { + this.addRangeError( + collectionDoesNotContain(this, name ?? null, unwanted).toString()); + } + return this.self(); + } + + containsExactly(expected: E[], name?: string): S; + containsExactly(expected: Set, name?: string): S; + containsExactly(expected: E[] | Set, name?: string): S + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + const difference = this.value.undefinedOrNullToInvalid(). + map(v => Difference.actualVsOther(v, expected)).or(null); + if (difference === null || difference.areDifferent()) + { + this.addRangeError( + collectionContainsExactly(this, difference, name ?? null, expected, this.pluralizer). + toString()); + } + return this.self(); + } + + doesNotContainExactly(unwanted: E[], name?: string): S; + doesNotContainExactly(unwanted: Set, name?: string): S; + doesNotContainExactly(unwanted: E[] | Set, name?: string): S + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + const difference = this.value.undefinedOrNullToInvalid(). + map(v => Difference.actualVsOther(v, unwanted)).or(null); + if (difference === null || difference.areTheSame()) + { + this.addRangeError( + collectionDoesNotContainExactly(this, name ?? null, unwanted, this.pluralizer).toString()); + } + return this.self(); + } + + containsAny(expected: E[], name?: string): S; + containsAny(expected: Set, name?: string): S; + containsAny(expected: E[] | Set, name?: string): S + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + const definedOrNull = this.value.or(null); + + if (definedOrNull == null || + this.isDisjoint(this.collectionAsSet(definedOrNull), this.collectionAsSet(expected))) + { + this.addRangeError( + collectionContainsAny(this, name ?? null, expected, this.pluralizer).toString()); + } + return this.self(); + } + + /** + * @param first - a set + * @param second - a second set + * @returns `true` if the sets do not contain any of the same elements + */ + private isDisjoint(first: Set, second: Set) + { + // WORKAROUND: Can be replaced by Set.isDisjointFrom() once Typescript supports ES2024 + for (const v of first) + if (second.has(v)) + return false; + return true; + } + + doesNotContainAny(unwanted: E[], name?: string): S; + doesNotContainAny(unwanted: Set, name?: string): S; + doesNotContainAny(unwanted: E[] | Set, name?: string): S + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + const difference = this.value.undefinedOrNullToInvalid().map(v => Difference.actualVsOther(v, unwanted)). + or(null); + if (difference === null || difference.common.size !== 0) + { + this.addRangeError( + collectionDoesNotContainAny(this, difference, name ?? null, unwanted, this.pluralizer). + toString()); + } + return this.self(); + } + + containsAll(expected: E[], name?: string): S; + containsAll(expected: Set, name?: string): S; + containsAll(expected: E[] | Set, name?: string): S + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + const difference = this.value.undefinedOrNullToInvalid().map(v => Difference.actualVsOther(v, expected)). + or(null); + if (difference === null || difference.onlyInOther.size !== 0) + { + this.addRangeError( + collectionContainsAll(this, difference, name ?? null, expected, this.pluralizer). + toString()); + } + return this.self(); + } + + doesNotContainAll(unwanted: E[], name?: string): S; + doesNotContainAll(unwanted: Set, name?: string): S; + doesNotContainAll(unwanted: E[] | Set, name?: string): S + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && !this.collectionContainsAll(v, unwanted))) + { + this.addRangeError( + collectionDoesNotContainAll(this, name ?? null, unwanted, this.pluralizer). + toString()); + } + return this.self(); + } + + /** + * Indicates if an array contains all elements of another array. + * + * @param value - the value + * @param expected - a collection of expected elements + * @returns true if `value` contains all the `expected` elements + */ + private collectionContainsAll(value: T, expected: E[] | Set): boolean + { + // WORKAROUND: Replace with Set.isSupersetOf() once Typescript supports ES2024 + for (const element of this.collectionAsArray(expected)) + { + if (!this.collectionContainsElement(value, element)) + return false; + } + return true; + } + + doesNotContainDuplicates(): S + { + if (this.value.isNull()) + this.onNull(); + const duplicates = this.value.undefinedOrNullToInvalid().map(v => + this.getDuplicates(this.collectionAsArray(v))); + if (duplicates.validationFailed(v => v.size === 0)) + { + this.addRangeError( + collectionDoesNotContainDuplicates(this, duplicates.or(null), this.pluralizer). + toString()); + } + return this.self(); + } + + /** + * @param value - the value + * @returns the duplicate elements in the value + */ + protected getDuplicates(value: E[]): Set + { + const unique = new Set(); + const duplicates = new Set(); + for (let i = 0; i < value.length; ++i) + { + const element = value[i]; + if (unique.has(element)) + duplicates.add(element); + else + unique.add(element); + } + return duplicates; + } + + public containsSameNullity(): S + { + if (this.value.validationFailed(v => + { + let numberOfNulls = 0; + for (const element of v) + if (element == null) + ++numberOfNulls; + return numberOfNulls == this.getLength(v); + })) + { + this.addRangeError( + collectionContainsSameNullity(this).toString()); + } + return this.self(); + } + + length(): UnsignedNumberValidator + { + if (this.value.isNull()) + this.onNull(); + return new ObjectSizeValidatorImpl(this.scope, this._configuration, this, this.name + ".length()", + this.value.undefinedOrNullToInvalid().map(v => this.getLength(v)), this.pluralizer, this.context, + this.failures); + } +} + +export {AbstractCollectionValidator}; \ No newline at end of file diff --git a/src/internal/validator/AbstractValidator.mts b/src/internal/validator/AbstractValidator.mts new file mode 100644 index 0000000..919ae4a --- /dev/null +++ b/src/internal/validator/AbstractValidator.mts @@ -0,0 +1,413 @@ +import { + type ValidationFailure, + Configuration, + type ApplicationScope, + type ValidatorComponent, + MessageBuilder, + JavascriptValidatorsImpl, + type ErrorBuilder, + ValidationFailureImpl, + MultipleFailuresError, + assertThatType, + isApplicationScope, + assertThatInstanceOf, + Type, + ValidationTarget, + IllegalStateError, + requireThatStringIsNotEmpty, + assertThatValueIsNotNull, + ObjectSizeValidatorImpl, + messagesIsNotNull, + messagesIsInstanceOf, + messagesIsNotEqualTo, + ValidationFailures, + messagesIsEqualTo, + messagesIsUndefined, + type ObjectValidator, + type NonUndefinable, + messagesIsNull, + type ClassConstructor +} from "../internal.mjs"; +import isEqual from "lodash.isequal"; + +/** + * Validates the state of a value, recording failures without throwing an error. + * + * @typeParam S - the type of validator that the methods should return + * @typeParam T - the type of the value + */ +abstract class AbstractValidator implements ValidatorComponent +{ + protected static readonly VALUE_IS_UNDEFINED = () => new IllegalStateError("value is invalid"); + private static readonly CONTAINS_WHITESPACE = /.*\\s.*/u; + /** + * The application configuration. + */ + protected readonly scope: ApplicationScope; + /** + * The validator configuration. + */ + protected readonly _configuration: Configuration; + /** + * The name of the value. + */ + protected readonly name: string; + /** + * The value being validated. + */ + public readonly value: ValidationTarget; + /** + * The contextual information of this validator. + */ + protected readonly context: Map; + /** + * The list of validation failures. + */ + protected readonly failures: ValidationFailure[]; + + /** + * @param scope - the application configuration + * @param configuration - the validator configuration + * @param name - the name of the value + * @param value - the value being validated + * @param context - the contextual information set by a parent validator or the user + * @param failures - the list of validation failures + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace, or is empty + * @throws AssertionError if `scope`, `configuration`, `value`, `context` or `failures` are null + */ + protected constructor(scope: ApplicationScope, configuration: Configuration, name: string, + value: ValidationTarget, context: Map, + failures: ValidationFailure[]) + { + assertThatType(scope, "scope", Type.namedClass("ApplicationScope", () => isApplicationScope(scope))); + assertThatInstanceOf(configuration, "configuration", Configuration); + + requireThatStringIsNotEmpty(name, "name"); + if (AbstractValidator.CONTAINS_WHITESPACE.test(name)) + { + throw new RangeError("name may not contain whitespace.\n" + + "actual: \"" + name + "\""); + } + + assertThatValueIsNotNull(value, "value"); + assertThatValueIsNotNull(context, "context"); + assertThatValueIsNotNull(failures, "failures"); + this.scope = scope; + this._configuration = configuration; + this.name = name; + this.value = value; + this.context = context; + this.failures = failures; + } + + /** + * @returns the application configuration + */ + public getScope() + { + return this.scope; + } + + public getName() + { + return this.name; + } + + public validationFailed() + { + return this.failures.length !== 0; + } + + public getValue() + { + return this.value.orThrow(ObjectSizeValidatorImpl.VALUE_IS_UNDEFINED); + } + + getValueOrDefault(defaultValue: T): T; + public getValueOrDefault(defaultValue: T | null): T | null; + public getValueOrDefault(defaultValue: T | null): T | null + { + return this.value.or(defaultValue); + } + + and(validation: (validator: S) => void): S + { + validation(this.self()); + return this.self(); + } + + /** + * Adds a validation failure and throws an error if the validator is configured to throw an error on + * failure. + * + * @param message - a message that explains what went wrong + * @param errorBuilder - creates the error associated with this failure + */ + public addFailure(message: string, errorBuilder: ErrorBuilder) + { + const failure = new ValidationFailureImpl(this._configuration, message, errorBuilder); + this.failures.push(failure); + if (this._configuration.throwOnFailure()) + throw failure.getError(); + } + + /** + * Adds a `TypeError` validation failure and throws an error if the validator is + * configured to throw an error on failure. + * + * @param message - a message that explains what went wrong + */ + protected addTypeError(message: string): void + { + this.addFailure(message, (theMessage: string) => new TypeError(theMessage)); + } + + /** + * Adds a `RangeError` validation failure and throws an error if the validator is configured + * to throw an error on failure. + * + * @param message - a message that explains what went wrong + */ + protected addRangeError(message: string) + { + this.addFailure(message, (theMessage: string) => new RangeError(theMessage)); + } + + public configuration(): Configuration + { + return this._configuration; + } + + /** + * @typeParam U - the expected return type + * @returns this + */ + protected self(): U + { + return this as unknown as U; + } + + public elseGetFailures() + { + return new ValidationFailures(this.failures); + } + + public elseThrow(): boolean + { + const error = this.elseGetError(); + if (error === null) + return true; + throw error; + } + + public elseGetError(): Error | null + { + if (this.failures.length === 0) + return null; + if (this.failures.length === 1) + return this.failures[0].getError(); + return new MultipleFailuresError(this.failures); + } + + public getContext(): Map + { + return new Map(this.context); + } + + public withContext(value: unknown, name: string): S + { + this.requireThatNameIsUnique(name, false); + if (value === null) + this.context.delete(name); + else + this.context.set(name, value); + return this.self(); + } + + public getContextAsString(): string + { + return new MessageBuilder(this, "").toString(); + } + + /** + * Ensures that a name does not conflict with other variable names already in use by the validator. + * + * @param name - the name of the parameter + * @param checkContext - `false` to allow the name to be used even if it conflicts with an + * existing name in the validator context + * @returns the internal validator of the name + * @throws RangeError if `name` is null + * @throws RangeError if `name`: + *

    + *
  • contains whitespace
  • + *
  • is empty
  • + *
  • is already in use by the value being validated or the validator context
  • + *
+ */ + protected requireThatNameIsUnique(name: string, checkContext = true) + { + const internalValidators = JavascriptValidatorsImpl.INTERNAL; + internalValidators.requireThat(name, "name").isTrimmed().isNotEmpty(); + if (AbstractValidator.CONTAINS_WHITESPACE.test(name)) + throw new RangeError("name may not contain whitespace"); + + if (name === this.name) + { + throw new RangeError(`The name "${name}" is already in use by the value being validated. +Choose a different name.`); + } + if (checkContext && this.context.has(name)) + { + throw new RangeError(`The name "${name}" is already in use by the validator context. Choose a \ +different name.`); + } + return internalValidators; + } + + public isUndefined() + { + if (this.value.validationFailed(v => v === undefined)) + { + this.addTypeError( + messagesIsUndefined(this).toString()); + } + return this as unknown as ObjectValidator; + } + + public isNotUndefined() + { + if (this.value.validationFailed(v => v !== undefined)) + { + this.addTypeError( + messagesIsUndefined(this).toString()); + } + return this as unknown as ObjectValidator>; + } + + + public isNull() + { + if (this.value.validationFailed(v => v === null)) + { + this.addTypeError( + messagesIsNull(this).toString()); + } + return this as unknown as ObjectValidator; + } + + public isNotNull() + { + if (this.value.validationFailed(v => v !== null)) + { + this.addTypeError( + messagesIsNotNull(this).toString()); + } + return this as unknown as ObjectValidator>; + } + + /** + * @param otherType - another type + * @param mustBeEqual - `true` if the value must match the other type, `false` if it must not match the + * other type + * @throws TypeError if the value does not match the expected type and the validator is configured to throw + * an error on failure + * @returns true if the value does not match the expected type + */ + private validateType(otherType: Type, mustBeEqual: boolean): boolean + { + const validationFailed = this.value.validationFailed(v => + { + const typeOfValue = Type.of(v); + if (typeof (otherType.typeGuard) !== "undefined") + return otherType.typeGuard(v); + return isEqual(typeOfValue, otherType) === mustBeEqual; + }); + if (validationFailed) + { + this.addTypeError( + messagesIsInstanceOf(this, otherType).toString()); + return false; + } + return true; + } + + public isType(expected: Type): S + { + JavascriptValidatorsImpl.INTERNAL.requireThat(expected, "expected").isNotNull(); + if (this.value.validationFailed(v => Type.of(v).equals(expected))) + { + this.addTypeError( + messagesIsInstanceOf(this, expected).toString()); + } + return this.self(); + } + + public isInstanceOf(expected: ClassConstructor): ObjectValidator + { + JavascriptValidatorsImpl.INTERNAL.requireThat(expected, "expected").isNotNull(); + const className = Type.of(expected).name; + this.validateType(Type.namedClass(className), true); + return this as unknown as ObjectValidator; + } + + public isNotInstanceOf(expected: ClassConstructor): ObjectValidator + { + JavascriptValidatorsImpl.INTERNAL.requireThat(expected, "expected").isNotNull(); + const className = Type.of(expected).name; + this.validateType(Type.namedClass(className), false); + return this.self(); + } + + public isEqualTo(expected: unknown): S; + public isEqualTo(expected: unknown, name?: string) + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.validationFailed(v => isEqual(v, expected))) + { + this.addRangeError( + messagesIsEqualTo(this, name ?? null, expected).toString()); + } + return this.self(); + } + + public isNotEqualTo(unwanted: unknown): S; + public isNotEqualTo(unwanted: unknown, name?: string) + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.validationFailed(v => !isEqual(v, unwanted))) + { + this.addRangeError( + messagesIsNotEqualTo(this, name ?? null, unwanted).toString()); + } + return this.self(); + } + + /** + * @param name - the name of the value + * @param namePrefix - the string to prepend to the name if the name is null + * @param value - a value + * @param valuePrefix - the string to prepend to the value if the name is null + * @returns the prefixed name if it is defined; otherwise, the prefixed string representation of the value + */ + public getNameOrValue(namePrefix: string, name: string | null, valuePrefix: string, value: unknown) + { + if (name === null) + return valuePrefix + this.configuration().stringMappers().toString(value); + return namePrefix + MessageBuilder.quoteName(name); + } + + /** + * Invoked by a validation if the value is null. Sets the value to `undefined`. + */ + protected onNull() + { + this.addRangeError(messagesIsNotNull(this).toString()); + } +} + +export {AbstractValidator}; \ No newline at end of file diff --git a/src/internal/validator/AbstractValidators.mts b/src/internal/validator/AbstractValidators.mts new file mode 100644 index 0000000..111b6b6 --- /dev/null +++ b/src/internal/validator/AbstractValidators.mts @@ -0,0 +1,266 @@ +import { + type GlobalConfiguration, + Configuration, + IllegalStateError, + type ApplicationScope, + MutableConfiguration, + type ConfigurationUpdater, + MutableStringMappers, + type Validators, + internalValueToString, + requireThatValueIsNotNull, + AssertionError +} from "../internal.mjs"; + +/** + * Updates the configuration that will be used by new validators. + */ +class ConfigurationUpdaterImpl implements ConfigurationUpdater +{ + private readonly outer: AbstractValidators; + private readonly setConfiguration: (configuration: Configuration) => void; + private _allowDiff: boolean; + private readonly mutableStringMappers: MutableStringMappers; + private _recordStacktrace: boolean; + private _errorTransformer: (error: Error) => Error; + private changed = false; + private closed = false; + + /** + * Creates a new configuration updater. + * + * @param outer - a reference to the outer class + * @param setConfiguration - a method that sets the validator factory's configuration + * @throws TypeError if `setConfiguration` is `undefined` or `null` + */ + public constructor(outer: AbstractValidators, + setConfiguration: (configuration: Configuration) => void) + { + this.outer = outer; + this.setConfiguration = setConfiguration; + + const configuration = outer.getConfiguration(); + this._allowDiff = configuration.allowDiff(); + this.mutableStringMappers = MutableStringMappers.from(configuration.stringMappers()); + this._recordStacktrace = configuration.recordStacktrace(); + this._errorTransformer = configuration.errorTransformer(); + } + + public allowDiff(): boolean; + public allowDiff(mayDiff: boolean): ConfigurationUpdater + public allowDiff(allowDiff?: boolean): boolean | ConfigurationUpdater + { + this.ensureOpen(); + if (allowDiff === undefined) + return this._allowDiff; + if (allowDiff !== this._allowDiff) + { + this._allowDiff = allowDiff; + this.changed = true; + } + return this; + } + + public stringMappers(): MutableStringMappers + { + this.ensureOpen(); + return this.mutableStringMappers; + } + + public recordStacktrace(): boolean; + public recordStacktrace(recordStacktrace: boolean): ConfigurationUpdater; + public recordStacktrace(recordStacktrace?: boolean): boolean | ConfigurationUpdater + { + this.ensureOpen(); + if (recordStacktrace === undefined) + return this._recordStacktrace; + if (recordStacktrace !== this._recordStacktrace) + { + this._recordStacktrace = recordStacktrace; + this.changed = true; + } + return this; + } + + public errorTransformer(): (error: Error) => Error; + public errorTransformer(errorTransformer: (error: Error) => Error): ConfigurationUpdater; + public errorTransformer(errorTransformer?: (error: Error) => Error): ((error: Error) => Error) | ConfigurationUpdater + { + this.ensureOpen(); + if (errorTransformer === undefined) + return this._errorTransformer; + if (errorTransformer !== this._errorTransformer) + { + this._errorTransformer = errorTransformer; + this.changed = true; + } + return this; + } + + /** + * @throws IllegalStateError if the updater is closed + */ + private ensureOpen(): void + { + if (this.closed) + throw new IllegalStateError("The changes have already been applied"); + } + + public close(): void + { + if (this.closed) + return; + this.closed = true; + const oldConfiguration = this.outer.getConfiguration(); + const immutableStringMappers = this.mutableStringMappers.toImmutable(); + this.changed ||= immutableStringMappers !== oldConfiguration.stringMappers(); + if (!this.changed) + return; + this.outer.setConfiguration(new Configuration(this._allowDiff, immutableStringMappers, + this._recordStacktrace, oldConfiguration.throwOnFailure(), this._errorTransformer)); + } + + public toString() + { + return `allowDiff: ${this._allowDiff}, stringMappers: ${internalValueToString( + this.mutableStringMappers)}, recordStacktrace: ${this._recordStacktrace}`; + } +} + +/** + * @typeParam S - the type of the validator factory + */ +abstract class AbstractValidators implements Validators +{ + /** + * A function that converts the thrown error to `AssertionError`. + */ + private static readonly CONVERT_TO_ASSERTION_ERROR: (error: Error) => Error = + error => new AssertionError(error.message, {cause: error}); + protected readonly scope: ApplicationScope; + private requireThatConfiguration: Configuration; + private assertThatConfiguration: Configuration; + private checkIfConfiguration: Configuration; + protected readonly context = new Map(); + + /** + * Creates a new instance. + * + * @param scope - the application configuration + * @param configuration - the configuration to use for new validators + * @throws TypeError if any of the arguments are `null` + */ + protected constructor(scope: ApplicationScope, configuration: Configuration) + { + this.scope = scope; + requireThatValueIsNotNull(configuration, "configuration"); + this.requireThatConfiguration = configuration; + this.assertThatConfiguration = MutableConfiguration.from(configuration). + errorTransformer(AbstractValidators.CONVERT_TO_ASSERTION_ERROR).toImmutable(); + this.checkIfConfiguration = MutableConfiguration.from(configuration). + throwOnFailure(false).toImmutable(); + } + + /** + * @returns the application configuration + */ + public getScope(): ApplicationScope + { + return this.scope; + } + + public getConfiguration(): Configuration + { + return this.requireThatConfiguration; + } + + /** + * Returns the configuration for `assertThat` factory methods. + * + * @returns the configuration for `assertThat` factory methods + */ + protected getAssertThatConfiguration(): Configuration + { + return this.assertThatConfiguration; + } + + /** + * Returns the configuration for `checkIf` factory methods. + * + * @returns the configuration for `checkIf` factory methods + */ + protected getCheckIfConfiguration(): Configuration + { + return this.checkIfConfiguration; + } + + /** + * @returns this + */ + protected self(): S + { + return this as unknown as S; + } + + public updateConfiguration(): ConfigurationUpdater; + public updateConfiguration(updater: (configuration: ConfigurationUpdater) => void): S; + public updateConfiguration(updater?: (configuration: ConfigurationUpdater) => void): ConfigurationUpdater | S + { + if (updater === undefined) + { + return new ConfigurationUpdaterImpl(this, + (newConfig: Configuration) => this.setConfiguration(newConfig)); + } + const updatableConfiguration = this.updateConfiguration(); + updater(updatableConfiguration); + updatableConfiguration.close(); + return this.self(); + } + + /** + * Returns a configuration updater that sets the validator factory's configuration. + * + * @param setConfiguration - a method that sets the validator factory's configuration + * @returns the configuration updater + * @throws TypeError if `setConfiguration` is `null` + */ + public updateAndSetConfiguration(setConfiguration: (configuration: Configuration) => void) + { + return new ConfigurationUpdaterImpl(this, setConfiguration); + } + + /** + * Set the configuration used by new validators. + * + * @param configuration - the updated configuration + * @throws TypeError if `configuration` is null + */ + public setConfiguration(configuration: Configuration): void + { + requireThatValueIsNotNull(configuration, "configuration"); + this.requireThatConfiguration = configuration; + this.assertThatConfiguration = MutableConfiguration.from(configuration). + errorTransformer(AbstractValidators.CONVERT_TO_ASSERTION_ERROR).toImmutable(); + this.checkIfConfiguration = MutableConfiguration.from(configuration). + throwOnFailure(false).toImmutable(); + } + + + public getContext(): Map + { + return new Map(this.context); + } + + public getGlobalConfiguration(): GlobalConfiguration + { + return this.scope.getGlobalConfiguration(); + } + + abstract copy(): S; + + abstract removeContext(name: string): S; + + abstract withContext(value: unknown, name: string): S; +} + +export {AbstractValidators}; \ No newline at end of file diff --git a/src/internal/validator/ArrayValidatorImpl.mts b/src/internal/validator/ArrayValidatorImpl.mts new file mode 100644 index 0000000..e439028 --- /dev/null +++ b/src/internal/validator/ArrayValidatorImpl.mts @@ -0,0 +1,70 @@ +import { + type Configuration, + type ValidationFailure, + type ApplicationScope, + type ArrayValidator, + AbstractCollectionValidator, + ValidationTarget, + Pluralizer, + collectionIsSorted, + type UnsignedNumberValidator, + ObjectSizeValidatorImpl, +} from "../internal.mjs"; +import isEqual from "lodash.isequal"; + +/** + * Default implementation of `ArrayValidator`. + */ +class ArrayValidatorImpl extends AbstractCollectionValidator, E[], E> + implements ArrayValidator +{ + /** + * @param scope - the application configuration + * @param configuration - the validator configuration + * @param name - the name of the value + * @param value - the value + * @param pluralizer - the type of items in the array + * @param context - the contextual information set by a parent validator or the user + * @param failures - the list of validation failures + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace, or is empty + * @throws AssertionError if `scope`, `configuration`, `value`, `context` or `failures` are null + */ + public constructor(scope: ApplicationScope, configuration: Configuration, name: string, + value: ValidationTarget, pluralizer: Pluralizer, context: Map, + failures: ValidationFailure[]) + { + super(scope, configuration, name, value, pluralizer, context, failures); + } + + isSorted(comparator: (first: unknown, second: unknown) => number): ArrayValidator + { + if (this.value.isNull()) + this.onNull(); + const sorted = this.value.map(v => + { + const valueAsList = this.collectionAsArray(v); + const sortedList = [...valueAsList]; + sortedList.sort(comparator); + if (isEqual(valueAsList, sortedList)) + return null; + return sortedList; + }).or(null); + if (sorted !== null) + { + this.addRangeError( + collectionIsSorted(this, sorted).toString()); + } + return this.self(); + } + + size(): UnsignedNumberValidator + { + if (this.value.isNull()) + this.onNull(); + return new ObjectSizeValidatorImpl(this.scope, this._configuration, this, this.name + ".size()", + this.value.undefinedOrNullToInvalid().map(v => v.length), this.pluralizer, this.context, this.failures); + } +} + +export {ArrayValidatorImpl}; \ No newline at end of file diff --git a/src/internal/validator/BooleanValidatorImpl.mts b/src/internal/validator/BooleanValidatorImpl.mts new file mode 100644 index 0000000..20e8023 --- /dev/null +++ b/src/internal/validator/BooleanValidatorImpl.mts @@ -0,0 +1,61 @@ +import { + type BooleanValidator, + type Configuration, + type ValidationFailure, + type ApplicationScope, + isFalseFailed, + isTrueFailed, + AbstractValidator, + ValidationTarget +} from "../internal.mjs"; + +/** + * Default implementation of `BooleanValidator`. + */ +class BooleanValidatorImpl extends AbstractValidator + implements BooleanValidator +{ + /** + * @param scope - the application configuration + * @param configuration - the validator configuration + * @param name - the name of the value + * @param value - the value + * @param context - the contextual information set by a parent validator or the user + * @param failures - the list of validation failures + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace, or is empty + * @throws AssertionError if `scope`, `configuration`, `value`, `context` or `failures` are null + */ + public constructor(scope: ApplicationScope, configuration: Configuration, name: string, + value: ValidationTarget, context: Map, + failures: ValidationFailure[]) + { + super(scope, configuration, name, value, context, failures); + } + + public isTrue() + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && v)) + { + this.addRangeError( + isTrueFailed(this).toString()); + } + return this; + } + + public isFalse() + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && !v)) + { + this.addRangeError( + isFalseFailed(this).toString()); + } + return this; + } +} + +export {BooleanValidatorImpl}; \ No newline at end of file diff --git a/src/internal/validator/ClassValidatorImpl.mts b/src/internal/validator/ClassValidatorImpl.mts new file mode 100644 index 0000000..a24a6c9 --- /dev/null +++ b/src/internal/validator/ClassValidatorImpl.mts @@ -0,0 +1,58 @@ +import { + type ClassConstructor, + type ClassValidator, + AbstractValidator, + classIsSupertypeOf, + classIsSubtypeOf, + classIsPrimitive, + Type +} from "../internal.mjs"; + +/** + * Default implementation of ClassValidator. + */ +class ClassValidatorImpl extends AbstractValidator, ClassConstructor> + implements ClassValidator +{ + isPrimitive(): ClassValidator + { + if (this.value.validationFailed(v => Type.of(v).isPrimitive())) + { + this.addRangeError( + classIsPrimitive(this).toString()); + } + return this.self(); + } + + isSupertypeOf(type: ClassConstructor): ClassValidator + { + if (this.value.validationFailed(v => + { + const child = Type.of(v); + const parent = Type.of(type); + return child.isSubtypeOf(parent); + })) + { + this.addRangeError( + classIsSupertypeOf(this, type).toString()); + } + return this.self(); + } + + isSubtypeOf(type: ClassConstructor): ClassValidator + { + if (this.value.validationFailed(v => + { + const child = Type.of(type); + const parent = Type.of(v); + return child.isSubtypeOf(parent); + })) + { + this.addRangeError( + classIsSubtypeOf(this, type).toString()); + } + return this.self(); + } +} + +export {ClassValidatorImpl}; \ No newline at end of file diff --git a/src/internal/validator/ErrorBuilder.mts b/src/internal/validator/ErrorBuilder.mts new file mode 100644 index 0000000..48c9f20 --- /dev/null +++ b/src/internal/validator/ErrorBuilder.mts @@ -0,0 +1,21 @@ +/** + * Creates a new error. + * + * @param message - a message that explains what went wrong + * @returns a new error + */ +type ErrorBuilder = (message: string) => Error; + +/** + * @param value - a value + * @returns true if the value has the number of parameters expected by `ErrorBuilder` + */ +function isErrorBuilder(value: unknown): value is ErrorBuilder +{ + return typeof (value) === "function" && value.length === 1; +} + +export { + type ErrorBuilder, + isErrorBuilder +}; \ No newline at end of file diff --git a/src/internal/validator/JavascriptValidatorsImpl.mts b/src/internal/validator/JavascriptValidatorsImpl.mts new file mode 100644 index 0000000..832845a --- /dev/null +++ b/src/internal/validator/JavascriptValidatorsImpl.mts @@ -0,0 +1,239 @@ +import { + BooleanValidatorImpl, + StringValidatorImpl, + NumberValidatorImpl, + SetValidatorImpl, + MapValidatorImpl, + ObjectValidatorImpl, + Configuration, + AbstractValidators, + type BooleanValidator, + type StringValidator, + type NumberValidator, + type SetValidator, + type ObjectValidator, + type MapValidator, + TypeCategory, + type ArrayValidator, + ArrayValidatorImpl, + type ApplicationScope, + MainApplicationScope, + verifyName, + type ConfigurationUpdater, + JavascriptValidators, + Type, + requireThatType, + ValidationTarget, + Pluralizer +} from "../internal.mjs"; + +const typedocWorkaround: null | ConfigurationUpdater = null; +// noinspection PointlessBooleanExpressionJS +if (typedocWorkaround !== null) + console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); + + +/** + * The default implementation of JavascriptValidators. + */ +class JavascriptValidatorsImpl extends AbstractValidators + implements JavascriptValidators +{ + private static readonly DEFAULT_NAME = "value"; + /** + * A validator factory that creates validators to check the arguments of validation methods. + */ + public static readonly INTERNAL = new JavascriptValidatorsImpl( + MainApplicationScope.INSTANCE, Configuration.DEFAULT); + + /** + * Creates a new instance of this validator with an independent configuration. + * + * @param scope - the application configuration + * @param configuration - the configuration to use for new validators + * @throws TypeError if any of the arguments are `undefined` or `null` + */ + public constructor(scope: ApplicationScope, configuration: Configuration); + /** + * Creates a new instance of this validator with an independent configuration. + * + * @param scope - the application configuration + * @param other - the factory to copy + * @throws TypeError if any of the arguments are `undefined` or `null` + */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + public constructor(scope: ApplicationScope, other: JavascriptValidatorsImpl); + public constructor(scope: ApplicationScope, configurationOrOther: Configuration | JavascriptValidatorsImpl) + { + super(scope, JavascriptValidatorsImpl.getConfiguration(configurationOrOther)); + if (configurationOrOther instanceof JavascriptValidatorsImpl) + { + for (const entry of configurationOrOther.context) + this.context.set(entry[0], entry[1]); + } + } + + /** + * @param configurationOrOther - the configuration to use for new validators or the factory to copy + * @returns the configuration to use for new validators + */ + private static getConfiguration(configurationOrOther: Configuration | JavascriptValidatorsImpl) + { + if (configurationOrOther instanceof Configuration) + return configurationOrOther; + return configurationOrOther.getConfiguration(); + } + + /** + * Validates the state of a value, throwing errors immediately on validation failure. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the array or set + * @typeParam K - the type of keys in the map + * @typeParam V - the type of values in the map + * @param value - the value + * @param name - the name of the value + * @returns a verifier + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public requireThat(value: number, name: string): NumberValidator; + public requireThat(value: boolean, name: string): BooleanValidator; + public requireThat(value: E[], name: string): ArrayValidator; + public requireThat(value: Set, name: string): SetValidator; + public requireThat(value: Map, name: string): MapValidator; + public requireThat(value: string, name: string): StringValidator; + public requireThat(value: T, name: string): ObjectValidator; + public requireThat(value: T, name: string): NumberValidator | BooleanValidator | + ArrayValidator | SetValidator | MapValidator | StringValidator | ObjectValidator + { + verifyName(name, "name"); + return this.newInstance(value, name, this.getConfiguration()); + } + + /** + * Validates the state of a value, throwing `AssertionError` immediately on validation failure. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the array or set + * @typeParam K - the type of keys in the map + * @typeParam V - the type of values in the map + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public assertThat(value: number, name?: string): NumberValidator; + public assertThat(value: boolean, name?: string): BooleanValidator; + public assertThat(value: E[], name?: string): ArrayValidator; + public assertThat(value: Set, name?: string): SetValidator; + public assertThat(value: Map, name?: string): MapValidator; + public assertThat(value: string, name?: string): StringValidator; + public assertThat(value: T, name?: string): ObjectValidator; + public assertThat(value: T, name?: string): NumberValidator | BooleanValidator | + ArrayValidator | SetValidator | MapValidator | StringValidator | ObjectValidator + { + return this.newInstance(value, name, this.getAssertThatConfiguration()); + } + + /** + * Validates the state of a value, capturing errors on validation failure rather than throwing them + * immediately. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the array or set + * @typeParam K - the type of keys in the map + * @typeParam V - the type of values in the map + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public checkIf(value: number, name?: string): NumberValidator; + public checkIf(value: boolean, name?: string): BooleanValidator; + public checkIf(value: E[], name?: string): ArrayValidator; + public checkIf(value: Set, name?: string): SetValidator; + public checkIf(value: Map, name?: string): MapValidator; + public checkIf(value: string, name?: string): StringValidator; + public checkIf(value: T, name?: string): ObjectValidator; + public checkIf(value: T, name?: string): NumberValidator | BooleanValidator | + ArrayValidator | SetValidator | MapValidator | StringValidator | ObjectValidator + { + return this.newInstance(value, name, this.getCheckIfConfiguration()); + } + + public newInstance(value: T, name: string | undefined, configuration: Configuration): + NumberValidator | BooleanValidator | ArrayValidator | SetValidator | MapValidator | + StringValidator | ObjectValidator + { + if (name === undefined) + name = JavascriptValidatorsImpl.DEFAULT_NAME; + + const typeOfValue = Type.of(value); + switch (typeOfValue.category) + { + case TypeCategory.BOOLEAN: + { + return new BooleanValidatorImpl(this.scope, configuration, name, + ValidationTarget.valid(value as boolean), new Map(), []); + } + case TypeCategory.STRING: + { + return new StringValidatorImpl(this.scope, configuration, name, + ValidationTarget.valid(value as string), new Map(), []); + } + case TypeCategory.NUMBER: + { + return new NumberValidatorImpl(this.scope, configuration, name, + ValidationTarget.valid(value as number), new Map(), []); + } + case TypeCategory.ARRAY: + { + return new ArrayValidatorImpl(this.scope, configuration, name, + ValidationTarget.valid(value as E[]), Pluralizer.ELEMENT, new Map(), []); + } + case TypeCategory.CLASS: + { + switch (typeOfValue.name) + { + case "Set": + { + return new SetValidatorImpl(this.scope, configuration, name, + ValidationTarget.valid(value as Set), Pluralizer.ELEMENT, new Map(), []); + } + case "Map": + { + return new MapValidatorImpl(this.scope, configuration, name, + ValidationTarget.valid(value as Map), new Map(), []); + } + } + break; + } + } + return new ObjectValidatorImpl(this.scope, configuration, name, ValidationTarget.valid(value), + new Map(), []); + } + + public copy() + { + return new JavascriptValidatorsImpl(this.scope, this); + } + + public withContext(value: unknown, name: string) + { + requireThatType(name, "name", Type.STRING); + this.context.set(name, value); + return this; + } + + public removeContext(name: string) + { + this.context.delete(name); + return this; + } +} + +export {JavascriptValidatorsImpl}; \ No newline at end of file diff --git a/src/internal/validator/MapValidatorImpl.mts b/src/internal/validator/MapValidatorImpl.mts new file mode 100644 index 0000000..c922bb1 --- /dev/null +++ b/src/internal/validator/MapValidatorImpl.mts @@ -0,0 +1,111 @@ +import { + type Configuration, + type MapValidator, + type ValidationFailure, + ObjectSizeValidatorImpl, + Pluralizer, + type ApplicationScope, + ArrayValidatorImpl, + AbstractValidator, + ValidationTarget, + objectIsEmpty, + objectIsNotEmpty +} from "../internal.mjs"; + +/** + * Default implementation of `MapValidator`. + * + * @typeParam K - the type of keys in the map + * @typeParam V - the type of values in the map + */ +class MapValidatorImpl extends AbstractValidator, Map> + implements MapValidator +{ + /** + * Creates a new MapValidatorImpl. + * + * @param scope - the application configuration + * @param configuration - the validator configuration + * @param name - the name of the value + * @param value - the value being validated + * @param context - the contextual information set by the user + * @param failures - the list of validation failures + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace, or is empty + * @throws AssertionError if `scope`, `configuration, `value`, `context or `failures` are null + */ + public constructor(scope: ApplicationScope, configuration: Configuration, name: string, + value: ValidationTarget>, context: Map, + failures: ValidationFailure[]) + { + super(scope, configuration, name, value, context, failures); + } + + isEmpty() + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && v.size === 0)) + { + this.addRangeError( + objectIsEmpty(this).toString()); + } + return this; + } + + isNotEmpty() + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && v.size !== 0)) + { + this.addRangeError( + objectIsNotEmpty(this).toString()); + } + return this; + } + + keys() + { + if (this.value.isNull()) + this.onNull(); + const undefinedOrNullToInvalid = this.value.undefinedOrNullToInvalid(); + const newValidator = new ArrayValidatorImpl(this.scope, this._configuration, this.name + ".keys()", + undefinedOrNullToInvalid.map(v => [...v.keys()]), Pluralizer.KEY, this.context, this.failures); + undefinedOrNullToInvalid.ifValid(v => newValidator.withContext(v, this.name)); + return newValidator; + } + + values() + { + if (this.value.isNull()) + this.onNull(); + const undefinedOrNullToInvalid = this.value.undefinedOrNullToInvalid(); + const newValidator = new ArrayValidatorImpl(this.scope, this._configuration, this.name + ".values()", + undefinedOrNullToInvalid.map(v => [...v.values()]), Pluralizer.VALUE, this.context, this.failures); + undefinedOrNullToInvalid.ifValid(v => newValidator.withContext(v, this.name)); + return newValidator; + } + + entries() + { + if (this.value.isNull()) + this.onNull(); + const undefinedOrNullToInvalid = this.value.undefinedOrNullToInvalid(); + const newValidator = new ArrayValidatorImpl(this.scope, this._configuration, this.name + ".entries()", + undefinedOrNullToInvalid.map(v => [...v.entries()]), Pluralizer.ENTRY, this.context, this.failures); + undefinedOrNullToInvalid.ifValid(v => newValidator.withContext(v, this.name)); + return newValidator; + } + + size() + { + if (this.value.isNull()) + this.onNull(); + return new ObjectSizeValidatorImpl(this.scope, this._configuration, this, this.name + ".size()", + this.value.undefinedOrNullToInvalid().map(v => v.size), Pluralizer.ELEMENT, this.context, + this.failures); + } +} + +export {MapValidatorImpl}; \ No newline at end of file diff --git a/src/internal/validator/Maps.mts b/src/internal/validator/Maps.mts new file mode 100644 index 0000000..afff150 --- /dev/null +++ b/src/internal/validator/Maps.mts @@ -0,0 +1,31 @@ +/** + * Appends to a `string` map value. + * + * @param map - a map + * @param key - the map key + * @param value - the value to append + */ +function appendToValue(map: Map, key: K, value: string) +{ + const oldValue = map.get(key); + + if (oldValue === undefined) + map.set(key, value); + else + map.set(key, oldValue + value); +} + +/** + * @param map - a map + * @returns the map sorted by its keys + */ +function sortByKeys(map: Map) +{ + // https://stackoverflow.com/a/31159284/14731 + return new Map([...map.entries()].sort()); +} + +export { + appendToValue, + sortByKeys +}; \ No newline at end of file diff --git a/src/internal/validator/MutableConfiguration.mts b/src/internal/validator/MutableConfiguration.mts new file mode 100644 index 0000000..7ca6eff --- /dev/null +++ b/src/internal/validator/MutableConfiguration.mts @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2019 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +import { + internalValueToString, + MutableStringMappers, + Configuration +} from "../internal.mjs"; + +/** + * Determines the behavior of a validator. + */ +class MutableConfiguration +{ + private readonly _stringMappers: MutableStringMappers; + private _allowDiff: boolean; + private _recordStacktrace: boolean; + private _throwOnFailure: boolean; + private _errorTransformer: (error: Error) => Error; + + /** + * Creates a new configuration. + * + * @param allowDiff - `true` if error messages may include a diff that compares actual and + * expected values + * @param stringMappers - the configuration used to map contextual values to a String + * @param recordStacktrace - `true` if the error stack trace must be recorded when a validation failure + * occurs. If `false`, the error type remains the same, but the stack trace points to the invocation + * of `elseGetError()`. Users who only plan to + * {@link ValidationFailures.getMessages|list of failure messages} instead of retrieving an error + * may see a performance improvement if this value is set to `false`. + * @param throwOnFailure - `true` if an error is thrown on validation failure. + * @param errorTransformer - a function that transforms the validation error into a suitable runtime + * error or error + * @throws TypeError if any of the arguments are `undefined` or `null` + */ + private constructor(allowDiff: boolean, stringMappers: MutableStringMappers, recordStacktrace: boolean, + throwOnFailure: boolean, errorTransformer: (error: Error) => Error) + { + this._allowDiff = allowDiff; + this._stringMappers = stringMappers; + this._recordStacktrace = recordStacktrace; + this._throwOnFailure = throwOnFailure; + this._errorTransformer = errorTransformer; + } + + /** + * @param configuration - the immutable configuration + * @returns a mutable copy of the configuration + */ + public static from(configuration: Configuration): MutableConfiguration + { + return new MutableConfiguration(configuration.allowDiff(), + MutableStringMappers.from(configuration.stringMappers()), configuration.recordStacktrace(), + configuration.throwOnFailure(), configuration.errorTransformer()); + } + + /** + * Returns an immutable copy of this configuration. + * + * @returns an immutable copy of this configuration + */ + public toImmutable() + { + return new Configuration(this._allowDiff, this._stringMappers.toImmutable(), this._recordStacktrace, + this._throwOnFailure, this._errorTransformer); + } + + /** + * Returns `true` if error messages may include a diff that compares actual and expected values. + * + * @returns `true` by default + */ + public allowDiff(): boolean; + /** + * Specifies whether error messages may include a diff that compares actual and expected values. + * + * @param mayDiff - `true` if error messages may include a diff, `false` otherwise + * @returns this + */ + public allowDiff(mayDiff: boolean): MutableConfiguration; + public allowDiff(mayDiff?: boolean): boolean | MutableConfiguration + { + if (mayDiff === undefined) + return this._allowDiff; + this._allowDiff = mayDiff; + return this; + } + + /** + * Returns the configuration used to map contextual values to a String. Supports common types such as + * arrays, numbers, collections, maps, paths and errors. + * + * @returns a function that takes an object and returns the `string` representation of the object + */ + public stringMappers() + { + return this._stringMappers; + } + + /** + * Returns `true` if error stack traces should reference the code that triggers a validation + * failure. When set to `false`, the error type remains unchanged, but the stack trace location is + * undefined. Users who only plan to {@link ValidationFailures.getMessages|list of failure messages} + * instead of errors may experience a performance improvement if this value is set to `false`. + * + * @returns `true` if errors must be recorded when a validation failure occurs + */ + public recordStacktrace(): boolean; + /** + * Specifies whether error stack traces should reference the code that triggers a validation failure. + * When set to `false`, the error type remains unchanged, but the stack trace location is + * undefined. Users who only plan to {@link ValidationFailures.getMessages|list of failure messages} + * instead of errors may experience a performance improvement if this value is set to `false`. + * + * @param recordStacktrace - `true` if errors must be recorded when a validation failure occurs + * @returns this + */ + public recordStacktrace(recordStacktrace: boolean): MutableConfiguration; + public recordStacktrace(recordStacktrace?: boolean): boolean | MutableConfiguration + { + if (recordStacktrace === undefined) + return this._recordStacktrace; + this._recordStacktrace = recordStacktrace; + return this; + } + + /** + * Returns `true` if an error is thrown on validation failure. + * + * @returns `true` if an error is thrown on validation failure + */ + public throwOnFailure(): boolean; + /** + * Specifies whether an error is thrown on validation failure. + * + * @param throwOnFailure - `true` if an error is thrown on validation failure + * @returns this + */ + public throwOnFailure(throwOnFailure: boolean): MutableConfiguration; + public throwOnFailure(throwOnFailure?: boolean): boolean | MutableConfiguration + { + if (throwOnFailure === undefined) + return this._throwOnFailure; + this._throwOnFailure = throwOnFailure; + return this; + } + + /** + * Returns a function that transforms the validation error into a suitable runtime error or error. + * The input and output of the function must be subclasses of `RuntimeError` or + * `Error`. If the function returns `null` the input error will be thrown. + * + * @returns a function that transforms the validation error + */ + public errorTransformer(): (error: Error) => Error; + /** + * Transform the validation error into a suitable runtime error or error. If the function returns + * `undefined` or `null` then the input error will be thrown. + * + * @param errorTransformer - a function that transforms the validation error + * @throws TypeError if `errorTransformer` is `undefined` or `null` + * @returns this + */ + public errorTransformer(errorTransformer: (error: Error) => Error): MutableConfiguration; + public errorTransformer(errorTransformer?: (error: Error) => Error): + ((error: Error) => Error) | MutableConfiguration + { + if (errorTransformer === undefined) + return this._errorTransformer; + this._errorTransformer = errorTransformer; + return this; + } + + public toString() + { + return `Configuration[allowDiff=${this._allowDiff}, stringMappers=\ +${internalValueToString(this._stringMappers)}, recordStacktrace: ${this._recordStacktrace}, \ +throwOnFailure: ${this._throwOnFailure}]`; + } +} + +export {MutableConfiguration}; \ No newline at end of file diff --git a/src/internal/validator/NumberValidatorImpl.mts b/src/internal/validator/NumberValidatorImpl.mts new file mode 100644 index 0000000..5921908 --- /dev/null +++ b/src/internal/validator/NumberValidatorImpl.mts @@ -0,0 +1,370 @@ +import { + type Configuration, + type NumberValidator, + type ValidationFailure, + Type, + type ApplicationScope, + requireThatType, + AbstractValidator, + JavascriptValidatorsImpl, + comparableIsGreaterThan, + comparableIsLessThan, + comparableIsLessThanOrEqualTo, + comparableIsGreaterThanOrEqualTo, + numberIsNegative, + numberIsZero, + numberIsNotZero, + numberIsPositive, + numberIsNotPositive, + numberIsFinite, + numberIsMultipleOf, + numberIsNotMultipleOf, + isBetweenFailed, + numberIsInfinite, + numberIsNotNumber, + numberIsNumber, + ValidationTarget +} from "../internal.mjs"; + +/** + * Default implementation of `NumberValidator`. + */ +class NumberValidatorImpl extends AbstractValidator + implements NumberValidator +{ + /** + * @param scope - the application configuration + * @param configuration - the validator configuration + * @param name - the name of the value + * @param value - the value + * @param context - the contextual information set by a parent validator or the user + * @param failures - the list of validation failures + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace, or is empty + * @throws AssertionError if `scope`, `configuration`, `value`, `context` or `failures` are null + */ + public constructor(scope: ApplicationScope, configuration: Configuration, name: string, + value: ValidationTarget, context: Map, + failures: ValidationFailure[]) + { + super(scope, configuration, name, value, context, failures); + } + + isNegative(): NumberValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && v < 0)) + { + this.addRangeError( + numberIsNegative(this).toString()); + } + return this; + } + + isNotNegative(): NumberValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && !(v < 0))) + { + this.addRangeError( + numberIsNegative(this).toString()); + } + return this; + } + + isZero(): NumberValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && v === 0)) + { + this.addRangeError( + numberIsZero(this).toString()); + } + return this; + } + + isNotZero(): NumberValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && !(v === 0))) + { + this.addRangeError( + numberIsNotZero(this).toString()); + } + return this; + } + + isPositive(): NumberValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && v > 0)) + { + this.addRangeError( + numberIsPositive(this).toString()); + } + return this; + } + + isNotPositive(): NumberValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && !(v > 0))) + { + this.addRangeError( + numberIsNotPositive(this).toString()); + } + return this; + } + + isGreaterThan(value: number): NumberValidator; + isGreaterThan(minimumExclusive: number, name?: string) + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && v > minimumExclusive)) + { + this.addRangeError( + comparableIsGreaterThan(this, name ?? null, minimumExclusive).toString()); + } + return this; + } + + isGreaterThanOrEqualTo(minimumInclusive: number, name?: string): NumberValidator + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && v >= minimumInclusive)) + { + this. + addRangeError( + comparableIsGreaterThanOrEqualTo(this, name ?? null, minimumInclusive).toString()); + } + return this; + } + + isLessThan(maximumExclusive: number, name?: string): NumberValidator + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && v < maximumExclusive)) + { + this. + addRangeError( + comparableIsLessThan(this, name ?? null, maximumExclusive).toString()); + } + return this; + } + + isLessThanOrEqualTo(maximumInclusive: number, name?: string): NumberValidator + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && v <= maximumInclusive)) + { + this. + addRangeError( + comparableIsLessThanOrEqualTo(this, name ?? null, maximumInclusive).toString()); + } + return this; + } + + isBetween(minimumInclusive: number, maximumExclusive: number): NumberValidator; + isBetween(minimum: number, minimumIsInclusive: boolean, maximum: number, + maximumIsInclusive: boolean): NumberValidator; + isBetween(minimum: number, maximumExclusiveOrMinimumIsInclusive: number | boolean, maximum?: number, + maximumInclusive?: boolean): NumberValidator + { + const normalized = NumberValidatorImpl.normalizeIsBetweenParameters(minimum, + maximumExclusiveOrMinimumIsInclusive, maximum, maximumInclusive); + + const internalValidators = JavascriptValidatorsImpl.INTERNAL; + internalValidators.requireThat(normalized.minimum, "minimum"). + isLessThanOrEqualTo(normalized.maximum, "maximum"); + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => + { + if (normalized.minimumIsInclusive) + { + if (v < normalized.minimum) + return false; + } + else if (v <= normalized.minimum) + return false; + if (normalized.maximumIsInclusive) + return v <= normalized.maximum; + return v < normalized.maximum; + })) + { + this.addRangeError( + isBetweenFailed(this, normalized.minimum, normalized.minimumIsInclusive, normalized.maximum, + normalized.maximumIsInclusive).toString()); + } + return this.self(); + } + + /** + * Normalize the parameters of isBetween(). + * + * @param minimum - the lower bound of the range + * @param maximumExclusiveOrMinimumIsInclusive - the upper bound of the range, or `true` if the lower bound + * of the range is inclusive + * @param maximum - the upper bound of the range + * @param maximumIsInclusive - `true` if the upper bound of the range is inclusive + */ + public static normalizeIsBetweenParameters(minimum: number, + maximumExclusiveOrMinimumIsInclusive: number | boolean, + maximum?: number, maximumIsInclusive?: boolean): + { minimum: number, minimumIsInclusive: boolean, maximum: number, maximumIsInclusive: boolean } + { + if (maximum === undefined) + { + if (maximumIsInclusive !== undefined) + throw new TypeError("maximum may not be undefined"); + } + else if (maximumIsInclusive === undefined) + throw new TypeError("maximumIsInclusive may not be undefined"); + + if (maximum === undefined) + { + requireThatType(minimum, "minimum", Type.NUMBER); + requireThatType(maximumExclusiveOrMinimumIsInclusive, "maximumExclusiveOrMinimumIsInclusive", + Type.NUMBER); + return { + minimum, + minimumIsInclusive: true, + maximum: maximumExclusiveOrMinimumIsInclusive as number, + maximumIsInclusive: false + }; + } + requireThatType(minimum, "minimum", Type.NUMBER); + requireThatType(maximumExclusiveOrMinimumIsInclusive, "maximumExclusiveOrMinimumIsInclusive", + Type.BOOLEAN); + requireThatType(maximum, "maximum", Type.NUMBER); + requireThatType(maximumIsInclusive, "maximumIsInclusive", Type.BOOLEAN); + return { + minimum, + minimumIsInclusive: maximumExclusiveOrMinimumIsInclusive as boolean, + maximum, + maximumIsInclusive: maximumIsInclusive as boolean + }; + } + + isFinite(): NumberValidator + { + if (this.value.isNull()) + this.onNull(); + // See http://stackoverflow.com/a/1830844/14731 + if (this.value.validationFailed(v => v != null && Number.isFinite(v))) + { + this. + addRangeError( + numberIsFinite(this).toString()); + } + return this; + } + + isInfinite(): NumberValidator + { + if (this.value.isNull()) + this.onNull(); + // See http://stackoverflow.com/a/1830844/14731 + if (this.value.validationFailed(v => v != null && !Number.isFinite(v))) + { + this. + addRangeError( + numberIsInfinite(this).toString()); + } + return this; + } + + public isMultipleOf(factor: number): NumberValidator; + public isMultipleOf(factor: number, name?: string) + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && NumberValidatorImpl.valueIsMultipleOf(v, factor))) + { + this. + addRangeError( + numberIsMultipleOf(this, name ?? null, factor).toString()); + } + return this; + } + + /** + * @param value - the value + * @param factor - the number that the value is being divided by + * @returns true if the value is a multiple of `factor` + */ + public static valueIsMultipleOf(value: number, factor: number) + { + return factor !== 0 && (value === 0 || (value % factor === 0)); + } + + public isNotMultipleOf(factor: number): NumberValidator; + public isNotMultipleOf(factor: number, name?: string) + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && !NumberValidatorImpl.valueIsMultipleOf(v, factor))) + { + this. + addRangeError( + numberIsNotMultipleOf(this, name ?? null, factor).toString()); + } + return this; + } + + isNumber(): NumberValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && Number.isNaN(v))) + { + this. + addRangeError( + numberIsNumber(this).toString()); + } + return this; + } + + isNotNumber(): NumberValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && !Number.isNaN(v))) + { + this. + addRangeError( + numberIsNotNumber(this).toString()); + } + return this; + } +} + +export {NumberValidatorImpl}; \ No newline at end of file diff --git a/src/internal/validator/ObjectSizeValidatorImpl.mts b/src/internal/validator/ObjectSizeValidatorImpl.mts new file mode 100644 index 0000000..ff1aa45 --- /dev/null +++ b/src/internal/validator/ObjectSizeValidatorImpl.mts @@ -0,0 +1,291 @@ +import { + type Configuration, + type Pluralizer, + type ValidationFailure, + NumberValidatorImpl, + type ApplicationScope, + AbstractValidator, + type UnsignedNumberValidator, + numberIsMultipleOf, + numberIsNotMultipleOf, + ValidationTarget, + JavascriptValidatorsImpl, + numberIsFinite, + numberIsInfinite, + numberIsNumber, + numberIsNotNumber, + collectionContainsSize, + objectIsNotEmpty, + objectIsEmpty, + collectionSizeIsBetween +} from "../internal.mjs"; +import {requireThatValueIsDefined} from "./Objects.mjs"; + +/** + * Validates the state of an object's size. + */ +class ObjectSizeValidatorImpl extends AbstractValidator + implements UnsignedNumberValidator +{ + private readonly objectValidator: AbstractValidator; + private readonly pluralizer: Pluralizer; + + /** + * @param scope - the application configuration + * @param configuration - the validator configuration + * @param objectValidator - the object's validator + * @param sizeName - the name of the object's size + * @param size - the object's size + * @param pluralizer - the type of elements in the object + * @param context - the contextual information set by a parent validator or the user + * @param failures - the list of validation failures + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace, or is empty + * @throws AssertionError if `scope`, `configuration`, `value`, `context` or `failures` are null + */ + public constructor(scope: ApplicationScope, configuration: Configuration, + objectValidator: AbstractValidator, sizeName: string, + size: ValidationTarget, pluralizer: Pluralizer, context: Map, + failures: ValidationFailure[]) + { + super(scope, configuration, sizeName, size, context, failures); + + requireThatValueIsDefined(objectValidator, "objectValidator"); + requireThatValueIsDefined(pluralizer, "pluralizer"); + + this.objectValidator = objectValidator; + this.pluralizer = pluralizer; + } + + public isZero(): UnsignedNumberValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(value => value == 0)) + this.addRangeError(objectIsEmpty(this.objectValidator).toString()); + return this.self(); + } + + public isNotZero(): UnsignedNumberValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v !== null && !(v == 0))) + this.addRangeError(objectIsNotEmpty(this.objectValidator).toString()); + return this.self(); + } + + public isPositive() + { + return this.isNotZero(); + } + + public isNotPositive() + { + return this.isZero(); + } + + public isLessThan(maximumExclusive: number): UnsignedNumberValidator; + public isLessThan(maximumExclusive: number, name?: string): UnsignedNumberValidator + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v !== null && v < maximumExclusive)) + { + this.addRangeError( + collectionContainsSize(this.objectValidator, this.name, this.value.or(null), + "must contain less than", name ?? null, maximumExclusive, this.pluralizer).toString()); + } + return this.self(); + } + + public isLessThanOrEqualTo(maximumInclusive: number): UnsignedNumberValidator; + public isLessThanOrEqualTo(maximumInclusive: number, name?: string) + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v !== null && v <= maximumInclusive)) + { + this.addRangeError( + collectionContainsSize(this.objectValidator, this.name, this.value.or(null), + "may not contain more than", name ?? null, maximumInclusive, this.pluralizer).toString()); + } + return this.self(); + } + + public isGreaterThanOrEqualTo(minimumInclusive: number): UnsignedNumberValidator; + public isGreaterThanOrEqualTo(minimumInclusive: number, name?: string) + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(value => value >= minimumInclusive)) + { + this.addRangeError( + collectionContainsSize(this.objectValidator, this.name, this.value.or(null), + "must contain at least", name ?? null, minimumInclusive, this.pluralizer).toString()); + } + return this.self(); + } + + public isGreaterThan(minimumExclusive: number): UnsignedNumberValidator; + public isGreaterThan(minimumExclusive: number, name?: string): UnsignedNumberValidator + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(value => value >= minimumExclusive)) + { + this.addRangeError( + collectionContainsSize(this.objectValidator, this.name, this.value.or(null), + "must contain more than", name ?? null, minimumExclusive, this.pluralizer).toString()); + } + return this.self(); + } + + public isBetween(minimumInclusive: number, maximumExclusive: number): UnsignedNumberValidator; + public isBetween(minimum: number, minimumIsInclusive: boolean, maximum: number, + maximumIsInclusive: boolean): UnsignedNumberValidator; + public isBetween(minimum: number, maximumExclusiveOrMinimumIsInclusive: number | boolean, + maximum?: number, maximumIsInclusive?: boolean) + { + const normalized = NumberValidatorImpl.normalizeIsBetweenParameters( + minimum, maximumExclusiveOrMinimumIsInclusive, maximum, maximumIsInclusive); + + const internalValidators = JavascriptValidatorsImpl.INTERNAL; + internalValidators.requireThat(normalized.minimum, "minimum"). + isLessThanOrEqualTo(normalized.maximum, "maximum"); + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v !== null && + ObjectSizeValidatorImpl.inBounds(v, normalized.minimum, normalized.minimumIsInclusive, + normalized.maximum, normalized.maximumIsInclusive))) + { + this.addRangeError( + collectionSizeIsBetween(this, this.name, this.value.or(null), normalized.minimum, + normalized.minimumIsInclusive, normalized.maximum, normalized.maximumIsInclusive, this.pluralizer). + toString()); + } + return this.self(); + } + + /** + * @param value - the value being validated + * @param minimum - the lower bound of the range + * @param minimumIsInclusive - `true` if the lower bound of the range is inclusive + * @param maximum - the upper bound of the range + * @param maximumIsInclusive - `true` if the upper bound of the range is inclusive + * @returns `true` if the value is in bounds; false otherwise + */ + private static inBounds(value: number, minimum: number, minimumIsInclusive: boolean, maximum: number, + maximumIsInclusive: boolean) + { + if (minimumIsInclusive) + { + if (value < minimum) + return false; + } + else if (value <= minimum) + return false; + if (maximumIsInclusive) + return value <= maximum; + return value < maximum; + } + + public isMultipleOf(factor: number): UnsignedNumberValidator; + public isMultipleOf(factor: number, name?: string) + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v !== null && NumberValidatorImpl.valueIsMultipleOf(v, factor))) + { + { + const messageBuilder = numberIsMultipleOf(this, name ?? null, factor); + this.objectValidator.value.ifValid(v => + messageBuilder.withContext(v, this.objectValidator.getName())); + this.addRangeError(messageBuilder.toString()); + } + } + return this.self(); + } + + public isNotMultipleOf(factor: number): UnsignedNumberValidator; + public isNotMultipleOf(factor: number, name?: string) + { + if (name !== undefined) + this.requireThatNameIsUnique(name); + + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v !== null && !NumberValidatorImpl.valueIsMultipleOf(v, factor))) + { + const messageBuilder = numberIsNotMultipleOf(this, name ?? null, factor); + this.objectValidator.value.ifValid(v => messageBuilder.withContext(v, this.objectValidator.getName())); + this.addRangeError(messageBuilder.toString()); + } + return this.self(); + } + + public isFinite(): UnsignedNumberValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v !== null && Number.isFinite(v))) + { + this.addRangeError( + numberIsFinite(this).toString()); + } + return this.self(); + } + + public isInfinite(): UnsignedNumberValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v !== null && !Number.isFinite(v) && !Number.isNaN(v))) + { + this.addRangeError( + numberIsInfinite(this).toString()); + } + return this.self(); + } + + isNumber(): UnsignedNumberValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v !== null && !Number.isNaN(v))) + { + this.addRangeError( + numberIsNumber(this).toString()); + } + return this.self(); + } + + isNotNumber(): UnsignedNumberValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v !== null && Number.isNaN(v))) + { + this.addRangeError( + numberIsNotNumber(this).toString()); + } + return this.self(); + } +} + +export {ObjectSizeValidatorImpl}; \ No newline at end of file diff --git a/src/internal/validator/ObjectValidatorImpl.mts b/src/internal/validator/ObjectValidatorImpl.mts new file mode 100644 index 0000000..4e2dc46 --- /dev/null +++ b/src/internal/validator/ObjectValidatorImpl.mts @@ -0,0 +1,38 @@ +import { + type Configuration, + type ObjectValidator, + type ValidationFailure, + type ApplicationScope, + AbstractValidator, + ValidationTarget, + +} from "../internal.mjs"; + +/** + * Default implementation of `BaseValidator`. + * + * @typeParam T - the type the value + */ +class ObjectValidatorImpl + extends AbstractValidator, T> + implements ObjectValidator +{ + /** + * @param scope - the application configuration + * @param configuration - the validator configuration + * @param name - the name of the value + * @param value - the value + * @param context - the contextual information set by a parent validator or the user + * @param failures - the list of validation failures + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace, or is empty + * @throws AssertionError if `scope`, `configuration`, `value`, `context` or `failures` are null + */ + public constructor(scope: ApplicationScope, configuration: Configuration, name: string, + value: ValidationTarget, context: Map, failures: ValidationFailure[]) + { + super(scope, configuration, name, value, context, failures); + } +} + +export {ObjectValidatorImpl}; \ No newline at end of file diff --git a/src/internal/validator/Objects.mts b/src/internal/validator/Objects.mts new file mode 100644 index 0000000..ec57671 --- /dev/null +++ b/src/internal/validator/Objects.mts @@ -0,0 +1,545 @@ +import { + Type, + TypeCategory, + AssertionError +} from "../internal.mjs"; +import isEqual from "lodash.isEqual"; + +type ElementOf = T extends readonly (infer E)[] ? E : (T extends Set ? E : never); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type MapKey = T extends Map ? K : never; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type MapValue = T extends Map ? V : never; + +// Object and all its subclasses, excluding Function which is its superclass. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type ClassConstructor = abstract new (...args: any[]) => NonNullable; +type Comparable = number | string | boolean; +type NonUndefinable = T extends undefined ? never : T; + +/** + * Indicates if an object is an instance of a type. To convert a type to an object, use + * `prototype` such as `Error.prototype`. To convert an object to a type, use + * `constructor` such as `instance.constructor`. + * + * @param child - the child class + * @param parent - the parent class + * @returns `true` if `child` extends `parent`; false if + * `parent` or `child` are `undefined` or `null`; false if `child` does not extend `parent` + */ +function classExtends(child: ClassConstructor, parent: ClassConstructor) +{ + if (child === undefined || child === null || parent === undefined || parent === null) + return false; + // https://stackoverflow.com/a/14486171/14731 + return child.prototype instanceof parent; +} + +/** + * Throws an `Error` if `condition` is false. + * + * @param condition - a condition + * @param error - the type of error to throw (Default: `Error`) + * @param message - the error message to use on failure + * @throws AssertionError if `condition` is false + */ +function assert(condition: boolean, + error: (message?: string) => Error = (message?: string) => new AssertionError(message), + message?: string): + asserts condition +{ + // Will be stripped out using uglify.js option "pure_funcs" + if (!condition) + throw error(message); +} + +/** + * Ensures that an object is defined. + * + * @param value - the value of a parameter + * @param name - the name of the parameter + * @returns `true` + * @throws TypeError if: + *
    + *
  • `name` is not a string
  • + *
  • if `value` is `undefined`
  • + *
+ */ +function requireThatValueIsDefined(value: unknown, name: string) +{ + const type = Type.of(name); + if (type !== Type.STRING) + { + throw new TypeError(`name must be a string. +Actual: ${internalValueToString(name)} +Type : ${internalValueToString(type)}`); + } + if (value === undefined) + throw new TypeError(name + " must be defined"); + return true; +} + +/** + * Ensures that an object is defined and not null. + * + * @param value - the value of a parameter + * @param name - the name of the parameter + * @returns `true` + * @throws TypeError if: + *
    + *
  • `name` is not a string
  • + *
  • if `value` is `undefined` or `null`
  • + *
+ */ +function requireThatValueIsNotNull(value: unknown, name: string) +{ + requireThatValueIsDefined(value, name); + if (value === null) + throw new TypeError(name + " may not be null"); + return true; +} + +/** + * Ensures that an object is defined and not null. + * + * @param value - the value of a parameter + * @param name - the name of the parameter + * @returns `true` + * @throws AssertionError if: + *
    + *
  • `name` is not a string
  • + *
  • if `value` is `undefined` or `null`
  • + *
+ */ +function assertThatValueIsNotNull(value: unknown, name: string): void +{ + try + { + assert(requireThatValueIsNotNull(value, name)); + } + catch (e) + { + if (e instanceof Error) + throw new AssertionError(e.message, {cause: e}); + throw e; + } +} + + +/** + * Requires that an object has the expected type. + * + * @param value - the value of a parameter + * @param name - the name of the parameter + * @param type - `value`'s expected type + * @returns `true` + * @throws TypeError if `value` does not have the expected `type`. If `name` is not a string. + */ +function requireThatType(value: unknown, name: string, type: Type) +{ + const typeOfName = Type.of(name); + if (typeOfName !== Type.STRING) + { + throw new TypeError(`name must be a string. +Actual: ${internalValueToString(name)} +Type : ${typeOfName.toString()}`); + } + + const typeOfValue = Type.of(value); + let matchFound; + if (typeof (type.typeGuard) !== "undefined") + matchFound = type.typeGuard(value); + else + matchFound = isEqual(typeOfValue, type); + if (!matchFound) + { + throw new TypeError(`${name} must be a ${internalValueToString(type)}. +Actual: ${internalValueToString(value)} +Type : ${typeOfValue.toString()}`); + } + return true; +} + +/** + * Requires that a value has the expected type if assertions are enabled. We assume that + * `assert()` will be stripped out at build-time if assertions are disabled. + * + * @param value - the value of a parameter + * @param name - the name of the parameter + * @param type - `value`'s expected type + * @returns `true` + * @throws TypeError if `value` does not have the expected `type`. If `name` is not a string. + */ +function assertThatType(value: unknown, name: string, type: Type): void +{ + try + { + assert(requireThatType(value, name, type)); + } + catch (e) + { + if (e instanceof Error) + throw new AssertionError(e.message, {cause: e}); + throw e; + } +} + +/** + * Requires that an object has the expected type category. + * + * @param value - the value of a parameter + * @param name - the name of the parameter + * @param typeCategory - `value`'s expected type category + * @param typeGuard - (optional) for certain types, such as Typescript interfaces, runtime validation is not + * possible. In such a case, use a type guard to check if the value satisfies the type condition. + * @returns `true` + * @throws TypeError if `value` does not have the expected `typeCategory`. If `name` is not a string. + */ +function requireThatTypeCategory(value: unknown, name: string, typeCategory: TypeCategory, + typeGuard?: (value: unknown) => boolean) +{ + const typeOfName = Type.of(name); + if (typeOfName !== Type.STRING) + { + throw new TypeError(`name must be a string. +Actual: ${internalValueToString(name)} +Type : ${typeOfName.toString()}`); + } + + const typeCategoryOfValue = Type.of(value).category; + let matchFound; + if (typeGuard !== undefined) + matchFound = typeGuard(value); + else + matchFound = isEqual(typeCategoryOfValue, typeCategory); + if (!matchFound) + { + throw new TypeError(`${name} must be a ${TypeCategory[typeCategory]}. +Actual : ${internalValueToString(value)} +TypeCategory: ${TypeCategory[typeCategoryOfValue]}`); + } + return true; +} + +/** + * Requires that a value has the expected type category if assertions are enabled. We assume that + * `assert()` will be stripped out at build-time if assertions are disabled. + * + * @param value - the value of a parameter + * @param name - the name of the parameter + * @param category - `value`'s expected type category + * @param typeGuard - (optional) for certain types, such as Typescript interfaces, runtime validation is not + * possible. In such a case, use a type guard to check if the value satisfies the type condition. + * @returns `true` + * @throws TypeError if `value` does not have the expected `typeCategory` category. If `name` is not a string. + */ +function assertThatTypeCategory(value: unknown, name: string, category: TypeCategory, + typeGuard?: (value: unknown) => boolean): void +{ + try + { + assert(requireThatTypeCategory(value, name, category, typeGuard)); + } + catch (e) + { + if (e instanceof Error) + throw new AssertionError(e.message, {cause: e}); + throw e; + } + +} + +/** + * Requires that an object is an instance of `type`. + * + * @param value - the value of a parameter + * @param name - the name of the parameter + * @param type - the class that `value` is expected to be an instance of. This may not reference + * an interface or abstract class because + * Typescript does not expose them at runtime. + * @returns `true` + * @throws TypeError if `value` is not an instance of `type`. + * If `name` is not a string. + */ +function requireThatInstanceOf(value: unknown, name: string, + type: ClassConstructor | Record) +{ + // WARNING: Per https://github.com/typescript-eslint/typescript-eslint/issues/9370 instanceof returns false + // if a class "implements" an interface or another class. + const typeOfName = Type.of(name); + if (typeOfName !== Type.STRING) + { + throw new TypeError(`name must be a string. +Actual : ${internalValueToString(name)} +Actual.type: ${typeOfName.toString()}`); + } + const typeOfType = Type.of(type); + switch (typeOfType.category) + { + case TypeCategory.CLASS: + { + const classType = type as ClassConstructor; + if (!(value instanceof classType)) + { + const typeOfValue = Type.of(value); + throw new TypeError(`${name} must be ${typeOfType.toString()}. +Actual: ${typeOfValue.toString()}`); + } + break; + } + case TypeCategory.NUMBER: + case TypeCategory.STRING: + { + // Enum + if (!Object.values(type).includes(value)) + { + throw new TypeError(`${name} must be ${typeOfType.toString()}. +Actual: ${internalValueToString(type)} +Type : ${typeOfType.toString()}`); + } + break; + } + default: + throw new TypeError(`type must be a class or enum. +Actual: ${internalValueToString(typeOfType)}`); + } + return true; +} + +/** + * Requires that an object is an instance of the expected type. + * + * @param value - the value of a parameter + * @param name - the name of the parameter + * @param type - the class the value is expected to be an instance of + * @throws TypeError if `value` is not an instance of `type`. + * If `name` is not a string. + */ +function assertThatInstanceOf(value: T, name: string, type: ClassConstructor): void +{ + try + { + assert(requireThatInstanceOf(value, name, type)); + } + catch (e) + { + if (e instanceof Error) + throw new AssertionError(e.message, {cause: e}); + throw e; + } +} + +/** + * Requires that a string is not empty. + * + * @param value - the value of a parameter + * @param name - the name of the parameter + * @returns `true` + * @throws TypeError if `name` or `value` are empty. + * If `name` is not a string. + */ +function requireThatStringIsNotEmpty(value: string, name: string): boolean +{ + requireThatType(name, "name", Type.STRING); + name = name.trim(); + if (name.length === 0) + throw new RangeError("name may not be empty"); + requireThatType(value, "value", Type.STRING); + value = value.trim(); + if (value.length === 0) + throw new RangeError(`${name} may not be empty`); + return true; +} + +/** + * Requires that a string is not empty. + * + * @param value - the value of a parameter + * @param name - the name of the parameter + * @returns `true` + * @throws TypeError if `name` or `value` are empty. + * If `name` is not a string. + */ +function assertThatStringIsNotEmpty(value: string, name: string): void +{ + try + { + assert(requireThatStringIsNotEmpty(value, name)); + } + catch (e) + { + if (e instanceof Error) + throw new AssertionError(e.message, {cause: e}); + throw e; + } +} + +/** + * Converts an internal value to a string. + * + * @param value - a value + * @returns the string representation of the value + */ +function internalValueToString(value: unknown): string +{ + let typeOfObject = Type.of(value); + switch (typeOfObject.category) + { + case TypeCategory.CLASS: + { + switch (typeOfObject.name) + { + case "Set": + { + const set = value as Set; + return arrayToString(Array.from(set.values())); + } + case "Map": + { + const result: { [key: string]: unknown } = {}; + const map = value as Map; + for (const entry of map.entries()) + { + const key = internalValueToString(entry[0]); + result[key] = entry[1]; + } + return JSON.stringify(result, null, 2); + } + } + break; + } + case TypeCategory.UNDEFINED: + return "undefined"; + case TypeCategory.NULL: + return "null"; + case TypeCategory.STRING: + return quoteString(value as string); + default: + return `${JSON.stringify(value, undefined, 2)}`; + } + + // An instance of a user class + let current = value as ClassConstructor; + while (true) + { + // See http://stackoverflow.com/a/22445303/14731, + // Invoke toString() if it was defined + // https://stackoverflow.com/a/57214796/14731: invoke toString() on safeTypes + if (Object.prototype.hasOwnProperty.call(current.constructor.prototype, "toString")) + return current.toString(); + + // Get the superclass and try again + current = getSuperclass(current); + typeOfObject = Type.of(current); + assert(typeOfObject.category === TypeCategory.CLASS, undefined, + `expected: CLASS +actual: ${typeOfObject.toString()}`); + + const className = typeOfObject.name as string; + if (className === "Object") + { + // Prefer JSON.stringify() to Object.toString(). + return JSON.stringify(current, null, 2); + } + } +} + +/** + * Quotes a String, escaping any nested quotes. + * + * @param value - a `String` + * @returns the quoted string + */ +function quoteString(value: string) +{ + let result = ""; + + for (let i = 0; i < value.length; ++i) + { + const char = value.charAt(i); + if (char == "\"") + result += "\\\""; + else + result += char; + } + result = "\"" + result; + result = result + "\""; + return result.toString(); +} + + +/** + * Returns the superclass of a value's type. + * + * @param value - a value + * @returns `null` if the type does not have a superclass + */ +function getSuperclass(value: ClassConstructor) +{ + return Object.getPrototypeOf(value.constructor.prototype) as ClassConstructor; +} + +/** + * @param array - an array + * @returns the string representation of the array, using toString() to convert nested values + */ +function arrayToString(array: unknown[]): string +{ + let result = "["; + // Can't use Array.join() because it doesn't handle nested arrays well + const size = array.length; + for (let i = 0; i < size; ++i) + { + result += internalValueToString(array[i]); + if (i < size - 1) + result += ", "; + } + result += "]"; + return result; +} + +/** + * @param value - a name + * @param name - the name of the name variable + * @throws TypeError if `name` or `value` are not a string + * @throws RangeError if `value` is empty + */ +function verifyName(value: string, name: string): void +{ + requireThatType(name, "name", Type.STRING); + requireThatType(value, "value", Type.STRING); + const trimmed = value.trim(); + if (value.length !== trimmed.length) + throw new RangeError(`${name} may not contain leading or trailing whitespace. +Actual: "${name}"`); + if (trimmed.length === 0) + throw new RangeError(`${name} may not be empty`); +} + +export type { + ElementOf, + MapKey, + MapValue, + ClassConstructor, + Comparable, + NonUndefinable +}; +export { + classExtends, + assert, + requireThatValueIsDefined, + requireThatValueIsNotNull, + assertThatValueIsNotNull, + requireThatType, + assertThatType, + requireThatTypeCategory, + assertThatTypeCategory, + requireThatInstanceOf, + assertThatInstanceOf, + requireThatStringIsNotEmpty, + assertThatStringIsNotEmpty, + internalValueToString, + getSuperclass, + verifyName, + quoteString +}; \ No newline at end of file diff --git a/src/internal/Pluralizer.mts b/src/internal/validator/Pluralizer.mts similarity index 100% rename from src/internal/Pluralizer.mts rename to src/internal/validator/Pluralizer.mts diff --git a/src/internal/validator/SetValidatorImpl.mts b/src/internal/validator/SetValidatorImpl.mts new file mode 100644 index 0000000..0f1eda7 --- /dev/null +++ b/src/internal/validator/SetValidatorImpl.mts @@ -0,0 +1,48 @@ +import { + type Configuration, + type SetValidator, + type ValidationFailure, + AbstractCollectionValidator, + Pluralizer, + type ApplicationScope, + type UnsignedNumberValidator, + ObjectSizeValidatorImpl, + ValidationTarget +} from "../internal.mjs"; + +/** + * Default implementation of `SetValidator`. + */ +class SetValidatorImpl extends AbstractCollectionValidator, Set, E> + implements SetValidator +{ + /** + * @param scope - the application configuration + * @param configuration - the validator configuration + * @param name - the name of the value + * @param value - the value being validated + * @param pluralizer - the type of items in the array + * @param context - the contextual information set by a parent validator or the user + * @param failures - the list of validation failures + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace or is empty + * @throws AssertionError if `scope`, `configuration`, `value` `context` or `failures` are null + */ + public constructor(scope: ApplicationScope, configuration: Configuration, name: string, + value: ValidationTarget>, pluralizer: Pluralizer, context: Map, + failures: ValidationFailure[]) + { + super(scope, configuration, name, value, pluralizer, context, failures); + } + + size(): UnsignedNumberValidator + { + if (this.value.isNull()) + this.onNull(); + return new ObjectSizeValidatorImpl(this.scope, this._configuration, this, this.name + ".size()", + this.value.undefinedOrNullToInvalid().map(v => this.getLength(v)), this.pluralizer, this.context, + this.failures); + } +} + +export {SetValidatorImpl}; \ No newline at end of file diff --git a/src/internal/validator/StringValidatorImpl.mts b/src/internal/validator/StringValidatorImpl.mts new file mode 100644 index 0000000..abead0a --- /dev/null +++ b/src/internal/validator/StringValidatorImpl.mts @@ -0,0 +1,178 @@ +import { + type Configuration, + type StringValidator, + type ValidationFailure, + Pluralizer, + ObjectSizeValidatorImpl, + type ApplicationScope, + type UnsignedNumberValidator, + AbstractValidator, + ValidationTarget, + stringStartsWith, + stringDoesNotStartWith, + stringEndsWith, + stringDoesNotEndWith, + stringContains, + stringDoesNotContain, + stringMatches, + stringIsTrimmed, + objectIsEmpty, + objectIsNotEmpty +} from "../internal.mjs"; + + +/** + * Default implementation of `StringValidator`. + */ +class StringValidatorImpl extends AbstractValidator + implements StringValidator +{ + /** + * @param scope - the application configuration + * @param configuration - the validator configuration + * @param name - the name of the value + * @param value - the value + * @param context - the contextual information set by a parent validator or the user + * @param failures - the list of validation failures + * @throws TypeError if `name` is null + * @throws RangeError if `name` contains whitespace, or is empty + * @throws AssertionError if `scope`, `configuration`, `value`, `context` or `failures` are null + */ + public constructor(scope: ApplicationScope, configuration: Configuration, name: string, + value: ValidationTarget, context: Map, + failures: ValidationFailure[]) + { + super(scope, configuration, name, value, context, failures); + } + + isEmpty(): StringValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && v.length === 0)) + { + this.addRangeError( + objectIsEmpty(this).toString()); + } + return this; + } + + isNotEmpty(): StringValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && v.length !== 0)) + { + this.addRangeError( + objectIsNotEmpty(this).toString()); + } + return this; + } + + isTrimmed(): StringValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => !/^\s|\s$/.test(v))) + { + this.addRangeError( + stringIsTrimmed(this).toString()); + } + return this; + } + + startsWith(prefix: string): StringValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && v.startsWith(prefix))) + { + this.addRangeError( + stringStartsWith(this, prefix).toString()); + } + return this; + } + + doesNotStartWith(prefix: string): StringValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && !v.startsWith(prefix))) + { + this.addRangeError( + stringDoesNotStartWith(this, prefix).toString()); + } + return this; + } + + endsWith(suffix: string): StringValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && v.endsWith(suffix))) + { + this.addRangeError( + stringEndsWith(this, suffix).toString()); + } + return this; + } + + doesNotEndWith(suffix: string): StringValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && !v.endsWith(suffix))) + { + this.addRangeError( + stringDoesNotEndWith(this, suffix).toString()); + } + return this; + } + + contains(expected: string): StringValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && v.includes(expected))) + { + this.addRangeError( + stringContains(this, expected).toString()); + } + return this; + } + + doesNotContain(unwanted: string): StringValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && !v.includes(unwanted))) + { + this.addRangeError( + stringDoesNotContain(this, unwanted).toString()); + } + return this; + } + + matches(regex: RegExp): StringValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && regex.test(v))) + { + this.addRangeError( + stringMatches(this, regex.source).toString()); + } + return this; + } + + length(): UnsignedNumberValidator + { + if (this.value.isNull()) + this.onNull(); + return new ObjectSizeValidatorImpl(this.scope, this._configuration, this, this.name + ".length()", + this.value.undefinedOrNullToInvalid().map(v => v.length), Pluralizer.ELEMENT, this.context, + this.failures); + } +} + +export {StringValidatorImpl}; \ No newline at end of file diff --git a/src/internal/validator/Terminal.mts b/src/internal/validator/Terminal.mts new file mode 100644 index 0000000..81cef2e --- /dev/null +++ b/src/internal/validator/Terminal.mts @@ -0,0 +1,125 @@ +import chalk from "chalk"; +import { + TerminalEncoding, + sortByDecreasingRank, + Type, + assertThatType +} from "../internal.mjs"; + + +/** + * The terminal associated with the process. + */ +class Terminal +{ + private supportedTypes: Set | undefined; + private encoding: TerminalEncoding | undefined; + + /** + * @returns the encodings supported by the terminal + */ + getSupportedTypes() + { + if (this.supportedTypes === undefined) + { + this.supportedTypes = new Set(); + this.supportedTypes.add(TerminalEncoding.NONE); + // https://stackoverflow.com/a/4224668/14731 + if (globalThis.window === undefined) + { + // Node + switch (chalk.level) + { + case 3: + this.supportedTypes.add(TerminalEncoding.NODE_16MILLION_COLORS); + // fallthrough + case 2: + this.supportedTypes.add(TerminalEncoding.NODE_256_COLORS); + // fallthrough + case 1: + this.supportedTypes.add(TerminalEncoding.NODE_16_COLORS); + // fallthrough + case 0: + break; + default: + { + throw new RangeError(`chalk.level had an unexpected value. +Actual: ${String(chalk.level)}`); + } + } + } + else + { + // Browsers support colors using console.log() but error messages do not support any colors. + } + } + return this.supportedTypes; + } + + /** + * Indicates the type of encoding that the terminal should use. + *

+ * This feature can be used to force the use of colors even when their support is not detected. + * + * @param encoding - the type of encoding that the terminal should use + * @param force - true if the encoding should be forced regardless of what the system supports + * @throws TypeError if `encoding` is not a `TerminalEncoding`. + * If `force` is not a `boolean`. + * @see Terminal#useBestEncoding + */ + private setEncodingImpl(encoding: TerminalEncoding, force: boolean) + { + assertThatType(force, "force", Type.BOOLEAN); + console.debug("setEncodingImpl(%s, %s)", encoding, force); + + if (!this.getSupportedTypes().has(encoding) && !force) + { + this.encoding = TerminalEncoding.NONE; + return; + } + this.encoding = encoding; + console.debug("Setting encoding to %s", encoding); + } + + /** + * Indicates the type of encoding that the terminal should use. + *

+ * This feature can be used to force the use of colors even when their support is not detected. + * + * @param encoding - the type of encoding that the terminal should use + * @throws TypeError if `encoding` is not a `TerminalEncoding` + * @see Terminal#useBestEncoding + */ + setEncoding(encoding: TerminalEncoding) + { + this.setEncodingImpl(encoding, true); + } + + /** + * Indicates that verifiers should output the best encoding supported by the terminal. + * + * @see Terminal#setEncoding + */ + useBestEncoding() + { + const supportedTypes = this.getSupportedTypes(); + const sortedTypes: TerminalEncoding[] = [...supportedTypes].sort(sortByDecreasingRank); + this.setEncodingImpl(sortedTypes[0], false); + } + + /** + * @returns the encoding that the terminal should use (defaults to the best available encoding) + */ + getEncoding() + { + let result = this.encoding; + if (result === undefined) + { + this.useBestEncoding(); + result = this.encoding; + } + return result as TerminalEncoding; + } +} + +export {Terminal}; \ No newline at end of file diff --git a/src/internal/validator/ValidationFailureImpl.mts b/src/internal/validator/ValidationFailureImpl.mts new file mode 100644 index 0000000..c2376c6 --- /dev/null +++ b/src/internal/validator/ValidationFailureImpl.mts @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +import { + requireThatStringIsNotEmpty, + internalValueToString, + type ErrorBuilder, + Configuration, + type ValidationFailure, + assertThatInstanceOf, + assertThatType, + isErrorBuilder, + Type +} from "../internal.mjs"; + +class ValidationFailureImpl implements ValidationFailure +{ + private readonly message: string; + private readonly errorBuilder: ErrorBuilder | null; + private error: Error | null; + private readonly errorTransformer: (error: Error) => Error; + private transformedError: Error | null = null; + + /** + * @param configuration - the validator's configuration + * @param message - the failure message + * @param errorBuilder - returns the error associated with the failure message + * @throws AssertionError if any of the arguments are `null`. If the error message contains leading or + * trailing whitespace, or is empty. + */ + public constructor(configuration: Configuration, message: string, errorBuilder: ErrorBuilder) + { + assertThatInstanceOf(configuration, "configuration", Configuration); + requireThatStringIsNotEmpty(message, "message"); + assertThatType(errorBuilder, "errorBuilder", + Type.namedClass("ErrorBuilder", () => isErrorBuilder(errorBuilder))); + + this.message = message; + if (configuration.recordStacktrace()) + { + this.errorBuilder = errorBuilder; + this.error = null; + } + else + { + this.errorBuilder = null; + this.error = errorBuilder(message); + } + this.errorTransformer = configuration.errorTransformer(); + } + + public getMessage() + { + return this.message; + } + + public getType() + { + return this.getTransformedError().name; + } + + public getError(): Error + { + return this.getTransformedError(); + } + + private getTransformedError() + { + if (this.transformedError === null) + { + if (this.error === null) + this.error = (this.errorBuilder as ErrorBuilder)(this.message); + this.transformedError = this.errorTransformer(this.error); + if (this.transformedError === null) + this.transformedError = this.error; + } + return this.transformedError; + } + + public toString() + { + return `error: ${internalValueToString(this.error)}`; + } +} + +export {ValidationFailureImpl}; \ No newline at end of file diff --git a/src/validator/ArrayValidator.mts b/src/validator/ArrayValidator.mts new file mode 100644 index 0000000..b6ee225 --- /dev/null +++ b/src/validator/ArrayValidator.mts @@ -0,0 +1,37 @@ +import type { + UnsignedNumberValidator, + ValidatorComponent, + CollectionComponent +} from "../internal/internal.mjs"; + +/** + * Validates the state of an array. + * + * @typeParam E - the type of elements in the collection + */ +interface ArrayValidator extends + ValidatorComponent, E[]>, + CollectionComponent, E> +{ + /** + * Returns a validator for the array's length. + * + * @throws TypeError if the value is `undefined` or `null` + * @returns a validator for the array's length + */ + length(): UnsignedNumberValidator; + + /** + * Ensures that the array is sorted. + * + * @param comparator - a function that returns a negative number if `first` should come + * before `second`, zero or `NaN` if the two values are equal, or a positive number + * if `first` should come after `second`. + * @throws TypeError if the value or `comparator` are `null` + * @throws RangeError if the collection is not sorted + * @returns this + */ + isSorted(comparator: (first: unknown, second: unknown) => number): ArrayValidator; +} + +export type {ArrayValidator}; \ No newline at end of file diff --git a/src/validator/BooleanValidator.mts b/src/validator/BooleanValidator.mts new file mode 100644 index 0000000..b39b0a3 --- /dev/null +++ b/src/validator/BooleanValidator.mts @@ -0,0 +1,27 @@ +import type {ValidatorComponent} from "../internal/internal.mjs"; + +/** + * Validates the state of a `boolean`. + */ +interface BooleanValidator extends ValidatorComponent +{ + /** + * Ensures that the value is `true`. + * + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value is `false` + */ + isTrue(): BooleanValidator; + + /** + * Ensures that the value is `false`. + * + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value is `true` + */ + isFalse(): BooleanValidator; +} + +export type {BooleanValidator}; \ No newline at end of file diff --git a/src/validator/ClassValidator.mts b/src/validator/ClassValidator.mts new file mode 100644 index 0000000..c126d8f --- /dev/null +++ b/src/validator/ClassValidator.mts @@ -0,0 +1,41 @@ +import type { + ClassConstructor, + ValidatorComponent +} from "../internal/internal.mjs"; + +/** + * Validates the state of a type. + * + * @typeParam T - the type of the class + */ +interface ClassValidator extends ValidatorComponent, ClassConstructor> +{ + /** + * Ensures that the value is a primitive type. + * + * @returns this + * @throws TypeError if the value is null + * @throws RangeError if value is not a primitive type + */ + isPrimitive(): ClassValidator; + + /** + * Ensures that the actual value is the specified type, or a subtype. + * + * @typeParam U - the child type + * @param type - the child type + * @returns the updated validator + */ + isSupertypeOf(type: ClassConstructor): ClassValidator; + + /** + * Ensures that the actual value is the specified type, or a subtype. + * + * @typeParam U - the parent type + * @param type - the parent type + * @returns the updated validator + */ + isSubtypeOf(type: ClassConstructor): ClassValidator; +} + +export {type ClassValidator}; \ No newline at end of file diff --git a/src/validator/MapValidator.mts b/src/validator/MapValidator.mts new file mode 100644 index 0000000..c9710a9 --- /dev/null +++ b/src/validator/MapValidator.mts @@ -0,0 +1,66 @@ +import type { + ValidatorComponent, + ArrayValidator, + UnsignedNumberValidator +} from "../internal/internal.mjs"; + +/** + * Validates the state of a `Map`. + * + * @typeParam K - the type of keys in the map + * @typeParam V - the type of values in the map + */ +interface MapValidator extends ValidatorComponent, Map> +{ + /** + * Returns a validator for the value's {@link Map.keys|keys}. + * @returns a validator for the value's {@link Map.keys|keys} + * @throws TypeError if the value is `undefined` or `null` + */ + keys(): ArrayValidator; + + /** + * Returns a validator for the value's {@link Map.values|values}. + * + * @returns a validator for the value's {@link Map.values|values} + * @throws TypeError if the value is `undefined` or `null` + */ + values(): ArrayValidator; + + /** + * Returns a validator for the value's {@link Map.entries|entries} + * (an array of `[key, this.value]` for each element in the Map). + * + * @returns a validator for the value's {@link Map.entries|entries} + * @throws TypeError if the value is `undefined` or `null` + */ + entries(): ArrayValidator<[K, V]>; + + /** + * Ensures that the value is empty. + * + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if value is not empty + */ + isEmpty(): MapValidator; + + /** + * Ensures that the value is not empty. + * + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if value is empty + */ + isNotEmpty(): MapValidator; + + /** + * Returns a validator for the map's {@link Map.size|size}. + * + * @returns a validator for the map's {@link Map.size|size} + * @throws TypeError if the value is `undefined` or `null` + */ + size(): UnsignedNumberValidator; +} + +export type {MapValidator}; \ No newline at end of file diff --git a/src/validator/NumberValidator.mts b/src/validator/NumberValidator.mts new file mode 100644 index 0000000..e5dc839 --- /dev/null +++ b/src/validator/NumberValidator.mts @@ -0,0 +1,20 @@ +import { + type ValidatorComponent, + type NegativeNumberComponent, + type ZeroNumberComponent, + type PositiveNumberComponent, + type NumberComponent +} from "../internal/internal.mjs"; + +/** + * Validates the state of a `number`. + */ +interface NumberValidator extends ValidatorComponent, + NumberComponent, + NegativeNumberComponent, + ZeroNumberComponent, + PositiveNumberComponent +{ +} + +export type {NumberValidator}; \ No newline at end of file diff --git a/src/validator/ObjectValidator.mts b/src/validator/ObjectValidator.mts new file mode 100644 index 0000000..696b2b8 --- /dev/null +++ b/src/validator/ObjectValidator.mts @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ + +import {type ValidatorComponent} from "../internal/internal.mjs"; + +/** + * Validates the state of an unknown value or a value that does not have a specialized validator. + * + * @typeParam T - the type of the value that is being validated + */ +type ObjectValidator = ValidatorComponent, T>; + +export type {ObjectValidator}; \ No newline at end of file diff --git a/src/validator/SetValidator.mts b/src/validator/SetValidator.mts new file mode 100644 index 0000000..6d02f79 --- /dev/null +++ b/src/validator/SetValidator.mts @@ -0,0 +1,24 @@ +import type { + ValidatorComponent, + CollectionComponent, + UnsignedNumberValidator +} from "../internal/internal.mjs"; + +/** + * Validates the state of a `Set`. + * + * @typeParam E - the type of elements in the set + */ +interface SetValidator extends ValidatorComponent, Set>, + CollectionComponent, E> +{ + /** + * Returns a validator for the set's size. + * + * @throws TypeError if the value is `undefined` or `null` + * @returns a validator for the set's size + */ + size(): UnsignedNumberValidator; +} + +export type {SetValidator}; \ No newline at end of file diff --git a/src/validator/StringValidator.mts b/src/validator/StringValidator.mts new file mode 100644 index 0000000..af3ff27 --- /dev/null +++ b/src/validator/StringValidator.mts @@ -0,0 +1,120 @@ +import type { + ValidatorComponent, + UnsignedNumberValidator +} from "../internal/internal.mjs"; + +/** + * Validates the state of a `string`. + */ +interface StringValidator extends ValidatorComponent +{ + /** + * Ensures that the value starts with some prefix. + * + * @param prefix - the value that the string must start with + * @returns this + * @throws TypeError if the value or `prefix` are `null` + * @throws RangeError if the value does not start with `prefix` + */ + startsWith(prefix: string): StringValidator; + + /** + * Ensures that the value does not start with some prefix. + * + * @param prefix - the value that the string may not start with + * @returns this + * @throws TypeError if the value or `prefix` are `null` + * @throws RangeError if the value starts with `prefix` + */ + doesNotStartWith(prefix: string): StringValidator; + + /** + * Ensures that the value ends with some suffix. + * + * @param suffix - the value that the string must end with + * @returns this + * @throws TypeError if the value or `suffix` are `null` + * @throws RangeError if the value does not end with `suffix` + */ + endsWith(suffix: string): StringValidator; + + /** + * Ensures that the value does not end with some suffix. + * + * @param suffix - the value that the string may not end with + * @returns this + * @throws TypeError if the value or `suffix` are `null` + * @throws RangeError if the value ends with `suffix` + */ + doesNotEndWith(suffix: string): StringValidator; + + /** + * Ensures that the value contains some substring. + * + * @param expected - the string that the value must contain + * @returns this + * @throws TypeError if the value or `expected` are `null` + * @throws RangeError if the value does not contain `expected` + */ + contains(expected: string): StringValidator; + + /** + * Ensures that the value does not contain some substring. + * + * @param unwanted - the string that the value may not contain + * @returns this + * @throws TypeError if the value or `unwanted` are `null` + * @throws RangeError if the value contains `unwanted` + */ + doesNotContain(unwanted: string): StringValidator; + + /** + * Ensures that the value matches a regular expression. + * + * @param regex - the regular expression + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value does not match `regex` + */ + matches(regex: RegExp): StringValidator; + + /** + * Ensures that the value is empty. + * + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value is not empty + */ + isEmpty(): StringValidator; + + /** + * Ensures that the value is not empty. + * + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value is empty + */ + isNotEmpty(): StringValidator; + + /** + * Ensures that the value does not contain leading or trailing whitespace, where whitespace is defined by + * {@link String.trim}. + * + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value contains leading or trailing whitespace + * @see {@link String.trim} + * @see {@link StringValidator.isEmpty} + */ + isTrimmed(): StringValidator; + + /** + * Returns a validator for the length of the string. + * + * @returns a validator for the length of the string + * @throws TypeError if the value is `undefined` or `null` + */ + length(): UnsignedNumberValidator; +} + +export type {StringValidator}; \ No newline at end of file diff --git a/src/validator/UnsignedNumberValidator.mts b/src/validator/UnsignedNumberValidator.mts new file mode 100644 index 0000000..fb01e3d --- /dev/null +++ b/src/validator/UnsignedNumberValidator.mts @@ -0,0 +1,18 @@ +import type { + ValidatorComponent, + NumberComponent, + ZeroNumberComponent, + PositiveNumberComponent +} from "../internal/internal.mjs"; + +/** + * Validates the state of an unsigned `number`. + */ +interface UnsignedNumberValidator extends ValidatorComponent, + NumberComponent, + ZeroNumberComponent, + PositiveNumberComponent +{ +} + +export type {UnsignedNumberValidator}; \ No newline at end of file diff --git a/src/validator/component/CollectionComponent.mts b/src/validator/component/CollectionComponent.mts new file mode 100644 index 0000000..68f2c99 --- /dev/null +++ b/src/validator/component/CollectionComponent.mts @@ -0,0 +1,515 @@ +import type { + UnsignedNumberValidator, + ValidatorComponent +} from "../../internal/internal.mjs"; + +const typedocWorkaround: null | ValidatorComponent = null; +// noinspection PointlessBooleanExpressionJS +if (typedocWorkaround !== null) + console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); + +/** + * Methods that all collection validators must contain. + * + * @typeParam S - the type this validator + * @typeParam E - the type of elements in the collection + */ +interface CollectionComponent, E> +{ + /** + * Ensures that the collection is empty. + * + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the collection is not empty + */ + isEmpty(): S; + + /** + * Ensures that the collection is not empty. + * + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the collection is empty + */ + isNotEmpty(): S; + + /** + * Ensures that the collection contains an element. + * + * @param expected - the element + * @returns this + * @throws TypeError if the value or `expected` are `undefined` or `null` + * @throws RangeError if the collection does not contain `expected` + */ + contains(expected: E): S; + + /** + * Ensures that the collection does not contain `unwanted`. + * + * @param unwanted - the unwanted element + * @returns this + * @throws TypeError if the value or `unwanted` are `undefined` or `null` + * @throws RangeError if the collection contains `unwanted` + */ + doesNotContain(unwanted: E): S; + + /** + * Ensures that the collection contains an element. + * + * @param expected - the element + * @param name - the name of the element + * @returns this + * @throws TypeError if the value or any of the arguments are `undefined` or `null` + * @throws RangeError if: + *

    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
  • the collection does not contain `expected`
  • + *
+ */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + contains(expected: E, name: string): S; + + /** + * Ensures that the collection does not contain `unwanted`. + * + * @param unwanted - the unwanted element + * @param name - the name of the element + * @returns this + * @throws TypeError if the value or any of the arguments are `undefined` or `null` + * @throws RangeError if: + *
    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
  • the collection contains `unwanted`
  • + *
+ */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + doesNotContain(unwanted: E, name: string): S; + + /** + * Ensures that the collection consists of the same elements as `expected`, irrespective of their + * order. + *

+ * In contrast, {@link ValidatorComponent.isEqualTo|isEqualTo()} requires the same element ordering. + * + * @param expected - the desired elements + * @returns this + * @throws TypeError if the value or `expected` are `undefined` or `null` + * @throws RangeError if: + *

    + *
  • the collection is missing any element in `expected`
  • + *
  • the collection contains any element that is not in `expected`
  • + *
+ */ + containsExactly(expected: Set): S; + + /** + * Ensures that the collection and `unwanted` consist of different elements, irrespective of their + * order. + * + * @param unwanted - the unwanted elements + * @returns this + * @throws TypeError if the value or `unwanted` are `null` + * @throws RangeError if the collection consists of the same elements as `unwanted`, + * irrespective of their order + */ + doesNotContainExactly(unwanted: Set): S; + + /** + * Ensures that the collection consists of the same elements as `expected`, irrespective of + * their order. + *

+ * In contrast, {@link ValidatorComponent.isEqualTo|isEqualTo()} requires the same element ordering. + * + * @param expected - the desired elements + * @returns this + * @throws TypeError if the value or `expected` are `null` + * @throws RangeError if: + *

    + *
  • the collection is missing any element in `expected`
  • + *
  • the collection contains any element that is not in + * `expected`
  • + *
+ */ + containsExactly(expected: E[]): S; + + /** + * Ensures that the collection and `unwanted` consist of different elements, irrespective of their + * order. + * + * @param unwanted - the unwanted elements + * @returns this + * @throws TypeError if the value or `unwanted` are `null` + * @throws RangeError if the collection consists of the same elements as `unwanted`, + * irrespective of their order + */ + doesNotContainExactly(unwanted: E[]): S; + + /** + * Ensures that the collection consists of the same elements as `expected`, irrespective of their + * order. + *

+ * In contrast, {@link ValidatorComponent.isEqualTo|isEqualTo()} requires the same element ordering. + * + * @param expected - the desired elements + * @param name - the name of the expected collection + * @returns this + * @throws TypeError if the value or any of the arguments are `null` + * @throws RangeError if: + *

    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
  • the collection and `expected` contain different elements, + * irrespective of their order
  • + *
+ */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + containsExactly(expected: Set, name: string): S; + + /** + * Ensures that the collection and `unwanted` consist of different elements, irrespective of + * their order. + * + * @param unwanted - the unwanted elements + * @param name - the name of the unwanted collection + * @returns this + * @throws TypeError if the value or any of the arguments are `null` + * @throws RangeError if: + *
    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
  • the collection consists of the same elements as `unwanted`, + * irrespective of their order
  • + *
+ */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + doesNotContainExactly(unwanted: Set, name: string): S; + + /** + * Ensures that the collection consists of the same elements as `expected`, irrespective of + * their order. + *

+ * In contrast, {@link ValidatorComponent.isEqualTo|isEqualTo()} requires the same element ordering. + * + * @param expected - the desired elements + * @param name - the name of the expected collection + * @returns this + * @throws TypeError if the value or any of the arguments are `null` + * @throws RangError if: + *

    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
  • the collection and `expected` contain different elements, + * irrespective of their order
  • + *
+ */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + containsExactly(expected: E[], name: string): S; + + /** + * Ensures that the collection and `unwanted` consist of different elements, irrespective of their + * order. + * + * @param unwanted - the unwanted elements + * @param name - the name of the unwanted collection + * @returns this + * @throws TypeError if the value or any of the arguments are `null` + * @throws RangeError if: + *
    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
  • the collection consists of the same elements as `unwanted`, + * irrespective of their order
  • + *
+ */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + doesNotContainExactly(unwanted: E[], name: string): S; + + /** + * Ensures that the collection contains any elements in `expected`. + * + * @param expected - the desired elements + * @returns this + * @throws TypeError if the value or `expected` are `null` + * @throws RangeError if the collection does not contain any element in `expected` + */ + containsAny(expected: Set): S; + + /** + * Ensures that the collection does not contain any of the elements in `unwanted`. + * + * @param unwanted - the unwanted elements + * @returns this + * @throws TypeError if the value or `unwanted` are `null` + * @throws RangeError if the collection contains any of the elements in `unwanted` + */ + doesNotContainAny(unwanted: Set): S; + + /** + * Ensures that the collection contains any elements in `expected`. + * + * @param expected - the desired elements + * @returns this + * @throws TypeError if the value or `expected` are `null` + * @throws RangeError if the collection does not contain any element in `expected` + */ + containsAny(expected: E[]): S; + + /** + * Ensures that the collection does not contain any of the elements in `unwanted`. + * + * @param unwanted - the unwanted elements + * @returns this + * @throws TypeError if the value or `unwanted` are `null` + * @throws RangeError if the collection contains any of the elements in `unwanted` + */ + doesNotContainAny(unwanted: E[]): S; + + /** + * Ensures that the collection contains at least one element in `expected`. + * + * @param expected - the desired elements + * @param name - the name of the expected collection + * @returns this + * @throws TypeError if the value or any of the arguments are `null` + * @throws RangeError if: + *
    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
  • the collection does not contain any element in `expected`
  • + *
+ */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + containsAny(expected: Set, name: string): S; + + /** + * Ensures that the collection does not contain any of the elements in `unwanted`. + * + * @param unwanted - the unwanted elements + * @param name - the name of the unwanted collection + * @returns this + * @throws TypeError if the value or any of the arguments are `null` + * @throws RangeError if: + *
    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
  • the collection contains any of the elements in `unwanted`
  • + *
+ */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + doesNotContainAny(unwanted: Set, name: string): S; + + /** + * Ensures that the collection contains at least one element in `expected`. + * + * @param expected - the desired elements + * @param name - the name of the expected collection + * @returns this + * @throws TypeError if the value or any of the arguments are `null` + * @throws RangeError if: + *
    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
  • the collection does not contain any element in `expected`
  • + *
+ */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + containsAny(expected: E[], name: string): S; + + /** + * Ensures that the collection does not contain any of the elements in `unwanted`. + * + * @param unwanted - the unwanted elements + * @param name - the name of the unwanted collection + * @returns this + * @throws TypeError if the value or any of the arguments are `null` + * @throws RangeError if: + *
    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
  • the collection contains any of the elements in `unwanted`
  • + *
+ */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + doesNotContainAny(unwanted: E[], name: string): S; + + /** + * Ensures that the collection contains all the elements in `expected`. + * + * @param expected - the desired elements + * @returns this + * @throws TypeError if the value or `expected` are `null` + * @throws RangeError if the collection does not contain all the elements in `expected` + */ + containsAll(expected: Set): S; + + /** + * Allows the collection to contain some, but not all, elements from a collection. + * + * @param unwanted - the unwanted elements + * @returns this + * @throws TypeError if the value or `unwanted` are `null` + * @throws RangeError if the collection contains all the elements of `unwanted` + */ + doesNotContainAll(unwanted: Set): S; + + /** + * Ensures that the collection contains all the elements in `expected`. + * + * @param expected - the desired elements + * @returns this + * @throws TypeError if the value or `expected` are `null` + * @throws RangeError if the collection does not contain all the elements in `expected` + */ + containsAll(expected: E[]): S; + + /** + * Allows the collection to contain some, but not all, elements from a collection. + * + * @param unwanted - the unwanted elements + * @returns this + * @throws TypeError if the value or `unwanted` are `null` + * @throws RangeError if the collection contains all the elements of `unwanted` + */ + doesNotContainAll(unwanted: E[]): S; + + /** + * Ensures that the collection contains all the elements in `expected`. + * + * @param expected - the desired elements + * @param name - the name of the expected collection + * @returns this + * @throws TypeError if the value or any of the arguments are `null` + * @throws RangeError if: + *
    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
  • the collection does not contain all elements in `expected`
  • + *
+ */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + containsAll(expected: Set, name: string): S; + + /** + * Allows the collection to contain some, but not all, elements from a collection. + * + * @param unwanted - the unwanted elements + * @param name - the name of the unwanted collection + * @returns this + * @throws TypeError if the value or any of the arguments are `null` + * @throws RangeError if: + *
    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
  • the collection contains all the elements in `unwanted`
  • + *
+ */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + doesNotContainAll(unwanted: Set, name: string): S; + + /** + * Ensures that the collection contains all the elements in `expected`. + * + * @param expected - the desired elements + * @param name - the name of the expected collection + * @returns this + * @throws TypeError if the value or any of the arguments are `null` + * @throws RangeError if: + *
    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
  • the collection does not contain all elements in `expected`
  • + *
+ */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + containsAll(expected: E[], name: string): S; + + /** + * Allows the collection to contain some, but not all, elements from a collection. + * + * @param unwanted - the unwanted elements + * @param name - the name of the unwanted collection + * @returns this + * @throws TypeError if the value or any of the arguments are `null` + * @throws RangeError if: + *
    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
  • the collection contains all the elements in `unwanted`
  • + *
+ */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + doesNotContainAll(unwanted: E[], name: string): S; + + /** + * Ensures that the collection contains only null values, or only non-null values. + * + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the collection contains `undefined` or a mix of `null` and non-`null` values + */ + containsSameNullity(): S; + + /** + * Ensures that the collection does not contain any duplicate elements. + * + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the collection contains any duplicate elements + */ + doesNotContainDuplicates(): S; + + /** + * Returns a validator for the collection's size. + * + * @returns a validator for the collection's size + * @throws TypeError if the value is `undefined` or `null` + */ + size(): UnsignedNumberValidator; +} + +export type {CollectionComponent}; \ No newline at end of file diff --git a/src/validator/component/NegativeNumberComponent.mts b/src/validator/component/NegativeNumberComponent.mts new file mode 100644 index 0000000..fb90496 --- /dev/null +++ b/src/validator/component/NegativeNumberComponent.mts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ + +/** + * Methods that validators for numbers that may be negative must contain. + * + * @typeParam S - the type of this validator + */ +interface NegativeNumberComponent +{ + /** + * Ensures that the value is negative. `-0.0` is considered to be zero *and* negative. + * + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value is: + *
    + *
  • not negative
  • + *
  • not a number
  • + *
+ * @returns this + */ + isNegative(): S; + + /** + * Ensures that the value is not a negative number. `-0.0` is considered to be zero *and* negative. + * + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value is a negative number + * @returns this + */ + isNotNegative(): S; +} + +export type {NegativeNumberComponent}; \ No newline at end of file diff --git a/src/validator/component/NumberComponent.mts b/src/validator/component/NumberComponent.mts new file mode 100644 index 0000000..3e5b0c8 --- /dev/null +++ b/src/validator/component/NumberComponent.mts @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2019 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ + +/** + * Methods that all number validators must contain. + * + * @typeParam S - the type of this validator + */ +interface NumberComponent +{ + /** + * Returns the value that is being validated. + * + * @returns the validated value + * @throws IllegalStateError if a previous validation failed + */ + getValue(): number; + + /** + * Ensures that the value is a multiple of `factor`. + * + * @param factor - the number being multiplied + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value is not a multiple of `factor` + */ + isMultipleOf(factor: number): S; + + /** + * Ensures that the value is a multiple of `factor`. + * + * @param factor - the number being multiplied + * @param name - the name of the factor + * @returns this + * @throws TypeError if the value or `name` are `undefined` or `null` + * @throws RangeError if `name` contains whitespace or is empty. If + * the value is not a multiple of `factor`. + */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + isMultipleOf(factor: number, name: string): S; + + /** + * Ensures that the value is not a multiple of `factor`. + * + * @param factor - the number being multiplied + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value is a multiple of `factor` + */ + isNotMultipleOf(factor: number): S; + + /** + * Ensures that the value is not a multiple of `factor`. + * + * @param factor - the number being multiplied + * @param name - the name of the factor + * @returns this + * @throws TypeError if the value or `name` are `null` + * @throws RangeError if `name` contains whitespace or is empty. If + * the value is a multiple of `factor`. + */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + isNotMultipleOf(factor: number, name: string): S; + + /** + * Ensures that the value is less than an upper bound. + * + * @param maximumExclusive - the exclusive upper bound + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value is greater than or equal to `maximumExclusive` + */ + isLessThan(maximumExclusive: number): S; + + /** + * Ensures that the value is less than an upper bound. + * + * @param maximumExclusive - the exclusive upper bound + * @param name - the name of the upper bound + * @returns this + * @throws TypeError if the value or `name` are `undefined` or `null` + * @throws RangeError if `name` contains a leading, trailing whitespace or is empty. If + * the value is greater than or equal to `maximumExclusive`. + */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + isLessThan(maximumExclusive: number, name: string): S; + + /** + * Ensures that the value is less than or equal to a maximum value. + * + * @param maximumInclusive - the inclusive upper value + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value is greater than `maximumInclusive` + */ + isLessThanOrEqualTo(maximumInclusive: number): S; + + /** + * Ensures that the value is less than or equal to a maximum value. + * + * @param maximumInclusive - the maximum value + * @param name - the name of the maximum value + * @returns this + * @throws TypeError if the value or `name` are `undefined` or `null` + * @throws RangeError if `name` contains whitespace or is empty. If + * the value is greater than `maximumInclusive`. + */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + isLessThanOrEqualTo(maximumInclusive: number, name: string): S; + + /** + * Ensures that the value is greater than or equal to a minimum value. + * + * @param minimumInclusive - the minimum value + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value is less than `minimumInclusive` + */ + isGreaterThanOrEqualTo(minimumInclusive: number): S; + + /** + * Ensures that the value is greater than or equal a minimum value. + * + * @param minimumInclusive - the minimum value + * @param name - the name of the minimum value + * @returns this + * @throws TypeError if the value or `name` are `undefined` or `null` + * @throws RangeError if `name` contains whitespace or is empty. If + * the value is less than `minimumInclusive`. + */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + isGreaterThanOrEqualTo(minimumInclusive: number, name: string): S; + + /** + * Ensures that the value is greater than a lower bound. + * + * @param minimumExclusive - the exclusive lower bound + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value is less than `minimumExclusive` + */ + isGreaterThan(minimumExclusive: number): S; + + /** + * Ensures that the value is greater than a lower bound. + * + * @param minimumExclusive - the exclusive lower bound + * @param name - the name of the lower bound + * @returns this + * @throws TypeError if the value or `name` are `undefined` or `null` + * @throws RangeError if `name` contains whitespace or is empty. If + * the value is less than `minimumExclusive`. + */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + isGreaterThan(minimumExclusive: number, name: string): S; + + /** + * Ensures that the value is within a range. + * + * @param minimumInclusive - the lower bound of the range (inclusive) + * @param maximumExclusive - the upper bound of the range (exclusive) + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if `minimumInclusive` is greater than `maximumExclusive`. If the value is less than + * `minimumInclusive`. If the value is greater than or equal to `maximumExclusive`. + */ + isBetween(minimumInclusive: number, maximumExclusive: number): S; + + /** + * Ensures that the value is within a range. + * + * @param minimum - the lower bound of the range + * @param minimumInclusive - `true` if the lower bound of the range is inclusive + * @param maximum - the upper bound of the range + * @param maximumInclusive - `true` if the upper bound of the range is inclusive + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if `minimum` is greater than `maximum`. If + * `minimumInclusive` is `true`, and the value is less than `minimum`. + * If `minimumInclusive` is `false`, and the value is less than or equal to + * `minimum`. If `maximumInclusive` is `true` and the value is greater + * than `maximum`. If `maximumInclusive` is `false`, and the value is + * greater than or equal to `maximum`. + */ + isBetween(minimum: number, minimumInclusive: boolean, maximum: number, maximumInclusive: boolean): S; + + /** + * Ensures that the value is a finite number. + * + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if value is not a number or is an infinite number + * @see {@link isNumber} + * @see {@link Number.isFinite} + */ + isFinite(): S; + + /** + * Ensures that the value is an infinite number. + * + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if value is not a number or is a finite number + * @see {@link isNumber} + * @see {@link Number.isFinite} + */ + isInfinite(): S; + + /** + * Ensures that the value is a well-defined number. + * + * @returns this + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if value is not a well-defined number + * @see #isNotNumber() + */ + isNumber(): S; + + /** + * Ensures that the value is the result of a mathematically undefined numerical operation (such as division + * by zero) or don't have a representation in real numbers (such as the square-root of -1). + * + * @returns this + * @throws NullPointerError if the value is `undefined` or `null` + * @throws RangeError if value is a well-defined number + */ + isNotNumber(): S; +} + +export type {NumberComponent}; \ No newline at end of file diff --git a/src/validator/component/PositiveNumberComponent.mts b/src/validator/component/PositiveNumberComponent.mts new file mode 100644 index 0000000..34f7b66 --- /dev/null +++ b/src/validator/component/PositiveNumberComponent.mts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ + +/** + * Methods that validators for numbers that may be positive must contain. + * + * @typeParam S - the type of this validator + */ +interface PositiveNumberComponent +{ + /** + * Ensures that the value is positive. + * + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value is: + *
    + *
  • not positive
  • + *
  • not a number
  • + *
+ * @returns this + */ + isPositive(): S; + + /** + * Ensures that the value is not a positive number. + * + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value is a positive number + * @returns this + */ + isNotPositive(): S; +} + +export type {PositiveNumberComponent}; \ No newline at end of file diff --git a/src/validator/component/ValidatorComponent.mts b/src/validator/component/ValidatorComponent.mts new file mode 100644 index 0000000..9c0003d --- /dev/null +++ b/src/validator/component/ValidatorComponent.mts @@ -0,0 +1,269 @@ +import { + Type, + ValidationFailures, + type NonUndefinable, + type ClassConstructor, + type ObjectValidator +} from "../../internal/internal.mjs"; + +/** + * Methods that all validators must contain. + * + * @typeParam S - the type of validator + * @typeParam T - the type of the value + */ +interface ValidatorComponent +{ + /** + * Returns the name of the value. + * + * @returns the name of the value + */ + getName(): string; + + /** + * Returns the value that is being validated. + * + * @returns the value + * @throws RangeError if the value is invalid (e.g. due to dereferencing a property of a `null` object) + */ + getValue(): T; + + /** + * Returns the value that is being validated. + * + * @param defaultValue - the fallback value to use if the value is invalid + * @returns the validated value, or `defaultValue` if the value is invalid (e.g. due to dereferencing a + * property of a `null` object) + */ + getValueOrDefault(defaultValue: T): T; + + getValueOrDefault(defaultValue: T | null): T | null; + + /** + * Returns the contextual information for upcoming validations carried out by this validator. The contextual + * information is a map of key-value pairs that can provide more details about validation failures. For + * example, if the message is "Password may not be empty" and the map contains the key-value pair + * `{"username": "john.smith"}`, the error message would be: + * ```console + * Password may not be empty + * username: john.smith + * ``` + * + * @returns an unmodifiable map from each entry's name to its value + * @see {@link Validators.getContext} + */ + getContext(): Map; + + /** + * Sets the contextual information for upcoming validations. + *

+ * This method adds contextual information to error messages. The contextual information is stored as + * key-value pairs in a map. Values set by this method override any values that are set using + * {@link Validators.withContext}. + *

+ * There is no way to remove contextual information from a validator. Thread-level contextual information is + * removed automatically. + * + * @param value - the value of the entry + * @param name - the name of an entry + * @returns this + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name`: + *

    + *
  • contains whitespace
  • + *
  • is empty
  • + *
  • is already in use by the value being validated or the validator + * context
  • + *
+ */ + withContext(value: unknown, name: string): S; + + /** + * Facilitates the validation of related properties. For example: + *

+ * ```ts + * requireThat(nameToFrequency, "nameToFrequency"). + * and(m => m.size().isPositive()). + * and(m => m.keySet().contains("John")); + * ``` + *

+ * Any changes made during the validation process will impact this validator. + * + * @param validation - the nested validation + * @returns this + * @throws TypeError if `validation` is `undefined` or `null` + */ + and(validation: (validator: S) => void): S; + + /** + * Checks if any validation has failed. + * + * @returns `true` if at least one validation has failed + */ + validationFailed(): boolean; + + /** + * Returns the array of failed validations. + * + * @returns an array of failed validations + */ + elseGetFailures(): ValidationFailures; + + /** + * Throws an error if a validation failed; otherwise, returns `true`. + * + * @returns true if the validation passed + * @throws RangeError if a method precondition, class invariant, or method postcondition was violated + * @throws MultipleFailuresError if more than one validation failed. This error contains an array of + * the failures. + */ + elseThrow(): boolean; + + /** + * Returns the contextual information associated with this validator. + * + * @returns the contextual information associated with this validator + */ + getContextAsString(): string; + + /** + * Ensures that the value is undefined. + * + * @returns this + * @throws TypeError if the value is not `undefined` + */ + isUndefined(): ObjectValidator; + + /** + * Ensures that the value is not undefined. + *

+ * This method should be used to validate method arguments that are assigned to class fields but not + * accessed right away (such as constructor and setter arguments). It should also be used to validate any + * method arguments when the validator contains + * {@link ValidatorComponent.getContext|additional contextual information}. + * + * @returns this + * @throws TypeError if the value is `undefined` + */ + isNotUndefined(): ObjectValidator>; + + /** + * Ensures that the value is `null`. + * + * @returns this + * @throws TypeError if the value is not `null` + */ + isNull(): ObjectValidator; + + /** + * Ensures that the value is not `null`. + *

+ * This method should be used to validate method arguments that are assigned to class fields but not + * accessed right away (such as constructor and setter arguments). It should also be used to validate any + * method arguments when the validator contains + * {@link ValidatorComponent.getContext|additional contextual information}. + * + * @returns this + * @throws TypeError if the value is `null` + */ + isNotNull(): ObjectValidator>; + + /** + * Ensures that the object is an instance of a class. + * + * @typeParam U - the desired class + * @param expected - the desired class + * @returns a validator for an object of the desired class + * @throws TypeError if the value or `expected` are `undefined` or `null` + * @throws RangeError if the value is not an instance of the desired class + */ + isInstanceOf(expected: ClassConstructor): ObjectValidator; + + /** + * Ensures that the value has the specified type. + * + * @param expected - the expected type of the value + * @returns this + * @throws TypeError if the value is not an instance of the specified type + */ + isType(expected: Type): S; + + /** + * Ensures that the object is not an instance of a class. + * + * @param unwanted - the unwanted class + * @returns this + * @throws TypeError if the value or `unwanted` are `undefined` or `null` + * @throws RangeError if the value is an instance of the unwanted class + */ + isNotInstanceOf(unwanted: ClassConstructor): ObjectValidator; + + /** + * Ensures that the object is equal to `expected`. + * + * @param expected - the expected value + * @returns this + * @throws RangeError if the value is not equal to `expected` + * @see An + * explanation of the output format + */ + isEqualTo(expected: unknown): S; + + /** + * Ensures that the object is equal to `expected`. + * + * @param expected - the expected value + * @param name - the name of the expected value + * @returns this + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if: + *

    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
  • the value is not equal to `expected`
  • + *
+ * @see An + * explanation of the output format + */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + isEqualTo(expected: unknown, name: string): S; + + /** + * Ensures that the object is not equal to `unwanted`. + * + * @param unwanted - the unwanted value + * @returns this + * @throws RangeError if the value is equal to `expected` + * @see An + * explanation of the output format + */ + isNotEqualTo(unwanted: unknown): S; + + /** + * Ensures that the object is not equal to `unwanted`. + * + * @param unwanted - the unwanted value + * @param name - the name of the other value + * @returns this + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if: + *
    + *
  • `name` is empty
  • + *
  • `name` contains whitespace
  • + *
  • `name` is already in use by the value being validated or + * the validator context
  • + *
  • the value is equal to the `unwanted` value
  • + *
+ * @see An + * explanation of the output format + */ + // Retain separate methods because their documentation is different. + // eslint-disable-next-line @typescript-eslint/unified-signatures + isNotEqualTo(unwanted: unknown, name: string): S; +} + +export type {ValidatorComponent}; \ No newline at end of file diff --git a/src/validator/component/ZeroNumberComponent.mts b/src/validator/component/ZeroNumberComponent.mts new file mode 100644 index 0000000..3dd6196 --- /dev/null +++ b/src/validator/component/ZeroNumberComponent.mts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ + +/** + * Methods that validators for numbers that may be zero must contain. + * + * @typeParam S - the type of this validator + */ +interface ZeroNumberComponent +{ + /** + * Ensures that the value is zero. `-0.0` is considered to be zero *and* negative. + * + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value is: + *
    + *
  • not zero
  • + *
  • not a number
  • + *
+ * @returns this + */ + isZero(): S; + + /** + * Ensures that the value is not zero. `-0.0` is considered to be zero *and* negative. + * + * @throws TypeError if the value is `undefined` or `null` + * @throws RangeError if the value is zero + * @returns this + */ + isNotZero(): S; +} + +export type {ZeroNumberComponent}; \ No newline at end of file diff --git a/test/ArrayTest.mts b/test/ArrayTest.mts index 92babbc..238b898 100644 --- a/test/ArrayTest.mts +++ b/test/ArrayTest.mts @@ -4,15 +4,17 @@ import { } from "mocha"; import {assert} from "chai"; import { - Configuration, TerminalEncoding, - Requirements + Configuration, + Type } from "../src/index.mjs"; -import {TestGlobalConfiguration} from "./TestGlobalConfiguration.mjs"; +import {JavascriptValidatorsImpl} from "../src/internal/validator/JavascriptValidatorsImpl.mjs"; +import {TestApplicationScope} from "./TestApplicationScope.mjs"; +import {map} from "lodash"; + -const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); -const configuration = new Configuration(globalConfiguration); -const requirements = new Requirements(configuration); +const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); /* eslint-disable max-statements */ suite("ArrayTest", () => @@ -21,7 +23,7 @@ suite("ArrayTest", () => test("isEmpty", () => { const actual: unknown[] = []; - requirements.requireThat(actual, "actual").isEmpty(); + validators.requireThat(actual, "actual").isEmpty(); }); test("isEmpty_actualContainsOneElement", () => @@ -29,14 +31,14 @@ suite("ArrayTest", () => assert.throws(function() { const actual = ["element"]; - requirements.requireThat(actual, "actual").isEmpty(); + validators.requireThat(actual, "actual").isEmpty(); }, RangeError); }); test("isNotEmpty", () => { const actual = ["element"]; - requirements.requireThat(actual, "actual").isNotEmpty(); + validators.requireThat(actual, "actual").isNotEmpty(); }); test("isNotEmpty_False", () => @@ -44,14 +46,14 @@ suite("ArrayTest", () => assert.throws(function() { const actual: unknown[] = []; - requirements.requireThat(actual, "actual").isNotEmpty(); + validators.requireThat(actual, "actual").isNotEmpty(); }, RangeError); }); test("contains", () => { const actual = ["element"]; - requirements.requireThat(actual, "actual").contains("element"); + validators.requireThat(actual, "actual").contains("element"); }); test("contains_False", () => @@ -59,14 +61,14 @@ suite("ArrayTest", () => assert.throws(function() { const actual = ["notElement"]; - requirements.requireThat(actual, "actual").contains("element"); + validators.requireThat(actual, "actual").contains("element"); }, RangeError); }); test("containsVariable", () => { const actual = ["element"]; - requirements.requireThat(actual, "actual").contains("element", "nameOfExpected"); + validators.requireThat(actual, "actual").contains("element", "nameOfExpected"); }); test("containsVariable_False", () => @@ -74,7 +76,7 @@ suite("ArrayTest", () => assert.throws(function() { const actual = ["notElement"]; - requirements.requireThat(actual, "actual").contains("element", "nameOfExpected"); + validators.requireThat(actual, "actual").contains("element", "nameOfExpected"); }, RangeError); }); @@ -83,7 +85,7 @@ suite("ArrayTest", () => assert.throws(function() { const actual = ["element"]; - requirements.requireThat(actual, "actual").contains(" "); + validators.requireThat(actual, "actual").contains(" "); }, RangeError); }); @@ -95,7 +97,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").containsExactly(["one", "two", "three"]); + validators.requireThat(actual, "actual").containsExactly(["one", "two", "three"]); }); test("containsExactly_actualContainsUnwantedElements", () => @@ -108,7 +110,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").containsExactly(["one", "two"]); + validators.requireThat(actual, "actual").containsExactly(["one", "two"]); }, RangeError); }); @@ -121,7 +123,7 @@ suite("ArrayTest", () => "one", "two" ]; - requirements.requireThat(actual, "actual").containsExactly(["one", "two", "three"]); + validators.requireThat(actual, "actual").containsExactly(["one", "two", "three"]); }, RangeError); }); @@ -134,7 +136,7 @@ suite("ArrayTest", () => "one", "two" ]; - requirements.requireThat(actual, "actual").containsExactly(["one", "two", "three"], "expected"); + validators.requireThat(actual, "actual").containsExactly(["one", "two", "three"], "expected"); }, RangeError); }); @@ -146,7 +148,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").containsExactly(["one", "two", "three"], "nameOfExpected"); + validators.requireThat(actual, "actual").containsExactly(["one", "two", "three"], "nameOfExpected"); }); test("containsExactlyVariable_False", () => @@ -159,7 +161,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").containsExactly(["one", "two"], "nameOfExpected"); + validators.requireThat(actual, "actual").containsExactly(["one", "two"], "nameOfExpected"); }, RangeError); }); @@ -173,7 +175,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").containsExactly(["one", "two", "three"], " "); + validators.requireThat(actual, "actual").containsExactly(["one", "two", "three"], " "); }, RangeError); }); @@ -185,7 +187,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").containsAny(["two", "four"]); + validators.requireThat(actual, "actual").containsAny(["two", "four"]); }); test("containsAny_False", () => @@ -198,7 +200,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").containsAny(["four", "five"]); + validators.requireThat(actual, "actual").containsAny(["four", "five"]); }, RangeError); }); @@ -210,7 +212,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").containsAny(["two", "four"], "nameOfExpected"); + validators.requireThat(actual, "actual").containsAny(["two", "four"], "nameOfExpected"); }); test("containsAnyVariable_False", () => @@ -223,7 +225,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").containsAny(["four", "five"], "nameOfExpected"); + validators.requireThat(actual, "actual").containsAny(["four", "five"], "nameOfExpected"); }, RangeError); }); @@ -237,7 +239,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").containsAny(["two", "four"], " "); + validators.requireThat(actual, "actual").containsAny(["two", "four"], " "); }, RangeError); }); @@ -249,7 +251,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").containsAll(["two", "three"]); + validators.requireThat(actual, "actual").containsAll(["two", "three"]); }); test("containsAll_False", () => @@ -262,7 +264,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").containsAll(["two", "four"]); + validators.requireThat(actual, "actual").containsAll(["two", "four"]); }, RangeError); }); @@ -274,7 +276,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").containsAll(["two", "three"], "nameOfExpected"); + validators.requireThat(actual, "actual").containsAll(["two", "three"], "nameOfExpected"); }); test("containsAllVariable_False", () => @@ -287,7 +289,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").containsAll(["two", "four"], "nameOfExpected"); + validators.requireThat(actual, "actual").containsAll(["two", "four"], "nameOfExpected"); }, RangeError); }); @@ -301,7 +303,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").containsAll(["two", "three"], " "); + validators.requireThat(actual, "actual").containsAll(["two", "three"], " "); }, RangeError); }); @@ -311,7 +313,7 @@ suite("ArrayTest", () => [ "notElement" ]; - requirements.requireThat(actual, "actual").doesNotContain("element"); + validators.requireThat(actual, "actual").doesNotContain("element"); }); test("doesNotContain_False", () => @@ -322,7 +324,7 @@ suite("ArrayTest", () => [ "element" ]; - requirements.requireThat(actual, "actual").doesNotContain("element"); + validators.requireThat(actual, "actual").doesNotContain("element"); }, RangeError); }); @@ -332,7 +334,7 @@ suite("ArrayTest", () => [ "notElement" ]; - requirements.requireThat(actual, "actual").doesNotContain("element", "nameOfExpected"); + validators.requireThat(actual, "actual").doesNotContain("element", "nameOfExpected"); }); test("doesNotContainVariable_False", () => @@ -343,7 +345,7 @@ suite("ArrayTest", () => [ "element" ]; - requirements.requireThat(actual, "actual").doesNotContain("element", "nameOfExpected"); + validators.requireThat(actual, "actual").doesNotContain("element", "nameOfExpected"); }, RangeError); }); @@ -355,7 +357,7 @@ suite("ArrayTest", () => [ "notElement" ]; - requirements.requireThat(actual, "actual").doesNotContain("element", " "); + validators.requireThat(actual, "actual").doesNotContain("element", " "); }, RangeError); }); @@ -367,7 +369,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").doesNotContainAny(["four", "five", "six"]); + validators.requireThat(actual, "actual").doesNotContainAny(["four", "five", "six"]); }); test("doesNotContainAny_False", () => @@ -380,7 +382,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").doesNotContainAny(["three", "four", "five"]); + validators.requireThat(actual, "actual").doesNotContainAny(["three", "four", "five"]); }, RangeError); }); @@ -392,7 +394,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual"). + validators.requireThat(actual, "actual"). doesNotContainAny(["four", "five", "six"], "nameOfExpected"); }); @@ -406,7 +408,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual"). + validators.requireThat(actual, "actual"). doesNotContainAny(["three", "four", "five"], "nameOfExpected"); }, RangeError); }); @@ -421,7 +423,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").doesNotContainAny(["four", "five", "six"], " "); + validators.requireThat(actual, "actual").doesNotContainAny(["four", "five", "six"], " "); }, RangeError); }); @@ -433,7 +435,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").doesNotContainAll(["one", "two", "four"]); + validators.requireThat(actual, "actual").doesNotContainAll(["one", "two", "four"]); }); test("doesNotContainAll_False", () => @@ -447,7 +449,7 @@ suite("ArrayTest", () => "three", "four" ]; - requirements.requireThat(actual, "actual").doesNotContainAll(["one", "two", "three"]); + validators.requireThat(actual, "actual").doesNotContainAll(["one", "two", "three"]); }, RangeError); }); @@ -459,7 +461,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").doesNotContainAll(["one", "two", "four"], "nameOfExpected"); + validators.requireThat(actual, "actual").doesNotContainAll(["one", "two", "four"], "nameOfExpected"); }); test("doesNotContainAllVariable_False", () => @@ -473,7 +475,7 @@ suite("ArrayTest", () => "three", "four" ]; - requirements.requireThat(actual, "actual").doesNotContainAll(["one", "two", "three"], + validators.requireThat(actual, "actual").doesNotContainAll(["one", "two", "three"], "nameOfExpected"); }, RangeError); }); @@ -488,7 +490,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").doesNotContainAll(["one", "two", "four"], " "); + validators.requireThat(actual, "actual").doesNotContainAll(["one", "two", "four"], " "); }, RangeError); }); @@ -500,7 +502,7 @@ suite("ArrayTest", () => "two", "three" ]; - requirements.requireThat(actual, "actual").doesNotContainDuplicates(); + validators.requireThat(actual, "actual").doesNotContainDuplicates(); }); test("doesNotContainDuplicates_False", () => @@ -515,7 +517,7 @@ suite("ArrayTest", () => "two", "four" ]; - requirements.requireThat(actual, "actual").doesNotContainDuplicates(); + validators.requireThat(actual, "actual").doesNotContainDuplicates(); }, RangeError); }); @@ -525,19 +527,19 @@ suite("ArrayTest", () => [ "element" ]; - requirements.requireThat(actual, "actual").length().isEqualTo(1); + validators.requireThat(actual, "actual").length().isEqualTo(1); }); - test("lengthConsumerIsEqualTo", () => + test("lengthNestedValidatorIsEqualTo", () => { const actual = [ "element" ]; - requirements.requireThat(actual, "actual").lengthConsumer(l => l.isEqualTo(1)); + validators.requireThat(actual, "actual").and(v => v.length().isEqualTo(1)); }); - test("lengthConsumerIsEqualTo_False", () => + test("lengthNestedValidatorIsEqualTo_False", () => { assert.throws(function() { @@ -545,7 +547,7 @@ suite("ArrayTest", () => [ "element" ]; - requirements.requireThat(actual, "actual").lengthConsumer(l => l.isEqualTo(2)); + validators.requireThat(actual, "actual").and(v => v.length().isEqualTo(2)); }, RangeError); }); @@ -557,7 +559,7 @@ suite("ArrayTest", () => [ "element" ]; - requirements.requireThat(actual, "actual").length().isEqualTo(2); + validators.requireThat(actual, "actual").length().isEqualTo(2); }, RangeError); }); @@ -567,7 +569,7 @@ suite("ArrayTest", () => [ "element" ]; - requirements.requireThat(actual, "actual").length().isEqualTo(1, "nameOfExpected"); + validators.requireThat(actual, "actual").length().isEqualTo(1, "nameOfExpected"); }); test("lengthIsEqualToVariable_False", () => @@ -578,7 +580,7 @@ suite("ArrayTest", () => [ "element" ]; - requirements.requireThat(actual, "actual").length().isEqualTo(2, "nameOfExpected"); + validators.requireThat(actual, "actual").length().isEqualTo(2, "nameOfExpected"); }, RangeError); }); @@ -590,7 +592,7 @@ suite("ArrayTest", () => [ "element" ]; - requirements.requireThat(actual, "actual").length().isEqualTo(1, " "); + validators.requireThat(actual, "actual").length().isEqualTo(1, " "); }, RangeError); }); @@ -600,7 +602,7 @@ suite("ArrayTest", () => [ "element" ]; - requirements.requireThat(actual, "actual").length().isNotEqualTo(2); + validators.requireThat(actual, "actual").length().isNotEqualTo(2); }); test("lengthIsNotEqualTo_False", () => @@ -611,7 +613,7 @@ suite("ArrayTest", () => [ "element" ]; - requirements.requireThat(actual, "actual").length().isNotEqualTo(1); + validators.requireThat(actual, "actual").length().isNotEqualTo(1); }, RangeError); }); @@ -621,7 +623,7 @@ suite("ArrayTest", () => [ "element" ]; - requirements.requireThat(actual, "actual").length().isNotEqualTo(2, "nameOfExpected"); + validators.requireThat(actual, "actual").length().isNotEqualTo(2, "nameOfExpected"); }); test("lengthIsNotEqualToVariable_False", () => @@ -632,7 +634,7 @@ suite("ArrayTest", () => [ "element" ]; - requirements.requireThat(actual, "actual").length().isNotEqualTo(1, "nameOfExpected"); + validators.requireThat(actual, "actual").length().isNotEqualTo(1, "nameOfExpected"); }, RangeError); }); @@ -644,7 +646,7 @@ suite("ArrayTest", () => [ "element" ]; - requirements.requireThat(actual, "actual").length().isNotEqualTo(2, " "); + validators.requireThat(actual, "actual").length().isNotEqualTo(2, " "); }, RangeError); }); @@ -656,7 +658,7 @@ suite("ArrayTest", () => 2, 3 ]; - requirements.requireThat(actual, "actual").length().isBetween(3, 5); + validators.requireThat(actual, "actual").length().isBetween(3, 5); }); test("isBetween_expectedIsInBounds", () => @@ -668,7 +670,7 @@ suite("ArrayTest", () => 3, 4 ]; - requirements.requireThat(actual, "actual").length().isBetween(3, 5); + validators.requireThat(actual, "actual").length().isBetween(3, 5); }); test("isBetween_expectedIsUpperBound", () => @@ -683,7 +685,7 @@ suite("ArrayTest", () => 4, 5 ]; - requirements.requireThat(actual, "actual").length().isBetween(3, 5); + validators.requireThat(actual, "actual").length().isBetween(3, 5); }, RangeError); }); @@ -696,7 +698,7 @@ suite("ArrayTest", () => 1, 2 ]; - requirements.requireThat(actual, "actual").length().isBetween(3, 5); + validators.requireThat(actual, "actual").length().isBetween(3, 5); }, RangeError); }); @@ -710,14 +712,14 @@ suite("ArrayTest", () => 4, 5 ]; - requirements.requireThat(actual, "actual").length().isBetweenClosed(3, 5); + validators.requireThat(actual, "actual").length().isBetween(3, true, 5, true); }); test("isString", () => { const actual = [1, 2, 3]; const actualIsString = actual.toString(); - requirements.requireThat(actualIsString, "actual").isEqualTo("1,2,3"); + validators.requireThat(actualIsString, "actual").isEqualTo("1,2,3"); }); test("getActual", () => @@ -730,8 +732,8 @@ suite("ArrayTest", () => 4, 5 ]; - const output = requirements.requireThat(input, "input").getActual(); - assert.equal(output, input); + const output = validators.requireThat(input, "input").getValue(); + assert.strictEqual(output, input); }); test("getActual", () => @@ -744,19 +746,17 @@ suite("ArrayTest", () => 4, 5 ]; - const output = requirements.requireThat(input, "input").getActual(); - assert.equal(output, input); + const output = validators.requireThat(input, "input").getValue(); + assert.strictEqual(output, input); }); - test("validateThatNullLength", () => + test("checkIfNullLength", () => { const actual = null; - const expectedMessages = ["actual must be an Array.\n" + - "Actual: null\n" + - "Type : null"]; - const actualFailures = requirements.validateThat(actual as unknown, "actual").isArray().length(). - getFailures(); - const actualMessages = actualFailures.map(failure => failure.getMessage()); - requirements.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages); + const expectedMessages = [`"actual" must be an array. +actual: null`]; + const actualFailures = validators.checkIf(actual as unknown, "actual").isType(Type.ARRAY).elseGetFailures(); + const actualMessages = actualFailures.getMessages(); + validators.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages, "expectedMessages"); }); }); \ No newline at end of file diff --git a/test/BooleanTest.mts b/test/BooleanTest.mts index 8ae1b6d..de5fce8 100644 --- a/test/BooleanTest.mts +++ b/test/BooleanTest.mts @@ -4,19 +4,20 @@ import { } from "mocha"; import {assert} from "chai"; import { - Configuration, TerminalEncoding, - Requirements, - requireThat + requireThat, + Configuration, + Type } from "../src/index.mjs"; -import {TestGlobalConfiguration} from "./TestGlobalConfiguration.mjs"; import {TestCompiler} from "../build/TestCompiler.mjs"; import os from "os"; import {mode} from "../build/mode.mjs"; +import {JavascriptValidatorsImpl} from "../src/internal/validator/JavascriptValidatorsImpl.mjs"; +import {TestApplicationScope} from "./TestApplicationScope.mjs"; + -const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); -const configuration = new Configuration(globalConfiguration); -const requirements = new Requirements(configuration); +const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); let compiler: TestCompiler | undefined; if (mode === "DEBUG") @@ -28,27 +29,27 @@ suite("BooleanTest", () => { test("isTrue", () => { - requirements.requireThat(true, "actual").isTrue(); + validators.requireThat(true, "actual").isTrue(); }); test("isTrue_False", () => { assert.throws(function() { - requirements.requireThat(false, "actual").isTrue(); + validators.requireThat(false, "actual").isTrue(); }, RangeError); }); test("isFalse", () => { - requirements.requireThat(false, "actual").isFalse(); + validators.requireThat(false, "actual").isFalse(); }); test("isFalse_False", () => { assert.throws(function() { - requirements.requireThat(true, "actual").isFalse(); + validators.requireThat(true, "actual").isFalse(); }, RangeError); }); @@ -57,7 +58,7 @@ suite("BooleanTest", () => assert.throws(function() { let actual; - requireThat(actual, "actual").isBoolean(); + requireThat(actual, "actual").isType(Type.BOOLEAN); }, TypeError); }); @@ -71,7 +72,7 @@ suite("BooleanTest", () => const actual = null; requireThat(actual, "actual").isFalse();`; const messages = compiler.compile(code); - assert.equal(messages, "test.mts(4,34): error TS2339: Property 'isFalse' does not exist on " + + assert.strictEqual(messages, "test.mts(4,34): error TS2339: Property 'isFalse' does not exist on " + "type 'ObjectVerifier'." + os.EOL); }).timeout(5000); @@ -85,7 +86,7 @@ suite("BooleanTest", () => const actual = 0; requireThat(actual, "actual").isFalse();`; const messages = compiler.compile(code); - assert.equal(messages, "test.mts(4,34): error TS2339: Property 'isFalse' does not exist on " + + assert.strictEqual(messages, "test.mts(4,34): error TS2339: Property 'isFalse' does not exist on " + "type 'NumberVerifier'." + os.EOL); }).timeout(5000); @@ -99,7 +100,7 @@ suite("BooleanTest", () => const actual = 1; requireThat(actual, "actual").isFalse();`; const messages = compiler.compile(code); - assert.equal(messages, "test.mts(4,34): error TS2339: Property 'isFalse' does not exist on " + + assert.strictEqual(messages, "test.mts(4,34): error TS2339: Property 'isFalse' does not exist on " + "type 'NumberVerifier'." + os.EOL); }).timeout(5000); @@ -113,7 +114,7 @@ suite("BooleanTest", () => const actual = "0"; requireThat(actual, "actual").isFalse();`; const messages = compiler.compile(code); - assert.equal(messages, "test.mts(4,34): error TS2339: Property 'isFalse' does not exist on " + + assert.strictEqual(messages, "test.mts(4,34): error TS2339: Property 'isFalse' does not exist on " + "type 'StringVerifier'." + os.EOL); }).timeout(5000); @@ -127,7 +128,7 @@ suite("BooleanTest", () => const actual = "1"; requireThat(actual, "actual").isTrue();`; const messages = compiler.compile(code); - assert.equal(messages, "test.mts(4,34): error TS2339: Property 'isTrue' does not exist on " + + assert.strictEqual(messages, "test.mts(4,34): error TS2339: Property 'isTrue' does not exist on " + "type 'StringVerifier'." + os.EOL); }).timeout(5000); @@ -141,7 +142,7 @@ suite("BooleanTest", () => const actual = "true"; requireThat(actual, "actual").isTrue();`; const messages = compiler.compile(code); - assert.equal(messages, "test.mts(4,34): error TS2339: Property 'isTrue' does not exist on " + + assert.strictEqual(messages, "test.mts(4,34): error TS2339: Property 'isTrue' does not exist on " + "type 'StringVerifier'." + os.EOL); }).timeout(5000); @@ -155,7 +156,7 @@ suite("BooleanTest", () => const actual = ""; requireThat(actual, "actual").isFalse();`; const messages = compiler.compile(code); - assert.equal(messages, "test.mts(4,34): error TS2339: Property 'isFalse' does not exist on " + + assert.strictEqual(messages, "test.mts(4,34): error TS2339: Property 'isFalse' does not exist on " + "type 'StringVerifier'." + os.EOL); }).timeout(5000); @@ -169,14 +170,14 @@ suite("BooleanTest", () => const actual = "false"; requireThat(actual, "actual").isFalse();`; const messages = compiler.compile(code); - assert.equal(messages, "test.mts(4,34): error TS2339: Property 'isFalse' does not exist on " + + assert.strictEqual(messages, "test.mts(4,34): error TS2339: Property 'isFalse' does not exist on " + "type 'StringVerifier'." + os.EOL); }).timeout(5000); test("getActual", () => { const input = true; - const output = requirements.requireThat(input, "input").getActual(); - assert.equal(output, input); + const output = validators.requireThat(input, "input").getValue(); + assert.strictEqual(output, input); }); }); \ No newline at end of file diff --git a/test/ClassTest.mts b/test/ClassTest.mts deleted file mode 100644 index 9b63062..0000000 --- a/test/ClassTest.mts +++ /dev/null @@ -1,86 +0,0 @@ -import { - suite, - test -} from "mocha"; -import {assert} from "chai"; -import { - Configuration, - TerminalEncoding, - Requirements -} from "../src/index.mjs"; -import {TestGlobalConfiguration} from "./TestGlobalConfiguration.mjs"; -import type {ClassVerifier} from "../src/ClassVerifier.mjs"; -import type {ObjectVerifier} from "../src/ObjectVerifier.mjs"; -import type {ClassConstructor} from "../src/internal/internal.mjs"; - - -const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); -const configuration = new Configuration(globalConfiguration); -const requirements = new Requirements(configuration); - -class MyClass -{ -} - -suite("ClassTest", () => -{ - test("any matches ObjectVerifier", () => - { - const myClassAsClass: ClassVerifier = requirements.requireThat(MyClass, "actual"); - const numberAsClass: ClassVerifier = requirements.requireThat(Number, "actual"); - const anyAsObject: ObjectVerifier = requirements.requireThat(Number as unknown, "actual"); - const objectAsClass: ClassVerifier = requirements.requireThat(Object, "actual"); - }); - - test("isClass", () => - { - const actual = Number; - const expected: ClassConstructor = requirements.requireThat(actual as unknown, "actual"). - isClass(Number).getActual(); - }); - - test("isSupertypeOf", () => - { - requirements.requireThat(Object, "actual").isSupertypeOf(Number); - }); - - test("isSupertypeOf_False", () => - { - assert.throws(function() - { - requirements.requireThat(String, "actual").isSupertypeOf(Number); - }, RangeError); - }); - - test("isSupertypeOf_expectedIsNull", () => - { - assert.throws(function() - { - const actual = Number; - requirements.requireThat(actual, "actual").isSupertypeOf(null as unknown as - new (...args: any[]) => any); - }, TypeError); - }); - - test("isSubtypeOf", () => - { - requirements.requireThat(Number, "actual").isSubtypeOf(Object); - }); - - test("isSubtypeOf_False", () => - { - assert.throws(function() - { - requirements.requireThat(Number, "actual").isSubtypeOf(String); - }, RangeError); - }); - - test("isSubtypeOf_expectedIsNull", () => - { - assert.throws(function() - { - const actual = Number; - requirements.requireThat(actual, "actual").isSubtypeOf(null as unknown as ClassConstructor); - }, TypeError); - }); -}); \ No newline at end of file diff --git a/test/ConfigurationTest.mts b/test/ConfigurationTest.mts index eafa63d..c897857 100644 --- a/test/ConfigurationTest.mts +++ b/test/ConfigurationTest.mts @@ -4,55 +4,40 @@ import { } from "mocha"; import {assert} from "chai"; import { - Configuration, TerminalEncoding, - Requirements + Configuration } from "../src/index.mjs"; -import {TestGlobalConfiguration} from "./TestGlobalConfiguration.mjs"; +import {JavascriptValidatorsImpl} from "../src/internal/validator/JavascriptValidatorsImpl.mjs"; +import {TestApplicationScope} from "./TestApplicationScope.mjs"; -const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); -const configuration = new Configuration(globalConfiguration); -const requirements = new Requirements(configuration); suite("Configuration", () => { - test("withAssertionsDisabled", () => - { - requirements.withAssertionsEnabled().withAssertionsDisabled(); - }); - - test("withAssertionsDisabled().alreadyDisabled", () => - { - requirements.withAssertionsDisabled(); - }); - - test("withAssertionsEnabled", () => - { - requirements.withAssertionsEnabled(); - }); - - test("withAssertionsEnabled().alreadyEnabled", () => - { - requirements.withAssertionsEnabled().withAssertionsEnabled(); - }); - test("getContext", () => { - assert.deepEqual(requirements.getContext(), new Map()); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); + + assert.deepEqual(validators.getContext(), new Map()); }); test("putContext", () => { + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); + const valueNotString = 12345; - const requirements2 = requirements.putContext("key", valueNotString); - assert.equal(requirements, requirements2); + const validators2 = validators.withContext(valueNotString, "key"); + assert.strictEqual(validators, validators2); }); test("putContext(keyNotString)", () => { assert.throws(function() { - requirements.putContext(5 as unknown as string, "value"); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); + validators.withContext("value", 5 as unknown as string); }, TypeError); }); @@ -60,20 +45,27 @@ suite("Configuration", () => { assert.throws(function() { - requirements.putContext(null as unknown as string, "value"); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); + validators.withContext("value", null as unknown as string); }, TypeError); }); test("convertToString(undefined)", () => { - // eslint-disable-next-line no-undefined - const actual = configuration.convertToString(undefined); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); + + const actual = validators.getConfiguration().stringMappers().toString(undefined); assert.strictEqual(actual, "undefined"); }); test("convertToString(null)", () => { - const actual = configuration.convertToString(null); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); + + const actual = validators.getConfiguration().stringMappers().toString(null); assert.strictEqual(actual, "null"); }); }); \ No newline at end of file diff --git a/test/DiffTest.mts b/test/DiffTest.mts index 7bd0564..3746f36 100644 --- a/test/DiffTest.mts +++ b/test/DiffTest.mts @@ -4,19 +4,39 @@ import { } from "mocha"; import {assert} from "chai"; import { - Configuration, EOS_MARKER, NEWLINE_MARKER, Node16Colors, Node16MillionColors, Node256Colors, TerminalEncoding, - TextOnly + TextOnly, + DIFF_INSERT as INSERT, + DIFF_DELETE as DELETE, + DIFF_EQUAL as EQUAL, + JavascriptValidatorsImpl, + Configuration, + MINIMUM_LENGTH_FOR_DIFF } from "../src/internal/internal.mjs"; -import {Requirements} from "../src/index.mjs"; -import {TestGlobalConfiguration} from "./TestGlobalConfiguration.mjs"; +import {TestApplicationScope} from "./TestApplicationScope.mjs"; + +const PADDING = TextOnly.DIFF_PADDING; +/** + * Pads a string until it is long enough to trigger a diff. + * + * @param text the text to process + * @return the updated text + */ +function EXTEND_LENGTH(text: string) +{ + let padded = text; + while (padded.length < MINIMUM_LENGTH_FOR_DIFF) + padded += text; + return padded; +} + suite("DiffTest", () => { /** @@ -24,27 +44,34 @@ suite("DiffTest", () => */ test("diffDeleteThenInsert", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); - const actual = "actual"; - const expected = "expected"; + const actual = EXTEND_LENGTH("actual"); + const expected = EXTEND_LENGTH("expected"); try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; - const expectedMessage = "Actual : actual" + - TextOnly.DIFF_PADDING.repeat("expected".length) + EOS_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_DELETE.repeat("actual".length) + - TextOnly.DIFF_INSERT.repeat("expected".length) + TextOnly.DIFF_EQUAL.repeat(EOS_MARKER.length) + "\n" + - "Expected: " + " ".repeat("actual".length) + "expected" + EOS_MARKER; - assert(actualMessage.includes(expectedMessage), "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual : "${EXTEND_LENGTH("actual")}"\ +${PADDING.repeat(("\"" + EXTEND_LENGTH("expected") + "\"").length)}${EOS_MARKER} +diff : ${DELETE.repeat(("\"" + EXTEND_LENGTH("actual") + "\"").length)}\ +${INSERT.repeat(("\"" + EXTEND_LENGTH("expected") + "\"").length)}\ +${EQUAL.repeat(EOS_MARKER.length)} +expected: ${PADDING.repeat(("\"" + EXTEND_LENGTH("actual") + "\"").length)}"\ +${EXTEND_LENGTH("expected")}"${EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); @@ -53,26 +80,30 @@ suite("DiffTest", () => */ test("diffMissingWhitespace", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); - const actual = "\"key\": \"value \""; - const expected = "\"key\": \"value\""; + const actual = `"key": "value "`; + const expected = `"key": "value"`; try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; - const expectedMessage = "Actual : \"key\": \"value \"" + EOS_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_EQUAL.repeat(13) + TextOnly.DIFF_DELETE + - TextOnly.DIFF_EQUAL.repeat(1 + EOS_MARKER.length) + "\n" + - "Expected: \"key\": \"value \"" + EOS_MARKER; - assert(actualMessage.includes(expectedMessage), "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual : "\\"key\\": \\"value \\""${EOS_MARKER} +diff : ${EQUAL.repeat(`"\\"key\\": \\"value`.length)}${DELETE}${EQUAL.repeat(`\\""${EOS_MARKER}`.length)} +expected: "\\"key\\": \\"value \\""${EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); @@ -81,32 +112,38 @@ suite("DiffTest", () => */ test("diffNewlinePrefix", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); - const actual = "\nactual"; - const expected = "expected"; + const actual = "\n" + EXTEND_LENGTH("actual"); + const expected = EXTEND_LENGTH("expected"); try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; - const expectedMessage = "Actual@0 : " + NEWLINE_MARKER + - TextOnly.DIFF_PADDING.repeat(("expected" + EOS_MARKER).length) + "\n" + - "Diff : " + TextOnly.DIFF_DELETE.repeat(NEWLINE_MARKER.length) + - TextOnly.DIFF_INSERT.repeat("expected".length) + TextOnly.DIFF_EQUAL.repeat(EOS_MARKER.length) + "\n" + - "Expected@0: " + TextOnly.DIFF_PADDING.repeat(NEWLINE_MARKER.length) + "expected" + EOS_MARKER + "\n" + - "\n" + - "Actual@1 : actual" + EOS_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_DELETE.repeat("actual".length) + - TextOnly.DIFF_EQUAL.repeat(EOS_MARKER.length) + "\n" + - "Expected : " + TextOnly.DIFF_PADDING.repeat(("actual" + EOS_MARKER).length); - assert(actualMessage.includes(expectedMessage), "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual@0 : "${NEWLINE_MARKER} +diff : ${DELETE.repeat(("\"" + NEWLINE_MARKER).length)} +expected : ${PADDING.repeat(("\"" + NEWLINE_MARKER).length)} + +actual@1 : ${EXTEND_LENGTH("actual")}"\ +${PADDING.repeat(("\"" + EXTEND_LENGTH("expected") + "\"").length)}${EOS_MARKER} +diff : ${DELETE.repeat((EXTEND_LENGTH("actual") + "\"").length)}\ +${INSERT.repeat(("\"" + EXTEND_LENGTH("expected") + "\"").length)}\ +${EQUAL.repeat(EOS_MARKER.length)} +expected@0: ${PADDING.repeat((EXTEND_LENGTH("actual") + "\"").length)}\ +"${EXTEND_LENGTH("expected")}"${EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); @@ -115,32 +152,36 @@ suite("DiffTest", () => */ test("diffNewlinePostfix", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); - const actual = "actual\n"; - const expected = "expected"; + const actual = EXTEND_LENGTH("actual") + "\n"; + const expected = EXTEND_LENGTH("expected"); try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; - const expectedMessage = "Actual@0 : actual" + NEWLINE_MARKER + - TextOnly.DIFF_PADDING.repeat(("expected" + EOS_MARKER).length) + "\n" + - "Diff : " + TextOnly.DIFF_DELETE.repeat(("actual" + NEWLINE_MARKER).length) + - TextOnly.DIFF_INSERT.repeat("expected".length) + TextOnly.DIFF_EQUAL.repeat(NEWLINE_MARKER.length) + - "\n" + - "Expected@0: " + TextOnly.DIFF_PADDING.repeat(("actual" + NEWLINE_MARKER).length) + "expected" + - EOS_MARKER + "\n" + - "\n" + - "Actual@1 : " + EOS_MARKER + "\n" + - "Expected : " + TextOnly.DIFF_PADDING.repeat(EOS_MARKER.length); - assert(actualMessage.includes(expectedMessage), "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual@0 : "${EXTEND_LENGTH("actual")}${NEWLINE_MARKER} +diff : ${DELETE.repeat(("\"" + EXTEND_LENGTH("actual") + NEWLINE_MARKER).length)} +expected : ${PADDING.repeat(("\"" + EXTEND_LENGTH("actual") + NEWLINE_MARKER).length)} + +actual@1 : "${PADDING.repeat(("\"" + EXTEND_LENGTH("expected") + "\"").length)}\ +${EOS_MARKER} +diff : ${DELETE}${INSERT.repeat(("\"" + EXTEND_LENGTH("expected") + "\"").length)}\ +${EQUAL.repeat(EOS_MARKER.length)} +expected@0: ${PADDING}"${EXTEND_LENGTH("expected")}"${EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); @@ -149,34 +190,38 @@ suite("DiffTest", () => */ test("matchAcrossLines", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); - const actual = "\n\nvalue"; - const expected = "value"; + const actual = EXTEND_LENGTH("prefix") + "\n\nvalue"; + const expected = EXTEND_LENGTH("prefix") + "value"; try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; - const expectedMessage = "Actual@0 : " + NEWLINE_MARKER + - TextOnly.DIFF_PADDING.repeat(("value" + EOS_MARKER).length) + "\n" + - "Diff : " + TextOnly.DIFF_DELETE.repeat(NEWLINE_MARKER.length) + - TextOnly.DIFF_EQUAL.repeat(("value" + EOS_MARKER).length) + "\n" + - "Expected@0: " + TextOnly.DIFF_PADDING.repeat(NEWLINE_MARKER.length) + "value" + EOS_MARKER + "\n" + - "\n" + - "Actual@1 : " + NEWLINE_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_DELETE.repeat(NEWLINE_MARKER.length) + "\n" + - "Expected : " + TextOnly.DIFF_PADDING.repeat(NEWLINE_MARKER.length) + "\n" + - "\n" + - "Actual@2 : value" + EOS_MARKER + "\n" + - "Expected : " + TextOnly.DIFF_PADDING.repeat(("value" + EOS_MARKER).length); - assert(actualMessage.includes(expectedMessage), "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual@0 : "${EXTEND_LENGTH("prefix")}${NEWLINE_MARKER} +diff : ${EQUAL.repeat(("\"" + EXTEND_LENGTH("prefix")).length)}\ +${DELETE.repeat(NEWLINE_MARKER.length)} +expected@0: "${EXTEND_LENGTH("prefix")}${PADDING.repeat(NEWLINE_MARKER.length)} + +actual@1 : ${NEWLINE_MARKER} +diff : ${DELETE.repeat(NEWLINE_MARKER.length)} +expected : ${PADDING.repeat(NEWLINE_MARKER.length)} + +actual@2 : value"${EOS_MARKER} +expected@0: value"${EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); @@ -185,36 +230,40 @@ suite("DiffTest", () => */ test("skipDuplicateLinesTest", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); const actual = "1\n2\n3\n4\n5"; const expected = "1\n2\n9\n4\n5"; try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; - const expectedMessage = "Actual@0 : 1" + NEWLINE_MARKER + "\n" + - "Expected@0: 1" + NEWLINE_MARKER + "\n" + - "\n" + - "[...]\n" + - "\n" + - "Actual@2 : 3" + TextOnly.DIFF_PADDING + NEWLINE_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_DELETE + TextOnly.DIFF_INSERT + - TextOnly.DIFF_EQUAL.repeat(NEWLINE_MARKER.length) + "\n" + - "Expected@2: " + TextOnly.DIFF_PADDING + "9" + NEWLINE_MARKER + "\n" + - "\n" + - "[...]\n" + - "\n" + - "Actual@4 : 5\\0\n" + - "Expected@4: 5\\0"; - assert(actualMessage.includes(expectedMessage), "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual@0 : "1${NEWLINE_MARKER} +expected@0: "1${NEWLINE_MARKER} + +[...] + +actual@2 : 3${PADDING + NEWLINE_MARKER} +diff : ${DELETE + INSERT + EQUAL.repeat(NEWLINE_MARKER.length)} +expected@2: ${PADDING}9${NEWLINE_MARKER} + +[...] + +actual@4 : 5"${EOS_MARKER} +expected@4: 5"${EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); @@ -223,134 +272,154 @@ suite("DiffTest", () => */ test("charlesTest", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); const actual = "The dog is brown"; const expected = "The fox is down"; try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; - const expectedMessage = "Actual : The dog" + - TextOnly.DIFF_PADDING.repeat("fox".length) + " is br" + TextOnly.DIFF_PADDING + "own" + EOS_MARKER + - "\n" + - "Diff : " + TextOnly.DIFF_EQUAL.repeat(4) + TextOnly.DIFF_DELETE.repeat(3) + - TextOnly.DIFF_INSERT.repeat(3) + TextOnly.DIFF_EQUAL.repeat(4) + - TextOnly.DIFF_DELETE.repeat(2) + TextOnly.DIFF_INSERT + - TextOnly.DIFF_EQUAL.repeat(3 + EOS_MARKER.length) + "\n" + - "Expected: The " + TextOnly.DIFF_PADDING.repeat("dog".length) + "fox is " + - TextOnly.DIFF_PADDING.repeat("br".length) + "down" + EOS_MARKER; - assert(actualMessage.includes(expectedMessage), "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual : "The dog${PADDING.repeat("fox".length)} is br\ +${PADDING}own"${EOS_MARKER} +diff : ${EQUAL.repeat(`"The `.length) + DELETE.repeat("dog".length) + INSERT.repeat("fox".length) + + EQUAL.repeat(" is ".length) + DELETE.repeat("br".length) + INSERT.repeat("d".length) + + EQUAL.repeat(`own"${EOS_MARKER}`.length)} +expected: "The ${PADDING.repeat("dog".length)}fox is \ +${PADDING.repeat("br".length)}down"${EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); test("smallChangeBeforeWord", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); const actual = "you like me?"; const expected = "Don't you like me?"; try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; - const expectedMessage = "Actual : " + TextOnly.DIFF_PADDING.repeat("Don't ".length) + "you like me?" + - EOS_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_INSERT.repeat("Don't ".length) + - TextOnly.DIFF_EQUAL.repeat("you like me?".length + EOS_MARKER.length) + "\n" + - "Expected: Don't you like me?" + EOS_MARKER; - assert(actualMessage.includes(expectedMessage), "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual : "${PADDING.repeat("Don't ".length)}you like me?"${EOS_MARKER} +diff : ${EQUAL.repeat(`"`.length)}${INSERT.repeat("Don't ".length) + + EQUAL.repeat(`you like me?"`.length + EOS_MARKER.length)} +expected: "Don't you like me?"${EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); test("smallChangeInMiddle", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); const actual = "I lice dogs"; const expected = "I like dogs"; try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; - const expectedMessage = "Actual : I lic" + TextOnly.DIFF_PADDING + "e dogs" + EOS_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_EQUAL.repeat("I li".length) + TextOnly.DIFF_DELETE + TextOnly.DIFF_INSERT + - TextOnly.DIFF_EQUAL.repeat("e dogs".length + EOS_MARKER.length) + "\n" + - "Expected: I li" + TextOnly.DIFF_PADDING + "ke dogs" + EOS_MARKER; - assert(actualMessage.includes(expectedMessage), "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual : "I lic${PADDING}e dogs"${EOS_MARKER} +diff : ${EQUAL.repeat(`"I li`.length) + DELETE + INSERT + + EQUAL.repeat(`e dogs"`.length + EOS_MARKER.length)} +expected: "I li${PADDING + `ke dogs"` + EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); test("smallChangeAfterWord", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); const actual = "I like dog"; const expected = "I like dogs"; try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; - const expectedMessage = "Actual : I like dog" + TextOnly.DIFF_PADDING + EOS_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_EQUAL.repeat("I like dog".length) + TextOnly.DIFF_INSERT + - TextOnly.DIFF_EQUAL.repeat(EOS_MARKER.length) + "\n" + - "Expected: I like dogs" + EOS_MARKER; - assert(actualMessage.includes(expectedMessage), "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual : "I like dog${PADDING}"${EOS_MARKER} +diff : ${EQUAL.repeat(`"I like dog`.length) + INSERT + EQUAL.repeat(`"${EOS_MARKER}`.length)} +expected: "I like dogs"${EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); test("largeChangeInMiddle", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); const actual = "I lices dogs"; const expected = "I like dogs"; try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; - const expectedMessage = "Actual : I lices" + TextOnly.DIFF_PADDING.repeat("like".length) + " dogs" + - EOS_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_EQUAL.repeat("I ".length) + TextOnly.DIFF_DELETE.repeat("lices".length) + - TextOnly.DIFF_INSERT.repeat("like".length) + - TextOnly.DIFF_EQUAL.repeat(" dogs".length + EOS_MARKER.length) + "\n" + - "Expected: I " + TextOnly.DIFF_PADDING.repeat("lices".length) + "like dogs" + EOS_MARKER; - assert(actualMessage.includes(expectedMessage), "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual : "I lices${PADDING.repeat("like".length)} dogs"\ +${EOS_MARKER} +diff : ${EQUAL.repeat(`"I `.length)}${DELETE.repeat("lices".length)}${INSERT.repeat("like".length)}\ +${EQUAL.repeat(` dogs"${EOS_MARKER}`.length)} +expected: "I ${PADDING.repeat("lices".length)}like dogs"${EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); @@ -359,110 +428,125 @@ suite("DiffTest", () => */ test("diffMiddleWhitespace", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); const actual = "one\n" + "\n" + - "three"; + "three\n"; const expected = "one\n" + " \n" + - "three"; + "three\n"; try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; - const expectedMessage = "actual must be equal to " + expected + ".\n" + - "\n" + - "Actual@0 : one" + NEWLINE_MARKER + "\n" + - "Expected@0: one" + NEWLINE_MARKER + "\n" + - "\n" + - "Actual@1 : " + TextOnly.DIFF_PADDING.repeat(3) + NEWLINE_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_INSERT.repeat(3) + TextOnly.DIFF_PADDING.repeat(NEWLINE_MARKER.length) + - "\n" + - "Expected@1: " + TextOnly.DIFF_PADDING.repeat(3) + NEWLINE_MARKER + "\n" + - "\n" + - "Actual@2 : three" + EOS_MARKER + "\n" + - "Expected@2: three" + EOS_MARKER; - assert(actualMessage.includes(expectedMessage), "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +"actual" had an unexpected value. + +actual@0 : "one${NEWLINE_MARKER} +expected@0: "one${NEWLINE_MARKER} + +actual@1 : ${PADDING.repeat(" ".length) + NEWLINE_MARKER} +diff : ${INSERT.repeat(" ".length) + PADDING.repeat(NEWLINE_MARKER.length)} +expected@1: ${PADDING.repeat(" ".length) + NEWLINE_MARKER} + +[...] + +actual@3 : "${EOS_MARKER} +expected@3: "${EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); - test("arrayOfIntegers", () => + test("arrayOfNumbers", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); try { const actual = [1, 2, 3, 4, 5]; const expected = [1, 2, 9, 4, 5]; - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; - const expectedMessage = "Actual[0] : 1" + EOS_MARKER + "\n" + - "Expected[0]: 1" + EOS_MARKER + "\n" + - "\n" + - "[...]\n" + - "\n" + - "Actual[2] : 3" + TextOnly.DIFF_PADDING.repeat(1) + EOS_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_DELETE + TextOnly.DIFF_INSERT + - TextOnly.DIFF_EQUAL.repeat(NEWLINE_MARKER.length) + "\n" + - "Expected[2]: " + TextOnly.DIFF_PADDING + "9" + EOS_MARKER + "\n" + - "\n" + - "[...]\n" + - "\n" + - "Actual[4] : 5" + EOS_MARKER + "\n" + - "Expected[4]: 5" + EOS_MARKER; - assert(actualMessage.includes(expectedMessage), "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual[0] : 1${EOS_MARKER} +expected[0]: 1${EOS_MARKER} + +[...] + +actual[2] : 3${PADDING.repeat(1) + EOS_MARKER} +diff : ${DELETE + INSERT + EQUAL.repeat(NEWLINE_MARKER.length)} +expected[2]: ${PADDING}9${EOS_MARKER} + +[...] + +actual[4] : 5${EOS_MARKER} +expected[4]: 5${EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); test("arrayOfStrings", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); try { const actual = ["1", "foo\nbar", "3"]; const expected = ["1", "bar\nfoo", "3"]; - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; - const expectedMessage = "Actual[0] : 1" + EOS_MARKER + "\n" + - "Expected[0] : 1" + EOS_MARKER + "\n" + - "\n" + - "Actual[1]@0 : " + TextOnly.DIFF_PADDING.repeat(("bar" + NEWLINE_MARKER).length) + "foo" + - NEWLINE_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_INSERT.repeat(("bar" + NEWLINE_MARKER).length) + - TextOnly.DIFF_EQUAL.repeat("foo".length) + TextOnly.DIFF_DELETE.repeat(NEWLINE_MARKER.length) + "\n" + - "Expected[1]@0: bar" + NEWLINE_MARKER + TextOnly.DIFF_PADDING.repeat(("foo" + NEWLINE_MARKER).length) + - "\n" + - "\n" + - "Actual[1]@1 : " + TextOnly.DIFF_PADDING.repeat("foo".length) + "bar" + EOS_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_EQUAL.repeat("foo".length) + - TextOnly.DIFF_DELETE.repeat("bar".length) + TextOnly.DIFF_EQUAL.repeat(EOS_MARKER.length) + "\n" + - "Expected[1]@1: foo" + TextOnly.DIFF_PADDING.repeat("bar".length) + EOS_MARKER + "\n" + - "\n" + - "Actual[2] : 3" + EOS_MARKER + "\n" + - "Expected[2] : 3" + EOS_MARKER; - assert(actualMessage.includes(expectedMessage), "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual[0] : "1"${EOS_MARKER} +expected[0] : "1"${EOS_MARKER} + +actual[1]@0 : "foo${NEWLINE_MARKER} +diff : ${EQUAL}${DELETE.repeat(("foo" + NEWLINE_MARKER).length)} +expected[1]@0: "${PADDING.repeat(("foo" + NEWLINE_MARKER).length)} + +actual[1]@1 : bar${PADDING.repeat(NEWLINE_MARKER.length)} +diff : ${EQUAL.repeat("bar".length)}${INSERT.repeat(NEWLINE_MARKER.length)} +expected[1]@0: bar${NEWLINE_MARKER} + +actual[1]@1 : ${PADDING.repeat("foo".length)}"${EOS_MARKER} +diff : ${INSERT.repeat("foo".length)}${EQUAL.repeat(("\"" + NEWLINE_MARKER).length)} +expected[1]@1: foo"${EOS_MARKER} + +actual[2] : "3"${EOS_MARKER} +expected[2] : "3"${EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); @@ -471,200 +555,129 @@ suite("DiffTest", () => */ test("diffArraySize", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); - const actual = "int[6]"; - const expected = "int[5]"; + const actual = "int[1234567890]"; + const expected = "int[1234 67890]"; try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { - const scheme = new TextOnly(); - const actualMessage = (e as Error).message; - const expectedMessage = "Actual : int[6" + scheme.decoratePadding(1) + "]" + EOS_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_EQUAL.repeat(4) + TextOnly.DIFF_DELETE + TextOnly.DIFF_INSERT + - TextOnly.DIFF_EQUAL.repeat(1 + EOS_MARKER.length) + "\n" + - "Expected: int[" + scheme.decoratePadding(1) + "5]" + EOS_MARKER; - assert(actualMessage.includes(expectedMessage), - "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual : "int[12345${PADDING}67890]\"${EOS_MARKER} +diff : ${EQUAL.repeat(`"int[1234`.length)}${DELETE}${INSERT}\ +${EQUAL.repeat(`67890]"${EOS_MARKER}`.length)} +expected: "int[1234${PADDING.repeat(2)}67890]"${EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); /** - * Ensures that DiffGenerator.ReduceDeltasPerWord does not modify EQUAL deltas between matches. Meaning, - * it should not collapse "-same-" into the [DELETE, INSERT] pair associated with "different"/"maybe". + * Ensures that DiffGenerator.ReduceDeltasPerWord does not modify EQUAL deltas between matches. Meaning, it + * should not collapse "-same-" into the [DELETE, INSERT] pair associated with "different"/"maybe". */ test("equalDeltaAfterReduceDeltasPerWord", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); const actual = "different-same-different"; const expected = "maybe-same-maybe"; try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; - const expectedMessage = "Actual : different" + TextOnly.DIFF_PADDING.repeat("maybe".length) + - "-same-different" + TextOnly.DIFF_PADDING.repeat("maybe".length) + EOS_MARKER + - "\n" + - "Diff : " + TextOnly.DIFF_DELETE.repeat("different".length) + - TextOnly.DIFF_INSERT.repeat("maybe".length) + TextOnly.DIFF_EQUAL.repeat("-same-".length) + - TextOnly.DIFF_DELETE.repeat("different".length) + TextOnly.DIFF_INSERT.repeat("maybe".length) + - TextOnly.DIFF_EQUAL.repeat(NEWLINE_MARKER.length) + "\n" + - "Expected: " + TextOnly.DIFF_PADDING.repeat("different".length) + "maybe-same-" + - TextOnly.DIFF_PADDING.repeat("different".length) + "maybe" + EOS_MARKER; - assert(actualMessage.includes(expectedMessage), - "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual : "different${PADDING.repeat(`"maybe`.length)}-same-different"\ +${PADDING.repeat(`maybe"`.length) + EOS_MARKER} +diff : ${DELETE.repeat(`"different`.length)}\ +${INSERT.repeat(`"maybe`.length)}\ +${EQUAL.repeat("-same-".length)}\ +${DELETE.repeat(`different"`.length)}\ +${INSERT.repeat(`maybe"`.length)}\ +${EQUAL.repeat(NEWLINE_MARKER.length)} +expected: ${PADDING.repeat(`"different`.length)}\ +"maybe-same-${PADDING.repeat(`different"`.length)}maybe"${EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); /** * When processing DELETE "same\nactual" followed by INSERT "same\nexpected", ensure that actual and - * expected keep track of different "diff" line numbers. Otherwise, the DELETE advances to the next line - * and INSERT updates the diff of the wrong line number. We end up with: + * expected keep track of different "diff" line numbers. Otherwise, the DELETE advances to the next line and + * INSERT updates the diff of the wrong line number. We end up with: * - *

-	 * Actual@1  : same\n
-	 * Diff      : ------
-	 * Expected  :
+	 * ```console
+	 * actual@1  : same\n
+	 * diff      : ------
+	 * expected  :
 	 *
-	 * Actual@2  : actual
-	 * Diff      : ------++++++
-	 * Expected@1:       same\n
-	 * 
- *

+ * actual@2 : actual + * diff : ------++++++ + * expected@1: same\n + * ``` * instead of: - * - *


-	 * Actual    : same\n
-	 * Expected  : same\n
-	 * 
+ * ``` + * actual : same\n + * expected : same\n + * ``` */ test("independentDiffLineNumbers", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); const actual = "actual\nsame\nactual actual"; const expected = "expected\nsame\nexpected expected"; try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; - const expectedMessage = "Actual@0 : actual" + TextOnly.DIFF_PADDING.repeat("expected".length) + - NEWLINE_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_DELETE.repeat("actual".length) + - TextOnly.DIFF_INSERT.repeat("expected".length) + TextOnly.DIFF_EQUAL.repeat(NEWLINE_MARKER.length) + - "\n" + - "Expected@0: " + TextOnly.DIFF_PADDING.repeat("actual".length) + "expected" + NEWLINE_MARKER + "\n" + - "\n" + - "[...]\n" + - "\n" + - "Actual@2 : actual " + TextOnly.DIFF_PADDING.repeat("expected".length) + "actual" + - TextOnly.DIFF_PADDING.repeat("expected".length) + EOS_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_DELETE.repeat("actual".length) + - TextOnly.DIFF_INSERT.repeat("expected".length) + TextOnly.DIFF_EQUAL + - TextOnly.DIFF_DELETE.repeat("actual".length) + TextOnly.DIFF_INSERT.repeat("expected".length) + - TextOnly.DIFF_EQUAL.repeat(EOS_MARKER.length) + "\n" + - "Expected@2: " + TextOnly.DIFF_PADDING.repeat("actual".length) + "expected " + - TextOnly.DIFF_PADDING.repeat("actual".length) + "expected" + EOS_MARKER; - assert(actualMessage.includes(expectedMessage), - "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); - } - }); + const expectedMessage = `\ +actual@0 : "actual${PADDING.repeat(`"expected`.length) + NEWLINE_MARKER} +diff : ${DELETE.repeat(`"actual`.length) + INSERT.repeat(`"expected`.length) + + EQUAL.repeat(NEWLINE_MARKER.length)} +expected@0: ${PADDING.repeat(`"actual`.length)}"expected${NEWLINE_MARKER} - /** - * Ensures that "expected" is included in the error message when it is shorter than the terminal width. - */ - test("expectedShorterThanTerminalWidth", () => - { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE, - "actual must be equal to expected.".length + 1); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); +[...] - const actual = "actual"; - const expected = "expected"; - try - { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); - } - catch (e) - { - const actualMessage = (e as Error).message; - assert(actualMessage.includes("must be equal to " + expected), - "Actual:\n" + actualMessage); - } - }); +actual@2 : actual ${PADDING.repeat("expected".length)}actual"${PADDING.repeat(`expected"`.length) + EOS_MARKER} +diff : ${DELETE.repeat("actual".length) + INSERT.repeat("expected".length) + EQUAL + + DELETE.repeat(`actual"`.length) + INSERT.repeat(`expected"`.length) + EQUAL.repeat(EOS_MARKER.length)} +expected@2: ${PADDING.repeat("actual".length)}expected${PADDING.repeat(` actual"`.length)}expected"\ +${EOS_MARKER}`; - /** - * Ensures that "expected" is excluded from the error message when it is equal to the terminal width. - */ - test("expectedEqualToTerminalWidth", () => - { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE, - "actual must be equal to expected.".length); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} - const actual = "actual"; - const expected = "expected"; - try - { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); - } - catch (e) - { - const actualMessage = (e as Error).message; - assert(!actualMessage.includes("must be equal to " + expected), - "Actual:\n" + actualMessage); - } - }); - - /** - * Ensures that "expected" is excluded from the error message when it is equal to the terminal width. - */ - test("expectedLongerThanTerminalWidth", () => - { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE, - "actual must be equal to expected.".length - 1); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); - - const actual = "actual"; - const expected = "expected"; - try - { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); - } - catch (e) - { - const actualMessage = (e as Error).message; - assert(!actualMessage.includes("must be equal to " + expected), - "Actual:\n" + actualMessage); +**************** expected: +${expectedMessage}`); } }); @@ -673,31 +686,34 @@ suite("DiffTest", () => */ test("diffArraySize_16Colors", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NODE_16_COLORS); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope( + TerminalEncoding.NODE_16_COLORS), Configuration.DEFAULT); - const actual = "int[6]"; - const expected = "int[5]"; + const actual = "int[1234567890]"; + const expected = "int[1234 67890]"; try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const scheme = new Node16Colors(); const actualMessage = (e as Error).message; - const expectedMessage = "Actual : " + scheme.decorateEqualText("int[") + - scheme.decorateDeletedText("6") + scheme.decoratePadding(1) + - scheme.decorateEqualText("]") + EOS_MARKER + "\n" + - "Expected: " + scheme.decorateEqualText("int[") + - scheme.decoratePadding(1) + scheme.decorateInsertedText("5") + - scheme.decorateEqualText("]") + EOS_MARKER; - assert(actualMessage.includes(expectedMessage), - "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual : ${scheme.decorateEqualText(`"int[1234`) + + scheme.decorateDeletedText("5") + scheme.decoratePadding(scheme.getPaddingMarker()) + + scheme.decorateEqualText(`67890]"`) + EOS_MARKER} +expected: ${scheme.decorateEqualText(`"int[1234`) + scheme.decoratePadding(scheme.getPaddingMarker()) + + scheme.decorateInsertedText(" ") + scheme.decorateEqualText(`67890]"`) + EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); @@ -706,31 +722,34 @@ suite("DiffTest", () => */ test("diffArraySize_256Colors", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NODE_256_COLORS); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope( + TerminalEncoding.NODE_256_COLORS), Configuration.DEFAULT); - const actual = "int[6]"; - const expected = "int[5]"; + const actual = "int[1234567890]"; + const expected = "int[1234 67890]"; try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const scheme = new Node256Colors(); const actualMessage = (e as Error).message; - const expectedMessage = "Actual : " + scheme.decorateEqualText("int[") + - scheme.decorateDeletedText("6") + scheme.decoratePadding(1) + - scheme.decorateEqualText("]") + EOS_MARKER + "\n" + - "Expected: " + scheme.decorateEqualText("int[") + - scheme.decoratePadding(1) + scheme.decorateInsertedText("5") + - scheme.decorateEqualText("]") + EOS_MARKER; - assert(actualMessage.includes(expectedMessage), - "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual : ${scheme.decorateEqualText(`"int[1234`) + + scheme.decorateDeletedText("5") + scheme.decoratePadding(scheme.getPaddingMarker()) + + scheme.decorateEqualText(`67890]"`) + EOS_MARKER} +expected: ${scheme.decorateEqualText(`"int[1234`) + scheme.decoratePadding(scheme.getPaddingMarker()) + + scheme.decorateInsertedText(" ") + scheme.decorateEqualText(`67890]"`) + EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); @@ -739,31 +758,34 @@ suite("DiffTest", () => */ test("diffArraySize_16MillionColors", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NODE_16MILLION_COLORS); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope( + TerminalEncoding.NODE_16MILLION_COLORS), Configuration.DEFAULT); - const actual = "int[6]"; - const expected = "int[5]"; + const actual = "int[1234567890]"; + const expected = "int[1234 67890]"; try { - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const scheme = new Node16MillionColors(); const actualMessage = (e as Error).message; - const expectedMessage = "Actual : " + scheme.decorateEqualText("int[") + - scheme.decorateDeletedText("6") + scheme.decoratePadding(1) + - scheme.decorateEqualText("]") + EOS_MARKER + "\n" + - "Expected: " + scheme.decorateEqualText("int[") + - scheme.decoratePadding(1) + scheme.decorateInsertedText("5") + scheme.decorateEqualText("]") + - EOS_MARKER; - assert(actualMessage.includes(expectedMessage), - "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual : ${scheme.decorateEqualText(`"int[1234`) + + scheme.decorateDeletedText("5") + scheme.decoratePadding(scheme.getPaddingMarker()) + + scheme.decorateEqualText(`67890]"`) + EOS_MARKER} +expected: ${scheme.decorateEqualText(`"int[1234`) + scheme.decoratePadding(scheme.getPaddingMarker()) + + scheme.decorateInsertedText(" ") + scheme.decorateEqualText(`67890]"`) + EOS_MARKER}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); @@ -772,31 +794,35 @@ suite("DiffTest", () => */ test("emptyLineNumber_16Colors", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NODE_16_COLORS); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope( + TerminalEncoding.NODE_16_COLORS), Configuration.DEFAULT); try { - const actual = "foo\nbar"; - const expected = "bar"; - requirements.requireThat(actual, "actual").isEqualTo(expected); - assert.fail("Expected method to throw exception"); + const actual = EXTEND_LENGTH("prefix") + "foo\nbar"; + const expected = EXTEND_LENGTH("prefix") + "bar"; + validators.requireThat(actual, "actual").isEqualTo(expected); + assert.fail("Expected method to throw error"); } catch (e) { const scheme = new Node16Colors(); const actualMessage = (e as Error).message; - const expectedMessage = "Actual@0 : " + scheme.decorateDeletedText("foo" + NEWLINE_MARKER) + - scheme.decoratePadding(("bar" + EOS_MARKER).length) + "\n" + - "Expected@0: " + scheme.decoratePadding(("foo" + NEWLINE_MARKER).length) + - scheme.decorateEqualText("bar" + EOS_MARKER) + "\n" + - "\n" + - "Actual@1 : " + scheme.decorateEqualText("bar" + EOS_MARKER) + "\n" + - "Expected : " + scheme.decoratePadding(("bar" + EOS_MARKER).length); - assert(actualMessage.includes(expectedMessage), "Expected:\n" + expectedMessage + - "\n****************\nActual:\n" + actualMessage); + const expectedMessage = `\ +actual@0 : ${scheme.decorateEqualText(`"${EXTEND_LENGTH("prefix")}`) + scheme.decorateDeletedText(`foo${NEWLINE_MARKER}`)} +expected@0: ${scheme.decorateEqualText(`"${EXTEND_LENGTH("prefix")}`) + + scheme.decoratePadding(scheme.getPaddingMarker().repeat((`foo${NEWLINE_MARKER}`).length))} + +actual@1 : ${scheme.decorateEqualText(`bar"${EOS_MARKER}`)} +expected@0: ${scheme.decorateEqualText(`bar"${EOS_MARKER}`)}`; + + assert(actualMessage.includes(expectedMessage), `\ +**************** Actual: +${actualMessage} + +**************** expected: +${expectedMessage}`); } }); }); \ No newline at end of file diff --git a/test/GenericTest.mts b/test/GenericTest.mts new file mode 100644 index 0000000..d6b9987 --- /dev/null +++ b/test/GenericTest.mts @@ -0,0 +1,267 @@ +import { + suite, + test +} from "mocha"; +import {assert} from "chai"; +import { + TerminalEncoding, + type ObjectValidator, + Configuration, + Type +} from "../src/index.mjs"; +import {TestCompiler} from "../build/TestCompiler.mjs"; +import * as os from "os"; +import {mode} from "../build/mode.mjs"; +import {JavascriptValidatorsImpl} from "../src/internal/internal.mjs"; +import {TestApplicationScope} from "./TestApplicationScope.mjs"; + + +const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); + +let compiler: TestCompiler | undefined; +if (mode === "DEBUG") + compiler = undefined; +else + compiler = new TestCompiler(); + +suite("BaseTest", () => +{ + test("nameIsNull", () => + { + assert.throws(function() + { + const actual = {} as object | null; + // Changes the compile-time type of the value to null + const isNull: null = validators.requireThat(actual, null as unknown as string).isNull().getValue(); + }, TypeError); + }); + + test("nameIsEmpty", () => + { + assert.throws(function() + { + const actual = {}; + validators.requireThat(actual, ""); + }, RangeError); + }); + + test("isEqualTo", () => + { + const actual = "actual"; + validators.requireThat(actual, "actual").isEqualTo(actual); + }); + + test("isEqual_False", () => + { + const actual = {}; + assert.throws(function() + { + validators.requireThat(actual, "actual").isEqualTo("expected"); + }, RangeError); + assert.throws(function() + { + validators.requireThat(actual, "actual").isEqualTo("expected", "expected"); + }, RangeError); + }); + + test("isEqual_sameToStringDifferentTypes", () => + { + if (!compiler) + return; + const code = + `import {requireThat} from "./target/publish/node/index.mjs"; + + const actual = "null" + requireThat(actual, "actual").isEqualTo(null);`; + const messages = compiler.compile(code); + assert.strictEqual(messages, "test.mts(4,44): error TS2345: Argument of type 'null' is not assignable " + + "to parameter of type 'string'." + os.EOL); + }).timeout(5000); + + test("isEqual_nullToNull", () => + { + const actual = null; + validators.requireThat(actual, "actual").isEqualTo(actual); + }); + + test("isEqualTo_nullToNotNull", () => + { + if (!compiler) + return; + const code = + `import {requireThat} from "./target/publish/node/index.mjs"; + + const actual = null; + requireThat(actual, "actual").isEqualTo("expected");`; + const messages = compiler.compile(code); + assert.strictEqual(messages, `test.mts(4,44): error TS2345: Argument of type '"expected"' is not \ +assignable to parameter of type 'null'.` + os.EOL); + }).timeout(5000); + + test("isEqualTo_notNullToNull", () => + { + if (!compiler) + return; + const code = + `import {requireThat} from "./target/publish/node/index.mjs"; + + const actual = "actual"; + requireThat(actual, "actual").isEqualTo(null);`; + const messages = compiler.compile(code); + assert.equal(messages, "test.mts(4,44): error TS2345: Argument of type 'null' is not assignable " + + "to parameter of type 'string'." + os.EOL); + }).timeout(5000); + + test("isNotEqualTo", () => + { + const actual = "actualValue"; + validators.requireThat(actual, "actual").isNotEqualTo("expectedValue"); + }); + + test("isNotEqualTo_False", () => + { + const actual = {}; + assert.throws(function() + { + validators.requireThat(actual, "actual").isNotEqualTo(actual); + }, RangeError); + assert.throws(function() + { + validators.requireThat(actual, "actual").isNotEqualTo(actual, "actual"); + }, RangeError); + }); + + test("getType_isUndefined", () => + { + const actual = undefined; + const validator: ObjectValidator = validators.requireThat(actual, "actual").isUndefined(); + }); + + test("getType_isNull", () => + { + const actual = null; + const validator: ObjectValidator = validators.requireThat(actual, "actual").isNull(); + }); + + class Person + { + name: string; + age: number; + + constructor(name: string, age: number) + { + this.name = name; + this.age = age; + } + } + + test("getType_isObject", () => + { + const actual = new Person("John Smith", 32); + validators.requireThat(actual, "actual").isType(Type.namedClass(null)); + }); + + test("getType_isType", () => + { + const actual = Type.of(new Person("name", 5)); + validators.requireThat(actual, "actual").isType(Type.namedClass("Type")); + }); + + test("isInstanceOf", () => + { + const actual = new Person("name", 5); + const expected: Person | null = validators.requireThat(actual as unknown, "actual"). + isInstanceOf(Person).getValue(); + }); + + test("isInstanceOf_actualIsNull", () => + { + assert.throws(function() + { + const actual = null; + validators.requireThat(actual, "actual").isInstanceOf(String); + }, TypeError); + }); + + test("isInstanceOf_False", () => + { + assert.throws(function() + { + const actual = {}; + validators.requireThat(actual as unknown, "actual").isInstanceOf(String); + }, TypeError); + }); + test("isInstanceOf_Object", () => + { + assert.throws(function() + { + const actual = 5; + validators.requireThat(actual as unknown, "actual").isInstanceOf(Object); + }, TypeError); + }); + + test("isNull", () => + { + validators.requireThat(null, "actual").isNull(); + }); + + test("isNull_False", () => + { + assert.throws(function() + { + const actual = {} as object | null; + const isNull: null = validators.requireThat(actual, "actual").isNull().getValue(); + }, TypeError); + }); + + test("isNotNull", () => + { + const actual = {} as object | null; + // Changes the compile-time type of the value to not-null + const notNull: object | null = validators.requireThat(actual, "actual").isNotNull().getValue(); + }); + + test("isNotNull_False", () => + { + assert.throws(function() + { + const actual = null; + validators.requireThat(actual, "actual").isNotNull(); + }, TypeError); + }); + + test("isDefined", () => + { + const actual = 5; + const foo: ObjectValidator = validators.requireThat(actual as unknown, "actual"); + console.log("foo: " + foo.constructor.name); + foo.isNotUndefined(); + }); + + test("isDefined_False", () => + { + assert.throws(function() + { + let actual; + // noinspection JSUnusedAssignment + validators.requireThat(actual, "actual").isNotUndefined(); + }, TypeError); + }); + + test("isUndefined", () => + { + let actual; + // noinspection JSUnusedAssignment + validators.requireThat(actual, "actual").isUndefined(); + }); + + test("isUndefined_False", () => + { + assert.throws(function() + { + const actual = 5; + validators.requireThat(actual as unknown, "actual").isUndefined(); + }, TypeError); + }); +}); \ No newline at end of file diff --git a/test/InetAddressTest.mts b/test/InetAddressTest.mts deleted file mode 100644 index ff701da..0000000 --- a/test/InetAddressTest.mts +++ /dev/null @@ -1,249 +0,0 @@ -import { - suite, - test -} from "mocha"; -import {assert} from "chai"; -import { - Configuration, - TerminalEncoding, - Requirements -} from "../src/index.mjs"; -import {TestGlobalConfiguration} from "./TestGlobalConfiguration.mjs"; - -const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); -const configuration = new Configuration(globalConfiguration); -const requirements = new Requirements(configuration); - -suite("InetAddressTest", () => -{ - test("asIpAddress_actualIsIpV4", () => - { - const actual = "1.2.3.4"; - requirements.requireThat(actual, "actual").isInetAddress(); - }); - - test("asIpAddress_actualIsInvalidIpV4", () => - { - assert.throws(function() - { - const actual = "1.256.3.4"; - requirements.requireThat(actual, "actual").isInetAddress(); - }, RangeError); - }); - - test("asIpAddress_actualIsIpV6", () => - { - const actual = "0000:0000:0000:0000:0000:0000:192.168.0.1"; - requirements.requireThat(actual, "actual").isInetAddress(); - }); - - test("asIpAddress_endsWithZeroCompression", () => - { - const actual = "0000:0000:0000:0000:192.168.0.1::"; - requirements.requireThat(actual, "actual").isInetAddress(); - }); - - test("asIpAddress_multipleZeroCompressions", () => - { - assert.throws(function() - { - const actual = "0000::0000::0000:0000:192.168.0.1:"; - requirements.requireThat(actual, "actual").isInetAddress(); - }, RangeError); - }); - - test("isIpV4", () => - { - const actual = "1.2.3.4"; - requirements.requireThat(actual, "actual").isInetAddress().isIpV4(); - }); - - test("isIpV4_actualIsV6", () => - { - assert.throws(function() - { - const actual = "2001:db8:a0b:12f0::1"; - requirements.requireThat(actual, "actual").isInetAddress().isIpV4(); - }, RangeError); - }); - - test("isIpV6", () => - { - const actual = "2001:db8:a0b:12f0::1"; - requirements.requireThat(actual, "actual").isInetAddress().isIpV6(); - }); - - test("isIpV6_actualIsV4", () => - { - assert.throws(function() - { - const actual = "1.2.3.4"; - requirements.requireThat(actual, "actual").isInetAddress().isIpV6(); - }, RangeError); - }); - - test("isIpV6_multipleZeroCompressions", () => - { - assert.throws(function() - { - const actual = "2001:db8::a0b:12f0::1"; - requirements.requireThat(actual, "actual").isInetAddress().isIpV6(); - }, RangeError); - }); - - test("isIpV6_actualContainsNonHexCharacters", () => - { - assert.throws(function() - { - const actual = "2001:gb8:a0b:12f0::1"; - requirements.requireThat(actual, "actual").isInetAddress().isIpV6(); - }, RangeError); - }); - - test("isIpV6_actualHasZeroSuppression", () => - { - const actual = "2001:DB8:0:2F3B:2AA:FF:FE28:9C5A"; - requirements.requireThat(actual, "actual").isInetAddress().isIpV6(); - }); - - test("isIpV6_actualHasLeadingZeros", () => - { - const actual = "::0:2F3B:2AA:FF:FE28:9C5A"; - requirements.requireThat(actual, "actual").isInetAddress().isIpV6(); - }); - - test("isIpV6_actualHasLeadingColon", () => - { - assert.throws(function() - { - const actual = ":0:2F3B:2AA:FF:FE28:9C5A"; - requirements.requireThat(actual, "actual").isInetAddress().isIpV6(); - }, RangeError); - }); - - test("asIpAddress_actualHasTrailingColon", () => - { - assert.throws(function() - { - const actual = "0000:0000:0000:0000:0000:0000:192.168.0.1:"; - requirements.requireThat(actual, "actual").isInetAddress(); - }, RangeError); - }); - - test("isHostname", () => - { - const actual = "example.com"; - requirements.requireThat(actual, "actual").isInetAddress().isHostname(); - }); - - test("isHostname_actualIsEmpty", () => - { - assert.throws(function() - { - const actual = ""; - requirements.requireThat(actual, "actual").isInetAddress().isHostname(); - }, RangeError); - }); - - test("isHostname_actualContainsNonAscii", () => - { - assert.throws(function() - { - const actual = "ex@mple.com"; - requirements.requireThat(actual, "actual").isInetAddress().isHostname(); - }, RangeError); - }); - - test("isHostname_actualComponentTooShort", () => - { - assert.throws(function() - { - const actual = "example..com"; - requirements.requireThat(actual, "actual").isInetAddress().isHostname(); - }, RangeError); - }); - - test("isHostname_actualComponentTooLong", () => - { - assert.throws(function() - { - const actual = "1234567890123456789012345678901234567890123456789012345678901234.com"; - requirements.requireThat(actual, "actual").isInetAddress().isHostname(); - }, RangeError); - }); - - test("isHostname_actualStartsWithHyphen", () => - { - assert.throws(function() - { - const actual = "-example.com"; - requirements.requireThat(actual, "actual").isInetAddress().isHostname(); - }, RangeError); - }); - - test("isHostname_actualEndsWithHyphen", () => - { - assert.throws(function() - { - const actual = "example-.com"; - requirements.requireThat(actual, "actual").isInetAddress().isHostname(); - }, RangeError); - }); - - test("isHostname_actualIsTooLong", () => - { - let prefix = ""; - // 3x63 characters - for (let i = 0; i < 3; ++i) - { - for (let j = 0; j < 6; ++j) - prefix += "1234567890"; - prefix += "123."; - } - for (let j = 0; j < 5; ++j) - prefix += "1234567890"; - prefix += "1234567"; - const suffix = ".com"; - let actual = prefix + suffix; - - assert.equal(actual.length, 253); - requirements.requireThat(actual, "actual").isInetAddress().isHostname(); - - assert.throws(function() - { - prefix += "c"; - actual = prefix + suffix; - requirements.requireThat(prefix, "actual").isInetAddress().isHostname(); - }, RangeError); - }); - - test("isHostname_actualIsIpAddress", () => - { - assert.throws(function() - { - const actual = "0000:0000:0000:0000:0000:0000:192.168.0.1"; - requirements.requireThat(actual, "actual").isInetAddress().isHostname(); - }, RangeError); - }); - - test("getActual", () => - { - const input = "::0:2F3B:2AA:FF:FE28:9C5A"; - const output = requirements.requireThat(input, "input").getActual(); - assert.equal(output, input); - }); - - test("validateThatNullIsInetAddress", () => - { - const actual = null; - const expectedMessages = ["actual must be a string.\n" + - "Actual: null\n" + - "Type : null", - "actual must contain a valid IP address or hostname.\n" + - "Actual: undefined\n" + - "Type : undefined"]; - const actualFailures = requirements.validateThat(actual, "actual").isInetAddress().getFailures(); - const actualMessages = actualFailures.map(failure => failure.getMessage()); - requirements.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages); - }); -}); \ No newline at end of file diff --git a/test/MapTest.mts b/test/MapTest.mts index b6befde..697dc79 100644 --- a/test/MapTest.mts +++ b/test/MapTest.mts @@ -1,18 +1,19 @@ import { - Requirements, + TerminalEncoding, Configuration, - TerminalEncoding + Type } from "../src/index.mjs"; import { suite, test } from "mocha"; import {assert} from "chai"; -import {TestGlobalConfiguration} from "./TestGlobalConfiguration.mjs"; +import {JavascriptValidatorsImpl} from "../src/internal/internal.mjs"; +import {TestApplicationScope} from "./TestApplicationScope.mjs"; -const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); -const configuration = new Configuration(globalConfiguration); -const requirements = new Requirements(configuration); + +const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); suite("MapTest", () => { @@ -21,7 +22,7 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map(); - requirements.requireThat(actual, null as unknown as string); + validators.requireThat(actual, null as unknown as string); }, TypeError); }); @@ -30,14 +31,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map(); - requirements.requireThat(actual, ""); + validators.requireThat(actual, ""); }, RangeError); }); test("isEmpty", () => { const actual = new Map(); - requirements.requireThat(actual, "actual").isEmpty(); + validators.requireThat(actual, "actual").isEmpty(); }); test("isEmpty_False", () => @@ -45,14 +46,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").isEmpty(); + validators.requireThat(actual, "actual").isEmpty(); }, RangeError); }); test("isNotEmpty", () => { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").isNotEmpty(); + validators.requireThat(actual, "actual").isNotEmpty(); }); test("isNotEmpty_False", () => @@ -60,14 +61,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map(); - requirements.requireThat(actual, "actual").isNotEmpty(); + validators.requireThat(actual, "actual").isNotEmpty(); }, RangeError); }); test("isEqualTo", () => { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").isEqualTo(actual); + validators.requireThat(actual, "actual").isEqualTo(actual); }); test("isEqual_False", () => @@ -75,13 +76,13 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").isEqualTo(new Map()); + validators.requireThat(actual, "actual").isEqualTo(new Map()); }, RangeError); }); test("isNotEqualTo", () => { - requirements.requireThat(new Map([[1, 10], [2, 20]]), "actual").isNotEqualTo(new Map()); + validators.requireThat(new Map([[1, 10], [2, 20]]), "actual").isNotEqualTo(new Map()); }); test("isNotEqualTo_False", () => @@ -89,14 +90,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map(); - requirements.requireThat(actual, "actual").isNotEqualTo(actual); + validators.requireThat(actual, "actual").isNotEqualTo(actual); }, RangeError); }); test("isInstanceOf", () => { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual as unknown, "actual").isInstanceOf(Map).isInstanceOf(Object); + validators.requireThat(actual as unknown, "actual").isInstanceOf(Map).isType(Type.namedClass(null)); }); test("isInstanceOf_False", () => @@ -104,7 +105,7 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map(); - requirements.requireThat(actual as unknown, "actual").isInstanceOf(String); + validators.requireThat(actual as unknown, "actual").isType(Type.namedClass("string")); }, TypeError); }); @@ -113,20 +114,20 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map(); - requirements.requireThat(actual as unknown, "actual").isNull(); + validators.requireThat(actual as unknown, "actual").isNull(); }, TypeError); }); test("isNotNull", () => { const actual = new Map(); - requirements.requireThat(actual as unknown, "actual").isNotNull(); + validators.requireThat(actual as unknown, "actual").isNotNull(); }); test("keysContain", () => { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").keys().contains(2); + validators.requireThat(actual, "actual").keys().contains(2); }); test("keysContain_False", () => @@ -134,14 +135,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").keys().contains(5); + validators.requireThat(actual, "actual").keys().contains(5); }, RangeError); }); test("keysDoesNotContain", () => { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").keys().doesNotContain(5); + validators.requireThat(actual, "actual").keys().doesNotContain(5); }); test("keysDoesNotContain_False", () => @@ -149,14 +150,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").keys().doesNotContain(2); + validators.requireThat(actual, "actual").keys().doesNotContain(2); }, RangeError); }); - test("keysConsumer", () => + test("keysNestedValidator", () => { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").keysConsumer(k => k.contains(2)).size().isEqualTo(2); + validators.requireThat(actual, "actual").and(v => v.keys().contains(2)).size().isEqualTo(2); }); test("keysLength_False", () => @@ -164,27 +165,27 @@ suite("MapTest", () => const actual = new Map(); assert.throws(function() { - requirements.requireThat(actual, "actual").keys().length().isGreaterThan(1); + validators.requireThat(actual, "actual").keys().length().isGreaterThan(1); }, RangeError); assert.throws(function() { - requirements.requireThat(actual, "actual").keys().length().isGreaterThan(2); + validators.requireThat(actual, "actual").keys().length().isGreaterThan(2); }, RangeError); }); - test("keysConsumer_Fail", () => + test("keysNestedValidator_Fail", () => { assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").keysConsumer(k => k.doesNotContain(2)); + validators.requireThat(actual, "actual").and(v => v.keys().doesNotContain(2)); }, RangeError); }); test("valuesContain", () => { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").values().contains(20); + validators.requireThat(actual, "actual").values().contains(20); }); test("valuesContain_False", () => @@ -192,14 +193,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").values().contains(50); + validators.requireThat(actual, "actual").values().contains(50); }, RangeError); }); test("valuesDoesNotContain", () => { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").values().doesNotContain(50); + validators.requireThat(actual, "actual").values().doesNotContain(50); }); test("valuesDoesNotContain_False", () => @@ -207,7 +208,7 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").values().doesNotContain(20); + validators.requireThat(actual, "actual").values().doesNotContain(20); }, RangeError); }); @@ -216,33 +217,33 @@ suite("MapTest", () => const actual = new Map(); assert.throws(function() { - requirements.requireThat(actual, "actual").values().length().isGreaterThan(1); + validators.requireThat(actual, "actual").values().length().isGreaterThan(1); }, RangeError); assert.throws(function() { - requirements.requireThat(actual, "actual").values().length().isGreaterThan(2); + validators.requireThat(actual, "actual").values().length().isGreaterThan(2); }, RangeError); }); - test("valuesConsumer", () => + test("valuesNestedValidator", () => { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").valuesConsumer(k => k.contains(20)).size().isEqualTo(2); + validators.requireThat(actual, "actual").and(v => v.values().contains(20)).size().isEqualTo(2); }); - test("valuesConsumer_Fail", () => + test("valuesNestedValidator_Fail", () => { assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").valuesConsumer(v => v.doesNotContain(20)); + validators.requireThat(actual, "actual").and(v => v.values().doesNotContain(20)); }, RangeError); }); test("entriesContain", () => { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").entries().contains([2, 20]); + validators.requireThat(actual, "actual").entries().contains([2, 20]); }); test("entriesContain_False", () => @@ -250,14 +251,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").entries().contains([5, 50]); + validators.requireThat(actual, "actual").entries().contains([5, 50]); }, RangeError); }); test("entriesDoesNotContain", () => { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").entries().doesNotContain([5, 50]); + validators.requireThat(actual, "actual").entries().doesNotContain([5, 50]); }); test("entriesDoesNotContain_False", () => @@ -265,7 +266,7 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").entries().doesNotContain([2, 20]); + validators.requireThat(actual, "actual").entries().doesNotContain([2, 20]); }, RangeError); }); @@ -274,34 +275,34 @@ suite("MapTest", () => const actual = new Map(); assert.throws(function() { - requirements.requireThat(actual, "actual").entries().length().isGreaterThan(1); + validators.requireThat(actual, "actual").entries().length().isGreaterThan(1); }, RangeError); assert.throws(function() { - requirements.requireThat(actual, "actual").entries().length().isGreaterThan(2); + validators.requireThat(actual, "actual").entries().length().isGreaterThan(2); }, RangeError); }); - test("entriesConsumer", () => + test("entriesNestedValidator", () => { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").entriesConsumer(k => k.contains([2, 20])).size(). + validators.requireThat(actual, "actual").and(v => v.entries().contains([2, 20])).size(). isEqualTo(2); }); - test("entriesConsumer_Fail", () => + test("entriesNestedValidator_Fail", () => { assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").entriesConsumer(v => v.doesNotContain([2, 20])); + validators.requireThat(actual, "actual").and(v => v.entries().doesNotContain([2, 20])); }, RangeError); }); test("sizeIsEqualTo", () => { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").size().isEqualTo(2); + validators.requireThat(actual, "actual").size().isEqualTo(2); }); test("sizeIsEqualTo_False", () => @@ -309,14 +310,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").size().isEqualTo(1); + validators.requireThat(actual, "actual").size().isEqualTo(1); }, RangeError); }); test("sizeIsNotEqualTo", () => { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").size().isNotEqualTo(1); + validators.requireThat(actual, "actual").size().isNotEqualTo(1); }); test("sizeIsNotEqualTo_False", () => @@ -324,50 +325,41 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").size().isNotEqualTo(2); + validators.requireThat(actual, "actual").size().isNotEqualTo(2); }, RangeError); }); - test("sizeConsumer", () => + test("sizeNestedValidator", () => { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").sizeConsumer(s => s.isEqualTo(2)).entries(). + validators.requireThat(actual, "actual").and(v => v.size().isEqualTo(2)).entries(). contains([2, 20]); }); - test("sizeConsumer_Fail", () => + test("sizeNestedValidator_Fail", () => { assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - requirements.requireThat(actual, "actual").sizeConsumer(s => s.isNotEqualTo(2)); + validators.requireThat(actual, "actual").and(v => v.size().isNotEqualTo(2)); }, RangeError); }); - test("isString", () => - { - const actual = JSON.stringify(Object.fromEntries(new Map([[1, 10], [2, 20]])), null, " "); - requirements.requireThat(actual as unknown, "actual").isString().isEqualTo("{\n" + - " \"1\": 10,\n" + - " \"2\": 20\n" + - "}"); - }); - test("getActual", () => { const input = new Map([[1, 10], [2, 20]]); - const output = requirements.requireThat(input, "input").getActual(); - assert.equal(output, input); + const output = validators.requireThat(input, "input").getValue(); + assert.strictEqual(output, input); }); - test("validateThatNullIsMap", () => + test("checkIfNullIsMap", () => { const actual = null; - const expectedMessages = ["actual must be a Map.\n" + - "Actual: null\n" + - "Type : null"]; - const actualFailures = requirements.validateThat(actual, "actual").isMap().getFailures(); - const actualMessages = actualFailures.map(failure => failure.getMessage()); - requirements.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages); + const expectedMessages = [`\ +"actual" must be a Map. +actual: null`]; + const actualFailures = validators.checkIf(actual, "actual").isInstanceOf(Map).elseGetFailures(); + const actualMessages = actualFailures.getFailures().map(failure => failure.getMessage()); + validators.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages, "expectedMessages"); }); }); \ No newline at end of file diff --git a/test/NumberTest.mts b/test/NumberTest.mts index 35ab42f..b11075e 100644 --- a/test/NumberTest.mts +++ b/test/NumberTest.mts @@ -1,36 +1,41 @@ import { - Requirements, - Configuration, - TerminalEncoding + TerminalEncoding, + Configuration } from "../src/index.mjs"; import { suite, test } from "mocha"; import {assert} from "chai"; -import {TestGlobalConfiguration} from "./TestGlobalConfiguration.mjs"; +import { + JavascriptValidatorsImpl, + Type +} from "../src/internal/internal.mjs"; +import {TestApplicationScope} from "./TestApplicationScope.mjs"; -const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); -const configuration = new Configuration(globalConfiguration); -const requirements = new Requirements(configuration); +const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); suite("NumberTest", () => { test("isBetween_actualIsLowerBound", () => { - requirements.requireThat(0 as unknown, "actual").isNumber().isBetween(0, 2); + validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isBetween(0, 2)); }); test("isBetween_actualIsInBounds", () => { - requirements.requireThat(1 as unknown, "actual").isNumber().isBetween(0, 2); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isBetween(0, 2)); }); test("isBetween_actualIsUpperBound", () => { assert.throws(function() { - requirements.requireThat(2 as unknown, "actual").isNumber().isBetween(0, 2); + validators.requireThat(2 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isBetween(0, 2)); }, RangeError); }); @@ -38,25 +43,29 @@ suite("NumberTest", () => { assert.throws(function() { - requirements.requireThat(1 as unknown, "actual").isNumber().isBetween(10, 20); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isBetween(10, 20)); }, RangeError); }); test("isBetweenClosed_actualIsUpperBound", () => { - requirements.requireThat(2 as unknown, "actual").isNumber().isBetweenClosed(0, 2); + validators.requireThat(2 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isBetween(0, true, 2, true)); }); test("isNegative_actualIsNegativeOne", () => { - requirements.requireThat(-1 as unknown, "actual").isNumber().isNegative(); + validators.requireThat(-1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isNegative()); }); test("isNegative_actualIsZero", () => { assert.throws(function() { - requirements.requireThat(0 as unknown, "actual").isNumber().isNegative(); + validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isNegative()); }, RangeError); }); @@ -64,34 +73,40 @@ suite("NumberTest", () => { assert.throws(function() { - requirements.requireThat(1 as unknown, "actual").isNumber().isNegative(); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isNegative()); }, RangeError); }); test("isNotNegative", () => { - requirements.requireThat(0 as unknown, "actual").isNumber().isNotNegative(); - requirements.requireThat(1 as unknown, "actual").isNumber().isNotNegative(); + validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotNegative()); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotNegative()); }); test("isNotNegative_actualIsNegativeOne", () => { assert.throws(function() { - requirements.requireThat(-1 as unknown, "actual").isNumber().isNotNegative(); + validators.requireThat(-1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotNegative()); }, RangeError); }); test("isZero", () => { - requirements.requireThat(0 as unknown, "actual").isNumber().isZero(); + validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isZero()); }); test("isZero_actualIsOne", () => { assert.throws(function() { - requirements.requireThat(1 as unknown, "actual").isNumber().isZero(); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isZero()); }, RangeError); }); @@ -99,34 +114,40 @@ suite("NumberTest", () => { assert.throws(function() { - requirements.requireThat(-1 as unknown, "actual").isNumber().isZero(); + validators.requireThat(-1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isZero()); }, RangeError); }); test("isNotZero", () => { - requirements.requireThat(-1 as unknown, "actual").isNumber().isNotZero(); - requirements.requireThat(1 as unknown, "actual").isNumber().isNotZero(); + validators.requireThat(-1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotZero()); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotZero()); }); test("isNotZero_False", () => { assert.throws(function() { - requirements.requireThat(0 as unknown, "actual").isNumber().isNotZero(); + validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotZero()); }, RangeError); }); test("isPositive", () => { - requirements.requireThat(1 as unknown, "actual").isNumber().isPositive(); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isPositive()); }); test("isPositive_actualIsZero", () => { assert.throws(function() { - requirements.requireThat(0 as unknown, "actual").isNumber().isPositive(); + validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isPositive()); }, RangeError); }); @@ -134,39 +155,46 @@ suite("NumberTest", () => { assert.throws(function() { - requirements.requireThat(-1 as unknown, "actual").isNumber().isPositive(); + validators.requireThat(-1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isPositive()); }, RangeError); }); test("isNotPositive", () => { - requirements.requireThat(0 as unknown, "actual").isNumber().isNotPositive(); - requirements.requireThat(-1 as unknown, "actual").isNumber().isNotPositive(); + validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotPositive()); + validators.requireThat(-1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotPositive()); }); test("isNotPositive_actualIsOne", () => { assert.throws(function() { - requirements.requireThat(1 as unknown, "actual").isNumber().isNotPositive(); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotPositive()); }, RangeError); }); test("isLessThanVariable", () => { - requirements.requireThat(0 as unknown, "actual").isNumber().isLessThan(1, "expected"); + validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isLessThan(1, "expected")); }); test("isLessThanConstant", () => { - requirements.requireThat(0 as unknown, "actual").isNumber().isLessThan(1); + validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isLessThan(1)); }); test("isLessThanVariable_actualIsEqual", () => { assert.throws(function() { - requirements.requireThat(1 as unknown, "actual").isNumber().isLessThan(1, "expected"); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isLessThan(1, "expected")); }, RangeError); }); @@ -174,7 +202,8 @@ suite("NumberTest", () => { assert.throws(function() { - requirements.requireThat(1 as unknown, "actual").isNumber().isLessThan(1); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isLessThan(1)); }, RangeError); }); @@ -182,7 +211,8 @@ suite("NumberTest", () => { assert.throws(function() { - requirements.requireThat(2 as unknown, "actual").isNumber().isLessThan(1, "expected"); + validators.requireThat(2 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isLessThan(1, "expected")); }, RangeError); }); @@ -190,25 +220,31 @@ suite("NumberTest", () => { assert.throws(function() { - requirements.requireThat(2 as unknown, "actual").isNumber().isLessThan(1); + validators.requireThat(2 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isLessThan(1)); }, RangeError); }); test("isLessThanOrEqualToVariable", () => { - requirements.requireThat(1 as unknown, "actual").isNumber().isLessThanOrEqualTo(1, "expected"); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()). + isLessThanOrEqualTo(1, "expected")); }); test("isLessThanOrEqualToConstant", () => { - requirements.requireThat(1 as unknown, "actual").isNumber().isLessThanOrEqualTo(1); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isLessThanOrEqualTo(1)); }); test("isLessThanOrEqualToVariable_actualIsGreater", () => { assert.throws(function() { - requirements.requireThat(3 as unknown, "actual").isNumber().isLessThanOrEqualTo(2, "expected"); + validators.requireThat(3 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()). + isLessThanOrEqualTo(2, "expected")); }, RangeError); }); @@ -216,25 +252,29 @@ suite("NumberTest", () => { assert.throws(function() { - requirements.requireThat(3 as unknown, "actual").isNumber().isLessThanOrEqualTo(2); + validators.requireThat(3 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isLessThanOrEqualTo(2)); }, RangeError); }); test("isGreaterThanVariable", () => { - requirements.requireThat(1 as unknown, "actual").isNumber().isGreaterThan(0, "expected"); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isGreaterThan(0, "expected")); }); test("isGreaterThanConstant", () => { - requirements.requireThat(1 as unknown, "actual").isNumber().isGreaterThan(0); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isGreaterThan(0)); }); test("isGreaterThanVariable_actualIsEqual", () => { assert.throws(function() { - requirements.requireThat(1 as unknown, "actual").isNumber().isGreaterThan(1, "expected"); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isGreaterThan(1, "expected")); }, RangeError); }); @@ -242,7 +282,8 @@ suite("NumberTest", () => { assert.throws(function() { - requirements.requireThat(1 as unknown, "actual").isNumber().isGreaterThan(1); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isGreaterThan(1)); }, RangeError); }); @@ -250,8 +291,8 @@ suite("NumberTest", () => { assert.throws(function() { - requirements.requireThat(1 as unknown, "actual").isNumber().isGreaterThan(2, - "expected"); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isGreaterThan(2, "expected")); }, RangeError); }); @@ -259,25 +300,31 @@ suite("NumberTest", () => { assert.throws(function() { - requirements.requireThat(1 as unknown, "actual").isNumber().isGreaterThan(2); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isGreaterThan(2)); }, RangeError); }); test("isGreaterThanOrEqualToVariable", () => { - requirements.requireThat(1 as unknown, "actual").isNumber().isGreaterThanOrEqualTo(1, "expected"); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()). + isGreaterThanOrEqualTo(1, "expected")); }); test("isGreaterThanOrEqualToConstant", () => { - requirements.requireThat(1 as unknown, "actual").isNumber().isGreaterThanOrEqualTo(1); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isGreaterThanOrEqualTo(1)); }); test("isGreaterThanOrEqualToVariable_actualIsLess", () => { assert.throws(function() { - requirements.requireThat(1 as unknown, "actual").isNumber().isGreaterThanOrEqualTo(2, "expected"); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()). + isGreaterThanOrEqualTo(2, "expected")); }, RangeError); }); @@ -285,77 +332,87 @@ suite("NumberTest", () => { assert.throws(function() { - requirements.requireThat(1 as unknown, "actual").isNumber().isGreaterThanOrEqualTo(2); + validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isGreaterThanOrEqualTo(2)); }, RangeError); }); test("isFinite", () => { - requirements.requireThat(1.0 as unknown, "actual").isNumber().isFinite(); + validators.requireThat(1.0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isFinite()); }); test("isFinite_False", () => { assert.throws(function() { - requirements.requireThat(1.0 / 0.0 as unknown, "actual").isNumber().isFinite(); + validators.requireThat(1.0 / 0.0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isFinite()); }, RangeError); }); test("isNotFinite", () => { - requirements.requireThat(1.0 / 0.0 as unknown, "actual").isNumber().isInfinite(); + validators.requireThat(1.0 / 0.0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isInfinite()); }); test("isNotFinite_False", () => { assert.throws(function() { - requirements.requireThat(1.0 as unknown, "actual").isNumber().isInfinite(); + validators.requireThat(1.0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isInfinite()); }, RangeError); }); test("isNumber", () => { - requirements.requireThat(1.0 as unknown, "actual").isNumber(); + validators.requireThat(1.0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName())); }); test("isNumber_False", () => { assert.throws(function() { - requirements.requireThat(0.0 / 0.0 as unknown, "actual").isNumber().isFinite(); + validators.requireThat(0.0 / 0.0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isFinite()); }, RangeError); }); test("isInfinite", () => { - requirements.requireThat(0.0 / 0.0 as unknown, "actual").isNumber().isInfinite(); + validators.requireThat(0.0 / 0.0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isInfinite()); }); test("isInfinite_False", () => { assert.throws(function() { - requirements.requireThat(1.0 as unknown, "actual").isNumber().isInfinite(); + validators.requireThat(1.0 as unknown, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as number, v.getName()).isInfinite()); }, RangeError); }); test("getActual", () => { const input = 5; - const output = requirements.requireThat(input, "input").getActual(); - assert.equal(output, input); + const output = validators.requireThat(input, "input").getValue(); + assert.strictEqual(output, input); }); - test("validateThatNullAsNumber", () => + test("checkIfNullAsNumber", () => { const actual = null; - const expectedMessages = ["actual must be a number.\n" + - "Actual: null\n" + - "Type : null"]; - const actualFailures = requirements.validateThat(actual, "actual").isNumber().getFailures(); - const actualMessages = actualFailures.map(failure => failure.getMessage()); - requirements.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages); + const expectedMessages = [`\ +"actual" must be a number. +actual: null`]; + const actualFailures = validators.checkIf(actual, "actual").isType(Type.NUMBER). + and(v => validators.requireThat(v.getValue() as unknown as number, v.getName())).elseGetFailures(); + const actualMessages = actualFailures.getFailures().map(failure => failure.getMessage()); + validators.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages, "expectedMessages"); }); }); \ No newline at end of file diff --git a/test/ObjectTest.mts b/test/ObjectTest.mts deleted file mode 100644 index 849337b..0000000 --- a/test/ObjectTest.mts +++ /dev/null @@ -1,348 +0,0 @@ -import { - suite, - test -} from "mocha"; -import {assert} from "chai"; -import type { - ObjectValidator, - ClassConstructor -} from "../src/internal/internal.mjs"; -import { - Configuration, - ObjectVerifierImpl, - TerminalEncoding -} from "../src/internal/internal.mjs"; -import {Requirements} from "../src/index.mjs"; -import {TestCompiler} from "../build/TestCompiler.mjs"; -import {TestGlobalConfiguration} from "./TestGlobalConfiguration.mjs"; -import * as os from "os"; -import {mode} from "../build/mode.mjs"; - - -const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); -const configuration = new Configuration(globalConfiguration); -const requirements = new Requirements(configuration); - -let compiler: TestCompiler | undefined; -if (mode === "DEBUG") - compiler = undefined; -else - compiler = new TestCompiler(); - -suite("ObjectTest", () => -{ - test("validatorIsUndefined", () => - { - assert.throws(function() - { - let actual: undefined; - /* eslint-disable no-new */ - new ObjectVerifierImpl(actual as unknown as ObjectValidator); - /* eslint-enable no-new */ - }, TypeError); - }); - - test("nameIsNull", () => - { - assert.throws(function() - { - const actual = {} as object | null; - // Changes the compile-time type of the value to null - const isNull: null = requirements.requireThat(actual, null as unknown as string).isNull().getActual(); - }, TypeError); - }); - - test("nameIsEmpty", () => - { - assert.throws(function() - { - const actual = {}; - requirements.requireThat(actual, ""); - }, RangeError); - }); - - test("isEqualTo", () => - { - const actual = "actual"; - requirements.requireThat(actual, "actual").isEqualTo(actual); - }); - - test("isEqual_False", () => - { - const actual = {}; - assert.throws(function() - { - requirements.requireThat(actual, "actual").isEqualTo("expected"); - }, RangeError); - assert.throws(function() - { - requirements.requireThat(actual, "actual").isEqualTo("expected", "expected"); - }, RangeError); - }); - - test("isEqual_sameToStringDifferentTypes", () => - { - if (!compiler) - return; - const code = - `import {requireThat} from "./target/publish/node/index.mjs"; - - const actual = "null" - requireThat(actual, "actual").isEqualTo(null);`; - const messages = compiler.compile(code); - assert.equal(messages, "test.mts(4,44): error TS2345: Argument of type 'null' is not assignable " + - "to parameter of type 'string'." + os.EOL); - }).timeout(5000); - - test("isEqual_nullToNull", () => - { - const actual = null; - requirements.requireThat(actual, "actual").isEqualTo(actual); - }); - - test("isEqualTo_nullToNotNull", () => - { - if (!compiler) - return; - const code = - `import {requireThat} from "./target/publish/node/index.mjs"; - - const actual = null; - requireThat(actual, "actual").isEqualTo("expected");`; - const messages = compiler.compile(code); - assert.equal(messages, "test.mts(4,44): error TS2345: Argument of type '\"expected\"' is not " + - "assignable to parameter of type 'null'." + os.EOL); - }).timeout(5000); - - test("isEqualTo_notNullToNull", () => - { - if (!compiler) - return; - const code = - `import {requireThat} from "./target/publish/node/index.mjs"; - - const actual = "actual"; - requireThat(actual, "actual").isEqualTo(null);`; - const messages = compiler.compile(code); - assert.equal(messages, "test.mts(4,44): error TS2345: Argument of type 'null' is not assignable " + - "to parameter of type 'string'." + os.EOL); - }).timeout(5000); - - test("isNotEqualTo", () => - { - const actual = "actualValue"; - requirements.requireThat(actual, "actual").isNotEqualTo("expectedValue"); - }); - - test("isNotEqualTo_False", () => - { - const actual = {}; - assert.throws(function() - { - requirements.requireThat(actual, "actual").isNotEqualTo(actual); - }, RangeError); - assert.throws(function() - { - requirements.requireThat(actual, "actual").isNotEqualTo(actual, "actual"); - }, RangeError); - }); - - test("isTypeOf", () => - { - const actual = "value"; - requirements.requireThat(actual, "actual").isTypeOf("string"); - }); - - test("isTypeOf_actualIsNull", () => - { - const actual = null; - // For backwards-compatibility reasons typeof(null) === "object". See - // Per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof#typeof_null - requirements.requireThat(actual, "actual").isTypeOf("object"); - }); - - test("isTypeOf_expectedIsNull", () => - { - assert.throws(function() - { - const actual = {}; - requirements.requireThat(actual, "actual").isTypeOf("null"); - }, TypeError); - }); - - test("isTypeOf_False", () => - { - assert.throws(function() - { - const actual = {}; - requirements.requireThat(actual, "actual").isTypeOf("string"); - }, TypeError); - }); - - class Person - { - name: string; - age: number; - - constructor(name: string, age: number) - { - this.name = name; - this.age = age; - } - } - - test("isInstanceOf", () => - { - const actual = new Person("name", 5); - const expected: Person = requirements.requireThat(actual as unknown, "actual").isInstanceOf(Person). - getActual(); - }); - - test("isInstanceOf_Array", () => - { - const actual = [1, 2, 3]; - requirements.requireThat(actual as unknown, "actual").isArray().length().isEqualTo(3); - }); - - test("isInstanceOf_actualIsNull", () => - { - assert.throws(function() - { - const actual = null; - requirements.requireThat(actual, "actual").isInstanceOf(String); - }, TypeError); - }); - - test("isInstanceOf_expectedIsNull", () => - { - assert.throws(function() - { - const actual = {}; - requirements.requireThat(actual as unknown, "actual").isInstanceOf(null as unknown as - ClassConstructor); - }, TypeError); - }); - - test("isInstanceOf_False", () => - { - assert.throws(function() - { - const actual = {}; - requirements.requireThat(actual as unknown, "actual").isInstanceOf(String); - }, TypeError); - }); - - test("isTypeOf_AnonymousFunction", () => - { - requirements.requireThat(function() - { - return "anonymousFunction"; - }, "actual").isTypeOf("function"); - }); - - test("isTypeOf_ArrowFunction", () => - { - requirements.requireThat((input: string) => input + " -> output", "actual").isTypeOf("function"); - }); - - test("isInstanceOf_Object", () => - { - assert.throws(function() - { - const actual = 5; - requirements.requireThat(actual as unknown, "actual").isInstanceOf(Object); - }, TypeError); - }); - - test("isNull", () => - { - requirements.requireThat(null, "actual").isNull(); - }); - - test("isNull_False", () => - { - assert.throws(function() - { - const actual = {} as object | null; - const isNull: null = requirements.requireThat(actual, "actual").isNull().getActual(); - }, TypeError); - }); - - test("isNotNull", () => - { - const actual = {} as object | null; - // Changes the compile-time type of the value to not-null - const notNull: object = requirements.requireThat(actual, "actual").isNotNull().getActual(); - }); - - test("isNotNull_False", () => - { - assert.throws(function() - { - const actual = null; - requirements.requireThat(actual, "actual").isNotNull(); - }, TypeError); - }); - - test("isDefined", () => - { - const actual = 5; - requirements.requireThat(actual as unknown, "actual").isDefined(); - }); - - test("isDefined_False", () => - { - assert.throws(function() - { - let actual; - // noinspection JSUnusedAssignment - requirements.requireThat(actual, "actual").isDefined(); - }, TypeError); - }); - - test("isUndefined", () => - { - let actual; - // noinspection JSUnusedAssignment - requirements.requireThat(actual, "actual").isUndefined(); - }); - - test("isUndefined_False", () => - { - assert.throws(function() - { - const actual = 5; - requirements.requireThat(actual as unknown, "actual").isUndefined(); - }, TypeError); - }); - - test("isArray", () => - { - const array = [1, 2, 3]; - const expected: number[] = requirements.requireThat(array as unknown, "actual").isArray(). - isEqualTo(array).getActual(); - }); - - test("isSet", () => - { - const set = new Set([1, 2, 3]); - const expected: Set = requirements.requireThat(set as unknown, "actual").isSet(). - isEqualTo(set).getActual(); - }); - - test("isString", () => - { - const actual = "[1, 2, 3]"; - const expected: string = requirements.requireThat(actual as unknown, "actual").isString(). - isEqualTo("[1, 2, 3]").getActual(); - }); - - test("isInetAddress", () => - { - const actual = "1.2.3.4"; - assert.throws(function() - { - requirements.requireThat(actual as unknown, "actual").isInetAddress().isIpV6(); - }, RangeError); - }); -}); \ No newline at end of file diff --git a/test/ObjectsTest.mts b/test/ObjectsTest.mts index e854bfc..5f8145f 100644 --- a/test/ObjectsTest.mts +++ b/test/ObjectsTest.mts @@ -1,108 +1,104 @@ -import { - Objects, - VariableType -} from "../src/internal/internal.mjs"; import { suite, test } from "mocha"; import {assert} from "chai"; +import { + Type, + TypeCategory +} from "../src/index.mjs"; + suite("ObjectsTest", () => { - test("getTypeOf_undefined", () => + test("getType_undefined", () => { // eslint-disable-next-line no-undefined - assert.deepEqual(Objects.getTypeInfo(undefined), VariableType.UNDEFINED); + assert.deepEqual(Type.of(undefined), Type.UNDEFINED); }); - test("getTypeOf_null", () => + test("getType_null", () => { - assert.deepEqual(Objects.getTypeInfo(null), VariableType.NULL); + assert.deepEqual(Type.of(null), Type.NULL); }); - test("getTypeOf_boolean", () => + test("getType_boolean", () => { - assert.deepEqual(Objects.getTypeInfo(true), VariableType.BOOLEAN); + assert.deepEqual(Type.of(true), Type.BOOLEAN); }); - test("getTypeOf_Boolean", () => + test("getType_Boolean", () => { // eslint-disable-next-line no-new-wrappers // noinspection JSPrimitiveTypeWrapperUsage const input = new Boolean(true); - assert.deepEqual(Objects.getTypeInfo(input), new VariableType("object", "Boolean")); + assert.deepEqual(Type.of(input), Type.namedClass("Boolean")); }); - test("getTypeOf_number", () => + test("getType_number", () => { - assert.deepEqual(Objects.getTypeInfo(5), VariableType.NUMBER); + assert.deepEqual(Type.of(5), Type.NUMBER); }); - test("getTypeOf_Number", () => + test("getType_Number", () => { // eslint-disable-next-line no-new-wrappers const input = new Number(5); - assert.deepEqual(Objects.getTypeInfo(input), new VariableType("object", "Number")); + assert.deepEqual(Type.of(input), Type.namedClass("Number")); }); - test("getTypeOf_bigint", () => + test("getType_bigint", () => { - assert.deepEqual(Objects.getTypeInfo(5n), VariableType.BIGINT); + assert.deepEqual(Type.of(5n), Type.BIGINT); }); - test("getTypeOf_BigInt", () => + test("getType_BigInt", () => { // eslint-disable-next-line no-new-wrappers const input = BigInt(5); - assert.deepEqual(Objects.getTypeInfo(input), VariableType.BIGINT); + assert.deepEqual(Type.of(input), Type.BIGINT); }); - test("getTypeOf_string", () => + test("getType_string", () => { - assert.deepEqual(Objects.getTypeInfo("test"), VariableType.STRING); + assert.deepEqual(Type.of("test"), Type.STRING); }); - test("getTypeOf_String", () => + test("getType_String", () => { // eslint-disable-next-line no-new-wrappers const input = new String("test"); - assert.deepEqual(Objects.getTypeInfo(input), new VariableType("object", "String")); + assert.deepEqual(Type.of(input), Type.of(String)); }); - test("getTypeOf_symbol", () => + test("getType_Symbol", () => { - assert.deepEqual(Objects.getTypeInfo(Symbol("test")), VariableType.SYMBOL); + assert.deepEqual(Type.of(Symbol("test")), Type.SYMBOL); }); - test("getTypeOf_Symbol", () => + test("getType_anonymousFunction", () => { - // eslint-disable-next-line no-new-wrappers, @typescript-eslint/ban-types - const input: Symbol = Object(Symbol("test")) as Symbol; - assert.deepEqual(Objects.getTypeInfo(input), new VariableType("object", "Symbol")); - }); - - test("getTypeOf_anonymousFunction", () => - { - assert.deepEqual(Objects.getTypeInfo(function() + assert.deepEqual(Type.of(function() { return "output"; - }), VariableType.ANONYMOUS_FUNCTION); + }), Type.ANONYMOUS_FUNCTION); }); - test("getTypeOf_arrowFunction", () => + test("getType_arrowFunction", () => { - assert.deepEqual(Objects.getTypeInfo((input: string) => input + " -> output"), - VariableType.ANONYMOUS_FUNCTION); + assert.deepEqual(Type.of((input: string) => input + " => output"), + Type.ANONYMOUS_FUNCTION); }); - test("getTypeOf_namedFunction", () => + test("getType_namedFunction", () => { const input = function MyFunction() { return "hello world"; }; - assert.deepEqual(Objects.getTypeInfo(input), new VariableType("function", "MyFunction")); + const type = Type.of(input); + assert.deepEqual(type.category, TypeCategory.FUNCTION); + assert.deepEqual(type.name, "MyFunction"); }); @@ -110,15 +106,9 @@ suite("ObjectsTest", () => { } - test("getTypeOf_object", () => - { - const input = new MyClass(); - assert.deepEqual(Objects.getTypeInfo(input), new VariableType("object", "MyClass")); - }); - - test("getTypeOf_class", () => + test("getType_class", () => { const input = MyClass; - assert.deepEqual(Objects.getTypeInfo(input), new VariableType("class", "MyClass")); + assert.deepEqual(Type.of(input), Type.namedClass("MyClass")); }); }); \ No newline at end of file diff --git a/test/RequirementsTest.mts b/test/RequirementsTest.mts index f2c17a6..085e9d3 100644 --- a/test/RequirementsTest.mts +++ b/test/RequirementsTest.mts @@ -1,135 +1,91 @@ import { - Requirements, + TerminalEncoding, Configuration, - TerminalEncoding + AssertionError } from "../src/index.mjs"; import { suite, test } from "mocha"; import {assert} from "chai"; -import {TestGlobalConfiguration} from "./TestGlobalConfiguration.mjs"; +import {JavascriptValidatorsImpl} from "../src/internal/internal.mjs"; +import {TestApplicationScope} from "./TestApplicationScope.mjs"; -const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); -const configuration = new Configuration(globalConfiguration); -const requirements = new Requirements(configuration); + +const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); suite("RequirementsTest", () => { test("assertThatString", () => { - const actual = "actual"; - requirements.assertThat(r => r.requireThat(actual, "actual").isEqualTo("expected")); + assert.throws(function() + { + const actual = "actual"; + validators.assertThat(actual, "actual").isEqualTo("expected"); + }, AssertionError); }); test("assertThatArray", () => { const actual = [1, 2, 3]; - requirements.assertThat(r => r.requireThat(actual, "actual").isEqualTo(actual, "expected")); + validators.assertThat(actual, "actual").isEqualTo(actual, "expected"); }); test("assertThatNumber", () => { const actual = 5; - requirements.assertThat(r => r.requireThat(actual, "actual").isEqualTo(actual, "expected")); + validators.assertThat(actual, "actual").isEqualTo(actual, "expected"); }); test("assertThatSet", () => { const actual = new Set([1, 2, 3]); - requirements.assertThat(r => r.requireThat(actual, "actual").isEqualTo(actual, "expected")); + validators.assertThat(actual, "actual").isEqualTo(actual, "expected"); }); test("assertThatMap", () => { const actual = new Map([[1, 2], [2, 3]]); - requirements.assertThat(r => r.requireThat(actual, "actual").isEqualTo(actual, "expected")); + validators.assertThat(actual, "actual").isEqualTo(actual, "expected"); }); test("assertThatUrl", () => { const actual = new URL("http://www.google.com/"); - requirements.assertThat(r => r.requireThat(actual, "actual").isEqualTo(actual, "expected")); + validators.assertThat(actual, "actual").isEqualTo(actual, "expected"); }); - test("withAssertionsEnabled.assertThatObject", () => + test("assertThatObject", () => { assert.throws(function() { - const actual = {}; - requirements.withAssertionsEnabled().assertThat(r => r.requireThat(actual, "actual"). - isEqualTo("expected")); - }, RangeError); - }); - - test("withAssertionsDisabled", () => - { - const actual = {}; - requirements.withAssertionsEnabled().withAssertionsDisabled().assertThat(r => - r.requireThat(actual, "actual").isEqualTo("expected")); - }); - - test("withAssertionsEnabled.withAssertionsEnabled", () => - { - assert.equal(requirements, requirements.withAssertionsEnabled()); - }); + const localValidators = validators.copy(); - test("withAssertionsDisabled.withAssertionsDisabled", () => - { - assert.equal(requirements, requirements.withAssertionsDisabled()); + const actual = {}; + localValidators.assertThat(actual, "actual").isEqualTo("expected"); + }, AssertionError); }); test("requireThat.getActual", () => { const input = 12345; - const output = requirements.requireThat(input, "input").getActual(); - assert.equal(output, input); - }); - - test("assertThat_assertionsEnabled", () => - { - const actual = 12345; - const expected = 54321; - assert.throws(() => - { - requirements.copy().withAssertionsEnabled().assertThat(r => - r.requireThat(actual, "actual").isEqualTo(expected, "expected")); - }, RangeError); - }); - - test("assertThat_assertionsDisabled", () => - { - const actual = 12345; - const expected = 54321; - requirements.copy().withAssertionsDisabled().assertThat(r => - r.requireThat(actual, "actual").isEqualTo(expected, "expected")); + const output = validators.requireThat(input, "input").getValue(); + assert.strictEqual(output, input); }); - test("assertThat.getActual_assertionsEnabled", () => + test("assertThat.getActual", () => { - const actual = 12345; - const getActual = requirements.copy().withAssertionsEnabled().assertThatAndReturn(r => - r.requireThat(actual, "actual").getActual()); - requirements.requireThat(actual, "actual").isEqualTo(getActual as number, "getActual()"); - }); + const localValidators = validators.copy(); - test("assertThat.getActual_assertionsDisabled", () => - { const actual = 12345; - const getActual = requirements.copy().withAssertionsDisabled().assertThatAndReturn(r => - r.requireThat(actual, "actual").getActual()); - requirements.requireThat(actual, "actual").isNotEqualTo(getActual as number, "getActual()"); - requirements.requireThat(getActual, "getActual").isUndefined(); - }); - - test("assertionsAreEnabled", () => - { - assert.equal(requirements.assertionsAreEnabled(), false); + const getActual = localValidators.assertThat(actual, "actual").getValue(); + validators.requireThat(actual, "actual").isEqualTo(getActual as number, "getActual()"); }); test("putContext", () => { - const verifiers = requirements.putContext("key", "value"); + const verifiers = validators.withContext("value", "key"); assert.deepEqual(verifiers.getContext(), new Map([["key", "value"]])); }); }); \ No newline at end of file diff --git a/test/SetTest.mts b/test/SetTest.mts index b7fac71..97f6579 100644 --- a/test/SetTest.mts +++ b/test/SetTest.mts @@ -1,18 +1,17 @@ import { - Requirements, - Configuration, - TerminalEncoding + TerminalEncoding, + Configuration } from "../src/index.mjs"; import { suite, test } from "mocha"; import {assert} from "chai"; -import {TestGlobalConfiguration} from "./TestGlobalConfiguration.mjs"; +import {JavascriptValidatorsImpl} from "../src/internal/internal.mjs"; +import {TestApplicationScope} from "./TestApplicationScope.mjs"; -const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); -const configuration = new Configuration(globalConfiguration); -const requirements = new Requirements(configuration); +const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); suite("SetTest", () => { @@ -21,7 +20,7 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set(); - requirements.requireThat(actual, null as unknown as string); + validators.requireThat(actual, null as unknown as string); }, TypeError); }); @@ -30,14 +29,14 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set(); - requirements.requireThat(actual, ""); + validators.requireThat(actual, ""); }, RangeError); }); test("isEmpty", () => { const actual = new Set(); - requirements.requireThat(actual, "actual").isEmpty(); + validators.requireThat(actual, "actual").isEmpty(); }); test("isEmpty_False", () => @@ -45,14 +44,14 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set([1, 2, 3]); - requirements.requireThat(actual, "actual").isEmpty(); + validators.requireThat(actual, "actual").isEmpty(); }, RangeError); }); test("isNotEmpty", () => { const actual = new Set([1, 2, 3]); - requirements.requireThat(actual, "actual").isNotEmpty(); + validators.requireThat(actual, "actual").isNotEmpty(); }); test("isNotEmpty_False", () => @@ -60,14 +59,14 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set(); - requirements.requireThat(actual, "actual").isNotEmpty(); + validators.requireThat(actual, "actual").isNotEmpty(); }, RangeError); }); test("isEqualTo", () => { const actual = new Set([1, 2, 3]); - requirements.requireThat(actual, "actual").isEqualTo(actual); + validators.requireThat(actual, "actual").isEqualTo(actual); }); test("isEqual_False", () => @@ -75,13 +74,13 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set([1, 2, 3]); - requirements.requireThat(actual, "actual").isEqualTo(new Set()); + validators.requireThat(actual, "actual").isEqualTo(new Set()); }, RangeError); }); test("isNotEqualTo", () => { - requirements.requireThat(new Set([1, 2, 3]), "actual").isNotEqualTo(new Set()); + validators.requireThat(new Set([1, 2, 3]), "actual").isNotEqualTo(new Set()); }); test("isNotEqualTo_False", () => @@ -89,15 +88,14 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set(); - requirements.requireThat(actual, "actual").isNotEqualTo(actual); + validators.requireThat(actual, "actual").isNotEqualTo(actual); }, RangeError); }); test("isSet", () => { const actual = new Set([1, 2, 3]); - const expected: Set = requirements.requireThat(actual as unknown, "actual").isSet(). - getActual(); + validators.requireThat(actual as unknown, "actual").isInstanceOf(Set).getValue(); }); test("isSet_False", () => @@ -105,7 +103,7 @@ suite("SetTest", () => assert.throws(function() { const actual = [1, 2, 3]; - requirements.requireThat(actual, "actual").isSet(); + validators.requireThat(actual as unknown, "actual").isInstanceOf(Set); }, TypeError); }); @@ -114,20 +112,20 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set(); - requirements.requireThat(actual, "actual").isNull(); + validators.requireThat(actual as unknown, "actual").isNull(); }, TypeError); }); test("isNotNull", () => { const actual = new Set(); - requirements.requireThat(actual, "actual").isNotNull(); + validators.requireThat(actual as unknown, "actual").isNotNull(); }); test("contains", () => { const actual = new Set([1, 2, 3]); - requirements.requireThat(actual, "actual").contains(2); + validators.requireThat(actual, "actual").contains(2); }); test("contains_False", () => @@ -135,18 +133,18 @@ suite("SetTest", () => const actual = new Set([1, 2, 3]); assert.throws(function() { - requirements.requireThat(actual, "actual").contains(5); + validators.requireThat(actual, "actual").contains(5); }, RangeError); assert.throws(function() { - requirements.requireThat(actual, "actual").contains(5, "expected"); + validators.requireThat(actual, "actual").contains(5, "expected"); }, RangeError); }); test("doesNotContain", () => { const actual = new Set([1, 2, 3]); - requirements.requireThat(actual, "actual").doesNotContain(5); + validators.requireThat(actual, "actual").doesNotContain(5); }); test("doesNotContain_False", () => @@ -154,18 +152,18 @@ suite("SetTest", () => const actual = new Set([1, 2, 3]); assert.throws(function() { - requirements.requireThat(actual, "actual").doesNotContain(2); + validators.requireThat(actual, "actual").doesNotContain(2); }, RangeError); assert.throws(function() { - requirements.requireThat(actual, "actual").doesNotContain(2, "expected"); + validators.requireThat(actual, "actual").doesNotContain(2, "expected"); }, RangeError); }); test("containsAny", () => { const actual = new Set([1, 2, 3]); - requirements.requireThat(actual, "actual").containsAny([0, 2, 4]); + validators.requireThat(actual, "actual").containsAny([0, 2, 4]); }); test("containsAny_False", () => @@ -173,18 +171,18 @@ suite("SetTest", () => const actual = new Set([1, 2, 3]); assert.throws(function() { - requirements.requireThat(actual, "actual").containsAny([0, 5]); + validators.requireThat(actual, "actual").containsAny([0, 5]); }, RangeError); assert.throws(function() { - requirements.requireThat(actual, "actual").containsAny([0, 5], "expected"); + validators.requireThat(actual, "actual").containsAny([0, 5], "expected"); }, RangeError); }); test("doesNotContainAny", () => { const actual = new Set([1, 2, 3]); - requirements.requireThat(actual, "actual").doesNotContainAny([0, 5]); + validators.requireThat(actual, "actual").doesNotContainAny([0, 5]); }); test("doesNotContainAny_False", () => @@ -192,18 +190,18 @@ suite("SetTest", () => const actual = new Set([1, 2, 3]); assert.throws(function() { - requirements.requireThat(actual, "actual").doesNotContainAny([0, 2]); + validators.requireThat(actual, "actual").doesNotContainAny([0, 2]); }, RangeError); assert.throws(function() { - requirements.requireThat(actual, "actual").doesNotContainAny([0, 2], "expected"); + validators.requireThat(actual, "actual").doesNotContainAny([0, 2], "expected"); }, RangeError); }); test("containsAll", () => { const actual = new Set([1, 2, 3]); - requirements.requireThat(actual, "actual").containsAll([2, 3]); + validators.requireThat(actual, "actual").containsAll([2, 3]); }); test("containsAll_False", () => @@ -211,18 +209,18 @@ suite("SetTest", () => const actual = new Set([1, 2, 3]); assert.throws(function() { - requirements.requireThat(actual, "actual").containsAll([0, 1, 2]); + validators.requireThat(actual, "actual").containsAll([0, 1, 2]); }, RangeError); assert.throws(function() { - requirements.requireThat(actual, "actual").containsAll([0, 1, 2], "expected"); + validators.requireThat(actual, "actual").containsAll([0, 1, 2], "expected"); }, RangeError); }); test("doesNotContainAll", () => { const actual = new Set([1, 2, 3]); - requirements.requireThat(actual, "actual").doesNotContainAll([0, 2, 3]); + validators.requireThat(actual, "actual").doesNotContainAll([0, 2, 3]); }); test("doesNotContainAll_False", () => @@ -230,18 +228,18 @@ suite("SetTest", () => const actual = new Set([1, 2, 3]); assert.throws(function() { - requirements.requireThat(actual, "actual").doesNotContainAll([2, 3]); + validators.requireThat(actual, "actual").doesNotContainAll([2, 3]); }, RangeError); assert.throws(function() { - requirements.requireThat(actual, "actual").doesNotContainAll([2, 3], "expected"); + validators.requireThat(actual, "actual").doesNotContainAll([2, 3], "expected"); }, RangeError); }); test("containsExactly", () => { const actual = new Set([1, 2, 3]); - requirements.requireThat(actual, "actual").containsExactly([1, 2, 3]); + validators.requireThat(actual, "actual").containsExactly([1, 2, 3]); }); test("containsExactly_False", () => @@ -249,18 +247,18 @@ suite("SetTest", () => const actual = new Set([1, 2, 3]); assert.throws(function() { - requirements.requireThat(actual, "actual").containsExactly([0, 1, 2, 3]); + validators.requireThat(actual, "actual").containsExactly([0, 1, 2, 3]); }, RangeError); assert.throws(function() { - requirements.requireThat(actual, "actual").containsExactly([0, 1, 2, 3], "expected"); + validators.requireThat(actual, "actual").containsExactly([0, 1, 2, 3], "expected"); }, RangeError); }); test("sizeIsEqualTo", () => { const actual = new Set([1, 2, 3]); - requirements.requireThat(actual, "actual").size().isEqualTo(3); + validators.requireThat(actual, "actual").size().isEqualTo(3); }); test("sizeIsEqualTo_False", () => @@ -268,14 +266,14 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set([1, 2, 3]); - requirements.requireThat(actual, "actual").size().isEqualTo(2); + validators.requireThat(actual, "actual").size().isEqualTo(2); }, RangeError); }); test("sizeIsNotEqualTo", () => { const actual = new Set([1, 2, 3]); - requirements.requireThat(actual, "actual").size().isNotEqualTo(2); + validators.requireThat(actual, "actual").size().isNotEqualTo(2); }); test("sizeIsNotEqualTo_False", () => @@ -283,34 +281,34 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set([1, 2, 3]); - requirements.requireThat(actual, "actual").size().isNotEqualTo(3); + validators.requireThat(actual, "actual").size().isNotEqualTo(3); }, RangeError); }); - test("sizeConsumer", () => + test("sizeNestedValidator", () => { assert.throws(function() { const actual = new Set([1, 2, 3]); - requirements.requireThat(actual, "actual").sizeConsumer(s => s.isNotEqualTo(3)); + validators.requireThat(actual, "actual").and(v => v.size().isNotEqualTo(3)); }, RangeError); }); test("getActual", () => { const input = new Set([1, 2, 3]); - const output = requirements.requireThat(input, "input").getActual(); - assert.equal(output, input); + const output = validators.requireThat(input, "input").getValue(); + assert.strictEqual(output, input); }); - test("validateThatNullAsSet", () => + test("checkIfNullAsSet", () => { const actual = null; - const expectedMessages = ["actual must be a Set.\n" + - "Actual: null\n" + - "Type : null"]; - const actualFailures = requirements.validateThat(actual, "actual").isSet().getFailures(); - const actualMessages = actualFailures.map(failure => failure.getMessage()); - requirements.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages); + const expectedMessages = [`\ +"actual" must be a Set. +actual: null`]; + const actualFailures = validators.checkIf(actual, "actual").isInstanceOf(Set).elseGetFailures(); + const actualMessages = actualFailures.getFailures().map(failure => failure.getMessage()); + validators.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages, "expectedMessages"); }); }); \ No newline at end of file diff --git a/test/SizeTest.mts b/test/SizeTest.mts index 0620594..a68298b 100644 --- a/test/SizeTest.mts +++ b/test/SizeTest.mts @@ -4,22 +4,30 @@ import { } from "mocha"; import {assert} from "chai"; import { - Configuration, TerminalEncoding, - Requirements + Configuration } from "../src/index.mjs"; -import {TestGlobalConfiguration} from "./TestGlobalConfiguration.mjs"; +import {TestCompiler} from "../build/TestCompiler.mjs"; +import os from "os"; +import {mode} from "../build/mode.mjs"; +import {JavascriptValidatorsImpl} from "../src/internal/internal.mjs"; +import {TestApplicationScope} from "./TestApplicationScope.mjs"; -const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); -const configuration = new Configuration(globalConfiguration); -const requirements = new Requirements(configuration); +const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); + +let compiler: TestCompiler | undefined; +if (mode === "DEBUG") + compiler = undefined; +else + compiler = new TestCompiler(); suite("SizeTest", () => { test("isGreaterThanOrEqualTo", () => { const actual: unknown[] = []; - requirements.requireThat(actual, "actual").length().isGreaterThanOrEqualTo(0); + validators.requireThat(actual, "actual").length().isGreaterThanOrEqualTo(0); }); test("isGreaterThanOrEqualTo_False", () => @@ -27,18 +35,18 @@ suite("SizeTest", () => const actual: unknown[] = []; assert.throws(function() { - requirements.requireThat(actual, "actual").length().isGreaterThanOrEqualTo(5); + validators.requireThat(actual, "actual").length().isGreaterThanOrEqualTo(5); }, RangeError); assert.throws(function() { - requirements.requireThat(actual, "actual").length().isGreaterThanOrEqualTo(5, "expected"); + validators.requireThat(actual, "actual").length().isGreaterThanOrEqualTo(5, "expected"); }, RangeError); }); test("isGreaterThan", () => { const actual = [1]; - requirements.requireThat(actual, "actual").length().isGreaterThan(0); + validators.requireThat(actual, "actual").length().isGreaterThan(0); }); test("isGreaterThan_False", () => @@ -46,18 +54,18 @@ suite("SizeTest", () => const actual: unknown[] = []; assert.throws(function() { - requirements.requireThat(actual, "actual").length().isGreaterThan(5); + validators.requireThat(actual, "actual").length().isGreaterThan(5); }, RangeError); assert.throws(function() { - requirements.requireThat(actual, "actual").length().isGreaterThan(5, "expected"); + validators.requireThat(actual, "actual").length().isGreaterThan(5, "expected"); }, RangeError); }); test("isLessThanOrEqualTo", () => { const actual = [1]; - requirements.requireThat(actual, "actual").length().isLessThanOrEqualTo(2); + validators.requireThat(actual, "actual").length().isLessThanOrEqualTo(2); }); test("isLessThanOrEqualTo_False", () => @@ -65,18 +73,18 @@ suite("SizeTest", () => const actual: unknown[] = []; assert.throws(function() { - requirements.requireThat(actual, "actual").length().isLessThanOrEqualTo(-1); + validators.requireThat(actual, "actual").length().isLessThanOrEqualTo(-1); }, RangeError); assert.throws(function() { - requirements.requireThat(actual, "actual").length().isLessThanOrEqualTo(-1, "expected"); + validators.requireThat(actual, "actual").length().isLessThanOrEqualTo(-1, "expected"); }, RangeError); }); test("isLessThan", () => { const actual = [1]; - requirements.requireThat(actual, "actual").length().isLessThan(2); + validators.requireThat(actual, "actual").length().isLessThan(2); }); test("isLessThan_False", () => @@ -84,18 +92,18 @@ suite("SizeTest", () => const actual: unknown[] = []; assert.throws(function() { - requirements.requireThat(actual, "actual").length().isLessThan(0); + validators.requireThat(actual, "actual").length().isLessThan(0); }, RangeError); assert.throws(function() { - requirements.requireThat(actual, "actual").length().isLessThan(0, "expected"); + validators.requireThat(actual, "actual").length().isLessThan(0, "expected"); }, RangeError); }); test("isNotPositive", () => { const actual: unknown[] = []; - requirements.requireThat(actual, "actual").length().isNotPositive(); + validators.requireThat(actual, "actual").length().isNotPositive(); }); test("isNotPositive_False", () => @@ -103,14 +111,14 @@ suite("SizeTest", () => assert.throws(function() { const actual = [1, 2, 3]; - requirements.requireThat(actual, "actual").length().isNotPositive(); + validators.requireThat(actual, "actual").length().isNotPositive(); }, RangeError); }); test("isPositive", () => { const actual = [1, 2, 3]; - requirements.requireThat(actual, "actual").length().isPositive(); + validators.requireThat(actual, "actual").length().isPositive(); }); test("isPositive_False", () => @@ -118,14 +126,14 @@ suite("SizeTest", () => assert.throws(function() { const actual: unknown[] = []; - requirements.requireThat(actual, "actual").length().isPositive(); + validators.requireThat(actual, "actual").length().isPositive(); }, RangeError); }); test("isNotZero", () => { const actual = [1, 2, 3]; - requirements.requireThat(actual, "actual").length().isNotZero(); + validators.requireThat(actual, "actual").length().isNotZero(); }); test("isNotZero_False", () => @@ -133,28 +141,41 @@ suite("SizeTest", () => assert.throws(function() { const actual: unknown[] = []; - requirements.requireThat(actual, "actual").length().isNotZero(); + validators.requireThat(actual, "actual").length().isNotZero(); }, RangeError); }); test("isZero", () => { const actual: unknown[] = []; - requirements.requireThat(actual, "actual").length().isZero(); + validators.requireThat(actual, "actual").length().isZero(); }); test("isNotNegative", () => { - const actual: unknown[] = []; - requirements.requireThat(actual, "actual").length().isNotNegative(); - }); + if (!compiler) + return; + const code = + `import {requireThat} from "./target/publish/node/index.mjs"; + + const actual: unknown[] = []; + validators.requireThat(actual, "actual").length().isNotNegative();`; + const messages = compiler.compile(code); + assert.strictEqual(messages, "test.mts(4,34): error TS2339: Property 'isFalse' does not exist on " + + "type 'ObjectVerifier'." + os.EOL); + }).timeout(5000); test("isNegative", () => { - assert.throws(function() - { + if (!compiler) + return; + const code = + `import {requireThat} from "./target/publish/node/index.mjs"; + const actual: unknown[] = []; - requirements.requireThat(actual, "actual").length().isNegative(); - }, RangeError); - }); + validators.requireThat(actual, "actual").length().isNegative();`; + const messages = compiler.compile(code); + assert.strictEqual(messages, "test.mts(4,34): error TS2339: Property 'isFalse' does not exist on " + + "type 'ObjectVerifier'." + os.EOL); + }).timeout(5000); }); \ No newline at end of file diff --git a/test/StringTest.mts b/test/StringTest.mts index 534d452..65320d8 100644 --- a/test/StringTest.mts +++ b/test/StringTest.mts @@ -4,43 +4,47 @@ import { } from "mocha"; import {assert} from "chai"; import { - Configuration, - TerminalEncoding, TextOnly, - EOS_MARKER + EOS_MARKER, + DIFF_DELETE, + DIFF_INSERT, + JavascriptValidatorsImpl, + Type } from "../src/internal/internal.mjs"; -import {Requirements} from "../src/index.mjs"; -import {TestGlobalConfiguration} from "./TestGlobalConfiguration.mjs"; +import { + TerminalEncoding, + Configuration +} from "../src/index.mjs"; +import {TestApplicationScope} from "./TestApplicationScope.mjs"; -const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); -const configuration = new Configuration(globalConfiguration); -const requirements = new Requirements(configuration); +const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); suite("StringTest", () => { test("isEmpty", () => { - requirements.requireThat("", "actual").isEmpty(); + validators.requireThat("", "actual").isEmpty(); }); test("isEmpty_False", () => { assert.throws(function() { - requirements.requireThat(" ", "actual").isEmpty(); + validators.requireThat(" ", "actual").isEmpty(); }, RangeError); }); test("isTrimmed", () => { - requirements.requireThat("", "actual").isTrimmed(); + validators.requireThat("", "actual").isTrimmed(); }); test("isTrimmed_LeftSpace", () => { assert.throws(function() { - requirements.requireThat(" value", "actual").isTrimmed(); + validators.requireThat(" value", "actual").isTrimmed(); }, RangeError); }); @@ -48,20 +52,20 @@ suite("StringTest", () => { assert.throws(function() { - requirements.requireThat("value ", "actual").isTrimmed(); + validators.requireThat("value ", "actual").isTrimmed(); }, RangeError); }); test("isNotEmpty", () => { - requirements.requireThat(" ", "actual").isNotEmpty(); + validators.requireThat(" ", "actual").isNotEmpty(); }); test("isNotEmpty_False", () => { assert.throws(function() { - requirements.requireThat("", "actual").isNotEmpty(); + validators.requireThat("", "actual").isNotEmpty(); }, RangeError); }); @@ -69,7 +73,7 @@ suite("StringTest", () => { const prefix = "home"; const actual = prefix + "1234"; - requirements.requireThat(actual, "actual").startsWith(prefix); + validators.requireThat(actual, "actual").startsWith(prefix); }); test("startsWith_False", () => @@ -78,7 +82,7 @@ suite("StringTest", () => { const prefix = "home"; const actual = "1234" + prefix; - requirements.requireThat(actual, "actual").startsWith(prefix); + validators.requireThat(actual, "actual").startsWith(prefix); }, RangeError); }); @@ -86,7 +90,7 @@ suite("StringTest", () => { const prefix = "home"; const actual = "1234" + prefix; - requirements.requireThat(actual, "actual").doesNotStartWith(prefix); + validators.requireThat(actual, "actual").doesNotStartWith(prefix); }); test("doesNotStartWith_False", () => @@ -95,7 +99,7 @@ suite("StringTest", () => { const prefix = "home"; const actual = prefix + "1234"; - requirements.requireThat(actual, "actual").doesNotStartWith(prefix); + validators.requireThat(actual, "actual").doesNotStartWith(prefix); }, RangeError); }); @@ -103,7 +107,7 @@ suite("StringTest", () => { const expected = "cat"; const actual = "my " + expected + " is the best"; - requirements.requireThat(actual, "actual").contains(expected); + validators.requireThat(actual, "actual").contains(expected); }); test("contains_False", () => @@ -112,7 +116,7 @@ suite("StringTest", () => { const expected = "cat"; const actual = "my dog is the best"; - requirements.requireThat(actual, "actual").contains(expected); + validators.requireThat(actual, "actual").contains(expected); }, RangeError); }); @@ -120,7 +124,7 @@ suite("StringTest", () => { const value = "cat"; const actual = "my dog is the best"; - requirements.requireThat(actual, "actual").doesNotContain(value); + validators.requireThat(actual, "actual").doesNotContain(value); }); test("doesNotContain_False", () => @@ -129,7 +133,7 @@ suite("StringTest", () => { const value = "cat"; const actual = "my " + value + " is the best"; - requirements.requireThat(actual, "actual").doesNotContain(value); + validators.requireThat(actual, "actual").doesNotContain(value); }, RangeError); }); @@ -137,7 +141,7 @@ suite("StringTest", () => { const suffix = "home"; const actual = "1234" + suffix; - requirements.requireThat(actual, "actual").endsWith(suffix); + validators.requireThat(actual, "actual").endsWith(suffix); }); test("endsWith_False", () => @@ -146,7 +150,7 @@ suite("StringTest", () => { const suffix = "home"; const actual = suffix + "1234"; - requirements.requireThat(actual, "actual").endsWith(suffix); + validators.requireThat(actual, "actual").endsWith(suffix); }, RangeError); }); @@ -154,7 +158,7 @@ suite("StringTest", () => { const suffix = "home"; const actual = suffix + "1234"; - requirements.requireThat(actual, "actual").doesNotEndWith(suffix); + validators.requireThat(actual, "actual").doesNotEndWith(suffix); }); test("doesNotEndWith_False", () => @@ -163,14 +167,14 @@ suite("StringTest", () => { const suffix = "home"; const actual = "1234" + suffix; - requirements.requireThat(actual, "actual").doesNotEndWith(suffix); + validators.requireThat(actual, "actual").doesNotEndWith(suffix); }, RangeError); }); test("lengthIsEqualTo", () => { const actual = "value"; - requirements.requireThat(actual, "actual").length().isEqualTo(actual.length); + validators.requireThat(actual, "actual").length().isEqualTo(actual.length); }); test("lengthIsEqualTo_False", () => @@ -178,18 +182,18 @@ suite("StringTest", () => const actual = "value"; assert.throws(function() { - requirements.requireThat(actual, "actual").length().isEqualTo(1); + validators.requireThat(actual, "actual").length().isEqualTo(1); }, RangeError); assert.throws(function() { - requirements.requireThat(actual, "actual").length().isEqualTo(actual.length + 1); + validators.requireThat(actual, "actual").length().isEqualTo(actual.length + 1); }, RangeError); }); test("lengthIsNotEqualTo", () => { const actual = "value"; - requirements.requireThat(actual, "actual").length().isNotEqualTo(actual.length + 1); + validators.requireThat(actual, "actual").length().isNotEqualTo(actual.length + 1); }); test("lengthIsNotEqualTo_False", () => @@ -197,7 +201,7 @@ suite("StringTest", () => assert.throws(function() { const actual = "value"; - requirements.requireThat(actual, "actual").length().isNotEqualTo(actual.length); + validators.requireThat(actual, "actual").length().isNotEqualTo(actual.length); }, RangeError); }); @@ -206,41 +210,36 @@ suite("StringTest", () => const actual = " value "; assert.throws(function() { - requirements.requireThat(actual, "actual").length().isEqualTo(actual.length + 1); + validators.requireThat(actual, "actual").length().isEqualTo(actual.length + 1); }, RangeError); }); - test("isString", () => + test("isTypeString", () => { const actual = "value"; - const expected: string = requirements.requireThat(actual as unknown, "actual").isString(). - isEqualTo(actual).getActual(); + validators.requireThat(actual as unknown, "actual").isType(Type.STRING).isEqualTo(actual).getValue(); }); test("getActual", () => { const input = "value"; - const output = requirements.requireThat(input, "input").getActual(); - assert.equal(output, input); + const output = validators.requireThat(input, "input").getValue(); + assert.strictEqual(output, input); }); - test("validateThatNullAsString", () => + test("checkIfNullAsString", () => { const actual = null; const expected = "not-null"; - const expectedMessages = ["actual must be a string.\n" + - "Actual: null\n" + - "Type : null", - "actual must be equal to " + expected + ".\n" + - "\n" + - "Actual : undefined" + TextOnly.DIFF_PADDING.repeat("not-null".length) + EOS_MARKER + "\n" + - "Diff : " + TextOnly.DIFF_DELETE.repeat("undefined".length) + - TextOnly.DIFF_INSERT.repeat("not-null".length) + TextOnly.DIFF_PADDING.repeat(EOS_MARKER.length) + "\n" + - "Expected: " + TextOnly.DIFF_PADDING.repeat("undefined".length) + "not-null" + EOS_MARKER]; - - const actualFailures = requirements.validateThat(actual, "actual").isString().isEqualTo(expected). - getFailures(); - const actualMessages = actualFailures.map(failure => failure.getMessage()); - requirements.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages); + const expectedMessages = [`\ +"actual" must be a string. +actual: null`, +`"actual" must be equal to "${expected}". +actual: null`]; + + const actualFailures = validators.checkIf(actual, "actual").isType(Type.STRING).isEqualTo(expected). + elseGetFailures(); + const actualMessages = actualFailures.getFailures().map(failure => failure.getMessage()); + validators.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages, "expectedMessages"); }); }); \ No newline at end of file diff --git a/test/TestApplicationScope.mts b/test/TestApplicationScope.mts new file mode 100644 index 0000000..b9dec04 --- /dev/null +++ b/test/TestApplicationScope.mts @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 Gili Tzabari + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +import {TerminalEncoding} from "../src/index.mjs"; +import { + DefaultProcessScope, + AbstractApplicationScope +} from "../src/internal/internal.mjs"; +import {TestGlobalConfiguration} from "./TestGlobalConfiguration.mjs"; + +/** + * ApplicationScope for the test codebase. + */ +class TestApplicationScope extends AbstractApplicationScope +{ + /** + * Creates a new application scope. + * + * @param terminalEncoding - the type of encoding that validators should output + * @throws TypeError if `terminalEncoding` is null + */ + public constructor(terminalEncoding: TerminalEncoding) + { + super(DefaultProcessScope.INSTANCE, new TestGlobalConfiguration(terminalEncoding)); + } + + public close() + { + } +} + +export {TestApplicationScope}; \ No newline at end of file diff --git a/test/TestGlobalConfiguration.mts b/test/TestGlobalConfiguration.mts index 3ec7312..29c3f4b 100644 --- a/test/TestGlobalConfiguration.mts +++ b/test/TestGlobalConfiguration.mts @@ -1,68 +1,40 @@ -import type {TerminalEncoding} from "../src/index.mjs"; -import {AbstractGlobalConfiguration} from "../src/internal/internal.mjs"; +import { + TerminalEncoding, + type GlobalConfiguration +} from "../src/index.mjs"; -class TestGlobalConfiguration extends AbstractGlobalConfiguration +class TestGlobalConfiguration implements GlobalConfiguration { - private readonly terminalEncoding: TerminalEncoding; - private readonly terminalWidth: number; + private readonly _terminalEncoding: TerminalEncoding; /** * Creates a new test configuration. * * @param terminalEncoding - the encoding of the terminal - * @param terminalWidth - (optional) the width of the terminal */ - constructor(terminalEncoding: TerminalEncoding, terminalWidth = 80) + constructor(terminalEncoding: TerminalEncoding) { - super(false, true); - this.terminalEncoding = terminalEncoding; - this.terminalWidth = terminalWidth; + this._terminalEncoding = terminalEncoding; } - listTerminalEncodings(): TerminalEncoding[] + supportedTerminalEncodings(): Set { - return [this.terminalEncoding]; + return new Set([this._terminalEncoding]); } - getTerminalEncoding(): TerminalEncoding + terminalEncoding(): TerminalEncoding; + terminalEncoding(encoding: TerminalEncoding): GlobalConfiguration; + terminalEncoding(encoding?: TerminalEncoding): TerminalEncoding | GlobalConfiguration { - return this.terminalEncoding; - } - - withDefaultTerminalEncoding(): this - { - return this; - } - - withTerminalEncoding(encoding: TerminalEncoding): this - { - if (encoding !== this.terminalEncoding) + if (typeof (encoding) === "undefined") + return this._terminalEncoding; + if (encoding !== this._terminalEncoding) { throw new RangeError("Test only supports one encoding: " + this.terminalEncoding + "\n" + "Actual: " + encoding); } return this; } - - getTerminalWidth(): number - { - return this.terminalWidth; - } - - withDefaultTerminalWidth(): this - { - return this; - } - - withTerminalWidth(width: number): this - { - if (width !== this.terminalWidth) - { - throw new RangeError("Test only supports one width: " + this.terminalWidth + "\n" + - "Actual: " + width); - } - return this; - } } export {TestGlobalConfiguration}; \ No newline at end of file diff --git a/test/ValidationFailureTest.mts b/test/ValidationFailureTest.mts index dda14cb..4a0daf9 100644 --- a/test/ValidationFailureTest.mts +++ b/test/ValidationFailureTest.mts @@ -6,10 +6,17 @@ import {assert} from "chai"; import { Configuration, TerminalEncoding, - ValidationFailure + requireThat +} from "../src/index.mjs"; +import { + ValidationFailureImpl, + type ErrorBuilder, + JavascriptValidatorsImpl, + StringValidatorImpl, + MessageBuilder, + AssertionError } from "../src/internal/internal.mjs"; -import {Requirements} from "../src/index.mjs"; -import {TestGlobalConfiguration} from "./TestGlobalConfiguration.mjs"; +import {TestApplicationScope} from "./TestApplicationScope.mjs"; suite("ValidationFailureTest", () => { @@ -18,84 +25,80 @@ suite("ValidationFailureTest", () => assert.throws(function() { let configuration: undefined; - let exceptionType: undefined; + let errorType: undefined; let message: undefined; // eslint-disable-next-line no-new - new ValidationFailure(configuration as unknown as Configuration, - exceptionType as unknown as new (exceptionMessage: string) => Error, message as unknown as string); - }, TypeError); + new ValidationFailureImpl(configuration as unknown as Configuration, + message as unknown as string, errorType as unknown as ErrorBuilder); + }, AssertionError); }); test("typeIsUndefined", () => { assert.throws(function() { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - let type: undefined; // eslint-disable-next-line no-new - new ValidationFailure(configuration, type as unknown as new (message: string) => Error, "message"); - }, TypeError); + new ValidationFailureImpl(Configuration.DEFAULT, "message.", type as unknown as ErrorBuilder); + }, AssertionError); }); test("messageIsUndefined", () => { assert.throws(function() { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - let message: undefined; // eslint-disable-next-line no-new - new ValidationFailure(configuration, RangeError, message as unknown as string); + new ValidationFailureImpl(Configuration.DEFAULT, message as unknown as string, RangeError); }, TypeError); }); test("addContext", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); - + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); + const validator = validators.requireThat("value", "actual") as StringValidatorImpl; const valueNotString = 12345; + // eslint-disable-next-line no-new - new ValidationFailure(configuration, RangeError, "message"). - addContext("key", valueNotString); + new ValidationFailureImpl(Configuration.DEFAULT, new MessageBuilder(validator, "message."). + withContext(valueNotString, "key").toString(), RangeError); }); test("addContext_keyNotString", () => { assert.throws(function() { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NONE); - const configuration = new Configuration(globalConfiguration); + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), + Configuration.DEFAULT); + const validator = validators.requireThat("value", "actual") as StringValidatorImpl; const key = null; - // eslint-disable-next-line no-new - new ValidationFailure(configuration, RangeError, "message"). - addContext(key as unknown as string, null); + new ValidationFailureImpl(Configuration.DEFAULT, new MessageBuilder(validator, "message."). + withContext(key as unknown as string, null as unknown as string).toString(), RangeError); }, TypeError); }); test("messageWithoutFormatting", () => { - const globalConfiguration = new TestGlobalConfiguration(TerminalEncoding.NODE_16_COLORS); - const configuration = new Configuration(globalConfiguration); - const requirements = new Requirements(configuration); + const validators = new JavascriptValidatorsImpl( + new TestApplicationScope(TerminalEncoding.NODE_16_COLORS), Configuration.DEFAULT); + validators.updateConfiguration(c => c.allowDiff(false)); + const validator = validators.requireThat("value", "actual") as StringValidatorImpl; const actual = "int[6]"; const expected = "int[5]"; - const expectedMessage = "actual must be equal to " + expected + ".\n" + - "Actual: int[6]"; + const expectedMessage = `\ +"actual" must be equal to "${expected}". +actual: "int[6]"`; const expectedMessages = [expectedMessage]; - const actualFailures = requirements.withoutDiff(). - validateThat(actual, "actual"). - isEqualTo(expected).getFailures(); - const actualMessages = actualFailures.map(failure => failure.getMessage()); - requirements.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages); + const actualFailures = validators.checkIf(actual, "actual"). + isEqualTo(expected).elseGetFailures(); + const actualMessages = actualFailures.getFailures().map(failure => failure.getMessage()); + requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages, "expectedMessages"); }); }); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 59c5861..d32ecc3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,10 +4,10 @@ "compilerOptions": { "preserveConstEnums": true, "allowSyntheticDefaultImports": true, - "target": "ES2021", + "target": "ES2023", "lib": [ "DOM", - "ES2021" + "ES2023" ], "module": "Node16", "moduleResolution": "Node16", diff --git a/typedoc.json b/typedoc.json index 3c17a98..8ad556f 100644 --- a/typedoc.json +++ b/typedoc.json @@ -1,7 +1,6 @@ { "entryPoints": [ - "src/*.mts", - "src/extension/*.mts" + "src/*.mts" ], "plugin": [ "typedoc-plugin-missing-exports" From 5a098b512ca0f055c0c97d77e2d0ef1a8e7b51ea Mon Sep 17 00:00:00 2001 From: Gili Tzabari Date: Sat, 31 Aug 2024 08:22:28 -0400 Subject: [PATCH 02/15] Added StringValidator.doesNotContainWhitespace() --- docs/Changelog.md | 1 + src/internal/internal.mts | 2 + src/internal/message/StringMessages.mts | 16 +++++++ .../message/diff/ContextGenerator.mts | 2 +- src/internal/message/diff/DiffResult.mts | 2 +- .../scope/AbstractApplicationScope.mts | 2 +- src/internal/validator/AbstractValidators.mts | 2 +- .../validator/StringValidatorImpl.mts | 17 ++++++- .../validator/ValidationFailureImpl.mts | 7 ++- src/validator/ArrayValidator.mts | 2 +- src/validator/StringValidator.mts | 21 ++++++--- .../component/CollectionComponent.mts | 46 +++++++++---------- src/validator/component/NumberComponent.mts | 28 +++++------ test/StringTest.mts | 15 ++++++ 14 files changed, 111 insertions(+), 52 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index 8cc74b1..00a9e76 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -18,6 +18,7 @@ https://github.com/cowwoc/requirements.java/commits/master for a full list. design for this yet. * New features: * Added `validationFailed()` and `getValueOrDefault()` to all validators. + * Added `StringValidator.doesNotContainWhitespace()`. * Added `StringValidator.matches(regex)`. * Replaced `ClassValidator.isSupertypeOf()`, `isSubtypeOf()` with `Type.isSupertypeOf()`. * Improvements diff --git a/src/internal/internal.mts b/src/internal/internal.mts index f3460b5..2f89a59 100644 --- a/src/internal/internal.mts +++ b/src/internal/internal.mts @@ -189,6 +189,7 @@ import { stringDoesNotEndWith, stringContains, stringDoesNotContain, + stringDoesNotContainWhitespace, stringMatches } from "./message/StringMessages.mjs"; import { @@ -356,6 +357,7 @@ export stringDoesNotEndWith, stringContains, stringDoesNotContain, + stringDoesNotContainWhitespace, stringMatches, Difference, ObjectAndSize, diff --git a/src/internal/message/StringMessages.mts b/src/internal/message/StringMessages.mts index 4732fcb..b17a129 100644 --- a/src/internal/message/StringMessages.mts +++ b/src/internal/message/StringMessages.mts @@ -165,6 +165,21 @@ function stringDoesNotContain(validator: AbstractValidator, unw return messageBuilder; } +/** + * @param validator - the validator + * @returns a message for the validation failure + */ +function stringDoesNotContainWhitespace(validator: AbstractValidator) +{ + const name = validator.getName(); + const messageBuilder = new MessageBuilder(validator, + `${MessageBuilder.quoteName(name)} may not contain whitespace.`); + const value = validator.getValueOrDefault(null); + if (value !== null) + messageBuilder.withContext(value, name); + return messageBuilder; +} + /** * @param validator - the validator * @param regex - the regular expression @@ -194,5 +209,6 @@ export stringDoesNotEndWith, stringContains, stringDoesNotContain, + stringDoesNotContainWhitespace, stringMatches }; \ No newline at end of file diff --git a/src/internal/message/diff/ContextGenerator.mts b/src/internal/message/diff/ContextGenerator.mts index 77795d8..9fc3433 100644 --- a/src/internal/message/diff/ContextGenerator.mts +++ b/src/internal/message/diff/ContextGenerator.mts @@ -407,7 +407,7 @@ expectedName: ${expectedName}`); /** * @returns the difference between the expected and actual values - * @throws TypeError if `actualName` or `expectedName` are `null` + * @throws TypeError if `actualName` or `expectedName` are `undefined` or `null` */ private compareTypes(): MessageSection[] { diff --git a/src/internal/message/diff/DiffResult.mts b/src/internal/message/diff/DiffResult.mts index f905188..a2d3878 100644 --- a/src/internal/message/diff/DiffResult.mts +++ b/src/internal/message/diff/DiffResult.mts @@ -18,7 +18,7 @@ class DiffResult * @param diffLines - the difference between the actual and expected values (empty list if omitted) * @param expectedLines - the lines of the expected string * @param equalLines - indicates if the actual and expected values are equal for each line - * @throws TypeError if any of the arguments are `null` + * @throws TypeError if any of the arguments are `undefined` or `null` */ constructor(actualLines: string[], diffLines: string[], expectedLines: string[], equalLines: boolean[]) { diff --git a/src/internal/scope/AbstractApplicationScope.mts b/src/internal/scope/AbstractApplicationScope.mts index dbb31ee..1e52561 100644 --- a/src/internal/scope/AbstractApplicationScope.mts +++ b/src/internal/scope/AbstractApplicationScope.mts @@ -31,7 +31,7 @@ abstract class AbstractApplicationScope implements ApplicationScope * * @param parent - the parent scope * @param globalConfiguration - the global configuration - * @throws TypeError if any of the arguments are `null` + * @throws TypeError if any of the arguments are `undefined` or `null` */ protected constructor(parent: ProcessScope, globalConfiguration: GlobalConfiguration) { diff --git a/src/internal/validator/AbstractValidators.mts b/src/internal/validator/AbstractValidators.mts index 111b6b6..ff1c30a 100644 --- a/src/internal/validator/AbstractValidators.mts +++ b/src/internal/validator/AbstractValidators.mts @@ -148,7 +148,7 @@ abstract class AbstractValidators implements Validators * * @param scope - the application configuration * @param configuration - the configuration to use for new validators - * @throws TypeError if any of the arguments are `null` + * @throws TypeError if any of the arguments are `undefined` or `null` */ protected constructor(scope: ApplicationScope, configuration: Configuration) { diff --git a/src/internal/validator/StringValidatorImpl.mts b/src/internal/validator/StringValidatorImpl.mts index abead0a..1840181 100644 --- a/src/internal/validator/StringValidatorImpl.mts +++ b/src/internal/validator/StringValidatorImpl.mts @@ -17,7 +17,8 @@ import { stringMatches, stringIsTrimmed, objectIsEmpty, - objectIsNotEmpty + objectIsNotEmpty, + stringDoesNotContainWhitespace } from "../internal.mjs"; @@ -73,7 +74,7 @@ class StringValidatorImpl extends AbstractValidator { if (this.value.isNull()) this.onNull(); - if (this.value.validationFailed(v => !/^\s|\s$/.test(v))) + if (this.value.validationFailed(v => v != null && !/^\s|\s$/.test(v))) { this.addRangeError( stringIsTrimmed(this).toString()); @@ -153,6 +154,18 @@ class StringValidatorImpl extends AbstractValidator return this; } + doesNotContainWhitespace(): StringValidator + { + if (this.value.isNull()) + this.onNull(); + if (this.value.validationFailed(v => v != null && !/\s/.test(v))) + { + this.addRangeError( + stringDoesNotContainWhitespace(this).toString()); + } + return this; + } + matches(regex: RegExp): StringValidator { if (this.value.isNull()) diff --git a/src/internal/validator/ValidationFailureImpl.mts b/src/internal/validator/ValidationFailureImpl.mts index c2376c6..878698a 100644 --- a/src/internal/validator/ValidationFailureImpl.mts +++ b/src/internal/validator/ValidationFailureImpl.mts @@ -26,8 +26,11 @@ class ValidationFailureImpl implements ValidationFailure * @param configuration - the validator's configuration * @param message - the failure message * @param errorBuilder - returns the error associated with the failure message - * @throws AssertionError if any of the arguments are `null`. If the error message contains leading or - * trailing whitespace, or is empty. + * @throws AssertionError if: + *
    + *
  • Any of the arguments are `undefined` or `null`.
  • + *
  • The error message contains leading or trailing whitespace, or is empty.
  • + *
*/ public constructor(configuration: Configuration, message: string, errorBuilder: ErrorBuilder) { diff --git a/src/validator/ArrayValidator.mts b/src/validator/ArrayValidator.mts index b6ee225..67aeb82 100644 --- a/src/validator/ArrayValidator.mts +++ b/src/validator/ArrayValidator.mts @@ -27,7 +27,7 @@ interface ArrayValidator extends * @param comparator - a function that returns a negative number if `first` should come * before `second`, zero or `NaN` if the two values are equal, or a positive number * if `first` should come after `second`. - * @throws TypeError if the value or `comparator` are `null` + * @throws TypeError if the value or `comparator` are `undefined` or `null` * @throws RangeError if the collection is not sorted * @returns this */ diff --git a/src/validator/StringValidator.mts b/src/validator/StringValidator.mts index af3ff27..de98306 100644 --- a/src/validator/StringValidator.mts +++ b/src/validator/StringValidator.mts @@ -13,7 +13,7 @@ interface StringValidator extends ValidatorComponent * * @param prefix - the value that the string must start with * @returns this - * @throws TypeError if the value or `prefix` are `null` + * @throws TypeError if the value or `prefix` are `undefined` or `null` * @throws RangeError if the value does not start with `prefix` */ startsWith(prefix: string): StringValidator; @@ -23,7 +23,7 @@ interface StringValidator extends ValidatorComponent * * @param prefix - the value that the string may not start with * @returns this - * @throws TypeError if the value or `prefix` are `null` + * @throws TypeError if the value or `prefix` are `undefined` or `null` * @throws RangeError if the value starts with `prefix` */ doesNotStartWith(prefix: string): StringValidator; @@ -33,7 +33,7 @@ interface StringValidator extends ValidatorComponent * * @param suffix - the value that the string must end with * @returns this - * @throws TypeError if the value or `suffix` are `null` + * @throws TypeError if the value or `suffix` are `undefined` or `null` * @throws RangeError if the value does not end with `suffix` */ endsWith(suffix: string): StringValidator; @@ -43,7 +43,7 @@ interface StringValidator extends ValidatorComponent * * @param suffix - the value that the string may not end with * @returns this - * @throws TypeError if the value or `suffix` are `null` + * @throws TypeError if the value or `suffix` are `undefined` or `null` * @throws RangeError if the value ends with `suffix` */ doesNotEndWith(suffix: string): StringValidator; @@ -53,7 +53,7 @@ interface StringValidator extends ValidatorComponent * * @param expected - the string that the value must contain * @returns this - * @throws TypeError if the value or `expected` are `null` + * @throws TypeError if the value or `expected` are `undefined` or `null` * @throws RangeError if the value does not contain `expected` */ contains(expected: string): StringValidator; @@ -63,11 +63,20 @@ interface StringValidator extends ValidatorComponent * * @param unwanted - the string that the value may not contain * @returns this - * @throws TypeError if the value or `unwanted` are `null` + * @throws TypeError if the value or `unwanted` are `undefined` or `null` * @throws RangeError if the value contains `unwanted` */ doesNotContain(unwanted: string): StringValidator; + /** + * Ensures that the value does not contain whitespace characters. + * + * @returns this + * @throws NullPointerException if the value is `undefined` or `null` + * @throws IllegalArgumentException if the value contains whitespace characters + */ + doesNotContainWhitespace(): StringValidator; + /** * Ensures that the value matches a regular expression. * diff --git a/src/validator/component/CollectionComponent.mts b/src/validator/component/CollectionComponent.mts index 68f2c99..af9aaa7 100644 --- a/src/validator/component/CollectionComponent.mts +++ b/src/validator/component/CollectionComponent.mts @@ -117,7 +117,7 @@ interface CollectionComponent, E> * * @param unwanted - the unwanted elements * @returns this - * @throws TypeError if the value or `unwanted` are `null` + * @throws TypeError if the value or `unwanted` are `undefined` or `null` * @throws RangeError if the collection consists of the same elements as `unwanted`, * irrespective of their order */ @@ -131,7 +131,7 @@ interface CollectionComponent, E> * * @param expected - the desired elements * @returns this - * @throws TypeError if the value or `expected` are `null` + * @throws TypeError if the value or `expected` are `undefined` or `null` * @throws RangeError if: *
    *
  • the collection is missing any element in `expected`
  • @@ -147,7 +147,7 @@ interface CollectionComponent, E> * * @param unwanted - the unwanted elements * @returns this - * @throws TypeError if the value or `unwanted` are `null` + * @throws TypeError if the value or `unwanted` are `undefined` or `null` * @throws RangeError if the collection consists of the same elements as `unwanted`, * irrespective of their order */ @@ -162,7 +162,7 @@ interface CollectionComponent, E> * @param expected - the desired elements * @param name - the name of the expected collection * @returns this - * @throws TypeError if the value or any of the arguments are `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if: *
      *
    • `name` is empty
    • @@ -184,7 +184,7 @@ interface CollectionComponent, E> * @param unwanted - the unwanted elements * @param name - the name of the unwanted collection * @returns this - * @throws TypeError if the value or any of the arguments are `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if: *
        *
      • `name` is empty
      • @@ -208,7 +208,7 @@ interface CollectionComponent, E> * @param expected - the desired elements * @param name - the name of the expected collection * @returns this - * @throws TypeError if the value or any of the arguments are `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangError if: *
          *
        • `name` is empty
        • @@ -230,7 +230,7 @@ interface CollectionComponent, E> * @param unwanted - the unwanted elements * @param name - the name of the unwanted collection * @returns this - * @throws TypeError if the value or any of the arguments are `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if: *
            *
          • `name` is empty
          • @@ -250,7 +250,7 @@ interface CollectionComponent, E> * * @param expected - the desired elements * @returns this - * @throws TypeError if the value or `expected` are `null` + * @throws TypeError if the value or `expected` are `undefined` or `null` * @throws RangeError if the collection does not contain any element in `expected` */ containsAny(expected: Set): S; @@ -260,7 +260,7 @@ interface CollectionComponent, E> * * @param unwanted - the unwanted elements * @returns this - * @throws TypeError if the value or `unwanted` are `null` + * @throws TypeError if the value or `unwanted` are `undefined` or `null` * @throws RangeError if the collection contains any of the elements in `unwanted` */ doesNotContainAny(unwanted: Set): S; @@ -270,7 +270,7 @@ interface CollectionComponent, E> * * @param expected - the desired elements * @returns this - * @throws TypeError if the value or `expected` are `null` + * @throws TypeError if the value or `expected` are `undefined` or `null` * @throws RangeError if the collection does not contain any element in `expected` */ containsAny(expected: E[]): S; @@ -280,7 +280,7 @@ interface CollectionComponent, E> * * @param unwanted - the unwanted elements * @returns this - * @throws TypeError if the value or `unwanted` are `null` + * @throws TypeError if the value or `unwanted` are `undefined` or `null` * @throws RangeError if the collection contains any of the elements in `unwanted` */ doesNotContainAny(unwanted: E[]): S; @@ -291,7 +291,7 @@ interface CollectionComponent, E> * @param expected - the desired elements * @param name - the name of the expected collection * @returns this - * @throws TypeError if the value or any of the arguments are `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if: *
              *
            • `name` is empty
            • @@ -311,7 +311,7 @@ interface CollectionComponent, E> * @param unwanted - the unwanted elements * @param name - the name of the unwanted collection * @returns this - * @throws TypeError if the value or any of the arguments are `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if: *
                *
              • `name` is empty
              • @@ -331,7 +331,7 @@ interface CollectionComponent, E> * @param expected - the desired elements * @param name - the name of the expected collection * @returns this - * @throws TypeError if the value or any of the arguments are `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if: *
                  *
                • `name` is empty
                • @@ -351,7 +351,7 @@ interface CollectionComponent, E> * @param unwanted - the unwanted elements * @param name - the name of the unwanted collection * @returns this - * @throws TypeError if the value or any of the arguments are `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if: *
                    *
                  • `name` is empty
                  • @@ -370,7 +370,7 @@ interface CollectionComponent, E> * * @param expected - the desired elements * @returns this - * @throws TypeError if the value or `expected` are `null` + * @throws TypeError if the value or `expected` are `undefined` or `null` * @throws RangeError if the collection does not contain all the elements in `expected` */ containsAll(expected: Set): S; @@ -380,7 +380,7 @@ interface CollectionComponent, E> * * @param unwanted - the unwanted elements * @returns this - * @throws TypeError if the value or `unwanted` are `null` + * @throws TypeError if the value or `unwanted` are `undefined` or `null` * @throws RangeError if the collection contains all the elements of `unwanted` */ doesNotContainAll(unwanted: Set): S; @@ -390,7 +390,7 @@ interface CollectionComponent, E> * * @param expected - the desired elements * @returns this - * @throws TypeError if the value or `expected` are `null` + * @throws TypeError if the value or `expected` are `undefined` or `null` * @throws RangeError if the collection does not contain all the elements in `expected` */ containsAll(expected: E[]): S; @@ -400,7 +400,7 @@ interface CollectionComponent, E> * * @param unwanted - the unwanted elements * @returns this - * @throws TypeError if the value or `unwanted` are `null` + * @throws TypeError if the value or `unwanted` are `undefined` or `null` * @throws RangeError if the collection contains all the elements of `unwanted` */ doesNotContainAll(unwanted: E[]): S; @@ -411,7 +411,7 @@ interface CollectionComponent, E> * @param expected - the desired elements * @param name - the name of the expected collection * @returns this - * @throws TypeError if the value or any of the arguments are `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if: *
                      *
                    • `name` is empty
                    • @@ -431,7 +431,7 @@ interface CollectionComponent, E> * @param unwanted - the unwanted elements * @param name - the name of the unwanted collection * @returns this - * @throws TypeError if the value or any of the arguments are `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if: *
                        *
                      • `name` is empty
                      • @@ -451,7 +451,7 @@ interface CollectionComponent, E> * @param expected - the desired elements * @param name - the name of the expected collection * @returns this - * @throws TypeError if the value or any of the arguments are `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if: *
                          *
                        • `name` is empty
                        • @@ -471,7 +471,7 @@ interface CollectionComponent, E> * @param unwanted - the unwanted elements * @param name - the name of the unwanted collection * @returns this - * @throws TypeError if the value or any of the arguments are `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if: *
                            *
                          • `name` is empty
                          • diff --git a/src/validator/component/NumberComponent.mts b/src/validator/component/NumberComponent.mts index 3e5b0c8..77cb2a9 100644 --- a/src/validator/component/NumberComponent.mts +++ b/src/validator/component/NumberComponent.mts @@ -23,7 +23,7 @@ interface NumberComponent * * @param factor - the number being multiplied * @returns this - * @throws TypeError if the value is `undefined` or `null` + * @throws TypeError if the value or `factor` are `undefined` or `null` * @throws RangeError if the value is not a multiple of `factor` */ isMultipleOf(factor: number): S; @@ -34,7 +34,7 @@ interface NumberComponent * @param factor - the number being multiplied * @param name - the name of the factor * @returns this - * @throws TypeError if the value or `name` are `undefined` or `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty. If * the value is not a multiple of `factor`. */ @@ -47,7 +47,7 @@ interface NumberComponent * * @param factor - the number being multiplied * @returns this - * @throws TypeError if the value is `undefined` or `null` + * @throws TypeError if the value or `factor` are `undefined` or `null` * @throws RangeError if the value is a multiple of `factor` */ isNotMultipleOf(factor: number): S; @@ -58,7 +58,7 @@ interface NumberComponent * @param factor - the number being multiplied * @param name - the name of the factor * @returns this - * @throws TypeError if the value or `name` are `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty. If * the value is a multiple of `factor`. */ @@ -71,7 +71,7 @@ interface NumberComponent * * @param maximumExclusive - the exclusive upper bound * @returns this - * @throws TypeError if the value is `undefined` or `null` + * @throws TypeError if the value or `maximumExclusive` are `undefined` or `null` * @throws RangeError if the value is greater than or equal to `maximumExclusive` */ isLessThan(maximumExclusive: number): S; @@ -82,7 +82,7 @@ interface NumberComponent * @param maximumExclusive - the exclusive upper bound * @param name - the name of the upper bound * @returns this - * @throws TypeError if the value or `name` are `undefined` or `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if `name` contains a leading, trailing whitespace or is empty. If * the value is greater than or equal to `maximumExclusive`. */ @@ -95,7 +95,7 @@ interface NumberComponent * * @param maximumInclusive - the inclusive upper value * @returns this - * @throws TypeError if the value is `undefined` or `null` + * @throws TypeError if the value or `maximumInclusive` are `undefined` or `null` * @throws RangeError if the value is greater than `maximumInclusive` */ isLessThanOrEqualTo(maximumInclusive: number): S; @@ -106,7 +106,7 @@ interface NumberComponent * @param maximumInclusive - the maximum value * @param name - the name of the maximum value * @returns this - * @throws TypeError if the value or `name` are `undefined` or `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty. If * the value is greater than `maximumInclusive`. */ @@ -119,7 +119,7 @@ interface NumberComponent * * @param minimumInclusive - the minimum value * @returns this - * @throws TypeError if the value is `undefined` or `null` + * @throws TypeError if the value or `minimumInclusive` are `undefined` or `null` * @throws RangeError if the value is less than `minimumInclusive` */ isGreaterThanOrEqualTo(minimumInclusive: number): S; @@ -130,7 +130,7 @@ interface NumberComponent * @param minimumInclusive - the minimum value * @param name - the name of the minimum value * @returns this - * @throws TypeError if the value or `name` are `undefined` or `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty. If * the value is less than `minimumInclusive`. */ @@ -143,7 +143,7 @@ interface NumberComponent * * @param minimumExclusive - the exclusive lower bound * @returns this - * @throws TypeError if the value is `undefined` or `null` + * @throws TypeError if the value or `minimumExclusive` are `undefined` or `null` * @throws RangeError if the value is less than `minimumExclusive` */ isGreaterThan(minimumExclusive: number): S; @@ -154,7 +154,7 @@ interface NumberComponent * @param minimumExclusive - the exclusive lower bound * @param name - the name of the lower bound * @returns this - * @throws TypeError if the value or `name` are `undefined` or `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty. If * the value is less than `minimumExclusive`. */ @@ -168,7 +168,7 @@ interface NumberComponent * @param minimumInclusive - the lower bound of the range (inclusive) * @param maximumExclusive - the upper bound of the range (exclusive) * @returns this - * @throws TypeError if the value is `undefined` or `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if `minimumInclusive` is greater than `maximumExclusive`. If the value is less than * `minimumInclusive`. If the value is greater than or equal to `maximumExclusive`. */ @@ -182,7 +182,7 @@ interface NumberComponent * @param maximum - the upper bound of the range * @param maximumInclusive - `true` if the upper bound of the range is inclusive * @returns this - * @throws TypeError if the value is `undefined` or `null` + * @throws TypeError if the value or any of the arguments are `undefined` or `null` * @throws RangeError if `minimum` is greater than `maximum`. If * `minimumInclusive` is `true`, and the value is less than `minimum`. * If `minimumInclusive` is `false`, and the value is less than or equal to diff --git a/test/StringTest.mts b/test/StringTest.mts index 65320d8..27525c7 100644 --- a/test/StringTest.mts +++ b/test/StringTest.mts @@ -137,6 +137,21 @@ suite("StringTest", () => }, RangeError); }); + test("doesNotContainWhitespace", () => + { + const actual = "mydogisthebest"; + validators.requireThat(actual, "actual").doesNotContainWhitespace(); + }); + + test("doesNotContainWhitespace_False", () => + { + assert.throws(function() + { + const actual = "my dog is the best"; + validators.requireThat(actual, "actual").doesNotContainWhitespace(); + }, RangeError); + }); + test("endsWith", () => { const suffix = "home"; From f4d28ce4a4b6e79168a08a33376af0f80a143077 Mon Sep 17 00:00:00 2001 From: Gili Tzabari Date: Wed, 4 Sep 2024 12:50:26 -0400 Subject: [PATCH 03/15] * Node needs "exports" in package.json for ESM-only libraries. * Do not include the diff legend for context that does not include a diff. --- docs/Changelog.md | 2 +- package.json | 13 +- pnpm-lock.yaml | 244 +++++++++--------- src/DefaultJavascriptValidators.mts | 26 +- .../message/diff/ContextGenerator.mts | 24 +- .../message/section/MessageBuilder.mts | 1 - src/internal/util/AssertionError.mts | 5 - src/internal/validator/AbstractValidators.mts | 7 +- .../validator/JavascriptValidatorsImpl.mts | 19 +- src/internal/validator/Objects.mts | 30 ++- test/ArrayTest.mts | 1 - test/NumberTest.mts | 1 + test/SetTest.mts | 1 + test/SizeTest.mts | 1 + test/StringTest.mts | 5 +- test/ValidationFailureTest.mts | 1 + 16 files changed, 217 insertions(+), 164 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index 00a9e76..40ead19 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,7 +1,7 @@ Minor updates involving cosmetic changes have been omitted from this list. See https://github.com/cowwoc/requirements.java/commits/master for a full list. -## Version 4.0 - ? +## Version 4.0.0 - ? * Breaking changes: * Replaced `validateThat()` with `checkIf()`. diff --git a/package.json b/package.json index 66658c2..7b2230c 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,11 @@ "browser": "browser/index.js", "module": "node/index.mjs", "types": "node/index.d.mts", + "exports": { + ".": { + "import": "./node/index.mjs" + } + }, "sideEffects": false, "dependencies": { "chalk": "^5.3.0", @@ -38,15 +43,15 @@ "@rollup/plugin-commonjs": "^26.0.1", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-typescript": "^11.1.6", - "@types/chai": "^4.3.18", - "@types/diff": "^5.2.1", + "@types/chai": "^4.3.19", + "@types/diff": "^5.2.2", "@types/eslint": "^9.6.1", "@types/lodash": "^4.17.7", "@types/lodash.debounce": "^4.0.9", "@types/lodash.isequal": "^4.5.8", "@types/minimist": "^1.2.5", "@types/mocha": "^10.0.7", - "@types/node": "^22.5.0", + "@types/node": "^22.5.1", "@types/rollup-plugin-node-globals": "^1.4.4", "@types/tmp": "^0.2.6", "@typescript-eslint/eslint-plugin": "^8.3.0", @@ -66,7 +71,7 @@ "pnpm": "^9.9.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "rollup": "^4.21.1", + "rollup": "^4.21.2", "rollup-plugin-node-globals": "^1.4.0", "source-map-support": "^0.5.21", "strip-ansi": "^7.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed1320a..43bfce7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,19 +23,19 @@ importers: version: 9.9.1 '@rollup/plugin-commonjs': specifier: ^26.0.1 - version: 26.0.1(rollup@4.21.1) + version: 26.0.1(rollup@4.21.2) '@rollup/plugin-node-resolve': specifier: ^15.2.3 - version: 15.2.3(rollup@4.21.1) + version: 15.2.3(rollup@4.21.2) '@rollup/plugin-typescript': specifier: ^11.1.6 - version: 11.1.6(rollup@4.21.1)(tslib@2.7.0)(typescript@5.5.4) + version: 11.1.6(rollup@4.21.2)(tslib@2.7.0)(typescript@5.5.4) '@types/chai': - specifier: ^4.3.18 - version: 4.3.18 + specifier: ^4.3.19 + version: 4.3.19 '@types/diff': - specifier: ^5.2.1 - version: 5.2.1 + specifier: ^5.2.2 + version: 5.2.2 '@types/eslint': specifier: ^9.6.1 version: 9.6.1 @@ -55,8 +55,8 @@ importers: specifier: ^10.0.7 version: 10.0.7 '@types/node': - specifier: ^22.5.0 - version: 22.5.0 + specifier: ^22.5.1 + version: 22.5.1 '@types/rollup-plugin-node-globals': specifier: ^1.4.4 version: 1.4.4 @@ -115,8 +115,8 @@ importers: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) rollup: - specifier: ^4.21.1 - version: 4.21.1 + specifier: ^4.21.2 + version: 4.21.2 rollup-plugin-node-globals: specifier: ^1.4.0 version: 1.4.0 @@ -137,7 +137,7 @@ importers: version: 0.2.3 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@22.5.0)(typescript@5.5.4) + version: 10.9.2(@types/node@22.5.1)(typescript@5.5.4) tslib: specifier: ^2.7.0 version: 2.7.0 @@ -448,88 +448,88 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.21.1': - resolution: {integrity: sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==} + '@rollup/rollup-android-arm-eabi@4.21.2': + resolution: {integrity: sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.21.1': - resolution: {integrity: sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==} + '@rollup/rollup-android-arm64@4.21.2': + resolution: {integrity: sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.21.1': - resolution: {integrity: sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==} + '@rollup/rollup-darwin-arm64@4.21.2': + resolution: {integrity: sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.21.1': - resolution: {integrity: sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==} + '@rollup/rollup-darwin-x64@4.21.2': + resolution: {integrity: sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.21.1': - resolution: {integrity: sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.21.2': + resolution: {integrity: sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.21.1': - resolution: {integrity: sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==} + '@rollup/rollup-linux-arm-musleabihf@4.21.2': + resolution: {integrity: sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.21.1': - resolution: {integrity: sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==} + '@rollup/rollup-linux-arm64-gnu@4.21.2': + resolution: {integrity: sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.21.1': - resolution: {integrity: sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==} + '@rollup/rollup-linux-arm64-musl@4.21.2': + resolution: {integrity: sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.21.1': - resolution: {integrity: sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==} + '@rollup/rollup-linux-powerpc64le-gnu@4.21.2': + resolution: {integrity: sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.21.1': - resolution: {integrity: sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==} + '@rollup/rollup-linux-riscv64-gnu@4.21.2': + resolution: {integrity: sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.21.1': - resolution: {integrity: sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==} + '@rollup/rollup-linux-s390x-gnu@4.21.2': + resolution: {integrity: sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.21.1': - resolution: {integrity: sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==} + '@rollup/rollup-linux-x64-gnu@4.21.2': + resolution: {integrity: sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.21.1': - resolution: {integrity: sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==} + '@rollup/rollup-linux-x64-musl@4.21.2': + resolution: {integrity: sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.21.1': - resolution: {integrity: sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==} + '@rollup/rollup-win32-arm64-msvc@4.21.2': + resolution: {integrity: sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.21.1': - resolution: {integrity: sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==} + '@rollup/rollup-win32-ia32-msvc@4.21.2': + resolution: {integrity: sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.21.1': - resolution: {integrity: sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==} + '@rollup/rollup-win32-x64-msvc@4.21.2': + resolution: {integrity: sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==} cpu: [x64] os: [win32] - '@shikijs/core@1.14.1': - resolution: {integrity: sha512-KyHIIpKNaT20FtFPFjCQB5WVSTpLR/n+jQXhWHWVUMm9MaOaG9BGOG0MSyt7yA4+Lm+4c9rTc03tt3nYzeYSfw==} + '@shikijs/core@1.15.2': + resolution: {integrity: sha512-hi6XZuwHYn6bU4wtXZxST8ynM55aiU2+rVU9aPIrSxqKmEKl4d65puwGsggwcZWTET+7zGXKe7AUj46iQ8Aq8w==} '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -546,8 +546,8 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - '@types/chai@4.3.18': - resolution: {integrity: sha512-2UfJzigyNa8kYTKn7o4hNMPphkxtu4WTJyobK3m4FBpyj7EK5xgtPcOtxLm7Dznk/Qxr0QXn+gQbkg7mCZKdfg==} + '@types/chai@4.3.19': + resolution: {integrity: sha512-2hHHvQBVE2FiSK4eN0Br6snX9MtolHaTo/batnLjlGRhoQzlCL61iVpxoqO7SfFyOw+P/pwv+0zNHzKoGWz9Cw==} '@types/cookie@0.4.1': resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} @@ -555,8 +555,8 @@ packages: '@types/cors@2.8.17': resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} - '@types/diff@5.2.1': - resolution: {integrity: sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==} + '@types/diff@5.2.2': + resolution: {integrity: sha512-qVqLpd49rmJA2nZzLVsmfS/aiiBpfVE95dHhPVwG0NmSBAt+riPxnj53wq2oBq5m4Q2RF1IWFEUpnZTgrQZfEQ==} '@types/eslint@9.6.1': resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} @@ -591,8 +591,8 @@ packages: '@types/mocha@10.0.7': resolution: {integrity: sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==} - '@types/node@22.5.0': - resolution: {integrity: sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==} + '@types/node@22.5.1': + resolution: {integrity: sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -1008,8 +1008,8 @@ packages: engines: {node: '>=18'} hasBin: true - escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} escape-html@1.0.3: @@ -1171,8 +1171,8 @@ packages: get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - get-tsconfig@4.7.6: - resolution: {integrity: sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==} + get-tsconfig@4.8.0: + resolution: {integrity: sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==} glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -1687,8 +1687,8 @@ packages: resolution: {integrity: sha512-dFf8LpUNzIj3oE0vCvobX6rqOzHzLBoblyFp+3znPbjiSmSvOoK2kMKx+Fv9jYduG1rvcCfCveSgEaQHjWRF6g==} hasBin: true - rollup@4.21.1: - resolution: {integrity: sha512-ZnYyKvscThhgd3M5+Qt3pmhO4jIRR5RGzaSovB6Q7rGNrK5cUncrtLmcTTJVSdcKXyZjW8X8MB0JMSuH9bcAJg==} + rollup@4.21.2: + resolution: {integrity: sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -1748,8 +1748,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shiki@1.14.1: - resolution: {integrity: sha512-FujAN40NEejeXdzPt+3sZ3F2dx1U24BY2XTY01+MG8mbxCiA2XukXdcbyMyLAHJ/1AUUnQd1tZlvIjefWWEJeA==} + shiki@1.15.2: + resolution: {integrity: sha512-M+7QZQZiZw/cZeizrC/yryG3eeG8pTUhu7ZaHxVyzPNFIRIlN46YBciquoNPCiXiwLnx6JB62f3lSuSYQrus1w==} signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} @@ -2239,94 +2239,94 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@rollup/plugin-commonjs@26.0.1(rollup@4.21.1)': + '@rollup/plugin-commonjs@26.0.1(rollup@4.21.2)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.21.1) + '@rollup/pluginutils': 5.1.0(rollup@4.21.2) commondir: 1.0.1 estree-walker: 2.0.2 glob: 10.4.5 is-reference: 1.2.1 magic-string: 0.30.11 optionalDependencies: - rollup: 4.21.1 + rollup: 4.21.2 - '@rollup/plugin-node-resolve@15.2.3(rollup@4.21.1)': + '@rollup/plugin-node-resolve@15.2.3(rollup@4.21.2)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.21.1) + '@rollup/pluginutils': 5.1.0(rollup@4.21.2) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-builtin-module: 3.2.1 is-module: 1.0.0 resolve: 1.22.8 optionalDependencies: - rollup: 4.21.1 + rollup: 4.21.2 - '@rollup/plugin-typescript@11.1.6(rollup@4.21.1)(tslib@2.7.0)(typescript@5.5.4)': + '@rollup/plugin-typescript@11.1.6(rollup@4.21.2)(tslib@2.7.0)(typescript@5.5.4)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.21.1) + '@rollup/pluginutils': 5.1.0(rollup@4.21.2) resolve: 1.22.8 typescript: 5.5.4 optionalDependencies: - rollup: 4.21.1 + rollup: 4.21.2 tslib: 2.7.0 - '@rollup/pluginutils@5.1.0(rollup@4.21.1)': + '@rollup/pluginutils@5.1.0(rollup@4.21.2)': dependencies: '@types/estree': 1.0.5 estree-walker: 2.0.2 picomatch: 2.3.1 optionalDependencies: - rollup: 4.21.1 + rollup: 4.21.2 - '@rollup/rollup-android-arm-eabi@4.21.1': + '@rollup/rollup-android-arm-eabi@4.21.2': optional: true - '@rollup/rollup-android-arm64@4.21.1': + '@rollup/rollup-android-arm64@4.21.2': optional: true - '@rollup/rollup-darwin-arm64@4.21.1': + '@rollup/rollup-darwin-arm64@4.21.2': optional: true - '@rollup/rollup-darwin-x64@4.21.1': + '@rollup/rollup-darwin-x64@4.21.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.21.1': + '@rollup/rollup-linux-arm-gnueabihf@4.21.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.21.1': + '@rollup/rollup-linux-arm-musleabihf@4.21.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.21.1': + '@rollup/rollup-linux-arm64-gnu@4.21.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.21.1': + '@rollup/rollup-linux-arm64-musl@4.21.2': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.21.1': + '@rollup/rollup-linux-powerpc64le-gnu@4.21.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.21.1': + '@rollup/rollup-linux-riscv64-gnu@4.21.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.21.1': + '@rollup/rollup-linux-s390x-gnu@4.21.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.21.1': + '@rollup/rollup-linux-x64-gnu@4.21.2': optional: true - '@rollup/rollup-linux-x64-musl@4.21.1': + '@rollup/rollup-linux-x64-musl@4.21.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.21.1': + '@rollup/rollup-win32-arm64-msvc@4.21.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.21.1': + '@rollup/rollup-win32-ia32-msvc@4.21.2': optional: true - '@rollup/rollup-win32-x64-msvc@4.21.1': + '@rollup/rollup-win32-x64-msvc@4.21.2': optional: true - '@shikijs/core@1.14.1': + '@shikijs/core@1.15.2': dependencies: '@types/hast': 3.0.4 @@ -2340,15 +2340,15 @@ snapshots: '@tsconfig/node16@1.0.4': {} - '@types/chai@4.3.18': {} + '@types/chai@4.3.19': {} '@types/cookie@0.4.1': {} '@types/cors@2.8.17': dependencies: - '@types/node': 22.5.0 + '@types/node': 22.5.1 - '@types/diff@5.2.1': {} + '@types/diff@5.2.2': {} '@types/eslint@9.6.1': dependencies: @@ -2381,7 +2381,7 @@ snapshots: '@types/mocha@10.0.7': {} - '@types/node@22.5.0': + '@types/node@22.5.1': dependencies: undici-types: 6.19.8 @@ -2389,7 +2389,7 @@ snapshots: '@types/rollup-plugin-node-globals@1.4.4': dependencies: - '@types/node': 22.5.0 + '@types/node': 22.5.1 rollup: 0.63.5 '@types/tmp@0.2.6': {} @@ -2822,7 +2822,7 @@ snapshots: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.17 - '@types/node': 22.5.0 + '@types/node': 22.5.1 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 @@ -2864,7 +2864,7 @@ snapshots: '@esbuild/win32-ia32': 0.23.1 '@esbuild/win32-x64': 0.23.1 - escalade@3.1.2: {} + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -3037,7 +3037,7 @@ snapshots: get-func-name@2.0.2: {} - get-tsconfig@4.7.6: + get-tsconfig@4.8.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -3541,28 +3541,28 @@ snapshots: rollup@0.63.5: dependencies: '@types/estree': 0.0.39 - '@types/node': 22.5.0 + '@types/node': 22.5.1 - rollup@4.21.1: + rollup@4.21.2: dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.21.1 - '@rollup/rollup-android-arm64': 4.21.1 - '@rollup/rollup-darwin-arm64': 4.21.1 - '@rollup/rollup-darwin-x64': 4.21.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.21.1 - '@rollup/rollup-linux-arm-musleabihf': 4.21.1 - '@rollup/rollup-linux-arm64-gnu': 4.21.1 - '@rollup/rollup-linux-arm64-musl': 4.21.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.21.1 - '@rollup/rollup-linux-riscv64-gnu': 4.21.1 - '@rollup/rollup-linux-s390x-gnu': 4.21.1 - '@rollup/rollup-linux-x64-gnu': 4.21.1 - '@rollup/rollup-linux-x64-musl': 4.21.1 - '@rollup/rollup-win32-arm64-msvc': 4.21.1 - '@rollup/rollup-win32-ia32-msvc': 4.21.1 - '@rollup/rollup-win32-x64-msvc': 4.21.1 + '@rollup/rollup-android-arm-eabi': 4.21.2 + '@rollup/rollup-android-arm64': 4.21.2 + '@rollup/rollup-darwin-arm64': 4.21.2 + '@rollup/rollup-darwin-x64': 4.21.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.21.2 + '@rollup/rollup-linux-arm-musleabihf': 4.21.2 + '@rollup/rollup-linux-arm64-gnu': 4.21.2 + '@rollup/rollup-linux-arm64-musl': 4.21.2 + '@rollup/rollup-linux-powerpc64le-gnu': 4.21.2 + '@rollup/rollup-linux-riscv64-gnu': 4.21.2 + '@rollup/rollup-linux-s390x-gnu': 4.21.2 + '@rollup/rollup-linux-x64-gnu': 4.21.2 + '@rollup/rollup-linux-x64-musl': 4.21.2 + '@rollup/rollup-win32-arm64-msvc': 4.21.2 + '@rollup/rollup-win32-ia32-msvc': 4.21.2 + '@rollup/rollup-win32-x64-msvc': 4.21.2 fsevents: 2.3.3 run-parallel@1.2.0: @@ -3638,9 +3638,9 @@ snapshots: shebang-regex@3.0.0: {} - shiki@1.14.1: + shiki@1.15.2: dependencies: - '@shikijs/core': 1.14.1 + '@shikijs/core': 1.15.2 '@types/hast': 3.0.4 signal-exit@4.1.0: {} @@ -3779,14 +3779,14 @@ snapshots: dependencies: typescript: 5.5.4 - ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4): + ts-node@10.9.2(@types/node@22.5.1)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.5.0 + '@types/node': 22.5.1 acorn: 8.12.1 acorn-walk: 8.3.3 arg: 4.1.3 @@ -3802,7 +3802,7 @@ snapshots: tsx@4.19.0: dependencies: esbuild: 0.23.1 - get-tsconfig: 4.7.6 + get-tsconfig: 4.8.0 optionalDependencies: fsevents: 2.3.3 @@ -3819,7 +3819,7 @@ snapshots: lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 - shiki: 1.14.1 + shiki: 1.15.2 typescript: 5.5.4 yaml: 2.5.0 @@ -3930,7 +3930,7 @@ snapshots: yargs@16.2.0: dependencies: cliui: 7.0.4 - escalade: 3.1.2 + escalade: 3.2.0 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 @@ -3940,7 +3940,7 @@ snapshots: yargs@17.7.2: dependencies: cliui: 8.0.1 - escalade: 3.1.2 + escalade: 3.2.0 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 diff --git a/src/DefaultJavascriptValidators.mts b/src/DefaultJavascriptValidators.mts index c1ef561..579a40a 100644 --- a/src/DefaultJavascriptValidators.mts +++ b/src/DefaultJavascriptValidators.mts @@ -11,11 +11,12 @@ import { MainApplicationScope, type ConfigurationUpdater, type ValidatorComponent, - JavascriptValidators + JavascriptValidators, + AssertionError } from "./internal/internal.mjs"; -const typedocWorkaround: null | ValidatorComponent | - JavascriptValidators = null; +const typedocWorkaround: null | ValidatorComponent | JavascriptValidators | + AssertionError = null; // noinspection PointlessBooleanExpressionJS if (typedocWorkaround !== null) console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); @@ -64,7 +65,8 @@ function requireThat(value: T, name: string): NumberValidator | Bool /** * Validates the state of a value. *

                            - * The returned validator throws an error immediately if a validation fails. + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. * * @typeParam T - the type the value * @typeParam E - the type elements in the array or set @@ -76,14 +78,14 @@ function requireThat(value: T, name: string): NumberValidator | Bool * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` is empty */ -function assertThat(value: number, name: string): NumberValidator; -function assertThat(value: boolean, name: string): BooleanValidator; -function assertThat(value: E[], name: string): ArrayValidator; -function assertThat(value: Set, name: string): SetValidator; -function assertThat(value: Map, name: string): MapValidator; -function assertThat(value: string, name: string): StringValidator; -function assertThat(value: T, name: string): ObjectValidator; -function assertThat(value: T, name: string): NumberValidator | BooleanValidator | +function assertThat(value: number, name?: string): NumberValidator; +function assertThat(value: boolean, name?: string): BooleanValidator; +function assertThat(value: E[], name?: string): ArrayValidator; +function assertThat(value: Set, name?: string): SetValidator; +function assertThat(value: Map, name?: string): MapValidator; +function assertThat(value: string, name?: string): StringValidator; +function assertThat(value: T, name?: string): ObjectValidator; +function assertThat(value: T, name?: string): NumberValidator | BooleanValidator | ArrayValidator | SetValidator | MapValidator | StringValidator | ObjectValidator { return DELEGATE.assertThat(value, name); diff --git a/src/internal/message/diff/ContextGenerator.mts b/src/internal/message/diff/ContextGenerator.mts index 9fc3433..3ac6677 100644 --- a/src/internal/message/diff/ContextGenerator.mts +++ b/src/internal/message/diff/ContextGenerator.mts @@ -14,7 +14,8 @@ import { isApplicationScope, assertThatInstanceOf, ValidationTarget, - AssertionError + AssertionError, + MessageBuilder } from "../../internal.mjs"; import isEqual from "lodash.isequal"; @@ -46,6 +47,10 @@ class ContextGenerator * `true` if error messages may include a diff that compares actual and expected values. */ private _allowDiff: boolean; + /** + * `true` if the output may include an explanation of the diff format. + */ + private _allowLegend = false; /** * Creates a ContextGenerator. @@ -117,6 +122,18 @@ expectedName: ${expectedName}`); return this; } + /** + * Determines if the output may include a legend of the diff format. + * + * @param allowLegend - `true` if the output may include an explanation of the diff format + * return this + */ + public allowLegend(allowLegend: boolean) + { + this._allowLegend = allowLegend; + return this; + } + /** * @returns the diff to append to the error message */ @@ -210,7 +227,8 @@ expectedName: ${expectedName}`); } const elementGenerator = new ContextGenerator(this.scope, this.configuration, - actualNameLine, expectedNameLine); + actualNameLine, expectedNameLine). + allowLegend(false); actualValueLine.ifValid(value => elementGenerator.actualValue(value)); expectedValueLine.ifValid(value => elementGenerator.expectedValue(value)); @@ -338,6 +356,8 @@ expectedName: ${expectedName}`); context.push(elementGenerator.getDiffSection(actualNameLine, actualValueLine, diffLine, expectedNameLine, expectedValueLine)); } + if (diffLinesExist && this._allowLegend) + context.push(new StringSection(MessageBuilder.DIFF_LEGEND)); return context; } diff --git a/src/internal/message/section/MessageBuilder.mts b/src/internal/message/section/MessageBuilder.mts index 47ec56b..a921248 100644 --- a/src/internal/message/section/MessageBuilder.mts +++ b/src/internal/message/section/MessageBuilder.mts @@ -88,7 +88,6 @@ Legend actualValue(actualValue). expectedValue(expectedValue); this.diff.push(...contextGenerator.build()); - this.diff.push(new StringSection(MessageBuilder.DIFF_LEGEND)); return this; } diff --git a/src/internal/util/AssertionError.mts b/src/internal/util/AssertionError.mts index 454a636..2089539 100644 --- a/src/internal/util/AssertionError.mts +++ b/src/internal/util/AssertionError.mts @@ -13,11 +13,6 @@ class AssertionError extends Error { super(message, options); this.name = this.constructor.name; - if (options !== undefined && options.cause !== undefined) - { - const originalError = options.cause; - this.stack = originalError.stack?.replace(originalError.name, this.name); - } } } diff --git a/src/internal/validator/AbstractValidators.mts b/src/internal/validator/AbstractValidators.mts index ff1c30a..f69a7d3 100644 --- a/src/internal/validator/AbstractValidators.mts +++ b/src/internal/validator/AbstractValidators.mts @@ -136,7 +136,12 @@ abstract class AbstractValidators implements Validators * A function that converts the thrown error to `AssertionError`. */ private static readonly CONVERT_TO_ASSERTION_ERROR: (error: Error) => Error = - error => new AssertionError(error.message, {cause: error}); + e => + { + const assertionError = new AssertionError(e.message); + assertionError.stack = e.stack?.replace(e.name, assertionError.name); + throw assertionError; + }; protected readonly scope: ApplicationScope; private requireThatConfiguration: Configuration; private assertThatConfiguration: Configuration; diff --git a/src/internal/validator/JavascriptValidatorsImpl.mts b/src/internal/validator/JavascriptValidatorsImpl.mts index 832845a..068e354 100644 --- a/src/internal/validator/JavascriptValidatorsImpl.mts +++ b/src/internal/validator/JavascriptValidatorsImpl.mts @@ -24,10 +24,11 @@ import { Type, requireThatType, ValidationTarget, - Pluralizer + Pluralizer, + AssertionError } from "../internal.mjs"; -const typedocWorkaround: null | ConfigurationUpdater = null; +const typedocWorkaround: null | ConfigurationUpdater | AssertionError = null; // noinspection PointlessBooleanExpressionJS if (typedocWorkaround !== null) console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); @@ -86,7 +87,9 @@ class JavascriptValidatorsImpl extends AbstractValidators } /** - * Validates the state of a value, throwing errors immediately on validation failure. + * Validates the state of a value. + *

                            + * The returned validator throws an error immediately if a validation fails. * * @typeParam T - the type the value * @typeParam E - the type elements in the array or set @@ -113,7 +116,10 @@ class JavascriptValidatorsImpl extends AbstractValidators } /** - * Validates the state of a value, throwing `AssertionError` immediately on validation failure. + * Validates the state of a value. + *

                            + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. * * @typeParam T - the type the value * @typeParam E - the type elements in the array or set @@ -139,8 +145,9 @@ class JavascriptValidatorsImpl extends AbstractValidators } /** - * Validates the state of a value, capturing errors on validation failure rather than throwing them - * immediately. + * Validates the state of a value. + *

                            + * The returned validator throws an error immediately if a validation fails. * * @typeParam T - the type the value * @typeParam E - the type elements in the array or set diff --git a/src/internal/validator/Objects.mts b/src/internal/validator/Objects.mts index ec57671..869c006 100644 --- a/src/internal/validator/Objects.mts +++ b/src/internal/validator/Objects.mts @@ -120,7 +120,11 @@ function assertThatValueIsNotNull(value: unknown, name: string): void catch (e) { if (e instanceof Error) - throw new AssertionError(e.message, {cause: e}); + { + const assertionError = new AssertionError(e.message); + assertionError.stack = e.stack?.replace(e.name, assertionError.name); + throw assertionError; + } throw e; } } @@ -179,7 +183,11 @@ function assertThatType(value: unknown, name: string, type: Type): void catch (e) { if (e instanceof Error) - throw new AssertionError(e.message, {cause: e}); + { + const assertionError = new AssertionError(e.message); + assertionError.stack = e.stack?.replace(e.name, assertionError.name); + throw assertionError; + } throw e; } } @@ -243,7 +251,11 @@ function assertThatTypeCategory(value: unknown, name: string, category: TypeCate catch (e) { if (e instanceof Error) - throw new AssertionError(e.message, {cause: e}); + { + const assertionError = new AssertionError(e.message); + assertionError.stack = e.stack?.replace(e.name, assertionError.name); + throw assertionError; + } throw e; } @@ -324,7 +336,11 @@ function assertThatInstanceOf(value: T, name: string, type: ClassConstructor< catch (e) { if (e instanceof Error) - throw new AssertionError(e.message, {cause: e}); + { + const assertionError = new AssertionError(e.message); + assertionError.stack = e.stack?.replace(e.name, assertionError.name); + throw assertionError; + } throw e; } } @@ -369,7 +385,11 @@ function assertThatStringIsNotEmpty(value: string, name: string): void catch (e) { if (e instanceof Error) - throw new AssertionError(e.message, {cause: e}); + { + const assertionError = new AssertionError(e.message); + assertionError.stack = e.stack?.replace(e.name, assertionError.name); + throw assertionError; + } throw e; } } diff --git a/test/ArrayTest.mts b/test/ArrayTest.mts index 238b898..db42a4e 100644 --- a/test/ArrayTest.mts +++ b/test/ArrayTest.mts @@ -10,7 +10,6 @@ import { } from "../src/index.mjs"; import {JavascriptValidatorsImpl} from "../src/internal/validator/JavascriptValidatorsImpl.mjs"; import {TestApplicationScope} from "./TestApplicationScope.mjs"; -import {map} from "lodash"; const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), diff --git a/test/NumberTest.mts b/test/NumberTest.mts index b11075e..629de65 100644 --- a/test/NumberTest.mts +++ b/test/NumberTest.mts @@ -13,6 +13,7 @@ import { } from "../src/internal/internal.mjs"; import {TestApplicationScope} from "./TestApplicationScope.mjs"; + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), Configuration.DEFAULT); diff --git a/test/SetTest.mts b/test/SetTest.mts index 97f6579..f9a3c2e 100644 --- a/test/SetTest.mts +++ b/test/SetTest.mts @@ -10,6 +10,7 @@ import {assert} from "chai"; import {JavascriptValidatorsImpl} from "../src/internal/internal.mjs"; import {TestApplicationScope} from "./TestApplicationScope.mjs"; + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), Configuration.DEFAULT); diff --git a/test/SizeTest.mts b/test/SizeTest.mts index a68298b..a0640a2 100644 --- a/test/SizeTest.mts +++ b/test/SizeTest.mts @@ -13,6 +13,7 @@ import {mode} from "../build/mode.mjs"; import {JavascriptValidatorsImpl} from "../src/internal/internal.mjs"; import {TestApplicationScope} from "./TestApplicationScope.mjs"; + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), Configuration.DEFAULT); diff --git a/test/StringTest.mts b/test/StringTest.mts index 27525c7..5dcce38 100644 --- a/test/StringTest.mts +++ b/test/StringTest.mts @@ -4,10 +4,6 @@ import { } from "mocha"; import {assert} from "chai"; import { - TextOnly, - EOS_MARKER, - DIFF_DELETE, - DIFF_INSERT, JavascriptValidatorsImpl, Type } from "../src/internal/internal.mjs"; @@ -17,6 +13,7 @@ import { } from "../src/index.mjs"; import {TestApplicationScope} from "./TestApplicationScope.mjs"; + const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), Configuration.DEFAULT); diff --git a/test/ValidationFailureTest.mts b/test/ValidationFailureTest.mts index 4a0daf9..b70556b 100644 --- a/test/ValidationFailureTest.mts +++ b/test/ValidationFailureTest.mts @@ -18,6 +18,7 @@ import { } from "../src/internal/internal.mjs"; import {TestApplicationScope} from "./TestApplicationScope.mjs"; + suite("ValidationFailureTest", () => { test("configurationIsUndefined", () => From a3514325b7f6dffcf95d08b50c6b00a1db880096 Mon Sep 17 00:00:00 2001 From: Gili Tzabari Date: Wed, 4 Sep 2024 16:56:55 -0400 Subject: [PATCH 04/15] Updated dependencies --- .mocharc.cjs | 15 +++ build/Project.mts | 4 +- eslint.config.d.mts | 15 +++ eslint.config.mjs | 25 +++- package.json | 12 +- pnpm-lock.yaml | 183 +++++++++++++++-------------- src/internal/validator/Objects.mts | 1 + tsconfig.json | 7 +- 8 files changed, 161 insertions(+), 101 deletions(-) diff --git a/.mocharc.cjs b/.mocharc.cjs index 2091edc..b7487bb 100644 --- a/.mocharc.cjs +++ b/.mocharc.cjs @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Gili Tzabari + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ module.exports = { // https://stackoverflow.com/a/78098005/14731 "node-option": ["import=tsx"], diff --git a/build/Project.mts b/build/Project.mts index 02dd714..f3d1773 100644 --- a/build/Project.mts +++ b/build/Project.mts @@ -377,12 +377,12 @@ class Project private async watchFiles(paths: string | ReadonlyArray, callback: (sources: string[]) => Promise): Promise { - const changes: Set = new Set(); + const changes: Set = new Set(); const project = this; async function processUpdate() { - const filesToProcess = new Set(changes); + const filesToProcess = new Set(changes); const posixPaths = []; for (const changed of filesToProcess) { diff --git a/eslint.config.d.mts b/eslint.config.d.mts index bfcf504..80c2e5f 100644 --- a/eslint.config.d.mts +++ b/eslint.config.d.mts @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Gili Tzabari + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import {Linter} from "eslint"; declare const config: Linter.Config; diff --git a/eslint.config.mjs b/eslint.config.mjs index 2756ddc..6ccdeb7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Gili Tzabari + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import esLint from "@eslint/js"; // https://typescript-eslint.io/getting-started#step-2-configuration import tsLint from "typescript-eslint"; @@ -56,7 +71,15 @@ export default [ ], "tsdoc/syntax": "warn", // Prevents "while(true)" - "no-constant-condition": "off" + "no-constant-condition": "off", + "no-mixed-spaces-and-tabs": [ + "error", + "smart-tabs" + ], + "@typescript-eslint/consistent-indexed-object-style": [ + "error", + "index-signature" + ] } } ]; \ No newline at end of file diff --git a/package.json b/package.json index 7b2230c..eea11f3 100644 --- a/package.json +++ b/package.json @@ -33,11 +33,11 @@ "sideEffects": false, "dependencies": { "chalk": "^5.3.0", - "diff": "6.0.0-beta", + "diff": "6.0.0", "lodash.isequal": "^4.5.0" }, "license": "Apache-2.0", - "packageManager": "pnpm@8.6.9", + "packageManager": "pnpm@9.9.0", "devDependencies": { "@eslint/js": "^9.9.1", "@rollup/plugin-commonjs": "^26.0.1", @@ -51,11 +51,11 @@ "@types/lodash.isequal": "^4.5.8", "@types/minimist": "^1.2.5", "@types/mocha": "^10.0.7", - "@types/node": "^22.5.1", + "@types/node": "^22.5.3", "@types/rollup-plugin-node-globals": "^1.4.4", "@types/tmp": "^0.2.6", - "@typescript-eslint/eslint-plugin": "^8.3.0", - "@typescript-eslint/parser": "^8.3.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", + "@typescript-eslint/parser": "^8.4.0", "browser-sync": "^3.0.2", "c8": "^10.1.2", "chai": "^5.1.1", @@ -84,7 +84,7 @@ "typedoc": "^0.26.6", "typedoc-plugin-missing-exports": "^3.0.0", "typescript": "^5.5.4", - "typescript-eslint": "^8.3.0", + "typescript-eslint": "^8.4.0", "winston": "^3.14.2" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43bfce7..95e7c47 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^5.3.0 version: 5.3.0 diff: - specifier: 6.0.0-beta - version: 6.0.0-beta + specifier: 6.0.0 + version: 6.0.0 lodash.isequal: specifier: ^4.5.0 version: 4.5.0 @@ -55,8 +55,8 @@ importers: specifier: ^10.0.7 version: 10.0.7 '@types/node': - specifier: ^22.5.1 - version: 22.5.1 + specifier: ^22.5.3 + version: 22.5.3 '@types/rollup-plugin-node-globals': specifier: ^1.4.4 version: 1.4.4 @@ -64,11 +64,11 @@ importers: specifier: ^0.2.6 version: 0.2.6 '@typescript-eslint/eslint-plugin': - specifier: ^8.3.0 - version: 8.3.0(@typescript-eslint/parser@8.3.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4) + specifier: ^8.4.0 + version: 8.4.0(@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4) '@typescript-eslint/parser': - specifier: ^8.3.0 - version: 8.3.0(eslint@9.9.1)(typescript@5.5.4) + specifier: ^8.4.0 + version: 8.4.0(eslint@9.9.1)(typescript@5.5.4) browser-sync: specifier: ^3.0.2 version: 3.0.2 @@ -137,7 +137,7 @@ importers: version: 0.2.3 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@22.5.1)(typescript@5.5.4) + version: 10.9.2(@types/node@22.5.3)(typescript@5.5.4) tslib: specifier: ^2.7.0 version: 2.7.0 @@ -154,8 +154,8 @@ importers: specifier: ^5.5.4 version: 5.5.4 typescript-eslint: - specifier: ^8.3.0 - version: 8.3.0(eslint@9.9.1)(typescript@5.5.4) + specifier: ^8.4.0 + version: 8.4.0(eslint@9.9.1)(typescript@5.5.4) winston: specifier: ^3.14.2 version: 3.14.2 @@ -528,8 +528,11 @@ packages: cpu: [x64] os: [win32] - '@shikijs/core@1.15.2': - resolution: {integrity: sha512-hi6XZuwHYn6bU4wtXZxST8ynM55aiU2+rVU9aPIrSxqKmEKl4d65puwGsggwcZWTET+7zGXKe7AUj46iQ8Aq8w==} + '@shikijs/core@1.16.2': + resolution: {integrity: sha512-XSVH5OZCvE4WLMgdoBqfPMYmGHGmCC3OgZhw0S7KcSi2XKZ+5oHGe71GFnTljgdOxvxx5WrRks6QoTLKrl1eAA==} + + '@shikijs/vscode-textmate@9.2.0': + resolution: {integrity: sha512-5FinaOp6Vdh/dl4/yaOTh0ZeKch+rYS8DUb38V3GMKYVkdqzxw53lViRKUYkVILRiVQT7dcPC7VvAKOR73zVtQ==} '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -591,8 +594,8 @@ packages: '@types/mocha@10.0.7': resolution: {integrity: sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==} - '@types/node@22.5.1': - resolution: {integrity: sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==} + '@types/node@22.5.3': + resolution: {integrity: sha512-njripolh85IA9SQGTAqbmnNZTdxv7X/4OYGPz8tgy5JDr8MP+uDBa921GpYEoDDnwm0Hmn5ZPeJgiiSTPoOzkQ==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -609,8 +612,8 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@typescript-eslint/eslint-plugin@8.3.0': - resolution: {integrity: sha512-FLAIn63G5KH+adZosDYiutqkOkYEx0nvcwNNfJAf+c7Ae/H35qWwTYvPZUKFj5AS+WfHG/WJJfWnDnyNUlp8UA==} + '@typescript-eslint/eslint-plugin@8.4.0': + resolution: {integrity: sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -620,8 +623,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@8.3.0': - resolution: {integrity: sha512-h53RhVyLu6AtpUzVCYLPhZGL5jzTD9fZL+SYf/+hYOx2bDkyQXztXSc4tbvKYHzfMXExMLiL9CWqJmVz6+78IQ==} + '@typescript-eslint/parser@8.4.0': + resolution: {integrity: sha512-NHgWmKSgJk5K9N16GIhQ4jSobBoJwrmURaLErad0qlLjrpP5bECYg+wxVTGlGZmJbU03jj/dfnb6V9bw+5icsA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -630,12 +633,12 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@8.3.0': - resolution: {integrity: sha512-mz2X8WcN2nVu5Hodku+IR8GgCOl4C0G/Z1ruaWN4dgec64kDBabuXyPAr+/RgJtumv8EEkqIzf3X2U5DUKB2eg==} + '@typescript-eslint/scope-manager@8.4.0': + resolution: {integrity: sha512-n2jFxLeY0JmKfUqy3P70rs6vdoPjHK8P/w+zJcV3fk0b0BwRXC/zxRTEnAsgYT7MwdQDt/ZEbtdzdVC+hcpF0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.3.0': - resolution: {integrity: sha512-wrV6qh//nLbfXZQoj32EXKmwHf4b7L+xXLrP3FZ0GOUU72gSvLjeWUl5J5Ue5IwRxIV1TfF73j/eaBapxx99Lg==} + '@typescript-eslint/type-utils@8.4.0': + resolution: {integrity: sha512-pu2PAmNrl9KX6TtirVOrbLPLwDmASpZhK/XU7WvoKoCUkdtq9zF7qQ7gna0GBZFN0hci0vHaSusiL2WpsQk37A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -643,12 +646,12 @@ packages: typescript: optional: true - '@typescript-eslint/types@8.3.0': - resolution: {integrity: sha512-y6sSEeK+facMaAyixM36dQ5NVXTnKWunfD1Ft4xraYqxP0lC0POJmIaL/mw72CUMqjY9qfyVfXafMeaUj0noWw==} + '@typescript-eslint/types@8.4.0': + resolution: {integrity: sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.3.0': - resolution: {integrity: sha512-Mq7FTHl0R36EmWlCJWojIC1qn/ZWo2YiWYc1XVtasJ7FIgjo0MVv9rZWXEE7IK2CGrtwe1dVOxWwqXUdNgfRCA==} + '@typescript-eslint/typescript-estree@8.4.0': + resolution: {integrity: sha512-kJ2OIP4dQw5gdI4uXsaxUZHRwWAGpREJ9Zq6D5L0BweyOrWsL6Sz0YcAZGWhvKnH7fm1J5YFE1JrQL0c9dd53A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -656,14 +659,14 @@ packages: typescript: optional: true - '@typescript-eslint/utils@8.3.0': - resolution: {integrity: sha512-F77WwqxIi/qGkIGOGXNBLV7nykwfjLsdauRB/DOFPdv6LTF3BHHkBpq81/b5iMPSF055oO2BiivDJV4ChvNtXA==} + '@typescript-eslint/utils@8.4.0': + resolution: {integrity: sha512-swULW8n1IKLjRAgciCkTCafyTHHfwVQFt8DovmaF69sKbOxTSFMmIZaSHjqO9i/RV0wIblaawhzvtva8Nmm7lQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - '@typescript-eslint/visitor-keys@8.3.0': - resolution: {integrity: sha512-RmZwrTbQ9QveF15m/Cl28n0LXD6ea2CjkhH5rQ55ewz3H24w+AMCJHPVYaZ8/0HoG8Z3cLLFFycRXxeO2tz9FA==} + '@typescript-eslint/visitor-keys@8.4.0': + resolution: {integrity: sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} accepts@1.3.8: @@ -957,8 +960,8 @@ packages: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} - diff@6.0.0-beta: - resolution: {integrity: sha512-ZLFOZi58WgvIcAre+bXFmKPx3j9rspGc1nR0pkJLFlbzY1FXPDhJu4sfBSvt4LHTNfPN/yoGy/46GMGxUyXyTQ==} + diff@6.0.0: + resolution: {integrity: sha512-NbGtgPSw7il+jeajji1H6iKjCk3r/ANQKw3FFUhGV50+MH5MKIMeUmi53piTr7jlkWcq9eS858qbkRzkehwe+w==} engines: {node: '>=0.3.1'} eastasianwidth@0.2.0: @@ -1133,8 +1136,8 @@ packages: fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} - follow-redirects@1.15.6: - resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + follow-redirects@1.15.8: + resolution: {integrity: sha512-xgrmBhBToVKay1q2Tao5LI26B83UhrB/vM1avwVSDzt8rx3rO6AizBAaF46EgksTVr+rFTQaqZZ9MVBfUe4nig==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -1748,8 +1751,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shiki@1.15.2: - resolution: {integrity: sha512-M+7QZQZiZw/cZeizrC/yryG3eeG8pTUhu7ZaHxVyzPNFIRIlN46YBciquoNPCiXiwLnx6JB62f3lSuSYQrus1w==} + shiki@1.16.2: + resolution: {integrity: sha512-gSym0hZf5a1U0iDPsdoOAZbvoi+e0c6c3NKAi03FoSLTm7oG20tum29+gk0wzzivOasn3loxfGUPT+jZXIUbWg==} signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} @@ -1913,8 +1916,8 @@ packages: peerDependencies: typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x - typescript-eslint@8.3.0: - resolution: {integrity: sha512-EvWjwWLwwKDIJuBjk2I6UkV8KEQcwZ0VM10nR1rIunRDIP67QJTZAHBXTX0HW/oI1H10YESF8yWie8fRQxjvFA==} + typescript-eslint@8.4.0: + resolution: {integrity: sha512-67qoc3zQZe3CAkO0ua17+7aCLI0dU+sSQd1eKPGq06QE4rfQjstVXR6woHO5qQvGUa550NfGckT4tzh3b3c8Pw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -2019,8 +2022,8 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - yaml@2.5.0: - resolution: {integrity: sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==} + yaml@2.5.1: + resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} engines: {node: '>= 14'} hasBin: true @@ -2326,10 +2329,13 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.21.2': optional: true - '@shikijs/core@1.15.2': + '@shikijs/core@1.16.2': dependencies: + '@shikijs/vscode-textmate': 9.2.0 '@types/hast': 3.0.4 + '@shikijs/vscode-textmate@9.2.0': {} + '@socket.io/component-emitter@3.1.2': {} '@tsconfig/node10@1.0.11': {} @@ -2346,7 +2352,7 @@ snapshots: '@types/cors@2.8.17': dependencies: - '@types/node': 22.5.1 + '@types/node': 22.5.3 '@types/diff@5.2.2': {} @@ -2381,7 +2387,7 @@ snapshots: '@types/mocha@10.0.7': {} - '@types/node@22.5.1': + '@types/node@22.5.3': dependencies: undici-types: 6.19.8 @@ -2389,7 +2395,7 @@ snapshots: '@types/rollup-plugin-node-globals@1.4.4': dependencies: - '@types/node': 22.5.1 + '@types/node': 22.5.3 rollup: 0.63.5 '@types/tmp@0.2.6': {} @@ -2398,14 +2404,14 @@ snapshots: '@types/unist@3.0.3': {} - '@typescript-eslint/eslint-plugin@8.3.0(@typescript-eslint/parser@8.3.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4)': + '@typescript-eslint/eslint-plugin@8.4.0(@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4)': dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 8.3.0(eslint@9.9.1)(typescript@5.5.4) - '@typescript-eslint/scope-manager': 8.3.0 - '@typescript-eslint/type-utils': 8.3.0(eslint@9.9.1)(typescript@5.5.4) - '@typescript-eslint/utils': 8.3.0(eslint@9.9.1)(typescript@5.5.4) - '@typescript-eslint/visitor-keys': 8.3.0 + '@typescript-eslint/parser': 8.4.0(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.4.0 + '@typescript-eslint/type-utils': 8.4.0(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/utils': 8.4.0(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.4.0 eslint: 9.9.1 graphemer: 1.4.0 ignore: 5.3.2 @@ -2416,12 +2422,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.3.0(eslint@9.9.1)(typescript@5.5.4)': + '@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.5.4)': dependencies: - '@typescript-eslint/scope-manager': 8.3.0 - '@typescript-eslint/types': 8.3.0 - '@typescript-eslint/typescript-estree': 8.3.0(typescript@5.5.4) - '@typescript-eslint/visitor-keys': 8.3.0 + '@typescript-eslint/scope-manager': 8.4.0 + '@typescript-eslint/types': 8.4.0 + '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.4.0 debug: 4.3.6(supports-color@8.1.1) eslint: 9.9.1 optionalDependencies: @@ -2429,15 +2435,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.3.0': + '@typescript-eslint/scope-manager@8.4.0': dependencies: - '@typescript-eslint/types': 8.3.0 - '@typescript-eslint/visitor-keys': 8.3.0 + '@typescript-eslint/types': 8.4.0 + '@typescript-eslint/visitor-keys': 8.4.0 - '@typescript-eslint/type-utils@8.3.0(eslint@9.9.1)(typescript@5.5.4)': + '@typescript-eslint/type-utils@8.4.0(eslint@9.9.1)(typescript@5.5.4)': dependencies: - '@typescript-eslint/typescript-estree': 8.3.0(typescript@5.5.4) - '@typescript-eslint/utils': 8.3.0(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.5.4) + '@typescript-eslint/utils': 8.4.0(eslint@9.9.1)(typescript@5.5.4) debug: 4.3.6(supports-color@8.1.1) ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: @@ -2446,12 +2452,12 @@ snapshots: - eslint - supports-color - '@typescript-eslint/types@8.3.0': {} + '@typescript-eslint/types@8.4.0': {} - '@typescript-eslint/typescript-estree@8.3.0(typescript@5.5.4)': + '@typescript-eslint/typescript-estree@8.4.0(typescript@5.5.4)': dependencies: - '@typescript-eslint/types': 8.3.0 - '@typescript-eslint/visitor-keys': 8.3.0 + '@typescript-eslint/types': 8.4.0 + '@typescript-eslint/visitor-keys': 8.4.0 debug: 4.3.6(supports-color@8.1.1) fast-glob: 3.3.2 is-glob: 4.0.3 @@ -2463,20 +2469,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.3.0(eslint@9.9.1)(typescript@5.5.4)': + '@typescript-eslint/utils@8.4.0(eslint@9.9.1)(typescript@5.5.4)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1) - '@typescript-eslint/scope-manager': 8.3.0 - '@typescript-eslint/types': 8.3.0 - '@typescript-eslint/typescript-estree': 8.3.0(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.4.0 + '@typescript-eslint/types': 8.4.0 + '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.5.4) eslint: 9.9.1 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@8.3.0': + '@typescript-eslint/visitor-keys@8.4.0': dependencies: - '@typescript-eslint/types': 8.3.0 + '@typescript-eslint/types': 8.4.0 eslint-visitor-keys: 3.4.3 accepts@1.3.8: @@ -2782,7 +2788,7 @@ snapshots: diff@5.2.0: {} - diff@6.0.0-beta: {} + diff@6.0.0: {} eastasianwidth@0.2.0: {} @@ -2822,7 +2828,7 @@ snapshots: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.17 - '@types/node': 22.5.1 + '@types/node': 22.5.3 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 @@ -3011,7 +3017,7 @@ snapshots: fn.name@1.1.0: {} - follow-redirects@1.15.6: {} + follow-redirects@1.15.8: {} foreground-child@3.3.0: dependencies: @@ -3109,7 +3115,7 @@ snapshots: http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.6 + follow-redirects: 1.15.8 requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -3541,7 +3547,7 @@ snapshots: rollup@0.63.5: dependencies: '@types/estree': 0.0.39 - '@types/node': 22.5.1 + '@types/node': 22.5.3 rollup@4.21.2: dependencies: @@ -3638,9 +3644,10 @@ snapshots: shebang-regex@3.0.0: {} - shiki@1.15.2: + shiki@1.16.2: dependencies: - '@shikijs/core': 1.15.2 + '@shikijs/core': 1.16.2 + '@shikijs/vscode-textmate': 9.2.0 '@types/hast': 3.0.4 signal-exit@4.1.0: {} @@ -3779,14 +3786,14 @@ snapshots: dependencies: typescript: 5.5.4 - ts-node@10.9.2(@types/node@22.5.1)(typescript@5.5.4): + ts-node@10.9.2(@types/node@22.5.3)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.5.1 + '@types/node': 22.5.3 acorn: 8.12.1 acorn-walk: 8.3.3 arg: 4.1.3 @@ -3819,15 +3826,15 @@ snapshots: lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 - shiki: 1.15.2 + shiki: 1.16.2 typescript: 5.5.4 - yaml: 2.5.0 + yaml: 2.5.1 - typescript-eslint@8.3.0(eslint@9.9.1)(typescript@5.5.4): + typescript-eslint@8.4.0(eslint@9.9.1)(typescript@5.5.4): dependencies: - '@typescript-eslint/eslint-plugin': 8.3.0(@typescript-eslint/parser@8.3.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4) - '@typescript-eslint/parser': 8.3.0(eslint@9.9.1)(typescript@5.5.4) - '@typescript-eslint/utils': 8.3.0(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/eslint-plugin': 8.4.0(@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/parser': 8.4.0(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/utils': 8.4.0(eslint@9.9.1)(typescript@5.5.4) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: @@ -3914,7 +3921,7 @@ snapshots: y18n@5.0.8: {} - yaml@2.5.0: {} + yaml@2.5.1: {} yargs-parser@20.2.9: {} diff --git a/src/internal/validator/Objects.mts b/src/internal/validator/Objects.mts index 869c006..0a958a1 100644 --- a/src/internal/validator/Objects.mts +++ b/src/internal/validator/Objects.mts @@ -274,6 +274,7 @@ function assertThatTypeCategory(value: unknown, name: string, category: TypeCate * If `name` is not a string. */ function requireThatInstanceOf(value: unknown, name: string, + // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style type: ClassConstructor | Record) { // WARNING: Per https://github.com/typescript-eslint/typescript-eslint/issues/9370 instanceof returns false diff --git a/tsconfig.json b/tsconfig.json index d32ecc3..8700812 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,8 +11,6 @@ ], "module": "Node16", "moduleResolution": "Node16", - // Fix importing of CommonJS/AMD/UMD modules - "esModuleInterop": true, "strict": true, "forceConsistentCasingInFileNames": true, "verbatimModuleSyntax": true, @@ -20,13 +18,14 @@ "sourceMap": true }, "include": [ - // Files used to generate the target/publish directory + // Files used to generate the target directory "./src" ], "exclude": [ "node_modules", "target", - "./.eslintrc.mjs", + "./eslint.config.d.mts", + "./eslint.config.mjs", "./build", "./test", "./test/TestGlobalConfiguration.mts" From 62c651e6274161a7ba624c1ad1f47d18c164ef5b Mon Sep 17 00:00:00 2001 From: Gili Tzabari Date: Fri, 6 Sep 2024 17:14:45 -0400 Subject: [PATCH 05/15] * Added the value's expected type to the function name (e.g. requireThatString() instead of requireThat()). This is the only way to ensure that the value's compile-time and runtime types match. * Previous validator implementations failed to take undefined values into consideration. --- README.md | 7 +- docs/Changelog.md | 3 + eslint.config.mjs | 57 +- package.json | 2 +- pnpm-lock.yaml | 92 +-- src/DefaultJavascriptValidators.mts | 418 +++++++++++-- src/JavascriptAssertThat.mts | 54 +- src/JavascriptCheckIf.mts | 53 +- src/JavascriptRequireThat.mts | 53 +- src/JavascriptValidators.mts | 94 ++- src/MultipleFailuresError.mts | 5 +- src/Type.mts | 27 +- src/ValidationFailure.mts | 2 + src/Validators.mts | 10 +- src/index.mts | 24 +- src/internal/Configuration.mts | 4 +- src/internal/ConfigurationUpdater.mts | 2 + src/internal/StringMappers.mts | 11 +- src/internal/internal.mts | 55 +- src/internal/message/BooleanMessages.mts | 4 +- src/internal/message/ClassMessages.mts | 6 +- src/internal/message/CollectionMessages.mts | 55 +- src/internal/message/ComparableMessages.mts | 28 +- src/internal/message/NumberMessages.mts | 31 +- src/internal/message/ObjectMessages.mts | 4 +- src/internal/message/StringMessages.mts | 24 +- src/internal/message/ValidatorMessages.mts | 30 +- .../message/diff/ContextGenerator.mts | 6 +- src/internal/message/diff/DiffGenerator.mts | 5 +- .../message/section/MessageBuilder.mts | 6 +- src/internal/scope/ApplicationScope.mts | 1 + .../scope/MainGlobalConfiguration.mts | 2 - src/internal/util/Strings.mts | 3 +- src/internal/util/ValidationTarget.mts | 32 +- .../validator/AbstractCollectionValidator.mts | 155 +++-- src/internal/validator/AbstractValidator.mts | 93 ++- src/internal/validator/AbstractValidators.mts | 55 +- src/internal/validator/ArrayValidatorImpl.mts | 22 +- .../validator/BooleanValidatorImpl.mts | 19 +- src/internal/validator/ClassValidatorImpl.mts | 10 +- .../validator/JavascriptValidatorsImpl.mts | 566 +++++++++++++++--- src/internal/validator/MapValidatorImpl.mts | 33 +- .../validator/MutableConfiguration.mts | 17 +- .../validator/NumberValidatorImpl.mts | 178 +++--- .../validator/ObjectSizeValidatorImpl.mts | 148 +++-- .../validator/ObjectValidatorImpl.mts | 6 +- src/internal/validator/Objects.mts | 29 +- src/internal/validator/SetValidatorImpl.mts | 11 +- .../validator/StringValidatorImpl.mts | 90 ++- src/internal/validator/Terminal.mts | 2 +- .../validator/ValidationFailureImpl.mts | 2 - src/validator/ArrayValidator.mts | 8 +- src/validator/BooleanValidator.mts | 12 +- src/validator/ClassValidator.mts | 2 +- src/validator/MapValidator.mts | 13 +- src/validator/NumberValidator.mts | 12 +- src/validator/ObjectValidator.mts | 2 +- src/validator/SetValidator.mts | 5 +- src/validator/StringValidator.mts | 26 +- src/validator/UnsignedNumberValidator.mts | 8 +- .../component/CollectionComponent.mts | 73 +-- .../component/NegativeNumberComponent.mts | 8 +- src/validator/component/NumberComponent.mts | 42 +- .../component/PositiveNumberComponent.mts | 8 +- .../component/ValidatorComponent.mts | 32 +- .../component/ZeroNumberComponent.mts | 8 +- test/ArrayTest.mts | 134 +++-- test/BooleanTest.mts | 16 +- test/ConfigurationTest.mts | 4 +- test/DiffTest.mts | 56 +- test/GenericTest.mts | 52 +- test/MapTest.mts | 105 ++-- test/NumberTest.mts | 214 +++---- test/ObjectsTest.mts | 8 +- test/RequirementsTest.mts | 20 +- test/SetTest.mts | 87 +-- test/SizeTest.mts | 42 +- test/StringTest.mts | 66 +- test/ValidationFailureTest.mts | 17 +- tsconfig.json | 11 +- 80 files changed, 2254 insertions(+), 1483 deletions(-) diff --git a/README.md b/README.md index 2d75734..8ebb00d 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ pnpm add @cowwoc/requirements@4.0.0 ## Sample Code ```typescript -import {requireThat} from "@cowwoc/requirements"; +import {requireThatString} from "@cowwoc/requirements"; class Address @@ -41,9 +41,8 @@ class PublicAPI { constructor(name: string | null, age: number, address: Address | undefined) { - // To validate user input, cast them to "unknown" prior to type-checks. - requireThat(name as unknown, "name").isString().length().isBetween(1, 30); - requireThat(age as unknown, "age").isNumber().isBetween(18, 30); + requireThatString(name, "name").length().isBetween(1, 30); + requireThatNumber(age, "age").isBetween(18, 30); // Methods that conduct runtime type-checks, such as isString() or isNotNull(), update the // compile-time type returned by getValue(). diff --git a/docs/Changelog.md b/docs/Changelog.md index 40ead19..486c03d 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -5,6 +5,9 @@ https://github.com/cowwoc/requirements.java/commits/master for a full list. * Breaking changes: * Replaced `validateThat()` with `checkIf()`. + * Validation methods now contain the type of value being validated (e.g. `requireThatString()` instead of + `requireThat()`). This is the only way to ensure that the return type of the method matches the + compile-time type of the value being validated. * Use consistent parameter ordering across the entire API: `(value, name)`. * Adding contextual information now looks like this: `requireThat().withContext(value, name)` * Added `Validator.and(validation: (validator: S) => void)` to nest validations. diff --git a/eslint.config.mjs b/eslint.config.mjs index 6ccdeb7..a17fee7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -13,45 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import esLint from "@eslint/js"; +import eslint from "@eslint/js"; // https://typescript-eslint.io/getting-started#step-2-configuration -import tsLint from "typescript-eslint"; +import tsEslint from "typescript-eslint"; import tsdoc from "eslint-plugin-tsdoc"; -import typescriptParser from "@typescript-eslint/parser"; -export default [ - esLint.configs.recommended, +export default tsEslint.config( + eslint.configs.recommended, // https://typescript-eslint.io/getting-started/typed-linting/ - ...tsLint.configs.strict, - ...tsLint.configs.recommendedTypeChecked, + ...tsEslint.configs.strictTypeChecked, + ...tsEslint.configs.recommendedTypeChecked, { - "settings": { - // https://github.com/import-js/eslint-plugin-import#typescript - "typescript": true, - "node": true, - "import/parsers": { - "@typescript-eslint/parser": [".mts"] - }, - // https://github.com/kriasoft/react-starter-kit/issues/1180#issuecomment-436753540 - "import/resolver": { - "typescript": { - "alwaysTryTypes": true - } - } - }, - ignores: [ - "**/node_modules", - "/target/**", - "./*.mts" - ], languageOptions: { - parser: typescriptParser, - ecmaVersion: "latest", - sourceType: "module", // https://typescript-eslint.io/getting-started/typed-linting/ parserOptions: { - project: true, - // "project": "tsconfig.json" + projectService: true, tsconfigRootDir: import.meta.dirname } }, @@ -63,7 +39,8 @@ export default [ "indent": "off", "quotes": [ "error", - "double" + "double", + {"allowTemplateLiterals": true} ], "semi": [ "error", @@ -79,7 +56,19 @@ export default [ "@typescript-eslint/consistent-indexed-object-style": [ "error", "index-signature" + ], + "@typescript-eslint/restrict-template-expressions": [ + "error", + { + allowNumber: true + } ] } - } -]; \ No newline at end of file + }, { + ignores: [ + // Do not lint this configuration file + "eslint.config.mjs", + "/target/**", + "**/node_modules" + ] + }); \ No newline at end of file diff --git a/package.json b/package.json index eea11f3..8acae74 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "tsx": "^4.19.0", "typedoc": "^0.26.6", "typedoc-plugin-missing-exports": "^3.0.0", - "typescript": "^5.5.4", + "typescript": "5.4.5", "typescript-eslint": "^8.4.0", "winston": "^3.14.2" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 95e7c47..14ef503 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,7 +29,7 @@ importers: version: 15.2.3(rollup@4.21.2) '@rollup/plugin-typescript': specifier: ^11.1.6 - version: 11.1.6(rollup@4.21.2)(tslib@2.7.0)(typescript@5.5.4) + version: 11.1.6(rollup@4.21.2)(tslib@2.7.0)(typescript@5.4.5) '@types/chai': specifier: ^4.3.19 version: 4.3.19 @@ -65,10 +65,10 @@ importers: version: 0.2.6 '@typescript-eslint/eslint-plugin': specifier: ^8.4.0 - version: 8.4.0(@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4) + version: 8.4.0(@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.4.5))(eslint@9.9.1)(typescript@5.4.5) '@typescript-eslint/parser': specifier: ^8.4.0 - version: 8.4.0(eslint@9.9.1)(typescript@5.5.4) + version: 8.4.0(eslint@9.9.1)(typescript@5.4.5) browser-sync: specifier: ^3.0.2 version: 3.0.2 @@ -137,7 +137,7 @@ importers: version: 0.2.3 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@22.5.3)(typescript@5.5.4) + version: 10.9.2(@types/node@22.5.3)(typescript@5.4.5) tslib: specifier: ^2.7.0 version: 2.7.0 @@ -146,16 +146,16 @@ importers: version: 4.19.0 typedoc: specifier: ^0.26.6 - version: 0.26.6(typescript@5.5.4) + version: 0.26.6(typescript@5.4.5) typedoc-plugin-missing-exports: specifier: ^3.0.0 - version: 3.0.0(typedoc@0.26.6(typescript@5.5.4)) + version: 3.0.0(typedoc@0.26.6(typescript@5.4.5)) typescript: - specifier: ^5.5.4 - version: 5.5.4 + specifier: 5.4.5 + version: 5.4.5 typescript-eslint: specifier: ^8.4.0 - version: 8.4.0(eslint@9.9.1)(typescript@5.5.4) + version: 8.4.0(eslint@9.9.1)(typescript@5.4.5) winston: specifier: ^3.14.2 version: 3.14.2 @@ -1925,8 +1925,8 @@ packages: typescript: optional: true - typescript@5.5.4: - resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} engines: {node: '>=14.17'} hasBin: true @@ -2264,11 +2264,11 @@ snapshots: optionalDependencies: rollup: 4.21.2 - '@rollup/plugin-typescript@11.1.6(rollup@4.21.2)(tslib@2.7.0)(typescript@5.5.4)': + '@rollup/plugin-typescript@11.1.6(rollup@4.21.2)(tslib@2.7.0)(typescript@5.4.5)': dependencies: '@rollup/pluginutils': 5.1.0(rollup@4.21.2) resolve: 1.22.8 - typescript: 5.5.4 + typescript: 5.4.5 optionalDependencies: rollup: 4.21.2 tslib: 2.7.0 @@ -2404,34 +2404,34 @@ snapshots: '@types/unist@3.0.3': {} - '@typescript-eslint/eslint-plugin@8.4.0(@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4)': + '@typescript-eslint/eslint-plugin@8.4.0(@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.4.5))(eslint@9.9.1)(typescript@5.4.5)': dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 8.4.0(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/parser': 8.4.0(eslint@9.9.1)(typescript@5.4.5) '@typescript-eslint/scope-manager': 8.4.0 - '@typescript-eslint/type-utils': 8.4.0(eslint@9.9.1)(typescript@5.5.4) - '@typescript-eslint/utils': 8.4.0(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/type-utils': 8.4.0(eslint@9.9.1)(typescript@5.4.5) + '@typescript-eslint/utils': 8.4.0(eslint@9.9.1)(typescript@5.4.5) '@typescript-eslint/visitor-keys': 8.4.0 eslint: 9.9.1 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.5.4) + ts-api-utils: 1.3.0(typescript@5.4.5) optionalDependencies: - typescript: 5.5.4 + typescript: 5.4.5 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.5.4)': + '@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.4.5)': dependencies: '@typescript-eslint/scope-manager': 8.4.0 '@typescript-eslint/types': 8.4.0 - '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.4.5) '@typescript-eslint/visitor-keys': 8.4.0 debug: 4.3.6(supports-color@8.1.1) eslint: 9.9.1 optionalDependencies: - typescript: 5.5.4 + typescript: 5.4.5 transitivePeerDependencies: - supports-color @@ -2440,21 +2440,21 @@ snapshots: '@typescript-eslint/types': 8.4.0 '@typescript-eslint/visitor-keys': 8.4.0 - '@typescript-eslint/type-utils@8.4.0(eslint@9.9.1)(typescript@5.5.4)': + '@typescript-eslint/type-utils@8.4.0(eslint@9.9.1)(typescript@5.4.5)': dependencies: - '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.5.4) - '@typescript-eslint/utils': 8.4.0(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.4.5) + '@typescript-eslint/utils': 8.4.0(eslint@9.9.1)(typescript@5.4.5) debug: 4.3.6(supports-color@8.1.1) - ts-api-utils: 1.3.0(typescript@5.5.4) + ts-api-utils: 1.3.0(typescript@5.4.5) optionalDependencies: - typescript: 5.5.4 + typescript: 5.4.5 transitivePeerDependencies: - eslint - supports-color '@typescript-eslint/types@8.4.0': {} - '@typescript-eslint/typescript-estree@8.4.0(typescript@5.5.4)': + '@typescript-eslint/typescript-estree@8.4.0(typescript@5.4.5)': dependencies: '@typescript-eslint/types': 8.4.0 '@typescript-eslint/visitor-keys': 8.4.0 @@ -2463,18 +2463,18 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.5.4) + ts-api-utils: 1.3.0(typescript@5.4.5) optionalDependencies: - typescript: 5.5.4 + typescript: 5.4.5 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.4.0(eslint@9.9.1)(typescript@5.5.4)': + '@typescript-eslint/utils@8.4.0(eslint@9.9.1)(typescript@5.4.5)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1) '@typescript-eslint/scope-manager': 8.4.0 '@typescript-eslint/types': 8.4.0 - '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.4.5) eslint: 9.9.1 transitivePeerDependencies: - supports-color @@ -3782,11 +3782,11 @@ snapshots: triple-beam@1.4.1: {} - ts-api-utils@1.3.0(typescript@5.5.4): + ts-api-utils@1.3.0(typescript@5.4.5): dependencies: - typescript: 5.5.4 + typescript: 5.4.5 - ts-node@10.9.2(@types/node@22.5.3)(typescript@5.5.4): + ts-node@10.9.2(@types/node@22.5.3)(typescript@5.4.5): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -3800,7 +3800,7 @@ snapshots: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.5.4 + typescript: 5.4.5 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 @@ -3817,31 +3817,31 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typedoc-plugin-missing-exports@3.0.0(typedoc@0.26.6(typescript@5.5.4)): + typedoc-plugin-missing-exports@3.0.0(typedoc@0.26.6(typescript@5.4.5)): dependencies: - typedoc: 0.26.6(typescript@5.5.4) + typedoc: 0.26.6(typescript@5.4.5) - typedoc@0.26.6(typescript@5.5.4): + typedoc@0.26.6(typescript@5.4.5): dependencies: lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 shiki: 1.16.2 - typescript: 5.5.4 + typescript: 5.4.5 yaml: 2.5.1 - typescript-eslint@8.4.0(eslint@9.9.1)(typescript@5.5.4): + typescript-eslint@8.4.0(eslint@9.9.1)(typescript@5.4.5): dependencies: - '@typescript-eslint/eslint-plugin': 8.4.0(@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4) - '@typescript-eslint/parser': 8.4.0(eslint@9.9.1)(typescript@5.5.4) - '@typescript-eslint/utils': 8.4.0(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/eslint-plugin': 8.4.0(@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.4.5))(eslint@9.9.1)(typescript@5.4.5) + '@typescript-eslint/parser': 8.4.0(eslint@9.9.1)(typescript@5.4.5) + '@typescript-eslint/utils': 8.4.0(eslint@9.9.1)(typescript@5.4.5) optionalDependencies: - typescript: 5.5.4 + typescript: 5.4.5 transitivePeerDependencies: - eslint - supports-color - typescript@5.5.4: {} + typescript@5.4.5: {} ua-parser-js@1.0.38: {} diff --git a/src/DefaultJavascriptValidators.mts b/src/DefaultJavascriptValidators.mts index 579a40a..32e111a 100644 --- a/src/DefaultJavascriptValidators.mts +++ b/src/DefaultJavascriptValidators.mts @@ -15,11 +15,13 @@ import { AssertionError } from "./internal/internal.mjs"; -const typedocWorkaround: null | ValidatorComponent | JavascriptValidators | +const typedocWorkaround: null | ValidatorComponent | JavascriptValidators | AssertionError = null; +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ // noinspection PointlessBooleanExpressionJS if (typedocWorkaround !== null) console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); +/* eslint-enable @typescript-eslint/no-unnecessary-condition */ /** * Creates validators for the Javascript API. @@ -35,41 +37,357 @@ if (typedocWorkaround !== null) const DELEGATE = new JavascriptValidatorsImpl(MainApplicationScope.INSTANCE, Configuration.DEFAULT); /** - * Validates the state of a value. + * Validates the state of a number. *

                            * The returned validator throws an error immediately if a validation fails. * * @typeParam T - the type the value - * @typeParam E - the type elements in the array or set + * @param value - the value + * @param name - the name of the value + * @returns a verifier + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function requireThatNumber +(value: T, name: string): NumberValidator +{ + return DELEGATE.requireThatNumber(value, name); +} + +/** + * Validates the state of a boolean. + *

                            + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns a verifier + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function requireThatBoolean +(value: T, name: string): BooleanValidator +{ + return DELEGATE.requireThatBoolean(value, name); +} + +/** + * Validates the state of an array. + *

                            + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the array + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function requireThatArray +(value: T, name: string): ArrayValidator +{ + return DELEGATE.requireThatArray(value, name); +} + +/** + * Validates the state of a set. + *

                            + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the set + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function requireThatSet | undefined | null, E> +(value: T, name: string): SetValidator +{ + return DELEGATE.requireThatSet(value, name); +} + +/** + * Validates the state of a map. + *

                            + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value * @typeParam K - the type of keys in the map * @typeParam V - the type of values in the map * @param value - the value * @param name - the name of the value - * @returns a verifier + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function requireThatMap | undefined | null, K, V> +(value: T, name: string): MapValidator +{ + return DELEGATE.requireThatMap(value, name); +} + +/** + * Validates the state of a string. + *

                            + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function requireThatString +(value: T, name: string): StringValidator +{ + return DELEGATE.requireThatString(value, name); +} + +/** + * Validates the state of an object. + *

                            + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function requireThatObject +(value: T, name: string): ObjectValidator +{ + return DELEGATE.requireThatObject(value, name); +} + +/** + * Validates the state of a number. + *

                            + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function assertThatNumber +(value: T, name?: string): NumberValidator +{ + return DELEGATE.assertThatNumber(value, name); +} + +/** + * Validates the state of a boolean. + *

                            + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function assertThatBoolean +(value: T, name?: string): BooleanValidator +{ + return DELEGATE.assertThatBoolean(value, name); +} + +/** + * Validates the state of an array. + *

                            + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the array + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function assertThatArray +(value: T, name?: string): ArrayValidator +{ + return DELEGATE.assertThatArray(value, name); +} + +/** + * Validates the state of a set. + *

                            + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the set + * @param value - the value + * @param name - the name of the value + * @returns validator for the value * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` is empty */ -function requireThat(value: number, name: string): NumberValidator; -function requireThat(value: boolean, name: string): BooleanValidator; -function requireThat(value: E[], name: string): ArrayValidator; -function requireThat(value: Set, name: string): SetValidator; -function requireThat(value: Map, name: string): MapValidator; -function requireThat(value: string, name: string): StringValidator; -function requireThat(value: T, name: string): ObjectValidator; -function requireThat(value: T, name: string): NumberValidator | BooleanValidator | - ArrayValidator | SetValidator | MapValidator | StringValidator | ObjectValidator +function assertThatSet | undefined | null, E> +(value: T, name?: string): SetValidator { - return DELEGATE.requireThat(value, name); + return DELEGATE.assertThatSet(value, name); } /** - * Validates the state of a value. + * Validates the state of a map. *

                            * The returned validator throws an exception immediately if a validation fails. This exception is then * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. * * @typeParam T - the type the value + * @typeParam K - the type of keys in the map + * @typeParam V - the type of values in the map + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function assertThatMap | undefined | null, K, V> +(value: T, name?: string): MapValidator +{ + return DELEGATE.assertThatMap(value, name); +} + +/** + * Validates the state of a string. + *

                            + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function assertThatString +(value: T, name?: string): StringValidator +{ + return DELEGATE.assertThatString(value, name); +} + +/** + * Validates the state of a number. + *

                            + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function assertThatObject +(value: T, name?: string): ObjectValidator +{ + return DELEGATE.assertThatObject(value, name); +} + +/** + * Validates the state of a number. + *

                            + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function checkIfNumber +(value: T, name: string): NumberValidator +{ + return DELEGATE.checkIfNumber(value, name); +} + +/** + * Validates the state of a boolean. + *

                            + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function checkIfBoolean +(value: T, name: string): BooleanValidator +{ + return DELEGATE.checkIfBoolean(value, name); +} + +/** + * Validates the state of an array. + *

                            + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the array + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function checkIfArray +(value: T, name: string): ArrayValidator +{ + return DELEGATE.checkIfArray(value, name); +} + +/** + * Validates the state of a set. + *

                            + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value * @typeParam E - the type elements in the array or set + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function checkIfSet | undefined | null, E> +(value: T, name: string): SetValidator +{ + return DELEGATE.checkIfSet(value, name); +} + +/** + * Validates the state of a map. + *

                            + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value * @typeParam K - the type of keys in the map * @typeParam V - the type of values in the map * @param value - the value @@ -78,21 +396,32 @@ function requireThat(value: T, name: string): NumberValidator | Bool * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` is empty */ -function assertThat(value: number, name?: string): NumberValidator; -function assertThat(value: boolean, name?: string): BooleanValidator; -function assertThat(value: E[], name?: string): ArrayValidator; -function assertThat(value: Set, name?: string): SetValidator; -function assertThat(value: Map, name?: string): MapValidator; -function assertThat(value: string, name?: string): StringValidator; -function assertThat(value: T, name?: string): ObjectValidator; -function assertThat(value: T, name?: string): NumberValidator | BooleanValidator | - ArrayValidator | SetValidator | MapValidator | StringValidator | ObjectValidator +function checkIfMap, K, V> +(value: T, name: string): MapValidator +{ + return DELEGATE.checkIfMap(value, name); +} + +/** + * Validates the state of a string. + *

                            + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ +function checkIfString +(value: T, name: string): StringValidator { - return DELEGATE.assertThat(value, name); + return DELEGATE.checkIfString(value, name); } /** - * Validates the state of a value. + * Validates the state of an object. *

                            * The returned validator throws an error immediately if a validation fails. * @@ -106,17 +435,10 @@ function assertThat(value: T, name?: string): NumberValidator | Bool * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` is empty */ -function checkIf(value: number, name: string): NumberValidator; -function checkIf(value: boolean, name: string): BooleanValidator; -function checkIf(value: E[], name: string): ArrayValidator; -function checkIf(value: Set, name: string): SetValidator; -function checkIf(value: Map, name: string): MapValidator; -function checkIf(value: string, name: string): StringValidator; -function checkIf(value: T, name: string): ObjectValidator; -function checkIf(value: T, name: string): NumberValidator | BooleanValidator | - ArrayValidator | SetValidator | MapValidator | StringValidator | ObjectValidator +function checkIfObject +(value: T, name: string): ObjectValidator { - return DELEGATE.checkIf(value, name); + return DELEGATE.checkIfObject(value, name); } /** @@ -193,9 +515,27 @@ function globalConfiguration() } export { - requireThat, - assertThat, - checkIf, + requireThatNumber, + requireThatBoolean, + requireThatArray, + requireThatSet, + requireThatMap, + requireThatString, + requireThatObject, + assertThatNumber, + assertThatBoolean, + assertThatArray, + assertThatSet, + assertThatMap, + assertThatString, + assertThatObject, + checkIfNumber, + checkIfBoolean, + checkIfArray, + checkIfSet, + checkIfMap, + checkIfString, + checkIfObject, updateConfiguration, getContext, withContext, diff --git a/src/JavascriptAssertThat.mts b/src/JavascriptAssertThat.mts index 7fa55ca..099b84f 100644 --- a/src/JavascriptAssertThat.mts +++ b/src/JavascriptAssertThat.mts @@ -15,10 +15,13 @@ import { } from "./internal/internal.mjs"; const typedocWorkaround: null | AssertionError = null; +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ // noinspection PointlessBooleanExpressionJS if (typedocWorkaround !== null) console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); +/* eslint-enable @typescript-eslint/no-unnecessary-condition */ + /** * Creates validators for the Javascript API that throw `AssertionError` immediately on validation failure. */ @@ -30,13 +33,14 @@ interface JavascriptAssertThat * The returned validator throws an error immediately if a validation fails. This error is then * converted into an {@link AssertionError}. Errors unrelated to validation failures are not converted. * + * @typeParam T - the type of the value * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is `null` + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - assertThat(value: number, name?: string): NumberValidator; + assertThatNumber(value: T, name?: string): NumberValidator; /** * Validates the state of a `boolean`. @@ -44,87 +48,93 @@ interface JavascriptAssertThat * The returned validator throws an error immediately if a validation fails. This error is then * converted into an {@link AssertionError}. Errors unrelated to validation failures are not converted. * + * @typeParam T - the type of the value * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is `null` + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - assertThat(value: boolean, name?: string): BooleanValidator; + assertThatBoolean(value: T, name?: string): BooleanValidator; /** - * Validates the state of an `object`. + * Validates the state of an array. *

                            * The returned validator throws an error immediately if a validation fails. This error is then * converted into an {@link AssertionError}. Errors unrelated to validation failures are not converted. * * @typeParam T - the type of the value + * @typeParam E - the type of elements in the collection * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is `null` + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - assertThat(value: T, name?: string): ObjectValidator; + assertThatArray(value: T, name?: string): ArrayValidator; /** - * Validates the state of an array. + * Validates the state of a `Set`. *

                            * The returned validator throws an error immediately if a validation fails. This error is then * converted into an {@link AssertionError}. Errors unrelated to validation failures are not converted. * + * @typeParam T - the type of the value + * @typeParam E - the type of elements in the set * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is `null` + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - assertThat(value: E[], name?: string): ArrayValidator; + assertThatSet | undefined | null, E>(value: T, name?: string): SetValidator; /** - * Validates the state of a `Set`. + * Validates the state of a `Map`. *

                            * The returned validator throws an error immediately if a validation fails. This error is then * converted into an {@link AssertionError}. Errors unrelated to validation failures are not converted. * - * @typeParam E - the type of elements in the set + * @typeParam T - the type of the value + * @typeParam K - the type of keys in the map + * @typeParam V - the type of values in the map * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is `null` + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - assertThat(value: Set, name?: string): SetValidator; + assertThatMap | undefined | null, K, V>(value: T, name?: string): MapValidator; /** - * Validates the state of a `Map`. + * Validates the state of a `string`. *

                            * The returned validator throws an error immediately if a validation fails. This error is then * converted into an {@link AssertionError}. Errors unrelated to validation failures are not converted. * - * @typeParam K - the type of keys in the map - * @typeParam V - the type of values in the map + * @typeParam T - the type of the value * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is `null` + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - assertThat(value: Map, name?: string): MapValidator; + assertThatString(value: T, name?: string): StringValidator; /** - * Validates the state of a `string`. + * Validates the state of an `object`. *

                            * The returned validator throws an error immediately if a validation fails. This error is then * converted into an {@link AssertionError}. Errors unrelated to validation failures are not converted. * + * @typeParam T - the type of the value * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is `null` + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - assertThat(value: string, name?: string): StringValidator; + assertThatObject(value: T, name?: string): ObjectValidator; } export type {JavascriptAssertThat}; \ No newline at end of file diff --git a/src/JavascriptCheckIf.mts b/src/JavascriptCheckIf.mts index e0d1064..ea58a79 100644 --- a/src/JavascriptCheckIf.mts +++ b/src/JavascriptCheckIf.mts @@ -10,7 +10,7 @@ import { type ObjectValidator, type ArrayValidator, type SetValidator, - type MapValidator, + type MapValidator } from "./internal/internal.mjs"; /** @@ -26,13 +26,14 @@ interface JavascriptCheckIf * These errors can be retrieved or thrown once the validation completes. Errors unrelated to * validation failures are thrown immediately. * + * @typeParam T - the type of the value * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is `null` + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - checkIf(value: number, name: string): NumberValidator; + checkIfNumber(value: T, name: string): NumberValidator; /** * Validates the state of a `boolean`. @@ -41,92 +42,98 @@ interface JavascriptCheckIf * These errors can be retrieved or thrown once the validation completes. Errors unrelated to * validation failures are thrown immediately. * + * @typeParam T - the type of the value * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is `null` + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - checkIf(value: boolean, name: string): BooleanValidator; + checkIfBoolean(value: T, name: string): BooleanValidator; /** - * Validates the state of an `object`. + * Validates the state of an array. *

                            * The returned validator captures errors on validation failure rather than throwing them immediately. * These errors can be retrieved or thrown once the validation completes. Errors unrelated to * validation failures are thrown immediately. * * @typeParam T - the type of the value + * @typeParam E - the type of elements in the collection * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is `null` + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - checkIf(value: T, name: string): ObjectValidator; + checkIfArray(value: T, name: string): ArrayValidator; /** - * Validates the state of an array. + * Validates the state of a `Set`. *

                            * The returned validator captures errors on validation failure rather than throwing them immediately. * These errors can be retrieved or thrown once the validation completes. Errors unrelated to * validation failures are thrown immediately. * + * @typeParam T - the type of the value + * @typeParam E - the type of elements in the set * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is `null` + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - checkIf(value: E[], name: string): ArrayValidator; + checkIfSet | undefined | null, E>(value: T, name: string): SetValidator; /** - * Validates the state of a `Set`. + * Validates the state of a `Map`. *

                            * The returned validator captures errors on validation failure rather than throwing them immediately. * These errors can be retrieved or thrown once the validation completes. Errors unrelated to * validation failures are thrown immediately. * - * @typeParam E - the type of elements in the set + * @typeParam T - the type of the value + * @typeParam K - the type of keys in the map + * @typeParam V - the type of values in the map * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - checkIf(value: Set, name: string): SetValidator; + checkIfMap | undefined | null, K, V>(value: T, name: string): MapValidator; /** - * Validates the state of a `Map`. + * Validates the state of a `string`. *

                            * The returned validator captures errors on validation failure rather than throwing them immediately. * These errors can be retrieved or thrown once the validation completes. Errors unrelated to * validation failures are thrown immediately. * - * @typeParam K - the type of keys in the map - * @typeParam V - the type of values in the map + * @typeParam T - the type of the value * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - checkIf(value: Map, name: string): MapValidator; + checkIfString(value: T, name: string): StringValidator; /** - * Validates the state of a `string`. + * Validates the state of an `object`. *

                            * The returned validator captures errors on validation failure rather than throwing them immediately. * These errors can be retrieved or thrown once the validation completes. Errors unrelated to * validation failures are thrown immediately. * + * @typeParam T - the type of the value * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - checkIf(value: string, name: string): StringValidator; + checkIfObject(value: T, name: string): ObjectValidator; } export type {JavascriptCheckIf}; \ No newline at end of file diff --git a/src/JavascriptRequireThat.mts b/src/JavascriptRequireThat.mts index a8c44b1..80ab78d 100644 --- a/src/JavascriptRequireThat.mts +++ b/src/JavascriptRequireThat.mts @@ -10,7 +10,7 @@ import { type SetValidator, type MapValidator, type StringValidator, - type ObjectValidator, + type ObjectValidator } from "./internal/internal.mjs"; /** @@ -23,95 +23,102 @@ interface JavascriptRequireThat *

                            * The returned validator throws an error immediately if a validation fails. * + * @typeParam T - the type of the value * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - requireThat(value: number, name: string): NumberValidator; + requireThatNumber(value: T, name: string): NumberValidator; /** * Validates the state of a `boolean`. *

                            * The returned validator throws an error immediately if a validation fails. * + * @typeParam T - the type of the value * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - requireThat(value: boolean, name: string): BooleanValidator; + requireThatBoolean(value: T, name: string): BooleanValidator; /** - * Validates the state of an `object`. + * Validates the state of an array. *

                            * The returned validator throws an error immediately if a validation fails. * * @typeParam T - the type of the value + * @typeParam E - the type of elements in the collection * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - requireThat(value: T, name: string): ObjectValidator; + requireThatArray(value: T, name: string): ArrayValidator; /** - * Validates the state of an array. + * Validates the state of a `Set`. *

                            * The returned validator throws an error immediately if a validation fails. * + * @typeParam T - the type of the value + * @typeParam E - the type of elements in the set * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - requireThat(value: E[], name: string): ArrayValidator; + requireThatSet | undefined | null, E>(value: T, name: string): SetValidator; /** - * Validates the state of a `Set`. + * Validates the state of a `Map`. *

                            * The returned validator throws an error immediately if a validation fails. * - * @typeParam E - the type of elements in the set + * @typeParam T - the type of the value + * @typeParam K - the type of keys in the map + * @typeParam V - the type of values in the map * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - requireThat(value: Set, name: string): SetValidator; + requireThatMap | undefined | null, K, V>(value: T, name: string): MapValidator; /** - * Validates the state of a `Map`. + * Validates the state of a `string`. *

                            * The returned validator throws an error immediately if a validation fails. * - * @typeParam K - the type of keys in the map - * @typeParam V - the type of values in the map + * @typeParam T - the type of the value * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - requireThat(value: Map, name: string): MapValidator; + requireThatString(value: T, name: string): StringValidator; /** - * Validates the state of a `string`. + * Validates the state of an `object`. *

                            * The returned validator throws an error immediately if a validation fails. * + * @typeParam T - the type of the value * @param value - the value * @param name - the name of the value * @returns a validator for the value - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - requireThat(value: string, name: string): StringValidator; + requireThatObject(value: T, name: string): ObjectValidator; } export type {JavascriptRequireThat}; \ No newline at end of file diff --git a/src/JavascriptValidators.mts b/src/JavascriptValidators.mts index 1d62579..6ca1e5c 100644 --- a/src/JavascriptValidators.mts +++ b/src/JavascriptValidators.mts @@ -23,10 +23,13 @@ import { } from "./internal/internal.mjs"; const typedocWorkaround: null | AssertionError = null; +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ // noinspection PointlessBooleanExpressionJS if (typedocWorkaround !== null) console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); +/* eslint-enable @typescript-eslint/no-unnecessary-condition */ + /** * Creates validators for the Javascript API with an independent configuration. *

                            @@ -56,35 +59,74 @@ abstract class JavascriptValidators abstract getContext(): Map; - abstract withContext(value: unknown, name: string): JavascriptValidators; + abstract withContext(value: unknown, name: string): this; abstract getGlobalConfiguration(): GlobalConfiguration; - abstract removeContext(name: string): JavascriptValidators; - - abstract requireThat(value: number, name: string): NumberValidator; - abstract requireThat(value: boolean, name: string): BooleanValidator; - abstract requireThat(value: E[], name: string): ArrayValidator; - abstract requireThat(value: Set, name: string): SetValidator; - abstract requireThat(value: Map, name: string): MapValidator; - abstract requireThat(value: string, name: string): StringValidator; - abstract requireThat(value: T, name: string): ObjectValidator; - - abstract assertThat(value: number, name?: string): NumberValidator; - abstract assertThat(value: boolean, name?: string): BooleanValidator; - abstract assertThat(value: E[], name?: string): ArrayValidator; - abstract assertThat(value: Set, name?: string): SetValidator; - abstract assertThat(value: Map, name?: string): MapValidator; - abstract assertThat(value: string, name?: string): StringValidator; - abstract assertThat(value: T, name?: string): ObjectValidator; - - abstract checkIf(value: number, name?: string): NumberValidator; - abstract checkIf(value: boolean, name?: string): BooleanValidator; - abstract checkIf(value: E[], name?: string): ArrayValidator; - abstract checkIf(value: Set, name?: string): SetValidator; - abstract checkIf(value: Map, name?: string): MapValidator; - abstract checkIf(value: string, name?: string): StringValidator; - abstract checkIf(value: T, name?: string): ObjectValidator; + abstract removeContext(name: string): this; + + abstract requireThatNumber + (value: T, name: string): NumberValidator; + + abstract requireThatBoolean + (value: T, name: string): BooleanValidator; + + abstract requireThatArray + (value: T, name: string): ArrayValidator; + + abstract requireThatSet | undefined | null, E> + (value: T, name: string): SetValidator; + + abstract requireThatMap | undefined | null, K, V> + (value: T, name: string): MapValidator; + + abstract requireThatString + (value: T, name: string): StringValidator; + + abstract requireThatObject + (value: T, name: string): ObjectValidator; + + abstract assertThatNumber + (value: T, name?: string): NumberValidator; + + abstract assertThatBoolean + (value: T, name?: string): BooleanValidator; + + abstract assertThatArray + (value: T, name?: string): ArrayValidator; + + abstract assertThatSet | undefined | null, E> + (value: T, name?: string): SetValidator; + + abstract assertThatMap | undefined | null, K, V> + (value: T, name?: string): MapValidator; + + abstract assertThatString + (value: T, name?: string): StringValidator; + + abstract assertThatObject + (value: T, name?: string): ObjectValidator; + + abstract checkIfNumber + (value: T, name?: string): NumberValidator; + + abstract checkIfBoolean + (value: T, name?: string): BooleanValidator; + + abstract checkIfArray + (value: T, name?: string): ArrayValidator; + + abstract checkIfSet | undefined | null, E> + (value: T, name?: string): SetValidator; + + abstract checkIfMap | undefined | null, K, V> + (value: T, name?: string): MapValidator; + + abstract checkIfString + (value: T, name?: string): StringValidator; + + abstract checkIfObject + (value: T, name?: string): ObjectValidator; } export {JavascriptValidators}; \ No newline at end of file diff --git a/src/MultipleFailuresError.mts b/src/MultipleFailuresError.mts index 1b88ebc..2ed8bfc 100644 --- a/src/MultipleFailuresError.mts +++ b/src/MultipleFailuresError.mts @@ -33,10 +33,7 @@ class MultipleFailuresError extends Error let i = 1; for (const failure of failures) { - result += `${i}. ${failure.getError().name}`; - const message = failure.getMessage(); - if (message !== null) - result += `: ${message}\n`; + result += `${i}. ${failure.getError().name}: ${failure.getMessage()}\n`; ++i; } return result.toString(); diff --git a/src/Type.mts b/src/Type.mts index 4f8a5ab..4e6d4e2 100644 --- a/src/Type.mts +++ b/src/Type.mts @@ -1,6 +1,4 @@ -import { - type ClassConstructor -} from "./internal/internal.mjs"; +import {type ClassConstructor} from "./internal/internal.mjs"; enum TypeCategory { @@ -72,7 +70,7 @@ class Type return Type.ANONYMOUS_FUNCTION; } const className = CLASS_NAME_REGEX.exec(valueAsString); - if (className !== null && className[1] !== undefined) + if (className !== null && className.length >= 2) { // A class const name = className[1]; @@ -80,7 +78,7 @@ class Type return Type.namedClass(name); } const builtInClassName = BUILT_IN_CLASS_NAME_REGEX.exec(valueAsString); - if (builtInClassName !== null && builtInClassName[1] !== undefined) + if (builtInClassName !== null && builtInClassName.length >= 2) { // A built-in class const name = builtInClassName[1].trim(); @@ -89,7 +87,7 @@ class Type } // Anonymous and named functions const functionName = FUNCTION_NAME_REGEX.exec(valueAsString); - if (functionName !== null && functionName[1] !== undefined) + if (functionName !== null && functionName.length >= 2) { // A named function const name = functionName[1].trim(); @@ -107,7 +105,7 @@ class Type /** * Returns the type of a named class. * - * @param name - the name of the class, or `null` to represent "any object". + * @param name - the name of the class, or `null` to represent any class. * @param typeGuard - (optional) for certain types, such as Typescript interfaces, runtime validation is * not possible. In such a case, use a type guard to check if the value satisfies the type condition. * @returns the type @@ -120,7 +118,7 @@ class Type /** * Returns the type of a named function. * - * @param name - (optional) the name of the function. `name` represents "any named function". + * @param name - (optional) the name of the function. `name` represents any named function. * @returns the type */ public static namedFunction(name: string | null): Type @@ -268,7 +266,7 @@ Actual: ${Type.of(category).toString()}`); return this.equals(parent); case TypeCategory.CLASS: { - if (parent === undefined || parent === null) + if (parent === Type.UNDEFINED || parent === Type.NULL) return false; if (parent.name === null) { @@ -276,15 +274,22 @@ Actual: ${Type.of(category).toString()}`); return true; } // There is no way to provide type-casting for a dynamic lookup of an unknown type - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + /* eslint-disable @typescript-eslint/no-explicit-any, + @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */ const parentClass = (globalThis as unknown as any)[parent.name]; + /* eslint-enable @typescript-eslint/no-explicit-any, + @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */ if (this.name == null) { // null represents any class return true; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + /* eslint-disable @typescript-eslint/no-explicit-any, + @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */ const childClass = (globalThis as unknown as any)[this.name]; + /* eslint-enable @typescript-eslint/no-explicit-any, + @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */ + // https://stackoverflow.com/a/14486171/14731 // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access return childClass.prototype instanceof parentClass; diff --git a/src/ValidationFailure.mts b/src/ValidationFailure.mts index 462277e..d761f3a 100644 --- a/src/ValidationFailure.mts +++ b/src/ValidationFailure.mts @@ -33,9 +33,11 @@ interface ValidationFailure function isValidationFailure(value: unknown): value is ValidationFailure { const validationFailure = value as ValidationFailure; + /* eslint-disable @typescript-eslint/no-unnecessary-condition */ return validationFailure.getMessage !== undefined && validationFailure.getType !== undefined && validationFailure.getError !== undefined; + /* eslint-enable @typescript-eslint/no-unnecessary-condition */ } export {type ValidationFailure, isValidationFailure}; \ No newline at end of file diff --git a/src/Validators.mts b/src/Validators.mts index e75ef16..1aa40e1 100644 --- a/src/Validators.mts +++ b/src/Validators.mts @@ -3,10 +3,12 @@ import type { GlobalConfiguration } from "./internal/internal.mjs"; -const typedocWorkaround: null | ValidatorComponent = null; +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ +const typedocWorkaround: null | ValidatorComponent = null; // noinspection PointlessBooleanExpressionJS if (typedocWorkaround !== null) console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); +/* eslint-enable @typescript-eslint/no-unnecessary-condition */ /** * A factory that creates different types of validators. @@ -15,7 +17,7 @@ if (typedocWorkaround !== null) *

                              *
                            • `requireThat()` for method preconditions.
                            • *
                            • `assertThat()` for class invariants, and method postconditions.
                            • - *
                            • `checkIf()` for returning multiple validation failures.
                            • + *
                            • `checkIf()` for returning multiple validation failures.
                            • *
                            * * @typeParam S - the type of the validator factory @@ -71,7 +73,7 @@ interface Validators * the validator context *
                          */ - withContext(value: unknown, name: string): S; + withContext(value: unknown, name: string): this; /** * Removes the contextual information of validators created by this factory. @@ -85,7 +87,7 @@ interface Validators *
                        • is empty
                        • *
                        */ - removeContext(name: string): S; + removeContext(name: string): this; /** * Returns the global configuration shared by all validators. diff --git a/src/index.mts b/src/index.mts index 262c84a..7b3cdc9 100644 --- a/src/index.mts +++ b/src/index.mts @@ -1,7 +1,25 @@ export { - requireThat, - assertThat, - checkIf, + requireThatNumber, + requireThatBoolean, + requireThatArray, + requireThatSet, + requireThatMap, + requireThatString, + requireThatObject, + assertThatNumber, + assertThatBoolean, + assertThatArray, + assertThatSet, + assertThatMap, + assertThatString, + assertThatObject, + checkIfNumber, + checkIfBoolean, + checkIfArray, + checkIfSet, + checkIfMap, + checkIfString, + checkIfObject, updateConfiguration, getContext, withContext, diff --git a/src/internal/Configuration.mts b/src/internal/Configuration.mts index 575fac4..b538e16 100644 --- a/src/internal/Configuration.mts +++ b/src/internal/Configuration.mts @@ -9,9 +9,11 @@ import { } from "./internal.mjs"; const typedocWorkaround: null | ValidationFailures = null; +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ // noinspection PointlessBooleanExpressionJS if (typedocWorkaround !== null) console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); +/* eslint-enable @typescript-eslint/no-unnecessary-condition */ /** * Determines the behavior of a validator. @@ -109,8 +111,6 @@ class Configuration /** * Returns a function that transforms validation errors before they are thrown or recorded. * - * If the function returns `undefined` or `null`, it’s treated as if it returned the input error. - * * @returns a function that transforms the validation error */ public errorTransformer() diff --git a/src/internal/ConfigurationUpdater.mts b/src/internal/ConfigurationUpdater.mts index cd98042..d59e39f 100644 --- a/src/internal/ConfigurationUpdater.mts +++ b/src/internal/ConfigurationUpdater.mts @@ -9,9 +9,11 @@ import { } from "./internal.mjs"; const typedocWorkaround: null | ValidationFailures = null; +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ // noinspection PointlessBooleanExpressionJS if (typedocWorkaround !== null) console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); +/* eslint-enable @typescript-eslint/no-unnecessary-condition */ /** * Updates the configuration used by new validators. diff --git a/src/internal/StringMappers.mts b/src/internal/StringMappers.mts index 26ec9b2..5c1f11c 100644 --- a/src/internal/StringMappers.mts +++ b/src/internal/StringMappers.mts @@ -1,6 +1,5 @@ import { Type, - internalValueToString, type StringMapper, getMapper, quoteString @@ -59,8 +58,11 @@ class StringMappers { if (element !== null && Array.isArray(element)) { - if (seen.add(element)) + if (!seen.has(element)) + { + seen.add(element); elements.push(this.valueToString(element, seen)); + } else elements.push("..."); } @@ -184,10 +186,7 @@ class StringMappers public toString(value?: unknown) { const mapper = getMapper(value, this.typeToMapper); - if (mapper === undefined) - return internalValueToString(this.typeToMapper); - else - return mapper(value); + return mapper(value); } } diff --git a/src/internal/internal.mts b/src/internal/internal.mts index 2f89a59..cdc7502 100644 --- a/src/internal/internal.mts +++ b/src/internal/internal.mts @@ -83,7 +83,10 @@ import { TerminalEncoding, sortByDecreasingRank } from "../TerminalEncoding.mjs"; -import {type ValidationFailure, isValidationFailure} from "../ValidationFailure.mjs"; +import { + type ValidationFailure, + isValidationFailure +} from "../ValidationFailure.mjs"; import {ValidationFailures} from "../ValidationFailures.mjs"; import {ValidationFailureImpl} from "./validator/ValidationFailureImpl.mjs"; import {MultipleFailuresError} from "../MultipleFailuresError.mjs"; @@ -92,9 +95,27 @@ import type {JavascriptRequireThat} from "../JavascriptRequireThat.mjs"; import type {JavascriptAssertThat} from "../JavascriptAssertThat.mjs"; import type {JavascriptCheckIf} from "../JavascriptCheckIf.mjs"; import { - requireThat, - assertThat, - checkIf, + requireThatNumber, + requireThatBoolean, + requireThatArray, + requireThatSet, + requireThatMap, + requireThatString, + requireThatObject, + assertThatNumber, + assertThatBoolean, + assertThatArray, + assertThatSet, + assertThatMap, + assertThatString, + assertThatObject, + checkIfNumber, + checkIfBoolean, + checkIfArray, + checkIfSet, + checkIfMap, + checkIfString, + checkIfObject, updateConfiguration, getContext, withContext, @@ -209,7 +230,7 @@ import type {MessageSection} from "./message/section/MessageSection.mjs"; import {ContextSection} from "./message/section/ContextSection.mjs"; import {StringSection} from "./message/section/StringSection.mjs"; import {ObjectAndSize} from "./util/ObjectAndSize.mjs"; -import { ValidationTarget} from "./util/ValidationTarget.mjs"; +import {ValidationTarget} from "./util/ValidationTarget.mjs"; import {Difference} from "./util/Difference.mjs"; import { collectionContainsSize, @@ -284,9 +305,27 @@ export AbstractValidator, ObjectValidatorImpl, Pluralizer, - requireThat, - assertThat, - checkIf, + requireThatNumber, + requireThatBoolean, + requireThatArray, + requireThatSet, + requireThatMap, + requireThatString, + requireThatObject, + assertThatNumber, + assertThatBoolean, + assertThatArray, + assertThatSet, + assertThatMap, + assertThatString, + assertThatObject, + checkIfNumber, + checkIfBoolean, + checkIfArray, + checkIfSet, + checkIfMap, + checkIfString, + checkIfObject, updateConfiguration, getContext, withContext, diff --git a/src/internal/message/BooleanMessages.mts b/src/internal/message/BooleanMessages.mts index 39abcc6..b669bb9 100644 --- a/src/internal/message/BooleanMessages.mts +++ b/src/internal/message/BooleanMessages.mts @@ -11,7 +11,7 @@ import { * @param validator - the validator * @returns a message for the validation failure */ -function isTrueFailed(validator: AbstractValidator) +function isTrueFailed(validator: AbstractValidator) { return messagesConstraint(validator, "must be true"); } @@ -20,7 +20,7 @@ function isTrueFailed(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function isFalseFailed(validator: AbstractValidator) +function isFalseFailed(validator: AbstractValidator) { return messagesConstraint(validator, "must be false"); } diff --git a/src/internal/message/ClassMessages.mts b/src/internal/message/ClassMessages.mts index 9bee246..c622e52 100644 --- a/src/internal/message/ClassMessages.mts +++ b/src/internal/message/ClassMessages.mts @@ -13,7 +13,7 @@ import { * @param validator - the validator * @returns a message for the validation failure */ - function classIsPrimitive(validator: AbstractValidator) + function classIsPrimitive(validator: AbstractValidator) { return messagesConstraint(validator, "must be a primitive type"); } @@ -23,7 +23,7 @@ import { * @param validator - the validator * @returns a message for the validation failure */ -function classIsSupertypeOf(validator: AbstractValidator, subtype: ClassConstructor) +function classIsSupertypeOf(validator: AbstractValidator, subtype: ClassConstructor) { return messagesConstraint(validator, " must be a supertype of " + subtype.toString()); } @@ -33,7 +33,7 @@ function classIsSupertypeOf(validator: AbstractValidator, subt * @param validator - the validator * @returns a message for the validation failure */ -function classIsSubtypeOf(validator: AbstractValidator, supertype: ClassConstructor) +function classIsSubtypeOf(validator: AbstractValidator, supertype: ClassConstructor) { return messagesConstraint(validator, "must be a subtype of " + supertype.toString()); } diff --git a/src/internal/message/CollectionMessages.mts b/src/internal/message/CollectionMessages.mts index f71e267..d54a4d3 100644 --- a/src/internal/message/CollectionMessages.mts +++ b/src/internal/message/CollectionMessages.mts @@ -22,7 +22,7 @@ import { * @param pluralizer - the type of items in the collection * @returns a message for the validation failure */ -function collectionContainsSize(validator: AbstractValidator, actualSizeName: string, +function collectionContainsSize(validator: AbstractValidator, actualSizeName: string, actualSize: number | null, relationship: string, expectedSizeName: string | null, expectedSize: number, pluralizer: Pluralizer) { @@ -55,7 +55,7 @@ function collectionContainsSize(validator: AbstractValidator, * @param maximumInclusive - `true` if maximum size is inclusive * @param pluralizer - the type of items in the collection */ -function collectionSizeIsBetween(validator: AbstractValidator, actualSizeName: string, +function collectionSizeIsBetween(validator: AbstractValidator, actualSizeName: string, actualSize: number | null, minimum: number, minimumInclusive: boolean, maximum: number, maximumInclusive: boolean, pluralizer: Pluralizer) { @@ -114,7 +114,7 @@ bounds: ${bounds}`); * @param validator - the validator * @returns a message for the validation failure */ -function collectionIsEmpty(validator: AbstractValidator) +function collectionIsEmpty(validator: AbstractValidator) { return objectIsEmpty(validator); } @@ -123,7 +123,7 @@ function collectionIsEmpty(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function collectionIsNotEmpty(validator: AbstractValidator) +function collectionIsNotEmpty(validator: AbstractValidator) { return objectIsNotEmpty(validator); } @@ -135,7 +135,7 @@ function collectionIsNotEmpty(validator: AbstractValidator) * @param expected - the expected value * @returns a message for the validation failure */ -function collectionContains(validator: AbstractValidator, expectedName: string | null, +function collectionContains(validator: AbstractValidator, expectedName: string | null, expected: unknown) { // "actual" must contain the same value as "expected". @@ -152,7 +152,7 @@ function collectionContains(validator: AbstractValidator, expe * @param other - the other value * @returns a message for the validation failure */ -function collectionContainsImpl(validator: AbstractValidator, relationship: string, +function collectionContainsImpl(validator: AbstractValidator, relationship: string, otherName: string | null, other: unknown) { // "actual" must contain the same value as "expected". @@ -162,7 +162,7 @@ function collectionContainsImpl(validator: AbstractValidator, const messageBuilder = new MessageBuilder(validator, `${MessageBuilder.quoteName(validator.getName())} ${relationship} ${otherNameOrValue}.`); - if (otherName != null) + if (otherName !== null) messageBuilder.withContext(other, otherName); return messageBuilder; } @@ -173,8 +173,8 @@ function collectionContainsImpl(validator: AbstractValidator, * @param unwanted - the unwanted value * @returns a message for the validation failure */ -function collectionDoesNotContain(validator: AbstractValidator, - unwantedName: string | null, unwanted: unknown) +function collectionDoesNotContain(validator: AbstractValidator, unwantedName: string | null, + unwanted: unknown) { // "actual" may not contain the same value as "unwanted". // actual : 5 @@ -189,8 +189,8 @@ function collectionDoesNotContain(validator: AbstractValidator * @param pluralizer - the type of items in the collections * @returns a message for the validation failure */ -function collectionContainsAny(validator: AbstractValidator, - expectedName: string | null, expected: unknown, pluralizer: Pluralizer) +function collectionContainsAny(validator: AbstractValidator, expectedName: string | null, + expected: unknown, pluralizer: Pluralizer) { // "actual" must contain any of the elements present in "expected". // actual : [1, 2, 3] @@ -217,7 +217,7 @@ present in ${expectedNameOrValue}.`); * @param pluralizer - the type of items in the collections * @returns a message for the validation failure */ -function collectionDoesNotContainAny(validator: AbstractValidator, +function collectionDoesNotContainAny(validator: AbstractValidator, difference: Difference | null, unwantedName: string | null, unwanted: unknown, pluralizer: Pluralizer) { @@ -233,9 +233,9 @@ function collectionDoesNotContainAny(validator: AbstractValidator messageBuilder.withContext(v, name)); - if (unwantedName != null) + if (unwantedName !== null) messageBuilder.withContext(unwanted, unwantedName); - if (difference != null) + if (difference !== null) messageBuilder.withContext(difference.common, "elementsToRemove"); return messageBuilder; } @@ -249,9 +249,8 @@ present in ${unwantedNameOrValue}.`); * @param pluralizer - the type of items in the value * @returns a message for the validation failure */ -function collectionContainsExactly(validator: AbstractValidator, - difference: Difference | null, expectedName: string | null, - expected: unknown, pluralizer: Pluralizer) +function collectionContainsExactly(validator: AbstractValidator, difference: Difference | null, + expectedName: string | null, expected: unknown, pluralizer: Pluralizer) { // "actual" must consist of the elements [2, 3, 4], regardless of their order. // @@ -290,9 +289,8 @@ function collectionContainsExactly(validator: AbstractValidator, - unwantedName: string | null, unwanted: unknown, - pluralizer: Pluralizer) +function collectionDoesNotContainExactly(validator: AbstractValidator, unwantedName: string | null, + unwanted: unknown, pluralizer: Pluralizer) { // "actual" may not consist of the elements [2, 3, 4], regardless of their order. // @@ -322,9 +320,8 @@ function collectionDoesNotContainExactly(validator: AbstractValidator(validator: AbstractValidator, - difference: Difference | null, expectedName: string | null, - expected: unknown, pluralizer: Pluralizer) +function collectionContainsAll(validator: AbstractValidator, difference: Difference | null, + expectedName: string | null, expected: unknown, pluralizer: Pluralizer) { // "actual" must contain all the elements present in "expected". // actual : [1, 2, 3] @@ -352,9 +349,8 @@ present in ${expectedNameOrValue}.`); * @param pluralizer - the type of items in the value * @returns a message for the validation failure */ -function collectionDoesNotContainAll(validator: AbstractValidator, - unwantedName: string | null, unwanted: unknown, - pluralizer: Pluralizer) +function collectionDoesNotContainAll(validator: AbstractValidator, unwantedName: string | null, + unwanted: unknown, pluralizer: Pluralizer) { // "actual" may not contain some, but not all, the elements present in "unwanted". // actual : [1, 2, 3] @@ -379,7 +375,7 @@ ${pluralizer.nameOf(2)} present in ${unwantedNameOrValue}.`); * @param pluralizer - the type of items in the value * @returns a message for the validation failure */ -function collectionDoesNotContainDuplicates(validator: AbstractValidator, +function collectionDoesNotContainDuplicates(validator: AbstractValidator, duplicates: Set | null, pluralizer: Pluralizer) { const name = validator.getName(); @@ -396,8 +392,7 @@ function collectionDoesNotContainDuplicates(validator: AbstractValidator, - sorted: unknown[] | null) +function collectionIsSorted(validator: AbstractValidator, sorted: unknown[] | null) { const name = validator.getName(); const messageBuilder = new MessageBuilder(validator, @@ -412,7 +407,7 @@ function collectionIsSorted(validator: AbstractValidator, * @param validator - the validator * @returns a message for the validation failure */ -function collectionContainsSameNullity(validator: AbstractValidator) +function collectionContainsSameNullity(validator: AbstractValidator) { const name = validator.getName(); const messageBuilder = new MessageBuilder(validator, diff --git a/src/internal/message/ComparableMessages.mts b/src/internal/message/ComparableMessages.mts index 637608d..835581d 100644 --- a/src/internal/message/ComparableMessages.mts +++ b/src/internal/message/ComparableMessages.mts @@ -10,7 +10,7 @@ import { * @param expected - the expected value * @returns a message for the validation failure */ -function comparableIsEqualTo(validator: AbstractValidator, expectedName: string | null, +function comparableIsEqualTo(validator: AbstractValidator, expectedName: string | null, expected: unknown) { return comparableCompareValues(validator, "must be equal to", expectedName, expected); @@ -22,7 +22,7 @@ function comparableIsEqualTo(validator: AbstractValidator, exp * @param maximumExclusive - the exclusive upper bound * @returns a message for the validation failure */ -function comparableIsLessThan(validator: AbstractValidator, limitName: string | null, +function comparableIsLessThan(validator: AbstractValidator, limitName: string | null, maximumExclusive: unknown) { return comparableCompareValues(validator, "must be less than", limitName, maximumExclusive); @@ -34,8 +34,8 @@ function comparableIsLessThan(validator: AbstractValidator, li * @param maximumInclusive - the inclusive upper bound * @returns a message for the validation failure */ -function comparableIsLessThanOrEqualTo(validator: AbstractValidator, - limitName: string | null, maximumInclusive: unknown) +function comparableIsLessThanOrEqualTo(validator: AbstractValidator, limitName: string | null, + maximumInclusive: unknown) { return comparableCompareValues(validator, "must be less than or equal to", limitName, maximumInclusive); } @@ -46,8 +46,8 @@ function comparableIsLessThanOrEqualTo(validator: AbstractValidator, - limitName: string | null, minimumInclusive: unknown) +function comparableIsGreaterThanOrEqualTo(validator: AbstractValidator, limitName: string | null, + minimumInclusive: unknown) { return comparableCompareValues(validator, "must be greater than or equal to", limitName, minimumInclusive); } @@ -58,8 +58,8 @@ function comparableIsGreaterThanOrEqualTo(validator: AbstractValidator, - limitName: string | null, minimumExclusive: unknown) +function comparableIsGreaterThan(validator: AbstractValidator, limitName: string | null, + minimumExclusive: unknown) { return comparableCompareValues(validator, "must be greater than", limitName, minimumExclusive); } @@ -72,7 +72,7 @@ function comparableIsGreaterThan(validator: AbstractValidator, * @param expected - the expected value * @returns a message for the validation failure */ -function comparableCompareValues(validator: AbstractValidator, relationship: string, +function comparableCompareValues(validator: AbstractValidator, relationship: string, expectedName: string | null, expected: unknown) { const actualName = validator.getName(); @@ -86,9 +86,9 @@ function comparableCompareValues(validator: AbstractValidator, `${MessageBuilder.quoteName(actualName)} ${relationship} ${expectedNameOrValue}.`); const invalidToNull = validator.getValueOrDefault(null); - if (invalidToNull != null) + if (invalidToNull !== null) messageBuilder.withContext(invalidToNull, actualName); - if (expectedName != null) + if (expectedName !== null) messageBuilder.withContext(expected, expectedName); return messageBuilder; } @@ -101,14 +101,14 @@ function comparableCompareValues(validator: AbstractValidator, * @param maximumInclusive - `true` if the upper bound of the range is inclusive * @returns a message for the validation failure */ -function isBetweenFailed(validator: AbstractValidator, minimum: unknown, - minimumInclusive: boolean, maximum: unknown, maximumInclusive: boolean) +function isBetweenFailed(validator: AbstractValidator, minimum: unknown, minimumInclusive: boolean, + maximum: unknown, maximumInclusive: boolean) { const name = validator.getName(); const builder = new MessageBuilder(validator, `${MessageBuilder.quoteName(name)} is out of bounds.`); const value = validator.getValueOrDefault(null); - if (value != null) + if (value !== null) builder.withContext(value, name); const bounds = comparableGetBounds(minimum, minimumInclusive, maximum, maximumInclusive, diff --git a/src/internal/message/NumberMessages.mts b/src/internal/message/NumberMessages.mts index 7f8a4e4..631359e 100644 --- a/src/internal/message/NumberMessages.mts +++ b/src/internal/message/NumberMessages.mts @@ -8,7 +8,7 @@ import { * @param validator - the validator * @returns a message for the validation failure */ -function numberIsNegative(validator: AbstractValidator) +function numberIsNegative(validator: AbstractValidator) { return messagesConstraint(validator, "must be negative"); } @@ -17,7 +17,7 @@ function numberIsNegative(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function numberIsNotNegative(validator: AbstractValidator) +function numberIsNotNegative(validator: AbstractValidator) { return messagesConstraint(validator, "may not be negative"); } @@ -26,7 +26,7 @@ function numberIsNotNegative(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function numberIsZero(validator: AbstractValidator) +function numberIsZero(validator: AbstractValidator) { return messagesConstraint(validator, "must be zero"); } @@ -35,7 +35,7 @@ function numberIsZero(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function numberIsNotZero(validator: AbstractValidator) +function numberIsNotZero(validator: AbstractValidator) { return messagesConstraint(validator, "may not be zero"); } @@ -44,7 +44,7 @@ function numberIsNotZero(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function numberIsPositive(validator: AbstractValidator) +function numberIsPositive(validator: AbstractValidator) { return messagesConstraint(validator, "must be positive"); } @@ -53,7 +53,7 @@ function numberIsPositive(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function numberIsNotPositive(validator: AbstractValidator) +function numberIsNotPositive(validator: AbstractValidator) { return messagesConstraint(validator, "may not be positive"); } @@ -64,8 +64,7 @@ function numberIsNotPositive(validator: AbstractValidator) * @param factor - the value being multiplied by * @returns a message for the validation failure */ -function numberIsMultipleOf(validator: AbstractValidator, - factorName: string | null, factor: number) +function numberIsMultipleOf(validator: AbstractValidator, factorName: string | null, factor: number) { return comparableCompareValues(validator, "must be a multiple of", factorName, factor); } @@ -76,8 +75,8 @@ function numberIsMultipleOf(validator: AbstractValidator, * @param factor - the value being multiplied by * @returns a message for the validation failure */ -function numberIsNotMultipleOf(validator: AbstractValidator, - factorName: string | null, factor: number) +function numberIsNotMultipleOf(validator: AbstractValidator, factorName: string | null, + factor: number) { return comparableCompareValues(validator, "may not be a multiple of", factorName, factor); } @@ -86,7 +85,7 @@ function numberIsNotMultipleOf(validator: AbstractValidator, * @param validator - the validator * @returns a message for the validation failure */ -function numberIsWholeNumber(validator: AbstractValidator) +function numberIsWholeNumber(validator: AbstractValidator) { return messagesConstraint(validator, "must be a whole number"); } @@ -95,7 +94,7 @@ function numberIsWholeNumber(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function numberIsNotWholeNumber(validator: AbstractValidator) +function numberIsNotWholeNumber(validator: AbstractValidator) { return messagesConstraint(validator, "may not be a whole number"); } @@ -104,7 +103,7 @@ function numberIsNotWholeNumber(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function numberIsNumber(validator: AbstractValidator) +function numberIsNumber(validator: AbstractValidator) { return messagesConstraint(validator, "must be a well-defined number"); } @@ -113,7 +112,7 @@ function numberIsNumber(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function numberIsNotNumber(validator: AbstractValidator) +function numberIsNotNumber(validator: AbstractValidator) { return messagesConstraint(validator, "may not be a well-defined number"); } @@ -122,7 +121,7 @@ function numberIsNotNumber(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function numberIsFinite(validator: AbstractValidator) +function numberIsFinite(validator: AbstractValidator) { return messagesConstraint(validator, "must be a finite number"); } @@ -131,7 +130,7 @@ function numberIsFinite(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function numberIsInfinite(validator: AbstractValidator) +function numberIsInfinite(validator: AbstractValidator) { return messagesConstraint(validator, "must be an infinite number"); } diff --git a/src/internal/message/ObjectMessages.mts b/src/internal/message/ObjectMessages.mts index 5d70823..ee88697 100644 --- a/src/internal/message/ObjectMessages.mts +++ b/src/internal/message/ObjectMessages.mts @@ -7,7 +7,7 @@ import { * @param validator - the validator * @returns a message for the validation failure */ -function objectIsEmpty(validator: AbstractValidator) +function objectIsEmpty(validator: AbstractValidator) { const name = validator.getName(); const messageBuilder = new MessageBuilder(validator, @@ -22,7 +22,7 @@ function objectIsEmpty(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function objectIsNotEmpty(validator: AbstractValidator) +function objectIsNotEmpty(validator: AbstractValidator) { const name = validator.getName(); return new MessageBuilder(validator, MessageBuilder.quoteName(name) + " may not be empty."); diff --git a/src/internal/message/StringMessages.mts b/src/internal/message/StringMessages.mts index b17a129..a962b02 100644 --- a/src/internal/message/StringMessages.mts +++ b/src/internal/message/StringMessages.mts @@ -7,7 +7,7 @@ import { * @param validator - the validator * @returns a message for the validation failure */ -function stringIsBlank(validator: AbstractValidator) +function stringIsBlank(validator: AbstractValidator) { const name = validator.getName(); const messageBuilder = new MessageBuilder(validator, @@ -22,7 +22,7 @@ function stringIsBlank(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function stringIsNotBlank(validator: AbstractValidator) +function stringIsNotBlank(validator: AbstractValidator) { const name = validator.getName(); const messageBuilder = new MessageBuilder(validator, @@ -37,7 +37,7 @@ function stringIsNotBlank(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function stringIsTrimmed(validator: AbstractValidator) +function stringIsTrimmed(validator: AbstractValidator) { const name = validator.getName(); const messageBuilder = new MessageBuilder(validator, @@ -52,7 +52,7 @@ function stringIsTrimmed(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function stringIsStripped(validator: AbstractValidator) +function stringIsStripped(validator: AbstractValidator) { const name = validator.getName(); const messageBuilder = new MessageBuilder(validator, @@ -68,7 +68,7 @@ function stringIsStripped(validator: AbstractValidator) * @param prefix - the value that the string must start with * @returns a message for the validation failure */ -function stringStartsWith(validator: AbstractValidator, prefix: string) +function stringStartsWith(validator: AbstractValidator, prefix: string) { const name = validator.getName(); const stringMappers = validator.configuration().stringMappers(); @@ -85,7 +85,7 @@ function stringStartsWith(validator: AbstractValidator, prefix: * @param prefix - the value that the string must start with * @returns a message for the validation failure */ -function stringDoesNotStartWith(validator: AbstractValidator, prefix: string) +function stringDoesNotStartWith(validator: AbstractValidator, prefix: string) { const name = validator.getName(); const stringMappers = validator.configuration().stringMappers(); @@ -102,7 +102,7 @@ function stringDoesNotStartWith(validator: AbstractValidator, p * @param suffix - the value that the string must end with * @returns a message for the validation failure */ -function stringEndsWith(validator: AbstractValidator, suffix: string) +function stringEndsWith(validator: AbstractValidator, suffix: string) { const name = validator.getName(); const stringMappers = validator.configuration().stringMappers(); @@ -119,7 +119,7 @@ function stringEndsWith(validator: AbstractValidator, suffix: s * @param suffix - the value that the string must end with * @returns a message for the validation failure */ -function stringDoesNotEndWith(validator: AbstractValidator, suffix: string) +function stringDoesNotEndWith(validator: AbstractValidator, suffix: string) { const name = validator.getName(); const stringMappers = validator.configuration().stringMappers(); @@ -136,7 +136,7 @@ function stringDoesNotEndWith(validator: AbstractValidator, suf * @param expected - the expected value * @returns a message for the validation failure */ -function stringContains(validator: AbstractValidator, expected: string) +function stringContains(validator: AbstractValidator, expected: string) { const name = validator.getName(); const stringMappers = validator.configuration().stringMappers(); @@ -153,7 +153,7 @@ function stringContains(validator: AbstractValidator, expected: * @param unwanted - the unwanted value * @returns a message for the validation failure */ -function stringDoesNotContain(validator: AbstractValidator, unwanted: string) +function stringDoesNotContain(validator: AbstractValidator, unwanted: string) { const name = validator.getName(); const stringMappers = validator.configuration().stringMappers(); @@ -169,7 +169,7 @@ function stringDoesNotContain(validator: AbstractValidator, unw * @param validator - the validator * @returns a message for the validation failure */ -function stringDoesNotContainWhitespace(validator: AbstractValidator) +function stringDoesNotContainWhitespace(validator: AbstractValidator) { const name = validator.getName(); const messageBuilder = new MessageBuilder(validator, @@ -185,7 +185,7 @@ function stringDoesNotContainWhitespace(validator: AbstractValidator, regex: string) +function stringMatches(validator: AbstractValidator, regex: string) { const name = validator.getName(); const stringMappers = validator.configuration().stringMappers(); diff --git a/src/internal/message/ValidatorMessages.mts b/src/internal/message/ValidatorMessages.mts index 54147c6..654b85e 100644 --- a/src/internal/message/ValidatorMessages.mts +++ b/src/internal/message/ValidatorMessages.mts @@ -14,7 +14,7 @@ const MINIMUM_LENGTH_FOR_DIFF = 10; * @param validator - the validator * @returns a message for the validation failure */ -function messagesIsUndefined(validator: AbstractValidator) +function messagesIsUndefined(validator: AbstractValidator) { const messageBuilder = new MessageBuilder(validator, MessageBuilder.quoteName(validator.getName()) + " must be undefined."); @@ -25,19 +25,19 @@ function messagesIsUndefined(validator: AbstractValidator) /** * @param validator - the validator - * @param name - the name of the value * @returns a message for the validation failure */ -function messagesIsNotUndefined(validator: AbstractValidator, name: string) +function messagesIsNotUndefined(validator: AbstractValidator) { - return new MessageBuilder(validator, MessageBuilder.quoteName(name) + " may not be undefined."); + return new MessageBuilder(validator, + MessageBuilder.quoteName(validator.getName()) + " may not be undefined."); } /** * @param validator - the validator * @returns a message for the validation failure */ -function messagesIsNull(validator: AbstractValidator) +function messagesIsNull(validator: AbstractValidator) { const name = validator.getName(); const messageBuilder = new MessageBuilder(validator, @@ -52,7 +52,7 @@ function messagesIsNull(validator: AbstractValidator) * @param validator - the validator * @returns a message for the validation failure */ -function messagesIsNotNull(validator: AbstractValidator) +function messagesIsNotNull(validator: AbstractValidator) { return new MessageBuilder(validator, MessageBuilder.quoteName(validator.getName()) + " may not be null."); } @@ -63,7 +63,7 @@ function messagesIsNotNull(validator: AbstractValidator) * @param constraint - the constraint that the value must adhere to (e.g. "must be negative") * @returns a message for the validation failure */ -function messagesConstraint(validator: AbstractValidator, constraint: string) +function messagesConstraint(validator: AbstractValidator, constraint: string) { // "actual" must be negative. // actual: 5 @@ -82,8 +82,8 @@ function messagesConstraint(validator: AbstractValidator, constra * @param expected - the expected value * @returns a message for the validation failure */ -function messagesIsEqualTo(validator: AbstractValidator, expectedName: string | null, - expected: unknown) +function messagesIsEqualTo(validator: AbstractValidator, expectedName: string | null, + expected: unknown) { const stringMappers = validator.configuration().stringMappers(); const name = validator.getName(); @@ -101,7 +101,7 @@ function messagesIsEqualTo(validator: AbstractValidator, expec `${MessageBuilder.quoteName(name)} must be equal to ${expectedNameOrValue}.`); validator.value.ifValid(v => messageBuilder.withContext(v, name)); - if (expectedName != null) + if (expectedName !== null) messageBuilder.withContext(expected, expectedName); return messageBuilder; } @@ -135,7 +135,7 @@ function unnecessaryDiff(value: unknown, stringMappers: StringMappers) * @param expected - the expected type * @returns a message for the validation failure */ -function messagesIsInstanceOf(validator: AbstractValidator, expected: Type) +function messagesIsInstanceOf(validator: AbstractValidator, expected: Type) { const name = validator.getName(); const messageBuilder = new MessageBuilder(validator, @@ -155,7 +155,7 @@ function messagesIsInstanceOf(validator: AbstractValidator, ex * @param unwanted - the unwanted type * @returns a message for the validation failure */ -function messagesIsNotInstanceOf(validator: AbstractValidator, unwanted: Type) +function messagesIsNotInstanceOf(validator: AbstractValidator, unwanted: Type) { const name = validator.getName(); const messageBuilder = new MessageBuilder(validator, @@ -177,8 +177,8 @@ function messagesIsNotInstanceOf(validator: AbstractValidator, * @param unwanted - the unwanted element * @returns a message for the validation failure */ -function messagesIsNotEqualTo(validator: AbstractValidator, - unwantedName: string | null, unwanted: unknown) +function messagesIsNotEqualTo(validator: AbstractValidator, unwantedName: string | null, + unwanted: unknown) { // "actual" may not be equal to "expected". // actual : 123 @@ -188,7 +188,7 @@ function messagesIsNotEqualTo(validator: AbstractValidator, const messageBuilder = new MessageBuilder(validator, `${MessageBuilder.quoteName(name)} may not be equal to ${unwantedNameOrValue}.`); validator.value.ifValid(v => messageBuilder.withContext(v, name)); - if (unwantedName != null) + if (unwantedName !== null) messageBuilder.withContext(unwanted, unwantedName); return messageBuilder; } diff --git a/src/internal/message/diff/ContextGenerator.mts b/src/internal/message/diff/ContextGenerator.mts index 3ac6677..d5fd818 100644 --- a/src/internal/message/diff/ContextGenerator.mts +++ b/src/internal/message/diff/ContextGenerator.mts @@ -91,7 +91,7 @@ expectedName: ${expectedName}`); * @param value - the object representation of the actual value * @returns this */ - public actualValue(value: unknown): ContextGenerator + public actualValue(value: unknown): this { this._actualValue = ValidationTarget.valid(value); return this; @@ -103,7 +103,7 @@ expectedName: ${expectedName}`); * @param value - the object representation of the expected value * @returns this */ - public expectedValue(value: unknown): ContextGenerator + public expectedValue(value: unknown): this { this._expectedValue = ValidationTarget.valid(value); return this; @@ -116,7 +116,7 @@ expectedName: ${expectedName}`); * values * @returns this */ - public allowDiff(allowDiff: boolean): ContextGenerator + public allowDiff(allowDiff: boolean): this { this._allowDiff = allowDiff; return this; diff --git a/src/internal/message/diff/DiffGenerator.mts b/src/internal/message/diff/DiffGenerator.mts index d01e023..a986abd 100644 --- a/src/internal/message/diff/DiffGenerator.mts +++ b/src/internal/message/diff/DiffGenerator.mts @@ -360,9 +360,8 @@ class SimplifyDeltas const result = lastIndexOf(delta.value, SimplifyDeltas.WORD_DELIMITER); if (result === null) { - throw new Error("Expecting result to be equal to indexOfNextWordInEndDelta (" + - this.startOfNextWord + ") or later.\n" + - "delta.value: " + delta.value); + throw new Error(`Expecting result to be equal to indexOfNextWordInEndDelta (${this.startOfNextWord}) or later. +delta.value: ${delta.value}`); } this.startOfWord = result.end; } diff --git a/src/internal/message/section/MessageBuilder.mts b/src/internal/message/section/MessageBuilder.mts index a921248..2423734 100644 --- a/src/internal/message/section/MessageBuilder.mts +++ b/src/internal/message/section/MessageBuilder.mts @@ -25,7 +25,7 @@ Legend [index] : Refers to the index of a collection element @line-number: Refers to the line number of a multiline string `; - private readonly validator: AbstractValidator; + private readonly validator: AbstractValidator; private readonly message: string; private readonly failureContext = new Map(); /** @@ -42,7 +42,7 @@ Legend *
                      • `message` is blank or does not end with a dot
                      • *
                      */ - public constructor(validator: AbstractValidator, message: string) + public constructor(validator: AbstractValidator, message: string) { assertThatValueIsNotNull(validator, "validator"); if (!message.endsWith(".")) @@ -164,8 +164,6 @@ Legend private addErrorMessageToContext(context: MessageSection[]) { - if (this.message === null) - return; let updatedMessage; if (context.length === 0 && !this.message.includes("\n")) { diff --git a/src/internal/scope/ApplicationScope.mts b/src/internal/scope/ApplicationScope.mts index 498c6f0..3f319dc 100644 --- a/src/internal/scope/ApplicationScope.mts +++ b/src/internal/scope/ApplicationScope.mts @@ -24,6 +24,7 @@ interface ApplicationScope extends ProcessScope */ function isApplicationScope(value: unknown): value is ApplicationScope { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition return (value as ApplicationScope).getGlobalConfiguration !== undefined; } diff --git a/src/internal/scope/MainGlobalConfiguration.mts b/src/internal/scope/MainGlobalConfiguration.mts index 6bd2efb..7a6a4c3 100644 --- a/src/internal/scope/MainGlobalConfiguration.mts +++ b/src/internal/scope/MainGlobalConfiguration.mts @@ -38,8 +38,6 @@ class MainGlobalConfiguration implements GlobalConfiguration { if (encoding === undefined) return this.terminal.getEncoding(); - if (encoding === null) - throw new TypeError("encoding may not be null"); this.terminal.setEncoding(encoding); return this; } diff --git a/src/internal/util/Strings.mts b/src/internal/util/Strings.mts index b14c375..a568ecf 100644 --- a/src/internal/util/Strings.mts +++ b/src/internal/util/Strings.mts @@ -73,6 +73,7 @@ function lastIndexOf(source: string, target: RegExp): SearchResult | null const matcher = new RegExp(target.source, flags); let match; let searchResult: SearchResult | null = null; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition while (true) { match = matcher.exec(source); @@ -94,7 +95,7 @@ function containsOnly(source: string, target: string) return source.length === 0 || lastConsecutiveIndexOf(source, target) === 0; } -const builtInMapper = (v: unknown) => `${internalValueToString(v)}`; +const builtInMapper = (v: unknown) => internalValueToString(v); /** * Returns a StringMapper for a value or the closest ancestor that has an associated mapper. diff --git a/src/internal/util/ValidationTarget.mts b/src/internal/util/ValidationTarget.mts index 917e811..50dd752 100644 --- a/src/internal/util/ValidationTarget.mts +++ b/src/internal/util/ValidationTarget.mts @@ -1,4 +1,7 @@ -import {Type} from "../internal.mjs"; +import { + Type, + type NonUndefinable +} from "../internal.mjs"; /** * Represents a value that is being validated. @@ -117,12 +120,13 @@ class ValidationTarget * action is taken. * * @returns an invalid value if the original value was `undefined` or `null`; otherwise, returns `this` + * with `T` excluding `undefined` and `null` */ - public undefinedOrNullToInvalid(): ValidationTarget + public undefinedOrNullToInvalid(): ValidationTarget>> { if (this.valid && (this.value === undefined || this.value === null)) return ValidationTarget.invalid(); - return this; + return this as ValidationTarget>>; } /** @@ -133,12 +137,12 @@ class ValidationTarget * @returns the value * @throws U if the value is invalid */ - public orThrow(errorSupplier: () => U): T + public orThrow(errorSupplier: () => Error): T { if (this.valid) return this.value; // WORKAROUND: https://github.com/typescript-eslint/typescript-eslint/issues/9882 - throw errorSupplier() as Error; + throw errorSupplier(); } /** @@ -154,7 +158,17 @@ class ValidationTarget /** * Checks if the value is null. * - * @returns `true` if the value is null; `false` otherwise + * @returns `true` if the value is `undefined`; `false` otherwise + */ + public isUndefined() + { + return this.valid && this.value === undefined; + } + + /** + * Checks if the value is null. + * + * @returns `true` if the value is `null`; `false` otherwise */ public isNull() { @@ -165,12 +179,12 @@ class ValidationTarget * Evaluates a condition on the value. * * @param condition - the condition to evaluate - * @returns `true` if the value is invalid or if the `condition` returns `false`; + * @returns `true` if the value is invalid, `undefined`, `null` or if the `condition` returns `false`; * otherwise, returns `false` */ - public validationFailed(condition: (value: T) => boolean) + public validationFailed(condition: (value: NonUndefinable>) => boolean) { - return !this.valid || !condition(this.value); + return !this.valid || this.value === undefined || this.value === null || !condition(this.value); } public toString() diff --git a/src/internal/validator/AbstractCollectionValidator.mts b/src/internal/validator/AbstractCollectionValidator.mts index f9e4f55..fa1292b 100644 --- a/src/internal/validator/AbstractCollectionValidator.mts +++ b/src/internal/validator/AbstractCollectionValidator.mts @@ -33,12 +33,11 @@ import { /** * Validates the state of a collection. * - * @typeParam S - the type of validator returned by the methods * @typeParam T - the type the collection * @typeParam E - the type of elements in the array */ -abstract class AbstractCollectionValidator, E> - extends AbstractValidator +abstract class AbstractCollectionValidator, E> + extends AbstractValidator { protected readonly pluralizer: Pluralizer; @@ -50,7 +49,7 @@ abstract class AbstractCollectionValidator, E> * @param pluralizer - the type of items in the array * @param context - the contextual information set by a parent validator or the user * @param failures - the list of validation failures - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace, or is empty * @throws AssertionError if `scope`, `configuration`, `value`, `context` or `failures` are null */ @@ -64,23 +63,22 @@ abstract class AbstractCollectionValidator, E> this.pluralizer = pluralizer; } - public isEmpty(): S + public isEmpty(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && this.getLength(v) === 0)) + if (this.value.validationFailed(v => this.getLength(v) === 0)) { + this.failOnUndefinedOrNull(); this.addRangeError( objectIsEmpty(this).toString()); } - return this.self(); + return this; } /** * @param value - the collection * @returns the length of the collection */ - protected getLength(value: T) + protected getLength(value: E[] | Set) { return this.collectionAsArray(value).length; } @@ -109,32 +107,30 @@ abstract class AbstractCollectionValidator, E> return new Set(value); } - public isNotEmpty(): S + public isNotEmpty(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && this.getLength(v) !== 0)) + if (this.value.validationFailed(v => this.getLength(v) !== 0)) { + this.failOnUndefinedOrNull(); this.addRangeError( objectIsNotEmpty(this).toString()); } - return this.self(); + return this; } - contains(expected: E): S; - contains(expected: E, name?: string): S + contains(expected: E): this; + contains(expected: E, name?: string): this { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && this.collectionContainsElement(v, expected))) + if (this.value.validationFailed(v => this.collectionContainsElement(v, expected))) { + this.failOnUndefinedOrNull(); this.addRangeError( collectionContains(this, name ?? null, expected).toString()); } - return this.self(); + return this; } /** @@ -144,7 +140,7 @@ abstract class AbstractCollectionValidator, E> * @param element - an element * @returns true if `value` contains the element */ - protected collectionContainsElement(value: T, element: E): boolean + protected collectionContainsElement(value: E[] | Set, element: E): boolean { // Set.has(), indexOf(), includes() do not work for multidimensional arrays: // http://stackoverflow.com/a/24943461/14731 @@ -157,79 +153,73 @@ abstract class AbstractCollectionValidator, E> return false; } - doesNotContain(unwanted: E): S; - doesNotContain(unwanted: E, name?: string): S + doesNotContain(unwanted: E): this; + doesNotContain(unwanted: E, name?: string): this { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && !this.collectionContainsElement(v, unwanted))) + if (this.value.validationFailed(v => !this.collectionContainsElement(v, unwanted))) { + this.failOnUndefinedOrNull(); this.addRangeError( collectionDoesNotContain(this, name ?? null, unwanted).toString()); } - return this.self(); + return this; } - containsExactly(expected: E[], name?: string): S; - containsExactly(expected: Set, name?: string): S; - containsExactly(expected: E[] | Set, name?: string): S + containsExactly(expected: E[], name?: string): this; + containsExactly(expected: Set, name?: string): this; + containsExactly(expected: E[] | Set, name?: string): this { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); const difference = this.value.undefinedOrNullToInvalid(). map(v => Difference.actualVsOther(v, expected)).or(null); - if (difference === null || difference.areDifferent()) + if (difference === null || !difference.areTheSame()) { + this.failOnUndefinedOrNull(); this.addRangeError( collectionContainsExactly(this, difference, name ?? null, expected, this.pluralizer). toString()); } - return this.self(); + return this; } - doesNotContainExactly(unwanted: E[], name?: string): S; - doesNotContainExactly(unwanted: Set, name?: string): S; - doesNotContainExactly(unwanted: E[] | Set, name?: string): S + doesNotContainExactly(unwanted: E[], name?: string): this; + doesNotContainExactly(unwanted: Set, name?: string): this; + doesNotContainExactly(unwanted: E[] | Set, name?: string): this { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); const difference = this.value.undefinedOrNullToInvalid(). map(v => Difference.actualVsOther(v, unwanted)).or(null); - if (difference === null || difference.areTheSame()) + if (difference === null || !difference.areDifferent()) { + this.failOnUndefinedOrNull(); this.addRangeError( collectionDoesNotContainExactly(this, name ?? null, unwanted, this.pluralizer).toString()); } - return this.self(); + return this; } - containsAny(expected: E[], name?: string): S; - containsAny(expected: Set, name?: string): S; - containsAny(expected: E[] | Set, name?: string): S + containsAny(expected: E[], name?: string): this; + containsAny(expected: Set, name?: string): this; + containsAny(expected: E[] | Set, name?: string): this { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); - const definedOrNull = this.value.or(null); - - if (definedOrNull == null || - this.isDisjoint(this.collectionAsSet(definedOrNull), this.collectionAsSet(expected))) + if (this.value.validationFailed(v => + !this.isDisjoint(this.collectionAsSet(v), this.collectionAsSet(expected)))) { + this.failOnUndefinedOrNull(); this.addRangeError( collectionContainsAny(this, name ?? null, expected, this.pluralizer).toString()); } - return this.self(); + return this; } /** @@ -246,62 +236,59 @@ abstract class AbstractCollectionValidator, E> return true; } - doesNotContainAny(unwanted: E[], name?: string): S; - doesNotContainAny(unwanted: Set, name?: string): S; - doesNotContainAny(unwanted: E[] | Set, name?: string): S + doesNotContainAny(unwanted: E[], name?: string): this; + doesNotContainAny(unwanted: Set, name?: string): this; + doesNotContainAny(unwanted: E[] | Set, name?: string): this { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); - const difference = this.value.undefinedOrNullToInvalid().map(v => Difference.actualVsOther(v, unwanted)). - or(null); - if (difference === null || difference.common.size !== 0) + const difference = this.value.undefinedOrNullToInvalid(). + map(v => Difference.actualVsOther(v, unwanted)).or(null); + if (difference === null || !(difference.common.size === 0)) { + this.failOnUndefinedOrNull(); this.addRangeError( collectionDoesNotContainAny(this, difference, name ?? null, unwanted, this.pluralizer). toString()); } - return this.self(); + return this; } - containsAll(expected: E[], name?: string): S; - containsAll(expected: Set, name?: string): S; - containsAll(expected: E[] | Set, name?: string): S + containsAll(expected: E[], name?: string): this; + containsAll(expected: Set, name?: string): this; + containsAll(expected: E[] | Set, name?: string): this { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); - const difference = this.value.undefinedOrNullToInvalid().map(v => Difference.actualVsOther(v, expected)). - or(null); + const difference = this.value.undefinedOrNullToInvalid(). + map(v => Difference.actualVsOther(v, expected)).or(null); if (difference === null || difference.onlyInOther.size !== 0) { + this.failOnUndefinedOrNull(); this.addRangeError( collectionContainsAll(this, difference, name ?? null, expected, this.pluralizer). toString()); } - return this.self(); + return this; } - doesNotContainAll(unwanted: E[], name?: string): S; - doesNotContainAll(unwanted: Set, name?: string): S; - doesNotContainAll(unwanted: E[] | Set, name?: string): S + doesNotContainAll(unwanted: E[], name?: string): this; + doesNotContainAll(unwanted: Set, name?: string): this; + doesNotContainAll(unwanted: E[] | Set, name?: string): this { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && !this.collectionContainsAll(v, unwanted))) + if (this.value.validationFailed(v => !this.collectionContainsAll(v, unwanted))) { + this.failOnUndefinedOrNull(); this.addRangeError( collectionDoesNotContainAll(this, name ?? null, unwanted, this.pluralizer). toString()); } - return this.self(); + return this; } /** @@ -311,7 +298,7 @@ abstract class AbstractCollectionValidator, E> * @param expected - a collection of expected elements * @returns true if `value` contains all the `expected` elements */ - private collectionContainsAll(value: T, expected: E[] | Set): boolean + private collectionContainsAll(value: E[] | Set, expected: E[] | Set): boolean { // WORKAROUND: Replace with Set.isSupersetOf() once Typescript supports ES2024 for (const element of this.collectionAsArray(expected)) @@ -322,19 +309,18 @@ abstract class AbstractCollectionValidator, E> return true; } - doesNotContainDuplicates(): S + doesNotContainDuplicates(): this { - if (this.value.isNull()) - this.onNull(); const duplicates = this.value.undefinedOrNullToInvalid().map(v => this.getDuplicates(this.collectionAsArray(v))); if (duplicates.validationFailed(v => v.size === 0)) { + this.failOnUndefinedOrNull(); this.addRangeError( collectionDoesNotContainDuplicates(this, duplicates.or(null), this.pluralizer). toString()); } - return this.self(); + return this; } /** @@ -356,7 +342,7 @@ abstract class AbstractCollectionValidator, E> return duplicates; } - public containsSameNullity(): S + public containsSameNullity(): this { if (this.value.validationFailed(v => { @@ -370,13 +356,12 @@ abstract class AbstractCollectionValidator, E> this.addRangeError( collectionContainsSameNullity(this).toString()); } - return this.self(); + return this; } length(): UnsignedNumberValidator { - if (this.value.isNull()) - this.onNull(); + this.failOnUndefinedOrNull(); return new ObjectSizeValidatorImpl(this.scope, this._configuration, this, this.name + ".length()", this.value.undefinedOrNullToInvalid().map(v => this.getLength(v)), this.pluralizer, this.context, this.failures); diff --git a/src/internal/validator/AbstractValidator.mts b/src/internal/validator/AbstractValidator.mts index 919ae4a..20261ac 100644 --- a/src/internal/validator/AbstractValidator.mts +++ b/src/internal/validator/AbstractValidator.mts @@ -24,19 +24,19 @@ import { messagesIsEqualTo, messagesIsUndefined, type ObjectValidator, - type NonUndefinable, messagesIsNull, - type ClassConstructor + type ClassConstructor, + messagesIsNotUndefined, + type NonUndefinable } from "../internal.mjs"; import isEqual from "lodash.isequal"; /** * Validates the state of a value, recording failures without throwing an error. * - * @typeParam S - the type of validator that the methods should return * @typeParam T - the type of the value */ -abstract class AbstractValidator implements ValidatorComponent +abstract class AbstractValidator implements ValidatorComponent { protected static readonly VALUE_IS_UNDEFINED = () => new IllegalStateError("value is invalid"); private static readonly CONTAINS_WHITESPACE = /.*\\s.*/u; @@ -72,7 +72,7 @@ abstract class AbstractValidator implements ValidatorComponent * @param value - the value being validated * @param context - the contextual information set by a parent validator or the user * @param failures - the list of validation failures - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace, or is empty * @throws AssertionError if `scope`, `configuration`, `value`, `context` or `failures` are null */ @@ -131,10 +131,10 @@ abstract class AbstractValidator implements ValidatorComponent return this.value.or(defaultValue); } - and(validation: (validator: S) => void): S + and(validation: (validator: this) => void): this { - validation(this.self()); - return this.self(); + validation(this); + return this; } /** @@ -158,7 +158,7 @@ abstract class AbstractValidator implements ValidatorComponent * * @param message - a message that explains what went wrong */ - protected addTypeError(message: string): void + public addTypeError(message: string): void { this.addFailure(message, (theMessage: string) => new TypeError(theMessage)); } @@ -179,15 +179,6 @@ abstract class AbstractValidator implements ValidatorComponent return this._configuration; } - /** - * @typeParam U - the expected return type - * @returns this - */ - protected self(): U - { - return this as unknown as U; - } - public elseGetFailures() { return new ValidationFailures(this.failures); @@ -215,14 +206,14 @@ abstract class AbstractValidator implements ValidatorComponent return new Map(this.context); } - public withContext(value: unknown, name: string): S + public withContext(value: unknown, name: string): this { this.requireThatNameIsUnique(name, false); if (value === null) this.context.delete(name); else this.context.set(name, value); - return this.self(); + return this; } public getContextAsString(): string @@ -237,7 +228,7 @@ abstract class AbstractValidator implements ValidatorComponent * @param checkContext - `false` to allow the name to be used even if it conflicts with an * existing name in the validator context * @returns the internal validator of the name - * @throws RangeError if `name` is null + * @throws RangeError if `name` is `undefined` or `null` * @throws RangeError if `name`: *
                        *
                      • contains whitespace
                      • @@ -248,7 +239,7 @@ abstract class AbstractValidator implements ValidatorComponent protected requireThatNameIsUnique(name: string, checkContext = true) { const internalValidators = JavascriptValidatorsImpl.INTERNAL; - internalValidators.requireThat(name, "name").isTrimmed().isNotEmpty(); + internalValidators.requireThatString(name, "name").isTrimmed().isNotEmpty(); if (AbstractValidator.CONTAINS_WHITESPACE.test(name)) throw new RangeError("name may not contain whitespace"); @@ -267,7 +258,7 @@ different name.`); public isUndefined() { - if (this.value.validationFailed(v => v === undefined)) + if (!this.value.isUndefined()) { this.addTypeError( messagesIsUndefined(this).toString()); @@ -277,18 +268,18 @@ different name.`); public isNotUndefined() { - if (this.value.validationFailed(v => v !== undefined)) + if (this.value.isUndefined()) { this.addTypeError( messagesIsUndefined(this).toString()); } - return this as unknown as ObjectValidator>; + return this as ObjectValidator>; } public isNull() { - if (this.value.validationFailed(v => v === null)) + if (!this.value.isNull()) { this.addTypeError( messagesIsNull(this).toString()); @@ -298,12 +289,12 @@ different name.`); public isNotNull() { - if (this.value.validationFailed(v => v !== null)) + if (this.value.isNull()) { this.addTypeError( messagesIsNotNull(this).toString()); } - return this as unknown as ObjectValidator>; + return this as ObjectValidator>; } /** @@ -316,13 +307,13 @@ different name.`); */ private validateType(otherType: Type, mustBeEqual: boolean): boolean { - const validationFailed = this.value.validationFailed(v => + const validationFailed = this.value.map(v => { const typeOfValue = Type.of(v); if (typeof (otherType.typeGuard) !== "undefined") return otherType.typeGuard(v); - return isEqual(typeOfValue, otherType) === mustBeEqual; - }); + return isEqual(typeOfValue, otherType) !== mustBeEqual; + }).or(true); if (validationFailed) { this.addTypeError( @@ -332,59 +323,59 @@ different name.`); return true; } - public isType(expected: Type): S + public isType(expected: Type): this { - JavascriptValidatorsImpl.INTERNAL.requireThat(expected, "expected").isNotNull(); - if (this.value.validationFailed(v => Type.of(v).equals(expected))) + JavascriptValidatorsImpl.INTERNAL.requireThatObject(expected, "expected").isNotNull(); + if (this.value.map(v => !Type.of(v).equals(expected)).or(true)) { this.addTypeError( messagesIsInstanceOf(this, expected).toString()); } - return this.self(); + return this; } public isInstanceOf(expected: ClassConstructor): ObjectValidator { - JavascriptValidatorsImpl.INTERNAL.requireThat(expected, "expected").isNotNull(); + JavascriptValidatorsImpl.INTERNAL.requireThatObject(expected, "expected").isNotNull(); const className = Type.of(expected).name; this.validateType(Type.namedClass(className), true); return this as unknown as ObjectValidator; } - public isNotInstanceOf(expected: ClassConstructor): ObjectValidator + public isNotInstanceOf(expected: ClassConstructor): this { - JavascriptValidatorsImpl.INTERNAL.requireThat(expected, "expected").isNotNull(); + JavascriptValidatorsImpl.INTERNAL.requireThatObject(expected, "expected").isNotNull(); const className = Type.of(expected).name; this.validateType(Type.namedClass(className), false); - return this.self(); + return this; } - public isEqualTo(expected: unknown): S; + public isEqualTo(expected: unknown): this; public isEqualTo(expected: unknown, name?: string) { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.validationFailed(v => isEqual(v, expected))) + if (this.value.map(v => !isEqual(v, expected)).or(true)) { this.addRangeError( messagesIsEqualTo(this, name ?? null, expected).toString()); } - return this.self(); + return this; } - public isNotEqualTo(unwanted: unknown): S; + public isNotEqualTo(unwanted: unknown): this; public isNotEqualTo(unwanted: unknown, name?: string) { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.validationFailed(v => !isEqual(v, unwanted))) + if (this.value.map(v => isEqual(v, unwanted)).or(true)) { this.addRangeError( messagesIsNotEqualTo(this, name ?? null, unwanted).toString()); } - return this.self(); + return this; } /** @@ -402,11 +393,17 @@ different name.`); } /** - * Invoked by a validation if the value is null. Sets the value to `undefined`. + * Fails the validation if the value is `undefined` or `null`. */ - protected onNull() + protected failOnUndefinedOrNull() { - this.addRangeError(messagesIsNotNull(this).toString()); + this.value.ifValid(v => + { + if (v === undefined) + this.addRangeError(messagesIsNotUndefined(this).toString()); + else if (v === null) + this.addRangeError(messagesIsNotNull(this).toString()); + }); } } diff --git a/src/internal/validator/AbstractValidators.mts b/src/internal/validator/AbstractValidators.mts index f69a7d3..39f8e13 100644 --- a/src/internal/validator/AbstractValidators.mts +++ b/src/internal/validator/AbstractValidators.mts @@ -14,11 +14,12 @@ import { /** * Updates the configuration that will be used by new validators. + * + * @typeParam S - the type of the validator factory */ -class ConfigurationUpdaterImpl implements ConfigurationUpdater +class ConfigurationUpdaterImpl implements ConfigurationUpdater { - private readonly outer: AbstractValidators; - private readonly setConfiguration: (configuration: Configuration) => void; + private readonly outer: AbstractValidators; private _allowDiff: boolean; private readonly mutableStringMappers: MutableStringMappers; private _recordStacktrace: boolean; @@ -30,16 +31,12 @@ class ConfigurationUpdaterImpl implements ConfigurationUpdater * Creates a new configuration updater. * * @param outer - a reference to the outer class - * @param setConfiguration - a method that sets the validator factory's configuration - * @throws TypeError if `setConfiguration` is `undefined` or `null` */ - public constructor(outer: AbstractValidators, - setConfiguration: (configuration: Configuration) => void) + public constructor(outer: AbstractValidators) { this.outer = outer; - this.setConfiguration = setConfiguration; - const configuration = outer.getConfiguration(); + const configuration = outer.getRequireThatConfiguration(); this._allowDiff = configuration.allowDiff(); this.mutableStringMappers = MutableStringMappers.from(configuration.stringMappers()); this._recordStacktrace = configuration.recordStacktrace(); @@ -111,7 +108,7 @@ class ConfigurationUpdaterImpl implements ConfigurationUpdater if (this.closed) return; this.closed = true; - const oldConfiguration = this.outer.getConfiguration(); + const oldConfiguration = this.outer.getRequireThatConfiguration(); const immutableStringMappers = this.mutableStringMappers.toImmutable(); this.changed ||= immutableStringMappers !== oldConfiguration.stringMappers(); if (!this.changed) @@ -174,7 +171,7 @@ abstract class AbstractValidators implements Validators return this.scope; } - public getConfiguration(): Configuration + public getRequireThatConfiguration(): Configuration { return this.requireThatConfiguration; } @@ -199,39 +196,17 @@ abstract class AbstractValidators implements Validators return this.checkIfConfiguration; } - /** - * @returns this - */ - protected self(): S - { - return this as unknown as S; - } - public updateConfiguration(): ConfigurationUpdater; - public updateConfiguration(updater: (configuration: ConfigurationUpdater) => void): S; - public updateConfiguration(updater?: (configuration: ConfigurationUpdater) => void): ConfigurationUpdater | S + public updateConfiguration(updater: (configuration: ConfigurationUpdater) => void): this; + public updateConfiguration(updater?: (configuration: ConfigurationUpdater) => void): + ConfigurationUpdater | this { if (updater === undefined) - { - return new ConfigurationUpdaterImpl(this, - (newConfig: Configuration) => this.setConfiguration(newConfig)); - } + return new ConfigurationUpdaterImpl(this); const updatableConfiguration = this.updateConfiguration(); updater(updatableConfiguration); updatableConfiguration.close(); - return this.self(); - } - - /** - * Returns a configuration updater that sets the validator factory's configuration. - * - * @param setConfiguration - a method that sets the validator factory's configuration - * @returns the configuration updater - * @throws TypeError if `setConfiguration` is `null` - */ - public updateAndSetConfiguration(setConfiguration: (configuration: Configuration) => void) - { - return new ConfigurationUpdaterImpl(this, setConfiguration); + return this; } /** @@ -263,9 +238,9 @@ abstract class AbstractValidators implements Validators abstract copy(): S; - abstract removeContext(name: string): S; + abstract removeContext(name: string): this; - abstract withContext(value: unknown, name: string): S; + abstract withContext(value: unknown, name: string): this; } export {AbstractValidators}; \ No newline at end of file diff --git a/src/internal/validator/ArrayValidatorImpl.mts b/src/internal/validator/ArrayValidatorImpl.mts index e439028..f329410 100644 --- a/src/internal/validator/ArrayValidatorImpl.mts +++ b/src/internal/validator/ArrayValidatorImpl.mts @@ -8,15 +8,15 @@ import { Pluralizer, collectionIsSorted, type UnsignedNumberValidator, - ObjectSizeValidatorImpl, + ObjectSizeValidatorImpl } from "../internal.mjs"; import isEqual from "lodash.isequal"; /** * Default implementation of `ArrayValidator`. */ -class ArrayValidatorImpl extends AbstractCollectionValidator, E[], E> - implements ArrayValidator +class ArrayValidatorImpl extends AbstractCollectionValidator + implements ArrayValidator { /** * @param scope - the application configuration @@ -26,22 +26,20 @@ class ArrayValidatorImpl extends AbstractCollectionValidator, pluralizer: Pluralizer, context: Map, + value: ValidationTarget, pluralizer: Pluralizer, context: Map, failures: ValidationFailure[]) { super(scope, configuration, name, value, pluralizer, context, failures); } - isSorted(comparator: (first: unknown, second: unknown) => number): ArrayValidator + isSorted(comparator: (first: unknown, second: unknown) => number): this { - if (this.value.isNull()) - this.onNull(); - const sorted = this.value.map(v => + const sorted = this.value.undefinedOrNullToInvalid().map(v => { const valueAsList = this.collectionAsArray(v); const sortedList = [...valueAsList]; @@ -52,16 +50,16 @@ class ArrayValidatorImpl extends AbstractCollectionValidator v.length), this.pluralizer, this.context, this.failures); } diff --git a/src/internal/validator/BooleanValidatorImpl.mts b/src/internal/validator/BooleanValidatorImpl.mts index 20e8023..1c2eb3b 100644 --- a/src/internal/validator/BooleanValidatorImpl.mts +++ b/src/internal/validator/BooleanValidatorImpl.mts @@ -12,8 +12,8 @@ import { /** * Default implementation of `BooleanValidator`. */ -class BooleanValidatorImpl extends AbstractValidator - implements BooleanValidator +class BooleanValidatorImpl extends AbstractValidator + implements BooleanValidator { /** * @param scope - the application configuration @@ -22,23 +22,21 @@ class BooleanValidatorImpl extends AbstractValidator * @param value - the value * @param context - the contextual information set by a parent validator or the user * @param failures - the list of validation failures - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace, or is empty * @throws AssertionError if `scope`, `configuration`, `value`, `context` or `failures` are null */ public constructor(scope: ApplicationScope, configuration: Configuration, name: string, - value: ValidationTarget, context: Map, - failures: ValidationFailure[]) + value: ValidationTarget, context: Map, failures: ValidationFailure[]) { super(scope, configuration, name, value, context, failures); } public isTrue() { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && v)) + if (this.value.validationFailed(v => v)) { + this.failOnUndefinedOrNull(); this.addRangeError( isTrueFailed(this).toString()); } @@ -47,10 +45,9 @@ class BooleanValidatorImpl extends AbstractValidator public isFalse() { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && !v)) + if (this.value.validationFailed(v => !v)) { + this.failOnUndefinedOrNull(); this.addRangeError( isFalseFailed(this).toString()); } diff --git a/src/internal/validator/ClassValidatorImpl.mts b/src/internal/validator/ClassValidatorImpl.mts index a24a6c9..e1f0ceb 100644 --- a/src/internal/validator/ClassValidatorImpl.mts +++ b/src/internal/validator/ClassValidatorImpl.mts @@ -11,7 +11,7 @@ import { /** * Default implementation of ClassValidator. */ -class ClassValidatorImpl extends AbstractValidator, ClassConstructor> +class ClassValidatorImpl extends AbstractValidator> implements ClassValidator { isPrimitive(): ClassValidator @@ -21,7 +21,7 @@ class ClassValidatorImpl extends AbstractValidator, ClassCo this.addRangeError( classIsPrimitive(this).toString()); } - return this.self(); + return this; } isSupertypeOf(type: ClassConstructor): ClassValidator @@ -33,10 +33,11 @@ class ClassValidatorImpl extends AbstractValidator, ClassCo return child.isSubtypeOf(parent); })) { + this.failOnUndefinedOrNull(); this.addRangeError( classIsSupertypeOf(this, type).toString()); } - return this.self(); + return this as unknown as ClassValidator; } isSubtypeOf(type: ClassConstructor): ClassValidator @@ -48,10 +49,11 @@ class ClassValidatorImpl extends AbstractValidator, ClassCo return child.isSubtypeOf(parent); })) { + this.failOnUndefinedOrNull(); this.addRangeError( classIsSubtypeOf(this, type).toString()); } - return this.self(); + return this as unknown as ClassValidator; } } diff --git a/src/internal/validator/JavascriptValidatorsImpl.mts b/src/internal/validator/JavascriptValidatorsImpl.mts index 068e354..9ed2f91 100644 --- a/src/internal/validator/JavascriptValidatorsImpl.mts +++ b/src/internal/validator/JavascriptValidatorsImpl.mts @@ -13,7 +13,6 @@ import { type SetValidator, type ObjectValidator, type MapValidator, - TypeCategory, type ArrayValidator, ArrayValidatorImpl, type ApplicationScope, @@ -24,15 +23,19 @@ import { Type, requireThatType, ValidationTarget, + AssertionError, Pluralizer, - AssertionError + messagesIsInstanceOf, + AbstractValidator } from "../internal.mjs"; const typedocWorkaround: null | ConfigurationUpdater | AssertionError = null; +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ // noinspection PointlessBooleanExpressionJS if (typedocWorkaround !== null) console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); +/* eslint-enable @typescript-eslint/no-unnecessary-condition */ /** * The default implementation of JavascriptValidators. @@ -44,8 +47,8 @@ class JavascriptValidatorsImpl extends AbstractValidators /** * A validator factory that creates validators to check the arguments of validation methods. */ - public static readonly INTERNAL = new JavascriptValidatorsImpl( - MainApplicationScope.INSTANCE, Configuration.DEFAULT); + public static readonly INTERNAL = new JavascriptValidatorsImpl(MainApplicationScope.INSTANCE, + Configuration.DEFAULT); /** * Creates a new instance of this validator with an independent configuration. @@ -67,7 +70,7 @@ class JavascriptValidatorsImpl extends AbstractValidators public constructor(scope: ApplicationScope, other: JavascriptValidatorsImpl); public constructor(scope: ApplicationScope, configurationOrOther: Configuration | JavascriptValidatorsImpl) { - super(scope, JavascriptValidatorsImpl.getConfiguration(configurationOrOther)); + super(scope, JavascriptValidatorsImpl.getRequireThatConfiguration(configurationOrOther)); if (configurationOrOther instanceof JavascriptValidatorsImpl) { for (const entry of configurationOrOther.context) @@ -79,20 +82,97 @@ class JavascriptValidatorsImpl extends AbstractValidators * @param configurationOrOther - the configuration to use for new validators or the factory to copy * @returns the configuration to use for new validators */ - private static getConfiguration(configurationOrOther: Configuration | JavascriptValidatorsImpl) + private static getRequireThatConfiguration(configurationOrOther: Configuration | JavascriptValidatorsImpl) { if (configurationOrOther instanceof Configuration) return configurationOrOther; - return configurationOrOther.getConfiguration(); + return configurationOrOther.getRequireThatConfiguration(); } /** - * Validates the state of a value. + * Validates the state of a number. + *

                        + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns a verifier + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public requireThatNumber + (value: T, name: string): NumberValidator + { + verifyName(name, "name"); + return this.validateNumber(value, name, this.getRequireThatConfiguration()); + } + + /** + * Validates the state of a boolean. + *

                        + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns a verifier + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public requireThatBoolean + (value: T, name: string): BooleanValidator + { + verifyName(name, "name"); + return this.validateBoolean(value, name, this.getRequireThatConfiguration()); + } + + /** + * Validates the state of an array. + *

                        + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the array + * @param value - the value + * @param name - the name of the value + * @returns a verifier + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public requireThatArray + (value: T, name: string): ArrayValidator + { + verifyName(name, "name"); + return this.validateArray(value, name, this.getRequireThatConfiguration()); + } + + /** + * Validates the state of a set. + *

                        + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the set + * @param value - the value + * @param name - the name of the value + * @returns a verifier + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public requireThatSet | undefined | null, E> + (value: T, name: string): SetValidator + { + verifyName(name, "name"); + return this.validateSet(value, name, this.getRequireThatConfiguration()); + } + + /** + * Validates the state of a map. *

                        * The returned validator throws an error immediately if a validation fails. * * @typeParam T - the type the value - * @typeParam E - the type elements in the array or set * @typeParam K - the type of keys in the map * @typeParam V - the type of values in the map * @param value - the value @@ -101,28 +181,136 @@ class JavascriptValidatorsImpl extends AbstractValidators * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` is empty */ - public requireThat(value: number, name: string): NumberValidator; - public requireThat(value: boolean, name: string): BooleanValidator; - public requireThat(value: E[], name: string): ArrayValidator; - public requireThat(value: Set, name: string): SetValidator; - public requireThat(value: Map, name: string): MapValidator; - public requireThat(value: string, name: string): StringValidator; - public requireThat(value: T, name: string): ObjectValidator; - public requireThat(value: T, name: string): NumberValidator | BooleanValidator | - ArrayValidator | SetValidator | MapValidator | StringValidator | ObjectValidator + public requireThatMap | undefined | null, K, V> + (value: T, name: string): MapValidator + { + verifyName(name, "name"); + return this.validateMap(value, name, this.getRequireThatConfiguration()); + } + + /** + * Validates the state of a string. + *

                        + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns a verifier + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public requireThatString + (value: T, name: string): StringValidator + { + verifyName(name, "name"); + return this.validateString(value, name, this.getRequireThatConfiguration()); + } + + /** + * Validates the state of an object. + *

                        + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns a verifier + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public requireThatObject + (value: T, name: string): ObjectValidator { verifyName(name, "name"); - return this.newInstance(value, name, this.getConfiguration()); + return this.validateObject(value, name, this.getRequireThatConfiguration()); } /** - * Validates the state of a value. + * Validates the state of a number. + *

                        + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public assertThatNumber + (value: T, name?: string): NumberValidator + { + return this.validateNumber(value, name, this.getAssertThatConfiguration()); + } + + /** + * Validates the state of a boolean. + *

                        + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public assertThatBoolean + (value: T, name?: string): BooleanValidator + { + return this.validateBoolean(value, name, this.getAssertThatConfiguration()); + } + + /** + * Validates the state of an array. + *

                        + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the array + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public assertThatArray + (value: T, name?: string): ArrayValidator + { + return this.validateArray(value, name, this.getAssertThatConfiguration()); + } + + /** + * Validates the state of a set. + *

                        + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the set + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public assertThatSet | undefined | null, E> + (value: T, name?: string): SetValidator + { + return this.validateSet(value, name, this.getAssertThatConfiguration()); + } + + /** + * Validates the state of a map. *

                        * The returned validator throws an exception immediately if a validation fails. This exception is then * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. * * @typeParam T - the type the value - * @typeParam E - the type elements in the array or set * @typeParam K - the type of keys in the map * @typeParam V - the type of values in the map * @param value - the value @@ -131,21 +319,164 @@ class JavascriptValidatorsImpl extends AbstractValidators * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` is empty */ - public assertThat(value: number, name?: string): NumberValidator; - public assertThat(value: boolean, name?: string): BooleanValidator; - public assertThat(value: E[], name?: string): ArrayValidator; - public assertThat(value: Set, name?: string): SetValidator; - public assertThat(value: Map, name?: string): MapValidator; - public assertThat(value: string, name?: string): StringValidator; - public assertThat(value: T, name?: string): ObjectValidator; - public assertThat(value: T, name?: string): NumberValidator | BooleanValidator | - ArrayValidator | SetValidator | MapValidator | StringValidator | ObjectValidator + public assertThatMap | undefined | null, K, V> + (value: T, name?: string): MapValidator { - return this.newInstance(value, name, this.getAssertThatConfiguration()); + return this.validateMap(value, name, this.getAssertThatConfiguration()); } /** - * Validates the state of a value. + * Validates the state of a string. + *

                        + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public assertThatString + (value: T, name?: string): StringValidator + { + return this.validateString(value, name, this.getAssertThatConfiguration()); + } + + /** + * Validates the state of an object. + *

                        + * The returned validator throws an exception immediately if a validation fails. This exception is then + * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public assertThatObject + (value: T, name?: string): ObjectValidator + { + return this.validateObject(value, name, this.getAssertThatConfiguration()); + } + + /** + * Validates the state of a number. + *

                        + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public checkIfNumber + (value: T, name?: string): NumberValidator + { + return this.validateNumber(value, name, this.getCheckIfConfiguration()); + } + + /** + * Validates the state of a boolean. + *

                        + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public checkIfBoolean + (value: T, name?: string): BooleanValidator + { + return this.validateBoolean(value, name, this.getCheckIfConfiguration()); + } + + /** + * Validates the state of an array. + *

                        + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the array + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public checkIfArray + (value: T, name?: string): ArrayValidator + { + return this.validateArray(value, name, this.getCheckIfConfiguration()); + } + + /** + * Validates the state of a set. + *

                        + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @typeParam E - the type elements in the set + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public checkIfSet | undefined | null, E> + (value: T, name?: string): SetValidator + { + return this.validateSet(value, name, this.getCheckIfConfiguration()); + } + + /** + * Validates the state of a map. + *

                        + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @typeParam K - the type of keys in the map + * @typeParam V - the type of values in the map + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public checkIfMap | undefined | null, K, V> + (value: T, name?: string): MapValidator + { + return this.validateMap(value, name, this.getCheckIfConfiguration()); + } + + /** + * Validates the state of a string. + *

                        + * The returned validator throws an error immediately if a validation fails. + * + * @typeParam T - the type the value + * @param value - the value + * @param name - the name of the value + * @returns validator for the value + * @throws TypeError if `name` is `undefined` or `null` + * @throws RangeError if `name` is empty + */ + public checkIfString + (value: T, name?: string): StringValidator + { + return this.validateString(value, name, this.getCheckIfConfiguration()); + } + + /** + * Validates the state of an object. *

                        * The returned validator throws an error immediately if a validation fails. * @@ -159,72 +490,133 @@ class JavascriptValidatorsImpl extends AbstractValidators * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` is empty */ - public checkIf(value: number, name?: string): NumberValidator; - public checkIf(value: boolean, name?: string): BooleanValidator; - public checkIf(value: E[], name?: string): ArrayValidator; - public checkIf(value: Set, name?: string): SetValidator; - public checkIf(value: Map, name?: string): MapValidator; - public checkIf(value: string, name?: string): StringValidator; - public checkIf(value: T, name?: string): ObjectValidator; - public checkIf(value: T, name?: string): NumberValidator | BooleanValidator | - ArrayValidator | SetValidator | MapValidator | StringValidator | ObjectValidator + public checkIfObject + (value: T, name?: string): ObjectValidator { - return this.newInstance(value, name, this.getCheckIfConfiguration()); + return this.validateObject(value, name, this.getCheckIfConfiguration()); } - public newInstance(value: T, name: string | undefined, configuration: Configuration): - NumberValidator | BooleanValidator | ArrayValidator | SetValidator | MapValidator | - StringValidator | ObjectValidator + public validateNumber + (value: T, name: string | undefined, configuration: Configuration): NumberValidator { if (name === undefined) name = JavascriptValidatorsImpl.DEFAULT_NAME; + else + verifyName(name, "name"); - const typeOfValue = Type.of(value); - switch (typeOfValue.category) + const validator = new NumberValidatorImpl(this.scope, configuration, name, + ValidationTarget.valid(value), new Map(), []); + this.validateType(validator, value, Type.NUMBER); + return validator; + } + + /** + * Ensures that the value's runtime and compile-time types match. + * + * @param validator - a validator + * @param value - the value + * @param expectedType - the value's expected type + */ + private validateType(validator: AbstractValidator, value: unknown, expectedType: Type) + { + const actualType = Type.of(value); + switch (actualType) { - case TypeCategory.BOOLEAN: - { - return new BooleanValidatorImpl(this.scope, configuration, name, - ValidationTarget.valid(value as boolean), new Map(), []); - } - case TypeCategory.STRING: - { - return new StringValidatorImpl(this.scope, configuration, name, - ValidationTarget.valid(value as string), new Map(), []); - } - case TypeCategory.NUMBER: - { - return new NumberValidatorImpl(this.scope, configuration, name, - ValidationTarget.valid(value as number), new Map(), []); - } - case TypeCategory.ARRAY: - { - return new ArrayValidatorImpl(this.scope, configuration, name, - ValidationTarget.valid(value as E[]), Pluralizer.ELEMENT, new Map(), []); - } - case TypeCategory.CLASS: - { - switch (typeOfValue.name) - { - case "Set": - { - return new SetValidatorImpl(this.scope, configuration, name, - ValidationTarget.valid(value as Set), Pluralizer.ELEMENT, new Map(), []); - } - case "Map": - { - return new MapValidatorImpl(this.scope, configuration, name, - ValidationTarget.valid(value as Map), new Map(), []); - } - } - break; - } + case Type.UNDEFINED: + case Type.NULL: + return; } - return new ObjectValidatorImpl(this.scope, configuration, name, ValidationTarget.valid(value), - new Map(), []); + if (!expectedType.equals(expectedType)) + { + // Cannot compare Type inside a switch statement because it doesn't map "name == null" to any class + validator.addTypeError( + messagesIsInstanceOf(validator, expectedType).toString()); + } + } + + public validateBoolean + (value: T, name: string | undefined, configuration: Configuration): BooleanValidator + { + if (name === undefined) + name = JavascriptValidatorsImpl.DEFAULT_NAME; + else + verifyName(name, "name"); + + const validator = new BooleanValidatorImpl(this.scope, configuration, name, + ValidationTarget.valid(value), new Map(), []); + this.validateType(validator, value, Type.BOOLEAN); + return validator; + } + + public validateArray + (value: T, name: string | undefined, configuration: Configuration): ArrayValidator + { + if (name === undefined) + name = JavascriptValidatorsImpl.DEFAULT_NAME; + else + verifyName(name, "name"); + + const validator = new ArrayValidatorImpl(this.scope, configuration, name, + ValidationTarget.valid(value), Pluralizer.ELEMENT, new Map(), []); + this.validateType(validator, value, Type.ARRAY); + return validator; + } + + public validateSet | undefined | null, E> + (value: T, name: string | undefined, configuration: Configuration): SetValidator + { + if (name === undefined) + name = JavascriptValidatorsImpl.DEFAULT_NAME; + else + verifyName(name, "name"); + + const validator = new SetValidatorImpl(this.scope, configuration, name, + ValidationTarget.valid(value), Pluralizer.ELEMENT, new Map(), []); + this.validateType(validator, value, Type.namedClass("Set")); + return validator; + } + + public validateMap | undefined | null, K, V> + (value: T, name: string | undefined, configuration: Configuration): MapValidator + { + if (name === undefined) + name = JavascriptValidatorsImpl.DEFAULT_NAME; + else + verifyName(name, "name"); + + const validator = new MapValidatorImpl(this.scope, configuration, name, + ValidationTarget.valid(value), new Map(), []); + this.validateType(validator, value, Type.namedClass("Map")); + return validator; + } + + public validateString + (value: T, name: string | undefined, configuration: Configuration): StringValidator + { + if (name === undefined) + name = JavascriptValidatorsImpl.DEFAULT_NAME; + else + verifyName(name, "name"); + const validator = new StringValidatorImpl(this.scope, configuration, name, + ValidationTarget.valid(value), new Map(), []); + this.validateType(validator, value, Type.STRING); + return validator; + } + + public validateObject + (value: T, name: string | undefined, configuration: Configuration): ObjectValidator + { + if (name === undefined) + name = JavascriptValidatorsImpl.DEFAULT_NAME; + else + verifyName(name, "name"); + const validator = new ObjectValidatorImpl(this.scope, configuration, name, + ValidationTarget.valid(value), new Map(), []); + this.validateType(validator, value, Type.namedClass(null)); + return validator; } - public copy() + public copy(): JavascriptValidators { return new JavascriptValidatorsImpl(this.scope, this); } diff --git a/src/internal/validator/MapValidatorImpl.mts b/src/internal/validator/MapValidatorImpl.mts index c922bb1..7db1439 100644 --- a/src/internal/validator/MapValidatorImpl.mts +++ b/src/internal/validator/MapValidatorImpl.mts @@ -15,11 +15,12 @@ import { /** * Default implementation of `MapValidator`. * + * @typeParam T - the type of the value * @typeParam K - the type of keys in the map * @typeParam V - the type of values in the map */ -class MapValidatorImpl extends AbstractValidator, Map> - implements MapValidator +class MapValidatorImpl | undefined | null, K, V> extends AbstractValidator + implements MapValidator { /** * Creates a new MapValidatorImpl. @@ -30,12 +31,12 @@ class MapValidatorImpl extends AbstractValidator, Map>, context: Map, + value: ValidationTarget, context: Map, failures: ValidationFailure[]) { super(scope, configuration, name, value, context, failures); @@ -43,10 +44,9 @@ class MapValidatorImpl extends AbstractValidator, Map v != null && v.size === 0)) + if (this.value.validationFailed(v => v.size === 0)) { + this.failOnUndefinedOrNull(); this.addRangeError( objectIsEmpty(this).toString()); } @@ -55,10 +55,9 @@ class MapValidatorImpl extends AbstractValidator, Map v != null && v.size !== 0)) + if (this.value.validationFailed(v => v.size !== 0)) { + this.failOnUndefinedOrNull(); this.addRangeError( objectIsNotEmpty(this).toString()); } @@ -67,8 +66,7 @@ class MapValidatorImpl extends AbstractValidator, Map [...v.keys()]), Pluralizer.KEY, this.context, this.failures); @@ -78,8 +76,7 @@ class MapValidatorImpl extends AbstractValidator, Map [...v.values()]), Pluralizer.VALUE, this.context, this.failures); @@ -89,8 +86,7 @@ class MapValidatorImpl extends AbstractValidator, Map [...v.entries()]), Pluralizer.ENTRY, this.context, this.failures); @@ -100,8 +96,7 @@ class MapValidatorImpl extends AbstractValidator, Map v.size), Pluralizer.ELEMENT, this.context, this.failures); diff --git a/src/internal/validator/MutableConfiguration.mts b/src/internal/validator/MutableConfiguration.mts index 7ca6eff..874fe71 100644 --- a/src/internal/validator/MutableConfiguration.mts +++ b/src/internal/validator/MutableConfiguration.mts @@ -79,8 +79,8 @@ class MutableConfiguration * @param mayDiff - `true` if error messages may include a diff, `false` otherwise * @returns this */ - public allowDiff(mayDiff: boolean): MutableConfiguration; - public allowDiff(mayDiff?: boolean): boolean | MutableConfiguration + public allowDiff(mayDiff: boolean): this; + public allowDiff(mayDiff?: boolean): boolean | this { if (mayDiff === undefined) return this._allowDiff; @@ -117,8 +117,8 @@ class MutableConfiguration * @param recordStacktrace - `true` if errors must be recorded when a validation failure occurs * @returns this */ - public recordStacktrace(recordStacktrace: boolean): MutableConfiguration; - public recordStacktrace(recordStacktrace?: boolean): boolean | MutableConfiguration + public recordStacktrace(recordStacktrace: boolean): this; + public recordStacktrace(recordStacktrace?: boolean): boolean | this { if (recordStacktrace === undefined) return this._recordStacktrace; @@ -138,8 +138,8 @@ class MutableConfiguration * @param throwOnFailure - `true` if an error is thrown on validation failure * @returns this */ - public throwOnFailure(throwOnFailure: boolean): MutableConfiguration; - public throwOnFailure(throwOnFailure?: boolean): boolean | MutableConfiguration + public throwOnFailure(throwOnFailure: boolean): this; + public throwOnFailure(throwOnFailure?: boolean): boolean | this { if (throwOnFailure === undefined) return this._throwOnFailure; @@ -163,9 +163,8 @@ class MutableConfiguration * @throws TypeError if `errorTransformer` is `undefined` or `null` * @returns this */ - public errorTransformer(errorTransformer: (error: Error) => Error): MutableConfiguration; - public errorTransformer(errorTransformer?: (error: Error) => Error): - ((error: Error) => Error) | MutableConfiguration + public errorTransformer(errorTransformer: (error: Error) => Error): this; + public errorTransformer(errorTransformer?: (error: Error) => Error): ((error: Error) => Error) | this { if (errorTransformer === undefined) return this._errorTransformer; diff --git a/src/internal/validator/NumberValidatorImpl.mts b/src/internal/validator/NumberValidatorImpl.mts index 5921908..ba9f596 100644 --- a/src/internal/validator/NumberValidatorImpl.mts +++ b/src/internal/validator/NumberValidatorImpl.mts @@ -29,8 +29,8 @@ import { /** * Default implementation of `NumberValidator`. */ -class NumberValidatorImpl extends AbstractValidator - implements NumberValidator +class NumberValidatorImpl extends AbstractValidator + implements NumberValidator { /** * @param scope - the application configuration @@ -39,167 +39,152 @@ class NumberValidatorImpl extends AbstractValidator * @param value - the value * @param context - the contextual information set by a parent validator or the user * @param failures - the list of validation failures - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace, or is empty * @throws AssertionError if `scope`, `configuration`, `value`, `context` or `failures` are null */ public constructor(scope: ApplicationScope, configuration: Configuration, name: string, - value: ValidationTarget, context: Map, + value: ValidationTarget, context: Map, failures: ValidationFailure[]) { super(scope, configuration, name, value, context, failures); } - isNegative(): NumberValidator + isNegative(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && v < 0)) + if (this.value.validationFailed(v => v < 0)) { + this.failOnUndefinedOrNull(); this.addRangeError( numberIsNegative(this).toString()); } return this; } - isNotNegative(): NumberValidator + isNotNegative(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && !(v < 0))) + if (this.value.validationFailed(v => !(v < 0))) { + this.failOnUndefinedOrNull(); this.addRangeError( numberIsNegative(this).toString()); } return this; } - isZero(): NumberValidator + isZero(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && v === 0)) + if (this.value.validationFailed(v => v === 0)) { + this.failOnUndefinedOrNull(); this.addRangeError( numberIsZero(this).toString()); } return this; } - isNotZero(): NumberValidator + isNotZero(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && !(v === 0))) + if (this.value.validationFailed(v => !(v === 0))) { + this.failOnUndefinedOrNull(); this.addRangeError( numberIsNotZero(this).toString()); } return this; } - isPositive(): NumberValidator + isPositive(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && v > 0)) + if (this.value.validationFailed(v => v > 0)) { + this.failOnUndefinedOrNull(); this.addRangeError( numberIsPositive(this).toString()); } return this; } - isNotPositive(): NumberValidator + isNotPositive(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && !(v > 0))) + if (this.value.validationFailed(v => !(v > 0))) { + this.failOnUndefinedOrNull(); this.addRangeError( numberIsNotPositive(this).toString()); } return this; } - isGreaterThan(value: number): NumberValidator; + isGreaterThan(value: number): this; isGreaterThan(minimumExclusive: number, name?: string) { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && v > minimumExclusive)) + if (this.value.validationFailed(v => v > minimumExclusive)) { + this.failOnUndefinedOrNull(); this.addRangeError( comparableIsGreaterThan(this, name ?? null, minimumExclusive).toString()); } return this; } - isGreaterThanOrEqualTo(minimumInclusive: number, name?: string): NumberValidator + isGreaterThanOrEqualTo(minimumInclusive: number, name?: string): this { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && v >= minimumInclusive)) + if (this.value.validationFailed(v => v >= minimumInclusive)) { - this. - addRangeError( - comparableIsGreaterThanOrEqualTo(this, name ?? null, minimumInclusive).toString()); + this.failOnUndefinedOrNull(); + this.addRangeError( + comparableIsGreaterThanOrEqualTo(this, name ?? null, minimumInclusive).toString()); } return this; } - isLessThan(maximumExclusive: number, name?: string): NumberValidator + isLessThan(maximumExclusive: number, name?: string): this { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && v < maximumExclusive)) + if (this.value.validationFailed(v => v < maximumExclusive)) { - this. - addRangeError( - comparableIsLessThan(this, name ?? null, maximumExclusive).toString()); + this.failOnUndefinedOrNull(); + this.addRangeError( + comparableIsLessThan(this, name ?? null, maximumExclusive).toString()); } return this; } - isLessThanOrEqualTo(maximumInclusive: number, name?: string): NumberValidator + isLessThanOrEqualTo(maximumInclusive: number, name?: string): this { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && v <= maximumInclusive)) + if (this.value.validationFailed(v => v <= maximumInclusive)) { - this. - addRangeError( - comparableIsLessThanOrEqualTo(this, name ?? null, maximumInclusive).toString()); + this.failOnUndefinedOrNull(); + this.addRangeError( + comparableIsLessThanOrEqualTo(this, name ?? null, maximumInclusive).toString()); } return this; } - isBetween(minimumInclusive: number, maximumExclusive: number): NumberValidator; + isBetween(minimumInclusive: number, maximumExclusive: number): this; isBetween(minimum: number, minimumIsInclusive: boolean, maximum: number, - maximumIsInclusive: boolean): NumberValidator; + maximumIsInclusive: boolean): this; isBetween(minimum: number, maximumExclusiveOrMinimumIsInclusive: number | boolean, maximum?: number, - maximumInclusive?: boolean): NumberValidator + maximumInclusive?: boolean): this { const normalized = NumberValidatorImpl.normalizeIsBetweenParameters(minimum, maximumExclusiveOrMinimumIsInclusive, maximum, maximumInclusive); const internalValidators = JavascriptValidatorsImpl.INTERNAL; - internalValidators.requireThat(normalized.minimum, "minimum"). + internalValidators.requireThatNumber(normalized.minimum, "minimum"). isLessThanOrEqualTo(normalized.maximum, "maximum"); - if (this.value.isNull()) - this.onNull(); if (this.value.validationFailed(v => { if (normalized.minimumIsInclusive) @@ -214,11 +199,12 @@ class NumberValidatorImpl extends AbstractValidator return v < normalized.maximum; })) { + this.failOnUndefinedOrNull(); this.addRangeError( isBetweenFailed(this, normalized.minimum, normalized.minimumIsInclusive, normalized.maximum, normalized.maximumIsInclusive).toString()); } - return this.self(); + return this; } /** @@ -268,47 +254,41 @@ class NumberValidatorImpl extends AbstractValidator }; } - isFinite(): NumberValidator + isFinite(): this { - if (this.value.isNull()) - this.onNull(); // See http://stackoverflow.com/a/1830844/14731 - if (this.value.validationFailed(v => v != null && Number.isFinite(v))) + if (this.value.validationFailed(v => Number.isFinite(v))) { - this. - addRangeError( - numberIsFinite(this).toString()); + this.failOnUndefinedOrNull(); + this.addRangeError( + numberIsFinite(this).toString()); } return this; } - isInfinite(): NumberValidator + isInfinite(): this { - if (this.value.isNull()) - this.onNull(); // See http://stackoverflow.com/a/1830844/14731 - if (this.value.validationFailed(v => v != null && !Number.isFinite(v))) + if (this.value.validationFailed(v => !Number.isFinite(v))) { - this. - addRangeError( - numberIsInfinite(this).toString()); + this.failOnUndefinedOrNull(); + this.addRangeError( + numberIsInfinite(this).toString()); } return this; } - public isMultipleOf(factor: number): NumberValidator; + public isMultipleOf(factor: number): this; public isMultipleOf(factor: number, name?: string) { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && NumberValidatorImpl.valueIsMultipleOf(v, factor))) + if (this.value.validationFailed(v => NumberValidatorImpl.valueIsMultipleOf(v, factor))) { - this. - addRangeError( - numberIsMultipleOf(this, name ?? null, factor).toString()); + this.failOnUndefinedOrNull(); + this.addRangeError( + numberIsMultipleOf(this, name ?? null, factor).toString()); } return this; } @@ -323,45 +303,39 @@ class NumberValidatorImpl extends AbstractValidator return factor !== 0 && (value === 0 || (value % factor === 0)); } - public isNotMultipleOf(factor: number): NumberValidator; + public isNotMultipleOf(factor: number): this; public isNotMultipleOf(factor: number, name?: string) { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && !NumberValidatorImpl.valueIsMultipleOf(v, factor))) + if (this.value.validationFailed(v => !NumberValidatorImpl.valueIsMultipleOf(v, factor))) { - this. - addRangeError( - numberIsNotMultipleOf(this, name ?? null, factor).toString()); + this.failOnUndefinedOrNull(); + this.addRangeError( + numberIsNotMultipleOf(this, name ?? null, factor).toString()); } return this; } - isNumber(): NumberValidator + isNumber(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && Number.isNaN(v))) + if (this.value.validationFailed(v => Number.isNaN(v))) { - this. - addRangeError( - numberIsNumber(this).toString()); + this.failOnUndefinedOrNull(); + this.addRangeError( + numberIsNumber(this).toString()); } return this; } - isNotNumber(): NumberValidator + isNotNumber(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && !Number.isNaN(v))) + if (this.value.validationFailed(v => !Number.isNaN(v))) { - this. - addRangeError( - numberIsNotNumber(this).toString()); + this.failOnUndefinedOrNull(); + this.addRangeError( + numberIsNotNumber(this).toString()); } return this; } diff --git a/src/internal/validator/ObjectSizeValidatorImpl.mts b/src/internal/validator/ObjectSizeValidatorImpl.mts index ff1aa45..1597396 100644 --- a/src/internal/validator/ObjectSizeValidatorImpl.mts +++ b/src/internal/validator/ObjectSizeValidatorImpl.mts @@ -24,10 +24,10 @@ import {requireThatValueIsDefined} from "./Objects.mjs"; /** * Validates the state of an object's size. */ -class ObjectSizeValidatorImpl extends AbstractValidator +class ObjectSizeValidatorImpl extends AbstractValidator implements UnsignedNumberValidator { - private readonly objectValidator: AbstractValidator; + private readonly objectValidator: AbstractValidator; private readonly pluralizer: Pluralizer; /** @@ -39,12 +39,12 @@ class ObjectSizeValidatorImpl extends AbstractValidator, sizeName: string, + objectValidator: AbstractValidator, sizeName: string, size: ValidationTarget, pluralizer: Pluralizer, context: Map, failures: ValidationFailure[]) { @@ -57,22 +57,24 @@ class ObjectSizeValidatorImpl extends AbstractValidator value == 0)) + { + this.failOnUndefinedOrNull(); this.addRangeError(objectIsEmpty(this.objectValidator).toString()); - return this.self(); + } + return this; } - public isNotZero(): UnsignedNumberValidator + public isNotZero(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v !== null && !(v == 0))) + if (this.value.validationFailed(v => !(v == 0))) + { + this.failOnUndefinedOrNull(); this.addRangeError(objectIsNotEmpty(this.objectValidator).toString()); - return this.self(); + } + return this; } public isPositive() @@ -85,77 +87,73 @@ class ObjectSizeValidatorImpl extends AbstractValidator v !== null && v < maximumExclusive)) + if (this.value.validationFailed(v => v < maximumExclusive)) { + this.failOnUndefinedOrNull(); this.addRangeError( collectionContainsSize(this.objectValidator, this.name, this.value.or(null), "must contain less than", name ?? null, maximumExclusive, this.pluralizer).toString()); } - return this.self(); + return this; } - public isLessThanOrEqualTo(maximumInclusive: number): UnsignedNumberValidator; + public isLessThanOrEqualTo(maximumInclusive: number): this; public isLessThanOrEqualTo(maximumInclusive: number, name?: string) { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v !== null && v <= maximumInclusive)) + if (this.value.validationFailed(v => v <= maximumInclusive)) { + this.failOnUndefinedOrNull(); this.addRangeError( collectionContainsSize(this.objectValidator, this.name, this.value.or(null), "may not contain more than", name ?? null, maximumInclusive, this.pluralizer).toString()); } - return this.self(); + return this; } - public isGreaterThanOrEqualTo(minimumInclusive: number): UnsignedNumberValidator; + public isGreaterThanOrEqualTo(minimumInclusive: number): this; public isGreaterThanOrEqualTo(minimumInclusive: number, name?: string) { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(value => value >= minimumInclusive)) + if (this.value.validationFailed(v => v >= minimumInclusive)) { + this.failOnUndefinedOrNull(); this.addRangeError( collectionContainsSize(this.objectValidator, this.name, this.value.or(null), "must contain at least", name ?? null, minimumInclusive, this.pluralizer).toString()); } - return this.self(); + return this; } - public isGreaterThan(minimumExclusive: number): UnsignedNumberValidator; - public isGreaterThan(minimumExclusive: number, name?: string): UnsignedNumberValidator + public isGreaterThan(minimumExclusive: number): this; + public isGreaterThan(minimumExclusive: number, name?: string): this { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(value => value >= minimumExclusive)) + if (this.value.validationFailed(v => v >= minimumExclusive)) { + this.failOnUndefinedOrNull(); this.addRangeError( collectionContainsSize(this.objectValidator, this.name, this.value.or(null), "must contain more than", name ?? null, minimumExclusive, this.pluralizer).toString()); } - return this.self(); + return this; } - public isBetween(minimumInclusive: number, maximumExclusive: number): UnsignedNumberValidator; + public isBetween(minimumInclusive: number, maximumExclusive: number): this; public isBetween(minimum: number, minimumIsInclusive: boolean, maximum: number, - maximumIsInclusive: boolean): UnsignedNumberValidator; + maximumIsInclusive: boolean): this; public isBetween(minimum: number, maximumExclusiveOrMinimumIsInclusive: number | boolean, maximum?: number, maximumIsInclusive?: boolean) { @@ -163,20 +161,18 @@ class ObjectSizeValidatorImpl extends AbstractValidator v !== null && - ObjectSizeValidatorImpl.inBounds(v, normalized.minimum, normalized.minimumIsInclusive, - normalized.maximum, normalized.maximumIsInclusive))) + if (this.value.validationFailed(v => ObjectSizeValidatorImpl.inBounds(v, normalized.minimum, + normalized.minimumIsInclusive, normalized.maximum, normalized.maximumIsInclusive))) { + this.failOnUndefinedOrNull(); this.addRangeError( collectionSizeIsBetween(this, this.name, this.value.or(null), normalized.minimum, normalized.minimumIsInclusive, normalized.maximum, normalized.maximumIsInclusive, this.pluralizer). toString()); } - return this.self(); + return this; } /** @@ -202,89 +198,81 @@ class ObjectSizeValidatorImpl extends AbstractValidator v !== null && NumberValidatorImpl.valueIsMultipleOf(v, factor))) + if (this.value.validationFailed(v => NumberValidatorImpl.valueIsMultipleOf(v, factor))) { - { - const messageBuilder = numberIsMultipleOf(this, name ?? null, factor); - this.objectValidator.value.ifValid(v => - messageBuilder.withContext(v, this.objectValidator.getName())); - this.addRangeError(messageBuilder.toString()); - } + this.failOnUndefinedOrNull(); + const messageBuilder = numberIsMultipleOf(this, name ?? null, factor); + this.objectValidator.value.ifValid(v => + messageBuilder.withContext(v, this.objectValidator.getName())); + this.addRangeError(messageBuilder.toString()); } - return this.self(); + return this; } - public isNotMultipleOf(factor: number): UnsignedNumberValidator; + public isNotMultipleOf(factor: number): this; public isNotMultipleOf(factor: number, name?: string) { if (name !== undefined) this.requireThatNameIsUnique(name); - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v !== null && !NumberValidatorImpl.valueIsMultipleOf(v, factor))) + if (this.value.validationFailed(v => !NumberValidatorImpl.valueIsMultipleOf(v, factor))) { + this.failOnUndefinedOrNull(); const messageBuilder = numberIsNotMultipleOf(this, name ?? null, factor); this.objectValidator.value.ifValid(v => messageBuilder.withContext(v, this.objectValidator.getName())); this.addRangeError(messageBuilder.toString()); } - return this.self(); + return this; } - public isFinite(): UnsignedNumberValidator + public isFinite(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v !== null && Number.isFinite(v))) + if (this.value.validationFailed(v => Number.isFinite(v))) { + this.failOnUndefinedOrNull(); this.addRangeError( numberIsFinite(this).toString()); } - return this.self(); + return this; } - public isInfinite(): UnsignedNumberValidator + public isInfinite(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v !== null && !Number.isFinite(v) && !Number.isNaN(v))) + if (this.value.validationFailed(v => !Number.isFinite(v) && !Number.isNaN(v))) { + this.failOnUndefinedOrNull(); this.addRangeError( numberIsInfinite(this).toString()); } - return this.self(); + return this; } - isNumber(): UnsignedNumberValidator + isNumber(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v !== null && !Number.isNaN(v))) + if (this.value.validationFailed(v => !Number.isNaN(v))) { + this.failOnUndefinedOrNull(); this.addRangeError( numberIsNumber(this).toString()); } - return this.self(); + return this; } - isNotNumber(): UnsignedNumberValidator + isNotNumber(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v !== null && Number.isNaN(v))) + if (this.value.validationFailed(v => Number.isNaN(v))) { + this.failOnUndefinedOrNull(); this.addRangeError( numberIsNotNumber(this).toString()); } - return this.self(); + return this; } } diff --git a/src/internal/validator/ObjectValidatorImpl.mts b/src/internal/validator/ObjectValidatorImpl.mts index 4e2dc46..385cf18 100644 --- a/src/internal/validator/ObjectValidatorImpl.mts +++ b/src/internal/validator/ObjectValidatorImpl.mts @@ -4,7 +4,7 @@ import { type ValidationFailure, type ApplicationScope, AbstractValidator, - ValidationTarget, + ValidationTarget } from "../internal.mjs"; @@ -14,7 +14,7 @@ import { * @typeParam T - the type the value */ class ObjectValidatorImpl - extends AbstractValidator, T> + extends AbstractValidator implements ObjectValidator { /** @@ -24,7 +24,7 @@ class ObjectValidatorImpl * @param value - the value * @param context - the contextual information set by a parent validator or the user * @param failures - the list of validation failures - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace, or is empty * @throws AssertionError if `scope`, `configuration`, `value`, `context` or `failures` are null */ diff --git a/src/internal/validator/Objects.mts b/src/internal/validator/Objects.mts index 0a958a1..e43fbc8 100644 --- a/src/internal/validator/Objects.mts +++ b/src/internal/validator/Objects.mts @@ -15,7 +15,7 @@ type MapValue = T extends Map ? V : never; // eslint-disable-next-line @typescript-eslint/no-explicit-any type ClassConstructor = abstract new (...args: any[]) => NonNullable; type Comparable = number | string | boolean; -type NonUndefinable = T extends undefined ? never : T; +type NonUndefinable = Exclude /** * Indicates if an object is an instance of a type. To convert a type to an object, use @@ -29,8 +29,6 @@ type NonUndefinable = T extends undefined ? never : T; */ function classExtends(child: ClassConstructor, parent: ClassConstructor) { - if (child === undefined || child === null || parent === undefined || parent === null) - return false; // https://stackoverflow.com/a/14486171/14731 return child.prototype instanceof parent; } @@ -436,11 +434,12 @@ function internalValueToString(value: unknown): string case TypeCategory.STRING: return quoteString(value as string); default: - return `${JSON.stringify(value, undefined, 2)}`; + return JSON.stringify(value, undefined, 2); } // An instance of a user class let current = value as ClassConstructor; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition while (true) { // See http://stackoverflow.com/a/22445303/14731, @@ -450,17 +449,25 @@ function internalValueToString(value: unknown): string return current.toString(); // Get the superclass and try again - current = getSuperclass(current); - typeOfObject = Type.of(current); - assert(typeOfObject.category === TypeCategory.CLASS, undefined, - `expected: CLASS + const superclass = getSuperclass(current); + + let className; + if (superclass === null) + className = "Object"; + else + { + current = superclass; + typeOfObject = Type.of(current); + assert(typeOfObject.category === TypeCategory.CLASS, undefined, + `expected: CLASS actual: ${typeOfObject.toString()}`); + className = typeOfObject.name as string; + } - const className = typeOfObject.name as string; if (className === "Object") { // Prefer JSON.stringify() to Object.toString(). - return JSON.stringify(current, null, 2); + return JSON.stringify(value, null, 2); } } } @@ -497,7 +504,7 @@ function quoteString(value: string) */ function getSuperclass(value: ClassConstructor) { - return Object.getPrototypeOf(value.constructor.prototype) as ClassConstructor; + return Object.getPrototypeOf(value.constructor.prototype) as ClassConstructor | null; } /** diff --git a/src/internal/validator/SetValidatorImpl.mts b/src/internal/validator/SetValidatorImpl.mts index 0f1eda7..4d73168 100644 --- a/src/internal/validator/SetValidatorImpl.mts +++ b/src/internal/validator/SetValidatorImpl.mts @@ -13,8 +13,8 @@ import { /** * Default implementation of `SetValidator`. */ -class SetValidatorImpl extends AbstractCollectionValidator, Set, E> - implements SetValidator +class SetValidatorImpl | undefined | null, E> extends AbstractCollectionValidator + implements SetValidator { /** * @param scope - the application configuration @@ -24,12 +24,12 @@ class SetValidatorImpl extends AbstractCollectionValidator, S * @param pluralizer - the type of items in the array * @param context - the contextual information set by a parent validator or the user * @param failures - the list of validation failures - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty * @throws AssertionError if `scope`, `configuration`, `value` `context` or `failures` are null */ public constructor(scope: ApplicationScope, configuration: Configuration, name: string, - value: ValidationTarget>, pluralizer: Pluralizer, context: Map, + value: ValidationTarget, pluralizer: Pluralizer, context: Map, failures: ValidationFailure[]) { super(scope, configuration, name, value, pluralizer, context, failures); @@ -37,8 +37,7 @@ class SetValidatorImpl extends AbstractCollectionValidator, S size(): UnsignedNumberValidator { - if (this.value.isNull()) - this.onNull(); + this.failOnUndefinedOrNull(); return new ObjectSizeValidatorImpl(this.scope, this._configuration, this, this.name + ".size()", this.value.undefinedOrNullToInvalid().map(v => this.getLength(v)), this.pluralizer, this.context, this.failures); diff --git a/src/internal/validator/StringValidatorImpl.mts b/src/internal/validator/StringValidatorImpl.mts index 1840181..5f5c371 100644 --- a/src/internal/validator/StringValidatorImpl.mts +++ b/src/internal/validator/StringValidatorImpl.mts @@ -25,8 +25,8 @@ import { /** * Default implementation of `StringValidator`. */ -class StringValidatorImpl extends AbstractValidator - implements StringValidator +class StringValidatorImpl extends AbstractValidator + implements StringValidator { /** * @param scope - the application configuration @@ -35,143 +35,132 @@ class StringValidatorImpl extends AbstractValidator * @param value - the value * @param context - the contextual information set by a parent validator or the user * @param failures - the list of validation failures - * @throws TypeError if `name` is null + * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace, or is empty * @throws AssertionError if `scope`, `configuration`, `value`, `context` or `failures` are null */ public constructor(scope: ApplicationScope, configuration: Configuration, name: string, - value: ValidationTarget, context: Map, + value: ValidationTarget, context: Map, failures: ValidationFailure[]) { super(scope, configuration, name, value, context, failures); } - isEmpty(): StringValidator + isEmpty(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && v.length === 0)) + if (this.value.validationFailed(v => v.length === 0)) { + this.failOnUndefinedOrNull(); this.addRangeError( objectIsEmpty(this).toString()); } return this; } - isNotEmpty(): StringValidator + isNotEmpty(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && v.length !== 0)) + if (this.value.validationFailed(v => v.length !== 0)) { + this.failOnUndefinedOrNull(); this.addRangeError( objectIsNotEmpty(this).toString()); } return this; } - isTrimmed(): StringValidator + isTrimmed(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && !/^\s|\s$/.test(v))) + if (this.value.validationFailed(v => !/^\s|\s$/.test(v))) { + this.failOnUndefinedOrNull(); this.addRangeError( stringIsTrimmed(this).toString()); } return this; } - startsWith(prefix: string): StringValidator + startsWith(prefix: string): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && v.startsWith(prefix))) + if (this.value.validationFailed(v => v.startsWith(prefix))) { + this.failOnUndefinedOrNull(); this.addRangeError( stringStartsWith(this, prefix).toString()); } return this; } - doesNotStartWith(prefix: string): StringValidator + doesNotStartWith(prefix: string): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && !v.startsWith(prefix))) + if (this.value.validationFailed(v => !v.startsWith(prefix))) { + this.failOnUndefinedOrNull(); this.addRangeError( stringDoesNotStartWith(this, prefix).toString()); } return this; } - endsWith(suffix: string): StringValidator + endsWith(suffix: string): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && v.endsWith(suffix))) + if (this.value.validationFailed(v => v.endsWith(suffix))) { + this.failOnUndefinedOrNull(); this.addRangeError( stringEndsWith(this, suffix).toString()); } return this; } - doesNotEndWith(suffix: string): StringValidator + doesNotEndWith(suffix: string): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && !v.endsWith(suffix))) + if (this.value.validationFailed(v => !v.endsWith(suffix))) { + this.failOnUndefinedOrNull(); this.addRangeError( stringDoesNotEndWith(this, suffix).toString()); } return this; } - contains(expected: string): StringValidator + contains(expected: string): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && v.includes(expected))) + if (this.value.validationFailed(v => v.includes(expected))) { + this.failOnUndefinedOrNull(); this.addRangeError( stringContains(this, expected).toString()); } return this; } - doesNotContain(unwanted: string): StringValidator + doesNotContain(unwanted: string): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && !v.includes(unwanted))) + if (this.value.validationFailed(v => !v.includes(unwanted))) { + this.failOnUndefinedOrNull(); this.addRangeError( stringDoesNotContain(this, unwanted).toString()); } return this; } - doesNotContainWhitespace(): StringValidator + doesNotContainWhitespace(): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && !/\s/.test(v))) + if (this.value.validationFailed(v => !/\s/.test(v))) { + this.failOnUndefinedOrNull(); this.addRangeError( stringDoesNotContainWhitespace(this).toString()); } return this; } - matches(regex: RegExp): StringValidator + matches(regex: RegExp): this { - if (this.value.isNull()) - this.onNull(); - if (this.value.validationFailed(v => v != null && regex.test(v))) + if (this.value.validationFailed(v => regex.test(v))) { + this.failOnUndefinedOrNull(); this.addRangeError( stringMatches(this, regex.source).toString()); } @@ -180,10 +169,9 @@ class StringValidatorImpl extends AbstractValidator length(): UnsignedNumberValidator { - if (this.value.isNull()) - this.onNull(); + this.failOnUndefinedOrNull(); return new ObjectSizeValidatorImpl(this.scope, this._configuration, this, this.name + ".length()", - this.value.undefinedOrNullToInvalid().map(v => v.length), Pluralizer.ELEMENT, this.context, + this.value.undefinedOrNullToInvalid().map(v => v.length), Pluralizer.CHARACTER, this.context, this.failures); } } diff --git a/src/internal/validator/Terminal.mts b/src/internal/validator/Terminal.mts index 81cef2e..8f4a144 100644 --- a/src/internal/validator/Terminal.mts +++ b/src/internal/validator/Terminal.mts @@ -25,7 +25,7 @@ class Terminal this.supportedTypes = new Set(); this.supportedTypes.add(TerminalEncoding.NONE); // https://stackoverflow.com/a/4224668/14731 - if (globalThis.window === undefined) + if (typeof (globalThis.window) === "undefined") { // Node switch (chalk.level) diff --git a/src/internal/validator/ValidationFailureImpl.mts b/src/internal/validator/ValidationFailureImpl.mts index 878698a..6269b31 100644 --- a/src/internal/validator/ValidationFailureImpl.mts +++ b/src/internal/validator/ValidationFailureImpl.mts @@ -75,8 +75,6 @@ class ValidationFailureImpl implements ValidationFailure if (this.error === null) this.error = (this.errorBuilder as ErrorBuilder)(this.message); this.transformedError = this.errorTransformer(this.error); - if (this.transformedError === null) - this.transformedError = this.error; } return this.transformedError; } diff --git a/src/validator/ArrayValidator.mts b/src/validator/ArrayValidator.mts index 67aeb82..a656355 100644 --- a/src/validator/ArrayValidator.mts +++ b/src/validator/ArrayValidator.mts @@ -7,11 +7,11 @@ import type { /** * Validates the state of an array. * + * @typeParam T - the type of the value * @typeParam E - the type of elements in the collection */ -interface ArrayValidator extends - ValidatorComponent, E[]>, - CollectionComponent, E> +interface ArrayValidator extends ValidatorComponent, + CollectionComponent { /** * Returns a validator for the array's length. @@ -31,7 +31,7 @@ interface ArrayValidator extends * @throws RangeError if the collection is not sorted * @returns this */ - isSorted(comparator: (first: unknown, second: unknown) => number): ArrayValidator; + isSorted(comparator: (first: unknown, second: unknown) => number): this; } export type {ArrayValidator}; \ No newline at end of file diff --git a/src/validator/BooleanValidator.mts b/src/validator/BooleanValidator.mts index b39b0a3..f51e7c4 100644 --- a/src/validator/BooleanValidator.mts +++ b/src/validator/BooleanValidator.mts @@ -2,26 +2,28 @@ import type {ValidatorComponent} from "../internal/internal.mjs"; /** * Validates the state of a `boolean`. + * + * @typeParam T - the type of the value */ -interface BooleanValidator extends ValidatorComponent +interface BooleanValidator extends ValidatorComponent { /** * Ensures that the value is `true`. * * @returns this - * @throws TypeError if the value is `undefined` or `null` + * @throws TypeError if the value is `undefined` or `null` * @throws RangeError if the value is `false` */ - isTrue(): BooleanValidator; + isTrue(): this; /** * Ensures that the value is `false`. * * @returns this - * @throws TypeError if the value is `undefined` or `null` + * @throws TypeError if the value is `undefined` or `null` * @throws RangeError if the value is `true` */ - isFalse(): BooleanValidator; + isFalse(): this; } export type {BooleanValidator}; \ No newline at end of file diff --git a/src/validator/ClassValidator.mts b/src/validator/ClassValidator.mts index c126d8f..1893282 100644 --- a/src/validator/ClassValidator.mts +++ b/src/validator/ClassValidator.mts @@ -8,7 +8,7 @@ import type { * * @typeParam T - the type of the class */ -interface ClassValidator extends ValidatorComponent, ClassConstructor> +interface ClassValidator extends ValidatorComponent> { /** * Ensures that the value is a primitive type. diff --git a/src/validator/MapValidator.mts b/src/validator/MapValidator.mts index c9710a9..8d8f958 100644 --- a/src/validator/MapValidator.mts +++ b/src/validator/MapValidator.mts @@ -7,17 +7,18 @@ import type { /** * Validates the state of a `Map`. * + * @typeParam T - the type of the value * @typeParam K - the type of keys in the map * @typeParam V - the type of values in the map */ -interface MapValidator extends ValidatorComponent, Map> +interface MapValidator | undefined | null, K, V> extends ValidatorComponent { /** * Returns a validator for the value's {@link Map.keys|keys}. * @returns a validator for the value's {@link Map.keys|keys} * @throws TypeError if the value is `undefined` or `null` */ - keys(): ArrayValidator; + keys(): ArrayValidator; /** * Returns a validator for the value's {@link Map.values|values}. @@ -25,7 +26,7 @@ interface MapValidator extends ValidatorComponent, Map< * @returns a validator for the value's {@link Map.values|values} * @throws TypeError if the value is `undefined` or `null` */ - values(): ArrayValidator; + values(): ArrayValidator; /** * Returns a validator for the value's {@link Map.entries|entries} @@ -34,7 +35,7 @@ interface MapValidator extends ValidatorComponent, Map< * @returns a validator for the value's {@link Map.entries|entries} * @throws TypeError if the value is `undefined` or `null` */ - entries(): ArrayValidator<[K, V]>; + entries(): ArrayValidator<[K, V][], [K, V]>; /** * Ensures that the value is empty. @@ -43,7 +44,7 @@ interface MapValidator extends ValidatorComponent, Map< * @throws TypeError if the value is `undefined` or `null` * @throws RangeError if value is not empty */ - isEmpty(): MapValidator; + isEmpty(): this; /** * Ensures that the value is not empty. @@ -52,7 +53,7 @@ interface MapValidator extends ValidatorComponent, Map< * @throws TypeError if the value is `undefined` or `null` * @throws RangeError if value is empty */ - isNotEmpty(): MapValidator; + isNotEmpty(): this; /** * Returns a validator for the map's {@link Map.size|size}. diff --git a/src/validator/NumberValidator.mts b/src/validator/NumberValidator.mts index e5dc839..35df969 100644 --- a/src/validator/NumberValidator.mts +++ b/src/validator/NumberValidator.mts @@ -8,12 +8,14 @@ import { /** * Validates the state of a `number`. + * + * @typeParam T - the type of the value */ -interface NumberValidator extends ValidatorComponent, - NumberComponent, - NegativeNumberComponent, - ZeroNumberComponent, - PositiveNumberComponent +interface NumberValidator extends ValidatorComponent, + NumberComponent, + NegativeNumberComponent, + ZeroNumberComponent, + PositiveNumberComponent { } diff --git a/src/validator/ObjectValidator.mts b/src/validator/ObjectValidator.mts index 696b2b8..945b417 100644 --- a/src/validator/ObjectValidator.mts +++ b/src/validator/ObjectValidator.mts @@ -10,6 +10,6 @@ import {type ValidatorComponent} from "../internal/internal.mjs"; * * @typeParam T - the type of the value that is being validated */ -type ObjectValidator = ValidatorComponent, T>; +type ObjectValidator = ValidatorComponent; export type {ObjectValidator}; \ No newline at end of file diff --git a/src/validator/SetValidator.mts b/src/validator/SetValidator.mts index 6d02f79..9f865e7 100644 --- a/src/validator/SetValidator.mts +++ b/src/validator/SetValidator.mts @@ -9,8 +9,9 @@ import type { * * @typeParam E - the type of elements in the set */ -interface SetValidator extends ValidatorComponent, Set>, - CollectionComponent, E> +interface SetValidator | undefined | null, E> extends + ValidatorComponent, + CollectionComponent { /** * Returns a validator for the set's size. diff --git a/src/validator/StringValidator.mts b/src/validator/StringValidator.mts index de98306..65b98cc 100644 --- a/src/validator/StringValidator.mts +++ b/src/validator/StringValidator.mts @@ -6,7 +6,7 @@ import type { /** * Validates the state of a `string`. */ -interface StringValidator extends ValidatorComponent +interface StringValidator extends ValidatorComponent { /** * Ensures that the value starts with some prefix. @@ -16,7 +16,7 @@ interface StringValidator extends ValidatorComponent * @throws TypeError if the value or `prefix` are `undefined` or `null` * @throws RangeError if the value does not start with `prefix` */ - startsWith(prefix: string): StringValidator; + startsWith(prefix: string): this; /** * Ensures that the value does not start with some prefix. @@ -26,7 +26,7 @@ interface StringValidator extends ValidatorComponent * @throws TypeError if the value or `prefix` are `undefined` or `null` * @throws RangeError if the value starts with `prefix` */ - doesNotStartWith(prefix: string): StringValidator; + doesNotStartWith(prefix: string): this; /** * Ensures that the value ends with some suffix. @@ -36,7 +36,7 @@ interface StringValidator extends ValidatorComponent * @throws TypeError if the value or `suffix` are `undefined` or `null` * @throws RangeError if the value does not end with `suffix` */ - endsWith(suffix: string): StringValidator; + endsWith(suffix: string): this; /** * Ensures that the value does not end with some suffix. @@ -46,7 +46,7 @@ interface StringValidator extends ValidatorComponent * @throws TypeError if the value or `suffix` are `undefined` or `null` * @throws RangeError if the value ends with `suffix` */ - doesNotEndWith(suffix: string): StringValidator; + doesNotEndWith(suffix: string): this; /** * Ensures that the value contains some substring. @@ -56,7 +56,7 @@ interface StringValidator extends ValidatorComponent * @throws TypeError if the value or `expected` are `undefined` or `null` * @throws RangeError if the value does not contain `expected` */ - contains(expected: string): StringValidator; + contains(expected: string): this; /** * Ensures that the value does not contain some substring. @@ -66,7 +66,7 @@ interface StringValidator extends ValidatorComponent * @throws TypeError if the value or `unwanted` are `undefined` or `null` * @throws RangeError if the value contains `unwanted` */ - doesNotContain(unwanted: string): StringValidator; + doesNotContain(unwanted: string): this; /** * Ensures that the value does not contain whitespace characters. @@ -75,7 +75,7 @@ interface StringValidator extends ValidatorComponent * @throws NullPointerException if the value is `undefined` or `null` * @throws IllegalArgumentException if the value contains whitespace characters */ - doesNotContainWhitespace(): StringValidator; + doesNotContainWhitespace(): this; /** * Ensures that the value matches a regular expression. @@ -85,7 +85,7 @@ interface StringValidator extends ValidatorComponent * @throws TypeError if the value is `undefined` or `null` * @throws RangeError if the value does not match `regex` */ - matches(regex: RegExp): StringValidator; + matches(regex: RegExp): this; /** * Ensures that the value is empty. @@ -94,7 +94,7 @@ interface StringValidator extends ValidatorComponent * @throws TypeError if the value is `undefined` or `null` * @throws RangeError if the value is not empty */ - isEmpty(): StringValidator; + isEmpty(): this; /** * Ensures that the value is not empty. @@ -103,7 +103,7 @@ interface StringValidator extends ValidatorComponent * @throws TypeError if the value is `undefined` or `null` * @throws RangeError if the value is empty */ - isNotEmpty(): StringValidator; + isNotEmpty(): this; /** * Ensures that the value does not contain leading or trailing whitespace, where whitespace is defined by @@ -113,9 +113,9 @@ interface StringValidator extends ValidatorComponent * @throws TypeError if the value is `undefined` or `null` * @throws RangeError if the value contains leading or trailing whitespace * @see {@link String.trim} - * @see {@link StringValidator.isEmpty} + * @see {@link isEmpty} */ - isTrimmed(): StringValidator; + isTrimmed(): this; /** * Returns a validator for the length of the string. diff --git a/src/validator/UnsignedNumberValidator.mts b/src/validator/UnsignedNumberValidator.mts index fb01e3d..5c2fa12 100644 --- a/src/validator/UnsignedNumberValidator.mts +++ b/src/validator/UnsignedNumberValidator.mts @@ -8,10 +8,10 @@ import type { /** * Validates the state of an unsigned `number`. */ -interface UnsignedNumberValidator extends ValidatorComponent, - NumberComponent, - ZeroNumberComponent, - PositiveNumberComponent +interface UnsignedNumberValidator extends ValidatorComponent, + NumberComponent, + ZeroNumberComponent, + PositiveNumberComponent { } diff --git a/src/validator/component/CollectionComponent.mts b/src/validator/component/CollectionComponent.mts index af9aaa7..7ffc7fc 100644 --- a/src/validator/component/CollectionComponent.mts +++ b/src/validator/component/CollectionComponent.mts @@ -3,18 +3,19 @@ import type { ValidatorComponent } from "../../internal/internal.mjs"; -const typedocWorkaround: null | ValidatorComponent = null; +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ +const typedocWorkaround: null | ValidatorComponent = null; // noinspection PointlessBooleanExpressionJS if (typedocWorkaround !== null) console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); +/* eslint-enable @typescript-eslint/no-unnecessary-condition */ /** * Methods that all collection validators must contain. * - * @typeParam S - the type this validator * @typeParam E - the type of elements in the collection */ -interface CollectionComponent, E> +interface CollectionComponent { /** * Ensures that the collection is empty. @@ -23,7 +24,7 @@ interface CollectionComponent, E> * @throws TypeError if the value is `undefined` or `null` * @throws RangeError if the collection is not empty */ - isEmpty(): S; + isEmpty(): this; /** * Ensures that the collection is not empty. @@ -32,7 +33,7 @@ interface CollectionComponent, E> * @throws TypeError if the value is `undefined` or `null` * @throws RangeError if the collection is empty */ - isNotEmpty(): S; + isNotEmpty(): this; /** * Ensures that the collection contains an element. @@ -42,7 +43,7 @@ interface CollectionComponent, E> * @throws TypeError if the value or `expected` are `undefined` or `null` * @throws RangeError if the collection does not contain `expected` */ - contains(expected: E): S; + contains(expected: E): this; /** * Ensures that the collection does not contain `unwanted`. @@ -52,7 +53,7 @@ interface CollectionComponent, E> * @throws TypeError if the value or `unwanted` are `undefined` or `null` * @throws RangeError if the collection contains `unwanted` */ - doesNotContain(unwanted: E): S; + doesNotContain(unwanted: E): this; /** * Ensures that the collection contains an element. @@ -72,7 +73,7 @@ interface CollectionComponent, E> */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - contains(expected: E, name: string): S; + contains(expected: E, name: string): this; /** * Ensures that the collection does not contain `unwanted`. @@ -92,7 +93,7 @@ interface CollectionComponent, E> */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - doesNotContain(unwanted: E, name: string): S; + doesNotContain(unwanted: E, name: string): this; /** * Ensures that the collection consists of the same elements as `expected`, irrespective of their @@ -109,7 +110,7 @@ interface CollectionComponent, E> *

                      • the collection contains any element that is not in `expected`
                      • *
                      */ - containsExactly(expected: Set): S; + containsExactly(expected: Set): this; /** * Ensures that the collection and `unwanted` consist of different elements, irrespective of their @@ -121,7 +122,7 @@ interface CollectionComponent, E> * @throws RangeError if the collection consists of the same elements as `unwanted`, * irrespective of their order */ - doesNotContainExactly(unwanted: Set): S; + doesNotContainExactly(unwanted: Set): this; /** * Ensures that the collection consists of the same elements as `expected`, irrespective of @@ -139,7 +140,7 @@ interface CollectionComponent, E> * `expected` *
                    */ - containsExactly(expected: E[]): S; + containsExactly(expected: E[]): this; /** * Ensures that the collection and `unwanted` consist of different elements, irrespective of their @@ -151,7 +152,7 @@ interface CollectionComponent, E> * @throws RangeError if the collection consists of the same elements as `unwanted`, * irrespective of their order */ - doesNotContainExactly(unwanted: E[]): S; + doesNotContainExactly(unwanted: E[]): this; /** * Ensures that the collection consists of the same elements as `expected`, irrespective of their @@ -175,7 +176,7 @@ interface CollectionComponent, E> */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - containsExactly(expected: Set, name: string): S; + containsExactly(expected: Set, name: string): this; /** * Ensures that the collection and `unwanted` consist of different elements, irrespective of @@ -197,7 +198,7 @@ interface CollectionComponent, E> */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - doesNotContainExactly(unwanted: Set, name: string): S; + doesNotContainExactly(unwanted: Set, name: string): this; /** * Ensures that the collection consists of the same elements as `expected`, irrespective of @@ -221,7 +222,7 @@ interface CollectionComponent, E> */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - containsExactly(expected: E[], name: string): S; + containsExactly(expected: E[], name: string): this; /** * Ensures that the collection and `unwanted` consist of different elements, irrespective of their @@ -243,7 +244,7 @@ interface CollectionComponent, E> */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - doesNotContainExactly(unwanted: E[], name: string): S; + doesNotContainExactly(unwanted: E[], name: string): this; /** * Ensures that the collection contains any elements in `expected`. @@ -253,7 +254,7 @@ interface CollectionComponent, E> * @throws TypeError if the value or `expected` are `undefined` or `null` * @throws RangeError if the collection does not contain any element in `expected` */ - containsAny(expected: Set): S; + containsAny(expected: Set): this; /** * Ensures that the collection does not contain any of the elements in `unwanted`. @@ -263,7 +264,7 @@ interface CollectionComponent, E> * @throws TypeError if the value or `unwanted` are `undefined` or `null` * @throws RangeError if the collection contains any of the elements in `unwanted` */ - doesNotContainAny(unwanted: Set): S; + doesNotContainAny(unwanted: Set): this; /** * Ensures that the collection contains any elements in `expected`. @@ -273,7 +274,7 @@ interface CollectionComponent, E> * @throws TypeError if the value or `expected` are `undefined` or `null` * @throws RangeError if the collection does not contain any element in `expected` */ - containsAny(expected: E[]): S; + containsAny(expected: E[]): this; /** * Ensures that the collection does not contain any of the elements in `unwanted`. @@ -283,7 +284,7 @@ interface CollectionComponent, E> * @throws TypeError if the value or `unwanted` are `undefined` or `null` * @throws RangeError if the collection contains any of the elements in `unwanted` */ - doesNotContainAny(unwanted: E[]): S; + doesNotContainAny(unwanted: E[]): this; /** * Ensures that the collection contains at least one element in `expected`. @@ -303,7 +304,7 @@ interface CollectionComponent, E> */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - containsAny(expected: Set, name: string): S; + containsAny(expected: Set, name: string): this; /** * Ensures that the collection does not contain any of the elements in `unwanted`. @@ -323,7 +324,7 @@ interface CollectionComponent, E> */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - doesNotContainAny(unwanted: Set, name: string): S; + doesNotContainAny(unwanted: Set, name: string): this; /** * Ensures that the collection contains at least one element in `expected`. @@ -343,7 +344,7 @@ interface CollectionComponent, E> */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - containsAny(expected: E[], name: string): S; + containsAny(expected: E[], name: string): this; /** * Ensures that the collection does not contain any of the elements in `unwanted`. @@ -363,7 +364,7 @@ interface CollectionComponent, E> */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - doesNotContainAny(unwanted: E[], name: string): S; + doesNotContainAny(unwanted: E[], name: string): this; /** * Ensures that the collection contains all the elements in `expected`. @@ -373,7 +374,7 @@ interface CollectionComponent, E> * @throws TypeError if the value or `expected` are `undefined` or `null` * @throws RangeError if the collection does not contain all the elements in `expected` */ - containsAll(expected: Set): S; + containsAll(expected: Set): this; /** * Allows the collection to contain some, but not all, elements from a collection. @@ -383,7 +384,7 @@ interface CollectionComponent, E> * @throws TypeError if the value or `unwanted` are `undefined` or `null` * @throws RangeError if the collection contains all the elements of `unwanted` */ - doesNotContainAll(unwanted: Set): S; + doesNotContainAll(unwanted: Set): this; /** * Ensures that the collection contains all the elements in `expected`. @@ -393,7 +394,7 @@ interface CollectionComponent, E> * @throws TypeError if the value or `expected` are `undefined` or `null` * @throws RangeError if the collection does not contain all the elements in `expected` */ - containsAll(expected: E[]): S; + containsAll(expected: E[]): this; /** * Allows the collection to contain some, but not all, elements from a collection. @@ -403,7 +404,7 @@ interface CollectionComponent, E> * @throws TypeError if the value or `unwanted` are `undefined` or `null` * @throws RangeError if the collection contains all the elements of `unwanted` */ - doesNotContainAll(unwanted: E[]): S; + doesNotContainAll(unwanted: E[]): this; /** * Ensures that the collection contains all the elements in `expected`. @@ -423,7 +424,7 @@ interface CollectionComponent, E> */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - containsAll(expected: Set, name: string): S; + containsAll(expected: Set, name: string): this; /** * Allows the collection to contain some, but not all, elements from a collection. @@ -443,7 +444,7 @@ interface CollectionComponent, E> */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - doesNotContainAll(unwanted: Set, name: string): S; + doesNotContainAll(unwanted: Set, name: string): this; /** * Ensures that the collection contains all the elements in `expected`. @@ -463,7 +464,7 @@ interface CollectionComponent, E> */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - containsAll(expected: E[], name: string): S; + containsAll(expected: E[], name: string): this; /** * Allows the collection to contain some, but not all, elements from a collection. @@ -483,16 +484,16 @@ interface CollectionComponent, E> */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - doesNotContainAll(unwanted: E[], name: string): S; + doesNotContainAll(unwanted: E[], name: string): this; /** - * Ensures that the collection contains only null values, or only non-null values. + * Ensures that the collection contains only `null` values or only non-`null` values. * * @returns this * @throws TypeError if the value is `undefined` or `null` * @throws RangeError if the collection contains `undefined` or a mix of `null` and non-`null` values */ - containsSameNullity(): S; + containsSameNullity(): this; /** * Ensures that the collection does not contain any duplicate elements. @@ -501,7 +502,7 @@ interface CollectionComponent, E> * @throws TypeError if the value is `undefined` or `null` * @throws RangeError if the collection contains any duplicate elements */ - doesNotContainDuplicates(): S; + doesNotContainDuplicates(): this; /** * Returns a validator for the collection's size. diff --git a/src/validator/component/NegativeNumberComponent.mts b/src/validator/component/NegativeNumberComponent.mts index fb90496..5ee938a 100644 --- a/src/validator/component/NegativeNumberComponent.mts +++ b/src/validator/component/NegativeNumberComponent.mts @@ -5,10 +5,8 @@ /** * Methods that validators for numbers that may be negative must contain. - * - * @typeParam S - the type of this validator */ -interface NegativeNumberComponent +interface NegativeNumberComponent { /** * Ensures that the value is negative. `-0.0` is considered to be zero *and* negative. @@ -21,7 +19,7 @@ interface NegativeNumberComponent *
                  * @returns this */ - isNegative(): S; + isNegative(): this; /** * Ensures that the value is not a negative number. `-0.0` is considered to be zero *and* negative. @@ -30,7 +28,7 @@ interface NegativeNumberComponent * @throws RangeError if the value is a negative number * @returns this */ - isNotNegative(): S; + isNotNegative(): this; } export type {NegativeNumberComponent}; \ No newline at end of file diff --git a/src/validator/component/NumberComponent.mts b/src/validator/component/NumberComponent.mts index 77cb2a9..4e54855 100644 --- a/src/validator/component/NumberComponent.mts +++ b/src/validator/component/NumberComponent.mts @@ -6,9 +6,9 @@ /** * Methods that all number validators must contain. * - * @typeParam S - the type of this validator + * @typeParam T - the type of the value */ -interface NumberComponent +interface NumberComponent { /** * Returns the value that is being validated. @@ -16,7 +16,7 @@ interface NumberComponent * @returns the validated value * @throws IllegalStateError if a previous validation failed */ - getValue(): number; + getValue(): T; /** * Ensures that the value is a multiple of `factor`. @@ -26,7 +26,7 @@ interface NumberComponent * @throws TypeError if the value or `factor` are `undefined` or `null` * @throws RangeError if the value is not a multiple of `factor` */ - isMultipleOf(factor: number): S; + isMultipleOf(factor: number): this; /** * Ensures that the value is a multiple of `factor`. @@ -40,7 +40,7 @@ interface NumberComponent */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - isMultipleOf(factor: number, name: string): S; + isMultipleOf(factor: number, name: string): this; /** * Ensures that the value is not a multiple of `factor`. @@ -50,7 +50,7 @@ interface NumberComponent * @throws TypeError if the value or `factor` are `undefined` or `null` * @throws RangeError if the value is a multiple of `factor` */ - isNotMultipleOf(factor: number): S; + isNotMultipleOf(factor: number): this; /** * Ensures that the value is not a multiple of `factor`. @@ -64,7 +64,7 @@ interface NumberComponent */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - isNotMultipleOf(factor: number, name: string): S; + isNotMultipleOf(factor: number, name: string): this; /** * Ensures that the value is less than an upper bound. @@ -74,7 +74,7 @@ interface NumberComponent * @throws TypeError if the value or `maximumExclusive` are `undefined` or `null` * @throws RangeError if the value is greater than or equal to `maximumExclusive` */ - isLessThan(maximumExclusive: number): S; + isLessThan(maximumExclusive: number): this; /** * Ensures that the value is less than an upper bound. @@ -88,7 +88,7 @@ interface NumberComponent */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - isLessThan(maximumExclusive: number, name: string): S; + isLessThan(maximumExclusive: number, name: string): this; /** * Ensures that the value is less than or equal to a maximum value. @@ -98,7 +98,7 @@ interface NumberComponent * @throws TypeError if the value or `maximumInclusive` are `undefined` or `null` * @throws RangeError if the value is greater than `maximumInclusive` */ - isLessThanOrEqualTo(maximumInclusive: number): S; + isLessThanOrEqualTo(maximumInclusive: number): this; /** * Ensures that the value is less than or equal to a maximum value. @@ -112,7 +112,7 @@ interface NumberComponent */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - isLessThanOrEqualTo(maximumInclusive: number, name: string): S; + isLessThanOrEqualTo(maximumInclusive: number, name: string): this; /** * Ensures that the value is greater than or equal to a minimum value. @@ -122,7 +122,7 @@ interface NumberComponent * @throws TypeError if the value or `minimumInclusive` are `undefined` or `null` * @throws RangeError if the value is less than `minimumInclusive` */ - isGreaterThanOrEqualTo(minimumInclusive: number): S; + isGreaterThanOrEqualTo(minimumInclusive: number): this; /** * Ensures that the value is greater than or equal a minimum value. @@ -136,7 +136,7 @@ interface NumberComponent */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - isGreaterThanOrEqualTo(minimumInclusive: number, name: string): S; + isGreaterThanOrEqualTo(minimumInclusive: number, name: string): this; /** * Ensures that the value is greater than a lower bound. @@ -146,7 +146,7 @@ interface NumberComponent * @throws TypeError if the value or `minimumExclusive` are `undefined` or `null` * @throws RangeError if the value is less than `minimumExclusive` */ - isGreaterThan(minimumExclusive: number): S; + isGreaterThan(minimumExclusive: number): this; /** * Ensures that the value is greater than a lower bound. @@ -160,7 +160,7 @@ interface NumberComponent */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - isGreaterThan(minimumExclusive: number, name: string): S; + isGreaterThan(minimumExclusive: number, name: string): this; /** * Ensures that the value is within a range. @@ -172,7 +172,7 @@ interface NumberComponent * @throws RangeError if `minimumInclusive` is greater than `maximumExclusive`. If the value is less than * `minimumInclusive`. If the value is greater than or equal to `maximumExclusive`. */ - isBetween(minimumInclusive: number, maximumExclusive: number): S; + isBetween(minimumInclusive: number, maximumExclusive: number): this; /** * Ensures that the value is within a range. @@ -190,7 +190,7 @@ interface NumberComponent * than `maximum`. If `maximumInclusive` is `false`, and the value is * greater than or equal to `maximum`. */ - isBetween(minimum: number, minimumInclusive: boolean, maximum: number, maximumInclusive: boolean): S; + isBetween(minimum: number, minimumInclusive: boolean, maximum: number, maximumInclusive: boolean): this; /** * Ensures that the value is a finite number. @@ -201,7 +201,7 @@ interface NumberComponent * @see {@link isNumber} * @see {@link Number.isFinite} */ - isFinite(): S; + isFinite(): this; /** * Ensures that the value is an infinite number. @@ -212,7 +212,7 @@ interface NumberComponent * @see {@link isNumber} * @see {@link Number.isFinite} */ - isInfinite(): S; + isInfinite(): this; /** * Ensures that the value is a well-defined number. @@ -222,7 +222,7 @@ interface NumberComponent * @throws RangeError if value is not a well-defined number * @see #isNotNumber() */ - isNumber(): S; + isNumber(): this; /** * Ensures that the value is the result of a mathematically undefined numerical operation (such as division @@ -232,7 +232,7 @@ interface NumberComponent * @throws NullPointerError if the value is `undefined` or `null` * @throws RangeError if value is a well-defined number */ - isNotNumber(): S; + isNotNumber(): this; } export type {NumberComponent}; \ No newline at end of file diff --git a/src/validator/component/PositiveNumberComponent.mts b/src/validator/component/PositiveNumberComponent.mts index 34f7b66..74a8fc3 100644 --- a/src/validator/component/PositiveNumberComponent.mts +++ b/src/validator/component/PositiveNumberComponent.mts @@ -5,10 +5,8 @@ /** * Methods that validators for numbers that may be positive must contain. - * - * @typeParam S - the type of this validator */ -interface PositiveNumberComponent +interface PositiveNumberComponent { /** * Ensures that the value is positive. @@ -21,7 +19,7 @@ interface PositiveNumberComponent *
                * @returns this */ - isPositive(): S; + isPositive(): this; /** * Ensures that the value is not a positive number. @@ -30,7 +28,7 @@ interface PositiveNumberComponent * @throws RangeError if the value is a positive number * @returns this */ - isNotPositive(): S; + isNotPositive(): this; } export type {PositiveNumberComponent}; \ No newline at end of file diff --git a/src/validator/component/ValidatorComponent.mts b/src/validator/component/ValidatorComponent.mts index 9c0003d..7d47e93 100644 --- a/src/validator/component/ValidatorComponent.mts +++ b/src/validator/component/ValidatorComponent.mts @@ -3,16 +3,24 @@ import { ValidationFailures, type NonUndefinable, type ClassConstructor, - type ObjectValidator + type ObjectValidator, + type Validators } from "../../internal/internal.mjs"; +const typedocWorkaround: null | Validators = null; +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ +// noinspection PointlessBooleanExpressionJS +if (typedocWorkaround !== null) + console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); + +/* eslint-enable @typescript-eslint/no-unnecessary-condition */ + /** * Methods that all validators must contain. * - * @typeParam S - the type of validator * @typeParam T - the type of the value */ -interface ValidatorComponent +interface ValidatorComponent { /** * Returns the name of the value. @@ -77,10 +85,10 @@ interface ValidatorComponent * context *
              */ - withContext(value: unknown, name: string): S; + withContext(value: unknown, name: string): this; /** - * Facilitates the validation of related properties. For example: + * Facilitates the validation of related properties. For example, *

              * ```ts * requireThat(nameToFrequency, "nameToFrequency"). @@ -94,7 +102,7 @@ interface ValidatorComponent * @returns this * @throws TypeError if `validation` is `undefined` or `null` */ - and(validation: (validator: S) => void): S; + and(validation: (validator: this) => void): this; /** * Checks if any validation has failed. @@ -187,7 +195,7 @@ interface ValidatorComponent * @returns this * @throws TypeError if the value is not an instance of the specified type */ - isType(expected: Type): S; + isType(expected: Type): this; /** * Ensures that the object is not an instance of a class. @@ -197,7 +205,7 @@ interface ValidatorComponent * @throws TypeError if the value or `unwanted` are `undefined` or `null` * @throws RangeError if the value is an instance of the unwanted class */ - isNotInstanceOf(unwanted: ClassConstructor): ObjectValidator; + isNotInstanceOf(unwanted: ClassConstructor): this; /** * Ensures that the object is equal to `expected`. @@ -208,7 +216,7 @@ interface ValidatorComponent * @see An * explanation of the output format */ - isEqualTo(expected: unknown): S; + isEqualTo(expected: unknown): this; /** * Ensures that the object is equal to `expected`. @@ -230,7 +238,7 @@ interface ValidatorComponent */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - isEqualTo(expected: unknown, name: string): S; + isEqualTo(expected: unknown, name: string): this; /** * Ensures that the object is not equal to `unwanted`. @@ -241,7 +249,7 @@ interface ValidatorComponent * @see An * explanation of the output format */ - isNotEqualTo(unwanted: unknown): S; + isNotEqualTo(unwanted: unknown): this; /** * Ensures that the object is not equal to `unwanted`. @@ -263,7 +271,7 @@ interface ValidatorComponent */ // Retain separate methods because their documentation is different. // eslint-disable-next-line @typescript-eslint/unified-signatures - isNotEqualTo(unwanted: unknown, name: string): S; + isNotEqualTo(unwanted: unknown, name: string): this; } export type {ValidatorComponent}; \ No newline at end of file diff --git a/src/validator/component/ZeroNumberComponent.mts b/src/validator/component/ZeroNumberComponent.mts index 3dd6196..b72e6d8 100644 --- a/src/validator/component/ZeroNumberComponent.mts +++ b/src/validator/component/ZeroNumberComponent.mts @@ -5,10 +5,8 @@ /** * Methods that validators for numbers that may be zero must contain. - * - * @typeParam S - the type of this validator */ -interface ZeroNumberComponent +interface ZeroNumberComponent { /** * Ensures that the value is zero. `-0.0` is considered to be zero *and* negative. @@ -21,7 +19,7 @@ interface ZeroNumberComponent *

            * @returns this */ - isZero(): S; + isZero(): this; /** * Ensures that the value is not zero. `-0.0` is considered to be zero *and* negative. @@ -30,7 +28,7 @@ interface ZeroNumberComponent * @throws RangeError if the value is zero * @returns this */ - isNotZero(): S; + isNotZero(): this; } export type {ZeroNumberComponent}; \ No newline at end of file diff --git a/test/ArrayTest.mts b/test/ArrayTest.mts index db42a4e..bbf95cb 100644 --- a/test/ArrayTest.mts +++ b/test/ArrayTest.mts @@ -22,7 +22,7 @@ suite("ArrayTest", () => test("isEmpty", () => { const actual: unknown[] = []; - validators.requireThat(actual, "actual").isEmpty(); + validators.requireThatArray(actual, "actual").isEmpty(); }); test("isEmpty_actualContainsOneElement", () => @@ -30,14 +30,14 @@ suite("ArrayTest", () => assert.throws(function() { const actual = ["element"]; - validators.requireThat(actual, "actual").isEmpty(); + validators.requireThatArray(actual, "actual").isEmpty(); }, RangeError); }); test("isNotEmpty", () => { const actual = ["element"]; - validators.requireThat(actual, "actual").isNotEmpty(); + validators.requireThatArray(actual, "actual").isNotEmpty(); }); test("isNotEmpty_False", () => @@ -45,14 +45,14 @@ suite("ArrayTest", () => assert.throws(function() { const actual: unknown[] = []; - validators.requireThat(actual, "actual").isNotEmpty(); + validators.requireThatArray(actual, "actual").isNotEmpty(); }, RangeError); }); test("contains", () => { const actual = ["element"]; - validators.requireThat(actual, "actual").contains("element"); + validators.requireThatArray(actual, "actual").contains("element"); }); test("contains_False", () => @@ -60,14 +60,14 @@ suite("ArrayTest", () => assert.throws(function() { const actual = ["notElement"]; - validators.requireThat(actual, "actual").contains("element"); + validators.requireThatArray(actual, "actual").contains("element"); }, RangeError); }); test("containsVariable", () => { const actual = ["element"]; - validators.requireThat(actual, "actual").contains("element", "nameOfExpected"); + validators.requireThatArray(actual, "actual").contains("element", "nameOfExpected"); }); test("containsVariable_False", () => @@ -75,7 +75,7 @@ suite("ArrayTest", () => assert.throws(function() { const actual = ["notElement"]; - validators.requireThat(actual, "actual").contains("element", "nameOfExpected"); + validators.requireThatArray(actual, "actual").contains("element", "nameOfExpected"); }, RangeError); }); @@ -84,7 +84,7 @@ suite("ArrayTest", () => assert.throws(function() { const actual = ["element"]; - validators.requireThat(actual, "actual").contains(" "); + validators.requireThatArray(actual, "actual").contains(" "); }, RangeError); }); @@ -96,7 +96,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").containsExactly(["one", "two", "three"]); + validators.requireThatArray(actual, "actual").containsExactly(["one", "two", "three"]); }); test("containsExactly_actualContainsUnwantedElements", () => @@ -109,7 +109,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").containsExactly(["one", "two"]); + validators.requireThatArray(actual, "actual").containsExactly(["one", "two"]); }, RangeError); }); @@ -122,7 +122,7 @@ suite("ArrayTest", () => "one", "two" ]; - validators.requireThat(actual, "actual").containsExactly(["one", "two", "three"]); + validators.requireThatArray(actual, "actual").containsExactly(["one", "two", "three"]); }, RangeError); }); @@ -135,7 +135,7 @@ suite("ArrayTest", () => "one", "two" ]; - validators.requireThat(actual, "actual").containsExactly(["one", "two", "three"], "expected"); + validators.requireThatArray(actual, "actual").containsExactly(["one", "two", "three"], "expected"); }, RangeError); }); @@ -147,7 +147,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").containsExactly(["one", "two", "three"], "nameOfExpected"); + validators.requireThatArray(actual, "actual").containsExactly(["one", "two", "three"], "nameOfExpected"); }); test("containsExactlyVariable_False", () => @@ -160,7 +160,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").containsExactly(["one", "two"], "nameOfExpected"); + validators.requireThatArray(actual, "actual").containsExactly(["one", "two"], "nameOfExpected"); }, RangeError); }); @@ -174,7 +174,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").containsExactly(["one", "two", "three"], " "); + validators.requireThatArray(actual, "actual").containsExactly(["one", "two", "three"], " "); }, RangeError); }); @@ -186,7 +186,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").containsAny(["two", "four"]); + validators.requireThatArray(actual, "actual").containsAny(["two", "four"]); }); test("containsAny_False", () => @@ -199,7 +199,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").containsAny(["four", "five"]); + validators.requireThatArray(actual, "actual").containsAny(["four", "five"]); }, RangeError); }); @@ -211,7 +211,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").containsAny(["two", "four"], "nameOfExpected"); + validators.requireThatArray(actual, "actual").containsAny(["two", "four"], "nameOfExpected"); }); test("containsAnyVariable_False", () => @@ -224,7 +224,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").containsAny(["four", "five"], "nameOfExpected"); + validators.requireThatArray(actual, "actual").containsAny(["four", "five"], "nameOfExpected"); }, RangeError); }); @@ -238,7 +238,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").containsAny(["two", "four"], " "); + validators.requireThatArray(actual, "actual").containsAny(["two", "four"], " "); }, RangeError); }); @@ -250,7 +250,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").containsAll(["two", "three"]); + validators.requireThatArray(actual, "actual").containsAll(["two", "three"]); }); test("containsAll_False", () => @@ -263,7 +263,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").containsAll(["two", "four"]); + validators.requireThatArray(actual, "actual").containsAll(["two", "four"]); }, RangeError); }); @@ -275,7 +275,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").containsAll(["two", "three"], "nameOfExpected"); + validators.requireThatArray(actual, "actual").containsAll(["two", "three"], "nameOfExpected"); }); test("containsAllVariable_False", () => @@ -288,7 +288,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").containsAll(["two", "four"], "nameOfExpected"); + validators.requireThatArray(actual, "actual").containsAll(["two", "four"], "nameOfExpected"); }, RangeError); }); @@ -302,7 +302,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").containsAll(["two", "three"], " "); + validators.requireThatArray(actual, "actual").containsAll(["two", "three"], " "); }, RangeError); }); @@ -312,7 +312,7 @@ suite("ArrayTest", () => [ "notElement" ]; - validators.requireThat(actual, "actual").doesNotContain("element"); + validators.requireThatArray(actual, "actual").doesNotContain("element"); }); test("doesNotContain_False", () => @@ -323,7 +323,7 @@ suite("ArrayTest", () => [ "element" ]; - validators.requireThat(actual, "actual").doesNotContain("element"); + validators.requireThatArray(actual, "actual").doesNotContain("element"); }, RangeError); }); @@ -333,7 +333,7 @@ suite("ArrayTest", () => [ "notElement" ]; - validators.requireThat(actual, "actual").doesNotContain("element", "nameOfExpected"); + validators.requireThatArray(actual, "actual").doesNotContain("element", "nameOfExpected"); }); test("doesNotContainVariable_False", () => @@ -344,7 +344,7 @@ suite("ArrayTest", () => [ "element" ]; - validators.requireThat(actual, "actual").doesNotContain("element", "nameOfExpected"); + validators.requireThatArray(actual, "actual").doesNotContain("element", "nameOfExpected"); }, RangeError); }); @@ -356,7 +356,7 @@ suite("ArrayTest", () => [ "notElement" ]; - validators.requireThat(actual, "actual").doesNotContain("element", " "); + validators.requireThatArray(actual, "actual").doesNotContain("element", " "); }, RangeError); }); @@ -368,7 +368,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").doesNotContainAny(["four", "five", "six"]); + validators.requireThatArray(actual, "actual").doesNotContainAny(["four", "five", "six"]); }); test("doesNotContainAny_False", () => @@ -381,7 +381,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").doesNotContainAny(["three", "four", "five"]); + validators.requireThatArray(actual, "actual").doesNotContainAny(["three", "four", "five"]); }, RangeError); }); @@ -393,7 +393,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual"). + validators.requireThatArray(actual, "actual"). doesNotContainAny(["four", "five", "six"], "nameOfExpected"); }); @@ -407,7 +407,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual"). + validators.requireThatArray(actual, "actual"). doesNotContainAny(["three", "four", "five"], "nameOfExpected"); }, RangeError); }); @@ -422,7 +422,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").doesNotContainAny(["four", "five", "six"], " "); + validators.requireThatArray(actual, "actual").doesNotContainAny(["four", "five", "six"], " "); }, RangeError); }); @@ -434,7 +434,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").doesNotContainAll(["one", "two", "four"]); + validators.requireThatArray(actual, "actual").doesNotContainAll(["one", "two", "four"]); }); test("doesNotContainAll_False", () => @@ -448,7 +448,7 @@ suite("ArrayTest", () => "three", "four" ]; - validators.requireThat(actual, "actual").doesNotContainAll(["one", "two", "three"]); + validators.requireThatArray(actual, "actual").doesNotContainAll(["one", "two", "three"]); }, RangeError); }); @@ -460,7 +460,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").doesNotContainAll(["one", "two", "four"], "nameOfExpected"); + validators.requireThatArray(actual, "actual").doesNotContainAll(["one", "two", "four"], "nameOfExpected"); }); test("doesNotContainAllVariable_False", () => @@ -474,7 +474,7 @@ suite("ArrayTest", () => "three", "four" ]; - validators.requireThat(actual, "actual").doesNotContainAll(["one", "two", "three"], + validators.requireThatArray(actual, "actual").doesNotContainAll(["one", "two", "three"], "nameOfExpected"); }, RangeError); }); @@ -489,7 +489,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").doesNotContainAll(["one", "two", "four"], " "); + validators.requireThatArray(actual, "actual").doesNotContainAll(["one", "two", "four"], " "); }, RangeError); }); @@ -501,7 +501,7 @@ suite("ArrayTest", () => "two", "three" ]; - validators.requireThat(actual, "actual").doesNotContainDuplicates(); + validators.requireThatArray(actual, "actual").doesNotContainDuplicates(); }); test("doesNotContainDuplicates_False", () => @@ -516,7 +516,7 @@ suite("ArrayTest", () => "two", "four" ]; - validators.requireThat(actual, "actual").doesNotContainDuplicates(); + validators.requireThatArray(actual, "actual").doesNotContainDuplicates(); }, RangeError); }); @@ -526,7 +526,7 @@ suite("ArrayTest", () => [ "element" ]; - validators.requireThat(actual, "actual").length().isEqualTo(1); + validators.requireThatArray(actual, "actual").length().isEqualTo(1); }); test("lengthNestedValidatorIsEqualTo", () => @@ -535,7 +535,7 @@ suite("ArrayTest", () => [ "element" ]; - validators.requireThat(actual, "actual").and(v => v.length().isEqualTo(1)); + validators.requireThatArray(actual, "actual").and(v => v.length().isEqualTo(1)); }); test("lengthNestedValidatorIsEqualTo_False", () => @@ -546,7 +546,7 @@ suite("ArrayTest", () => [ "element" ]; - validators.requireThat(actual, "actual").and(v => v.length().isEqualTo(2)); + validators.requireThatArray(actual, "actual").and(v => v.length().isEqualTo(2)); }, RangeError); }); @@ -558,7 +558,7 @@ suite("ArrayTest", () => [ "element" ]; - validators.requireThat(actual, "actual").length().isEqualTo(2); + validators.requireThatArray(actual, "actual").length().isEqualTo(2); }, RangeError); }); @@ -568,7 +568,7 @@ suite("ArrayTest", () => [ "element" ]; - validators.requireThat(actual, "actual").length().isEqualTo(1, "nameOfExpected"); + validators.requireThatArray(actual, "actual").length().isEqualTo(1, "nameOfExpected"); }); test("lengthIsEqualToVariable_False", () => @@ -579,7 +579,7 @@ suite("ArrayTest", () => [ "element" ]; - validators.requireThat(actual, "actual").length().isEqualTo(2, "nameOfExpected"); + validators.requireThatArray(actual, "actual").length().isEqualTo(2, "nameOfExpected"); }, RangeError); }); @@ -591,7 +591,7 @@ suite("ArrayTest", () => [ "element" ]; - validators.requireThat(actual, "actual").length().isEqualTo(1, " "); + validators.requireThatArray(actual, "actual").length().isEqualTo(1, " "); }, RangeError); }); @@ -601,7 +601,7 @@ suite("ArrayTest", () => [ "element" ]; - validators.requireThat(actual, "actual").length().isNotEqualTo(2); + validators.requireThatArray(actual, "actual").length().isNotEqualTo(2); }); test("lengthIsNotEqualTo_False", () => @@ -612,7 +612,7 @@ suite("ArrayTest", () => [ "element" ]; - validators.requireThat(actual, "actual").length().isNotEqualTo(1); + validators.requireThatArray(actual, "actual").length().isNotEqualTo(1); }, RangeError); }); @@ -622,7 +622,7 @@ suite("ArrayTest", () => [ "element" ]; - validators.requireThat(actual, "actual").length().isNotEqualTo(2, "nameOfExpected"); + validators.requireThatArray(actual, "actual").length().isNotEqualTo(2, "nameOfExpected"); }); test("lengthIsNotEqualToVariable_False", () => @@ -633,7 +633,7 @@ suite("ArrayTest", () => [ "element" ]; - validators.requireThat(actual, "actual").length().isNotEqualTo(1, "nameOfExpected"); + validators.requireThatArray(actual, "actual").length().isNotEqualTo(1, "nameOfExpected"); }, RangeError); }); @@ -645,7 +645,7 @@ suite("ArrayTest", () => [ "element" ]; - validators.requireThat(actual, "actual").length().isNotEqualTo(2, " "); + validators.requireThatArray(actual, "actual").length().isNotEqualTo(2, " "); }, RangeError); }); @@ -657,7 +657,7 @@ suite("ArrayTest", () => 2, 3 ]; - validators.requireThat(actual, "actual").length().isBetween(3, 5); + validators.requireThatArray(actual, "actual").length().isBetween(3, 5); }); test("isBetween_expectedIsInBounds", () => @@ -669,7 +669,7 @@ suite("ArrayTest", () => 3, 4 ]; - validators.requireThat(actual, "actual").length().isBetween(3, 5); + validators.requireThatArray(actual, "actual").length().isBetween(3, 5); }); test("isBetween_expectedIsUpperBound", () => @@ -684,7 +684,7 @@ suite("ArrayTest", () => 4, 5 ]; - validators.requireThat(actual, "actual").length().isBetween(3, 5); + validators.requireThatArray(actual, "actual").length().isBetween(3, 5); }, RangeError); }); @@ -697,7 +697,7 @@ suite("ArrayTest", () => 1, 2 ]; - validators.requireThat(actual, "actual").length().isBetween(3, 5); + validators.requireThatArray(actual, "actual").length().isBetween(3, 5); }, RangeError); }); @@ -711,14 +711,14 @@ suite("ArrayTest", () => 4, 5 ]; - validators.requireThat(actual, "actual").length().isBetween(3, true, 5, true); + validators.requireThatArray(actual, "actual").length().isBetween(3, true, 5, true); }); test("isString", () => { const actual = [1, 2, 3]; - const actualIsString = actual.toString(); - validators.requireThat(actualIsString, "actual").isEqualTo("1,2,3"); + const actualAsString = actual.toString(); + validators.requireThatString(actualAsString, "actual").isEqualTo("1,2,3"); }); test("getActual", () => @@ -731,7 +731,7 @@ suite("ArrayTest", () => 4, 5 ]; - const output = validators.requireThat(input, "input").getValue(); + const output = validators.requireThatArray(input, "input").getValue(); assert.strictEqual(output, input); }); @@ -745,7 +745,7 @@ suite("ArrayTest", () => 4, 5 ]; - const output = validators.requireThat(input, "input").getValue(); + const output = validators.requireThatArray(input, "input").getValue(); assert.strictEqual(output, input); }); @@ -754,8 +754,10 @@ suite("ArrayTest", () => const actual = null; const expectedMessages = [`"actual" must be an array. actual: null`]; - const actualFailures = validators.checkIf(actual as unknown, "actual").isType(Type.ARRAY).elseGetFailures(); + const actualFailures = validators.checkIfArray(actual as unknown as unknown[], "actual"). + isType(Type.ARRAY).elseGetFailures(); const actualMessages = actualFailures.getMessages(); - validators.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages, "expectedMessages"); + validators.requireThatArray(actualMessages, "actualMessages"). + isEqualTo(expectedMessages, "expectedMessages"); }); }); \ No newline at end of file diff --git a/test/BooleanTest.mts b/test/BooleanTest.mts index de5fce8..6f3f554 100644 --- a/test/BooleanTest.mts +++ b/test/BooleanTest.mts @@ -5,9 +5,9 @@ import { import {assert} from "chai"; import { TerminalEncoding, - requireThat, Configuration, - Type + Type, + requireThatBoolean } from "../src/index.mjs"; import {TestCompiler} from "../build/TestCompiler.mjs"; import os from "os"; @@ -29,27 +29,27 @@ suite("BooleanTest", () => { test("isTrue", () => { - validators.requireThat(true, "actual").isTrue(); + validators.requireThatBoolean(true, "actual").isTrue(); }); test("isTrue_False", () => { assert.throws(function() { - validators.requireThat(false, "actual").isTrue(); + validators.requireThatBoolean(false, "actual").isTrue(); }, RangeError); }); test("isFalse", () => { - validators.requireThat(false, "actual").isFalse(); + validators.requireThatBoolean(false, "actual").isFalse(); }); test("isFalse_False", () => { assert.throws(function() { - validators.requireThat(true, "actual").isFalse(); + validators.requireThatBoolean(true, "actual").isFalse(); }, RangeError); }); @@ -58,7 +58,7 @@ suite("BooleanTest", () => assert.throws(function() { let actual; - requireThat(actual, "actual").isType(Type.BOOLEAN); + requireThatBoolean(actual, "actual").isType(Type.BOOLEAN); }, TypeError); }); @@ -177,7 +177,7 @@ suite("BooleanTest", () => test("getActual", () => { const input = true; - const output = validators.requireThat(input, "input").getValue(); + const output = validators.requireThatBoolean(input, "input").getValue(); assert.strictEqual(output, input); }); }); \ No newline at end of file diff --git a/test/ConfigurationTest.mts b/test/ConfigurationTest.mts index c897857..c8a92d1 100644 --- a/test/ConfigurationTest.mts +++ b/test/ConfigurationTest.mts @@ -56,7 +56,7 @@ suite("Configuration", () => const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), Configuration.DEFAULT); - const actual = validators.getConfiguration().stringMappers().toString(undefined); + const actual = validators.getRequireThatConfiguration().stringMappers().toString(undefined); assert.strictEqual(actual, "undefined"); }); @@ -65,7 +65,7 @@ suite("Configuration", () => const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), Configuration.DEFAULT); - const actual = validators.getConfiguration().stringMappers().toString(null); + const actual = validators.getRequireThatConfiguration().stringMappers().toString(null); assert.strictEqual(actual, "null"); }); }); \ No newline at end of file diff --git a/test/DiffTest.mts b/test/DiffTest.mts index 3746f36..69e652b 100644 --- a/test/DiffTest.mts +++ b/test/DiffTest.mts @@ -26,8 +26,8 @@ const PADDING = TextOnly.DIFF_PADDING; /** * Pads a string until it is long enough to trigger a diff. * - * @param text the text to process - * @return the updated text + * @param text - the text to process + * @returns the updated text */ function EXTEND_LENGTH(text: string) { @@ -51,7 +51,7 @@ suite("DiffTest", () => const expected = EXTEND_LENGTH("expected"); try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -87,7 +87,7 @@ ${expectedMessage}`); const expected = `"key": "value"`; try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -119,7 +119,7 @@ ${expectedMessage}`); const expected = EXTEND_LENGTH("expected"); try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -159,7 +159,7 @@ ${expectedMessage}`); const expected = EXTEND_LENGTH("expected"); try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -197,7 +197,7 @@ ${expectedMessage}`); const expected = EXTEND_LENGTH("prefix") + "value"; try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -237,7 +237,7 @@ ${expectedMessage}`); const expected = "1\n2\n9\n4\n5"; try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -279,7 +279,7 @@ ${expectedMessage}`); const expected = "The fox is down"; try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -312,7 +312,7 @@ ${expectedMessage}`); const expected = "Don't you like me?"; try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -342,7 +342,7 @@ ${expectedMessage}`); const expected = "I like dogs"; try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -372,7 +372,7 @@ ${expectedMessage}`); const expected = "I like dogs"; try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -401,7 +401,7 @@ ${expectedMessage}`); const expected = "I like dogs"; try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -439,7 +439,7 @@ ${expectedMessage}`); "three\n"; try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -478,7 +478,7 @@ ${expectedMessage}`); { const actual = [1, 2, 3, 4, 5]; const expected = [1, 2, 9, 4, 5]; - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatArray(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -516,7 +516,7 @@ ${expectedMessage}`); { const actual = ["1", "foo\nbar", "3"]; const expected = ["1", "bar\nfoo", "3"]; - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatArray(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -562,14 +562,14 @@ ${expectedMessage}`); const expected = "int[1234 67890]"; try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) { const actualMessage = (e as Error).message; const expectedMessage = `\ -actual : "int[12345${PADDING}67890]\"${EOS_MARKER} +actual : "int[12345${PADDING}67890]"${EOS_MARKER} diff : ${EQUAL.repeat(`"int[1234`.length)}${DELETE}${INSERT}\ ${EQUAL.repeat(`67890]"${EOS_MARKER}`.length)} expected: "int[1234${PADDING.repeat(2)}67890]"${EOS_MARKER}`; @@ -596,7 +596,7 @@ ${expectedMessage}`); const expected = "maybe-same-maybe"; try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -624,7 +624,7 @@ ${expectedMessage}`); }); /** - * When processing DELETE "same\nactual" followed by INSERT "same\nexpected", ensure that actual and + * When processing DELETE "same\\nactual" followed by INSERT "same\\nexpected", ensure that actual and * expected keep track of different "diff" line numbers. Otherwise, the DELETE advances to the next line and * INSERT updates the diff of the wrong line number. We end up with: * @@ -652,7 +652,7 @@ ${expectedMessage}`); const expected = "expected\nsame\nexpected expected"; try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -666,7 +666,8 @@ expected@0: ${PADDING.repeat(`"actual`.length)}"expected${NEWLINE_MARKER} [...] -actual@2 : actual ${PADDING.repeat("expected".length)}actual"${PADDING.repeat(`expected"`.length) + EOS_MARKER} +actual@2 : actual ${PADDING.repeat("expected".length)}actual"${PADDING.repeat( + `expected"`.length) + EOS_MARKER} diff : ${DELETE.repeat("actual".length) + INSERT.repeat("expected".length) + EQUAL + DELETE.repeat(`actual"`.length) + INSERT.repeat(`expected"`.length) + EQUAL.repeat(EOS_MARKER.length)} expected@2: ${PADDING.repeat("actual".length)}expected${PADDING.repeat(` actual"`.length)}expected"\ @@ -693,7 +694,7 @@ ${expectedMessage}`); const expected = "int[1234 67890]"; try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -729,7 +730,7 @@ ${expectedMessage}`); const expected = "int[1234 67890]"; try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -765,7 +766,7 @@ ${expectedMessage}`); const expected = "int[1234 67890]"; try { - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -801,7 +802,7 @@ ${expectedMessage}`); { const actual = EXTEND_LENGTH("prefix") + "foo\nbar"; const expected = EXTEND_LENGTH("prefix") + "bar"; - validators.requireThat(actual, "actual").isEqualTo(expected); + validators.requireThatString(actual, "actual").isEqualTo(expected); assert.fail("Expected method to throw error"); } catch (e) @@ -810,7 +811,8 @@ ${expectedMessage}`); const actualMessage = (e as Error).message; const expectedMessage = `\ -actual@0 : ${scheme.decorateEqualText(`"${EXTEND_LENGTH("prefix")}`) + scheme.decorateDeletedText(`foo${NEWLINE_MARKER}`)} +actual@0 : ${scheme.decorateEqualText(`"${EXTEND_LENGTH("prefix")}`) + scheme.decorateDeletedText( + `foo${NEWLINE_MARKER}`)} expected@0: ${scheme.decorateEqualText(`"${EXTEND_LENGTH("prefix")}`) + scheme.decoratePadding(scheme.getPaddingMarker().repeat((`foo${NEWLINE_MARKER}`).length))} diff --git a/test/GenericTest.mts b/test/GenericTest.mts index d6b9987..85138ac 100644 --- a/test/GenericTest.mts +++ b/test/GenericTest.mts @@ -33,7 +33,7 @@ suite("BaseTest", () => { const actual = {} as object | null; // Changes the compile-time type of the value to null - const isNull: null = validators.requireThat(actual, null as unknown as string).isNull().getValue(); + validators.requireThatObject(actual, null as unknown as string).isNull().getValue(); }, TypeError); }); @@ -42,14 +42,14 @@ suite("BaseTest", () => assert.throws(function() { const actual = {}; - validators.requireThat(actual, ""); + validators.requireThatObject(actual, ""); }, RangeError); }); test("isEqualTo", () => { const actual = "actual"; - validators.requireThat(actual, "actual").isEqualTo(actual); + validators.requireThatString(actual, "actual").isEqualTo(actual); }); test("isEqual_False", () => @@ -57,11 +57,11 @@ suite("BaseTest", () => const actual = {}; assert.throws(function() { - validators.requireThat(actual, "actual").isEqualTo("expected"); + validators.requireThatObject(actual, "actual").isEqualTo("expected"); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").isEqualTo("expected", "expected"); + validators.requireThatObject(actual, "actual").isEqualTo("expected", "expected"); }, RangeError); }); @@ -82,7 +82,7 @@ suite("BaseTest", () => test("isEqual_nullToNull", () => { const actual = null; - validators.requireThat(actual, "actual").isEqualTo(actual); + validators.requireThatObject(actual, "actual").isEqualTo(actual); }); test("isEqualTo_nullToNotNull", () => @@ -116,7 +116,7 @@ assignable to parameter of type 'null'.` + os.EOL); test("isNotEqualTo", () => { const actual = "actualValue"; - validators.requireThat(actual, "actual").isNotEqualTo("expectedValue"); + validators.requireThatString(actual, "actual").isNotEqualTo("expectedValue"); }); test("isNotEqualTo_False", () => @@ -124,24 +124,24 @@ assignable to parameter of type 'null'.` + os.EOL); const actual = {}; assert.throws(function() { - validators.requireThat(actual, "actual").isNotEqualTo(actual); + validators.requireThatObject(actual, "actual").isNotEqualTo(actual); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").isNotEqualTo(actual, "actual"); + validators.requireThatObject(actual, "actual").isNotEqualTo(actual, "actual"); }, RangeError); }); test("getType_isUndefined", () => { const actual = undefined; - const validator: ObjectValidator = validators.requireThat(actual, "actual").isUndefined(); + validators.requireThatObject(actual, "actual").isUndefined(); }); test("getType_isNull", () => { const actual = null; - const validator: ObjectValidator = validators.requireThat(actual, "actual").isNull(); + validators.requireThatObject(actual, "actual").isNull(); }); class Person @@ -159,20 +159,19 @@ assignable to parameter of type 'null'.` + os.EOL); test("getType_isObject", () => { const actual = new Person("John Smith", 32); - validators.requireThat(actual, "actual").isType(Type.namedClass(null)); + validators.requireThatObject(actual, "actual").isType(Type.namedClass(null)); }); test("getType_isType", () => { const actual = Type.of(new Person("name", 5)); - validators.requireThat(actual, "actual").isType(Type.namedClass("Type")); + validators.requireThatObject(actual, "actual").isType(Type.namedClass("Type")); }); test("isInstanceOf", () => { const actual = new Person("name", 5); - const expected: Person | null = validators.requireThat(actual as unknown, "actual"). - isInstanceOf(Person).getValue(); + validators.requireThatObject(actual, "actual").isInstanceOf(Person).getValue(); }); test("isInstanceOf_actualIsNull", () => @@ -180,7 +179,7 @@ assignable to parameter of type 'null'.` + os.EOL); assert.throws(function() { const actual = null; - validators.requireThat(actual, "actual").isInstanceOf(String); + validators.requireThatObject(actual, "actual").isInstanceOf(String); }, TypeError); }); @@ -189,7 +188,7 @@ assignable to parameter of type 'null'.` + os.EOL); assert.throws(function() { const actual = {}; - validators.requireThat(actual as unknown, "actual").isInstanceOf(String); + validators.requireThatObject(actual, "actual").isInstanceOf(String); }, TypeError); }); test("isInstanceOf_Object", () => @@ -197,13 +196,13 @@ assignable to parameter of type 'null'.` + os.EOL); assert.throws(function() { const actual = 5; - validators.requireThat(actual as unknown, "actual").isInstanceOf(Object); + validators.requireThatNumber(actual, "actual").isInstanceOf(Object); }, TypeError); }); test("isNull", () => { - validators.requireThat(null, "actual").isNull(); + validators.requireThatObject(null, "actual").isNull(); }); test("isNull_False", () => @@ -211,7 +210,7 @@ assignable to parameter of type 'null'.` + os.EOL); assert.throws(function() { const actual = {} as object | null; - const isNull: null = validators.requireThat(actual, "actual").isNull().getValue(); + validators.requireThatObject(actual, "actual").isNull().getValue(); }, TypeError); }); @@ -219,7 +218,7 @@ assignable to parameter of type 'null'.` + os.EOL); { const actual = {} as object | null; // Changes the compile-time type of the value to not-null - const notNull: object | null = validators.requireThat(actual, "actual").isNotNull().getValue(); + validators.requireThatObject(actual, "actual").isNotNull().getValue(); }); test("isNotNull_False", () => @@ -227,15 +226,14 @@ assignable to parameter of type 'null'.` + os.EOL); assert.throws(function() { const actual = null; - validators.requireThat(actual, "actual").isNotNull(); + validators.requireThatObject(actual, "actual").isNotNull(); }, TypeError); }); test("isDefined", () => { const actual = 5; - const foo: ObjectValidator = validators.requireThat(actual as unknown, "actual"); - console.log("foo: " + foo.constructor.name); + const foo: ObjectValidator = validators.requireThatNumber(actual, "actual"); foo.isNotUndefined(); }); @@ -245,7 +243,7 @@ assignable to parameter of type 'null'.` + os.EOL); { let actual; // noinspection JSUnusedAssignment - validators.requireThat(actual, "actual").isNotUndefined(); + validators.requireThatObject(actual, "actual").isNotUndefined(); }, TypeError); }); @@ -253,7 +251,7 @@ assignable to parameter of type 'null'.` + os.EOL); { let actual; // noinspection JSUnusedAssignment - validators.requireThat(actual, "actual").isUndefined(); + validators.requireThatObject(actual, "actual").isUndefined(); }); test("isUndefined_False", () => @@ -261,7 +259,7 @@ assignable to parameter of type 'null'.` + os.EOL); assert.throws(function() { const actual = 5; - validators.requireThat(actual as unknown, "actual").isUndefined(); + validators.requireThatNumber(actual, "actual").isUndefined(); }, TypeError); }); }); \ No newline at end of file diff --git a/test/MapTest.mts b/test/MapTest.mts index 697dc79..07d4db9 100644 --- a/test/MapTest.mts +++ b/test/MapTest.mts @@ -22,7 +22,7 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map(); - validators.requireThat(actual, null as unknown as string); + validators.requireThatMap(actual, null as unknown as string); }, TypeError); }); @@ -31,14 +31,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map(); - validators.requireThat(actual, ""); + validators.requireThatMap(actual, ""); }, RangeError); }); test("isEmpty", () => { const actual = new Map(); - validators.requireThat(actual, "actual").isEmpty(); + validators.requireThatMap(actual, "actual").isEmpty(); }); test("isEmpty_False", () => @@ -46,14 +46,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").isEmpty(); + validators.requireThatMap(actual, "actual").isEmpty(); }, RangeError); }); test("isNotEmpty", () => { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").isNotEmpty(); + validators.requireThatMap(actual, "actual").isNotEmpty(); }); test("isNotEmpty_False", () => @@ -61,14 +61,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map(); - validators.requireThat(actual, "actual").isNotEmpty(); + validators.requireThatMap(actual, "actual").isNotEmpty(); }, RangeError); }); test("isEqualTo", () => { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").isEqualTo(actual); + validators.requireThatMap(actual, "actual").isEqualTo(actual); }); test("isEqual_False", () => @@ -76,13 +76,13 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").isEqualTo(new Map()); + validators.requireThatMap(actual, "actual").isEqualTo(new Map()); }, RangeError); }); test("isNotEqualTo", () => { - validators.requireThat(new Map([[1, 10], [2, 20]]), "actual").isNotEqualTo(new Map()); + validators.requireThatMap(new Map([[1, 10], [2, 20]]), "actual").isNotEqualTo(new Map()); }); test("isNotEqualTo_False", () => @@ -90,22 +90,24 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map(); - validators.requireThat(actual, "actual").isNotEqualTo(actual); + validators.requireThatMap(actual, "actual").isNotEqualTo(actual); }, RangeError); }); test("isInstanceOf", () => { - const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual as unknown, "actual").isInstanceOf(Map).isType(Type.namedClass(null)); + const actual = new Map([[1, 10], [2, 20]]); + validators.requireThatMap(actual as unknown as Map, "actual").isInstanceOf(Map). + isType(Type.namedClass(null)); }); test("isInstanceOf_False", () => { assert.throws(function() { - const actual = new Map(); - validators.requireThat(actual as unknown, "actual").isType(Type.namedClass("string")); + const actual = new Map(); + validators.requireThatMap(actual as unknown as Map, "actual"). + isType(Type.namedClass("string")); }, TypeError); }); @@ -113,21 +115,21 @@ suite("MapTest", () => { assert.throws(function() { - const actual = new Map(); - validators.requireThat(actual as unknown, "actual").isNull(); + const actual = new Map(); + validators.requireThatMap(actual as unknown as Map, "actual").isNull(); }, TypeError); }); test("isNotNull", () => { - const actual = new Map(); - validators.requireThat(actual as unknown, "actual").isNotNull(); + const actual = new Map(); + validators.requireThatMap(actual as unknown as Map, "actual").isNotNull(); }); test("keysContain", () => { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").keys().contains(2); + validators.requireThatMap(actual, "actual").keys().contains(2); }); test("keysContain_False", () => @@ -135,14 +137,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").keys().contains(5); + validators.requireThatMap(actual, "actual").keys().contains(5); }, RangeError); }); test("keysDoesNotContain", () => { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").keys().doesNotContain(5); + validators.requireThatMap(actual, "actual").keys().doesNotContain(5); }); test("keysDoesNotContain_False", () => @@ -150,14 +152,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").keys().doesNotContain(2); + validators.requireThatMap(actual, "actual").keys().doesNotContain(2); }, RangeError); }); test("keysNestedValidator", () => { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").and(v => v.keys().contains(2)).size().isEqualTo(2); + validators.requireThatMap(actual, "actual").and(v => v.keys().contains(2)).size().isEqualTo(2); }); test("keysLength_False", () => @@ -165,11 +167,11 @@ suite("MapTest", () => const actual = new Map(); assert.throws(function() { - validators.requireThat(actual, "actual").keys().length().isGreaterThan(1); + validators.requireThatMap(actual, "actual").keys().length().isGreaterThan(1); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").keys().length().isGreaterThan(2); + validators.requireThatMap(actual, "actual").keys().length().isGreaterThan(2); }, RangeError); }); @@ -178,14 +180,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").and(v => v.keys().doesNotContain(2)); + validators.requireThatMap(actual, "actual").and(v => v.keys().doesNotContain(2)); }, RangeError); }); test("valuesContain", () => { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").values().contains(20); + validators.requireThatMap(actual, "actual").values().contains(20); }); test("valuesContain_False", () => @@ -193,14 +195,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").values().contains(50); + validators.requireThatMap(actual, "actual").values().contains(50); }, RangeError); }); test("valuesDoesNotContain", () => { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").values().doesNotContain(50); + validators.requireThatMap(actual, "actual").values().doesNotContain(50); }); test("valuesDoesNotContain_False", () => @@ -208,7 +210,7 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").values().doesNotContain(20); + validators.requireThatMap(actual, "actual").values().doesNotContain(20); }, RangeError); }); @@ -217,18 +219,18 @@ suite("MapTest", () => const actual = new Map(); assert.throws(function() { - validators.requireThat(actual, "actual").values().length().isGreaterThan(1); + validators.requireThatMap(actual, "actual").values().length().isGreaterThan(1); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").values().length().isGreaterThan(2); + validators.requireThatMap(actual, "actual").values().length().isGreaterThan(2); }, RangeError); }); test("valuesNestedValidator", () => { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").and(v => v.values().contains(20)).size().isEqualTo(2); + validators.requireThatMap(actual, "actual").and(v => v.values().contains(20)).size().isEqualTo(2); }); test("valuesNestedValidator_Fail", () => @@ -236,14 +238,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").and(v => v.values().doesNotContain(20)); + validators.requireThatMap(actual, "actual").and(v => v.values().doesNotContain(20)); }, RangeError); }); test("entriesContain", () => { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").entries().contains([2, 20]); + validators.requireThatMap(actual, "actual").entries().contains([2, 20]); }); test("entriesContain_False", () => @@ -251,14 +253,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").entries().contains([5, 50]); + validators.requireThatMap(actual, "actual").entries().contains([5, 50]); }, RangeError); }); test("entriesDoesNotContain", () => { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").entries().doesNotContain([5, 50]); + validators.requireThatMap(actual, "actual").entries().doesNotContain([5, 50]); }); test("entriesDoesNotContain_False", () => @@ -266,7 +268,7 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").entries().doesNotContain([2, 20]); + validators.requireThatMap(actual, "actual").entries().doesNotContain([2, 20]); }, RangeError); }); @@ -275,18 +277,18 @@ suite("MapTest", () => const actual = new Map(); assert.throws(function() { - validators.requireThat(actual, "actual").entries().length().isGreaterThan(1); + validators.requireThatMap(actual, "actual").entries().length().isGreaterThan(1); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").entries().length().isGreaterThan(2); + validators.requireThatMap(actual, "actual").entries().length().isGreaterThan(2); }, RangeError); }); test("entriesNestedValidator", () => { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").and(v => v.entries().contains([2, 20])).size(). + validators.requireThatMap(actual, "actual").and(v => v.entries().contains([2, 20])).size(). isEqualTo(2); }); @@ -295,14 +297,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").and(v => v.entries().doesNotContain([2, 20])); + validators.requireThatMap(actual, "actual").and(v => v.entries().doesNotContain([2, 20])); }, RangeError); }); test("sizeIsEqualTo", () => { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").size().isEqualTo(2); + validators.requireThatMap(actual, "actual").size().isEqualTo(2); }); test("sizeIsEqualTo_False", () => @@ -310,14 +312,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").size().isEqualTo(1); + validators.requireThatMap(actual, "actual").size().isEqualTo(1); }, RangeError); }); test("sizeIsNotEqualTo", () => { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").size().isNotEqualTo(1); + validators.requireThatMap(actual, "actual").size().isNotEqualTo(1); }); test("sizeIsNotEqualTo_False", () => @@ -325,14 +327,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").size().isNotEqualTo(2); + validators.requireThatMap(actual, "actual").size().isNotEqualTo(2); }, RangeError); }); test("sizeNestedValidator", () => { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").and(v => v.size().isEqualTo(2)).entries(). + validators.requireThatMap(actual, "actual").and(v => v.size().isEqualTo(2)).entries(). contains([2, 20]); }); @@ -341,14 +343,14 @@ suite("MapTest", () => assert.throws(function() { const actual = new Map([[1, 10], [2, 20]]); - validators.requireThat(actual, "actual").and(v => v.size().isNotEqualTo(2)); + validators.requireThatMap(actual, "actual").and(v => v.size().isNotEqualTo(2)); }, RangeError); }); test("getActual", () => { const input = new Map([[1, 10], [2, 20]]); - const output = validators.requireThat(input, "input").getValue(); + const output = validators.requireThatMap(input, "input").getValue(); assert.strictEqual(output, input); }); @@ -358,8 +360,9 @@ suite("MapTest", () => const expectedMessages = [`\ "actual" must be a Map. actual: null`]; - const actualFailures = validators.checkIf(actual, "actual").isInstanceOf(Map).elseGetFailures(); + const actualFailures = validators.checkIfMap(actual, "actual").isInstanceOf(Map).elseGetFailures(); const actualMessages = actualFailures.getFailures().map(failure => failure.getMessage()); - validators.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages, "expectedMessages"); + validators.requireThatArray(actualMessages, "actualMessages"). + isEqualTo(expectedMessages, "expectedMessages"); }); }); \ No newline at end of file diff --git a/test/NumberTest.mts b/test/NumberTest.mts index 629de65..08c6431 100644 --- a/test/NumberTest.mts +++ b/test/NumberTest.mts @@ -21,22 +21,22 @@ suite("NumberTest", () => { test("isBetween_actualIsLowerBound", () => { - validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isBetween(0, 2)); + validators.requireThatNumber(0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isBetween(0, 2)); }); test("isBetween_actualIsInBounds", () => { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isBetween(0, 2)); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isBetween(0, 2)); }); test("isBetween_actualIsUpperBound", () => { assert.throws(function() { - validators.requireThat(2 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isBetween(0, 2)); + validators.requireThatNumber(2 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isBetween(0, 2)); }, RangeError); }); @@ -44,29 +44,29 @@ suite("NumberTest", () => { assert.throws(function() { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isBetween(10, 20)); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isBetween(10, 20)); }, RangeError); }); test("isBetweenClosed_actualIsUpperBound", () => { - validators.requireThat(2 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isBetween(0, true, 2, true)); + validators.requireThatNumber(2 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isBetween(0, true, 2, true)); }); test("isNegative_actualIsNegativeOne", () => { - validators.requireThat(-1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isNegative()); + validators.requireThatNumber(-1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isNegative()); }); test("isNegative_actualIsZero", () => { assert.throws(function() { - validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isNegative()); + validators.requireThatNumber(0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isNegative()); }, RangeError); }); @@ -74,40 +74,40 @@ suite("NumberTest", () => { assert.throws(function() { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isNegative()); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isNegative()); }, RangeError); }); test("isNotNegative", () => { - validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotNegative()); - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotNegative()); + validators.requireThatNumber(0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isNotNegative()); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isNotNegative()); }); test("isNotNegative_actualIsNegativeOne", () => { assert.throws(function() { - validators.requireThat(-1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotNegative()); + validators.requireThatNumber(-1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isNotNegative()); }, RangeError); }); test("isZero", () => { - validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isZero()); + validators.requireThatNumber(0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isZero()); }); test("isZero_actualIsOne", () => { assert.throws(function() { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isZero()); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isZero()); }, RangeError); }); @@ -115,40 +115,40 @@ suite("NumberTest", () => { assert.throws(function() { - validators.requireThat(-1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isZero()); + validators.requireThatNumber(-1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isZero()); }, RangeError); }); test("isNotZero", () => { - validators.requireThat(-1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotZero()); - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotZero()); + validators.requireThatNumber(-1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isNotZero()); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isNotZero()); }); test("isNotZero_False", () => { assert.throws(function() { - validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotZero()); + validators.requireThatNumber(0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isNotZero()); }, RangeError); }); test("isPositive", () => { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isPositive()); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isPositive()); }); test("isPositive_actualIsZero", () => { assert.throws(function() { - validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isPositive()); + validators.requireThatNumber(0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isPositive()); }, RangeError); }); @@ -156,46 +156,46 @@ suite("NumberTest", () => { assert.throws(function() { - validators.requireThat(-1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isPositive()); + validators.requireThatNumber(-1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isPositive()); }, RangeError); }); test("isNotPositive", () => { - validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotPositive()); - validators.requireThat(-1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotPositive()); + validators.requireThatNumber(0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isNotPositive()); + validators.requireThatNumber(-1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isNotPositive()); }); test("isNotPositive_actualIsOne", () => { assert.throws(function() { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isNotPositive()); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isNotPositive()); }, RangeError); }); test("isLessThanVariable", () => { - validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isLessThan(1, "expected")); + validators.requireThatNumber(0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isLessThan(1, "expected")); }); test("isLessThanConstant", () => { - validators.requireThat(0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isLessThan(1)); + validators.requireThatNumber(0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isLessThan(1)); }); test("isLessThanVariable_actualIsEqual", () => { assert.throws(function() { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isLessThan(1, "expected")); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isLessThan(1, "expected")); }, RangeError); }); @@ -203,8 +203,8 @@ suite("NumberTest", () => { assert.throws(function() { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isLessThan(1)); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isLessThan(1)); }, RangeError); }); @@ -212,8 +212,8 @@ suite("NumberTest", () => { assert.throws(function() { - validators.requireThat(2 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isLessThan(1, "expected")); + validators.requireThatNumber(2 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isLessThan(1, "expected")); }, RangeError); }); @@ -221,30 +221,30 @@ suite("NumberTest", () => { assert.throws(function() { - validators.requireThat(2 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isLessThan(1)); + validators.requireThatNumber(2 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isLessThan(1)); }, RangeError); }); test("isLessThanOrEqualToVariable", () => { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()). + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()). isLessThanOrEqualTo(1, "expected")); }); test("isLessThanOrEqualToConstant", () => { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isLessThanOrEqualTo(1)); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isLessThanOrEqualTo(1)); }); test("isLessThanOrEqualToVariable_actualIsGreater", () => { assert.throws(function() { - validators.requireThat(3 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()). + validators.requireThatNumber(3 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()). isLessThanOrEqualTo(2, "expected")); }, RangeError); }); @@ -253,29 +253,29 @@ suite("NumberTest", () => { assert.throws(function() { - validators.requireThat(3 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isLessThanOrEqualTo(2)); + validators.requireThatNumber(3 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isLessThanOrEqualTo(2)); }, RangeError); }); test("isGreaterThanVariable", () => { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isGreaterThan(0, "expected")); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isGreaterThan(0, "expected")); }); test("isGreaterThanConstant", () => { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isGreaterThan(0)); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isGreaterThan(0)); }); test("isGreaterThanVariable_actualIsEqual", () => { assert.throws(function() { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isGreaterThan(1, "expected")); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isGreaterThan(1, "expected")); }, RangeError); }); @@ -283,8 +283,8 @@ suite("NumberTest", () => { assert.throws(function() { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isGreaterThan(1)); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isGreaterThan(1)); }, RangeError); }); @@ -292,8 +292,8 @@ suite("NumberTest", () => { assert.throws(function() { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isGreaterThan(2, "expected")); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isGreaterThan(2, "expected")); }, RangeError); }); @@ -301,30 +301,30 @@ suite("NumberTest", () => { assert.throws(function() { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isGreaterThan(2)); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isGreaterThan(2)); }, RangeError); }); test("isGreaterThanOrEqualToVariable", () => { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()). + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()). isGreaterThanOrEqualTo(1, "expected")); }); test("isGreaterThanOrEqualToConstant", () => { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isGreaterThanOrEqualTo(1)); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isGreaterThanOrEqualTo(1)); }); test("isGreaterThanOrEqualToVariable_actualIsLess", () => { assert.throws(function() { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()). + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()). isGreaterThanOrEqualTo(2, "expected")); }, RangeError); }); @@ -333,75 +333,75 @@ suite("NumberTest", () => { assert.throws(function() { - validators.requireThat(1 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isGreaterThanOrEqualTo(2)); + validators.requireThatNumber(1 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isGreaterThanOrEqualTo(2)); }, RangeError); }); test("isFinite", () => { - validators.requireThat(1.0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isFinite()); + validators.requireThatNumber(1.0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isFinite()); }); test("isFinite_False", () => { assert.throws(function() { - validators.requireThat(1.0 / 0.0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isFinite()); + validators.requireThatNumber(1.0 / 0.0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isFinite()); }, RangeError); }); test("isNotFinite", () => { - validators.requireThat(1.0 / 0.0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isInfinite()); + validators.requireThatNumber(1.0 / 0.0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isInfinite()); }); test("isNotFinite_False", () => { assert.throws(function() { - validators.requireThat(1.0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isInfinite()); + validators.requireThatNumber(1.0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isInfinite()); }, RangeError); }); test("isNumber", () => { - validators.requireThat(1.0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName())); + validators.requireThatNumber(1.0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName())); }); test("isNumber_False", () => { assert.throws(function() { - validators.requireThat(0.0 / 0.0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isFinite()); + validators.requireThatNumber(0.0 / 0.0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isFinite()); }, RangeError); }); test("isInfinite", () => { - validators.requireThat(0.0 / 0.0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isInfinite()); + validators.requireThatNumber(0.0 / 0.0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isInfinite()); }); test("isInfinite_False", () => { assert.throws(function() { - validators.requireThat(1.0 as unknown, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as number, v.getName()).isInfinite()); + validators.requireThatNumber(1.0 as unknown as number, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue(), v.getName()).isInfinite()); }, RangeError); }); test("getActual", () => { const input = 5; - const output = validators.requireThat(input, "input").getValue(); + const output = validators.requireThatNumber(input, "input").getValue(); assert.strictEqual(output, input); }); @@ -411,9 +411,11 @@ suite("NumberTest", () => const expectedMessages = [`\ "actual" must be a number. actual: null`]; - const actualFailures = validators.checkIf(actual, "actual").isType(Type.NUMBER). - and(v => validators.requireThat(v.getValue() as unknown as number, v.getName())).elseGetFailures(); + const actualFailures = validators.checkIfArray(actual, "actual").isType(Type.NUMBER). + and(v => validators.requireThatNumber(v.getValue() as unknown as number, v.getName())). + elseGetFailures(); const actualMessages = actualFailures.getFailures().map(failure => failure.getMessage()); - validators.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages, "expectedMessages"); + validators.requireThatArray(actualMessages, "actualMessages"). + isEqualTo(expectedMessages, "expectedMessages"); }); }); \ No newline at end of file diff --git a/test/ObjectsTest.mts b/test/ObjectsTest.mts index 5f8145f..66a9822 100644 --- a/test/ObjectsTest.mts +++ b/test/ObjectsTest.mts @@ -13,7 +13,6 @@ suite("ObjectsTest", () => { test("getType_undefined", () => { - // eslint-disable-next-line no-undefined assert.deepEqual(Type.of(undefined), Type.UNDEFINED); }); @@ -29,7 +28,6 @@ suite("ObjectsTest", () => test("getType_Boolean", () => { - // eslint-disable-next-line no-new-wrappers // noinspection JSPrimitiveTypeWrapperUsage const input = new Boolean(true); assert.deepEqual(Type.of(input), Type.namedClass("Boolean")); @@ -42,7 +40,7 @@ suite("ObjectsTest", () => test("getType_Number", () => { - // eslint-disable-next-line no-new-wrappers + // noinspection JSPrimitiveTypeWrapperUsage const input = new Number(5); assert.deepEqual(Type.of(input), Type.namedClass("Number")); }); @@ -54,7 +52,6 @@ suite("ObjectsTest", () => test("getType_BigInt", () => { - // eslint-disable-next-line no-new-wrappers const input = BigInt(5); assert.deepEqual(Type.of(input), Type.BIGINT); }); @@ -66,7 +63,7 @@ suite("ObjectsTest", () => test("getType_String", () => { - // eslint-disable-next-line no-new-wrappers + // noinspection JSPrimitiveTypeWrapperUsage const input = new String("test"); assert.deepEqual(Type.of(input), Type.of(String)); }); @@ -102,6 +99,7 @@ suite("ObjectsTest", () => }); + // eslint-disable-next-line @typescript-eslint/no-extraneous-class class MyClass { } diff --git a/test/RequirementsTest.mts b/test/RequirementsTest.mts index 085e9d3..627a286 100644 --- a/test/RequirementsTest.mts +++ b/test/RequirementsTest.mts @@ -22,38 +22,38 @@ suite("RequirementsTest", () => assert.throws(function() { const actual = "actual"; - validators.assertThat(actual, "actual").isEqualTo("expected"); + validators.assertThatString(actual, "actual").isEqualTo("expected"); }, AssertionError); }); test("assertThatArray", () => { const actual = [1, 2, 3]; - validators.assertThat(actual, "actual").isEqualTo(actual, "expected"); + validators.assertThatArray(actual, "actual").isEqualTo(actual, "expected"); }); test("assertThatNumber", () => { const actual = 5; - validators.assertThat(actual, "actual").isEqualTo(actual, "expected"); + validators.assertThatNumber(actual, "actual").isEqualTo(actual, "expected"); }); test("assertThatSet", () => { const actual = new Set([1, 2, 3]); - validators.assertThat(actual, "actual").isEqualTo(actual, "expected"); + validators.assertThatSet(actual, "actual").isEqualTo(actual, "expected"); }); test("assertThatMap", () => { const actual = new Map([[1, 2], [2, 3]]); - validators.assertThat(actual, "actual").isEqualTo(actual, "expected"); + validators.assertThatMap(actual, "actual").isEqualTo(actual, "expected"); }); test("assertThatUrl", () => { const actual = new URL("http://www.google.com/"); - validators.assertThat(actual, "actual").isEqualTo(actual, "expected"); + validators.assertThatObject(actual, "actual").isEqualTo(actual, "expected"); }); test("assertThatObject", () => @@ -63,14 +63,14 @@ suite("RequirementsTest", () => const localValidators = validators.copy(); const actual = {}; - localValidators.assertThat(actual, "actual").isEqualTo("expected"); + localValidators.assertThatObject(actual, "actual").isEqualTo("expected"); }, AssertionError); }); test("requireThat.getActual", () => { const input = 12345; - const output = validators.requireThat(input, "input").getValue(); + const output = validators.requireThatNumber(input, "input").getValue(); assert.strictEqual(output, input); }); @@ -79,8 +79,8 @@ suite("RequirementsTest", () => const localValidators = validators.copy(); const actual = 12345; - const getActual = localValidators.assertThat(actual, "actual").getValue(); - validators.requireThat(actual, "actual").isEqualTo(getActual as number, "getActual()"); + const getActual = localValidators.assertThatNumber(actual, "actual").getValue(); + validators.requireThatNumber(actual, "actual").isEqualTo(getActual as number, "getActual()"); }); test("putContext", () => diff --git a/test/SetTest.mts b/test/SetTest.mts index f9a3c2e..6cf485c 100644 --- a/test/SetTest.mts +++ b/test/SetTest.mts @@ -21,7 +21,7 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set(); - validators.requireThat(actual, null as unknown as string); + validators.requireThatSet(actual, null as unknown as string); }, TypeError); }); @@ -30,14 +30,14 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set(); - validators.requireThat(actual, ""); + validators.requireThatSet(actual, ""); }, RangeError); }); test("isEmpty", () => { const actual = new Set(); - validators.requireThat(actual, "actual").isEmpty(); + validators.requireThatSet(actual, "actual").isEmpty(); }); test("isEmpty_False", () => @@ -45,14 +45,14 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set([1, 2, 3]); - validators.requireThat(actual, "actual").isEmpty(); + validators.requireThatSet(actual, "actual").isEmpty(); }, RangeError); }); test("isNotEmpty", () => { const actual = new Set([1, 2, 3]); - validators.requireThat(actual, "actual").isNotEmpty(); + validators.requireThatSet(actual, "actual").isNotEmpty(); }); test("isNotEmpty_False", () => @@ -60,14 +60,14 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set(); - validators.requireThat(actual, "actual").isNotEmpty(); + validators.requireThatSet(actual, "actual").isNotEmpty(); }, RangeError); }); test("isEqualTo", () => { const actual = new Set([1, 2, 3]); - validators.requireThat(actual, "actual").isEqualTo(actual); + validators.requireThatSet(actual, "actual").isEqualTo(actual); }); test("isEqual_False", () => @@ -75,13 +75,13 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set([1, 2, 3]); - validators.requireThat(actual, "actual").isEqualTo(new Set()); + validators.requireThatSet(actual, "actual").isEqualTo(new Set()); }, RangeError); }); test("isNotEqualTo", () => { - validators.requireThat(new Set([1, 2, 3]), "actual").isNotEqualTo(new Set()); + validators.requireThatSet(new Set([1, 2, 3]), "actual").isNotEqualTo(new Set()); }); test("isNotEqualTo_False", () => @@ -89,14 +89,14 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set(); - validators.requireThat(actual, "actual").isNotEqualTo(actual); + validators.requireThatSet(actual, "actual").isNotEqualTo(actual); }, RangeError); }); test("isSet", () => { const actual = new Set([1, 2, 3]); - validators.requireThat(actual as unknown, "actual").isInstanceOf(Set).getValue(); + validators.requireThatSet(actual as unknown as Set, "actual").isInstanceOf(Set).getValue(); }); test("isSet_False", () => @@ -104,7 +104,7 @@ suite("SetTest", () => assert.throws(function() { const actual = [1, 2, 3]; - validators.requireThat(actual as unknown, "actual").isInstanceOf(Set); + validators.requireThatSet(actual as unknown as Set, "actual").isInstanceOf(Set); }, TypeError); }); @@ -113,20 +113,20 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set(); - validators.requireThat(actual as unknown, "actual").isNull(); + validators.requireThatSet(actual as unknown as Set, "actual").isNull(); }, TypeError); }); test("isNotNull", () => { const actual = new Set(); - validators.requireThat(actual as unknown, "actual").isNotNull(); + validators.requireThatSet(actual as unknown as Set, "actual").isNotNull(); }); test("contains", () => { const actual = new Set([1, 2, 3]); - validators.requireThat(actual, "actual").contains(2); + validators.requireThatSet(actual, "actual").contains(2); }); test("contains_False", () => @@ -134,18 +134,18 @@ suite("SetTest", () => const actual = new Set([1, 2, 3]); assert.throws(function() { - validators.requireThat(actual, "actual").contains(5); + validators.requireThatSet(actual, "actual").contains(5); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").contains(5, "expected"); + validators.requireThatSet(actual, "actual").contains(5, "expected"); }, RangeError); }); test("doesNotContain", () => { const actual = new Set([1, 2, 3]); - validators.requireThat(actual, "actual").doesNotContain(5); + validators.requireThatSet(actual, "actual").doesNotContain(5); }); test("doesNotContain_False", () => @@ -153,18 +153,18 @@ suite("SetTest", () => const actual = new Set([1, 2, 3]); assert.throws(function() { - validators.requireThat(actual, "actual").doesNotContain(2); + validators.requireThatSet(actual, "actual").doesNotContain(2); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").doesNotContain(2, "expected"); + validators.requireThatSet(actual, "actual").doesNotContain(2, "expected"); }, RangeError); }); test("containsAny", () => { const actual = new Set([1, 2, 3]); - validators.requireThat(actual, "actual").containsAny([0, 2, 4]); + validators.requireThatSet(actual, "actual").containsAny([0, 2, 4]); }); test("containsAny_False", () => @@ -172,18 +172,18 @@ suite("SetTest", () => const actual = new Set([1, 2, 3]); assert.throws(function() { - validators.requireThat(actual, "actual").containsAny([0, 5]); + validators.requireThatSet(actual, "actual").containsAny([0, 5]); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").containsAny([0, 5], "expected"); + validators.requireThatSet(actual, "actual").containsAny([0, 5], "expected"); }, RangeError); }); test("doesNotContainAny", () => { const actual = new Set([1, 2, 3]); - validators.requireThat(actual, "actual").doesNotContainAny([0, 5]); + validators.requireThatSet(actual, "actual").doesNotContainAny([0, 5]); }); test("doesNotContainAny_False", () => @@ -191,18 +191,18 @@ suite("SetTest", () => const actual = new Set([1, 2, 3]); assert.throws(function() { - validators.requireThat(actual, "actual").doesNotContainAny([0, 2]); + validators.requireThatSet(actual, "actual").doesNotContainAny([0, 2]); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").doesNotContainAny([0, 2], "expected"); + validators.requireThatSet(actual, "actual").doesNotContainAny([0, 2], "expected"); }, RangeError); }); test("containsAll", () => { const actual = new Set([1, 2, 3]); - validators.requireThat(actual, "actual").containsAll([2, 3]); + validators.requireThatSet(actual, "actual").containsAll([2, 3]); }); test("containsAll_False", () => @@ -210,18 +210,18 @@ suite("SetTest", () => const actual = new Set([1, 2, 3]); assert.throws(function() { - validators.requireThat(actual, "actual").containsAll([0, 1, 2]); + validators.requireThatSet(actual, "actual").containsAll([0, 1, 2]); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").containsAll([0, 1, 2], "expected"); + validators.requireThatSet(actual, "actual").containsAll([0, 1, 2], "expected"); }, RangeError); }); test("doesNotContainAll", () => { const actual = new Set([1, 2, 3]); - validators.requireThat(actual, "actual").doesNotContainAll([0, 2, 3]); + validators.requireThatSet(actual, "actual").doesNotContainAll([0, 2, 3]); }); test("doesNotContainAll_False", () => @@ -229,18 +229,18 @@ suite("SetTest", () => const actual = new Set([1, 2, 3]); assert.throws(function() { - validators.requireThat(actual, "actual").doesNotContainAll([2, 3]); + validators.requireThatSet(actual, "actual").doesNotContainAll([2, 3]); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").doesNotContainAll([2, 3], "expected"); + validators.requireThatSet(actual, "actual").doesNotContainAll([2, 3], "expected"); }, RangeError); }); test("containsExactly", () => { const actual = new Set([1, 2, 3]); - validators.requireThat(actual, "actual").containsExactly([1, 2, 3]); + validators.requireThatSet(actual, "actual").containsExactly([1, 2, 3]); }); test("containsExactly_False", () => @@ -248,18 +248,18 @@ suite("SetTest", () => const actual = new Set([1, 2, 3]); assert.throws(function() { - validators.requireThat(actual, "actual").containsExactly([0, 1, 2, 3]); + validators.requireThatSet(actual, "actual").containsExactly([0, 1, 2, 3]); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").containsExactly([0, 1, 2, 3], "expected"); + validators.requireThatSet(actual, "actual").containsExactly([0, 1, 2, 3], "expected"); }, RangeError); }); test("sizeIsEqualTo", () => { const actual = new Set([1, 2, 3]); - validators.requireThat(actual, "actual").size().isEqualTo(3); + validators.requireThatSet(actual, "actual").size().isEqualTo(3); }); test("sizeIsEqualTo_False", () => @@ -267,14 +267,14 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set([1, 2, 3]); - validators.requireThat(actual, "actual").size().isEqualTo(2); + validators.requireThatSet(actual, "actual").size().isEqualTo(2); }, RangeError); }); test("sizeIsNotEqualTo", () => { const actual = new Set([1, 2, 3]); - validators.requireThat(actual, "actual").size().isNotEqualTo(2); + validators.requireThatSet(actual, "actual").size().isNotEqualTo(2); }); test("sizeIsNotEqualTo_False", () => @@ -282,7 +282,7 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set([1, 2, 3]); - validators.requireThat(actual, "actual").size().isNotEqualTo(3); + validators.requireThatSet(actual, "actual").size().isNotEqualTo(3); }, RangeError); }); @@ -291,14 +291,14 @@ suite("SetTest", () => assert.throws(function() { const actual = new Set([1, 2, 3]); - validators.requireThat(actual, "actual").and(v => v.size().isNotEqualTo(3)); + validators.requireThatSet(actual, "actual").and(v => v.size().isNotEqualTo(3)); }, RangeError); }); test("getActual", () => { const input = new Set([1, 2, 3]); - const output = validators.requireThat(input, "input").getValue(); + const output = validators.requireThatSet(input, "input").getValue(); assert.strictEqual(output, input); }); @@ -308,8 +308,9 @@ suite("SetTest", () => const expectedMessages = [`\ "actual" must be a Set. actual: null`]; - const actualFailures = validators.checkIf(actual, "actual").isInstanceOf(Set).elseGetFailures(); + const actualFailures = validators.checkIfSet(actual, "actual").isInstanceOf(Set).elseGetFailures(); const actualMessages = actualFailures.getFailures().map(failure => failure.getMessage()); - validators.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages, "expectedMessages"); + validators.requireThatArray(actualMessages, "actualMessages"). + isEqualTo(expectedMessages, "expectedMessages"); }); }); \ No newline at end of file diff --git a/test/SizeTest.mts b/test/SizeTest.mts index a0640a2..e4e14d2 100644 --- a/test/SizeTest.mts +++ b/test/SizeTest.mts @@ -28,7 +28,7 @@ suite("SizeTest", () => test("isGreaterThanOrEqualTo", () => { const actual: unknown[] = []; - validators.requireThat(actual, "actual").length().isGreaterThanOrEqualTo(0); + validators.requireThatArray(actual, "actual").length().isGreaterThanOrEqualTo(0); }); test("isGreaterThanOrEqualTo_False", () => @@ -36,18 +36,18 @@ suite("SizeTest", () => const actual: unknown[] = []; assert.throws(function() { - validators.requireThat(actual, "actual").length().isGreaterThanOrEqualTo(5); + validators.requireThatArray(actual, "actual").length().isGreaterThanOrEqualTo(5); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").length().isGreaterThanOrEqualTo(5, "expected"); + validators.requireThatArray(actual, "actual").length().isGreaterThanOrEqualTo(5, "expected"); }, RangeError); }); test("isGreaterThan", () => { const actual = [1]; - validators.requireThat(actual, "actual").length().isGreaterThan(0); + validators.requireThatArray(actual, "actual").length().isGreaterThan(0); }); test("isGreaterThan_False", () => @@ -55,18 +55,18 @@ suite("SizeTest", () => const actual: unknown[] = []; assert.throws(function() { - validators.requireThat(actual, "actual").length().isGreaterThan(5); + validators.requireThatArray(actual, "actual").length().isGreaterThan(5); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").length().isGreaterThan(5, "expected"); + validators.requireThatArray(actual, "actual").length().isGreaterThan(5, "expected"); }, RangeError); }); test("isLessThanOrEqualTo", () => { const actual = [1]; - validators.requireThat(actual, "actual").length().isLessThanOrEqualTo(2); + validators.requireThatArray(actual, "actual").length().isLessThanOrEqualTo(2); }); test("isLessThanOrEqualTo_False", () => @@ -74,18 +74,18 @@ suite("SizeTest", () => const actual: unknown[] = []; assert.throws(function() { - validators.requireThat(actual, "actual").length().isLessThanOrEqualTo(-1); + validators.requireThatArray(actual, "actual").length().isLessThanOrEqualTo(-1); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").length().isLessThanOrEqualTo(-1, "expected"); + validators.requireThatArray(actual, "actual").length().isLessThanOrEqualTo(-1, "expected"); }, RangeError); }); test("isLessThan", () => { const actual = [1]; - validators.requireThat(actual, "actual").length().isLessThan(2); + validators.requireThatArray(actual, "actual").length().isLessThan(2); }); test("isLessThan_False", () => @@ -93,18 +93,18 @@ suite("SizeTest", () => const actual: unknown[] = []; assert.throws(function() { - validators.requireThat(actual, "actual").length().isLessThan(0); + validators.requireThatArray(actual, "actual").length().isLessThan(0); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").length().isLessThan(0, "expected"); + validators.requireThatArray(actual, "actual").length().isLessThan(0, "expected"); }, RangeError); }); test("isNotPositive", () => { const actual: unknown[] = []; - validators.requireThat(actual, "actual").length().isNotPositive(); + validators.requireThatArray(actual, "actual").length().isNotPositive(); }); test("isNotPositive_False", () => @@ -112,14 +112,14 @@ suite("SizeTest", () => assert.throws(function() { const actual = [1, 2, 3]; - validators.requireThat(actual, "actual").length().isNotPositive(); + validators.requireThatArray(actual, "actual").length().isNotPositive(); }, RangeError); }); test("isPositive", () => { const actual = [1, 2, 3]; - validators.requireThat(actual, "actual").length().isPositive(); + validators.requireThatArray(actual, "actual").length().isPositive(); }); test("isPositive_False", () => @@ -127,14 +127,14 @@ suite("SizeTest", () => assert.throws(function() { const actual: unknown[] = []; - validators.requireThat(actual, "actual").length().isPositive(); + validators.requireThatArray(actual, "actual").length().isPositive(); }, RangeError); }); test("isNotZero", () => { const actual = [1, 2, 3]; - validators.requireThat(actual, "actual").length().isNotZero(); + validators.requireThatArray(actual, "actual").length().isNotZero(); }); test("isNotZero_False", () => @@ -142,14 +142,14 @@ suite("SizeTest", () => assert.throws(function() { const actual: unknown[] = []; - validators.requireThat(actual, "actual").length().isNotZero(); + validators.requireThatArray(actual, "actual").length().isNotZero(); }, RangeError); }); test("isZero", () => { const actual: unknown[] = []; - validators.requireThat(actual, "actual").length().isZero(); + validators.requireThatArray(actual, "actual").length().isZero(); }); test("isNotNegative", () => @@ -160,7 +160,7 @@ suite("SizeTest", () => `import {requireThat} from "./target/publish/node/index.mjs"; const actual: unknown[] = []; - validators.requireThat(actual, "actual").length().isNotNegative();`; + validators.requireThatArray(actual, "actual").length().isNotNegative();`; const messages = compiler.compile(code); assert.strictEqual(messages, "test.mts(4,34): error TS2339: Property 'isFalse' does not exist on " + "type 'ObjectVerifier'." + os.EOL); @@ -174,7 +174,7 @@ suite("SizeTest", () => `import {requireThat} from "./target/publish/node/index.mjs"; const actual: unknown[] = []; - validators.requireThat(actual, "actual").length().isNegative();`; + validators.requireThatArray(actual, "actual").length().isNegative();`; const messages = compiler.compile(code); assert.strictEqual(messages, "test.mts(4,34): error TS2339: Property 'isFalse' does not exist on " + "type 'ObjectVerifier'." + os.EOL); diff --git a/test/StringTest.mts b/test/StringTest.mts index 5dcce38..ef6c80d 100644 --- a/test/StringTest.mts +++ b/test/StringTest.mts @@ -21,27 +21,27 @@ suite("StringTest", () => { test("isEmpty", () => { - validators.requireThat("", "actual").isEmpty(); + validators.requireThatString("", "actual").isEmpty(); }); test("isEmpty_False", () => { assert.throws(function() { - validators.requireThat(" ", "actual").isEmpty(); + validators.requireThatString(" ", "actual").isEmpty(); }, RangeError); }); test("isTrimmed", () => { - validators.requireThat("", "actual").isTrimmed(); + validators.requireThatString("", "actual").isTrimmed(); }); test("isTrimmed_LeftSpace", () => { assert.throws(function() { - validators.requireThat(" value", "actual").isTrimmed(); + validators.requireThatString(" value", "actual").isTrimmed(); }, RangeError); }); @@ -49,20 +49,20 @@ suite("StringTest", () => { assert.throws(function() { - validators.requireThat("value ", "actual").isTrimmed(); + validators.requireThatString("value ", "actual").isTrimmed(); }, RangeError); }); test("isNotEmpty", () => { - validators.requireThat(" ", "actual").isNotEmpty(); + validators.requireThatString(" ", "actual").isNotEmpty(); }); test("isNotEmpty_False", () => { assert.throws(function() { - validators.requireThat("", "actual").isNotEmpty(); + validators.requireThatString("", "actual").isNotEmpty(); }, RangeError); }); @@ -70,7 +70,7 @@ suite("StringTest", () => { const prefix = "home"; const actual = prefix + "1234"; - validators.requireThat(actual, "actual").startsWith(prefix); + validators.requireThatString(actual, "actual").startsWith(prefix); }); test("startsWith_False", () => @@ -79,7 +79,7 @@ suite("StringTest", () => { const prefix = "home"; const actual = "1234" + prefix; - validators.requireThat(actual, "actual").startsWith(prefix); + validators.requireThatString(actual, "actual").startsWith(prefix); }, RangeError); }); @@ -87,7 +87,7 @@ suite("StringTest", () => { const prefix = "home"; const actual = "1234" + prefix; - validators.requireThat(actual, "actual").doesNotStartWith(prefix); + validators.requireThatString(actual, "actual").doesNotStartWith(prefix); }); test("doesNotStartWith_False", () => @@ -96,7 +96,7 @@ suite("StringTest", () => { const prefix = "home"; const actual = prefix + "1234"; - validators.requireThat(actual, "actual").doesNotStartWith(prefix); + validators.requireThatString(actual, "actual").doesNotStartWith(prefix); }, RangeError); }); @@ -104,7 +104,7 @@ suite("StringTest", () => { const expected = "cat"; const actual = "my " + expected + " is the best"; - validators.requireThat(actual, "actual").contains(expected); + validators.requireThatString(actual, "actual").contains(expected); }); test("contains_False", () => @@ -113,7 +113,7 @@ suite("StringTest", () => { const expected = "cat"; const actual = "my dog is the best"; - validators.requireThat(actual, "actual").contains(expected); + validators.requireThatString(actual, "actual").contains(expected); }, RangeError); }); @@ -121,7 +121,7 @@ suite("StringTest", () => { const value = "cat"; const actual = "my dog is the best"; - validators.requireThat(actual, "actual").doesNotContain(value); + validators.requireThatString(actual, "actual").doesNotContain(value); }); test("doesNotContain_False", () => @@ -130,14 +130,14 @@ suite("StringTest", () => { const value = "cat"; const actual = "my " + value + " is the best"; - validators.requireThat(actual, "actual").doesNotContain(value); + validators.requireThatString(actual, "actual").doesNotContain(value); }, RangeError); }); test("doesNotContainWhitespace", () => { const actual = "mydogisthebest"; - validators.requireThat(actual, "actual").doesNotContainWhitespace(); + validators.requireThatString(actual, "actual").doesNotContainWhitespace(); }); test("doesNotContainWhitespace_False", () => @@ -145,7 +145,7 @@ suite("StringTest", () => assert.throws(function() { const actual = "my dog is the best"; - validators.requireThat(actual, "actual").doesNotContainWhitespace(); + validators.requireThatString(actual, "actual").doesNotContainWhitespace(); }, RangeError); }); @@ -153,7 +153,7 @@ suite("StringTest", () => { const suffix = "home"; const actual = "1234" + suffix; - validators.requireThat(actual, "actual").endsWith(suffix); + validators.requireThatString(actual, "actual").endsWith(suffix); }); test("endsWith_False", () => @@ -162,7 +162,7 @@ suite("StringTest", () => { const suffix = "home"; const actual = suffix + "1234"; - validators.requireThat(actual, "actual").endsWith(suffix); + validators.requireThatString(actual, "actual").endsWith(suffix); }, RangeError); }); @@ -170,7 +170,7 @@ suite("StringTest", () => { const suffix = "home"; const actual = suffix + "1234"; - validators.requireThat(actual, "actual").doesNotEndWith(suffix); + validators.requireThatString(actual, "actual").doesNotEndWith(suffix); }); test("doesNotEndWith_False", () => @@ -179,14 +179,14 @@ suite("StringTest", () => { const suffix = "home"; const actual = "1234" + suffix; - validators.requireThat(actual, "actual").doesNotEndWith(suffix); + validators.requireThatString(actual, "actual").doesNotEndWith(suffix); }, RangeError); }); test("lengthIsEqualTo", () => { const actual = "value"; - validators.requireThat(actual, "actual").length().isEqualTo(actual.length); + validators.requireThatString(actual, "actual").length().isEqualTo(actual.length); }); test("lengthIsEqualTo_False", () => @@ -194,18 +194,18 @@ suite("StringTest", () => const actual = "value"; assert.throws(function() { - validators.requireThat(actual, "actual").length().isEqualTo(1); + validators.requireThatString(actual, "actual").length().isEqualTo(1); }, RangeError); assert.throws(function() { - validators.requireThat(actual, "actual").length().isEqualTo(actual.length + 1); + validators.requireThatString(actual, "actual").length().isEqualTo(actual.length + 1); }, RangeError); }); test("lengthIsNotEqualTo", () => { const actual = "value"; - validators.requireThat(actual, "actual").length().isNotEqualTo(actual.length + 1); + validators.requireThatString(actual, "actual").length().isNotEqualTo(actual.length + 1); }); test("lengthIsNotEqualTo_False", () => @@ -213,7 +213,7 @@ suite("StringTest", () => assert.throws(function() { const actual = "value"; - validators.requireThat(actual, "actual").length().isNotEqualTo(actual.length); + validators.requireThatString(actual, "actual").length().isNotEqualTo(actual.length); }, RangeError); }); @@ -222,20 +222,21 @@ suite("StringTest", () => const actual = " value "; assert.throws(function() { - validators.requireThat(actual, "actual").length().isEqualTo(actual.length + 1); + validators.requireThatString(actual, "actual").length().isEqualTo(actual.length + 1); }, RangeError); }); test("isTypeString", () => { const actual = "value"; - validators.requireThat(actual as unknown, "actual").isType(Type.STRING).isEqualTo(actual).getValue(); + validators.requireThatString(actual as unknown as string, "actual").isType(Type.STRING).isEqualTo(actual). + getValue(); }); test("getActual", () => { const input = "value"; - const output = validators.requireThat(input, "input").getValue(); + const output = validators.requireThatString(input, "input").getValue(); assert.strictEqual(output, input); }); @@ -246,12 +247,13 @@ suite("StringTest", () => const expectedMessages = [`\ "actual" must be a string. actual: null`, -`"actual" must be equal to "${expected}". + `"actual" must be equal to "${expected}". actual: null`]; - const actualFailures = validators.checkIf(actual, "actual").isType(Type.STRING).isEqualTo(expected). + const actualFailures = validators.checkIfString(actual, "actual").isType(Type.STRING).isEqualTo(expected). elseGetFailures(); const actualMessages = actualFailures.getFailures().map(failure => failure.getMessage()); - validators.requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages, "expectedMessages"); + validators.requireThatArray(actualMessages, "actualMessages"). + isEqualTo(expectedMessages, "expectedMessages"); }); }); \ No newline at end of file diff --git a/test/ValidationFailureTest.mts b/test/ValidationFailureTest.mts index b70556b..4e95381 100644 --- a/test/ValidationFailureTest.mts +++ b/test/ValidationFailureTest.mts @@ -6,7 +6,7 @@ import {assert} from "chai"; import { Configuration, TerminalEncoding, - requireThat + requireThatArray } from "../src/index.mjs"; import { ValidationFailureImpl, @@ -29,7 +29,6 @@ suite("ValidationFailureTest", () => let errorType: undefined; let message: undefined; - // eslint-disable-next-line no-new new ValidationFailureImpl(configuration as unknown as Configuration, message as unknown as string, errorType as unknown as ErrorBuilder); }, AssertionError); @@ -40,7 +39,6 @@ suite("ValidationFailureTest", () => assert.throws(function() { let type: undefined; - // eslint-disable-next-line no-new new ValidationFailureImpl(Configuration.DEFAULT, "message.", type as unknown as ErrorBuilder); }, AssertionError); }); @@ -51,7 +49,6 @@ suite("ValidationFailureTest", () => { let message: undefined; - // eslint-disable-next-line no-new new ValidationFailureImpl(Configuration.DEFAULT, message as unknown as string, RangeError); }, TypeError); }); @@ -60,10 +57,9 @@ suite("ValidationFailureTest", () => { const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), Configuration.DEFAULT); - const validator = validators.requireThat("value", "actual") as StringValidatorImpl; + const validator = validators.requireThatString("value", "actual") as StringValidatorImpl; const valueNotString = 12345; - // eslint-disable-next-line no-new new ValidationFailureImpl(Configuration.DEFAULT, new MessageBuilder(validator, "message."). withContext(valueNotString, "key").toString(), RangeError); }); @@ -74,10 +70,9 @@ suite("ValidationFailureTest", () => { const validators = new JavascriptValidatorsImpl(new TestApplicationScope(TerminalEncoding.NONE), Configuration.DEFAULT); - const validator = validators.requireThat("value", "actual") as StringValidatorImpl; + const validator = validators.requireThatString("value", "actual") as StringValidatorImpl; const key = null; - // eslint-disable-next-line no-new new ValidationFailureImpl(Configuration.DEFAULT, new MessageBuilder(validator, "message."). withContext(key as unknown as string, null as unknown as string).toString(), RangeError); }, TypeError); @@ -88,7 +83,7 @@ suite("ValidationFailureTest", () => const validators = new JavascriptValidatorsImpl( new TestApplicationScope(TerminalEncoding.NODE_16_COLORS), Configuration.DEFAULT); validators.updateConfiguration(c => c.allowDiff(false)); - const validator = validators.requireThat("value", "actual") as StringValidatorImpl; + validators.requireThatString("value", "actual") as StringValidatorImpl; const actual = "int[6]"; const expected = "int[5]"; @@ -97,9 +92,9 @@ suite("ValidationFailureTest", () => actual: "int[6]"`; const expectedMessages = [expectedMessage]; - const actualFailures = validators.checkIf(actual, "actual"). + const actualFailures = validators.checkIfString(actual, "actual"). isEqualTo(expected).elseGetFailures(); const actualMessages = actualFailures.getFailures().map(failure => failure.getMessage()); - requireThat(actualMessages, "actualMessages").isEqualTo(expectedMessages, "expectedMessages"); + requireThatArray(actualMessages, "actualMessages").isEqualTo(expectedMessages, "expectedMessages"); }); }); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 8700812..f8a3726 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,10 +4,10 @@ "compilerOptions": { "preserveConstEnums": true, "allowSyntheticDefaultImports": true, - "target": "ES2023", + "target": "ES2022", "lib": [ "DOM", - "ES2023" + "ES2022" ], "module": "Node16", "moduleResolution": "Node16", @@ -19,15 +19,14 @@ }, "include": [ // Files used to generate the target directory - "./src" + "./src", + "./test" ], "exclude": [ "node_modules", "target", - "./eslint.config.d.mts", - "./eslint.config.mjs", + "eslint.config.mjs", "./build", - "./test", "./test/TestGlobalConfiguration.mts" ], "declaration": true From 259e45ae6005e92107bd9e449db8badb10f8c8a4 Mon Sep 17 00:00:00 2001 From: Gili Tzabari Date: Sun, 8 Sep 2024 12:44:52 -0400 Subject: [PATCH 06/15] * Added the value's expected type to the function name (e.g. requireThatString() instead of requireThat()). This is the only way to ensure that the value's compile-time and runtime types match. * Previous validator implementations failed to take undefined values into consideration. --- build/Project.mts | 2 - eslint.config.mjs | 3 ++ src/DefaultJavascriptValidators.mts | 29 ++++++------ src/JavascriptAssertThat.mts | 6 +-- src/JavascriptCheckIf.mts | 6 +-- src/JavascriptRequireThat.mts | 6 +-- src/JavascriptValidators.mts | 11 ++--- src/index.mts | 8 ++-- src/internal/internal.mts | 20 ++++----- src/internal/util/AssertionError.mts | 2 +- src/internal/validator/AbstractValidator.mts | 20 ++++----- .../validator/JavascriptValidatorsImpl.mts | 43 +++++++++--------- ...datorImpl.mts => UnknownValidatorImpl.mts} | 10 ++--- ...jectValidator.mts => UnknownValidator.mts} | 4 +- .../component/ValidatorComponent.mts | 12 ++--- test/GenericTest.mts | 45 +++++++++---------- test/RequirementsTest.mts | 6 +-- 17 files changed, 112 insertions(+), 121 deletions(-) rename src/internal/validator/{ObjectValidatorImpl.mts => UnknownValidatorImpl.mts} (86%) rename src/validator/{ObjectValidator.mts => UnknownValidator.mts} (82%) diff --git a/build/Project.mts b/build/Project.mts index f3d1773..f37689f 100644 --- a/build/Project.mts +++ b/build/Project.mts @@ -79,7 +79,6 @@ class Project /** * @param sources - the files to compile - * @private */ private async bundleForNode(sources: string[]) { @@ -410,7 +409,6 @@ class Project /** * @returns the resources in the project - * @private */ private getResourceFiles() { diff --git a/eslint.config.mjs b/eslint.config.mjs index a17fee7..d7fca53 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -60,6 +60,9 @@ export default tsEslint.config( "@typescript-eslint/restrict-template-expressions": [ "error", { + allowArray: true, + allowBoolean: true, + allowNullish: true, allowNumber: true } ] diff --git a/src/DefaultJavascriptValidators.mts b/src/DefaultJavascriptValidators.mts index 32e111a..5cc81cb 100644 --- a/src/DefaultJavascriptValidators.mts +++ b/src/DefaultJavascriptValidators.mts @@ -5,7 +5,7 @@ import { type ArrayValidator, type SetValidator, type MapValidator, - type ObjectValidator, + type UnknownValidator, Configuration, JavascriptValidatorsImpl, MainApplicationScope, @@ -153,7 +153,7 @@ function requireThatString } /** - * Validates the state of an object. + * Validates the state of an unknown value or a value that does not have a specialized validator. *

            * The returned validator throws an exception immediately if a validation fails. This exception is then * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. @@ -165,10 +165,9 @@ function requireThatString * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` is empty */ -function requireThatObject -(value: T, name: string): ObjectValidator +function requireThat(value: T, name: string): UnknownValidator { - return DELEGATE.requireThatObject(value, name); + return DELEGATE.requireThat(value, name); } /** @@ -290,7 +289,7 @@ function assertThatString } /** - * Validates the state of a number. + * Validates the state of an unknown value or a value that does not have a specialized validator. *

            * The returned validator throws an exception immediately if a validation fails. This exception is then * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. @@ -302,10 +301,9 @@ function assertThatString * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` is empty */ -function assertThatObject -(value: T, name?: string): ObjectValidator +function assertThat(value: T, name?: string): UnknownValidator { - return DELEGATE.assertThatObject(value, name); + return DELEGATE.assertThat(value, name); } /** @@ -421,7 +419,7 @@ function checkIfString } /** - * Validates the state of an object. + * Validates the state of an unknown value or a value that does not have a specialized validator. *

            * The returned validator throws an error immediately if a validation fails. * @@ -435,10 +433,9 @@ function checkIfString * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` is empty */ -function checkIfObject -(value: T, name: string): ObjectValidator +function checkIf(value: T, name: string): UnknownValidator { - return DELEGATE.checkIfObject(value, name); + return DELEGATE.checkIf(value, name); } /** @@ -521,21 +518,21 @@ export { requireThatSet, requireThatMap, requireThatString, - requireThatObject, + requireThat, assertThatNumber, assertThatBoolean, assertThatArray, assertThatSet, assertThatMap, assertThatString, - assertThatObject, + assertThat, checkIfNumber, checkIfBoolean, checkIfArray, checkIfSet, checkIfMap, checkIfString, - checkIfObject, + checkIf, updateConfiguration, getContext, withContext, diff --git a/src/JavascriptAssertThat.mts b/src/JavascriptAssertThat.mts index 099b84f..ce304d5 100644 --- a/src/JavascriptAssertThat.mts +++ b/src/JavascriptAssertThat.mts @@ -9,7 +9,7 @@ import { type MapValidator, type StringValidator, type NumberValidator, - type ObjectValidator, + type UnknownValidator, type ArrayValidator, AssertionError } from "./internal/internal.mjs"; @@ -122,7 +122,7 @@ interface JavascriptAssertThat assertThatString(value: T, name?: string): StringValidator; /** - * Validates the state of an `object`. + * Validates the state of an unknown value or a value that does not have a specialized validator. *

            * The returned validator throws an error immediately if a validation fails. This error is then * converted into an {@link AssertionError}. Errors unrelated to validation failures are not converted. @@ -134,7 +134,7 @@ interface JavascriptAssertThat * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - assertThatObject(value: T, name?: string): ObjectValidator; + assertThat(value: T, name?: string): UnknownValidator; } export type {JavascriptAssertThat}; \ No newline at end of file diff --git a/src/JavascriptCheckIf.mts b/src/JavascriptCheckIf.mts index ea58a79..2e8f144 100644 --- a/src/JavascriptCheckIf.mts +++ b/src/JavascriptCheckIf.mts @@ -7,7 +7,7 @@ import { type BooleanValidator, type StringValidator, type NumberValidator, - type ObjectValidator, + type UnknownValidator, type ArrayValidator, type SetValidator, type MapValidator @@ -120,7 +120,7 @@ interface JavascriptCheckIf checkIfString(value: T, name: string): StringValidator; /** - * Validates the state of an `object`. + * Validates the state of an unknown value or a value that does not have a specialized validator. *

            * The returned validator captures errors on validation failure rather than throwing them immediately. * These errors can be retrieved or thrown once the validation completes. Errors unrelated to @@ -133,7 +133,7 @@ interface JavascriptCheckIf * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - checkIfObject(value: T, name: string): ObjectValidator; + checkIf(value: T, name: string): UnknownValidator; } export type {JavascriptCheckIf}; \ No newline at end of file diff --git a/src/JavascriptRequireThat.mts b/src/JavascriptRequireThat.mts index 80ab78d..08c2ed8 100644 --- a/src/JavascriptRequireThat.mts +++ b/src/JavascriptRequireThat.mts @@ -10,7 +10,7 @@ import { type SetValidator, type MapValidator, type StringValidator, - type ObjectValidator + type UnknownValidator } from "./internal/internal.mjs"; /** @@ -107,7 +107,7 @@ interface JavascriptRequireThat requireThatString(value: T, name: string): StringValidator; /** - * Validates the state of an `object`. + * Validates the state of an unknown value or a value that does not have a specialized validator. *

            * The returned validator throws an error immediately if a validation fails. * @@ -118,7 +118,7 @@ interface JavascriptRequireThat * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` contains whitespace or is empty */ - requireThatObject(value: T, name: string): ObjectValidator; + requireThat(value: T, name: string): UnknownValidator; } export type {JavascriptRequireThat}; \ No newline at end of file diff --git a/src/JavascriptValidators.mts b/src/JavascriptValidators.mts index 6ca1e5c..5d49e64 100644 --- a/src/JavascriptValidators.mts +++ b/src/JavascriptValidators.mts @@ -18,7 +18,7 @@ import { type JavascriptCheckIf, Configuration, type GlobalConfiguration, - type ObjectValidator, + type UnknownValidator, AssertionError } from "./internal/internal.mjs"; @@ -83,8 +83,7 @@ abstract class JavascriptValidators abstract requireThatString (value: T, name: string): StringValidator; - abstract requireThatObject - (value: T, name: string): ObjectValidator; + abstract requireThat(value: T, name: string): UnknownValidator; abstract assertThatNumber (value: T, name?: string): NumberValidator; @@ -104,8 +103,7 @@ abstract class JavascriptValidators abstract assertThatString (value: T, name?: string): StringValidator; - abstract assertThatObject - (value: T, name?: string): ObjectValidator; + abstract assertThat(value: T, name?: string): UnknownValidator; abstract checkIfNumber (value: T, name?: string): NumberValidator; @@ -125,8 +123,7 @@ abstract class JavascriptValidators abstract checkIfString (value: T, name?: string): StringValidator; - abstract checkIfObject - (value: T, name?: string): ObjectValidator; + abstract checkIf(value: T, name?: string): UnknownValidator; } export {JavascriptValidators}; \ No newline at end of file diff --git a/src/index.mts b/src/index.mts index 7b3cdc9..a7bae41 100644 --- a/src/index.mts +++ b/src/index.mts @@ -5,21 +5,21 @@ export { requireThatSet, requireThatMap, requireThatString, - requireThatObject, + requireThat, assertThatNumber, assertThatBoolean, assertThatArray, assertThatSet, assertThatMap, assertThatString, - assertThatObject, + assertThat, checkIfNumber, checkIfBoolean, checkIfArray, checkIfSet, checkIfMap, checkIfString, - checkIfObject, + checkIf, updateConfiguration, getContext, withContext, @@ -35,7 +35,7 @@ export { type MapValidator, type NumberValidator, type BooleanValidator, - type ObjectValidator, + type UnknownValidator, type ElementOf, type MapKey, type MapValue, diff --git a/src/internal/internal.mts b/src/internal/internal.mts index cdc7502..778b57b 100644 --- a/src/internal/internal.mts +++ b/src/internal/internal.mts @@ -9,7 +9,7 @@ // internal.js determines the library-wide loading order. // Dependencies must be loaded before dependents. -import type {ObjectValidator} from "../validator/ObjectValidator.mjs"; +import type {UnknownValidator} from "../validator/UnknownValidator.mjs"; import { Type, TypeCategory @@ -68,7 +68,7 @@ import {AbstractApplicationScope} from "./scope/AbstractApplicationScope.mjs"; import {MainApplicationScope} from "./scope/MainApplicationScope.mjs"; import {Configuration} from "./Configuration.mjs"; import {JavascriptValidatorsImpl} from "./validator/JavascriptValidatorsImpl.mjs"; -import {ObjectValidatorImpl} from "./validator/ObjectValidatorImpl.mjs"; +import {UnknownValidatorImpl} from "./validator/UnknownValidatorImpl.mjs"; import type {MapValidator} from "../validator/MapValidator.mjs"; import {MapValidatorImpl} from "./validator/MapValidatorImpl.mjs"; import type {NumberValidator} from "../validator/NumberValidator.mjs"; @@ -101,21 +101,21 @@ import { requireThatSet, requireThatMap, requireThatString, - requireThatObject, + requireThat, assertThatNumber, assertThatBoolean, assertThatArray, assertThatSet, assertThatMap, assertThatString, - assertThatObject, + assertThat, checkIfNumber, checkIfBoolean, checkIfArray, checkIfSet, checkIfMap, checkIfString, - checkIfObject, + checkIf, updateConfiguration, getContext, withContext, @@ -303,7 +303,7 @@ export quoteString, AbstractValidators, AbstractValidator, - ObjectValidatorImpl, + UnknownValidatorImpl, Pluralizer, requireThatNumber, requireThatBoolean, @@ -311,21 +311,21 @@ export requireThatSet, requireThatMap, requireThatString, - requireThatObject, + requireThat, assertThatNumber, assertThatBoolean, assertThatArray, assertThatSet, assertThatMap, assertThatString, - assertThatObject, + assertThat, checkIfNumber, checkIfBoolean, checkIfArray, checkIfSet, checkIfMap, checkIfString, - checkIfObject, + checkIf, updateConfiguration, getContext, withContext, @@ -435,7 +435,7 @@ export type MapValidator, NumberValidator, BooleanValidator, - ObjectValidator, + UnknownValidator, ElementOf, MapKey, MapValue, diff --git a/src/internal/util/AssertionError.mts b/src/internal/util/AssertionError.mts index 2089539..8ff2b02 100644 --- a/src/internal/util/AssertionError.mts +++ b/src/internal/util/AssertionError.mts @@ -9,7 +9,7 @@ class AssertionError extends Error * @param message - an explanation of what went wrong * @param options - configuration options */ - constructor(message?: string, options?: { cause: Error }) + constructor(message?: string, options?: { cause: unknown }) { super(message, options); this.name = this.constructor.name; diff --git a/src/internal/validator/AbstractValidator.mts b/src/internal/validator/AbstractValidator.mts index 20261ac..6e575c8 100644 --- a/src/internal/validator/AbstractValidator.mts +++ b/src/internal/validator/AbstractValidator.mts @@ -23,7 +23,7 @@ import { ValidationFailures, messagesIsEqualTo, messagesIsUndefined, - type ObjectValidator, + type UnknownValidator, messagesIsNull, type ClassConstructor, messagesIsNotUndefined, @@ -263,7 +263,7 @@ different name.`); this.addTypeError( messagesIsUndefined(this).toString()); } - return this as unknown as ObjectValidator; + return this as unknown as UnknownValidator; } public isNotUndefined() @@ -273,7 +273,7 @@ different name.`); this.addTypeError( messagesIsUndefined(this).toString()); } - return this as ObjectValidator>; + return this as UnknownValidator>; } @@ -284,7 +284,7 @@ different name.`); this.addTypeError( messagesIsNull(this).toString()); } - return this as unknown as ObjectValidator; + return this as unknown as UnknownValidator; } public isNotNull() @@ -294,7 +294,7 @@ different name.`); this.addTypeError( messagesIsNotNull(this).toString()); } - return this as ObjectValidator>; + return this as UnknownValidator>; } /** @@ -325,7 +325,7 @@ different name.`); public isType(expected: Type): this { - JavascriptValidatorsImpl.INTERNAL.requireThatObject(expected, "expected").isNotNull(); + JavascriptValidatorsImpl.INTERNAL.requireThat(expected, "expected").isNotNull(); if (this.value.map(v => !Type.of(v).equals(expected)).or(true)) { this.addTypeError( @@ -334,17 +334,17 @@ different name.`); return this; } - public isInstanceOf(expected: ClassConstructor): ObjectValidator + public isInstanceOf(expected: ClassConstructor): UnknownValidator { - JavascriptValidatorsImpl.INTERNAL.requireThatObject(expected, "expected").isNotNull(); + JavascriptValidatorsImpl.INTERNAL.requireThat(expected, "expected").isNotNull(); const className = Type.of(expected).name; this.validateType(Type.namedClass(className), true); - return this as unknown as ObjectValidator; + return this as unknown as UnknownValidator; } public isNotInstanceOf(expected: ClassConstructor): this { - JavascriptValidatorsImpl.INTERNAL.requireThatObject(expected, "expected").isNotNull(); + JavascriptValidatorsImpl.INTERNAL.requireThat(expected, "expected").isNotNull(); const className = Type.of(expected).name; this.validateType(Type.namedClass(className), false); return this; diff --git a/src/internal/validator/JavascriptValidatorsImpl.mts b/src/internal/validator/JavascriptValidatorsImpl.mts index 9ed2f91..30a715e 100644 --- a/src/internal/validator/JavascriptValidatorsImpl.mts +++ b/src/internal/validator/JavascriptValidatorsImpl.mts @@ -4,14 +4,14 @@ import { NumberValidatorImpl, SetValidatorImpl, MapValidatorImpl, - ObjectValidatorImpl, + UnknownValidatorImpl, Configuration, AbstractValidators, type BooleanValidator, type StringValidator, type NumberValidator, type SetValidator, - type ObjectValidator, + type UnknownValidator, type MapValidator, type ArrayValidator, ArrayValidatorImpl, @@ -208,7 +208,7 @@ class JavascriptValidatorsImpl extends AbstractValidators } /** - * Validates the state of an object. + * Validates the state of an unknown value or a value that does not have a specialized validator. *

            * The returned validator throws an error immediately if a validation fails. * @@ -219,11 +219,10 @@ class JavascriptValidatorsImpl extends AbstractValidators * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` is empty */ - public requireThatObject - (value: T, name: string): ObjectValidator + public requireThat(value: T, name: string): UnknownValidator { verifyName(name, "name"); - return this.validateObject(value, name, this.getRequireThatConfiguration()); + return this.validateUnknown(value, name, this.getRequireThatConfiguration()); } /** @@ -345,7 +344,7 @@ class JavascriptValidatorsImpl extends AbstractValidators } /** - * Validates the state of an object. + * Validates the state of an unknown value or a value that does not have a specialized validator. *

            * The returned validator throws an exception immediately if a validation fails. This exception is then * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. @@ -357,10 +356,9 @@ class JavascriptValidatorsImpl extends AbstractValidators * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` is empty */ - public assertThatObject - (value: T, name?: string): ObjectValidator + public assertThat(value: T, name?: string): UnknownValidator { - return this.validateObject(value, name, this.getAssertThatConfiguration()); + return this.validateUnknown(value, name, this.getAssertThatConfiguration()); } /** @@ -476,7 +474,7 @@ class JavascriptValidatorsImpl extends AbstractValidators } /** - * Validates the state of an object. + * Validates the state of an unknown value or a value that does not have a specialized validator. *

            * The returned validator throws an error immediately if a validation fails. * @@ -490,13 +488,12 @@ class JavascriptValidatorsImpl extends AbstractValidators * @throws TypeError if `name` is `undefined` or `null` * @throws RangeError if `name` is empty */ - public checkIfObject - (value: T, name?: string): ObjectValidator + public checkIf(value: T, name?: string): UnknownValidator { - return this.validateObject(value, name, this.getCheckIfConfiguration()); + return this.validateUnknown(value, name, this.getCheckIfConfiguration()); } - public validateNumber + private validateNumber (value: T, name: string | undefined, configuration: Configuration): NumberValidator { if (name === undefined) @@ -534,7 +531,7 @@ class JavascriptValidatorsImpl extends AbstractValidators } } - public validateBoolean + private validateBoolean (value: T, name: string | undefined, configuration: Configuration): BooleanValidator { if (name === undefined) @@ -548,7 +545,7 @@ class JavascriptValidatorsImpl extends AbstractValidators return validator; } - public validateArray + private validateArray (value: T, name: string | undefined, configuration: Configuration): ArrayValidator { if (name === undefined) @@ -562,7 +559,7 @@ class JavascriptValidatorsImpl extends AbstractValidators return validator; } - public validateSet | undefined | null, E> + private validateSet | undefined | null, E> (value: T, name: string | undefined, configuration: Configuration): SetValidator { if (name === undefined) @@ -576,7 +573,7 @@ class JavascriptValidatorsImpl extends AbstractValidators return validator; } - public validateMap | undefined | null, K, V> + private validateMap | undefined | null, K, V> (value: T, name: string | undefined, configuration: Configuration): MapValidator { if (name === undefined) @@ -590,7 +587,7 @@ class JavascriptValidatorsImpl extends AbstractValidators return validator; } - public validateString + private validateString (value: T, name: string | undefined, configuration: Configuration): StringValidator { if (name === undefined) @@ -603,14 +600,14 @@ class JavascriptValidatorsImpl extends AbstractValidators return validator; } - public validateObject - (value: T, name: string | undefined, configuration: Configuration): ObjectValidator + private validateUnknown(value: T, name: string | undefined, + configuration: Configuration): UnknownValidator { if (name === undefined) name = JavascriptValidatorsImpl.DEFAULT_NAME; else verifyName(name, "name"); - const validator = new ObjectValidatorImpl(this.scope, configuration, name, + const validator = new UnknownValidatorImpl(this.scope, configuration, name, ValidationTarget.valid(value), new Map(), []); this.validateType(validator, value, Type.namedClass(null)); return validator; diff --git a/src/internal/validator/ObjectValidatorImpl.mts b/src/internal/validator/UnknownValidatorImpl.mts similarity index 86% rename from src/internal/validator/ObjectValidatorImpl.mts rename to src/internal/validator/UnknownValidatorImpl.mts index 385cf18..cac3cad 100644 --- a/src/internal/validator/ObjectValidatorImpl.mts +++ b/src/internal/validator/UnknownValidatorImpl.mts @@ -1,6 +1,6 @@ import { type Configuration, - type ObjectValidator, + type UnknownValidator, type ValidationFailure, type ApplicationScope, AbstractValidator, @@ -9,13 +9,13 @@ import { } from "../internal.mjs"; /** - * Default implementation of `BaseValidator`. + * Default implementation of `UnknownValidator`. * * @typeParam T - the type the value */ -class ObjectValidatorImpl +class UnknownValidatorImpl extends AbstractValidator - implements ObjectValidator + implements UnknownValidator { /** * @param scope - the application configuration @@ -35,4 +35,4 @@ class ObjectValidatorImpl } } -export {ObjectValidatorImpl}; \ No newline at end of file +export {UnknownValidatorImpl}; \ No newline at end of file diff --git a/src/validator/ObjectValidator.mts b/src/validator/UnknownValidator.mts similarity index 82% rename from src/validator/ObjectValidator.mts rename to src/validator/UnknownValidator.mts index 945b417..1d7aa31 100644 --- a/src/validator/ObjectValidator.mts +++ b/src/validator/UnknownValidator.mts @@ -10,6 +10,6 @@ import {type ValidatorComponent} from "../internal/internal.mjs"; * * @typeParam T - the type of the value that is being validated */ -type ObjectValidator = ValidatorComponent; +type UnknownValidator = ValidatorComponent; -export type {ObjectValidator}; \ No newline at end of file +export type {UnknownValidator}; \ No newline at end of file diff --git a/src/validator/component/ValidatorComponent.mts b/src/validator/component/ValidatorComponent.mts index 7d47e93..ae99c89 100644 --- a/src/validator/component/ValidatorComponent.mts +++ b/src/validator/component/ValidatorComponent.mts @@ -3,7 +3,7 @@ import { ValidationFailures, type NonUndefinable, type ClassConstructor, - type ObjectValidator, + type UnknownValidator, type Validators } from "../../internal/internal.mjs"; @@ -141,7 +141,7 @@ interface ValidatorComponent * @returns this * @throws TypeError if the value is not `undefined` */ - isUndefined(): ObjectValidator; + isUndefined(): UnknownValidator; /** * Ensures that the value is not undefined. @@ -154,7 +154,7 @@ interface ValidatorComponent * @returns this * @throws TypeError if the value is `undefined` */ - isNotUndefined(): ObjectValidator>; + isNotUndefined(): UnknownValidator>; /** * Ensures that the value is `null`. @@ -162,7 +162,7 @@ interface ValidatorComponent * @returns this * @throws TypeError if the value is not `null` */ - isNull(): ObjectValidator; + isNull(): UnknownValidator; /** * Ensures that the value is not `null`. @@ -175,7 +175,7 @@ interface ValidatorComponent * @returns this * @throws TypeError if the value is `null` */ - isNotNull(): ObjectValidator>; + isNotNull(): UnknownValidator>; /** * Ensures that the object is an instance of a class. @@ -186,7 +186,7 @@ interface ValidatorComponent * @throws TypeError if the value or `expected` are `undefined` or `null` * @throws RangeError if the value is not an instance of the desired class */ - isInstanceOf(expected: ClassConstructor): ObjectValidator; + isInstanceOf(expected: ClassConstructor): UnknownValidator; /** * Ensures that the value has the specified type. diff --git a/test/GenericTest.mts b/test/GenericTest.mts index 85138ac..e0c5730 100644 --- a/test/GenericTest.mts +++ b/test/GenericTest.mts @@ -5,7 +5,7 @@ import { import {assert} from "chai"; import { TerminalEncoding, - type ObjectValidator, + type UnknownValidator, Configuration, Type } from "../src/index.mjs"; @@ -32,8 +32,7 @@ suite("BaseTest", () => assert.throws(function() { const actual = {} as object | null; - // Changes the compile-time type of the value to null - validators.requireThatObject(actual, null as unknown as string).isNull().getValue(); + validators.requireThat(actual, null as unknown as string).isNull().getValue(); }, TypeError); }); @@ -42,7 +41,7 @@ suite("BaseTest", () => assert.throws(function() { const actual = {}; - validators.requireThatObject(actual, ""); + validators.requireThat(actual, ""); }, RangeError); }); @@ -57,11 +56,11 @@ suite("BaseTest", () => const actual = {}; assert.throws(function() { - validators.requireThatObject(actual, "actual").isEqualTo("expected"); + validators.requireThat(actual, "actual").isEqualTo("expected"); }, RangeError); assert.throws(function() { - validators.requireThatObject(actual, "actual").isEqualTo("expected", "expected"); + validators.requireThat(actual, "actual").isEqualTo("expected", "expected"); }, RangeError); }); @@ -82,7 +81,7 @@ suite("BaseTest", () => test("isEqual_nullToNull", () => { const actual = null; - validators.requireThatObject(actual, "actual").isEqualTo(actual); + validators.requireThat(actual, "actual").isEqualTo(actual); }); test("isEqualTo_nullToNotNull", () => @@ -124,24 +123,24 @@ assignable to parameter of type 'null'.` + os.EOL); const actual = {}; assert.throws(function() { - validators.requireThatObject(actual, "actual").isNotEqualTo(actual); + validators.requireThat(actual, "actual").isNotEqualTo(actual); }, RangeError); assert.throws(function() { - validators.requireThatObject(actual, "actual").isNotEqualTo(actual, "actual"); + validators.requireThat(actual, "actual").isNotEqualTo(actual, "actual"); }, RangeError); }); test("getType_isUndefined", () => { const actual = undefined; - validators.requireThatObject(actual, "actual").isUndefined(); + validators.requireThat(actual, "actual").isUndefined(); }); test("getType_isNull", () => { const actual = null; - validators.requireThatObject(actual, "actual").isNull(); + validators.requireThat(actual, "actual").isNull(); }); class Person @@ -159,19 +158,19 @@ assignable to parameter of type 'null'.` + os.EOL); test("getType_isObject", () => { const actual = new Person("John Smith", 32); - validators.requireThatObject(actual, "actual").isType(Type.namedClass(null)); + validators.requireThat(actual, "actual").isType(Type.namedClass(null)); }); test("getType_isType", () => { const actual = Type.of(new Person("name", 5)); - validators.requireThatObject(actual, "actual").isType(Type.namedClass("Type")); + validators.requireThat(actual, "actual").isType(Type.namedClass("Type")); }); test("isInstanceOf", () => { const actual = new Person("name", 5); - validators.requireThatObject(actual, "actual").isInstanceOf(Person).getValue(); + validators.requireThat(actual, "actual").isInstanceOf(Person).getValue(); }); test("isInstanceOf_actualIsNull", () => @@ -179,7 +178,7 @@ assignable to parameter of type 'null'.` + os.EOL); assert.throws(function() { const actual = null; - validators.requireThatObject(actual, "actual").isInstanceOf(String); + validators.requireThat(actual, "actual").isInstanceOf(String); }, TypeError); }); @@ -188,7 +187,7 @@ assignable to parameter of type 'null'.` + os.EOL); assert.throws(function() { const actual = {}; - validators.requireThatObject(actual, "actual").isInstanceOf(String); + validators.requireThat(actual, "actual").isInstanceOf(String); }, TypeError); }); test("isInstanceOf_Object", () => @@ -202,7 +201,7 @@ assignable to parameter of type 'null'.` + os.EOL); test("isNull", () => { - validators.requireThatObject(null, "actual").isNull(); + validators.requireThat(null, "actual").isNull(); }); test("isNull_False", () => @@ -210,7 +209,7 @@ assignable to parameter of type 'null'.` + os.EOL); assert.throws(function() { const actual = {} as object | null; - validators.requireThatObject(actual, "actual").isNull().getValue(); + validators.requireThat(actual, "actual").isNull().getValue(); }, TypeError); }); @@ -218,7 +217,7 @@ assignable to parameter of type 'null'.` + os.EOL); { const actual = {} as object | null; // Changes the compile-time type of the value to not-null - validators.requireThatObject(actual, "actual").isNotNull().getValue(); + validators.requireThat(actual, "actual").isNotNull().getValue(); }); test("isNotNull_False", () => @@ -226,14 +225,14 @@ assignable to parameter of type 'null'.` + os.EOL); assert.throws(function() { const actual = null; - validators.requireThatObject(actual, "actual").isNotNull(); + validators.requireThat(actual, "actual").isNotNull(); }, TypeError); }); test("isDefined", () => { const actual = 5; - const foo: ObjectValidator = validators.requireThatNumber(actual, "actual"); + const foo: UnknownValidator = validators.requireThatNumber(actual, "actual"); foo.isNotUndefined(); }); @@ -243,7 +242,7 @@ assignable to parameter of type 'null'.` + os.EOL); { let actual; // noinspection JSUnusedAssignment - validators.requireThatObject(actual, "actual").isNotUndefined(); + validators.requireThat(actual, "actual").isNotUndefined(); }, TypeError); }); @@ -251,7 +250,7 @@ assignable to parameter of type 'null'.` + os.EOL); { let actual; // noinspection JSUnusedAssignment - validators.requireThatObject(actual, "actual").isUndefined(); + validators.requireThat(actual, "actual").isUndefined(); }); test("isUndefined_False", () => diff --git a/test/RequirementsTest.mts b/test/RequirementsTest.mts index 627a286..65294f6 100644 --- a/test/RequirementsTest.mts +++ b/test/RequirementsTest.mts @@ -53,17 +53,17 @@ suite("RequirementsTest", () => test("assertThatUrl", () => { const actual = new URL("http://www.google.com/"); - validators.assertThatObject(actual, "actual").isEqualTo(actual, "expected"); + validators.assertThat(actual, "actual").isEqualTo(actual, "expected"); }); - test("assertThatObject", () => + test("assertThat", () => { assert.throws(function() { const localValidators = validators.copy(); const actual = {}; - localValidators.assertThatObject(actual, "actual").isEqualTo("expected"); + localValidators.assertThat(actual, "actual").isEqualTo("expected"); }, AssertionError); }); From 2df55ea60e810aea92ecff32b8d7b3a551fe5b6d Mon Sep 17 00:00:00 2001 From: Gili Tzabari Date: Sun, 8 Sep 2024 17:45:41 -0400 Subject: [PATCH 07/15] Removed unused dependencies --- package.json | 3 --- pnpm-lock.yaml | 21 --------------------- 2 files changed, 24 deletions(-) diff --git a/package.json b/package.json index 8acae74..7b14a7a 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,6 @@ "@types/chai": "^4.3.19", "@types/diff": "^5.2.2", "@types/eslint": "^9.6.1", - "@types/lodash": "^4.17.7", - "@types/lodash.debounce": "^4.0.9", "@types/lodash.isequal": "^4.5.8", "@types/minimist": "^1.2.5", "@types/mocha": "^10.0.7", @@ -65,7 +63,6 @@ "fancy-log": "^2.0.0", "glob": "^11.0.0", "install": "^0.13.0", - "lodash.debounce": "^4.0.8", "minimist": "^1.2.8", "mocha": "^10.7.3", "pnpm": "^9.9.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14ef503..4b979ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,12 +39,6 @@ importers: '@types/eslint': specifier: ^9.6.1 version: 9.6.1 - '@types/lodash': - specifier: ^4.17.7 - version: 4.17.7 - '@types/lodash.debounce': - specifier: ^4.0.9 - version: 4.0.9 '@types/lodash.isequal': specifier: ^4.5.8 version: 4.5.8 @@ -96,9 +90,6 @@ importers: install: specifier: ^0.13.0 version: 0.13.0 - lodash.debounce: - specifier: ^4.0.8 - version: 4.0.8 minimist: specifier: ^1.2.8 version: 1.2.8 @@ -579,9 +570,6 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/lodash.debounce@4.0.9': - resolution: {integrity: sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==} - '@types/lodash.isequal@4.5.8': resolution: {integrity: sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==} @@ -1397,9 +1385,6 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash.debounce@4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} @@ -2373,10 +2358,6 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/lodash.debounce@4.0.9': - dependencies: - '@types/lodash': 4.17.7 - '@types/lodash.isequal@4.5.8': dependencies: '@types/lodash': 4.17.7 @@ -3258,8 +3239,6 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash.debounce@4.0.8: {} - lodash.isequal@4.5.0: {} lodash.isfinite@3.3.2: {} From 319c3a7590534ec6354d54dddcbb0f953497c373 Mon Sep 17 00:00:00 2001 From: Gili Tzabari Date: Sun, 8 Sep 2024 17:47:58 -0400 Subject: [PATCH 08/15] Upgraded Github Actions --- .github/workflows/build.yml | 6 +++--- package.json | 2 ++ pnpm-lock.yaml | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5a1955f..2ebc301 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,10 +6,10 @@ jobs: name: Build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: latest @@ -21,7 +21,7 @@ jobs: # pnpm dependencies cannot be cached until pnpm is installed # WORKAROUND: https://github.com/actions/setup-node/issues/531 - name: Extract cached dependencies - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: cache: pnpm diff --git a/package.json b/package.json index 7b14a7a..cbbe235 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@types/chai": "^4.3.19", "@types/diff": "^5.2.2", "@types/eslint": "^9.6.1", + "@types/lodash.debounce": "^4.0.9", "@types/lodash.isequal": "^4.5.8", "@types/minimist": "^1.2.5", "@types/mocha": "^10.0.7", @@ -63,6 +64,7 @@ "fancy-log": "^2.0.0", "glob": "^11.0.0", "install": "^0.13.0", + "lodash.debounce": "^4.0.8", "minimist": "^1.2.8", "mocha": "^10.7.3", "pnpm": "^9.9.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b979ca..00c821d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,6 +39,9 @@ importers: '@types/eslint': specifier: ^9.6.1 version: 9.6.1 + '@types/lodash.debounce': + specifier: ^4.0.9 + version: 4.0.9 '@types/lodash.isequal': specifier: ^4.5.8 version: 4.5.8 @@ -90,6 +93,9 @@ importers: install: specifier: ^0.13.0 version: 0.13.0 + lodash.debounce: + specifier: ^4.0.8 + version: 4.0.8 minimist: specifier: ^1.2.8 version: 1.2.8 @@ -570,6 +576,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/lodash.debounce@4.0.9': + resolution: {integrity: sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==} + '@types/lodash.isequal@4.5.8': resolution: {integrity: sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==} @@ -1385,6 +1394,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} @@ -2358,6 +2370,10 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/lodash.debounce@4.0.9': + dependencies: + '@types/lodash': 4.17.7 + '@types/lodash.isequal@4.5.8': dependencies: '@types/lodash': 4.17.7 @@ -3239,6 +3255,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.debounce@4.0.8: {} + lodash.isequal@4.5.0: {} lodash.isfinite@3.3.2: {} From fb2a8cebfbb074e6e8d06c8406ae76bdc2c9dc88 Mon Sep 17 00:00:00 2001 From: Gili Tzabari Date: Sun, 8 Sep 2024 17:52:52 -0400 Subject: [PATCH 09/15] * Fixed incorrect filename casing. * Fixed IDE warning about build directory being excluded from tsconfig.json. --- build/Project.mts | 81 ++++++++++++++---------------- build/mode.mts | 6 ++- src/internal/validator/Objects.mts | 2 +- tsconfig.json | 4 +- 4 files changed, 45 insertions(+), 48 deletions(-) diff --git a/build/Project.mts b/build/Project.mts index f37689f..2ce14cc 100644 --- a/build/Project.mts +++ b/build/Project.mts @@ -3,9 +3,9 @@ import path from "path"; import {ESLint} from "eslint"; import TypeDoc from "typedoc"; import fs from "node:fs"; -import rollupCommonjs from "@rollup/plugin-commonjs"; +import _rollupCommonJs from "@rollup/plugin-commonjs"; import {nodeResolve as rollupNodeResolve} from "@rollup/plugin-node-resolve"; -import rollupTypescript from "@rollup/plugin-typescript"; +import _rollupTypescript, {type RollupTypescriptOptions} from "@rollup/plugin-typescript"; import { type Plugin, rollup @@ -23,6 +23,9 @@ import {mode} from "./mode.mjs"; import parseArgs from "minimist"; import debounce from "lodash.debounce"; +// WORKAROUND: https://github.com/rollup/plugins/issues/1662#issuecomment-2337703188 +const rollupCommonJs = _rollupCommonJs as unknown as (options?: unknown) => Plugin; +const rollupTypescript = _rollupTypescript as unknown as (options?: RollupTypescriptOptions) => Plugin; class Project { @@ -39,8 +42,7 @@ class Project } /** - * @param sources the files to lint - * @private + * @param sources - the files to lint */ private async lintTypescript(sources: string[]) { @@ -65,7 +67,7 @@ class Project } catch (error) { - this.log.error(`Lint error: ${error}`); + this.log.error(`Lint error: ${JSON.stringify(error, null, 2)}`); throw error; } @@ -85,33 +87,36 @@ class Project console.time("bundleForNode"); try { - await Promise.all([this.lintTypescript(sources), this.compileTypescript(sources)]); + await this.lintTypescript(sources); } catch (error) { - this.log.error(`bundleForNode error: ${error}`); + this.log.error(`bundleForNode error: ${JSON.stringify(error, null, 2)}`); throw error; } + this.compileTypescript(sources); console.timeEnd("bundleForNode"); } /** - * @param sources the files to compile - * @private + * @param sources - the files to compile */ - private async compileTypescript(sources: string[]) + private compileTypescript(sources: string[]) { console.time("compileTypescript"); // Example of compiling using API: https://gist.github.com/jeremyben/4de4fdc40175d0f76892209e00ece98f const cwd = process.cwd(); - const configFile = ts.findConfigFile(cwd, ts.sys.fileExists, "tsconfig.json"); + const configFile = ts.findConfigFile(cwd, filename => ts.sys.fileExists(filename), "tsconfig.json"); if (!configFile) throw Error("tsconfig.json not found"); - const {config} = ts.readConfigFile(configFile, ts.sys.readFile); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const {config} = ts.readConfigFile(configFile, path => ts.sys.readFile(path)); + /* eslint-disable @typescript-eslint/no-unsafe-member-access */ config.compilerOptions.outDir = "target/publish/node/"; config.compilerOptions.declaration = true; config.include = undefined; config.files = sources; + /* eslint-enable @typescript-eslint/no-unsafe-member-access */ const { options, @@ -134,7 +139,7 @@ class Project { const formatHost: ts.FormatDiagnosticsHost = { getCanonicalFileName: (path) => path, - getCurrentDirectory: ts.sys.getCurrentDirectory, + getCurrentDirectory: () => ts.sys.getCurrentDirectory(), getNewLine: () => ts.sys.newLine }; const message = ts.formatDiagnostics(allDiagnostics, formatHost); @@ -149,12 +154,8 @@ class Project { console.time("bundleForBrowser"); - // Need to cast plugins to Function due to bug in type definitions. - // WORKAROUND: - // https://github.com/algolia/algoliasearch-client-javascript/issues/1431#issuecomment-1568529321 const plugins: Plugin[] = [ - rollupCommonjs, - (rollupTypescript as unknown as Function)({ + rollupTypescript({ "module": "ES2022", "moduleResolution": "bundler" }), @@ -163,7 +164,7 @@ class Project mainFields: ["module"], preferBuiltins: true }), - (rollupCommonjs as unknown as Function)({include: "node_modules/**"}) + rollupCommonJs({include: "node_modules/**"}) ]; try @@ -194,7 +195,7 @@ class Project } catch (error) { - this.log.error(`bundleForBrowser error: ${error}`); + this.log.error(`bundleForBrowser error: ${JSON.stringify(error, null, 2)}`); throw error; } console.timeEnd("bundleForBrowser"); @@ -202,8 +203,6 @@ class Project /** * Minify the source-code. - * - * @private */ private async minifyBrowserSources() { @@ -222,13 +221,11 @@ class Project ecma: 2020, compress: { - /* eslint-disable camelcase */ drop_console: true, global_defs: { "@alert": "console.log" } - /* eslint-enable camelcase */ }, sourceMap: { filename: "index.min.mjs.map" @@ -267,7 +264,6 @@ class Project /** * @param sources - the resources in the project - * @private */ public async bundleResources(sources: string[]) { @@ -282,13 +278,13 @@ class Project for (const source of sources) { const target = targetDirectory + path.posix.basename(source); - promises.concat(fs.promises.copyFile(source, target)); + promises = promises.concat(fs.promises.copyFile(source, target)); } await Promise.all(promises); } catch (error) { - this.log.error(`bundleResources error: ${error}`); + this.log.error(`bundleResources error: ${JSON.stringify(error, null, 2)}`); throw error; } console.timeEnd("bundleResources"); @@ -300,6 +296,7 @@ class Project const binPath = path.posix.resolve("./node_modules/.bin"); const c8Path = path.posix.resolve(binPath + "/c8"); const mochaPath = path.posix.resolve(binPath + "/mocha"); + const mode = this.mode; // https://stackoverflow.com/a/53204227/14731 const promise = new Promise(function(resolve, reject) @@ -327,7 +324,7 @@ class Project } catch (error) { - this.log.error(`bundleForBrowser error: ${error}`); + this.log.error(`bundleForBrowser error: ${JSON.stringify(error, null, 2)}`); throw error; } console.timeEnd("test"); @@ -346,7 +343,7 @@ class Project // REMINDER: If the tests return "ERROR: null" it means that the test files could not be compiled, or two // tests had the same name. await Promise.all([this.bundleForBrowser(), this.generateDocumentation(), - this.bundleResources(this.getResourceFiles()), await this.test()]); + this.bundleResources(this.getResourceFiles()), this.test()]); console.timeEnd("build"); } @@ -367,19 +364,17 @@ class Project } /** - * @param paths the paths to watch - * @param callback the callback to notify after a path is updated. + * @param paths - the paths to watch + * @param callback - the callback to notify after a path is updated. * The callback consumes a list of changed paths and returns a promise for the operation that processes * the changes. - * @private */ - private async watchFiles(paths: string | ReadonlyArray, - callback: (sources: string[]) => Promise): Promise + private watchFiles(paths: string | ReadonlyArray, + callback: (sources: string[]) => Promise) { const changes: Set = new Set(); - const project = this; - async function processUpdate() + const processUpdate = async () => { const filesToProcess = new Set(changes); const posixPaths = []; @@ -390,20 +385,20 @@ class Project const posixPath = changed.split(path.sep).join(path.posix.sep); posixPaths.push(posixPath); } - project.log.info(`Updating: [${Array.from(filesToProcess).join(", ")}]`); + this.log.info(`Updating: [${Array.from(filesToProcess).join(", ")}]`); await callback(posixPaths); - } + }; const queueUpdate = debounce(processUpdate, 500); - const onUpdate = async (changed: string, stats: fs.Stats) => + const onUpdate = (changed: string) => { changes.add(changed); - await queueUpdate(); + void queueUpdate(); }; chokidar.watch(paths).on("add", onUpdate). on("addDir", onUpdate). on("change", onUpdate). - on("error", error => this.log.info(`Watch error: ${error}`)). + on("error", error => this.log.info(`Watch error: ${JSON.stringify(error, null, 2)}`)). on("ready", () => this.log.info("Watch ready...")); } @@ -421,8 +416,8 @@ class Project public async watch() { - await Promise.all([this.watchFiles("src/**/*.mts", this.bundleForNode.bind(this)), - this.watchFiles(this.getResourceFiles(), this.bundleResources.bind(this))]); + this.watchFiles("src/**/*.mts", this.bundleForNode.bind(this)); + this.watchFiles(this.getResourceFiles(), this.bundleResources.bind(this)); this.log.info("Watching for changes..."); // Wait forever diff --git a/build/mode.mts b/build/mode.mts index f2ca708..675e673 100644 --- a/build/mode.mts +++ b/build/mode.mts @@ -1,8 +1,10 @@ import parseArgs from "minimist"; const env = parseArgs(process.argv.slice(2)); -let mode = env.mode; -if (mode === undefined) +let mode: string; +if (env.mode === undefined) mode = "DEBUG"; +else + mode = env.mode as string; export {mode}; \ No newline at end of file diff --git a/src/internal/validator/Objects.mts b/src/internal/validator/Objects.mts index e43fbc8..c1cf44b 100644 --- a/src/internal/validator/Objects.mts +++ b/src/internal/validator/Objects.mts @@ -3,7 +3,7 @@ import { TypeCategory, AssertionError } from "../internal.mjs"; -import isEqual from "lodash.isEqual"; +import isEqual from "lodash.isequal"; type ElementOf = T extends readonly (infer E)[] ? E : (T extends Set ? E : never); // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/tsconfig.json b/tsconfig.json index f8a3726..fcb15ac 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,13 +20,13 @@ "include": [ // Files used to generate the target directory "./src", - "./test" + "./test", + "./build" ], "exclude": [ "node_modules", "target", "eslint.config.mjs", - "./build", "./test/TestGlobalConfiguration.mts" ], "declaration": true From 399091abac53b0591bad51e03b7047d49616d73c Mon Sep 17 00:00:00 2001 From: Gili Tzabari Date: Mon, 9 Sep 2024 09:38:42 -0400 Subject: [PATCH 10/15] assertThat() now records exceptions instead of throwing them immediately. --- README.md | 169 ++++++++++++------ docs/Features.md | 80 ++++++--- src/internal/validator/AbstractValidators.mts | 2 +- .../validator/JavascriptValidatorsImpl.mts | 35 ++-- test/RequirementsTest.mts | 4 +- 5 files changed, 193 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index 8ebb00d..20e84fa 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ [![npm version](https://badge.fury.io/js/%40cowwoc%2Frequirements.svg)](https://badge.fury.io/js/%40cowwoc%2Frequirements) [![build-status](https://github.com/cowwoc/requirements.js/workflows/Build/badge.svg)](https://github.com/cowwoc/requirements.js/actions?query=workflow%3ABuild) -# checklist Fluent API for Design Contracts +# checklist Requirements API [![API](https://img.shields.io/badge/api_docs-5B45D5.svg)](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/) [![Changelog](https://img.shields.io/badge/changelog-A345D5.svg)](docs/Changelog.md) [![java](https://img.shields.io/badge/other%20languages-java-457FD5.svg)](../../../requirements.java) -A [fluent API](https://en.wikipedia.org/wiki/Fluent_interface) for enforcing -[design contracts](https://en.wikipedia.org/wiki/Design_by_contract) -with [automatic message generation](#usage). +A [fluent API](https://en.m.wikipedia.org/docs/Fluent_interface) for enforcing +[design contracts](https://en.wikipedia.org/docs/Design_by_contract) with +[automatic message generation](docs/Features.md#automatic-message-generation): ✔️ Easy to use ✔️ Fast @@ -27,83 +27,140 @@ or [pnpm](https://pnpm.io/): pnpm add @cowwoc/requirements@4.0.0 ``` -## Sample Code +## Usage Example ```typescript import {requireThatString} from "@cowwoc/requirements"; - -class Address +class Cake { + private bitesTaken = 0; + private piecesLeft; + + public constructor(piecesLeft: number) + { + requireThat(piecesLeft, "piecesLeft").isPositive(); + this.piecesLeft = piecesLeft; + } + + public eat(): number + { + ++bitesTaken; + assertThat(bitesTaken, "bitesTaken").isNotNegative().elseThrow(); + + piecesLeft -= ThreadLocalRandom.current().nextInt(5); + + assertThat(piecesLeft, "piecesLeft").isNotNegative().elseThrow(); + return piecesLeft; + } + + public getFailures(): String[] + { + return checkIf(bitesTaken, "bitesTaken").isNotNegative(). + and(checkIf(piecesLeft, "piecesLeft").isGreaterThan(3)). + elseGetMessages(); + } } +``` -class PublicAPI -{ - constructor(name: string | null, age: number, address: Address | undefined) - { - requireThatString(name, "name").length().isBetween(1, 30); - requireThatNumber(age, "age").isBetween(18, 30); - - // Methods that conduct runtime type-checks, such as isString() or isNotNull(), update the - // compile-time type returned by getValue(). - const nameIsString: string = requireThat(name as unknown, "name").isString().getValue(); - const address: Address = requireThat(address as unknown, "address").isInstance(Address).getValue(); - } -} +If you violate a **precondition**: + +```typescript +const cake = new Cake(-1000); +``` + +You'll get: + +``` +RangeError: "piecesLeft" must be positive. +actual: -1000 +``` + +If you violate a **class invariant**: + +```typescript +const cake = new Cake(1_000_000); +while (true) + cake.eat(); +``` + +You'll get: + +``` +lang.AssertionError: "bitesTaken" may not be negative. +actual: -128 +``` + +If you violate a **postcondition**: + +```typescript +const cake = new Cake(100); +while (true) + cake.eat(); +``` + +You'll get: -class PrivateAPI -{ - public static toCamelCase(text): string - { - // Trusted input does not need to be casted to "unknown". The input type will be inferred - // and runtime checks will be skipped. Notice the lack of isString() or isNumber() invocations - // in the following code. - assertThat(r => r.requireThat(name, "name").length().isBetween(1, 30)); - assertThat(r => r.requireThat(age, "age").isBetween(18, 30)); - } -} ``` +AssertionError: "piecesLeft" may not be negative. +actual: -4 +``` + +If you violate **multiple** conditions at once: -Failure messages will look like this: +```typescript +const cake = new Cake(1); +cake.bitesTaken = -1; +cake.piecesLeft = 2; +const failures = []; +for (const failure of cake.getFailures()) + failures.add(failure); +console.log(failures.join("\n\n")); +``` -```text -TypeError: name may not be null +You'll get: -RangeError: name may not be empty +``` +"bitesTaken" may not be negative. +actual: -1 -RangeError: age must be in range [18, 30). -Actual: 15 +"piecesLeft" must be greater than 3. +actual: 2 ``` ## Features -* [Automatic message generation](docs/Features.md#automatic-message-generation) -* [Diffs provided whenever possible](docs/Features.md#diffs-provided-whenever-possible) -* [Assertion support](docs/Features.md#assertion-support) -* [Grouping nested requirements](docs/Features.md#grouping-nested-requirements) -* [String diff](docs/Features.md#string-diff) +This library offers the following features: + +* [Automatic message generation](docs/Features.md#automatic-message-generation) for validation failures +* [Diffs provided whenever possible](docs/Features.md#diffs-provided-whenever-possible) to highlight the + differences between expected and actual values +* [Zero overhead when assertions are disabled](docs/Features.md#assertion-support) for better performance +* [Multiple validation failures](docs/Features.md#multiple-validation-failures) that report all the errors at + once +* [Nested validations](docs/Features.md#nested-validations) that allow you to validate complex objects +* [String diff](docs/Features.md#string-diff) that shows the differences between two strings -## Getting Started +## Entry Points -The best way to learn about the API is using your IDE's auto-complete engine. -There are six entry points you can navigate from: +Designed for discovery using your favorite IDE's auto-complete feature. +The main entry points are: * [requireThat(value, name)](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/module-DefaultRequirements.html#~requireThat) -* [validateThat(value, name)](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/module-DefaultRequirements.html#~validateThat) -* [assertThat(Function)](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/module-DefaultRequirements.html#~assertThat) -* [assertThatAndReturn(Function)](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/module-DefaultRequirements.html#~assertThatAndReturn) + for method preconditions. +* [assertThat(value, name)](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/module-DefaultRequirements.html#~assertThat) + for [class invariants, method postconditions and private methods](docs/Features.md#assertion-support). +* [checkIf(value, name)](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/module-DefaultRequirements.html#~checkIf) + for multiple failures and customized error handling. -* [Requirements](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/module-Requirements-Requirements.html) -* [GlobalRequirements](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/module-GlobalRequirements-GlobalRequirements.html) +See the [API documentation](https://cowwoc.github.io/requirements.java/10.0/docs/api/) for more details. ## Best practices -* Use `requireThat()` to verify pre-conditions of public APIs. -* Use `assertThat()` to verify object invariants and method post-conditions. - This results in excellent performance when assertions are disabled. - Have your cake and eat it too! -* Don't bother validating any constraints that are already enforced by the Typescript compiler (such as the - type of a variable) unless it will result in silent failures or security vulnerabilities when violated. +* Use `checkIf().elseGetMessages()` to return failure messages without throwing an exception. + This is the fastest validation approach, ideal for web services. +* To enhance the clarity of failure messages, you should provide parameter names, even when they are optional. + In other words, favor `assert that(value, name)` over `assert that(value)`. ## Related Projects diff --git a/docs/Features.md b/docs/Features.md index 60cd10d..6cfa2bc 100644 --- a/docs/Features.md +++ b/docs/Features.md @@ -31,54 +31,86 @@ Missing: [1, 5] ## Assertion support -All verifiers allocate memory which is especially hard to justify given that most checks are never going to -fail. If -you need to run in a high-performance, zero allocation environment (to reduce latency and jitter) look no -further than -`DefaultRequirements.assertThat()`. +If you need to run in a high performance, zero allocation environment (to reduce latency and jitter) look no +further than the following design pattern: -`assertThat()` skips verification if assertions are disabled. `DefaultRequirements` might be less flexible -than `Requirements` but it only allocates `Requirements` once per application. Together, they guarantee high -performance and no allocations if assertions are disabled. +```typescript +import {assertThat} from "@cowwoc/requirements"; + +class Person +{ + public void eatLunch() + { + assertThat("time", new Date().getHours()).isGreaterThanOrEqualTo(12, "noon").elseThrow(); + } +} +``` + +Use a build tool like Terser to declare `assertThat()` as a pure function and it will be stripped out from production builds. + +## Multiple validation failures + +```typescript +const name = "George"; +const province = "Florida"; +const provinces = ["Ontario", "Quebec", "Nova Scotia", "New Brunswick", "Manitoba", + "British Columbia", "Prince Edward Island", "Saskatchewan", "Alberta", "Newfoundland and Labrador"]; -## Grouping nested requirements +const failures = checkIf(name, "name").length().isBetween(10, 30).elseGetFailures(); +failures.addAll(checkIf(provinces, "provinces").contains(province).elseGetFailures()); -Some classes provide a mechanism for grouping nested requirements. For example, `MapVerifier` has -methods `keys()` and -`keys(consumer)`, `values()` and `values(consumer)`. This enables one to group requirements that share the -same parent. -For example, +for (const failure of failures) + console.log(failure.getMessage()); +``` + +Output will look like: + +``` +name must contain [10, 30) characters. + +"provinces" must contain provide "province". +province: Florida +Actual: [Ontario, Quebec, Nova Scotia, New Brunswick, Manitoba, British Columbia, Prince Edward Island, Saskatchewan, Alberta, Newfoundland and Labrador] +``` + +## Nested validations + +Nested validations facilitate checking multiple properties of a value. For example, ```typescript const nameToAge = new Map(); nameToAge.set("Leah", 3); nameToAge.set("Nathaniel", 1); -requireThat(nameToAge, "nameToAge").asMap().keys().containsAll(["Leah", "Nathaniel"]); -requireThat(nameToAge, "nameToAge").asMap().values().containsAll([3, 1]); +requireThat(nameToAge, "nameToAge"). +keys().containsAll(["Leah", "Nathaniel"]); +requireThat(nameToAge, "nameToAge"). +values().containsAll([3, 1]); ``` -can be rewritten as: +can be converted to: ```typescript -requireThat(nameToAge, "nameToAge").asMap(). - keys(k => k.containsAll(["Leah", "Nathaniel"])). - values(v => v.containsAll([3, 1])); +requireThat(nameToAge, "nameToAge"). + and(k => k.keys().containsAll(["Leah", "Nathaniel"])). + and(v => v.values().containsAll([3, 1])); ``` ## String diff When a [String comparison](https://cowwoc.github.io/requirements.js/4.0.0/docs/api/ObjectVerifier.html#isEqualTo) -fails, the library outputs a [diff](String_Diff.md) of the values being compared. +fails, the library outputs a diff of the values being compared. + +Depending on the terminal capability, you will see a [textual](Textual_Diff.md) or a colored diff. ![colored-diff-example4.png](colored-diff-example4.png) -Node supports colored messages. Browsers do not. +Node supports colored exception messages. Browsers do not. -## Getting the actual value +## Returning the value after validation -Sometimes it is convenient to retrieve the actual value after a verification/validation: +You can get the value after validating or transforming it, e.g. ```typescript class Player diff --git a/src/internal/validator/AbstractValidators.mts b/src/internal/validator/AbstractValidators.mts index 39f8e13..775ffa2 100644 --- a/src/internal/validator/AbstractValidators.mts +++ b/src/internal/validator/AbstractValidators.mts @@ -158,7 +158,7 @@ abstract class AbstractValidators implements Validators requireThatValueIsNotNull(configuration, "configuration"); this.requireThatConfiguration = configuration; this.assertThatConfiguration = MutableConfiguration.from(configuration). - errorTransformer(AbstractValidators.CONVERT_TO_ASSERTION_ERROR).toImmutable(); + throwOnFailure(false).errorTransformer(AbstractValidators.CONVERT_TO_ASSERTION_ERROR).toImmutable(); this.checkIfConfiguration = MutableConfiguration.from(configuration). throwOnFailure(false).toImmutable(); } diff --git a/src/internal/validator/JavascriptValidatorsImpl.mts b/src/internal/validator/JavascriptValidatorsImpl.mts index 30a715e..1469bc5 100644 --- a/src/internal/validator/JavascriptValidatorsImpl.mts +++ b/src/internal/validator/JavascriptValidatorsImpl.mts @@ -228,8 +228,9 @@ class JavascriptValidatorsImpl extends AbstractValidators /** * Validates the state of a number. *

            - * The returned validator throws an exception immediately if a validation fails. This exception is then - * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * The returned validator captures exceptions on validation failure rather than throwing them immediately. + * The exceptions are converted into an {@link AssertionError} and can be retrieved or thrown once the + * validation completes. Exceptions unrelated to validation failures are thrown immediately. * * @typeParam T - the type the value * @param value - the value @@ -247,8 +248,9 @@ class JavascriptValidatorsImpl extends AbstractValidators /** * Validates the state of a boolean. *

            - * The returned validator throws an exception immediately if a validation fails. This exception is then - * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * The returned validator captures exceptions on validation failure rather than throwing them immediately. + * The exceptions are converted into an {@link AssertionError} and can be retrieved or thrown once the + * validation completes. Exceptions unrelated to validation failures are thrown immediately. * * @typeParam T - the type the value * @param value - the value @@ -266,8 +268,9 @@ class JavascriptValidatorsImpl extends AbstractValidators /** * Validates the state of an array. *

            - * The returned validator throws an exception immediately if a validation fails. This exception is then - * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * The returned validator captures exceptions on validation failure rather than throwing them immediately. + * The exceptions are converted into an {@link AssertionError} and can be retrieved or thrown once the + * validation completes. Exceptions unrelated to validation failures are thrown immediately. * * @typeParam T - the type the value * @typeParam E - the type elements in the array @@ -286,8 +289,9 @@ class JavascriptValidatorsImpl extends AbstractValidators /** * Validates the state of a set. *

            - * The returned validator throws an exception immediately if a validation fails. This exception is then - * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * The returned validator captures exceptions on validation failure rather than throwing them immediately. + * The exceptions are converted into an {@link AssertionError} and can be retrieved or thrown once the + * validation completes. Exceptions unrelated to validation failures are thrown immediately. * * @typeParam T - the type the value * @typeParam E - the type elements in the set @@ -306,8 +310,9 @@ class JavascriptValidatorsImpl extends AbstractValidators /** * Validates the state of a map. *

            - * The returned validator throws an exception immediately if a validation fails. This exception is then - * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * The returned validator captures exceptions on validation failure rather than throwing them immediately. + * The exceptions are converted into an {@link AssertionError} and can be retrieved or thrown once the + * validation completes. Exceptions unrelated to validation failures are thrown immediately. * * @typeParam T - the type the value * @typeParam K - the type of keys in the map @@ -327,8 +332,9 @@ class JavascriptValidatorsImpl extends AbstractValidators /** * Validates the state of a string. *

            - * The returned validator throws an exception immediately if a validation fails. This exception is then - * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * The returned validator captures exceptions on validation failure rather than throwing them immediately. + * The exceptions are converted into an {@link AssertionError} and can be retrieved or thrown once the + * validation completes. Exceptions unrelated to validation failures are thrown immediately. * * @typeParam T - the type the value * @param value - the value @@ -346,8 +352,9 @@ class JavascriptValidatorsImpl extends AbstractValidators /** * Validates the state of an unknown value or a value that does not have a specialized validator. *

            - * The returned validator throws an exception immediately if a validation fails. This exception is then - * converted into an {@link AssertionError}. Exceptions unrelated to validation failures are not converted. + * The returned validator captures exceptions on validation failure rather than throwing them immediately. + * The exceptions are converted into an {@link AssertionError} and can be retrieved or thrown once the + * validation completes. Exceptions unrelated to validation failures are thrown immediately. * * @typeParam T - the type the value * @param value - the value diff --git a/test/RequirementsTest.mts b/test/RequirementsTest.mts index 65294f6..73aa0a8 100644 --- a/test/RequirementsTest.mts +++ b/test/RequirementsTest.mts @@ -22,14 +22,14 @@ suite("RequirementsTest", () => assert.throws(function() { const actual = "actual"; - validators.assertThatString(actual, "actual").isEqualTo("expected"); + validators.assertThatString(actual, "actual").isEqualTo("expected").elseThrow(); }, AssertionError); }); test("assertThatArray", () => { const actual = [1, 2, 3]; - validators.assertThatArray(actual, "actual").isEqualTo(actual, "expected"); + validators.assertThatArray(actual, "actual").isEqualTo(actual, "expected").elseThrow(); }); test("assertThatNumber", () => From 2ef3daa0e11b3f6e2761aabf7e7174b99e1c586d Mon Sep 17 00:00:00 2001 From: Gili Tzabari Date: Tue, 10 Sep 2024 08:27:44 -0400 Subject: [PATCH 11/15] Migrate from Winston to Diary for logging --- .gitignore | 17 +- .idea/codeStyles/Project.xml | 104 +++ .idea/codeStyles/codeStyleConfig.xml | 5 + build/LogFactory.mts | 49 -- build/Project.mts | 147 +++-- package.json | 26 +- pnpm-lock.yaml | 593 +++++++----------- src/JavascriptValidators.mts | 54 +- src/Type.mts | 2 +- src/Validators.mts | 1 + src/internal/Configuration.mts | 1 + src/internal/ConfigurationUpdater.mts | 1 + .../message/diff/ContextGenerator.mts | 2 +- src/internal/message/diff/DiffGenerator.mts | 2 +- src/internal/validator/Terminal.mts | 6 +- src/validator/MapValidator.mts | 20 +- src/validator/StringValidator.mts | 6 +- .../component/CollectionComponent.mts | 1 + src/validator/component/NumberComponent.mts | 10 +- .../component/ValidatorComponent.mts | 2 +- test/RequirementsTest.mts | 2 +- tsconfig.json | 4 +- 22 files changed, 505 insertions(+), 550 deletions(-) create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 build/LogFactory.mts diff --git a/.gitignore b/.gitignore index 24cb733..2bdbf5e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,14 @@ -syntax: glob +# Maven Wrapper files that will be regenerate automatically +.mvn/wrapper/maven-wrapper.jar # IntelliJ IDEA -.idea/ +/.idea/* +!/.idea +!/.idea/codeStyles -# Files generated as part of the build -**/node_modules/ +*.iml + +# Java target/ coverage/ @@ -15,8 +19,9 @@ coverage/ commonjs.html modulejs.html -# eslint -/.eslintcache +# Node.js +node_modules/ +.eslintcache # Credentials /keys.txt \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..f2e8495 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,104 @@ + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/build/LogFactory.mts b/build/LogFactory.mts deleted file mode 100644 index 01d29cb..0000000 --- a/build/LogFactory.mts +++ /dev/null @@ -1,49 +0,0 @@ -import { - createLogger, - format, - Logger, - transports -} from "winston"; - -// https://stackoverflow.com/a/63486530/14731 -const Reset = "\x1b[0m"; -const FgWhite = "\x1b[37m"; -const BgRed = "\x1b[41m"; - -class LogFactory -{ - /** - * @param name - the name of the logger - */ - public static getLogger(name: string): Logger - { - return createLogger({ - transports: [new transports.Console()], - format: format.combine( - format(info => - { - // https://github.com/winstonjs/winston/issues/1345#issuecomment-393853665 - info.level = info.level.toUpperCase(); - return info; - })(), - format.errors({stack: true}), - format.prettyPrint(), - format.colorize(), - format.timestamp({format: "YYYY-MM-DD HH:mm:ss.SSS"}), - format.printf(({ - timestamp, - level, - message, - stack - }) => - { - if (stack) - return `${timestamp} ${level} ${FgWhite + BgRed + name + Reset} - ${message}\n${stack}`; - return `${timestamp} ${level} ${FgWhite + BgRed + name + Reset} - ${message}`; - }) - ) - }); - } -} - -export {LogFactory}; \ No newline at end of file diff --git a/build/Project.mts b/build/Project.mts index 2ce14cc..8be6e7b 100644 --- a/build/Project.mts +++ b/build/Project.mts @@ -15,13 +15,16 @@ import ts from "typescript"; import {glob} from "glob"; import {minify} from "terser"; import {spawn} from "child_process"; -import {LogFactory} from "./LogFactory.mjs"; import {default as chokidar} from "chokidar"; import eslintConfig from "../eslint.config.mjs"; -import {Logger} from "winston"; import {mode} from "./mode.mjs"; import parseArgs from "minimist"; +import { + diary, + enable as enableDiaries +} from "diary"; import debounce from "lodash.debounce"; +import padStart from "lodash.padstart"; // WORKAROUND: https://github.com/rollup/plugins/issues/1662#issuecomment-2337703188 const rollupCommonJs = _rollupCommonJs as unknown as (options?: unknown) => Plugin; @@ -29,15 +32,11 @@ const rollupTypescript = _rollupTypescript as unknown as (options?: RollupTypesc class Project { + private static readonly outputDirectory = "target"; private readonly mode: string; - private readonly log: Logger; constructor(mode: string) { - // Use POSIX paths across all platforms - const posixPath = url.fileURLToPath(import.meta.url).split(path.sep).join(path.posix.sep); - const __filename = path.posix.basename(posixPath); - this.log = LogFactory.getLogger(__filename); this.mode = mode; } @@ -46,7 +45,9 @@ class Project */ private async lintTypescript(sources: string[]) { - console.time("lintTypescript"); + if (sources.length === 0) + return; + const startTime = performance.now(); const eslint = new ESLint({ baseConfig: eslintConfig, cache: true @@ -63,11 +64,11 @@ class Project const formatter = await eslint.loadFormatter("stylish"); const resultText = await formatter.format(results, resultsMeta); if (resultText) - this.log.info(resultText); + log.info(resultText); } catch (error) { - this.log.error(`Lint error: ${JSON.stringify(error, null, 2)}`); + log.error(`Lint error: ${JSON.stringify(error, null, 2)}`); throw error; } @@ -76,7 +77,7 @@ class Project if (result.errorCount > 0) throw new Error("lintTypescript failed"); } - console.timeEnd("lintTypescript"); + log.info(`lintTypescript: ${Project.timeElapsedSince(startTime)}`); } /** @@ -84,18 +85,18 @@ class Project */ private async bundleForNode(sources: string[]) { - console.time("bundleForNode"); + const startTime = performance.now(); try { await this.lintTypescript(sources); } catch (error) { - this.log.error(`bundleForNode error: ${JSON.stringify(error, null, 2)}`); + log.error(`bundleForNode error: ${JSON.stringify(error, null, 2)}`); throw error; } this.compileTypescript(sources); - console.timeEnd("bundleForNode"); + log.info(`bundleForNode: ${Project.timeElapsedSince(startTime)}`); } /** @@ -103,7 +104,7 @@ class Project */ private compileTypescript(sources: string[]) { - console.time("compileTypescript"); + const startTime = performance.now(); // Example of compiling using API: https://gist.github.com/jeremyben/4de4fdc40175d0f76892209e00ece98f const cwd = process.cwd(); const configFile = ts.findConfigFile(cwd, filename => ts.sys.fileExists(filename), "tsconfig.json"); @@ -112,7 +113,7 @@ class Project // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const {config} = ts.readConfigFile(configFile, path => ts.sys.readFile(path)); /* eslint-disable @typescript-eslint/no-unsafe-member-access */ - config.compilerOptions.outDir = "target/publish/node/"; + config.compilerOptions.outDir = `${Project.outputDirectory}/publish/node/`; config.compilerOptions.declaration = true; config.include = undefined; config.files = sources; @@ -143,17 +144,16 @@ class Project getNewLine: () => ts.sys.newLine }; const message = ts.formatDiagnostics(allDiagnostics, formatHost); - this.log.warn(message); + log.warn(message); } if (emitSkipped) throw new Error("compileTypescript() failed"); - console.timeEnd("compileTypescript"); + log.info(`compileTypescript: ${Project.timeElapsedSince(startTime)}`); } - public async bundleForBrowser() + private async bundleForBrowser() { - console.time("bundleForBrowser"); - + const startTime = performance.now(); const plugins: Plugin[] = [ rollupTypescript({ "module": "ES2022", @@ -188,17 +188,17 @@ class Project await bundle.write( { sourcemap: true, - dir: "target/publish/browser" + dir: `${Project.outputDirectory}/publish/browser` }); if (this.mode === "RELEASE") await this.minifyBrowserSources(); } catch (error) { - this.log.error(`bundleForBrowser error: ${JSON.stringify(error, null, 2)}`); + log.error(`bundleForBrowser error: ${JSON.stringify(error, null, 2)}`); throw error; } - console.timeEnd("bundleForBrowser"); + log.info(`bundleForBrowser: ${Project.timeElapsedSince(startTime)}`); } /** @@ -206,10 +206,10 @@ class Project */ private async minifyBrowserSources() { - console.time("minifyBrowserSources"); - const targetDirectory = "target/publish/browser/"; + const startTime = performance.now(); + const targetDirectory = `${Project.outputDirectory}/publish/browser/`; - const sourceFiles = glob.sync("target/publish/browser/index.js"); + const sourceFiles = glob.sync(`${Project.outputDirectory}/publish/browser/index.js`); const pathToCode: { [name: string]: string } = {}; for (const file of sourceFiles) @@ -237,13 +237,13 @@ class Project fs.writeFileSync(targetDirectory + "index.min.mjs", code); fs.writeFileSync(targetDirectory + "index.min.mjs.map", map); - console.timeEnd("minifyBrowserSources"); + log.info(`minifyBrowserSources: ${Project.timeElapsedSince(startTime)}`); } public async generateDocumentation() { - console.time("generateDocumentation"); - const targetDirectory = "target/apidocs/"; + const startTime = performance.now(); + const targetDirectory = `${Project.outputDirectory}/apidocs/`; const app = await TypeDoc.Application.bootstrapWithPlugins({}, [ new TypeDoc.TypeDocReader(), @@ -259,16 +259,18 @@ class Project await app.generateDocs(project, targetDirectory); if (app.logger.hasErrors()) throw new Error("generateDocumentation failed"); - console.timeEnd("generateDocumentation"); + log.info(`generateDocumentation: ${Project.timeElapsedSince(startTime)}`); } /** - * @param sources - the resources in the project + * @param sources - the files to bundle */ - public async bundleResources(sources: string[]) + private async bundleResources(sources: string[]) { - console.time("bundleResources"); - const targetDirectory = "target/publish/"; + if (sources.length === 0) + return; + const startTime = performance.now(); + const targetDirectory = `${Project.outputDirectory}/publish/`; if (!fs.existsSync(targetDirectory)) fs.mkdirSync(targetDirectory, {recursive: true}); let promises: Promise[] = []; @@ -284,15 +286,15 @@ class Project } catch (error) { - this.log.error(`bundleResources error: ${JSON.stringify(error, null, 2)}`); + log.error(`bundleResources error: ${JSON.stringify(error, null, 2)}`); throw error; } - console.timeEnd("bundleResources"); + log.info(`bundleResources: ${Project.timeElapsedSince(startTime)}`); } private async test() { - console.time("test"); + const startTime = performance.now(); const binPath = path.posix.resolve("./node_modules/.bin"); const c8Path = path.posix.resolve(binPath + "/c8"); const mochaPath = path.posix.resolve(binPath + "/mocha"); @@ -324,10 +326,10 @@ class Project } catch (error) { - this.log.error(`bundleForBrowser error: ${JSON.stringify(error, null, 2)}`); + log.error(`bundleForBrowser error: ${JSON.stringify(error, null, 2)}`); throw error; } - console.timeEnd("test"); + log.info(`test: ${Project.timeElapsedSince(startTime)}`); } private async getTypescriptFiles() @@ -337,19 +339,19 @@ class Project public async build() { - console.time("build"); + const startTime = performance.now(); const typescriptFiles = await this.getTypescriptFiles(); await this.bundleForNode(typescriptFiles); // REMINDER: If the tests return "ERROR: null" it means that the test files could not be compiled, or two // tests had the same name. await Promise.all([this.bundleForBrowser(), this.generateDocumentation(), this.bundleResources(this.getResourceFiles()), this.test()]); - console.timeEnd("build"); + log.info(`build: ${Project.timeElapsedSince(startTime)}`); } public async clean() { - console.time("clean"); + const startTime = performance.now(); const typescriptFiles = await this.getTypescriptFiles(); for (const file of typescriptFiles) { @@ -360,7 +362,7 @@ class Project if (fs.existsSync(basePath + ".mjs.map")) await fs.promises.unlink(basePath + ".mjs.map"); } - console.timeEnd("clean"); + log.info(`clean: ${Project.timeElapsedSince(startTime)}`); } /** @@ -385,8 +387,16 @@ class Project const posixPath = changed.split(path.sep).join(path.posix.sep); posixPaths.push(posixPath); } - this.log.info(`Updating: [${Array.from(filesToProcess).join(", ")}]`); - await callback(posixPaths); + log.info(`Updating: [${Array.from(filesToProcess).join(", ")}]`); + try + { + await callback(posixPaths); + } + catch (error) + { + console.log(error); + // Keep on watching the files even if an error occurs + } }; const queueUpdate = debounce(processUpdate, 500); @@ -398,8 +408,14 @@ class Project chokidar.watch(paths).on("add", onUpdate). on("addDir", onUpdate). on("change", onUpdate). - on("error", error => this.log.info(`Watch error: ${JSON.stringify(error, null, 2)}`)). - on("ready", () => this.log.info("Watch ready...")); + on("error", error => + { + log.info(`Watch error: ${JSON.stringify(error, null, 2)}`); + }). + on("ready", () => + { + log.info("Watch ready..."); + }); } /** @@ -416,18 +432,42 @@ class Project public async watch() { + fs.rmSync(Project.outputDirectory, { + recursive: true, + force: true + }); this.watchFiles("src/**/*.mts", this.bundleForNode.bind(this)); this.watchFiles(this.getResourceFiles(), this.bundleResources.bind(this)); - this.log.info("Watching for changes..."); + log.info("Watching for changes..."); // Wait forever await new Promise(() => { }); } + + public static timeElapsedSince(startTime: number) + { + const endTime = performance.now(); + const duration = endTime - startTime; + const milliseconds = Math.floor((duration % 1000) / 100); + const seconds = Math.floor((duration / 1000) % 60); + const minutes = Math.floor((duration / (1000 * 60)) % 60); + const hours = Math.floor((duration / (1000 * 60 * 60)) % 24); + assert(hours === 0); + assert(minutes === 0); + + return seconds.toString() + "." + padStart(milliseconds.toString(), 3, "0") + " seconds"; + } } -console.time("Time elapsed"); +enableDiaries("*"); +// Use POSIX paths across all platforms +const posixPath = url.fileURLToPath(import.meta.url).split(path.sep).join(path.posix.sep); +const __filename = path.posix.basename(posixPath); +const log = diary(__filename); + +const startTime = performance.now(); const project = new Project(mode); const command = parseArgs(process.argv.slice(2)); switch (command._[0]) @@ -449,11 +489,8 @@ switch (command._[0]) } default: { - // Use POSIX paths across all platforms - const posixPath = url.fileURLToPath(import.meta.url).split(path.sep).join(path.posix.sep); - const __filename = path.posix.basename(posixPath); - const log = LogFactory.getLogger(__filename); log.error(`Unknown command: ${command._[0]}`); + break; } } -console.timeEnd("Time elapsed"); \ No newline at end of file +log.info(`Time elapsed: ${Project.timeElapsedSince(startTime)}`); \ No newline at end of file diff --git a/package.json b/package.json index cbbe235..a774ce1 100644 --- a/package.json +++ b/package.json @@ -33,13 +33,13 @@ "sideEffects": false, "dependencies": { "chalk": "^5.3.0", - "diff": "6.0.0", + "diff": "7.0.0", "lodash.isequal": "^4.5.0" }, "license": "Apache-2.0", "packageManager": "pnpm@9.9.0", "devDependencies": { - "@eslint/js": "^9.9.1", + "@eslint/js": "^9.10.0", "@rollup/plugin-commonjs": "^26.0.1", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-typescript": "^11.1.6", @@ -48,26 +48,29 @@ "@types/eslint": "^9.6.1", "@types/lodash.debounce": "^4.0.9", "@types/lodash.isequal": "^4.5.8", + "@types/lodash.padstart": "^4.6.9", "@types/minimist": "^1.2.5", "@types/mocha": "^10.0.7", - "@types/node": "^22.5.3", + "@types/node": "^22.5.4", "@types/rollup-plugin-node-globals": "^1.4.4", "@types/tmp": "^0.2.6", - "@typescript-eslint/eslint-plugin": "^8.4.0", - "@typescript-eslint/parser": "^8.4.0", + "@typescript-eslint/eslint-plugin": "^8.5.0", + "@typescript-eslint/parser": "^8.5.0", "browser-sync": "^3.0.2", "c8": "^10.1.2", "chai": "^5.1.1", "chokidar": "^3.6.0", - "eslint": "^9.9.1", + "diary": "^0.4.5", + "eslint": "^9.10.0", "eslint-plugin-tsdoc": "^0.3.0", "fancy-log": "^2.0.0", "glob": "^11.0.0", "install": "^0.13.0", "lodash.debounce": "^4.0.8", + "lodash.padstart": "^4.6.1", "minimist": "^1.2.8", "mocha": "^10.7.3", - "pnpm": "^9.9.0", + "pnpm": "^9.10.0", "react": "^18.3.1", "react-dom": "^18.3.1", "rollup": "^4.21.2", @@ -75,15 +78,14 @@ "source-map-support": "^0.5.21", "strip-ansi": "^7.1.0", "taffydb": "^2.7.3", - "terser": "^5.31.6", + "terser": "^5.32.0", "tmp": "^0.2.3", "ts-node": "^10.9.2", "tslib": "^2.7.0", "tsx": "^4.19.0", - "typedoc": "^0.26.6", + "typedoc": "^0.26.7", "typedoc-plugin-missing-exports": "^3.0.0", - "typescript": "5.4.5", - "typescript-eslint": "^8.4.0", - "winston": "^3.14.2" + "typescript": "^5.6.2", + "typescript-eslint": "^8.5.0" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00c821d..dd84501 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,15 +12,15 @@ importers: specifier: ^5.3.0 version: 5.3.0 diff: - specifier: 6.0.0 - version: 6.0.0 + specifier: 7.0.0 + version: 7.0.0 lodash.isequal: specifier: ^4.5.0 version: 4.5.0 devDependencies: '@eslint/js': - specifier: ^9.9.1 - version: 9.9.1 + specifier: ^9.10.0 + version: 9.10.0 '@rollup/plugin-commonjs': specifier: ^26.0.1 version: 26.0.1(rollup@4.21.2) @@ -29,7 +29,7 @@ importers: version: 15.2.3(rollup@4.21.2) '@rollup/plugin-typescript': specifier: ^11.1.6 - version: 11.1.6(rollup@4.21.2)(tslib@2.7.0)(typescript@5.4.5) + version: 11.1.6(rollup@4.21.2)(tslib@2.7.0)(typescript@5.6.2) '@types/chai': specifier: ^4.3.19 version: 4.3.19 @@ -45,6 +45,9 @@ importers: '@types/lodash.isequal': specifier: ^4.5.8 version: 4.5.8 + '@types/lodash.padstart': + specifier: ^4.6.9 + version: 4.6.9 '@types/minimist': specifier: ^1.2.5 version: 1.2.5 @@ -52,8 +55,8 @@ importers: specifier: ^10.0.7 version: 10.0.7 '@types/node': - specifier: ^22.5.3 - version: 22.5.3 + specifier: ^22.5.4 + version: 22.5.4 '@types/rollup-plugin-node-globals': specifier: ^1.4.4 version: 1.4.4 @@ -61,11 +64,11 @@ importers: specifier: ^0.2.6 version: 0.2.6 '@typescript-eslint/eslint-plugin': - specifier: ^8.4.0 - version: 8.4.0(@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.4.5))(eslint@9.9.1)(typescript@5.4.5) + specifier: ^8.5.0 + version: 8.5.0(@typescript-eslint/parser@8.5.0(eslint@9.10.0)(typescript@5.6.2))(eslint@9.10.0)(typescript@5.6.2) '@typescript-eslint/parser': - specifier: ^8.4.0 - version: 8.4.0(eslint@9.9.1)(typescript@5.4.5) + specifier: ^8.5.0 + version: 8.5.0(eslint@9.10.0)(typescript@5.6.2) browser-sync: specifier: ^3.0.2 version: 3.0.2 @@ -78,9 +81,12 @@ importers: chokidar: specifier: ^3.6.0 version: 3.6.0 + diary: + specifier: ^0.4.5 + version: 0.4.5 eslint: - specifier: ^9.9.1 - version: 9.9.1 + specifier: ^9.10.0 + version: 9.10.0 eslint-plugin-tsdoc: specifier: ^0.3.0 version: 0.3.0 @@ -96,6 +102,9 @@ importers: lodash.debounce: specifier: ^4.0.8 version: 4.0.8 + lodash.padstart: + specifier: ^4.6.1 + version: 4.6.1 minimist: specifier: ^1.2.8 version: 1.2.8 @@ -103,8 +112,8 @@ importers: specifier: ^10.7.3 version: 10.7.3 pnpm: - specifier: ^9.9.0 - version: 9.9.0 + specifier: ^9.10.0 + version: 9.10.0 react: specifier: ^18.3.1 version: 18.3.1 @@ -127,14 +136,14 @@ importers: specifier: ^2.7.3 version: 2.7.3 terser: - specifier: ^5.31.6 - version: 5.31.6 + specifier: ^5.32.0 + version: 5.32.0 tmp: specifier: ^0.2.3 version: 0.2.3 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@22.5.3)(typescript@5.4.5) + version: 10.9.2(@types/node@22.5.4)(typescript@5.6.2) tslib: specifier: ^2.7.0 version: 2.7.0 @@ -142,37 +151,27 @@ importers: specifier: ^4.19.0 version: 4.19.0 typedoc: - specifier: ^0.26.6 - version: 0.26.6(typescript@5.4.5) + specifier: ^0.26.7 + version: 0.26.7(typescript@5.6.2) typedoc-plugin-missing-exports: specifier: ^3.0.0 - version: 3.0.0(typedoc@0.26.6(typescript@5.4.5)) + version: 3.0.0(typedoc@0.26.7(typescript@5.6.2)) typescript: - specifier: 5.4.5 - version: 5.4.5 + specifier: ^5.6.2 + version: 5.6.2 typescript-eslint: - specifier: ^8.4.0 - version: 8.4.0(eslint@9.9.1)(typescript@5.4.5) - winston: - specifier: ^3.14.2 - version: 3.14.2 + specifier: ^8.5.0 + version: 8.5.0(eslint@9.10.0)(typescript@5.6.2) packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@colors/colors@1.6.0': - resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} - engines: {node: '>=0.1.90'} - '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@dabh/diagnostics@2.0.3': - resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} - '@esbuild/aix-ppc64@0.23.1': resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} engines: {node: '>=18'} @@ -335,14 +334,18 @@ packages: resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.9.1': - resolution: {integrity: sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==} + '@eslint/js@9.10.0': + resolution: {integrity: sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.4': resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.1.0': + resolution: {integrity: sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} @@ -525,11 +528,11 @@ packages: cpu: [x64] os: [win32] - '@shikijs/core@1.16.2': - resolution: {integrity: sha512-XSVH5OZCvE4WLMgdoBqfPMYmGHGmCC3OgZhw0S7KcSi2XKZ+5oHGe71GFnTljgdOxvxx5WrRks6QoTLKrl1eAA==} + '@shikijs/core@1.16.3': + resolution: {integrity: sha512-yETIvrETCeC39gSPIiSADmjri9FwKmxz0QvONMtTIUYlKZe90CJkvcjPksayC2VQOtzOJonEiULUa8v8crUQvA==} - '@shikijs/vscode-textmate@9.2.0': - resolution: {integrity: sha512-5FinaOp6Vdh/dl4/yaOTh0ZeKch+rYS8DUb38V3GMKYVkdqzxw53lViRKUYkVILRiVQT7dcPC7VvAKOR73zVtQ==} + '@shikijs/vscode-textmate@9.2.2': + resolution: {integrity: sha512-TMp15K+GGYrWlZM8+Lnj9EaHEFmOen0WJBrfa17hF7taDOYthuPPV0GWzfd/9iMij0akS/8Yw2ikquH7uVi/fg==} '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -582,6 +585,9 @@ packages: '@types/lodash.isequal@4.5.8': resolution: {integrity: sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==} + '@types/lodash.padstart@4.6.9': + resolution: {integrity: sha512-KVXQ65AiorTc+Dn9eSRZDs1SnzXULRJcMYhCDEIgsRtHU7mbVpghPSxkySh3Vgm+doWVzpJCA24259fkRL46sA==} + '@types/lodash@4.17.7': resolution: {integrity: sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==} @@ -591,8 +597,8 @@ packages: '@types/mocha@10.0.7': resolution: {integrity: sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==} - '@types/node@22.5.3': - resolution: {integrity: sha512-njripolh85IA9SQGTAqbmnNZTdxv7X/4OYGPz8tgy5JDr8MP+uDBa921GpYEoDDnwm0Hmn5ZPeJgiiSTPoOzkQ==} + '@types/node@22.5.4': + resolution: {integrity: sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -603,14 +609,11 @@ packages: '@types/tmp@0.2.6': resolution: {integrity: sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==} - '@types/triple-beam@1.3.5': - resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} - '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@typescript-eslint/eslint-plugin@8.4.0': - resolution: {integrity: sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==} + '@typescript-eslint/eslint-plugin@8.5.0': + resolution: {integrity: sha512-lHS5hvz33iUFQKuPFGheAB84LwcJ60G8vKnEhnfcK1l8kGVLro2SFYW6K0/tj8FUhRJ0VHyg1oAfg50QGbPPHw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -620,8 +623,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@8.4.0': - resolution: {integrity: sha512-NHgWmKSgJk5K9N16GIhQ4jSobBoJwrmURaLErad0qlLjrpP5bECYg+wxVTGlGZmJbU03jj/dfnb6V9bw+5icsA==} + '@typescript-eslint/parser@8.5.0': + resolution: {integrity: sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -630,12 +633,12 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@8.4.0': - resolution: {integrity: sha512-n2jFxLeY0JmKfUqy3P70rs6vdoPjHK8P/w+zJcV3fk0b0BwRXC/zxRTEnAsgYT7MwdQDt/ZEbtdzdVC+hcpF0A==} + '@typescript-eslint/scope-manager@8.5.0': + resolution: {integrity: sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.4.0': - resolution: {integrity: sha512-pu2PAmNrl9KX6TtirVOrbLPLwDmASpZhK/XU7WvoKoCUkdtq9zF7qQ7gna0GBZFN0hci0vHaSusiL2WpsQk37A==} + '@typescript-eslint/type-utils@8.5.0': + resolution: {integrity: sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -643,12 +646,12 @@ packages: typescript: optional: true - '@typescript-eslint/types@8.4.0': - resolution: {integrity: sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==} + '@typescript-eslint/types@8.5.0': + resolution: {integrity: sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.4.0': - resolution: {integrity: sha512-kJ2OIP4dQw5gdI4uXsaxUZHRwWAGpREJ9Zq6D5L0BweyOrWsL6Sz0YcAZGWhvKnH7fm1J5YFE1JrQL0c9dd53A==} + '@typescript-eslint/typescript-estree@8.5.0': + resolution: {integrity: sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -656,14 +659,14 @@ packages: typescript: optional: true - '@typescript-eslint/utils@8.4.0': - resolution: {integrity: sha512-swULW8n1IKLjRAgciCkTCafyTHHfwVQFt8DovmaF69sKbOxTSFMmIZaSHjqO9i/RV0wIblaawhzvtva8Nmm7lQ==} + '@typescript-eslint/utils@8.5.0': + resolution: {integrity: sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - '@typescript-eslint/visitor-keys@8.4.0': - resolution: {integrity: sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A==} + '@typescript-eslint/visitor-keys@8.5.0': + resolution: {integrity: sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} accepts@1.3.8: @@ -675,8 +678,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.3: - resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} acorn@5.7.4: @@ -703,8 +706,8 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} ansi-styles@4.3.0: @@ -736,9 +739,6 @@ packages: async@2.6.4: resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==} - async@3.2.6: - resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -840,32 +840,17 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-string@1.9.1: - resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} - color-support@1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true - color@3.2.1: - resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} - - colorspace@1.1.4: - resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} - commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -909,8 +894,8 @@ packages: supports-color: optional: true - debug@4.3.6: - resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -949,6 +934,9 @@ packages: engines: {node: '>= 0.8.0'} hasBin: true + diary@0.4.5: + resolution: {integrity: sha512-dUtG/AVG5bt9Mi+23TgTvjZ0NDJaszjs1GpYooM5cbEzk2xoqdvxCOlVw0xkenQXZw/DFxp23tj5VkP6YmlRmw==} + diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -957,8 +945,8 @@ packages: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} - diff@6.0.0: - resolution: {integrity: sha512-NbGtgPSw7il+jeajji1H6iKjCk3r/ANQKw3FFUhGV50+MH5MKIMeUmi53piTr7jlkWcq9eS858qbkRzkehwe+w==} + diff@7.0.0: + resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==} engines: {node: '>=0.3.1'} eastasianwidth@0.2.0: @@ -981,9 +969,6 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - enabled@2.0.0: - resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} - encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -1034,8 +1019,8 @@ packages: resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.9.1: - resolution: {integrity: sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==} + eslint@9.10.0: + resolution: {integrity: sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1100,9 +1085,6 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - fecha@4.2.3: - resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} - file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -1130,11 +1112,8 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - fn.name@1.1.0: - resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} - - follow-redirects@1.15.8: - resolution: {integrity: sha512-xgrmBhBToVKay1q2Tao5LI26B83UhrB/vM1avwVSDzt8rx3rO6AizBAaF46EgksTVr+rFTQaqZZ9MVBfUe4nig==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -1267,9 +1246,6 @@ packages: resolution: {integrity: sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==} engines: {node: '>= 0.10'} - is-arrayish@0.3.2: - resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -1315,10 +1291,6 @@ packages: is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} @@ -1377,9 +1349,6 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kuler@2.0.0: - resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} - levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1406,6 +1375,9 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.padstart@4.6.1: + resolution: {integrity: sha512-sW73O6S8+Tg66eY56DBk85aQzzUJDtpoXFBgELMd5P/SotAguo+1kYO6RuYgXxA4HJH3LFTFPASX6ET6bjfriw==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -1413,10 +1385,6 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} - logform@2.6.1: - resolution: {integrity: sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==} - engines: {node: '>= 12.0.0'} - loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -1427,8 +1395,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.0.0: - resolution: {integrity: sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==} + lru-cache@11.0.1: + resolution: {integrity: sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==} engines: {node: 20 || >=22} lunr@2.3.9: @@ -1507,9 +1475,6 @@ packages: ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1535,8 +1500,8 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - one-time@1.0.0: - resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + oniguruma-to-js@0.3.3: + resolution: {integrity: sha512-m90/WEhgs8g4BxG37+Nu3YrMfJDs2YXtYtIllhsEPR+wP3+K4EZk6dDUvy2v2K4MNFDDOYKL4/yqYPXDqyozTQ==} opn@5.3.0: resolution: {integrity: sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==} @@ -1592,8 +1557,8 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - pnpm@9.9.0: - resolution: {integrity: sha512-YMGKzROL/2ldM5vmrRP36TbupnRWYNTMSndtUkfFQNDt7hpWNpXBg6ZuuRfviPK0/rH8JfMqetytx6rzQ46ZwQ==} + pnpm@9.10.0: + resolution: {integrity: sha512-c6Ka+jag0JLs5Scd5Rd+y/gxjUVOzXATQxMbjrwMGpHEh9pGq3fI5ZbWrPFGHjWUztS+zt+JIbB0+9hlPtcFHA==} engines: {node: '>=18.12'} hasBin: true @@ -1639,14 +1604,13 @@ packages: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + regex@4.3.2: + resolution: {integrity: sha512-kK/AA3A9K6q2js89+VMymcboLOlF5lZRCYJv3gzszXFHBr6kO6qLGzbm+UIugBEV8SMMKCTR59txoY6ctRHYVw==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -1701,10 +1665,6 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-stable-stringify@2.5.0: - resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} - engines: {node: '>=10'} - safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -1748,16 +1708,13 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shiki@1.16.2: - resolution: {integrity: sha512-gSym0hZf5a1U0iDPsdoOAZbvoi+e0c6c3NKAi03FoSLTm7oG20tum29+gk0wzzivOasn3loxfGUPT+jZXIUbWg==} + shiki@1.16.3: + resolution: {integrity: sha512-GypUE+fEd06FqDs63LSAVlmq7WsahhPQU62cgZxGF+TJT5LjD2k7HTxXj4/CKOVuMM3+wWQ1t4Y5oooeJFRRBQ==} signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-swizzle@0.2.2: - resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} - socket.io-adapter@2.5.5: resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==} @@ -1780,9 +1737,6 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - stack-trace@0.0.10: - resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} - statuses@1.3.1: resolution: {integrity: sha512-wuTCPGlJONk/a1kqZ4fQM2+908lC7fa7nPYpTC1EhnvqLX/IICbeP1OZGDtA374trpSq68YubKUMo8oRhN46yg==} engines: {node: '>= 0.6'} @@ -1808,9 +1762,6 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1838,8 +1789,8 @@ packages: taffydb@2.7.3: resolution: {integrity: sha512-GQ3gtYFSOAxSMN/apGtDKKkbJf+8izz5YfbGqIsUc7AMiQOapARZ76dhilRY2h39cynYxBFdafQo5HUL5vgkrg==} - terser@5.31.6: - resolution: {integrity: sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==} + terser@5.32.0: + resolution: {integrity: sha512-v3Gtw3IzpBJ0ugkxEX8U0W6+TnPKRRCWGh1jC/iM/e3Ki5+qvO1L1EAZ56bZasc64aXHwRHNIQEzm6//i5cemQ==} engines: {node: '>=10'} hasBin: true @@ -1847,9 +1798,6 @@ packages: resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} engines: {node: '>=18'} - text-hex@1.0.0: - resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} - text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -1865,10 +1813,6 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - triple-beam@1.4.1: - resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} - engines: {node: '>= 14.0.0'} - ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -1906,15 +1850,15 @@ packages: peerDependencies: typedoc: 0.26.x - typedoc@0.26.6: - resolution: {integrity: sha512-SfEU3SH3wHNaxhFPjaZE2kNl/NFtLNW5c1oHsg7mti7GjmUj1Roq6osBQeMd+F4kL0BoRBBr8gQAuqBlfFu8LA==} + typedoc@0.26.7: + resolution: {integrity: sha512-gUeI/Wk99vjXXMi8kanwzyhmeFEGv1LTdTQsiyIsmSYsBebvFxhbcyAx7Zjo4cMbpLGxM4Uz3jVIjksu/I2v6Q==} engines: {node: '>= 18'} hasBin: true peerDependencies: - typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x + typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x - typescript-eslint@8.4.0: - resolution: {integrity: sha512-67qoc3zQZe3CAkO0ua17+7aCLI0dU+sSQd1eKPGq06QE4rfQjstVXR6woHO5qQvGUa550NfGckT4tzh3b3c8Pw==} + typescript-eslint@8.5.0: + resolution: {integrity: sha512-uD+XxEoSIvqtm4KE97etm32Tn5MfaZWgWfMMREStLxR6JzvHkc2Tkj7zhTEK5XmtpTmKHNnG8Sot6qDfhHtR1Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -1922,8 +1866,8 @@ packages: typescript: optional: true - typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + typescript@5.6.2: + resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} engines: {node: '>=14.17'} hasBin: true @@ -1947,9 +1891,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} @@ -1973,14 +1914,6 @@ packages: engines: {node: '>= 8'} hasBin: true - winston-transport@4.7.1: - resolution: {integrity: sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA==} - engines: {node: '>= 12.0.0'} - - winston@3.14.2: - resolution: {integrity: sha512-CO8cdpBB2yqzEf8v895L+GNKYJiEq8eKlHU38af3snQBQ+sdAIUepjMSguOIJC7ICbzm0ZI+Af2If4vIJrtmOg==} - engines: {node: '>= 12.0.0'} - word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -2056,18 +1989,10 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@colors/colors@1.6.0': {} - '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@dabh/diagnostics@2.0.3': - dependencies: - colorspace: 1.1.4 - enabled: 2.0.0 - kuler: 2.0.0 - '@esbuild/aix-ppc64@0.23.1': optional: true @@ -2140,9 +2065,9 @@ snapshots: '@esbuild/win32-x64@0.23.1': optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@9.9.1)': + '@eslint-community/eslint-utils@4.4.0(eslint@9.10.0)': dependencies: - eslint: 9.9.1 + eslint: 9.10.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.11.0': {} @@ -2150,7 +2075,7 @@ snapshots: '@eslint/config-array@0.18.0': dependencies: '@eslint/object-schema': 2.1.4 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -2158,7 +2083,7 @@ snapshots: '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) espree: 10.1.0 globals: 14.0.0 ignore: 5.3.2 @@ -2169,10 +2094,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.9.1': {} + '@eslint/js@9.10.0': {} '@eslint/object-schema@2.1.4': {} + '@eslint/plugin-kit@0.1.0': + dependencies: + levn: 0.4.1 + '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/retry@0.3.0': {} @@ -2261,11 +2190,11 @@ snapshots: optionalDependencies: rollup: 4.21.2 - '@rollup/plugin-typescript@11.1.6(rollup@4.21.2)(tslib@2.7.0)(typescript@5.4.5)': + '@rollup/plugin-typescript@11.1.6(rollup@4.21.2)(tslib@2.7.0)(typescript@5.6.2)': dependencies: '@rollup/pluginutils': 5.1.0(rollup@4.21.2) resolve: 1.22.8 - typescript: 5.4.5 + typescript: 5.6.2 optionalDependencies: rollup: 4.21.2 tslib: 2.7.0 @@ -2326,12 +2255,14 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.21.2': optional: true - '@shikijs/core@1.16.2': + '@shikijs/core@1.16.3': dependencies: - '@shikijs/vscode-textmate': 9.2.0 + '@shikijs/vscode-textmate': 9.2.2 '@types/hast': 3.0.4 + oniguruma-to-js: 0.3.3 + regex: 4.3.2 - '@shikijs/vscode-textmate@9.2.0': {} + '@shikijs/vscode-textmate@9.2.2': {} '@socket.io/component-emitter@3.1.2': {} @@ -2349,7 +2280,7 @@ snapshots: '@types/cors@2.8.17': dependencies: - '@types/node': 22.5.3 + '@types/node': 22.5.4 '@types/diff@5.2.2': {} @@ -2378,13 +2309,17 @@ snapshots: dependencies: '@types/lodash': 4.17.7 + '@types/lodash.padstart@4.6.9': + dependencies: + '@types/lodash': 4.17.7 + '@types/lodash@4.17.7': {} '@types/minimist@1.2.5': {} '@types/mocha@10.0.7': {} - '@types/node@22.5.3': + '@types/node@22.5.4': dependencies: undici-types: 6.19.8 @@ -2392,94 +2327,92 @@ snapshots: '@types/rollup-plugin-node-globals@1.4.4': dependencies: - '@types/node': 22.5.3 + '@types/node': 22.5.4 rollup: 0.63.5 '@types/tmp@0.2.6': {} - '@types/triple-beam@1.3.5': {} - '@types/unist@3.0.3': {} - '@typescript-eslint/eslint-plugin@8.4.0(@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.4.5))(eslint@9.9.1)(typescript@5.4.5)': + '@typescript-eslint/eslint-plugin@8.5.0(@typescript-eslint/parser@8.5.0(eslint@9.10.0)(typescript@5.6.2))(eslint@9.10.0)(typescript@5.6.2)': dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 8.4.0(eslint@9.9.1)(typescript@5.4.5) - '@typescript-eslint/scope-manager': 8.4.0 - '@typescript-eslint/type-utils': 8.4.0(eslint@9.9.1)(typescript@5.4.5) - '@typescript-eslint/utils': 8.4.0(eslint@9.9.1)(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 8.4.0 - eslint: 9.9.1 + '@typescript-eslint/parser': 8.5.0(eslint@9.10.0)(typescript@5.6.2) + '@typescript-eslint/scope-manager': 8.5.0 + '@typescript-eslint/type-utils': 8.5.0(eslint@9.10.0)(typescript@5.6.2) + '@typescript-eslint/utils': 8.5.0(eslint@9.10.0)(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 8.5.0 + eslint: 9.10.0 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.4.5) + ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: - typescript: 5.4.5 + typescript: 5.6.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.4.5)': + '@typescript-eslint/parser@8.5.0(eslint@9.10.0)(typescript@5.6.2)': dependencies: - '@typescript-eslint/scope-manager': 8.4.0 - '@typescript-eslint/types': 8.4.0 - '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 8.4.0 - debug: 4.3.6(supports-color@8.1.1) - eslint: 9.9.1 + '@typescript-eslint/scope-manager': 8.5.0 + '@typescript-eslint/types': 8.5.0 + '@typescript-eslint/typescript-estree': 8.5.0(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 8.5.0 + debug: 4.3.7(supports-color@8.1.1) + eslint: 9.10.0 optionalDependencies: - typescript: 5.4.5 + typescript: 5.6.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.4.0': + '@typescript-eslint/scope-manager@8.5.0': dependencies: - '@typescript-eslint/types': 8.4.0 - '@typescript-eslint/visitor-keys': 8.4.0 + '@typescript-eslint/types': 8.5.0 + '@typescript-eslint/visitor-keys': 8.5.0 - '@typescript-eslint/type-utils@8.4.0(eslint@9.9.1)(typescript@5.4.5)': + '@typescript-eslint/type-utils@8.5.0(eslint@9.10.0)(typescript@5.6.2)': dependencies: - '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.4.5) - '@typescript-eslint/utils': 8.4.0(eslint@9.9.1)(typescript@5.4.5) - debug: 4.3.6(supports-color@8.1.1) - ts-api-utils: 1.3.0(typescript@5.4.5) + '@typescript-eslint/typescript-estree': 8.5.0(typescript@5.6.2) + '@typescript-eslint/utils': 8.5.0(eslint@9.10.0)(typescript@5.6.2) + debug: 4.3.7(supports-color@8.1.1) + ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: - typescript: 5.4.5 + typescript: 5.6.2 transitivePeerDependencies: - eslint - supports-color - '@typescript-eslint/types@8.4.0': {} + '@typescript-eslint/types@8.5.0': {} - '@typescript-eslint/typescript-estree@8.4.0(typescript@5.4.5)': + '@typescript-eslint/typescript-estree@8.5.0(typescript@5.6.2)': dependencies: - '@typescript-eslint/types': 8.4.0 - '@typescript-eslint/visitor-keys': 8.4.0 - debug: 4.3.6(supports-color@8.1.1) + '@typescript-eslint/types': 8.5.0 + '@typescript-eslint/visitor-keys': 8.5.0 + debug: 4.3.7(supports-color@8.1.1) fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.4.5) + ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: - typescript: 5.4.5 + typescript: 5.6.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.4.0(eslint@9.9.1)(typescript@5.4.5)': + '@typescript-eslint/utils@8.5.0(eslint@9.10.0)(typescript@5.6.2)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1) - '@typescript-eslint/scope-manager': 8.4.0 - '@typescript-eslint/types': 8.4.0 - '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.4.5) - eslint: 9.9.1 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.10.0) + '@typescript-eslint/scope-manager': 8.5.0 + '@typescript-eslint/types': 8.5.0 + '@typescript-eslint/typescript-estree': 8.5.0(typescript@5.6.2) + eslint: 9.10.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@8.4.0': + '@typescript-eslint/visitor-keys@8.5.0': dependencies: - '@typescript-eslint/types': 8.4.0 + '@typescript-eslint/types': 8.5.0 eslint-visitor-keys: 3.4.3 accepts@1.3.8: @@ -2491,7 +2424,7 @@ snapshots: dependencies: acorn: 8.12.1 - acorn-walk@8.3.3: + acorn-walk@8.3.4: dependencies: acorn: 8.12.1 @@ -2517,7 +2450,7 @@ snapshots: ansi-regex@5.0.1: {} - ansi-regex@6.0.1: {} + ansi-regex@6.1.0: {} ansi-styles@4.3.0: dependencies: @@ -2542,8 +2475,6 @@ snapshots: dependencies: lodash: 4.17.21 - async@3.2.6: {} - balanced-match@1.0.2: {} base64id@2.0.0: {} @@ -2692,35 +2623,14 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} - color-string@1.9.1: - dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.2 - color-support@1.1.3: {} - color@3.2.1: - dependencies: - color-convert: 1.9.3 - color-string: 1.9.1 - - colorspace@1.1.4: - dependencies: - color: 3.2.1 - text-hex: 1.0.0 - commander@2.20.3: {} commondir@1.0.1: {} @@ -2759,9 +2669,9 @@ snapshots: dependencies: ms: 2.0.0 - debug@4.3.6(supports-color@8.1.1): + debug@4.3.7(supports-color@8.1.1): dependencies: - ms: 2.1.2 + ms: 2.1.3 optionalDependencies: supports-color: 8.1.1 @@ -2781,11 +2691,13 @@ snapshots: dev-ip@1.0.1: {} + diary@0.4.5: {} + diff@4.0.2: {} diff@5.2.0: {} - diff@6.0.0: {} + diff@7.0.0: {} eastasianwidth@0.2.0: {} @@ -2803,14 +2715,12 @@ snapshots: emoji-regex@9.2.2: {} - enabled@2.0.0: {} - encodeurl@1.0.2: {} engine.io-client@6.5.4: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) engine.io-parser: 5.2.3 ws: 8.17.1 xmlhttprequest-ssl: 2.0.0 @@ -2825,12 +2735,12 @@ snapshots: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.17 - '@types/node': 22.5.3 + '@types/node': 22.5.4 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 cors: 2.8.5 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) engine.io-parser: 5.2.3 ws: 8.17.1 transitivePeerDependencies: @@ -2887,20 +2797,21 @@ snapshots: eslint-visitor-keys@4.0.0: {} - eslint@9.9.1: + eslint@9.10.0: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.10.0) '@eslint-community/regexpp': 4.11.0 '@eslint/config-array': 0.18.0 '@eslint/eslintrc': 3.1.0 - '@eslint/js': 9.9.1 + '@eslint/js': 9.10.0 + '@eslint/plugin-kit': 0.1.0 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.3.0 '@nodelib/fs.walk': 1.2.8 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) escape-string-regexp: 4.0.0 eslint-scope: 8.0.2 eslint-visitor-keys: 4.0.0 @@ -2916,7 +2827,6 @@ snapshots: is-glob: 4.0.3 is-path-inside: 3.0.3 json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 @@ -2976,8 +2886,6 @@ snapshots: dependencies: reusify: 1.0.4 - fecha@4.2.3: {} - file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -3012,9 +2920,7 @@ snapshots: flatted@3.3.1: {} - fn.name@1.1.0: {} - - follow-redirects@1.15.8: {} + follow-redirects@1.15.9: {} foreground-child@3.3.0: dependencies: @@ -3112,7 +3018,7 @@ snapshots: http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.8 + follow-redirects: 1.15.9 requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -3143,8 +3049,6 @@ snapshots: install@0.13.0: {} - is-arrayish@0.3.2: {} - is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -3181,8 +3085,6 @@ snapshots: dependencies: '@types/estree': 1.0.5 - is-stream@2.0.1: {} - is-unicode-supported@0.1.0: {} is-wsl@1.1.0: {} @@ -3238,8 +3140,6 @@ snapshots: dependencies: json-buffer: 3.0.1 - kuler@2.0.0: {} - levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -3263,6 +3163,8 @@ snapshots: lodash.merge@4.6.2: {} + lodash.padstart@4.6.1: {} + lodash@4.17.21: {} log-symbols@4.1.0: @@ -3270,15 +3172,6 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 - logform@2.6.1: - dependencies: - '@colors/colors': 1.6.0 - '@types/triple-beam': 1.3.5 - fecha: 4.2.3 - ms: 2.1.3 - safe-stable-stringify: 2.5.0 - triple-beam: 1.4.1 - loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -3289,7 +3182,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.0.0: {} + lru-cache@11.0.1: {} lunr@2.3.9: {} @@ -3360,7 +3253,7 @@ snapshots: ansi-colors: 4.1.3 browser-stdout: 1.3.1 chokidar: 3.6.0 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) diff: 5.2.0 escape-string-regexp: 4.0.0 find-up: 5.0.0 @@ -3380,8 +3273,6 @@ snapshots: ms@2.0.0: {} - ms@2.1.2: {} - ms@2.1.3: {} natural-compare@1.4.0: {} @@ -3400,9 +3291,7 @@ snapshots: dependencies: wrappy: 1.0.2 - one-time@1.0.0: - dependencies: - fn.name: 1.1.0 + oniguruma-to-js@0.3.3: {} opn@5.3.0: dependencies: @@ -3446,14 +3335,14 @@ snapshots: path-scurry@2.0.0: dependencies: - lru-cache: 11.0.0 + lru-cache: 11.0.1 minipass: 7.1.2 pathval@2.0.0: {} picomatch@2.3.1: {} - pnpm@9.9.0: {} + pnpm@9.10.0: {} portscanner@2.2.0: dependencies: @@ -3493,16 +3382,12 @@ snapshots: dependencies: loose-envify: 1.4.0 - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - readdirp@3.6.0: dependencies: picomatch: 2.3.1 + regex@4.3.2: {} + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -3544,7 +3429,7 @@ snapshots: rollup@0.63.5: dependencies: '@types/estree': 0.0.39 - '@types/node': 22.5.3 + '@types/node': 22.5.4 rollup@4.21.2: dependencies: @@ -3576,8 +3461,6 @@ snapshots: safe-buffer@5.2.1: {} - safe-stable-stringify@2.5.0: {} - safer-buffer@2.1.2: {} scheduler@0.23.2: @@ -3641,21 +3524,17 @@ snapshots: shebang-regex@3.0.0: {} - shiki@1.16.2: + shiki@1.16.3: dependencies: - '@shikijs/core': 1.16.2 - '@shikijs/vscode-textmate': 9.2.0 + '@shikijs/core': 1.16.3 + '@shikijs/vscode-textmate': 9.2.2 '@types/hast': 3.0.4 signal-exit@4.1.0: {} - simple-swizzle@0.2.2: - dependencies: - is-arrayish: 0.3.2 - socket.io-adapter@2.5.5: dependencies: - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) ws: 8.17.1 transitivePeerDependencies: - bufferutil @@ -3665,7 +3544,7 @@ snapshots: socket.io-client@4.7.5: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) engine.io-client: 6.5.4 socket.io-parser: 4.2.4 transitivePeerDependencies: @@ -3676,7 +3555,7 @@ snapshots: socket.io-parser@4.2.4: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -3685,7 +3564,7 @@ snapshots: accepts: 1.3.8 base64id: 2.0.0 cors: 2.8.5 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) engine.io: 6.5.5 socket.io-adapter: 2.5.5 socket.io-parser: 4.2.4 @@ -3701,8 +3580,6 @@ snapshots: source-map@0.6.1: {} - stack-trace@0.0.10: {} - statuses@1.3.1: {} statuses@1.4.0: {} @@ -3726,17 +3603,13 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 strip-ansi@7.1.0: dependencies: - ansi-regex: 6.0.1 + ansi-regex: 6.1.0 strip-json-comments@3.1.1: {} @@ -3752,7 +3625,7 @@ snapshots: taffydb@2.7.3: {} - terser@5.31.6: + terser@5.32.0: dependencies: '@jridgewell/source-map': 0.3.6 acorn: 8.12.1 @@ -3765,8 +3638,6 @@ snapshots: glob: 10.4.5 minimatch: 9.0.5 - text-hex@1.0.0: {} - text-table@0.2.0: {} tmp@0.2.3: {} @@ -3777,27 +3648,25 @@ snapshots: toidentifier@1.0.1: {} - triple-beam@1.4.1: {} - - ts-api-utils@1.3.0(typescript@5.4.5): + ts-api-utils@1.3.0(typescript@5.6.2): dependencies: - typescript: 5.4.5 + typescript: 5.6.2 - ts-node@10.9.2(@types/node@22.5.3)(typescript@5.4.5): + ts-node@10.9.2(@types/node@22.5.4)(typescript@5.6.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.5.3 + '@types/node': 22.5.4 acorn: 8.12.1 - acorn-walk: 8.3.3 + acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.4.5 + typescript: 5.6.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 @@ -3814,31 +3683,31 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typedoc-plugin-missing-exports@3.0.0(typedoc@0.26.6(typescript@5.4.5)): + typedoc-plugin-missing-exports@3.0.0(typedoc@0.26.7(typescript@5.6.2)): dependencies: - typedoc: 0.26.6(typescript@5.4.5) + typedoc: 0.26.7(typescript@5.6.2) - typedoc@0.26.6(typescript@5.4.5): + typedoc@0.26.7(typescript@5.6.2): dependencies: lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 - shiki: 1.16.2 - typescript: 5.4.5 + shiki: 1.16.3 + typescript: 5.6.2 yaml: 2.5.1 - typescript-eslint@8.4.0(eslint@9.9.1)(typescript@5.4.5): + typescript-eslint@8.5.0(eslint@9.10.0)(typescript@5.6.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.4.0(@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.4.5))(eslint@9.9.1)(typescript@5.4.5) - '@typescript-eslint/parser': 8.4.0(eslint@9.9.1)(typescript@5.4.5) - '@typescript-eslint/utils': 8.4.0(eslint@9.9.1)(typescript@5.4.5) + '@typescript-eslint/eslint-plugin': 8.5.0(@typescript-eslint/parser@8.5.0(eslint@9.10.0)(typescript@5.6.2))(eslint@9.10.0)(typescript@5.6.2) + '@typescript-eslint/parser': 8.5.0(eslint@9.10.0)(typescript@5.6.2) + '@typescript-eslint/utils': 8.5.0(eslint@9.10.0)(typescript@5.6.2) optionalDependencies: - typescript: 5.4.5 + typescript: 5.6.2 transitivePeerDependencies: - eslint - supports-color - typescript@5.4.5: {} + typescript@5.6.2: {} ua-parser-js@1.0.38: {} @@ -3854,8 +3723,6 @@ snapshots: dependencies: punycode: 2.3.1 - util-deprecate@1.0.2: {} - utils-merge@1.0.1: {} v8-compile-cache-lib@3.0.1: {} @@ -3874,26 +3741,6 @@ snapshots: dependencies: isexe: 2.0.0 - winston-transport@4.7.1: - dependencies: - logform: 2.6.1 - readable-stream: 3.6.2 - triple-beam: 1.4.1 - - winston@3.14.2: - dependencies: - '@colors/colors': 1.6.0 - '@dabh/diagnostics': 2.0.3 - async: 3.2.6 - is-stream: 2.0.1 - logform: 2.6.1 - one-time: 1.0.0 - readable-stream: 3.6.2 - safe-stable-stringify: 2.5.0 - stack-trace: 0.0.10 - triple-beam: 1.4.1 - winston-transport: 4.7.1 - word-wrap@1.2.5: {} workerpool@6.5.1: {} diff --git a/src/JavascriptValidators.mts b/src/JavascriptValidators.mts index 5d49e64..16d1cbb 100644 --- a/src/JavascriptValidators.mts +++ b/src/JavascriptValidators.mts @@ -50,80 +50,80 @@ abstract class JavascriptValidators * * @returns an instance of this interface */ - static newInstance(): JavascriptValidators + public static newInstance(): JavascriptValidators { return new JavascriptValidatorsImpl(MainApplicationScope.INSTANCE, Configuration.DEFAULT); } - abstract copy(): JavascriptValidators; + public abstract copy(): JavascriptValidators; - abstract getContext(): Map; + public abstract getContext(): Map; - abstract withContext(value: unknown, name: string): this; + public abstract withContext(value: unknown, name: string): this; - abstract getGlobalConfiguration(): GlobalConfiguration; + public abstract getGlobalConfiguration(): GlobalConfiguration; - abstract removeContext(name: string): this; + public abstract removeContext(name: string): this; - abstract requireThatNumber + public abstract requireThatNumber (value: T, name: string): NumberValidator; - abstract requireThatBoolean + public abstract requireThatBoolean (value: T, name: string): BooleanValidator; - abstract requireThatArray + public abstract requireThatArray (value: T, name: string): ArrayValidator; - abstract requireThatSet | undefined | null, E> + public abstract requireThatSet | undefined | null, E> (value: T, name: string): SetValidator; - abstract requireThatMap | undefined | null, K, V> + public abstract requireThatMap | undefined | null, K, V> (value: T, name: string): MapValidator; - abstract requireThatString + public abstract requireThatString (value: T, name: string): StringValidator; - abstract requireThat(value: T, name: string): UnknownValidator; + public abstract requireThat(value: T, name: string): UnknownValidator; - abstract assertThatNumber + public abstract assertThatNumber (value: T, name?: string): NumberValidator; - abstract assertThatBoolean + public abstract assertThatBoolean (value: T, name?: string): BooleanValidator; - abstract assertThatArray + public abstract assertThatArray (value: T, name?: string): ArrayValidator; - abstract assertThatSet | undefined | null, E> + public abstract assertThatSet | undefined | null, E> (value: T, name?: string): SetValidator; - abstract assertThatMap | undefined | null, K, V> + public abstract assertThatMap | undefined | null, K, V> (value: T, name?: string): MapValidator; - abstract assertThatString + public abstract assertThatString (value: T, name?: string): StringValidator; - abstract assertThat(value: T, name?: string): UnknownValidator; + public abstract assertThat(value: T, name?: string): UnknownValidator; - abstract checkIfNumber + public abstract checkIfNumber (value: T, name?: string): NumberValidator; - abstract checkIfBoolean + public abstract checkIfBoolean (value: T, name?: string): BooleanValidator; - abstract checkIfArray + public abstract checkIfArray (value: T, name?: string): ArrayValidator; - abstract checkIfSet | undefined | null, E> + public abstract checkIfSet | undefined | null, E> (value: T, name?: string): SetValidator; - abstract checkIfMap | undefined | null, K, V> + public abstract checkIfMap | undefined | null, K, V> (value: T, name?: string): MapValidator; - abstract checkIfString + public abstract checkIfString (value: T, name?: string): StringValidator; - abstract checkIf(value: T, name?: string): UnknownValidator; + public abstract checkIf(value: T, name?: string): UnknownValidator; } export {JavascriptValidators}; \ No newline at end of file diff --git a/src/Type.mts b/src/Type.mts index 4e6d4e2..1296eec 100644 --- a/src/Type.mts +++ b/src/Type.mts @@ -47,7 +47,7 @@ class Type * @param value - a value * @returns the value's type * @see http://stackoverflow.com/a/332429/14731 - * @see {@link Type.isPrimitive} + * @see Type.isPrimitive */ public static of(value: unknown): Type { diff --git a/src/Validators.mts b/src/Validators.mts index 1aa40e1..d2bca20 100644 --- a/src/Validators.mts +++ b/src/Validators.mts @@ -8,6 +8,7 @@ const typedocWorkaround: null | ValidatorComponent = null; // noinspection PointlessBooleanExpressionJS if (typedocWorkaround !== null) console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); + /* eslint-enable @typescript-eslint/no-unnecessary-condition */ /** diff --git a/src/internal/Configuration.mts b/src/internal/Configuration.mts index b538e16..1f1cfa8 100644 --- a/src/internal/Configuration.mts +++ b/src/internal/Configuration.mts @@ -13,6 +13,7 @@ const typedocWorkaround: null | ValidationFailures = null; // noinspection PointlessBooleanExpressionJS if (typedocWorkaround !== null) console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); + /* eslint-enable @typescript-eslint/no-unnecessary-condition */ /** diff --git a/src/internal/ConfigurationUpdater.mts b/src/internal/ConfigurationUpdater.mts index d59e39f..4b42494 100644 --- a/src/internal/ConfigurationUpdater.mts +++ b/src/internal/ConfigurationUpdater.mts @@ -13,6 +13,7 @@ const typedocWorkaround: null | ValidationFailures = null; // noinspection PointlessBooleanExpressionJS if (typedocWorkaround !== null) console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); + /* eslint-enable @typescript-eslint/no-unnecessary-condition */ /** diff --git a/src/internal/message/diff/ContextGenerator.mts b/src/internal/message/diff/ContextGenerator.mts index d5fd818..8ecba3b 100644 --- a/src/internal/message/diff/ContextGenerator.mts +++ b/src/internal/message/diff/ContextGenerator.mts @@ -228,7 +228,7 @@ expectedName: ${expectedName}`); const elementGenerator = new ContextGenerator(this.scope, this.configuration, actualNameLine, expectedNameLine). - allowLegend(false); + allowLegend(false); actualValueLine.ifValid(value => elementGenerator.actualValue(value)); expectedValueLine.ifValid(value => elementGenerator.expectedValue(value)); diff --git a/src/internal/message/diff/DiffGenerator.mts b/src/internal/message/diff/DiffGenerator.mts index a986abd..103a3dd 100644 --- a/src/internal/message/diff/DiffGenerator.mts +++ b/src/internal/message/diff/DiffGenerator.mts @@ -265,7 +265,7 @@ class SimplifyDeltas * @param expectedBuilder - a buffer to insert the expected value of the word into * @returns the updated values of `actualBuilder` and `expectedBuilder` */ - processMiddleDeltas(actualBuilder: string, expectedBuilder: string, ) + processMiddleDeltas(actualBuilder: string, expectedBuilder: string) { for (let i = this.indexOfStartDelta + 1; i < this.indexOfEndDelta; ++i) { diff --git a/src/internal/validator/Terminal.mts b/src/internal/validator/Terminal.mts index 8f4a144..5ce5d60 100644 --- a/src/internal/validator/Terminal.mts +++ b/src/internal/validator/Terminal.mts @@ -65,7 +65,7 @@ Actual: ${String(chalk.level)}`); * @param force - true if the encoding should be forced regardless of what the system supports * @throws TypeError if `encoding` is not a `TerminalEncoding`. * If `force` is not a `boolean`. - * @see Terminal#useBestEncoding + * @see Terminal.useBestEncoding */ private setEncodingImpl(encoding: TerminalEncoding, force: boolean) { @@ -88,7 +88,7 @@ Actual: ${String(chalk.level)}`); * * @param encoding - the type of encoding that the terminal should use * @throws TypeError if `encoding` is not a `TerminalEncoding` - * @see Terminal#useBestEncoding + * @see Terminal.useBestEncoding */ setEncoding(encoding: TerminalEncoding) { @@ -98,7 +98,7 @@ Actual: ${String(chalk.level)}`); /** * Indicates that verifiers should output the best encoding supported by the terminal. * - * @see Terminal#setEncoding + * @see Terminal.setEncoding */ useBestEncoding() { diff --git a/src/validator/MapValidator.mts b/src/validator/MapValidator.mts index 8d8f958..972c4e2 100644 --- a/src/validator/MapValidator.mts +++ b/src/validator/MapValidator.mts @@ -1,7 +1,7 @@ -import type { - ValidatorComponent, - ArrayValidator, - UnsignedNumberValidator +import { + type ValidatorComponent, + type ArrayValidator, + type UnsignedNumberValidator } from "../internal/internal.mjs"; /** @@ -14,25 +14,25 @@ import type { interface MapValidator | undefined | null, K, V> extends ValidatorComponent { /** - * Returns a validator for the value's {@link Map.keys|keys}. - * @returns a validator for the value's {@link Map.keys|keys} + * Returns a validator for the map's {@link Map.keys|keys}. + * @returns a validator for the map's {@link Map.keys|keys} * @throws TypeError if the value is `undefined` or `null` */ keys(): ArrayValidator; /** - * Returns a validator for the value's {@link Map.values|values}. + * Returns a validator for the map's {@link Map.values|values}. * - * @returns a validator for the value's {@link Map.values|values} + * @returns a validator for the map's {@link Map.values|values} * @throws TypeError if the value is `undefined` or `null` */ values(): ArrayValidator; /** - * Returns a validator for the value's {@link Map.entries|entries} + * Returns a validator for the map's {@link Map.entries|entries} * (an array of `[key, this.value]` for each element in the Map). * - * @returns a validator for the value's {@link Map.entries|entries} + * @returns a validator for the map's {@link Map.entries|entries} * @throws TypeError if the value is `undefined` or `null` */ entries(): ArrayValidator<[K, V][], [K, V]>; diff --git a/src/validator/StringValidator.mts b/src/validator/StringValidator.mts index 65b98cc..de3445b 100644 --- a/src/validator/StringValidator.mts +++ b/src/validator/StringValidator.mts @@ -106,14 +106,12 @@ interface StringValidator extends Validator isNotEmpty(): this; /** - * Ensures that the value does not contain leading or trailing whitespace, where whitespace is defined by - * {@link String.trim}. + * Ensures that the value does not contain leading or trailing whitespace. * * @returns this * @throws TypeError if the value is `undefined` or `null` * @throws RangeError if the value contains leading or trailing whitespace - * @see {@link String.trim} - * @see {@link isEmpty} + * @see isEmpty */ isTrimmed(): this; diff --git a/src/validator/component/CollectionComponent.mts b/src/validator/component/CollectionComponent.mts index 7ffc7fc..f3603fa 100644 --- a/src/validator/component/CollectionComponent.mts +++ b/src/validator/component/CollectionComponent.mts @@ -8,6 +8,7 @@ const typedocWorkaround: null | ValidatorComponent = null; // noinspection PointlessBooleanExpressionJS if (typedocWorkaround !== null) console.log("WORKAROUND: https://github.com/microsoft/tsdoc/issues/348"); + /* eslint-enable @typescript-eslint/no-unnecessary-condition */ /** diff --git a/src/validator/component/NumberComponent.mts b/src/validator/component/NumberComponent.mts index 4e54855..7de1571 100644 --- a/src/validator/component/NumberComponent.mts +++ b/src/validator/component/NumberComponent.mts @@ -198,8 +198,8 @@ interface NumberComponent * @returns this * @throws TypeError if the value is `undefined` or `null` * @throws RangeError if value is not a number or is an infinite number - * @see {@link isNumber} - * @see {@link Number.isFinite} + * @see isNumber + * @see Number.isFinite */ isFinite(): this; @@ -209,8 +209,8 @@ interface NumberComponent * @returns this * @throws TypeError if the value is `undefined` or `null` * @throws RangeError if value is not a number or is a finite number - * @see {@link isNumber} - * @see {@link Number.isFinite} + * @see isNumber + * @see Number.isFinite */ isInfinite(): this; @@ -220,7 +220,7 @@ interface NumberComponent * @returns this * @throws TypeError if the value is `undefined` or `null` * @throws RangeError if value is not a well-defined number - * @see #isNotNumber() + * @see isNotNumber */ isNumber(): this; diff --git a/src/validator/component/ValidatorComponent.mts b/src/validator/component/ValidatorComponent.mts index ae99c89..74bc4f4 100644 --- a/src/validator/component/ValidatorComponent.mts +++ b/src/validator/component/ValidatorComponent.mts @@ -59,7 +59,7 @@ interface ValidatorComponent * ``` * * @returns an unmodifiable map from each entry's name to its value - * @see {@link Validators.getContext} + * @see Validators.getContext */ getContext(): Map; diff --git a/test/RequirementsTest.mts b/test/RequirementsTest.mts index 73aa0a8..f09f036 100644 --- a/test/RequirementsTest.mts +++ b/test/RequirementsTest.mts @@ -63,7 +63,7 @@ suite("RequirementsTest", () => const localValidators = validators.copy(); const actual = {}; - localValidators.assertThat(actual, "actual").isEqualTo("expected"); + localValidators.assertThat(actual, "actual").isEqualTo("expected").elseThrow(); }, AssertionError); }); diff --git a/tsconfig.json b/tsconfig.json index fcb15ac..7026361 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,8 +14,10 @@ "strict": true, "forceConsistentCasingInFileNames": true, "verbatimModuleSyntax": true, - // Required by C8 + // Required by C8 and IntelliJ's debugger "sourceMap": true + // WORKAROUND: https://github.com/isaacs/node-lru-cache/issues/353 + // "strictBuiltinIteratorReturn": false }, "include": [ // Files used to generate the target directory From be84e79582d913c6a3d4da3c1083a47bd774e16a Mon Sep 17 00:00:00 2001 From: Gili Tzabari Date: Tue, 10 Sep 2024 08:35:54 -0400 Subject: [PATCH 12/15] Cosmetic changes --- .idea/codeStyles/Project.xml | 1 - build/Project.mts | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index f2e8495..d9cc7ed 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -15,7 +15,6 @@