diff --git a/.editorconfig b/.editorconfig index 9966bd6..3fefca2 100755 --- a/.editorconfig +++ b/.editorconfig @@ -1,19 +1,19 @@ # http://editorconfig.org -root = true +root=true [*] -charset = utf-8 -end_of_line = lf -indent_style = space -indent_size = 4 -trim_trailing_whitespace = true -insert_final_newline = true +charset=utf-8 +end_of_line=lf +indent_style=space +indent_size=4 +trim_trailing_whitespace=true +insert_final_newline=true [*.md] -trim_trailing_whitespace = false +trim_trailing_whitespace=false # Use 2 spaces since npm does not respect custom indentation settings [package.json] -indent_style = space -indent_size = 2 +indent_style=space +indent_size=2 diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100755 index 170b4a9..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,308 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module', - impliedStrict: true, - warnOnUnsupportedTypeScriptVersion: false - }, - env: { - node: true - }, - plugins: ['@typescript-eslint', 'import', 'no-null', 'prefer-arrow', 'prettier', 'import'], - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:prettier/recommended', - 'prettier/prettier', - 'plugin:import/recommended', - 'plugin:import/typescript' - ], - noInlineConfig: false, - reportUnusedDisableDirectives: true, - rules: { - '@typescript-eslint/array-type': ['error'], - '@typescript-eslint/ban-types': ['error'], - '@typescript-eslint/consistent-type-definitions': ['error'], - '@typescript-eslint/explicit-function-return-type': [ - 'error', - { - allowExpressions: true, - allowTypedFunctionExpressions: true, - allowHigherOrderFunctions: true - } - ], - '@typescript-eslint/explicit-member-accessibility': [ - 'error', - { - accessibility: 'explicit' - } - ], - '@typescript-eslint/member-ordering': [ - 'error', - { - default: { - memberTypes: [ - // Static - // ====== - // fields - 'public-static-field', - 'protected-static-field', - 'private-static-field', - '#private-static-field', - 'static-field', - // accessors - 'public-static-accessor', - 'protected-static-accessor', - 'private-static-accessor', - '#private-static-accessor', - 'static-accessor', - // initialization - 'static-initialization', - // Getter and setter - ['public-static-get', 'public-static-set'], - ['protected-static-get', 'protected-static-set'], - ['private-static-get', 'private-static-set'], - ['#private-static-get', '#private-static-set'], - ['static-get', 'static-set'], - // Methods - 'public-static-method', - 'protected-static-method', - 'private-static-method', - '#private-static-method', - 'static-method', - - // Instance - // ======== - // Index signature - 'signature', - 'call-signature', - - // Fields - 'public-abstract-field', - 'protected-abstract-field', - - 'public-decorated-field', - 'protected-decorated-field', - 'private-decorated-field', - - 'public-instance-field', - 'protected-instance-field', - 'private-instance-field', - '#private-instance-field', - - 'public-field', - 'protected-field', - 'private-field', - '#private-field', - - 'abstract-field', - 'instance-field', - - 'decorated-field', - - 'field', - - // Constructors - 'public-constructor', - 'protected-constructor', - 'private-constructor', - 'constructor', - - // Accessors - 'public-abstract-accessor', - 'protected-abstract-accessor', - - 'public-decorated-accessor', - 'protected-decorated-accessor', - 'private-decorated-accessor', - - 'public-instance-accessor', - 'protected-instance-accessor', - 'private-instance-accessor', - '#private-instance-accessor', - - 'public-accessor', - 'protected-accessor', - 'private-accessor', - '#private-accessor', - - 'abstract-accessor', - 'instance-accessor', - - 'decorated-accessor', - - 'accessor', - - // Getters and Setter - ['public-abstract-get', 'public-abstract-set'], - ['protected-abstract-get', 'protected-abstract-set'], - - ['public-decorated-get', 'public-decorated-set'], - ['protected-decorated-get', 'protected-decorated-set'], - ['private-decorated-get', 'private-decorated-set'], - - ['public-instance-get', 'public-instance-set'], - ['protected-instance-get', 'protected-instance-set'], - ['private-instance-get', 'private-instance-set'], - ['#private-instance-get', '#private-instance-set'], - - ['public-get', 'public-set'], - ['protected-get', 'protected-set'], - ['private-get', 'private-set'], - ['#private-get', '#private-set'], - - ['abstract-get', 'abstract-set'], - ['decorated-get', 'decorated-set'], - ['instance-get', 'instance-set'], - - ['get', 'set'], - - // Methods - 'public-abstract-method', - 'protected-abstract-method', - - 'public-decorated-method', - 'protected-decorated-method', - 'private-decorated-method', - - 'public-instance-method', - 'protected-instance-method', - 'private-instance-method', - '#private-instance-method', - - 'public-method', - 'protected-method', - 'private-method', - '#private-method', - - 'abstract-method', - 'instance-method', - - 'decorated-method', - - 'method' - ] - } - } - ], - '@typescript-eslint/no-empty-function': ['error'], - '@typescript-eslint/no-explicit-any': ['off'], - '@typescript-eslint/no-inferrable-types': ['off'], - '@typescript-eslint/no-namespace': ['off'], - '@typescript-eslint/no-parameter-properties': ['off'], - '@typescript-eslint/no-shadow': [ - 'error', - { - hoist: 'all' - } - ], - '@typescript-eslint/no-unused-vars': [ - 'error', - { - vars: 'all', - args: 'none', - ignoreRestSiblings: false - } - ], - '@typescript-eslint/prefer-for-of': ['error'], - '@typescript-eslint/prefer-function-type': ['error'], - '@typescript-eslint/prefer-namespace-keyword': ['error'], - '@typescript-eslint/triple-slash-reference': ['error'], - '@typescript-eslint/unified-signatures': ['error'], - - 'no-null/no-null': ['error'], - - 'prettier/prettier': ['error'], - - 'prefer-arrow/prefer-arrow-functions': [ - 'error', - { - disallowPrototype: true, - singleReturnOnly: true, - classPropertiesAllowed: false - } - ], - - 'import/no-unresolved': ['error', { ignore: ['^@?[\\w\\d-_]+/?[\\w\\d-_]+/[\\w\\d-_]+$'] }], - 'import/order': [ - 'error', - { - groups: ['builtin', 'external', 'internal', 'sibling', 'parent', 'index', 'unknown'], - 'newlines-between': 'always', - alphabetize: { - /* sort in ascending order. Options: ["ignore", "asc", "desc"] */ - order: 'asc', - /* ignore case. Options: [true, false] */ - caseInsensitive: true - } - } - ], - - 'arrow-body-style': ['error'], - camelcase: ['error'], - 'capitalized-comments': ['off'], - 'comma-dangle': [ - 'error', - { - objects: 'never', - arrays: 'never', - functions: 'never' - } - ], - complexity: ['off'], - 'default-case': ['error'], - 'dot-location': ['error', 'property'], - eqeqeq: ['error', 'smart'], - 'max-len': [ - 'error', - { - code: 120, - ignoreUrls: true - } - ], - 'no-alert': ['error'], - 'no-bitwise': ['error'], - 'no-caller': ['error'], - 'no-console': ['error'], - 'no-constructor-return': ['error'], - 'no-div-regex': ['error'], - 'no-empty': ['error'], - 'no-empty-function': [ - 'error', - { - allow: ['constructors'] - } - ], - 'no-eval': ['error'], - 'no-extra-bind': ['error'], - 'no-import-assign': ['error'], - 'no-invalid-this': ['error'], - 'no-labels': ['error'], - 'no-multiple-empty-lines': ['error'], - 'no-new-wrappers': ['error'], - 'no-regex-spaces': ['error'], - 'no-return-assign': ['error'], - 'no-return-await': ['error'], - 'no-self-compare': ['error'], - 'no-shadow': ['off'], - 'no-throw-literal': ['error'], - 'no-undef-init': ['error'], - 'no-underscore-dangle': ['off'], - 'no-unused-expressions': ['error'], - 'no-useless-call': ['error'], - 'no-useless-concat': ['error'], - 'no-var': ['error'], - 'object-shorthand': ['error'], - 'one-var': ['error', 'never'], - 'prefer-const': ['error'], - 'prefer-regex-literals': ['error'], - radix: ['error'], - 'require-await': ['error'], - 'spaced-comment': ['error'], - 'use-isnan': ['error'], - 'valid-typeof': ['error'], - yoda: ['error'] - } -}; diff --git a/.gitignore b/.gitignore index 6a98bde..114ef15 100755 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,3 @@ dist/ # Docs generated folder docs/ - diff --git a/.husky/commit-msg b/.husky/commit-msg index 2f1a4f5..a7dcb45 100755 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -1,13 +1,8 @@ -#!/usr/bin/env sh - -# Load husky -. "$(dirname -- "$0")/_/husky.sh"; - # Read parameters -COMMIT_MSG_FILE=$1; +COMMIT_MSG_FILE=$1 # Set failing on command fail, and undefined variable use -set -eu; +set -eu # This hook is invoked by git-commit and git-merge, and can be # bypassed with the --no-verify option. It takes a single @@ -28,10 +23,10 @@ set -eu; # message sent has the correct format for the project, aborting otherwise. # Show welcome message -echo "**************************"; -echo "Linting the commit message"; -echo "**************************"; -echo ""; +echo "**************************" +echo "Linting the commit message" +echo "**************************" +echo "" # Run commitlint -npx --no -- commitlint --edit ${1}; +npx --no -- commitlint --edit ${1} diff --git a/.husky/pre-commit b/.husky/pre-commit index 0cfbe23..70e0926 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,13 +1,8 @@ -#!/usr/bin/env sh - -# Load husky -. "$(dirname -- "$0")/_/husky.sh"; - # Read parameters # NOTHING TO READ HERE # Set failing on command fail, and undefined variable use -set -eu; +set -eu # This hook is invoked by git-commit, and can be bypassed with the # --no-verify option. It takes no parameters, and is invoked before @@ -31,26 +26,29 @@ set -eu; # Generated files are added to the commit. # Show welcome message -echo "**************************"; -echo "Running pre commit hooks"; -echo "**************************"; -echo ""; +echo "**************************" +echo "Running pre commit hooks" +echo "**************************" +echo "" # Run license -echo "\nAdd license text to code files\n"; -npx --no -- gobstones-scripts run license --silent ; - -# Run prettify -echo "Run prettify\n"; -npx --no -- gobstones-scripts run prettify --silent ; +echo "\nAdd license text to code files\n" +npx --no -- gobstones-scripts run license --silent # Run changelog -echo "\nRun changelog\n"; -npx --no -- gobstones-scripts run changelog --silent ; +echo "\nRun changelog\n" +# Only run if not first commit +if git-log &> /dev/null; then + npx --no -- gobstones-scripts run changelog --silent +fi + +# Run prettify +echo "Run prettify\n" +npx --no -- gobstones-scripts run prettify --silent # Add all generated files -echo "\nAdd generated files to commit\n"; -git add --all; +echo "\nAdd generated files to commit\n" +git add --all # Exit -exit 0; +exit 0 diff --git a/.husky/pre-push b/.husky/pre-push index 6d1acf0..305a10c 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,24 +1,23 @@ -#!/usr/bin/env sh - -# Load husky -. "$(dirname -- "$0")/_/husky.sh" - # Read parameters -REMOTE_NAME=$1; -REMOTE_URL=$2; +REMOTE_NAME=$1 +REMOTE_URL=$2 # Read the input and split it approprietly -INPUT=$(cat); -COUNTER=0; +INPUT=$(cat) +COUNTER=0 for WORD in $INPUT; do case $COUNTER in 0) - LOCAL_REF=$WORD;; + LOCAL_REF=$WORD + ;; 1) - LOCAL_OBJ_NAME=$WORD;; + LOCAL_OBJ_NAME=$WORD + ;; 2) - REMOTE_REF=$WORD;; + REMOTE_REF=$WORD + ;; 3) - REMOTE_OBJ_NAME=$WORD;; + REMOTE_OBJ_NAME=$WORD + ;; esac COUNTER=$((COUNTER + 1)) done @@ -26,14 +25,16 @@ done case "$LOCAL_REF" in refs\/tags*) # Apply specific code when publishing tags - IS_TAG=1;; + IS_TAG=1 + ;; *) # Apply specific code when publishing any branch - IS_TAG=0;; + IS_TAG=0 + ;; esac # Set failing on command fail, and undefined variable use -set -eu; +set -eu # This hook is called by git-push and can be used to prevent a # push from taking place. The hook is called with two parameters @@ -70,13 +71,13 @@ set -eu; # to match that of the tag. # Show welcome message -echo "**************************"; -echo "Running pre push hooks"; -echo "**************************"; -echo ""; +echo "**************************" +echo "Running pre push hooks" +echo "**************************" +echo "" # Run all the tests -echo "Running tests"; -npx --no -- gobstones-scripts run test --silent; +echo "Running tests" +npx --no -- gobstones-scripts run test --silent -exit 0; +exit 0 diff --git a/.husky/prepare-commit-msg b/.husky/prepare-commit-msg index d69cd7d..f68c5d7 100755 --- a/.husky/prepare-commit-msg +++ b/.husky/prepare-commit-msg @@ -1,15 +1,10 @@ -#!/usr/bin/env sh - -# Load husky -. "$(dirname -- "$0")/_/husky.sh"; - # Read parameters -COMMIT_MSG_FILE=$1; -COMMIT_SOURCE=$2; -SHA1=$3; +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 # Set failing on command fail, and undefined variable use -set -eu; +set -eu # This hook is invoked by git-commit right after preparing # the default log message, and before the editor is started. @@ -39,44 +34,44 @@ set -eu; # semantic commit message through a prompt. # Show welcome message -echo "**************************"; -echo "Generate commit message"; -echo "**************************"; -echo ""; +echo "**************************" +echo "Generate commit message" +echo "**************************" +echo "" # Check if we are doing an amend if [ "$COMMIT_SOURCE" = 'commit' ] && [ -n $SHA1 ]; then # Amends do not call the "cz" command. - exit 0; + exit 0 fi # Check if we are doing an squash if [ "$COMMIT_SOURCE" = 'squash' ]; then # Squashes should not trigger the cz command. - exit 0; + exit 0 fi # Check if we are doing an merge if [ "$COMMIT_SOURCE" = 'merge' ]; then # Merging should not trigger the cz command. - exit 0; + exit 0 fi # Check if we are using a template if [ "$COMMIT_SOURCE" = 'template' ]; then # Cannot use templates, warn the user and fail. - echo "Setting a template through -t or commit.template in this project "; - echo "has been disabled. Please run a simple commit.\n"; - exit 1; + echo "Setting a template through -t or commit.template in this project " + echo "has been disabled. Please run a simple commit.\n" + exit 1 fi # Check if a message was given if [ "$COMMIT_SOURCE" = 'message' ]; then # Cannot use message, warn the user and fail. - echo "Setting a message through -m or -F option in this project "; - echo "has been disabled. Please run a simple commit.\n"; - exit 1; + echo "Setting a message through -m or -F option in this project " + echo "has been disabled. Please run a simple commit.\n" + exit 1 fi # Regular commit has been performed, run the command. -exec < /dev/tty && npx cz --hook || true; +exec < /dev/tty && npx cz --hook || true diff --git a/.prettierrc b/.prettierrc index d58a139..6e9466b 100755 --- a/.prettierrc +++ b/.prettierrc @@ -1,67 +1,73 @@ { - "printWidth": 120, - "tabWidth": 4, - "useTabs": false, - "semi": true, - "singleQuote": true, - "endOfLine": "lf", - "trailingComma": "none", - "overrides": [ - { - "files": ["*.js", "**/*.js", "*.jsx", "**/*.jsx"], - "options": { - "parser": "babel" - } - }, - { - "files": ["*.ts", "**/*.ts", "*.tsx", "**/*.tsx"], - "options": { - "parser": "typescript" - } - }, - { - "files": ["*.yml", "**/*.yml", "*.yaml", "**/*.yaml"], - "options": { - "parser": "yaml" - } - }, - { - "files": ["*.json", "**/*.json"], - "options": { - "parser": "json" - } - }, - { - "files": ["*.md", "**/*.md"], - "options": { - "parser": "markdown" - } - }, - { - "files": ["*.css", "**/*.css", "*.scss", "**/*.scss", "*.less", "**/*.less"], - "options": { - "parser": "css" - } - }, - { - "files": ["*.html", "**/*.html"], - "options": { - "parser": "html" - } - }, - { - "files": ["package.json"], - "options": { - "tabWidth": 2, - "parser": "json" - } - }, - { - "files": [".prettierrc"], - "options": { - "tabWidth": 2, - "parser": "json" - } - } - ] + "plugins": ["prettier-plugin-ini", "prettier-plugin-sh"], + "printWidth": 120, + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true, + "endOfLine": "lf", + "trailingComma": "none", + "overrides": [ + { + "files": ["*.js", "**/*.js", "*.jsx", "**/*.jsx"], + "options": { + "parser": "babel" + } + }, + { + "files": ["*.ts", "**/*.ts", "*.tsx", "**/*.tsx"], + "options": { + "parser": "typescript" + } + }, + { + "files": ["*.yml", "**/*.yml", "*.yaml", "**/*.yaml"], + "options": { + "parser": "yaml" + } + }, + { + "files": ["*.json", "**/*.json", ".czrc", "**/.czrc"], + "options": { + "parser": "json" + } + }, + { + "files": ["package.json"], + "options": { + "tabWidth": 2, + "parser": "json" + } + }, + { + "files": ["*.md", "**/*.md"], + "options": { + "parser": "markdown" + } + }, + { + "files": ["*.css", "**/*.css", "*.scss", "**/*.scss", "*.less", "**/*.less"], + "options": { + "parser": "css" + } + }, + { + "files": ["*.html", "**/*.html"], + "options": { + "parser": "html" + } + }, + { + "files": ["**/*.ini", ".editorconfig", ".npmrc"], + "options": { + "parser": "ini" + } + }, + { + "files": [".gitignore", ".npmignore", ".husky/*"], + "options": { + "parser": "sh" + } + } + ] } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index dfb1c04..5d1da3c 100755 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -3,8 +3,10 @@ "streetsidesoftware.code-spell-checker", "editorconfig.editorconfig", "dbaeumer.vscode-eslint", - "orta.vscode-jest", "christian-kohler.path-intellisense", - "SantaCodes.santacodes-region-viewer" + "SantaCodes.santacodes-region-viewer", + "foxundermoon.shell-format", + "github.vscode-github-actions", + "redhat.vscode-yaml" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index e09e0ac..f1e8015 100755 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,18 +1,16 @@ { + // CSpell + "cSpell.words": [], // Node and npm configuration "npm.autoDetect": "on", - "debug.node.autoAttach": "smart", + "debug.node.autoAttach": "never", "files.exclude": { "tsconfig.build.json": true }, // Code style, regular and ESLint related "files.trimTrailingWhitespace": true, - "editor.defaultFormatter": "esbenp.prettier-vscode", "eslint.validate": ["typescript"], "editor.codeActionsOnSave": { - "source.fixAll": true - }, - "editor.formatOnSave": false, - // CSpell - "cSpell.words": [] + "source.fixAll.eslint": "always" + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 4938ebc..f457923 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ -## 0.5.3 (2024-05-05) +## 0.5.5 (2024-09-05) +- build: bump gobstones-scripts version to 0.9.3 ([f9e850a](https://github.com/gobstones/gobstones-core/commit/f9e850a)) +## 0.5.5 (2024-09-05) +## 0.5.3 (2024-05-05) ## 0.5.2 (2024-05-05) diff --git a/commitlint.config.js b/commitlint.config.mjs similarity index 73% rename from commitlint.config.js rename to commitlint.config.mjs index a989bfc..115abd6 100644 --- a/commitlint.config.js +++ b/commitlint.config.mjs @@ -1,3 +1,3 @@ -module.exports = { +export default { extends: ['@commitlint/config-conventional'] }; diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..ace463d --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,251 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; + +import { includeIgnoreFile } from '@eslint/compat'; +import { FlatCompat } from '@eslint/eslintrc'; +import eslintJs from '@eslint/js'; +import eslintPluginNoNull from 'eslint-plugin-no-null'; +import eslintPluginPreferArrow from 'eslint-plugin-prefer-arrow'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import globals from 'globals'; +import eslintTs from 'typescript-eslint'; + +const baseUrl = path.resolve(path.dirname(fileURLToPath(import.meta.url))); + +const tsConfigPath = path.join(baseUrl, 'tsconfig.json'); +const gitignorePath = path.join(baseUrl, '.gitignore'); + +const compat = new FlatCompat({ + baseDirectory: baseUrl, + recommendedConfig: eslintJs.configs.recommended +}); + +const withFilesOnly = (config, files) => + config.map((e) => { + e.files = files; + return e; + }); +const _jsFiles = ['src/**/*.js', 'src/**/*.jsx', 'src/**/*.mjs', 'src/**/*.cjs']; +const _tsFiles = ['src/**/*.ts', 'src/**/*.tsx', 'src/**/*.mts', 'src/**/*.cts']; +const _codeFiles = [..._jsFiles, ..._tsFiles]; + +const config = eslintTs.config( + // Add all .gitignore files to the ignore path + includeIgnoreFile(gitignorePath), + // Asure oackage-scripts as CJS, to avoid no-undef issues + { files: ['**/package-scripts.js'], languageOptions: { globals: globals.node } }, + // Recommended settings from ESLint for JS + eslintJs.configs.recommended, + // Prettier plugin usage + eslintPluginPrettierRecommended, + // Import default settings. Import is not yet ESLint 9 compatible + ...withFilesOnly( + compat.extends( + 'plugin:import/recommended', + 'plugin:import/errors', + 'plugin:import/warnings', + 'plugin:import/typescript' + ), + _codeFiles + ), + // Custom settings and rules for all JS and TS files + { + files: _codeFiles, + linterOptions: { + reportUnusedDisableDirectives: true + }, + languageOptions: { + parser: eslintJs.parser, + parserOptions: eslintJs.parserOptions, + globals: { + ...globals.node + }, + ecmaVersion: 2022, + sourceType: 'module' + }, + plugins: { + 'no-null': eslintPluginNoNull, + 'prefer-arrow': eslintPluginPreferArrow + // import: legacyPlugin('eslint-plugin-import', 'import') + }, + settings: { + // 'import/ignore': ['i18next', 'fs', 'path'] + }, + rules: { + // Non plugin rules + 'no-console': ['error'], + 'arrow-body-style': ['error'], + 'no-shadow': ['off'], + camelcase: ['error'], + 'capitalized-comments': ['off'], + 'default-case': ['error'], + 'dot-location': ['error', 'property'], + eqeqeq: ['error', 'smart'], + 'no-alert': ['error'], + 'no-bitwise': ['error'], + 'no-caller': ['error'], + 'no-constructor-return': ['error'], + 'no-div-regex': ['error'], + 'no-empty': ['error'], + + 'no-empty-function': [ + 'error', + { + allow: ['constructors'] + } + ], + + 'no-eval': ['error'], + 'no-extra-bind': ['error'], + 'no-import-assign': ['error'], + 'no-invalid-this': ['error'], + 'no-labels': ['error'], + 'no-multiple-empty-lines': ['error'], + 'no-new-wrappers': ['error'], + 'no-regex-spaces': ['error'], + 'no-return-assign': ['error'], + 'no-return-await': ['error'], + 'no-self-compare': ['error'], + 'no-throw-literal': ['error'], + 'no-undef-init': ['error'], + 'no-underscore-dangle': ['off'], + 'no-unused-expressions': ['error'], + 'no-useless-call': ['error'], + 'no-useless-concat': ['error'], + 'no-var': ['error'], + 'object-shorthand': ['error'], + 'one-var': ['error', 'never'], + 'prefer-const': ['error'], + 'prefer-regex-literals': ['error'], + radix: ['error'], + 'require-await': ['error'], + + 'sort-imports': [ + 'error', + { + ignoreCase: false, + ignoreDeclarationSort: true, + ignoreMemberSort: false, + memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'], + allowSeparatedGroups: true + } + ], + + 'spaced-comment': ['error'], + 'use-isnan': ['error'], + 'valid-typeof': ['error'], + yoda: ['error'], + + // No Null plugin + 'no-null/no-null': ['error'], + + // Prefer arrow Plugin + 'prefer-arrow/prefer-arrow-functions': [ + 'error', + { + disallowPrototype: true, + singleReturnOnly: false, + classPropertiesAllowed: false + } + ], + + // Import is still not v9 compatible, some some rules fail. + // Instead of using the default provided configurations, + // we manually configure the rules, to avoid rules that have problems. + + // Helpful warnings + 'import/no-empty-named-blocks': ['error'], + 'import/no-extraneous-dependencies': ['error'], + 'import/no-mutable-exports': ['off'], + 'import/no-named-as-default': ['off'], + 'import/no-named-as-default-member': ['off'], + // Module systems + 'import/no-import-module-exports': ['error'], + // Static Analysis + 'import/default': ['off'], + 'import/namespace': ['off'], + 'import/no-absolute-path': ['error'], + 'import/no-dynamic-require': ['error'], + 'import/no-self-import': ['error'], + 'import/no-unresolved': ['off'], + 'import/no-useless-path-segments': ['error'], + 'import/no-webpack-loader-syntax': ['error'], + // Style guide + 'import/no-duplicates': ['error'], + 'import/order': [ + 'error', + { + groups: ['builtin', 'external', 'internal', 'sibling', 'parent', 'index', 'unknown'], + 'newlines-between': 'always', + + alphabetize: { + order: 'asc', + caseInsensitive: true + } + } + ] + } + }, + // This rules applies only for TS files, recommended typescript-eslint settings. + ...withFilesOnly(eslintTs.configs.strict, _tsFiles), + ...withFilesOnly(eslintTs.configs.stylistic, _tsFiles), + ...withFilesOnly(eslintTs.configs.strictTypeChecked, _tsFiles), + ...withFilesOnly(eslintTs.configs.stylisticTypeChecked, _tsFiles), + // Custom typescript-eslint configuration + { + files: _tsFiles, + languageOptions: { + parser: eslintTs.parser, + parserOptions: { + project: [tsConfigPath], + impliedStrict: true, + warnOnUnsupportedTypeScriptVersion: false + } + }, + rules: { + '@typescript-eslint/prefer-nullish-coalescing': ['off'], + '@typescript-eslint/no-unnecessary-type-parameters': ['off'], + '@typescript-eslint/no-unnecessary-condition': ['off'], + '@typescript-eslint/no-inferrable-types': ['off'], + '@typescript-eslint/no-namespace': ['off'], + '@typescript-eslint/prefer-regexp-exec': ['off'], + '@typescript-eslint/no-shadow': ['error'], + '@typescript-eslint/restrict-template-expressions': [ + 'error', + { + allowNumber: true + } + ], + '@typescript-eslint/explicit-member-accessibility': [ + 'error', + { + accessibility: 'explicit' + } + ], + '@typescript-eslint/member-ordering': [ + 'error', + { + default: ['signature', 'field', 'constructor', ['get', 'set'], 'method'] + } + ], + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_' + } + ], + '@typescript-eslint/explicit-function-return-type': [ + 'error', + { + allowExpressions: true, + allowTypedFunctionExpressions: true, + allowHigherOrderFunctions: true + } + ] + } + } +); + +export default config; diff --git a/package.json b/package.json index df4eb60..3fba094 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gobstones/gobstones-core", - "version": "0.5.4", + "version": "0.5.5", "description": "A set of utility types, interfaces and classes that are used through all the Gobstones Platform repositories.", "repository": "https://github.com/gobstones/gobstones-core", "homepage": "https://gobstones.github.io/gobstones-core", @@ -38,9 +38,9 @@ ".": ["./dist/esm/typings/index.d.ts"] } }, - "packageManager": "npm@9.2.0", + "packageManager": "npm@10.8.2", "scripts": { - "prepare": "is-ci || husky install", + "prepare": "is-ci || husky", "prepack": "npm start build", "start": "gobstones-scripts run", "gbs": "gobstones-scripts" @@ -52,10 +52,10 @@ } }, "dependencies": { - "commander": "^11.1.0" + "commander": "^12.1.0" }, "devDependencies": { - "@gobstones/gobstones-scripts": "^0.8.7", - "husky": "^9.0.11" + "@gobstones/gobstones-scripts": "^0.9.3", + "husky": "^9.1.4" } } diff --git a/rollup.config.js b/rollup.config.mjs similarity index 77% rename from rollup.config.js rename to rollup.config.mjs index e9c2685..cfcfd83 100644 --- a/rollup.config.js +++ b/rollup.config.mjs @@ -1,17 +1,16 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -const fs = require('fs'); +import { readFileSync } from 'fs'; -const { config } = require('@gobstones/gobstones-scripts'); -const commonjs = require('@rollup/plugin-commonjs'); -const nodeResolve = require('@rollup/plugin-node-resolve'); -const typescript = require('@rollup/plugin-typescript'); +import { config } from '@gobstones/gobstones-scripts'; +import commonjs from '@rollup/plugin-commonjs'; +import nodeResolve from '@rollup/plugin-node-resolve'; +import typescript from '@rollup/plugin-typescript'; -const packageJson = JSON.parse(fs.readFileSync('./package.json').toString()); +const packageJson = JSON.parse(readFileSync('./package.json').toString()); config.init(); const tsConfigPath = config.projectType.tsConfigJSON.toolingFile; -module.exports = [ +export default [ { input: 'src/index.ts', output: [ diff --git a/src/Events/EventEmitter.ts b/src/Events/EventEmitter.ts index 4725464..131428f 100644 --- a/src/Events/EventEmitter.ts +++ b/src/Events/EventEmitter.ts @@ -10,21 +10,22 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Events + * @module Events * @author Alan Rodas Bonjour */ -/* eslint-disable @typescript-eslint/consistent-type-definitions */ + +import { asDefined } from '../Functions'; /** * This type describes the basic mapping of available events to the * corresponding subscriber functions that may subscribe to such event. * Multiple events could be added, with different signatures for each * subscriber. - * - * @group Internal: Types */ export type EventSignature = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any [E in keyof L]: (...args: any[]) => any; }; @@ -33,12 +34,9 @@ export type EventSignature = { * event is key is a string (the most common case), and the subscriber * functions are any function. This is the default behavior of most * JavaScript event emitter, and the DOM event's signature. - * - * @group Internal: Types */ -export type DefaultEventSignature = { - [k: string]: (...args: any[]) => any; -}; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type DefaultEventSignature = Record any>; /** * The EventEmitter class is a minimal observer pattern class definition. @@ -49,15 +47,13 @@ export type DefaultEventSignature = { * The EventEmitter accepts as a type as a {@link EventSignature}, that is, * a set of event names together with the expected parameters that the observer * will be called with when the event occurs. - * - * @group API: Main */ export class EventEmitter = DefaultEventSignature> { /** A map of event to observers for the full time observers. */ - private eventObservers: Map> = new Map(); + private eventObservers = new Map>(); /** A map of event to observers for the one time observers. */ - private eventOnceObservers: Map> = new Map(); + private eventOnceObservers = new Map>(); /** * Register a observer to a particular event of this event emitter, as a @@ -68,8 +64,8 @@ export class EventEmitter = DefaultEventSignature> { * as a one time observer. If it's a full time observer, it will be transformed * to a one time observer. * - * @param event The event that the observer will be registered to. - * @param observer The observer that will be called when the event occurs. + * @param event - The event that the observer will be registered to. + * @param observer - The observer that will be called when the event occurs. * * @returns The event emitter. */ @@ -88,8 +84,8 @@ export class EventEmitter = DefaultEventSignature> { * as a full time observer. If it's a one time observer, it will be transformed * to a full time observer. * - * @param event The event that the observer will be registered to. - * @param observer The observer that will be called when the event occurs. + * @param event - The event that the observer will be registered to. + * @param observer - The observer that will be called when the event occurs. * * @returns The event emitter. */ @@ -109,8 +105,8 @@ export class EventEmitter = DefaultEventSignature> { * Note that the observer is removed only to the specified event, and such * it may still be registered for other events. * - * @param event The event that the observer will be removed from. - * @param observer The observer that will be removed when the event occurs. + * @param event - The event that the observer will be removed from. + * @param observer - The observer that will be removed when the event occurs. * * @returns The event emitter. */ @@ -128,7 +124,7 @@ export class EventEmitter = DefaultEventSignature> { * Event if all observers are removed, an observer may subscribe again to * the event afterwards. * - * @param event The event that the observers will be removed from. + * @param event - The event that the observers will be removed from. * * @returns The event emitter. */ @@ -146,7 +142,7 @@ export class EventEmitter = DefaultEventSignature> { * After the emission, one time subscribers are removed as subscribers to the * event, as they should not be called again. * - * @param event The event to emit. + * @param event - The event to emit. */ public emit(event: U, ...args: Parameters): void { for (const observer of this.getSetOrEmpty(event, this.eventObservers)) { @@ -164,13 +160,13 @@ export class EventEmitter = DefaultEventSignature> { * only the new observer. If the observer is already present for that * event in the given map, do nothing. * - * @param event The event to add the observer to. - * @param observer The observer to add. - * @param map The map to register the event and observer. + * @param event - The event to add the observer to. + * @param observer - The observer to add. + * @param map - The map to register the event and observer. */ private appendToMap(event: U, observer: L[U], map: Map>): void { if (map.has(event)) { - const set = map.get(event) as Set; + const set = asDefined(map.get(event)); set.add(observer); } else { const set = new Set(); @@ -184,9 +180,9 @@ export class EventEmitter = DefaultEventSignature> { * If the observer does not exists in the event for the map, or the event * is not present in the map, do nothing. * - * @param event The event to remove the observer from. - * @param observer The observer to be removed. - * @param map The map to unregister the event and observer. + * @param event - The event to remove the observer from. + * @param observer - The observer to be removed. + * @param map - The map to unregister the event and observer. */ private removeOfMap(event: U, observer: L[U], map: Map>): void { const set = this.getSetOrEmpty(event, map); @@ -197,8 +193,8 @@ export class EventEmitter = DefaultEventSignature> { * Remove all entries for an event in the given map. That is, remove the event * itself. * - * @param event The event to remove. - * @param map The map to remove the event from. + * @param event - The event to remove. + * @param map - The map to remove the event from. */ private deleteEntriesInMap(event: U, map: Map>): void { if (map.has(event)) { @@ -210,14 +206,14 @@ export class EventEmitter = DefaultEventSignature> { * Return a set for the given event. If the event exists in the given map, * return the set of that event. If not, return a new set. * - * @param event The event to obtain the set for. - * @param map The map from where to grab the set from. + * @param event - The event to obtain the set for. + * @param map - The map from where to grab the set from. * * @returns A set for the given event, a present one, or a new one. */ private getSetOrEmpty(event: U, map: Map>): Set { if (map.has(event)) { - return map.get(event) as Set; + return asDefined(map.get(event)); } return new Set(); } diff --git a/src/Events/index.ts b/src/Events/index.ts index fc95ab4..9758947 100644 --- a/src/Events/index.ts +++ b/src/Events/index.ts @@ -66,7 +66,8 @@ export function subscribeToEvent(): void { emitter.on('onStringChanged', subscriberToStringChanged); } * ``` - * @module API.Events + + * @module Events * @author Alan Rodas Bonjour */ export * from './EventEmitter'; diff --git a/src/Expectations/Classes/AbstractFinishedExpectation.ts b/src/Expectations/Implementation/AbstractFinishedExpectation.ts similarity index 86% rename from src/Expectations/Classes/AbstractFinishedExpectation.ts rename to src/Expectations/Implementation/AbstractFinishedExpectation.ts index a04cb8d..86379a5 100644 --- a/src/Expectations/Classes/AbstractFinishedExpectation.ts +++ b/src/Expectations/Implementation/AbstractFinishedExpectation.ts @@ -10,10 +10,13 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Expectations + * @module Expectations/Implementation * @author Alan Rodas Bonjour */ + +import { noop } from '../../Functions'; import { FinishedExpectation } from '../Interfaces'; /** @@ -50,13 +53,11 @@ export abstract class AbstractFinishedExpectation implements FinishedExpectation /** @inheritDoc {@link FinishedExpectation.andDo} */ public andDo(action: () => void): void { - // eslint-disable-next-line @typescript-eslint/no-empty-function, no-empty-function - this.andDoOr(action, () => {}); + this.andDoOr(action, noop); } /** @inheritDoc {@link FinishedExpectation.orDo} */ public orDo(action: () => void): void { - // eslint-disable-next-line @typescript-eslint/no-empty-function, no-empty-function - this.andDoOr(() => {}, action); + this.andDoOr(noop, action); } } diff --git a/src/Expectations/Classes/FullExpectation.ts b/src/Expectations/Implementation/FullExpectation.ts similarity index 90% rename from src/Expectations/Classes/FullExpectation.ts rename to src/Expectations/Implementation/FullExpectation.ts index 344bc35..69fe6b2 100644 --- a/src/Expectations/Classes/FullExpectation.ts +++ b/src/Expectations/Implementation/FullExpectation.ts @@ -10,22 +10,24 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Expectations + * @module Expectations/Implementation * @author Alan Rodas Bonjour */ + import { AbstractFinishedExpectation } from './AbstractFinishedExpectation'; import { ArrayExpectation, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + Expectation, FinishedExpectation, NumberExpectation, ObjectExpectation, - StringExpectation, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - Expectation + StringExpectation } from '../Interfaces'; -import { MatcherCall, Matchers } from '../Matchers'; +import * as Matchers from '../Matchers'; /** * The expectation class is the class that is actually instantiated for @@ -34,14 +36,14 @@ import { MatcherCall, Matchers } from '../Matchers'; * * @group Internal: Types */ -export class FullExpectation extends AbstractFinishedExpectation { +export class FullExpectation extends AbstractFinishedExpectation { // ----------------------------------------------- // #region Internal: Properties // ----------------------------------------------- /** * The querying element of this expectation. */ - protected element: any; + protected element: unknown; /** * The current result of this expectation. Undefined until * the first matcher is run. @@ -55,7 +57,7 @@ export class FullExpectation extends AbstractFinishedExpectation { /** * An array of the matchers run in this expectation. */ - protected states: MatcherCall[]; + protected states: Matchers.MatcherCall[]; // ----------------------------------------------- // #endregion Internal: Properties // ----------------------------------------------- @@ -66,9 +68,9 @@ export class FullExpectation extends AbstractFinishedExpectation { /** * Create a new expectation for the given element. * - * @param element The element to query to. + * @param element - The element to query to. */ - public constructor(element: any) { + public constructor(element: unknown) { super(); this.states = []; this.element = element; @@ -83,18 +85,18 @@ export class FullExpectation extends AbstractFinishedExpectation { // #region IGenericExpectation implementors // ----------------------------------------------- /** @inheritDoc {@link Expectation.not} */ - public get not(): FullExpectation { + public get not(): this { this.isNot = !this.isNot; return this; } /** @inheritDoc {@link Expectation.toBe} */ - public toBe(value: any): this & FinishedExpectation { + public toBe(value: unknown): this & FinishedExpectation { return this.runMatcher('toBe', [value]); } /** @inheritDoc {@link Expectation.toBeLike} */ - public toBeLike(value: any): this & FinishedExpectation { + public toBeLike(value: unknown): this & FinishedExpectation { return this.runMatcher('toBeLike', [value]); } @@ -210,7 +212,7 @@ export class FullExpectation extends AbstractFinishedExpectation { } /** @inheritDoc {@link NumberExpectation.toBeCloseTo} */ - public toBeCloseTo(value: number, digits: number = 5): this & FinishedExpectation { + public toBeCloseTo(value: number, digits = 5): this & FinishedExpectation { return this.runMatcher('toBeCloseTo', [value, digits]); } // ----------------------------------------------- @@ -250,28 +252,34 @@ export class FullExpectation extends AbstractFinishedExpectation { public toBeEmptyArray(count: number): this & FinishedExpectation { return this.runMatcher('toBeEmptyArray', [count]); } + /** @inheritDoc {@link ArrayExpectation.toHaveLength} */ public toHaveLength(count: number): this & FinishedExpectation { return this.runMatcher('toHaveLength', [count]); } + /** @inheritDoc {@link ArrayExpectation.toContain} */ - public toContain(value: T): this & FinishedExpectation { + public toContain(value: unknown): this & FinishedExpectation { return this.runMatcher('toContain', [value]); } + /** @inheritDoc {@link ArrayExpectation.toHaveAtPosition} */ - public toHaveAtPosition(value: T, position: number): this & FinishedExpectation { + public toHaveAtPosition(value: unknown, position: number): this & FinishedExpectation { return this.runMatcher('toHaveAtPosition', [value, position]); } + /** @inheritDoc {@link ArrayExpectation.allToSatisfy} */ - public allToSatisfy(criteria: (item: T) => boolean): this & FinishedExpectation { + public allToSatisfy(criteria: (item: unknown) => boolean): this & FinishedExpectation { return this.runMatcher('allToSatisfy', [criteria]); } + /** @inheritDoc {@link ArrayExpectation.anyToSatisfy} */ - public anyToSatisfy(criteria: (item: T) => boolean): this & FinishedExpectation { + public anyToSatisfy(criteria: (item: unknown) => boolean): this & FinishedExpectation { return this.runMatcher('anyToSatisfy', [criteria]); } + /** @inheritDoc {@link ArrayExpectation.amountToSatisfy} */ - public amountToSatisfy(count: number, criteria: (item: T) => boolean): this & FinishedExpectation { + public amountToSatisfy(count: number, criteria: (item: unknown) => boolean): this & FinishedExpectation { return this.runMatcher('amountToSatisfy', [count, criteria]); } // ----------------------------------------------- @@ -285,24 +293,29 @@ export class FullExpectation extends AbstractFinishedExpectation { public toBeEmptyObject(count: number): this & FinishedExpectation { return this.runMatcher('toBeEmptyObject', [count]); } + /** @inheritDoc {@link ObjectExpectation.toHavePropertyCount} */ public toHavePropertyCount(count: number): this & FinishedExpectation { return this.runMatcher('toHavePropertyCount', [count]); } + /** @inheritDoc {@link ObjectExpectation.toHaveAtLeast} */ public toHaveAtLeast(keys: string[]): this & FinishedExpectation { return this.runMatcher('toHaveAtLeast', keys, false); } + /** @inheritDoc {@link ObjectExpectation.toHaveNoOtherThan} */ public toHaveNoOtherThan(keys: string[]): this & FinishedExpectation { return this.runMatcher('toHaveNoOtherThan', keys, false); } + /** @inheritDoc {@link ObjectExpectation.toHaveProperty} */ public toHaveProperty(propertyName: string): this & FinishedExpectation { return this.runMatcher('toHaveProperty', [propertyName]); } + /** @inheritDoc {@link ObjectExpectation.toBeInstanceOf} */ - // eslint-disable-next-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type public toBeInstanceOf(classConstructor: Function): this & FinishedExpectation { return this.runMatcher('toBeInstanceOf', [classConstructor]); } @@ -347,13 +360,13 @@ export class FullExpectation extends AbstractFinishedExpectation { * given arguments. The result of running the matcher is stores, * and a new state is pushed to this particular matcher. * - * @param matcherName The matcher name to run - * @param args The arguments to pass to the matcher + * @param matcherName - The matcher name to run + * @param args - The arguments to pass to the matcher */ - protected runMatcher(matcherName: string, args: any[], sparse: boolean = true): this { + protected runMatcher(matcherName: string, args: unknown[], sparse = true): this { const matcherArgs = sparse ? [this.element, ...args] : [this.element, args]; - const matcherToCall = (Matchers as any)[matcherName] as any; - const matcherResult = matcherToCall.call(this, ...matcherArgs); + const matcherToCall = Matchers[matcherName] as (actual: unknown, ...args: unknown[]) => boolean; + const matcherResult = matcherToCall.call(this, ...matcherArgs) as boolean; const result = this.isNot ? !matcherResult : matcherResult; this.states.push({ matcher: matcherName, diff --git a/src/Expectations/Classes/JoinedExpectation.ts b/src/Expectations/Implementation/JoinedExpectation.ts similarity index 90% rename from src/Expectations/Classes/JoinedExpectation.ts rename to src/Expectations/Implementation/JoinedExpectation.ts index 71cb40d..d544ea6 100644 --- a/src/Expectations/Classes/JoinedExpectation.ts +++ b/src/Expectations/Implementation/JoinedExpectation.ts @@ -10,10 +10,12 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Expectations + * @module Expectations/Implementation * @author Alan Rodas Bonjour */ + import { AbstractFinishedExpectation } from './AbstractFinishedExpectation'; import { FinishedExpectation } from '../Interfaces'; @@ -38,8 +40,8 @@ export class JoinedExpectation extends AbstractFinishedExpectation { * Create a new instance of a JoinedExpectation for the given set * of expectations, using the provided joiner. * - * @param expectations The expectations that ought to be joined. - * @param joiner The joiner to use to calculate the result. + * @param expectations - The expectations that ought to be joined. + * @param joiner - The joiner to use to calculate the result. */ public constructor(expectations: FinishedExpectation[], joiner: (expectations: FinishedExpectation[]) => boolean) { super(); diff --git a/src/SourceReader/SourcePositions/SourceRange.ts b/src/Expectations/Implementation/index.ts similarity index 64% rename from src/SourceReader/SourcePositions/SourceRange.ts rename to src/Expectations/Implementation/index.ts index 6afc5f6..d6bc077 100644 --- a/src/SourceReader/SourcePositions/SourceRange.ts +++ b/src/Expectations/Implementation/index.ts @@ -10,16 +10,15 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ -/** - * @module API.Functions - * @author Pablo E. --Fidel-- Martínez López - */ /** - * Gives the string for a symbol, without the 'Symbol(...)' in it. - * @group Static operations + * This module contains classes that implements the expectations system + * interfaces and behavior. + * + * @module Expectations/Implementation + * @author Alan Rodas Bonjour */ -export const symbolAsString = (s: symbol): string => { - const str = s.toString(); - return str.slice(7, str.length - 1); -}; + +export * from './AbstractFinishedExpectation'; +export * from './FullExpectation'; +export * from './JoinedExpectation'; diff --git a/src/Expectations/Interfaces/ArrayExpectation.ts b/src/Expectations/Interfaces/ArrayExpectation.ts index b647ca4..aa951bc 100644 --- a/src/Expectations/Interfaces/ArrayExpectation.ts +++ b/src/Expectations/Interfaces/ArrayExpectation.ts @@ -10,8 +10,9 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Expectations + * @module Expectations/Interfaces * @author Alan Rodas Bonjour */ diff --git a/src/Expectations/Interfaces/BooleanExpectation.ts b/src/Expectations/Interfaces/BooleanExpectation.ts index 42d32f0..c234c2d 100644 --- a/src/Expectations/Interfaces/BooleanExpectation.ts +++ b/src/Expectations/Interfaces/BooleanExpectation.ts @@ -10,8 +10,9 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Expectations + * @module Expectations/Interfaces * @author Alan Rodas Bonjour */ diff --git a/src/Expectations/Interfaces/Expectation.ts b/src/Expectations/Interfaces/Expectation.ts index 49633b8..2846352 100644 --- a/src/Expectations/Interfaces/Expectation.ts +++ b/src/Expectations/Interfaces/Expectation.ts @@ -10,8 +10,9 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Expectations + * @module Expectations/Interfaces * @author Alan Rodas Bonjour */ @@ -40,13 +41,13 @@ export interface Expectation { /** * Answers if the actual value is the same as expected, using strict compare. * Do not use toBe with floating point numbers, use - * {@link Matchers.toBeCloseTo} instead. + * {@link Expectations/Matchers.toBeCloseTo} instead. */ toBe(value: T): this & FinishedExpectation; /** * Answers if the actual value is the same as expected, using a deep compare mechanism. * Do not use toBeLike with floating point numbers, use - * {@link Matchers.toBeCloseTo} instead. + * {@link Expectations/Matchers.toBeCloseTo} instead. */ toBeLike(value: T): this & FinishedExpectation; /** diff --git a/src/Expectations/Interfaces/FinishedExpectation.ts b/src/Expectations/Interfaces/FinishedExpectation.ts index 107b4ea..0c603f9 100644 --- a/src/Expectations/Interfaces/FinishedExpectation.ts +++ b/src/Expectations/Interfaces/FinishedExpectation.ts @@ -10,8 +10,9 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Expectations + * @module Expectations/Interfaces * @author Alan Rodas Bonjour */ /** diff --git a/src/Expectations/Interfaces/NumberExpectation.ts b/src/Expectations/Interfaces/NumberExpectation.ts index 817da06..d8225ea 100644 --- a/src/Expectations/Interfaces/NumberExpectation.ts +++ b/src/Expectations/Interfaces/NumberExpectation.ts @@ -10,8 +10,9 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Expectations + * @module Expectations/Interfaces * @author Alan Rodas Bonjour */ diff --git a/src/Expectations/Interfaces/ObjectExpectation.ts b/src/Expectations/Interfaces/ObjectExpectation.ts index 2e7d5c2..d915834 100644 --- a/src/Expectations/Interfaces/ObjectExpectation.ts +++ b/src/Expectations/Interfaces/ObjectExpectation.ts @@ -10,8 +10,9 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Expectations + * @module Expectations/Interfaces * @author Alan Rodas Bonjour */ @@ -49,6 +50,5 @@ export interface ObjectExpectation extends Expectation { /** * Answer if the actual element is an instance of a given class (using instanceof). */ - // eslint-disable-next-line @typescript-eslint/ban-types - toBeInstanceOf(classConstructor: Function): this & FinishedExpectation; + toBeInstanceOf(classConstructor: (...arguments_: readonly unknown[]) => unknown): this & FinishedExpectation; } diff --git a/src/Expectations/Interfaces/StringExpectation.ts b/src/Expectations/Interfaces/StringExpectation.ts index 503128b..3b4cb5e 100644 --- a/src/Expectations/Interfaces/StringExpectation.ts +++ b/src/Expectations/Interfaces/StringExpectation.ts @@ -10,8 +10,9 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Expectations + * @module Expectations/Interfaces * @author Alan Rodas Bonjour */ diff --git a/src/Expectations/Interfaces/index.ts b/src/Expectations/Interfaces/index.ts index c81fbf1..6e13ca5 100644 --- a/src/Expectations/Interfaces/index.ts +++ b/src/Expectations/Interfaces/index.ts @@ -10,10 +10,15 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Expectations + * This module contains interfaces that provide the basic messages that an + * expectation can understand, depending on it's type. + * + * @module Expectations/Interfaces * @author Alan Rodas Bonjour */ + export * from './Expectation'; export * from './FinishedExpectation'; export * from './BooleanExpectation'; diff --git a/src/Expectations/Matchers.ts b/src/Expectations/Matchers.ts index c50f2db..e4f39ec 100644 --- a/src/Expectations/Matchers.ts +++ b/src/Expectations/Matchers.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-null/no-null */ /* * ***************************************************************************** * Copyright (C) National University of Quilmes 2018-2024 @@ -10,11 +11,22 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Expectations + * This module contains a series of matchers, that is, a series of functions + * that can be called with the actual value (and in cases a series of arguments) + * and returns a boolean, `true` if the value satisfies the matcher, and `false` + * otherwise. + * + * @remarks + * Having the matchers separated from the instances that use the matchers allow for + * greater extensibility. + * + * @module Expectations/Matchers * @author Alan Rodas Bonjour */ -import { deepEquals } from '../Functions/deepEquals'; +import { deepEquals } from '../Functions/Querying/deepEquals'; +import { isBuffer } from '../Functions/Querying/isBuffer'; // =============================================== // #region MatcherCall @@ -23,11 +35,10 @@ import { deepEquals } from '../Functions/deepEquals'; * A matcher call represents a call to a matcher with it's corresponding * arguments and the actual result. * - * @group Internal: Types */ export interface MatcherCall { matcher: string; - args: any[]; + args: unknown[]; result: boolean; } // =============================================== @@ -37,261 +48,192 @@ export interface MatcherCall { // =============================================== // #region Matchers // =============================================== + +// ----------------------------------------------- +// #region IGenericExpectation implementors +// ----------------------------------------------- +/** Answers if the actual value is the same as expected, using strict compare */ +export const toBe = (actual: unknown, expected: unknown): boolean => actual === expected; +/** Answers if the actual value is the same as expected, using a deep compare mechanism */ +export const toBeLike = (actual: unknown, expected: unknown): boolean => deepEquals(actual, expected); +/** Answers if the actual value is defined (as in not equal to undefined) */ +export const toBeDefined = (actual: unknown): boolean => actual !== undefined; +/** Answers if the actual value is undefined */ +export const toBeUndefined = (actual: unknown): boolean => actual === undefined; +/** Answers if the actual value is null (strict null, not undefined) */ + +export const toBeNull = (actual: unknown): boolean => actual === null; +/** Answers if the actual value is a truthy value */ +export const toBeTruthy = (actual: unknown): boolean => !!actual; +/** Answers if the actual value is a falsy value */ +export const toBeFalsy = (actual: unknown): boolean => !actual; /** - * This object contains a series of matchers, that is, a series of functions - * that can be called with the actual value (and in cases a series of arguments) - * and returns a boolean, `true` if the value satisfies the matcher, and `false` - * otherwise. - * - * Having the matchers separated from the instances that use the matchers allow for - * greater extensibility. - * - * @group Internal: Types + * Answers if the actual value has a type matching the expected type. + * This comparison is performed using the `typeof` operation over the value, + * with additional logic added to support 'array' as a type. + * @example `toHaveType([1,2,3], 'array')` returns `true` as expected. */ -export class Matchers { - // ----------------------------------------------- - // #region IGenericExpectation implementors - // ----------------------------------------------- - /** Answers if the actual value is the same as expected, using strict compare */ - public static toBe(actual: any, expected: any): boolean { - return actual === expected; - } - /** Answers if the actual value is the same as expected, using a deep compare mechanism */ - public static toBeLike(actual: any, expected: any): boolean { - return deepEquals(actual, expected); - } - /** Answers if the actual value is defined (as in not equal to undefined) */ - public static toBeDefined(actual: any): boolean { - return actual !== undefined; - } - /** Answers if the actual value is undefined */ - public static toBeUndefined(actual: any): boolean { - return actual === undefined; - } - /** Answers if the actual value is null (strict null, not undefined) */ - public static toBeNull(actual: any): boolean { - // eslint-disable-next-line no-null/no-null - return actual === null; - } - /** Answers if the actual value is a truthy value */ - public static toBeTruthy(actual: any): boolean { - return !!actual; - } - /** Answers if the actual value is a falsy value */ - public static toBeFalsy(actual: any): boolean { - return !actual; - } - /** - * Answers if the actual value has a type matching the expected type. - * This comparison is performed using the `typeof` operation over the value, - * with additional logic added to support 'array' as a type. - * @example `toHaveType([1,2,3], 'array')` returns `true` as expected. - */ - public static toHaveType(actual: any, expectedType: string): boolean { - return ( - (expectedType !== 'object' && typeof actual === expectedType) || - (expectedType === 'array' && typeof actual === 'object' && Array.isArray(actual)) || - (expectedType === 'regexp' && typeof actual === 'object' && actual instanceof RegExp) || - (expectedType === 'buffer' && - actual && - actual.constructor && - typeof actual.constructor.isBuffer === 'function' && - actual.constructor.isBuffer(actual)) || - (expectedType === 'object' && !Array.isArray(actual) && typeof actual === expectedType) - ); - } +export const toHaveType = (actual: unknown, expectedType: string): boolean => + (expectedType !== 'object' && typeof actual === expectedType) || + (expectedType === 'array' && typeof actual === 'object' && Array.isArray(actual)) || + (expectedType === 'regexp' && typeof actual === 'object' && actual instanceof RegExp) || + (expectedType === 'buffer' && isBuffer(actual)) || + (expectedType === 'object' && !Array.isArray(actual) && typeof actual === expectedType); - // ----------------------------------------------- - // #endregion IGenericExpectation implementors - // ----------------------------------------------- +// ----------------------------------------------- +// #endregion IGenericExpectation implementors +// ----------------------------------------------- - // ----------------------------------------------- - // #region IBooleanExpectation implementors - // ----------------------------------------------- - /** Answers if the actual value is true */ - public static toBeTrue(actual: any): boolean { - return actual === true; - } - /** Answers if the actual value is false */ - public static toBeFalse(actual: any): boolean { - return actual === false; - } - // ----------------------------------------------- - // #endregion IBooleanExpectation implementors - // ----------------------------------------------- +// ----------------------------------------------- +// #region IBooleanExpectation implementors +// ----------------------------------------------- +/** Answers if the actual value is true */ +export const toBeTrue = (actual: unknown): boolean => actual === true; +/** Answers if the actual value is false */ +export const toBeFalse = (actual: unknown): boolean => actual === false; +// ----------------------------------------------- +// #endregion IBooleanExpectation implementors +// ----------------------------------------------- - // ----------------------------------------------- - // #region INumberExpectation implementors - // ----------------------------------------------- - /** Answer if the actual value is greater than the expected value. */ - public static toBeGreaterThan(actual: number, expected: number): boolean { - return typeof actual === 'number' && actual > expected; - } - /** Answer if the actual value is greater than or equal than the expected value. */ - public static toBeGreaterThanOrEqual(actual: number, expected: number): boolean { - return typeof actual === 'number' && actual >= expected; - } - /** Answer if the actual value is lower than the expected value. */ - public static toBeLowerThan(actual: number, expected: any): boolean { - return typeof actual === 'number' && actual < expected; - } - /** Answer if the actual value is lower than or equal than the expected value. */ - public static toBeLowerThanOrEqual(actual: number, expected: number): boolean { - return typeof actual === 'number' && actual <= expected; - } - /** Answer if the actual value is between the from and to values (inclusive). */ - public static toBeBetween(actual: number, from: number, to: number): boolean { - return typeof actual === 'number' && from <= actual && actual <= to; - } - /** Answer if the actual value is infinity (positive or negative). */ - public static toBeInfinity(actual: number): boolean { - return typeof actual === 'number' && (actual === Infinity || actual === -Infinity); - } - /** Answer if the actual value is not a number. */ - public static toBeNaN(actual: number): boolean { - return typeof actual === 'number' && Number.isNaN(actual); - } - /** - * Answer if the actual value is close to the expected value, by at least the number - * of digits given. - * @example `toBeCloseTo(4.0005, 4.0009, 3)` returns `true`, as there are 3 - * digits that are equal between actual and expected. - * If no amount of digits is given, 5 is taken by default. - */ - public static toBeCloseTo(actual: number, expected: number, numDigits: number): boolean { - return typeof actual === 'number' && Math.abs(expected - actual) < Math.pow(10, -numDigits) / 10; - } - // ----------------------------------------------- - // #endregion INumberExpectation implementors - // ----------------------------------------------- +// ----------------------------------------------- +// #region INumberExpectation implementors +// ----------------------------------------------- +/** Answer if the actual value is greater than the expected value. */ +export const toBeGreaterThan = (actual: number, expected: number): boolean => + typeof actual === 'number' && actual > expected; +/** Answer if the actual value is greater than or equal than the expected value. */ +export const toBeGreaterThanOrEqual = (actual: number, expected: number): boolean => + typeof actual === 'number' && actual >= expected; +/** Answer if the actual value is lower than the expected value. */ +export const toBeLowerThan = (actual: number, expected: number): boolean => + typeof actual === 'number' && actual < expected; +/** Answer if the actual value is lower than or equal than the expected value. */ +export const toBeLowerThanOrEqual = (actual: number, expected: number): boolean => + typeof actual === 'number' && actual <= expected; +/** Answer if the actual value is between the from and to values (inclusive). */ +export const toBeBetween = (actual: number, from: number, to: number): boolean => + typeof actual === 'number' && from <= actual && actual <= to; +/** Answer if the actual value is infinity (positive or negative). */ +export const toBeInfinity = (actual: number): boolean => + typeof actual === 'number' && (actual === Infinity || actual === -Infinity); +/** Answer if the actual value is not a number. */ +export const toBeNaN = (actual: number): boolean => typeof actual === 'number' && Number.isNaN(actual); +/** + * Answer if the actual value is close to the expected value, by at least the number + * of digits given. + * @example `toBeCloseTo(4.0005, 4.0009, 3)` returns `true`, as there are 3 + * digits that are equal between actual and expected. + * If no amount of digits is given, 5 is taken by default. + */ +export const toBeCloseTo = (actual: number, expected: number, numDigits: number): boolean => + typeof actual === 'number' && Math.abs(expected - actual) < Math.pow(10, -numDigits) / 10; +// ----------------------------------------------- +// #endregion INumberExpectation implementors +// ----------------------------------------------- - // ----------------------------------------------- - // #region IStringExpectation implementors - // ----------------------------------------------- - /** Answer if the actual value has expected as a substring. */ - public static toHaveSubstring(actual: string, expected: any): boolean { - return typeof actual === 'string' && actual.indexOf(expected) >= 0; - } - /** Answer if the actual value starts with the expected string. */ - public static toStartWith(actual: string, expected: any): boolean { - return typeof actual === 'string' && actual.startsWith(expected); - } - /** Answer if the actual value ends with the expected string. */ - public static toEndWith(actual: string, expected: any): boolean { - return typeof actual === 'string' && actual.endsWith(expected); - } - /** Answer if the actual value matches the given regexp. */ - public static toMatch(actual: string, expected: RegExp): boolean { - return typeof actual === 'string' && expected.test(actual); - } - // ----------------------------------------------- - // #endregion IStringExpectation implementors - // ----------------------------------------------- +// ----------------------------------------------- +// #region IStringExpectation implementors +// ----------------------------------------------- +/** Answer if the actual value has expected as a substring. */ +export const toHaveSubstring = (actual: string, expected: string): boolean => + typeof actual === 'string' && actual.includes(expected); +/** Answer if the actual value starts with the expected string. */ +export const toStartWith = (actual: string, expected: string): boolean => + typeof actual === 'string' && actual.startsWith(expected); +/** Answer if the actual value ends with the expected string. */ +export const toEndWith = (actual: string, expected: string): boolean => + typeof actual === 'string' && actual.endsWith(expected); +/** Answer if the actual value matches the given regexp. */ +export const toMatch = (actual: string, expected: RegExp): boolean => + typeof actual === 'string' && expected.test(actual); +// ----------------------------------------------- +// #endregion IStringExpectation implementors +// ----------------------------------------------- - // ----------------------------------------------- - // #region IArrayExpectation implementors - // ----------------------------------------------- - public static toBeEmptyArray(actual: any): boolean { - return typeof actual === 'object' && actual instanceof Array && actual.length === 0; - } - /** Answer if the actual value has a length of expected number. */ - public static toHaveLength(actual: any[], expected: number): boolean { - return typeof actual === 'object' && actual instanceof Array && actual.length === expected; - } - /** Answer if the actual value contains the expected element. */ - public static toContain(actual: any[], expected: any): boolean { - return typeof actual === 'object' && Array.isArray(actual) && actual.indexOf(expected) >= 0; - } - /** - * Answer if the actual value has a the expected element at a given position. - * Returns false if the position does not exist. - */ - public static toHaveAtPosition(actual: any[], expected: any, position: number): boolean { - return ( - typeof actual === 'object' && - Array.isArray(actual) && - actual.length > position && - position >= 0 && - actual[position] === expected - ); - } - /** Answer if all the element of the actual value satisfy a given criteria. */ - public static allToSatisfy(actual: any[], criteria: (elem: any) => boolean): boolean { - return ( - typeof actual === 'object' && - Array.isArray(actual) && - actual.reduce((r, a) => criteria(a) && r, true) - ); - } - /** Answer if any of the element of the actual value satisfy a given criteria. */ - public static anyToSatisfy(actual: any[], criteria: (elem: any) => boolean): boolean { - return ( - typeof actual === 'object' && - Array.isArray(actual) && - actual.reduce((r, a) => criteria(a) || r, false) - ); - } - /** Answer if a given amount of elements of the actual value satisfy a given criteria. */ - public static amountToSatisfy(actual: any[], amount: number, criteria: (elem: any) => boolean): boolean { - return ( - typeof actual === 'object' && - Array.isArray(actual) && - actual.reduce((r, a) => (criteria(a) ? r + 1 : r), 0) === amount - ); - } - // ----------------------------------------------- - // #endregion IArrayExpectation implementors - // ----------------------------------------------- +// ----------------------------------------------- +// #region IArrayExpectation implementors +// ----------------------------------------------- +export const toBeEmptyArray = (actual: unknown): boolean => + typeof actual === 'object' && actual instanceof Array && actual.length === 0; +/** Answer if the actual value has a length of expected number. */ +export const toHaveLength = (actual: unknown[], expected: number): boolean => + typeof actual === 'object' && actual instanceof Array && actual.length === expected; +/** Answer if the actual value contains the expected element. */ +export const toContain = (actual: unknown[], expected: unknown): boolean => + typeof actual === 'object' && Array.isArray(actual) && actual.includes(expected); +/** + * Answer if the actual value has a the expected element at a given position. + * Returns false if the position does not exist. + */ +export const toHaveAtPosition = (actual: unknown[], expected: unknown, position: number): boolean => + typeof actual === 'object' && + Array.isArray(actual) && + actual.length > position && + position >= 0 && + actual[position] === expected; +/** Answer if all the element of the actual value satisfy a given criteria. */ +export const allToSatisfy = (actual: unknown[], criteria: (elem: unknown) => boolean): boolean => + typeof actual === 'object' && Array.isArray(actual) && actual.reduce((r, a) => criteria(a) && r, true); +/** Answer if any of the element of the actual value satisfy a given criteria. */ +export const anyToSatisfy = (actual: unknown[], criteria: (elem: unknown) => boolean): boolean => + typeof actual === 'object' && Array.isArray(actual) && actual.reduce((r, a) => criteria(a) || r, false); +/** Answer if a given amount of elements of the actual value satisfy a given criteria. */ +export const amountToSatisfy = (actual: unknown[], amount: number, criteria: (elem: unknown) => boolean): boolean => + typeof actual === 'object' && + Array.isArray(actual) && + actual.reduce((r, a) => (criteria(a) ? r + 1 : r), 0) === amount; +// ----------------------------------------------- +// #endregion IArrayExpectation implementors +// ----------------------------------------------- - // ----------------------------------------------- - // #region IObjectExpectation implementors - // ----------------------------------------------- - /** Answer if the actual value is empty. */ - public static toBeEmptyObject(actual: any): boolean { - return ( - typeof actual === 'object' && - // eslint-disable-next-line no-null/no-null - actual !== null && - Object.keys(actual).filter((e) => Object.hasOwnProperty.call(actual, e)).length === 0 - ); - } - /** Answer if the actual element has the given amount of properties. */ - public static toHavePropertyCount(actual: any, amount: number): boolean { - return ( - typeof actual === 'object' && - Object.keys(actual).filter((e) => Object.hasOwnProperty.call(actual, e)).length === amount - ); - } - /** Answer if an object has at least all keys in the least. Combine with - * toHaveNoOtherThan to ensure exact key existence */ - public static toHaveAtLeast(actual: any, keys: string[]): boolean { - if (typeof actual !== 'object') return false; - for (const key of keys) { - if (!actual[key]) return false; - } - return true; - } - /** Answer if an object has no other than the given keys (although not all given - * need to be present). Combine with toHaveAtLeast to ensure exact key existence */ - public static toHaveNoOtherThan(actual: any, keys: string[]): boolean { - if (typeof actual !== 'object') return false; - for (const key of Object.keys(actual)) { - if (keys.indexOf(key) < 0) { - return false; - } +// ----------------------------------------------- +// #region IObjectExpectation implementors +// ----------------------------------------------- +/** Answer if the actual value is empty. */ +export const toBeEmptyObject = (actual: unknown): boolean => + typeof actual === 'object' && + actual !== null && + Object.keys(actual).filter((e) => Object.hasOwnProperty.call(actual, e)).length === 0; +/** Answer if the actual element has the given amount of properties. */ +export const toHavePropertyCount = (actual: unknown, amount: number): boolean => + typeof actual === 'object' && + actual !== null && + Object.keys(actual).filter((e) => Object.hasOwnProperty.call(actual, e)).length === amount; +/** Answer if an object has at least all keys in the least. Combine with + * toHaveNoOtherThan to ensure exact key existence */ +export const toHaveAtLeast = (actual: unknown, keys: string[]): boolean => { + if (typeof actual !== 'object') return false; + + if (actual === null) return false; + for (const key of keys) { + if (!actual[key]) return false; + } + return true; +}; +/** Answer if an object has no other than the given keys (although not all given + * need to be present). Combine with toHaveAtLeast to ensure exact key existence */ +export const toHaveNoOtherThan = (actual: unknown, keys: string[]): boolean => { + if (typeof actual !== 'object') return false; + + if (actual === null) return false; + for (const key of Object.keys(actual)) { + if (!keys.includes(key)) { + return false; } - return true; - } - /** Answer if the actual element has a property with the given name. */ - public static toHaveProperty(actual: any, propertyName: string): boolean { - return typeof actual === 'object' && Object.prototype.hasOwnProperty.call(actual, propertyName); } - /** Answer if the actual element is an instance of a given class (using instanceof). */ - // eslint-disable-next-line @typescript-eslint/ban-types - public static toBeInstanceOf(actual: any, classConstructor: Function): boolean { - return typeof actual === 'object' && actual instanceof classConstructor; - } -} + return true; +}; +/** Answer if the actual element has a property with the given name. */ +export const toHaveProperty = (actual: unknown, propertyName: string): boolean => + typeof actual === 'object' && + actual !== null && + (Object.prototype.hasOwnProperty.call(actual, propertyName) as boolean); + +/** Answer if the actual element is an instance of a given class (using instanceof). */ +export const toBeInstanceOf = ( + actual: unknown, + classConstructor: (...arguments_: readonly unknown[]) => unknown +): boolean => typeof actual === 'object' && actual instanceof classConstructor; // =============================================== // #endregion Matchers // =============================================== diff --git a/src/Expectations/expect.ts b/src/Expectations/expect.ts index f7e9e86..63d9ce5 100644 --- a/src/Expectations/expect.ts +++ b/src/Expectations/expect.ts @@ -11,11 +11,10 @@ * ***************************************************************************** */ /** - * @module API.Expectations + * @module Expectations * @author Alan Rodas Bonjour */ -import { FullExpectation } from './Classes/FullExpectation'; -import { JoinedExpectation } from './Classes/JoinedExpectation'; +import { FullExpectation, JoinedExpectation } from './Implementation'; import { ArrayExpectation, BooleanExpectation, @@ -30,54 +29,53 @@ import { * depends on the type of the element passed, and thus, different matchers may be * executed over the expectation. * The generic matchers can be called over any expectation for any element, this include: - * * {@link Matchers.toBe | toBe} - * * {@link Matchers.toBeLike | toBeLike} - * * {@link Matchers.toBeDefined | toBeDefined} - * * {@link Matchers.toBeUndefined | toBeUndefined} - * * {@link Matchers.toBeNull | toBeNull} - * * {@link Matchers.toBeTruthy | toBeTruthy} - * * {@link Matchers.toBeFalsy | toBeFalsy} - * * {@link Matchers.toHaveType | toHaveType} + * * {@link Expectations/Matchers.toBe | toBe} + * * {@link Expectations/Matchers.toBe | toBe} + * * {@link Expectations/Matchers.toBeLike | toBeLike} + * * {@link Expectations/Matchers.toBeDefined | toBeDefined} + * * {@link Expectations/Matchers.toBeUndefined | toBeUndefined} + * * {@link Expectations/Matchers.toBeNull | toBeNull} + * * {@link Expectations/Matchers.toBeTruthy | toBeTruthy} + * * {@link Expectations/Matchers.toBeFalsy | toBeFalsy} + * * {@link Expectations/Matchers.toHaveType | toHaveType} * * If the given element is a number, then an {@link NumberExpectation} is returned, and so * the following additional matchers are added to the previous list: - * * {@link Matchers.toBeGreaterThan | toBeGreaterThan} - * * {@link Matchers.toBeGreaterThanOrEqual | toBeGreaterThanOrEqual} - * * {@link Matchers.toBeLowerThan | toBeLowerThan} - * * {@link Matchers.toBeLowerThanOrEqual | toBeLowerThanOrEqual} - * * {@link Matchers.toBeBetween | toBeBetween} - * * {@link Matchers.toBeInfinity | toBeInfinity} - * * {@link Matchers.toBeNaN | toBeNaN} - * * {@link Matchers.toBeCloseTo | toBeCloseTo} + * * {@link Expectations/Matchers.toBeGreaterThan | toBeGreaterThan} + * * {@link Expectations/Matchers.toBeGreaterThanOrEqual | toBeGreaterThanOrEqual} + * * {@link Expectations/Matchers.toBeLowerThan | toBeLowerThan} + * * {@link Expectations/Matchers.toBeLowerThanOrEqual | toBeLowerThanOrEqual} + * * {@link Expectations/Matchers.toBeBetween | toBeBetween} + * * {@link Expectations/Matchers.toBeInfinity | toBeInfinity} + * * {@link Expectations/Matchers.toBeNaN | toBeNaN} + * * {@link Expectations/Matchers.toBeCloseTo | toBeCloseTo} * * If a string is used instead, the list is expanded with the following matchers: - * * {@link Matchers.toHaveSubstring | toHaveSubstring} - * * {@link Matchers.toStartWith | toStartWith} - * * {@link Matchers.toEndWith | toEndWith} - * * {@link Matchers.toMatch | toMatch} + * * {@link Expectations/Matchers.toHaveSubstring | toHaveSubstring} + * * {@link Expectations/Matchers.toStartWith | toStartWith} + * * {@link Expectations/Matchers.toEndWith | toEndWith} + * * {@link Expectations/Matchers.toMatch | toMatch} * Note that matchers for string to not check things such as size, if you want that, * we recommend calling expect over the string length and not the whole string. * * For the case of arrays, additional to the general ones, the following matchers * are provided, that allow to test things over the elements of the array. - * * {@link Matchers.toHaveLength | toHaveLength} - * * {@link Matchers.toContain | toContain} - * * {@link Matchers.toHaveAtPosition | toHaveAtPosition} - * * {@link Matchers.allToSatisfy | allToSatisfy} - * * {@link Matchers.anyToSatisfy | anyToSatisfy} - * * {@link Matchers.amountToSatisfy | amountToSatisfy} + * * {@link Expectations/Matchers.toHaveLength | toHaveLength} + * * {@link Expectations/Matchers.toContain | toContain} + * * {@link Expectations/Matchers.toHaveAtPosition | toHaveAtPosition} + * * {@link Expectations/Matchers.allToSatisfy | allToSatisfy} + * * {@link Expectations/Matchers.anyToSatisfy | anyToSatisfy} + * * {@link Expectations/Matchers.amountToSatisfy | amountToSatisfy} * * For object, the focus of the matcher is put on the instance type (class it belongs to) * and the properties it contains (attribute keys), extending so with: - * * {@link Matchers.toHavePropertyCount | toHavePropertyCount} - * * {@link Matchers.toHaveAtLeast | toHaveAtLeast} - * * {@link Matchers.toHaveNoOtherThan | toHaveNoOtherThan} - * * {@link Matchers.toHaveProperty | toHaveProperty} - * * {@link Matchers.toBeInstanceOf | toBeInstanceOf} + * * {@link Expectations/Matchers.toHavePropertyCount | toHavePropertyCount} + * * {@link Expectations/Matchers.toHaveAtLeast | toHaveAtLeast} + * * {@link Expectations/Matchers.toHaveNoOtherThan | toHaveNoOtherThan} + * * {@link Expectations/Matchers.toHaveProperty | toHaveProperty} + * * {@link Expectations/Matchers.toBeInstanceOf | toBeInstanceOf} * - * @param element The element that is going to be queried by the created expectation. - * - * @group API: Main + * @param element - The element that is going to be queried by the created expectation. */ export function expect(element?: boolean): BooleanExpectation; export function expect(element?: number): NumberExpectation; @@ -85,18 +83,16 @@ export function expect(element?: string): StringExpectation; export function expect(element?: T[]): ArrayExpectation; export function expect(element?: T): ObjectExpectation; // eslint-disable-next-line prefer-arrow/prefer-arrow-functions -export function expect(element?: any): any { - return new FullExpectation(element); +export function expect(element?: unknown): unknown { + return new FullExpectation(element); } /** * Create a new {@link JoinedExpectation} where all the expectations need to * have a `true` result in order for the result of the joined one to be also * `true`. That is, an expectation that joins it's components with a logical and. - * @param expectations A list of expectations that need to be fulfilled in order to + * @param expectations - A list of expectations that need to be fulfilled in order to * return `true` as result. - * - * @group API: Main */ export const and = (...expectations: FinishedExpectation[]): FinishedExpectation => new JoinedExpectation(expectations, (exp) => exp.reduce((r, e) => r && e.getResult(), true)); @@ -105,10 +101,8 @@ export const and = (...expectations: FinishedExpectation[]): FinishedExpectation * Create a new {@link JoinedExpectation} where any of the expectations need to * have a `true` result in order for the result of the joined one to be also * `true`. That is, an expectation that joins it's components with a logical or. - * @param expectations A list of expectations where one need to be fulfilled in order to + * @param expectations - A list of expectations where one need to be fulfilled in order to * return `true` as result. - * - * @group API: Main */ export const or = (...expectations: FinishedExpectation[]): FinishedExpectation => new JoinedExpectation(expectations, (exp) => exp.reduce((r, e) => r || e.getResult(), false)); diff --git a/src/Expectations/index.ts b/src/Expectations/index.ts index 5d22244..4a449ff 100644 --- a/src/Expectations/index.ts +++ b/src/Expectations/index.ts @@ -10,29 +10,7 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ -/* - * ***************************************************************************** - * Copyright (C) National University of Quilmes 2012-2024 - * Gobstones (TM) is a registered trademark of the National University of Quilmes. - * - * This program is free software distributed under the terms of the - * GNU Affero General Public License version 3. Additional terms added in compliance to section 7 of such license apply. - * - * You may read the full license at https://gobstones.github.org/gobstones-guidelines/LICENSE. - * ***************************************************************************** - */ -/* - * ***************************************************************************** - * Copyright (C) National University of Quilmes 2012-2024 - * Gobstones is a registered trademark of the National University of Quilmes. - * - * This program is free software distributed under the terms of the - * GNU Affero General Public License version 3. - * - * Additional terms added in compliance to section 7 of such license apply. - * You may read the full license at https://gobstones.github.org/gobstones-guidelines/LICENSE. - * ***************************************************************************** - */ + /** * This module exports the {@link expect} function, that is the kickoff to * create "expectations" over a specific element as shown in the following @@ -54,15 +32,15 @@ expect(x+y).toBeGreaterThan(z) * expect. * * When a matcher is called, the element given to expect is passed to the - * {@link Matchers} as the actual value, where the additional arguments over the + * {@link Expectations/Matchers} as the actual value, where the additional arguments over the * matcher are the arguments given to the matcher method called, so for example, - * `expect(x).toBe(y)` calls the {@link Matchers.toBe} with `x` as `actual` argument + * `expect(x).toBe(y)` calls the {@link Expectations/Matchers.toBe} with `x` as `actual` argument * and `y` as `expected` argument. * - * Once the first matcher is called over an expectation, a {@link FinishedExpectation} + * Once the first matcher is called over an expectation, a {@link Expectations/Interfaces.FinishedExpectation} * is obtained. A finished matcher has a result, that may be one of `true` * (if the matcher was satisfied) or `false` if not. A finished matcher may be - * queried for the result, by calling {@link FinishedExpectation.getResult | getResult}, + * queried for the result, by calling {@link Expectations/Interfaces.FinishedExpectation.getResult | getResult}, * but additional helper methods are provided by the interface in order to fulfill * different needs, such as throwing an error if the expectation was not satisfied, * or calling a function if it was. @@ -71,14 +49,14 @@ expect(x+y).toBeGreaterThan(z) * matcher would be calculated as an "and" over all the results. e.g. * `expect(x).toBeGreaterThan(y).toBeLowerThan(z)`. This creates a new expectations * that is satisfied only when both parts (x being greater than y, and x being - * lower than z) are true. This itself returns a new {@link FinishedExpectation}, + * lower than z) are true. This itself returns a new {@link Expectations/Interfaces.FinishedExpectation}, * that can be extended with new matchers, or queried about the results as * previously mentioned. * * To see a list of all matchers, read the {@link expect} documentation, and the - * {@link Matchers} documentation. + * {@link Expectations/Matchers} documentation. * - * @module API.Expectations + * @module Expectations * @author Alan Rodas Bonjour */ export * from './expect'; diff --git a/src/Functions/Conversion/asDefined.ts b/src/Functions/Conversion/asDefined.ts new file mode 100644 index 0000000..277ecbf --- /dev/null +++ b/src/Functions/Conversion/asDefined.ts @@ -0,0 +1,41 @@ +/* + * ***************************************************************************** + * Copyright (C) National University of Quilmes 2018-2024 + * Gobstones (TM) is a trademark of the National University of Quilmes. + * + * This program is free software distributed under the terms of the + * GNU Affero General Public License version 3. + * Additional terms added in compliance to section 7 of such license apply. + * + * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. + * ***************************************************************************** + */ + +/** + * @module Functions/Conversion + * @author Alan Rodas Bonjour + */ + +/** + * Returns a typed defined version of the given value. + * + * Given a value that may be undefined to the typechecker, but, + * you are sure that in runtime it would never be so, as per + * your preconditions, this function returns a typed defined version + * of the value. + * + * @remarks + * This is just an identity function, where the value + * is return as-is. This function only exist as to pass + * the static typechecking system in some particular + * scenarios, to avoid casting in multiple places. + * + * @param x - The value to return. + * + * @returns The given value. + */ +export const asDefined = (x: T | undefined): T => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + const ret = x as T; + return ret; +}; diff --git a/src/Translations/index.ts b/src/Functions/Conversion/index.ts similarity index 52% rename from src/Translations/index.ts rename to src/Functions/Conversion/index.ts index da57019..642610a 100644 --- a/src/Translations/index.ts +++ b/src/Functions/Conversion/index.ts @@ -10,18 +10,14 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * This module provides mechanisms to support basic localization of strings. - * This allows for error messages and CLI to support different languages - * without much effort. + * This module provides different functions that provide conversion between + * objects, types or shapes, as well as creation of some basic shapes. * - * Note that this module does not provide localization for the Gobstones Language - * but for a tool internally, and should not be confused with other classes - * exposed by this package. If you want to learn about how to translate the - * Gobstones Language see - * [The Gobstones Language Translation Module](https://gobstones.github.io/gobstones-lang-intl). - * - * @module API.Translator + * @module Functions/Conversion * @author Alan Rodas Bonjour */ -export * from './Translator'; +export * from './asDefined'; +export * from './matrix'; +export * from './symbolAsString'; diff --git a/src/Functions/matrix.ts b/src/Functions/Conversion/matrix.ts similarity index 83% rename from src/Functions/matrix.ts rename to src/Functions/Conversion/matrix.ts index 06a8a78..f03ab49 100644 --- a/src/Functions/matrix.ts +++ b/src/Functions/Conversion/matrix.ts @@ -10,8 +10,9 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Functions + * @module Functions/Conversion * @author Alan Rodas Bonjour */ @@ -21,14 +22,12 @@ * * @throws Error if the width or the height given are zero or negative. * - * @param width - The number of rows of the matrix - * @param height - The number of columns of the matrix - * @param initialValueGenerator - A function that given the i,j position of the element returns + * @param width - - The number of rows of the matrix + * @param height - - The number of columns of the matrix + * @param initialValueGenerator - - A function that given the i,j position of the element returns * the element to store the matrix at that position * * @returns A `T[][]` where T is the type of the elements in the matrix. - * - * @group API: Functions */ export const matrix = ( width: number, @@ -45,5 +44,5 @@ export const matrix = ( generatedMatrix[i][j] = initialValueGenerator ? initialValueGenerator(i, j) : undefined; } } - return generatedMatrix as T[][]; + return generatedMatrix; }; diff --git a/src/Functions/symbolAsString.ts b/src/Functions/Conversion/symbolAsString.ts similarity index 94% rename from src/Functions/symbolAsString.ts rename to src/Functions/Conversion/symbolAsString.ts index 6afc5f6..a677b36 100644 --- a/src/Functions/symbolAsString.ts +++ b/src/Functions/Conversion/symbolAsString.ts @@ -10,14 +10,14 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Functions + * @module Functions/Conversion * @author Pablo E. --Fidel-- Martínez López */ /** * Gives the string for a symbol, without the 'Symbol(...)' in it. - * @group Static operations */ export const symbolAsString = (s: symbol): string => { const str = s.toString(); diff --git a/src/Functions/ExecutionManagement/debounce.ts b/src/Functions/ExecutionManagement/debounce.ts new file mode 100644 index 0000000..6a2f905 --- /dev/null +++ b/src/Functions/ExecutionManagement/debounce.ts @@ -0,0 +1,160 @@ +/* + * ***************************************************************************** + * Copyright (C) National University of Quilmes 2018-2024 + * Gobstones (TM) is a trademark of the National University of Quilmes. + * + * This program is free software distributed under the terms of the + * GNU Affero General Public License version 3. + * Additional terms added in compliance to section 7 of such license apply. + * + * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. + * ***************************************************************************** + */ + +/** + * @module Functions/ExecutionManagement + * @author Alan Rodas Bonjour + */ + +import { AnyFunction } from '../../Types/AnyFunction'; + +/** + * @module Functions + * @author Alan Rodas Bonjour + */ + +/** + * Most of the code is copied from the `debounce` library by + * Sindre Sorhus (MIT licensed), + * but it's copied here to avoid extra dependencies. + */ + +/** + * The type of a function that was debounced. + * + * @internal + */ +export interface DebouncedFunction { + (...arguments_: Parameters): ReturnType | undefined; + /** cancels any scheduled executions. */ + clear(): void; + /** if an execution is scheduled then it will be immediately executed and the timer will be cleared. */ + flush(): void; + /** executes the function immediately and clears the timer if it was previously set. */ + trigger(): void; +} + +/** + * Returns a debounced function that delays execution until wait milliseconds have + * passed since its last invocation. + * + * @remarks + * Debouncing postpones the execution until after a period of inactivity. + * It's ideal for tasks that don't need to execute repeatedly in quick succession, + * such as API calls based on user input. + * + * @privateRemarks + * Note that this cannot be converted to an arrow function, because the context + * of `this` is important during the call. Arrow functions significantly change the + * way the context is managed. + * + * @param func - The function to debounce. + * @param wait - The number of milliseconds to wait before calling the function. + * @param options - Additional options for the function. + * + * @returns A debounced function. + */ +// eslint-disable-next-line prefer-arrow/prefer-arrow-functions +export function debounce( + func: F, + wait = 100, + options: { immediate: boolean } = { immediate: false } +): DebouncedFunction { + if (typeof func !== 'function') { + throw new TypeError(`Expected the first parameter to be a function, got \`${typeof func}\`.`); + } + + if (wait < 0) { + throw new RangeError('`wait` must not be negative.'); + } + + let storedContext: unknown; + let storedArguments: unknown[] | undefined; + let timeoutId: unknown; // The timeout handle, platform dependent + let timestamp = 0; + let result: ReturnType | undefined; + + const run = (): ReturnType | undefined => { + const callContext = storedContext; + const callArguments = storedArguments; + storedContext = undefined; + storedArguments = undefined; + result = func.apply(callContext, callArguments) as ReturnType; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return result; + }; + + const later = (): void => { + const last = Date.now() - timestamp; + + if (last < wait && last >= 0) { + timeoutId = setTimeout(later, wait - last); + } else { + timeoutId = undefined; + + if (!options.immediate) { + result = run(); + } + } + }; + + const debounced = function (...args): ReturnType | undefined { + // eslint-disable-next-line no-invalid-this + if (storedContext && this !== storedContext) { + throw new Error('Debounced method called with different contexts.'); + } + + // eslint-disable-next-line @typescript-eslint/no-this-alias, no-invalid-this + storedContext = this; + storedArguments = args; + timestamp = Date.now(); + + const callNow = options.immediate && !timeoutId; + + if (!timeoutId) { + timeoutId = setTimeout(later, wait); + } + + if (callNow) { + result = run(); + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return result; + }; + + debounced.clear = () => { + if (!timeoutId) { + return; + } + + clearTimeout(timeoutId as string | number); + timeoutId = undefined; + }; + + debounced.flush = () => { + if (!timeoutId) { + return; + } + + debounced.trigger(); + }; + + debounced.trigger = () => { + result = run(); + + debounced.clear(); + }; + + return debounced; +} diff --git a/src/Functions/ExecutionManagement/index.ts b/src/Functions/ExecutionManagement/index.ts new file mode 100644 index 0000000..3952759 --- /dev/null +++ b/src/Functions/ExecutionManagement/index.ts @@ -0,0 +1,22 @@ +/* + * ***************************************************************************** + * Copyright (C) National University of Quilmes 2018-2024 + * Gobstones (TM) is a trademark of the National University of Quilmes. + * + * This program is free software distributed under the terms of the + * GNU Affero General Public License version 3. + * Additional terms added in compliance to section 7 of such license apply. + * + * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. + * ***************************************************************************** + */ + +/** + * This module provides different functions that provide different ways to + * manage how functions are executed. + * + * @module Functions/ExecutionManagement + * @author Alan Rodas Bonjour + */ +export * from './debounce'; +export * from './throttle'; diff --git a/src/Functions/ExecutionManagement/throttle.ts b/src/Functions/ExecutionManagement/throttle.ts new file mode 100644 index 0000000..b06e827 --- /dev/null +++ b/src/Functions/ExecutionManagement/throttle.ts @@ -0,0 +1,110 @@ +/* + * ***************************************************************************** + * Copyright (C) National University of Quilmes 2018-2024 + * Gobstones (TM) is a trademark of the National University of Quilmes. + * + * This program is free software distributed under the terms of the + * GNU Affero General Public License version 3. + * Additional terms added in compliance to section 7 of such license apply. + * + * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. + * ***************************************************************************** + */ + +/** + * @module Functions/ExecutionManagement + * @author Alan Rodas Bonjour + */ + +import { AnyFunction } from '../../Types/AnyFunction'; + +/** + * Most of the code is copied from the `throttleit` library by + * Sindre Sorhus (MIT licensed), + * but it's copied here to avoid extra dependencies. + */ + +/** + * The type of a function that was throttled. + * + * @internal + */ +export interface ThrotteledFunction { + // eslint-disable-next-line @typescript-eslint/prefer-function-type + (...arguments_: Parameters): ReturnType | undefined; +} + +/** + * Returns a throttled function that limits calls to the original function to + * at most once every wait milliseconds. It guarantees execution after the final + * invocation and maintains the last context (this) and arguments. + * + * @remarks + * Throttling limits the execution to a fixed number of times over an interval. + * Throttling is suited for controlling the execution rate of functions called + * in response to events like scrolling or resizing. + * + * @privateRemarks + * Note that this cannot be converted to an arrow function, because the context + * of `this` is important during the call. Arrow functions significantly change the + * way the context is managed. + * + * @param func - The function to throttle + * @param wait - How many milliseconds to wait before running the function again. + * + * @returns A throttled function. + */ +// eslint-disable-next-line prefer-arrow/prefer-arrow-functions +export function throttle(func: F, wait: number): ThrotteledFunction { + if (typeof func !== 'function') { + throw new TypeError(`Expected the first argument to be a \`function\`, got \`${typeof func}\`.`); + } + + if (wait < 0) { + throw new RangeError('`wait` must not be zero or negative.'); + } + + let storedContext: unknown; + let storedArguments: unknown[] | undefined; + let timeoutId: unknown; // The timeout handle, platform dependent + let lastCallTime = 0; + let result: ReturnType | undefined; + + const run = (): ReturnType | undefined => { + const callContext = storedContext; + const callArguments = storedArguments; + storedContext = undefined; + storedArguments = undefined; + result = func.apply(callContext, callArguments) as ReturnType; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return result; + }; + + const throttled = function (...args): ReturnType | undefined { + clearTimeout(timeoutId as string | number); + + const now = Date.now(); + const timeSinceLastCall = now - lastCallTime; + const delayForNextCall = wait - timeSinceLastCall; + + // eslint-disable-next-line @typescript-eslint/no-this-alias, no-invalid-this + storedContext = this; + storedArguments = args; + + if (delayForNextCall <= 0) { + lastCallTime = now; + + run(); + } else { + timeoutId = setTimeout(() => { + lastCallTime = Date.now(); + run(); + }, delayForNextCall); + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return result; + }; + + return throttled; +} diff --git a/src/Functions/deepStringAssign.ts b/src/Functions/ObjectManipulation/deepStringAssign.ts similarity index 87% rename from src/Functions/deepStringAssign.ts rename to src/Functions/ObjectManipulation/deepStringAssign.ts index 3e7b8bb..4e9a0db 100644 --- a/src/Functions/deepStringAssign.ts +++ b/src/Functions/ObjectManipulation/deepStringAssign.ts @@ -10,11 +10,13 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Functions + * @module Functions/ObjectManipulation * @author Pablo E. --Fidel-- Martínez López */ -import { Subset } from '../Types/Subset'; + +import { Subset } from '../../Types/Subset'; /** * It returns a copy of the target object, where its string keys has been overwritten with @@ -57,12 +59,11 @@ import { Subset } from '../Types/Subset'; * ``` * * PRECONDITION: the keys of the objects have compatible types (that is, `T` is not just `object`). - * @param target An object that provides the default values for the fields of the result. - * @param sources A span parameter with objects possibly containing values to replace the defaults + * @param target - An object that provides the default values for the fields of the result. + * @param sources - A span parameter with objects possibly containing values to replace the defaults * in string keys with strings as values. - * @group API: Main */ -export function deepStringAssign(target: T, ...sources: Subset[]): T { +export const deepStringAssign = (target: T, ...sources: Subset[]): T => { // Needed for deepStringAssign not to overwrite the target const cloneTarget = JSON.parse(JSON.stringify(target)) as T; for (const source of sources) { @@ -70,12 +71,12 @@ export function deepStringAssign(target: T, ...sources: Subset const key = k as keyof T; if (typeof source[key] === 'object') { const tmp = cloneTarget[key] as object; - cloneTarget[key] = deepStringAssign(tmp, source[key] as object) as any; + cloneTarget[key] = deepStringAssign(tmp, source[key] as object) as unknown as T[keyof T]; } if (typeof source[key] === 'string') { - cloneTarget[key] = source[key] as any; + cloneTarget[key] = source[key] as unknown as T[keyof T]; } } } return cloneTarget; -} +}; diff --git a/src/Functions/flatten.ts b/src/Functions/ObjectManipulation/flatten.ts similarity index 70% rename from src/Functions/flatten.ts rename to src/Functions/ObjectManipulation/flatten.ts index 175c76b..5c66595 100644 --- a/src/Functions/flatten.ts +++ b/src/Functions/ObjectManipulation/flatten.ts @@ -10,17 +10,20 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Functions + * @module Functions/ObjectManipulation * @author Alan Rodas Bonjour - * + */ + +/** * Most of the code is copied from the `flat` library by * Hugh Kennedy (BSD-3 licensed), - * but it's copied here to avoid a dependency that may break - * when using ESM instead of CJS. + * but it's copied here to avoid extra dependencies. */ -import { isBuffer } from './isBuffer'; +import { asDefined } from '../Conversion/asDefined'; +import { isBuffer } from '../Querying/isBuffer'; /** * This type represent the options that are available for @@ -29,7 +32,6 @@ import { isBuffer } from './isBuffer'; * and we do not recommend to relay on them, as they might change * in the future. * - * @group Internal: Types * @internal */ export interface FlattenOptions { @@ -46,7 +48,6 @@ export interface FlattenOptions { * and we do not recommend to relay on them, as they might change * in the future. * - * @group Internal: Types * @internal */ export interface UnflattenOptions { @@ -89,22 +90,22 @@ flatten({ * * For an inverse operation see {@link unflatten}. * - * @param target The element to flatten. - * @param options The option to flatten. + * @param target - The element to flatten. + * @param options - The option to flatten. * - * @group API: Functions + * @returns The flattened object */ -export function flatten, TResult extends Record>( +export const flatten = , TResult extends Record>( target: TTarget, options: Partial = {} -): TResult { +): TResult => { const opts: FlattenOptions = Object.assign({}, flattenOptionsDefaults, options); - const output: Record = {}; + const output: Record = {}; - function step(object: any, prev?: any, currentDepth: number = 1): void { - Object.keys(object).forEach(function (key) { - const value: any = object[key]; - const type: string = Object.prototype.toString.call(value); + const step = (object: Record, prev?: string, currentDepth = 1): void => { + Object.keys(object).forEach((key) => { + const value: unknown = object[key]; + const type: string = Object.prototype.toString.call(value) as string; const isarray: boolean = opts.safe && Array.isArray(value); const isbuffer: boolean = isBuffer(value); const isobject: boolean = type === '[object Object]' || type === '[object Array]'; @@ -115,20 +116,21 @@ export function flatten, TResult extends Rec !isarray && !isbuffer && isobject && - Object.keys(value).length && + Object.keys(value as Record).length && (!opts.maxDepth || currentDepth < opts.maxDepth) ) { - return step(value, newKey, currentDepth + 1); + step(value as Record, newKey, currentDepth + 1); + return; } output[newKey] = value; }); - } + }; step(target); return output as TResult; -} +}; /** * Un-Flatten the given object. @@ -161,17 +163,18 @@ unflatten({ * Additional options may be passed, such as which character is used as a delimiter, * or the maximum depth level. * - * @param target The element to unflatten. - * @param options The option to unflatten. + * @param target - The element to unflatten. + * @param options - The option to unflatten. * - * @group API: Functions + * @returns The unflattened object */ -export function unflatten, TResult extends Record>( +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const unflatten = , TResult extends Record>( target: TTarget, options?: Partial -): TResult { +): TResult => { const opts: UnflattenOptions = Object.assign({}, unflattenOptionsDefaults, options); - const output = {}; + const output: Record = {}; const isbuffer = isBuffer(target); if (isbuffer || Object.prototype.toString.call(target) !== '[object Object]') { @@ -179,19 +182,23 @@ export function unflatten, TResult extends R } // return the key as a string or as an integer - function getKey(key: string): string | number { + const getKey = (key: string): string | number => { const parsedKey = Number(key); - return isNaN(parsedKey) || key.indexOf('.') !== -1 || opts.object ? key : parsedKey; - } + return isNaN(parsedKey) || key.includes('.') || opts.object ? key : parsedKey; + }; - const addKeys = (keyPrefix: string, recipient: any, targeted: any): any => - Object.keys(targeted).reduce(function (acc, key) { + const addKeys = ( + keyPrefix: string, + recipient: Record, + targeted: Record + ): Record => + Object.keys(targeted).reduce((acc, key) => { acc[keyPrefix + opts.delimiter + key] = targeted[key]; return acc; }, recipient); - function isEmpty(val: any): boolean { - const type = Object.prototype.toString.call(val); + const isEmpty = (val: unknown): boolean => { + const type = Object.prototype.toString.call(val) as string; const isArray = type === '[object Array]'; const isObject = type === '[object Object]'; @@ -199,28 +206,28 @@ export function unflatten, TResult extends R if (!val) { return true; } else if (isArray) { - return !val.length; + return !(val as unknown[]).length; } else if (isObject) { return !Object.keys(val).length; } /* istanbul ignore next */ return false; - } + }; - target = Object.keys(target).reduce(function (acc: Record, key: string) { - const type = Object.prototype.toString.call(target[key]); + target = Object.keys(target).reduce>((acc: Record, key: string) => { + const type = Object.prototype.toString.call(target[key]) as string; const isObject = type === '[object Object]' || type === '[object Array]'; if (!isObject || isEmpty(target[key])) { acc[key] = target[key]; return acc; } else { - return addKeys(key, acc, flatten(target[key], opts)); + return addKeys(key, acc, flatten(target[key] as Record, opts)); } }, {}) as TTarget; - Object.keys(target).forEach(function (key) { + Object.keys(target).forEach((key) => { const split = key.split(opts.delimiter).map(opts.transformKey); - let key1 = getKey(split.shift() as string); + let key1 = getKey(asDefined(split.shift())); let key2 = getKey(split[0]); let recipient = output; @@ -229,58 +236,56 @@ export function unflatten, TResult extends R return; } - const type = Object.prototype.toString.call((recipient as any)[key1]); + const type = Object.prototype.toString.call(recipient[key1]) as string; const isobject = type === '[object Object]' || type === '[object Array]'; // do not write over falsey, non-undefined values if overwrite is false - if (!opts.overwrite && !isobject && typeof (recipient as any)[key1] !== 'undefined') { + if (!opts.overwrite && !isobject && typeof recipient[key1] !== 'undefined') { return; } if ( (opts.overwrite && !isobject) || // eslint-disable-next-line no-null/no-null - (!opts.overwrite && (recipient as any)[key1] == null) + (!opts.overwrite && recipient[key1] == null) ) { - (recipient as any)[key1] = typeof key2 === 'number' && !opts.object ? [] : {}; + recipient[key1] = typeof key2 === 'number' && !opts.object ? [] : {}; } - recipient = (recipient as any)[key1]; + recipient = recipient[key1] as Record; if (split.length > 0) { - key1 = getKey(split.shift() as string); + key1 = getKey(asDefined(split.shift())); key2 = getKey(split[0]); } } // unflatten again for 'messy objects' - (recipient as any)[key1] = unflatten((target as any)[key], opts); + recipient[key1] = unflatten(target[key] as Record, opts); }); return output as TResult; -} +}; /** * The default options for the flatten function. * - * @group Internal: Objects * @internal */ const flattenOptionsDefaults: FlattenOptions = { delimiter: '.', maxDepth: undefined, safe: false, - transformKey: (key: any): any => key + transformKey: (key: string): string => key }; /** * The default options for the unflatten function. * - * @group Internal: Objects * @internal */ const unflattenOptionsDefaults: UnflattenOptions = { delimiter: '.', object: false, overwrite: false, - transformKey: (key: any): any => key + transformKey: (key: string): string => key }; diff --git a/src/Functions/ObjectManipulation/index.ts b/src/Functions/ObjectManipulation/index.ts new file mode 100644 index 0000000..66f7ad9 --- /dev/null +++ b/src/Functions/ObjectManipulation/index.ts @@ -0,0 +1,22 @@ +/* + * ***************************************************************************** + * Copyright (C) National University of Quilmes 2018-2024 + * Gobstones (TM) is a trademark of the National University of Quilmes. + * + * This program is free software distributed under the terms of the + * GNU Affero General Public License version 3. + * Additional terms added in compliance to section 7 of such license apply. + * + * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. + * ***************************************************************************** + */ + +/** + * This module provides different functions that provide different ways to + * change an object's shape and values. + * + * @module Functions/ObjectManipulation + * @author Alan Rodas Bonjour + */ +export * from './deepStringAssign'; +export * from './flatten'; diff --git a/src/Functions/deepEquals.ts b/src/Functions/Querying/deepEquals.ts similarity index 86% rename from src/Functions/deepEquals.ts rename to src/Functions/Querying/deepEquals.ts index f756fde..b4997ea 100644 --- a/src/Functions/deepEquals.ts +++ b/src/Functions/Querying/deepEquals.ts @@ -10,13 +10,16 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Functions + * @module Functions/Querying * @author Alan Rodas Bonjour */ import { isBuffer } from './isBuffer'; +import { asDefined } from '../Conversion/asDefined'; + /** * Answer wether or not two elements are semantically equal, considering them * equal when they have the same type and all their internal elements are @@ -58,15 +61,13 @@ import { isBuffer } from './isBuffer'; * If you want to see all supported and unsupported cases, we recommend you to check * out the test cases. * - * @param first The element to compare to. - * @param second The element to compare against. + * @param first - The element to compare to. + * @param second - The element to compare against. * * @return `true` if both elements are equal, `false` otherwise. - * - * @group API: Functions */ export const deepEquals = (first: T, second: T): boolean => { - const compare = (a: any, b: any): boolean => { + const compare = (a: unknown, b: unknown): boolean => { // Return true if they are the same object if (a === b) return true; // and false if they don't have the same type @@ -80,7 +81,7 @@ export const deepEquals = (first: T, second: T): boolean => { if (typeof a === 'object' && typeof b === 'object') { // If they belong to different classes, then they are not equal, // one of them might not have a class, so consider that case too. - if (a.constructor && b.constructor && a.constructor !== b.constructor) return false; + if (a?.constructor && b?.constructor && a.constructor !== b.constructor) return false; // Use array comparison if both are arrays if (Array.isArray(a) && Array.isArray(b)) return arrayEquals(a, b, compare); // If both are Sets @@ -108,8 +109,8 @@ export const deepEquals = (first: T, second: T): boolean => { * Answer if two numbers are equal. Two numbers are equal * if they happen to be the same number, or, if both are NaN. * - * @param a The first number - * @param b The second number + * @param a - The first number + * @param b - The second number * * @returns true when both numbers are the same, or both are NaN. * @@ -128,13 +129,12 @@ const numberEquals = (a: number, b: number): boolean => { * the {@link innerComparer} is used. The expected value for {@link innerComparer} * is the recursive comparer function in deepEquals. * - * @param a The first array - * @param b The second array - * @param innerComparer The function for testing if two inner elements are equal + * @param aArr - The first array + * @param bArr - The second array + * @param innerComparer - The function for testing if two inner elements are equal * * @returns `true` if both arrays are equal, `false` otherwise. * - * @group Internal: Function * @internal */ const arrayEquals = (aArr: T[], bArr: T[], innerComparer: (a: T, b: T) => boolean): boolean => { @@ -158,13 +158,12 @@ const arrayEquals = (aArr: T[], bArr: T[], innerComparer: (a: T, b: T) => boo * are equal the {@link innerComparer} is used. The expected value for {@link innerComparer} * is the recursive comparer function in deepEquals. * - * @param a The first object - * @param b The second object - * @param innerComparer The function for testing if two inner elements are equal + * @param objA - The first object + * @param objB - The second object + * @param innerComparer - The function for testing if two inner elements are equal * * @returns `true` if both object are equal, `false` otherwise. * - * @group Internal: Function * @internal */ const objectEquals = (objA: T, objB: T, innerComparer: (a: T, b: T) => boolean): boolean => { @@ -178,11 +177,12 @@ const objectEquals = (objA: T, objB: T, innerComparer: (a: T, b: T) => boolea if (aKeys[i] !== bKeys[i]) return false; } // If they do, perform a more expensive deep equal test in all values + // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let i = 0; i < aKeys.length; i++) { - const aValue: any = (objA as any)[aKeys[i]]; - const bValue: any = (objB as any)[aKeys[i]]; - if (!innerComparer(aValue, bValue)) return false; + const aValue: unknown = objA[aKeys[i]]; + const bValue: unknown = objB[aKeys[i]]; + if (!innerComparer(aValue as T, bValue as T)) return false; } // They must be equal when this is reached return true; @@ -193,12 +193,11 @@ const objectEquals = (objA: T, objB: T, innerComparer: (a: T, b: T) => boolea * have the exact same number of elements, and they have the same * elements. * - * @param a The first object - * @param b The second object + * @param a - The first object + * @param b - The second object * * @returns `true` if both object are equal, `false` otherwise. * - * @group Internal: Function * @internal */ const setEquals = (a: Set, b: Set): boolean => { @@ -206,7 +205,7 @@ const setEquals = (a: Set, b: Set): boolean => { const aIterator = a.entries(); let aNext = aIterator.next(); while (aNext && !aNext.done) { - if (!b.has(aNext.value[1])) return false; + if (!b.has((aNext.value as [T, T])[1])) return false; aNext = aIterator.next(); } return true; @@ -219,13 +218,12 @@ const setEquals = (a: Set, b: Set): boolean => { * are equal the {@link innerComparer} is used. The expected value for {@link innerComparer} * is the recursive comparer function in deepEquals. * - * @param a The first map - * @param b The second map - * @param innerComparer The function for testing if two inner elements are equal + * @param a - The first map + * @param b - The second map + * @param innerComparer - The function for testing if two inner elements are equal * * @returns `true` if both Maps are equal, `false` otherwise. * - * @group Internal: Function * @internal */ const mapEquals = (a: Map, b: Map, innerComparer: (a: V, b: V) => boolean): boolean => { @@ -234,8 +232,8 @@ const mapEquals = (a: Map, b: Map, innerComparer: (a: V, b: V) let aNext = aEntries.next(); while (!aNext.done) { // Should have a key with same name or value - if (!b.has(aNext.value[0])) return false; - if (!innerComparer(aNext.value[1], b.get(aNext.value[0]) as V)) return false; + if (!b.has((aNext.value as [K, V])[0])) return false; + if (!innerComparer((aNext.value as [K, V])[1], asDefined(b.get((aNext.value as [K, V])[0])))) return false; aNext = aEntries.next(); } return true; @@ -245,8 +243,8 @@ const mapEquals = (a: Map, b: Map, innerComparer: (a: V, b: V) * Answer if two Errors are equal. Two Errors are equal when they both * have the exact name and message. * - * @param a The first Error - * @param b The second Error + * @param a - The first Error + * @param b - The second Error * * @returns `true` if both Errors are equal, `false` otherwise. * @@ -259,12 +257,11 @@ const errorEquals = (a: Error, b: Error): boolean => a.name === b.name && a.mess * Answer if two RegExps are equal. Two RegExps are equal when they both * have the exact source and flags. * - * @param a The first RegExp - * @param b The second RegExp + * @param a - The first RegExp + * @param b - The second RegExp * * @returns `true` if both RegExp are equal, `false` otherwise. * - * @group Internal: Function * @internal */ const regexpEquals = (a: RegExp, b: RegExp): boolean => a.source === b.source && a.flags === b.flags; @@ -273,12 +270,11 @@ const regexpEquals = (a: RegExp, b: RegExp): boolean => a.source === b.source && * Answer if two Dates are equal. Two Date are equal when they both * have the exact time. * - * @param a The first Date - * @param b The second Date + * @param a - The first Date + * @param b - The second Date * * @returns `true` if both Date are equal, `false` otherwise. * - * @group Internal: Function * @internal */ const dateEquals = (a: Date, b: Date): boolean => a.getTime() === b.getTime(); @@ -287,12 +283,11 @@ const dateEquals = (a: Date, b: Date): boolean => a.getTime() === b.getTime(); * Answer if two Buffers are equal. Two Buffers are equal when they both * have the exact element at each position. * - * @param a The first Buffer - * @param b The second Buffer + * @param a - The first Buffer + * @param b - The second Buffer * * @returns `true` if both Buffers are equal, `false` otherwise. * - * @group Internal: Function * @internal */ const bufferEquals = (a: Buffer, b: Buffer): boolean => { diff --git a/src/Functions/Querying/index.ts b/src/Functions/Querying/index.ts new file mode 100644 index 0000000..0c5e771 --- /dev/null +++ b/src/Functions/Querying/index.ts @@ -0,0 +1,22 @@ +/* + * ***************************************************************************** + * Copyright (C) National University of Quilmes 2018-2024 + * Gobstones (TM) is a trademark of the National University of Quilmes. + * + * This program is free software distributed under the terms of the + * GNU Affero General Public License version 3. + * Additional terms added in compliance to section 7 of such license apply. + * + * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. + * ***************************************************************************** + */ + +/** + * This module provides different functions that allows you to query about the shape + * or type of an object. + * + * @module Functions/Querying + * @author Alan Rodas Bonjour + */ +export * from './deepEquals'; +export * from './isBuffer'; diff --git a/src/Functions/Querying/isBuffer.ts b/src/Functions/Querying/isBuffer.ts new file mode 100644 index 0000000..bcfda5a --- /dev/null +++ b/src/Functions/Querying/isBuffer.ts @@ -0,0 +1,33 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* + * ***************************************************************************** + * Copyright (C) National University of Quilmes 2018-2024 + * Gobstones (TM) is a trademark of the National University of Quilmes. + * + * This program is free software distributed under the terms of the + * GNU Affero General Public License version 3. + * Additional terms added in compliance to section 7 of such license apply. + * + * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. + * ***************************************************************************** + */ + +/** + * @module Functions/Querying + * @author Alan Rodas Bonjour + */ + +/** + * Answer if an element is a Buffer. + * + * @param obj - The element to test if it's a buffer + * + * @returns `true` if the element is a Buffer, `false` otherwise. + */ +export const isBuffer = (obj: unknown): obj is Buffer => + obj?.constructor && + typeof (obj.constructor as any).isBuffer === 'function' && + (obj.constructor as any).isBuffer(obj); diff --git a/src/Functions/index.ts b/src/Functions/index.ts index 0f3dbfd..fe72b8f 100644 --- a/src/Functions/index.ts +++ b/src/Functions/index.ts @@ -10,6 +10,7 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** * This module provides different functions that provide common functionality * that may be reused in different packages. @@ -17,11 +18,11 @@ * for better accessibility in all packages. * * - * @module API.Functions + * @module Functions * @author Alan Rodas Bonjour */ -export * from './deepEquals'; -export * from './flatten'; -export * from './matrix'; -export * from './symbolAsString'; -export * from './deepStringAssign'; +export * from './Conversion'; +export * from './ExecutionManagement'; +export * from './Querying'; +export * from './ObjectManipulation'; +export * from './noop'; diff --git a/src/Functions/isBuffer.ts b/src/Functions/noop.ts similarity index 64% rename from src/Functions/isBuffer.ts rename to src/Functions/noop.ts index e9cf250..dcda95f 100644 --- a/src/Functions/isBuffer.ts +++ b/src/Functions/noop.ts @@ -11,17 +11,19 @@ * ***************************************************************************** */ /** - * @module API.Functions + * @module Functions * @author Alan Rodas Bonjour */ + /** - * Answer if an element is a Buffer. + * A function that does nothing. * - * @param obj The element to test if it's a buffer - * - * @returns `true` if the element is a Buffer, `false` otherwise. - * - * @group API: Function + * @remarks + * This is useful as a placeholder in many scenarios, specially + * in react applications, where a function must be given to callbacks + * and events, even though the real function may not still be ready in + * the app yet. */ -export const isBuffer = (obj: any): boolean => - obj && obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj); +export const noop = (): void => { + /* Intentionally empty */ +}; diff --git a/src/GobstonesLang/Board.ts b/src/GobstonesLang/Board.ts deleted file mode 100644 index 3f0cc32..0000000 --- a/src/GobstonesLang/Board.ts +++ /dev/null @@ -1,1154 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) National University of Quilmes 2018-2024 - * Gobstones (TM) is a trademark of the National University of Quilmes. - * - * This program is free software distributed under the terms of the - * GNU Affero General Public License version 3. - * Additional terms added in compliance to section 7 of such license apply. - * - * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. - * ***************************************************************************** - */ -/** - * @module API.GobstonesLang - * @author Alan Rodas Bonjour - */ -import { BoardDefinition, CellDataDefinition, CellLocation } from './BoardDefinition'; -import { - InvalidBoardDescription, - InvalidCellReading, - InvalidSizeChange, - InvalidSizeChangeAttempt, - LocationChangeActionAttempt, - LocationFallsOutsideBoard -} from './BoardErrors'; -import { Cell } from './Cell'; -import { Color } from './Color'; -import { Direction } from './Direction'; - -import { EventEmitter } from '../Events/EventEmitter'; -import { and, expect, or } from '../Expectations'; -import { matrix } from '../Functions/matrix'; - -/** - * This object contains the default values for a {@link Board}. - * When a specific value is not given, the defaults are used. - * - * @group Internal: Default Values - * @internal - */ -const Defaults = { - width: 4, - height: 4, - head: [0, 0] as CellLocation -}; - -/** - * This type represents the function that acts as a callback of - * the {@link Board.onSizeChanged} event. Such an event is thrown by - * an instance of a board whenever an operation is performed such - * that the original size of the board is altered. - * - * This function receives 5 arguments: - * * the size of the board (encapsulated in an object containing both - * width and height attributes), that being, the new size that the - * board has been set to. - * * the new head location. This might be the same location that the head was - * previously in, but it might change if the size was reduced and the cell - * where the head was no longer exists. - * * A boolean, that is `true`if the board change was performed origin first. - * That is, the size was reduced from the West or the South, or expanded - * into those directions. - * * The previous size the board had, in an encapsulated object with `width` and `height`. - * * The previous cell that the head was at. Might be the same as the current one. - * - * @see {@link Board.onSizeChanged} for more information. - * - * @group API: Events - * @event - */ -export type OnBoardSizeChangedCallback = ( - newSize: { width: number; height: number }, - newHeadLocation: CellLocation, - fromOriginCell: boolean, - previousSize: { width: number; height: number }, - previousHeadLocation: CellLocation -) => void; - -/** - * This type represents the function that acts as a callback of - * the {@link Board.onHeadMoved} event. Such an event is thrown by - * an instance of a board whenever an operation is performed such - * that the current head location is altered. - * - * This function receives two arguments. - * * The current head location. - * * The previous head location. - * - * @see {@link Board.onHeadMoved} for more information. - * - * @event - * @group API: Events - */ -export type OnBoardHeadMovedCallback = (currentLocation: CellLocation, previousLocation: CellLocation) => void; - -/** - * This interface contains the signature of the - * events that a Board can throw. - * - * @group Internal: Events - * @internal - */ -export interface BoardEvents { - [Board.onSizeChanged]: OnBoardSizeChangedCallback; - [Board.onHeadMoved]: OnBoardHeadMovedCallback; -} - -/** - * This class models a Gobstones Board with all it's associated behavior. - * Note that an instance of this class implements {@link BoardDefinition}, - * so it can be used as a valid description of a board in the [Gobstones - * Interpreter](http://github.com/gobstones/gobstones-interpreter) and is - * returned by [The Gobstones GBB Parser](http://github.com/gobstones/gobstones-gbb-parser). - * All newer developments are expected to use this class instead of a particular ad-hoc - * representation for a board. - * - * Note that this class provides a {@link board} attribute, that used to be the standard - * way to access the cells of the boards, but this new class is intended to be used - * as an ADT. So it's expected that you use the provided functions for accessing - * cells, columns ands rows, instead of using the board attribute. Be aware of - * deprecation warnings. - * - * The class provides a set of method for accessing and querying the properties - * of the board and cell contents, modify the head location, the size and the - * cell contents. This class works in conjunction with the {@link Cell} class. Note - * that internal representation of a board might change, so do not rely on - * representation, and abstract away of it as much as possible. - * - * @group API: Main - */ -export class Board extends EventEmitter implements BoardDefinition { - /** - * This event is thrown whenever an action that alters the position of the head - * is performed. Listeners of this action are expected to conform to - * {@link OnBoardHeadMovedCallback}. - * - * The actions that trigger this callback include: - * * Setting the {@link head} attribute of an instance. - * * Calling {@link moveHeadTo} on an instance with any direction. - * * Calling {@link moveHeadToEdgeAt} on an instance with any direction. - * - * Note however that there is a particular case where the head moves, but this - * event is not triggered. Such particular case happens when the board is resized - * is such a way that, the location where the head was at, no longer exists as - * a valid cell of the board, thus, the head is assigned to a new location. - * If you wish to consider such a case, consider also listening to {@link onSizeChanged}. - * @event - */ - public static readonly onHeadMoved: unique symbol = Symbol('onHeadMoved'); - - /** - * This event is thrown whenever an action that alters the size of the board - * is performed. Listeners of this action are expected to conform to - * {@link OnBoardSizeChangedCallback} - * - * The actions that trigger this callback include: - * * Setting the {@link width} attribute of an instance. - * * Setting the {@link height} attribute of an instance. - * * Calling {@link changeSizeTo} on an instance with any values - * (even with the same width and height as the actual size) - * * Calling {@link addColumn} on an instance. - * * Calling {@link addColumns} on an instance. - * * Calling {@link removeColumn} on an instance. - * * Calling {@link removeColumns} on an instance. - * * Calling {@link addRow} on an instance. - * * Calling {@link addRows} on an instance. - * * Calling {@link removeRow} on an instance. - * * Calling {@link removeRows} on an instance. - * @event - */ - public static readonly onSizeChanged: unique symbol = Symbol('onSizeChanged'); - - /** - * The internal representation of the width of the board. - * - * @see {@link width} - */ - private boardWidth: number; - - /** - * The internal representation of the height of the board - * - * @see {@link height} - */ - private boardHeight: number; - - /** - * The internal representation of head's X location, where the - * position should always comply to: - * ```typescript - * 0 <= x < width - * ``` - * - * @see {@link head} - */ - private headXLocation: number; - - /** - * The internal representation of head's Y location, where the - * position should comply to: - * ```typescript - * 0 <= y < height - * ``` - * - * @see {@link head} - */ - private headYLocation: number; - - /** - * The internal representation of the cells of the board. - * Currently this is represented as an array of {@link boardWidth} - * elements, where each element is itself an array of {@link boardHeight} - * elements, where each of them is a {@link Cell}. - * - * @see {@link board} - */ - private boardData: Cell[][]; - - /** - * Create a new instance of a board. You may choose between a default empty board - * with width=4, height=4 and the head at [0,0]. Alternatively pass a custom `width` - * and `height`, and an optional specific location for the `head`. - * - * Optionally and additionally, when creating a custom sized board, you can - * initialize some of the cells with some specific stones amount, by providing - * an array of {@link CellDataDefinition}, specifying the stones of each color for a - * given position of the board (There's no specific behavior if two elements - * for the same location is given, any of them might be used, - * so be sure to specify the stones of a specific location only once). - * - * @example The following example initializes a Board of 5 columns - * by 4 rows, with the head starting in the x=2, y=3 coordinate, and - * in which there are 10 red stones in each cell of the firs column. - * ``` - * new Board(5, 4, [2, 3], [ - * {x: 0, y: 0, [Color.Red]: 10}, - * {x: 0, y: 1, [Color.Red]: 10}, - * {x: 0, y: 2, [Color.Red]: 10}, - * {x: 0, y: 3, [Color.Red]: 10} - * ]); - * ``` - * - * Note that when passing {@link CellDataDefinition}, the usage of the - * {@link Color} enum values as keys for specifying the stones amount is - * preferred over the 'a', 'n', 'r', 'v' string keys. Although equivalent, - * the actual strings of the enum may change is the future. - * - * @throws {@link InvalidBoardDescription} if the given width or height - * are lower or equal than zero. - * @throws {@link InvalidBoardDescription} if the given head location - * has an `x` coordinate lower than zero or greater or equal - * than the `width` or an `y` coordinate lower than zero or - * greater or equal than the `height`. - * @throws {@link InvalidBoardDescription} if any of the given cell - * locations in `initialState` has a location as an `[x, y]` - * coordinate such that `x < 0 || x >= width` or - * `y < 0 || y >= height`. - * - * @param width The width of the board. - * @param height The height of the board. - * @param head The head location. - * @param initialState An array of {@link CellDataDefinition} for the cells - * that you want to specify initial stones to. - */ - public constructor(width?: number, height?: number, head?: CellLocation, initialState?: CellDataDefinition[]) { - super(); - this.boardWidth = width ?? Defaults.width; - this.boardHeight = height ?? Defaults.height; - this.headXLocation = (head ?? Defaults.head)[0]; - this.headYLocation = (head ?? Defaults.head)[1]; - - and( - expect(this.boardWidth).toBeGreaterThan(0), - expect(this.boardHeight).toBeGreaterThan(0), - expect(this.headX).toBeGreaterThanOrEqual(0).toBeLowerThan(this.boardWidth), - expect(this.headY).toBeGreaterThanOrEqual(0).toBeLowerThan(this.boardHeight) - ).orThrow(new InvalidBoardDescription(this.boardHeight, this.boardWidth, [this.headX, this.headY])); - - const cells: Map = new Map( - (initialState ?? []).map((cellDef) => { - and( - expect(cellDef.x).toBeGreaterThanOrEqual(0).toBeLowerThan(this.boardWidth), - expect(cellDef.y).toBeGreaterThanOrEqual(0).toBeLowerThan(this.boardHeight) - ); - return [`${cellDef.x}-${cellDef.y}`, cellDef]; - }) - ); - this.boardData = matrix( - this.boardWidth, - this.boardHeight, - (i, j) => new Cell(this, cells.get(`${i}-${j}`) ?? { x: i, y: j }) - ); - } - - /* ************* Accessors ************** */ - - /** - * Returns true for any board. - * Useful to test if an untyped object is a board. - */ - public get isBoard(): boolean { - return true; - } - - /** - * The board format. Always: GBB/1.0 as it's derived from the GBB format. - * - * @deprecated - * In the future, the version information would not be available on a board, - * as it's derived from the GBB format, but the way in which this instance - * is produces might come from any other location or format other than GBB, - * and is not at all relevant to the user. - * - * @see {@link BoardDefinition.format | BoardDefinition.format} - */ - public get format(): string { - return 'GBB/1.0'; - } - - /** - * Get the width of this board. - * - * @see {@link BoardDefinition.width | BoardDefinition.width} - * - * @returns The width of the board, always greater than 0. - */ - public get width(): number { - return this.boardWidth; - } - - /** - * Sets the width of this board. - * - * @see {@link BoardDefinition.width | BoardDefinition.width} - * - * @throws {@link InvalidSizeChange} with the attempt as `Resize` - * if attempting to set the attribute and the given value is - * lower or equal to zero. - */ - public set width(value: number) { - this.innerChangeSize(value, this.boardHeight, false, 'Resize'); - } - - /** - * Get the height of this board. - * - * @see {@link BoardDefinition.height | BoardDefinition.height} - * - * @returns The height of the board, always greater than 0. - */ - public get height(): number { - return this.boardHeight; - } - - /** - * Get or sets the height of this board. - * - * @see {@link BoardDefinition.height | BoardDefinition.height} - * - * @throws {@link InvalidSizeChange} with the attempt as `Resize` - * if attempting to set the attribute and the given value is - * lower or equal to zero. - */ - public set height(value: number) { - this.innerChangeSize(this.boardWidth, value, false, 'Resize'); - } - - /** - * Get the head location of this board as an `[x, y]` coordinate. - * - * @see {@link BoardDefinition.head | BoardDefinition.head} - * - * @returns The head location as a two element array `[x, y]`, that satisfies - * `0 <= x < width && 0 <= y < height` - */ - public get head(): CellLocation { - return [this.headX, this.headY] as CellLocation; - } - - /** - * Sets the head location of this board as an `[x, y]` coordinate. - * - * @see {@link BoardDefinition.head | BoardDefinition.head} - * - * @throws {@link LocationFallsOutsideBoard} with the attempt as `SetLocation` - * if attempting to set the attribute and the given cell location as - * `[x, y]` has `x < 0 || x > width` or `y < 0 || y > width`. - */ - public set head(value: CellLocation) { - this.innerSetHeadLocation(value, 'SetLocation'); - } - - /** - * The head's X coordinate of this board. - * - * @returns The board X's coordinate, which satisfies `0 <= x < width` - * - * @see {@link BoardDefinition.head | BoardDefinition.head} - */ - public get headX(): number { - return this.headXLocation; - } - - /** - * The head's Y coordinate of this board - * - * @returns The board X's coordinate, which satisfies `0 <= y < height` - * - * @see {@link BoardDefinition.head | BoardDefinition.head} - */ - public get headY(): number { - return this.headYLocation; - } - - /** - * Obtain the cells of the board as an array of `width` elements, each - * of which is an array of `height` elements, each of which is a {@link Cell}, - * or in another sense, a {@link API.Functions!matrix} of {@link Cell | Cells}. - * - * This is retain only for compatibility reasons. - * - * @see {@link BoardDefinition.board | BoardDefinition.board} - * - * @deprecated - * Note that this method of accessing the board is deprecated and should not - * be used. If you need a cell matrix, in such a way that the first - * array represents the columns, and each array inside the cells of such a column, - * the method {@link getColumns} should be used. Instead if you are attempting to - * access a specific cell as `board[x][y]`, use the {@link getCell} method instead as - * `getCell(x, y)`. - * - * @returns A matrix of cells. - */ - /* istanbul ignore next */ - public get board(): Cell[][] { - return this.boardData; - } - - /* ************* Cloning ************** */ - - /** - * Clone this board, returning a new one with the same characteristics. - * - * @returns A new Board. - */ - public clone(): Board { - const cellStates = this.foldCells((cells, cell) => { - cells.push({ - x: cell.x, - y: cell.y, - [Color.BLUE]: cell.getStonesOf(Color.Blue), - [Color.BLACK]: cell.getStonesOf(Color.Black), - [Color.RED]: cell.getStonesOf(Color.Red), - [Color.GREEN]: cell.getStonesOf(Color.Green) - }); - return cells; - }, []); - return new Board(this.width, this.height, this.head, cellStates); - } - - /* ************* Querying ************** */ - - /** - * Get a {@link Cell} for the given location, or the cell for the - * head location if none is given. - * - * @throws {@link LocationFallsOutsideBoard} with the attempt as `ReadCell` - * if the given cell location as `[x, y]` has `x < 0 || x > width` - * or `y < 0 || y > width`. - * - * @param location The location from which to obtain the cell - * data from, or undefined if the data is ought to be taken - * from the cell at the head location. - * - * @returns A {@link Cell} for the given location. - */ - public getCell(x?: number, y?: number): Cell { - return this.innerGetCell(x ?? this.headX, y ?? this.headY); - } - - /** - * Get an array of {@link Cell | Cells} for the specific column. - * - * @throws {@link LocationFallsOutsideBoard} with the attempt as `ReadColumn` - * if the given column number is lower than cero or greater than of equal - * than the {@link width}. - * - * @param columnNumber The column number to obtain the cell from. - * - * @returns A {@link Cell} array for the given column. - */ - public getColumn(columnNumber: number): Cell[] { - return this.innerGetColumn(columnNumber); - } - - /** - * Get an array of {@link Cell | Cells} for the specific row. - * - * Note that with the current implementation this takes more - * time than accessing by column, but this might change in future - * implementations. - * - * @throws {@link LocationFallsOutsideBoard} with the attempt as `ReadRow` - * if the given column number is lower than cero or greater of equal - * than the {@link height}. - * - * @param rowNumber The row number to obtain the cell from. - * - * @returns A {@link Cell} array for the given row. - */ - public getRow(rowNumber: number): Cell[] { - return this.innerGetRow(rowNumber); - } - - /** - * Get an array of arrays of {@link Cell | Cells} for the board, that can be - * iterated by column, that is, an array of columns. - * - * @returns A {@link Cell} array of arrays where each element is a column, - * and each column is a list of cells. - */ - public getColumns(): Cell[][] { - return this.innerGetColumns(); - } - - /** - * Get an array of arrays of {@link Cell | Cells} for the board, that can be - * iterated by rows, that is, an array of rows. - * - * Note that with the current implementation this takes more - * time than accessing by column, but this might change in future - * implementations. - * - * @returns A {@link Cell} array of arrays where each element is a row, - * and each row is a list of cells. - */ - public getRows(): Cell[][] { - const rows: Cell[][] = []; - for (let row = 0; row < this.height; row++) { - rows.push(this.getRow(row)); - } - return rows; - } - - /** - * Fold a value over all the cells of a board. - * The order of the cells should not be assumed, as any order might be used. - * - * Note that with the current implementation, the cells are iterated by column - * starting from 0,0 to the one in the North-East corner. - * - * @param f A function to apply to each cells. The function expect to receive - * the current accumulated value at the moment, the currently iterated cell, - * and the row and column position for such a cell and returning a value of - * the same type as the accumulated value. - * @param initialValue The initial value to fold against. - */ - public foldCells(f: (previousValue: A, cell: Cell, row: number, column: number) => A, initialValue: A): A { - let currentValue = initialValue; - for (let c = 0; c < this.boardData.length; c++) { - const column = this.boardData[c]; - for (let r = 0; r < column.length; r++) { - const cell = column[r]; - currentValue = f(currentValue, cell, r, c); - } - } - return currentValue; - } - - /** - * Map all the cells of the board, generating a matrix of values. - * The order of the cells should not be assumed, as any order might be used. - * - * Note that with the current implementation, the cells are iterated by column - * starting from 0,0 to the one in the North-East corner. - * - * @param f A function to apply to each cells. The function expect to receive - * the currently iterated cell, and the row and column position for such a cell, - * and returning any value. - */ - public mapCells(f: (cell: Cell, row: number, column: number) => A): A[][] { - return this.foldCells( - (previousMatrix, cell, row, column) => { - previousMatrix[column][row] = f(cell, row, column); - return previousMatrix; - }, - matrix(this.width, this.height) - ); - } - - /** - * Filter all the cells of the board, generating an array of Cells. - * The order of the cells should not be assumed, as any order might be used. - * - * Note that with the current implementation, the cells are iterated by column - * starting from 0,0 to the one in the North-East corner. - * - * @param f A function to apply to each cells. The function expect to receive - * the currently iterated cell, and the row and column position for such a cell, - * that returns a boolean, returning `true` if the cell is ought to be in the result, - * and `false`otherwise. - */ - public filterCells(f: (cell: Cell, row: number, column: number) => boolean): Cell[] { - return this.foldCells((previousList: Cell[], cell: Cell, row: number, column: number) => { - if (f(cell, row, column)) { - previousList.push(cell); - } - return previousList; - }, []); - } - - /** - * Execute a function over each of the cells of the board. - * The order of the cells should not be assumed, as any order might be used. - * - * Note that with the current implementation, the cells are iterated by column - * starting from 0,0 to the one in the North-East corner. - * - * @param f A function to apply to each cells. The function expect to receive - * the currently iterated cell, and the row and column position for such a cell. - */ - public foreachCells(f: (cell: Cell, row: number, column: number) => void): void { - this.foldCells((_, cell, row, column) => { - f(cell, row, column); - return undefined; - }, undefined); - } - - /* ************* Clean Board ************** */ - - /** - * Clean the board contents, leaving all cells empty. - */ - public clean(): void { - this.foreachCells((cell) => cell.empty()); - } - - /* ************* Modifying Head ************** */ - - /** - * Change the head coordinate, by moving the head to the adjacent cell - * in the given direction. Thus given a direction `dir`, and considering - * that the head is at `[x, y]` coordinate, the new head location is calculated as: - * * for `dir === Direction.North` the new head is at `[x, y+1]`. - * * for `dir === Direction.South` the new head is at `[x, y-1]`. - * * for `dir === Direction.East` the new head is at `[x+1, y]`. - * * for `dir === Direction.West` the new head is at `[x-1, y]`. - * - * @throws {@link LocationFallsOutsideBoard} with the attempt as `Move` - * if moving the head to the given direction makes it fall - * outside the board. - * - * @param dir The direction in which to move the head to. - */ - public moveHeadTo(dir: Direction): void { - const [deltaX, deltaY] = this.innerGetDeltaByDirection(dir, 1); - this.innerSetHeadLocation([this.headX + deltaX, this.headY + deltaY], 'Move'); - } - - /** - * Change the head coordinate location to the coordinate at the edge at - * given direction. Thus given a direction `dir`, and considering that - * the head is at `[x, y]` coordinate, the new head coordinate is - * calculated as: - * * for `dir === Direction.North` the new head is at `[x, height-1]`. - * * for `dir === Direction.South` the new head is at `[x, 0]`. - * * for `dir === Direction.East` the new head is at `[width-1, y]`. - * * for `dir === Direction.West` the new head is at `[0, y]`. - * - * @param dir The direction in which to move the head to. - */ - public moveHeadToEdgeAt(dir: Direction): void { - const [deltaX, deltaY] = this.innerGetDeltaByDirection(dir); - this.innerSetHeadLocation([this.headX + deltaX, this.headY + deltaY], 'MoveToEdge'); - } - - /* ************* Modifying Size ************** */ - - /** - * Change the size of the board to the given width and height. - * If `fromOriginCorner` is `true`, and cells are being added, they are - * added to the West and/or South (depending on the new size value), if - * `false` they are added to the East and/or North. The same happens when removing, - * that is, when the new size is lower that the current one, for any of width or - * height. - * Note that changing the size of the board by setting the {@link width} or {@link height} - * attributes act as using this same method with `fromOriginCorner` set to `false`. - * Thus, this methods provides a more generic way of modifying the size, allowing to - * add columns or rows at the beginning of the board instead of the end. - * - * Note that if the given size is smaller than the current size, the location - * of the head is adjusted in any case that the location falls outside the board - * given the new size. - * - * @throws {@link InvalidBoardDescription} if any of the given - * `width` or `height` are lower than or equal to zero. - * - * @param width The new width of the board. - * @param height The new height of the board. - * @param fromOriginCorner Wether to change the size considering the - * change being performed from the origin cell instead of the North-East corner. - */ - public changeSizeTo(width: number, height: number, fromOriginCorner: boolean = false): void { - this.innerChangeSize(width, height, fromOriginCorner, 'Resize'); - } - - /** - * Add a new column to the board at the given direction. - * Note that this is equivalent of adding one column - * by calling {@link addColumns} with argument 1 for the same direction - * as given. - * - * If no direction is given, the column is added to the East. - * - * @param dir The direction where to add the new column. - */ - public addColumn(dir: Direction = Direction.East): void { - or(expect(dir).toBe(Direction.East), expect(dir).toBe(Direction.West)).orThrow( - new TypeError('The direction to addColumn should be East or West') - ); - this.addColumns(1, dir); - } - - /** - * Add a given amount of new columns to the board at the given direction. - * Note that, this is equivalent as increasing the size of the board with - * {@link changeSizeTo} where the new width corresponds to the current width - * plus the number of new columns. If the given direction is `Direction.East`, - * then the new columns are added at the beginning of the board (that is, - * using `changeSizeTo` with `fromOriginCell` as `true`). - * - * If no direction is given, the columns are added to the East. - * - * @param amount The number of columns to add. - * @param dir The direction where to add the new column. - */ - public addColumns(amount: number, dir: Direction = Direction.East): void { - or(expect(dir).toBe(Direction.East), expect(dir).toBe(Direction.West)).orThrow( - new TypeError('The direction to addColumns should be East or West') - ); - this.innerChangeSize(this.width + amount, this.height, dir === Direction.West, 'AddColumns'); - } - - /** - * Remove a column to the board at the given direction. - * Note that this is equivalent to removing one column - * by calling {@link removeColumns} with argument 1 for the same direction - * as given. - * - * If no direction is given, the column is removed from the East. - * - * @param dir The direction where to remove the new column. - */ - public removeColumn(dir: Direction = Direction.East): void { - or(expect(dir).toBe(Direction.East), expect(dir).toBe(Direction.West)).orThrow( - new TypeError('The direction to removeColumn should be East or West') - ); - this.removeColumns(1, dir); - } - - /** - * Remove given number of columns from the board at the given direction. - * Note that, this is equivalent as decreasing the size of the board with - * {@link changeSizeTo} where the new width corresponds to the current width - * minus the number of columns to remove. If the given direction is - * `Direction.East`, then the new columns are removed from the beginning of - * the board (that is, using `changeSizeTo` with `fromOriginCell` as `true`). - * - * If no direction is given, the columns are removed from the East. - * - * @param amount The amount of columns to remove. - * @param dir The direction where to remove the new column. - */ - public removeColumns(amount: number, dir: Direction = Direction.East): void { - or(expect(dir).toBe(Direction.East), expect(dir).toBe(Direction.West)).orThrow( - new TypeError('The direction to removeColumns should be East or West') - ); - this.innerChangeSize(this.width - amount, this.height, dir === Direction.West, 'RemoveColumn'); - } - - /** - * Add a new row to the board at the given direction. - * Note that this is equivalent of adding one row - * by calling {@link addRows} with argument 1 for the same direction - * as given. - * - * If no direction is given, the row is added to the North. - * - * @param dir The direction where to add the new column. - */ - public addRow(dir: Direction = Direction.North): void { - or(expect(dir).toBe(Direction.North), expect(dir).toBe(Direction.South)).orThrow( - new TypeError('The direction to addRow should be North or South') - ); - this.addRows(1, dir); - } - - /** - * Add a given amount of new rows to the board at the given direction. - * Note that, this is equivalent as increasing the size of the board with - * {@link changeSizeTo} where the new height corresponds to the current height - * plus the number of new rows. If the given direction is `Direction.South`, - * then the new columns are added at the beginning of the board (that is, - * using `changeSizeTo` with `fromOriginCell` as `true`). - * - * If no direction is given, the rows are added to the North. - * - * @param amount The number of rows to add. - * @param dir The direction where to add the new row. - */ - public addRows(amount: number, dir: Direction = Direction.North): void { - or(expect(dir).toBe(Direction.North), expect(dir).toBe(Direction.South)).orThrow( - new TypeError('The direction to addRows should be North or South') - ); - this.innerChangeSize(this.width, this.height + amount, dir === Direction.South, 'AddRows'); - } - - /** - * Remove a row to the board at the given direction. - * Note that this is equivalent to removing one row - * by calling {@link removeRows} with argument 1 for the same direction - * as given. - * - * If no direction is given, the row is removed from the North. - * - * @param dir The direction where to remove the new row. - */ - public removeRow(dir: Direction = Direction.North): void { - or(expect(dir).toBe(Direction.North), expect(dir).toBe(Direction.South)).orThrow( - new TypeError('The direction to removeRow should be North or South') - ); - this.removeRows(1, dir); - } - - /** - * Remove given number of rows from the board at the given direction. - * Note that, this is equivalent as decreasing the size of the board with - * {@link changeSizeTo} where the new height corresponds to the current height - * minus the number of rows to remove. If the given direction is - * `Direction.South`, then the new columns are removed from the beginning of - * the board (that is, using `changeSizeTo` with `fromOriginCell` as `true`). - * - * If no direction is given, the rows are removed from the North. - * - * @param amount The amount of rows to remove. - * @param dir The direction where to remove the new row. - */ - public removeRows(amount: number, dir: Direction = Direction.North): void { - or(expect(dir).toBe(Direction.North), expect(dir).toBe(Direction.South)).orThrow( - new TypeError('The direction to removeRows should be North or South') - ); - this.innerChangeSize(this.width, this.height - amount, dir === Direction.South, 'RemoveRow'); - } - - /** - * Retrieve a string representation of the board as a pretty print. - * Useful for debugging purposes mostly. - * - * Note that this only pretty prints boards where - * there are less than ten stones in each cell. Other board - * may print incorrectly. - */ - /* istanbul ignore next */ - public toString(): string { - let board = ''; - const cellString = (cell: Cell, line: number): string => { - const sep = cell.isHeadLocation() ? '|' : '¦'; - return line === 0 - ? `${sep} ${cell.getStonesOf(Color.Blue)} B ${cell.getStonesOf(Color.Black)} K ${sep}` - : `${sep} ${cell.getStonesOf(Color.Red)} R ${cell.getStonesOf(Color.Green)} G ${sep}`; - }; - - for (let r = this.height - 1; r >= 0; r--) { - board += ' '; - for (let c = 0; c < this.width; c++) { - board += - this.boardData[c][r].isHeadLocation() || this.boardData[c][r + 1]?.isHeadLocation() - ? '-----------' - : '- - - - - -'; - } - board += '\n '; - for (let c = 0; c < this.width; c++) { - board += cellString(this.boardData[c][r], 0); - } - board += `\n${r} `; - for (let c = 0; c < this.width; c++) { - board += cellString(this.boardData[c][r], 1); - } - board += '\n'; - } - board += ' '; - for (let c = 0; c < this.width; c++) { - board += this.boardData[c][0].isHeadLocation() ? '-----------' : '- - - - - -'; - } - board += '\n '; - for (let c = 0; c < this.width; c++) { - board += ` ${c} `; - } - return board; - } - - /* ************* Private ************** */ - - /** - * Retrieve all the columns from the board. This is another way of - * saying, retrieve all the cells as a 2 dimensional matrix, where - * the outer element corresponds to a column, and the inner one to - * the cells in that column. - * - * @returns An array of arrays of cells with the board information - */ - private innerGetColumns(): Cell[][] { - return this.boardData; - } - - /** - * Retrieve all the cells in a given column. - * - * @throws {@link InvalidCellReading} with the attempt as `ReadColumn` - * if the given column number is lower than zero or greater - * or equal to the board width. - * - * @returns An array of cells with the cells in the given column. - */ - private innerGetColumn(column: number): Cell[] { - expect(column) - .toBeGreaterThanOrEqual(0) - .toBeLowerThan(this.width) - .orThrow(new InvalidCellReading('ReadColumn', [column, 0])); - return this.boardData[column]; - } - - /** - * Retrieve all the cells in a given row. - * - * @throws {@link InvalidCellReading} with the attempt as `ReadRow` - * if the given row number is lower than zero or greater - * or equal to the board height. - * - * @returns An array of cells with the cells in the given row. - */ - private innerGetRow(row: number): Cell[] { - expect(row) - .toBeGreaterThanOrEqual(0) - .toBeLowerThan(this.height) - .orThrow(new InvalidCellReading('ReadRow', [0, row])); - const rowData: Cell[] = []; - for (let c = 0; c < this.width; c++) { - rowData.push(this.boardData[c][row]); - } - return rowData; - } - - /** - * Retrieve the cells in a given location. - * - * @throws {@link InvalidCellReading} with the attempt as `ReadCell` - * if the location given as `[x, y]` has `x` that is lower - * than zero or greater or equal to the board width, or `y` - * that is lower than zero or greater or equal to the board height. - * - * @returns A cell for the given location - */ - private innerGetCell(x: number, y: number): Cell { - expect(x) - .toBeGreaterThanOrEqual(0) - .toBeLowerThan(this.width) - .orThrow(new InvalidCellReading('ReadCell', [x, y])); - expect(y) - .toBeGreaterThanOrEqual(0) - .toBeLowerThan(this.height) - .orThrow(new InvalidCellReading('ReadCell', [x, y])); - return this.boardData[x][y]; - } - - /** - * Change the head location to the specific cell location. - * - * @throws {@link LocationFallsOutsideBoard} with the attempt as `performedAction` - * if the given cell location as `[x, y]` has `x < 0 || x > width` - * or `y < 0 || y > width`. - * - * @param location The location to move the head to. - */ - private innerSetHeadLocation(location: CellLocation, performedAction: LocationChangeActionAttempt): void { - and( - expect(location[0]).toBeGreaterThanOrEqual(0).toBeLowerThan(this.width), - expect(location[1]).toBeGreaterThanOrEqual(0).toBeLowerThan(this.height) - ).orThrow(new LocationFallsOutsideBoard(performedAction, location, this.head)); - const oldHeadX = this.headXLocation; - const oldHeadY = this.headYLocation; - this.headXLocation = location[0]; - this.headYLocation = location[1]; - this.emit(Board.onHeadMoved, [this.headXLocation, this.headYLocation], [oldHeadX, oldHeadY]); - } - - /** - * Change the size of the board to the given width and height. - * - * @warning Changing size is an expensive operation with the current implementation, - * and probably with any other representation for the current API. All things - * considered, changing the size of the Board is not an operation that is - * performed multiple time, nor expected immediacy, so the penalties - * in performance are acceptable. - * - * @throws {@link InvalidBoardDescription} if any of the given - * `width` or `height` are lower tor equal than zero. - * - * @param width The new width of the board. - * @param height The new height of the board. - * @param fromOriginCorner Wether to change the size considering the - * change being performed from the origin cell instead of the North-East corner. - */ - private innerChangeSize( - width: number, - height: number, - fromOriginCorner: boolean = false, - attempt: InvalidSizeChangeAttempt - ): void { - and(expect(height).toBeGreaterThan(0), expect(width).toBeGreaterThan(0)).orThrow( - new InvalidSizeChange(attempt, this.boardWidth, this.boardHeight, width, height) - ); - - // Gather state of change - const oldHeight = this.boardHeight; - const oldWidth = this.boardWidth; - const oldHeadX = this.headXLocation; - const oldHeadY = this.headYLocation; - - this.boardHeight = height; - this.boardWidth = width; - - // Firstly fix columns - if (this.boardWidth >= oldWidth) { - // Add columns or leave as it - for (let k = 0; k < this.boardWidth - oldWidth; k++) { - if (fromOriginCorner) { - this.boardData.unshift([]); - } else { - this.boardData.push([]); - } - } - } else { - // Remove columns - for (let k = 0; k < oldWidth - this.boardWidth; k++) { - if (fromOriginCorner) { - this.boardData.shift(); - } else { - this.boardData.pop(); - } - } - } - // Then fix cells in columns - for (let c = 0; c < this.boardWidth; c++) { - const columnInitialLength = this.boardData[c].length; - if (this.boardHeight >= oldHeight) { - // Add cell or leave as it - for (let k = 0; k < this.boardHeight - columnInitialLength; k++) { - if (fromOriginCorner) { - // Note that Cell location does not matter at this point, so 0,0 is OK. - this.boardData[c].unshift(new Cell(this, { x: 0, y: 0 })); - } else { - this.boardData[c].push(new Cell(this, { x: 0, y: 0 })); - } - } - } else { - // Remove cells - for (let k = 0; k < columnInitialLength - this.boardHeight; k++) { - if (fromOriginCorner) { - this.boardData[c].shift(); - } else { - this.boardData[c].pop(); - } - } - } - } - // Then fix all cell locations - // Note that our previously added 0,0 cells get fixed at this point. - this.foreachCells((cell: Cell, row: number, column: number) => { - cell.x = column; - cell.y = row; - }); - - if (fromOriginCorner) { - /* istanbul ignore next */ - if (this.boardWidth >= oldWidth) { - // cells added first - const newHeadX = oldHeadX + (this.boardWidth - oldWidth); - this.headXLocation = newHeadX < this.boardWidth ? newHeadX : this.boardWidth - 1; - } - if (this.boardWidth < oldWidth) { - // cells were removed first - const newHeadX = oldHeadX - (oldWidth - this.boardWidth); - this.headXLocation = newHeadX >= 0 ? newHeadX : 0; - } - /* istanbul ignore next */ - if (this.boardHeight >= oldHeight) { - // cells added first - const newHeadY = oldHeadY + (this.boardHeight - oldHeight); - this.headYLocation = newHeadY < this.boardHeight ? newHeadY : this.boardHeight - 1; - } - if (this.boardHeight < oldHeight) { - // cells were removed first - const newHeadY = oldHeadY - (oldHeight - this.boardHeight); - this.headYLocation = newHeadY >= 0 ? newHeadY : 0; - } - } else { - // The added case should not alter head - if (this.boardWidth < oldWidth) { - // cells were removed last - this.headXLocation = oldHeadX < this.boardWidth ? oldHeadX : this.boardWidth - 1; - } - if (this.boardHeight < oldHeight) { - // cells were removed last - this.headYLocation = oldHeadY < this.boardHeight ? oldHeadY : this.boardHeight - 1; - } - } - this.emit( - Board.onSizeChanged, - { width: this.boardWidth, height: this.boardHeight }, - [this.headXLocation, this.headYLocation], - fromOriginCorner, - { width: oldWidth, height: oldHeight }, - [oldHeadX, oldHeadY] - ); - } - - /** - * Retrieve a two element array with the delta value to move the head to given a - * specific direction. The delta is considered using the `deltaValue` argument, - * thus increasing or decreasing by that amount depending on the given direction. - * If no deltaValue is given, the delta becomes the maximum possible value for the - * given board, that is, the delta between the head location and the border in the - * given direction. - * @param direction The direction to use to calculate the delta value. - * @param deltaValue The delta number to use when calculating, or undefined if - * the maximum available is ought to be used. - */ - private innerGetDeltaByDirection(direction: Direction, deltaValue?: number): [number, number] { - switch (direction) { - case Direction.East: - return [deltaValue ?? this.width - 1 - this.headXLocation, 0]; - case Direction.West: - return [deltaValue ? -deltaValue : -this.headXLocation, 0]; - case Direction.North: - return [0, deltaValue ?? this.height - 1 - this.headYLocation]; - case Direction.South: - return [0, deltaValue ? -deltaValue : -this.headYLocation]; - /* istanbul ignore next */ - default: - return [0, 0]; - } - } -} diff --git a/src/GobstonesLang/BoardDefinition.ts b/src/GobstonesLang/BoardDefinition.ts deleted file mode 100644 index 3ff052d..0000000 --- a/src/GobstonesLang/BoardDefinition.ts +++ /dev/null @@ -1,113 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) National University of Quilmes 2018-2024 - * Gobstones (TM) is a trademark of the National University of Quilmes. - * - * This program is free software distributed under the terms of the - * GNU Affero General Public License version 3. - * Additional terms added in compliance to section 7 of such license apply. - * - * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. - * ***************************************************************************** - */ -/** - * @module API.GobstonesLang - * @author Alan Rodas Bonjour - */ - -/** - * The definition of a Board as expected by the current Gobstones Interpreter. - * This definition make heavy assumptions as for the origin of the board and - * it's internal structure. Although this definition is left for reference, - * the {@link Board} class (which implements this interface) should be considered - * the new standard of what a board is, and should be used for every new development. - * - * @group Internal: Types - * @internal - */ -export interface BoardDefinition { - /** The board format. Always: GBB/1.0 as it's derived from the GBB format */ - format: string; - /** The width of the board */ - width: number; - /** The height of the board */ - height: number; - /** - * The head location as [x, y] position, where the position should comply to: - * ```typescript - * 0 <= x < this.width && 0 <= y < this.height - * ``` - */ - head: CellLocation; - /** - * Array of `this.width` elements, each of which is an array of `this.height` elements, - * each of which is a cell, of the form `{"a": na, "n": nn, "r": nr, "v": nv}`, - * in such a way that: - * * `this.board[x][y].a` = number of blue stones at (x, y) - * * `this.board[x][y].n` = number of black stones at (x, y) - * * `this.board[x][y].r` = number of red stones at (x, y) - * * `this.board[x][y].v` = number of green stones at (x, y) - * - * And it's assured that each element `this.board[x][y]` exists - * for `0 <= x < this.width && 0 <= y < this.height`. - */ - board: BoardInfo; -} - -/** - * This type represents the Board cell information where given a board satisfies - * `board.width` elements, each of which is an array of `board.height` elements, - * each of which is a cell, of the form `{"a": na, "n": nn, "r": nr, "v": nv}`, - * in such a way that: - * * `board.board[x][y].a` = number of blue stones at (x, y) - * * `board.board[x][y].n` = number of black stones at (x, y) - * * `board.board[x][y].r` = number of red stones at (x, y) - * * `board.board[x][y].v` = number of green stones at (x, y) - * - * And it's assured that each element `this.board[x][y]` exists - * for `0 <= x < board.width && 0 <= y < board.height`. - * - * @group Internal: Types - * @internal - */ -export type BoardInfo = CellInfo[][]; - -/** - * A cell location is just a two elements array (a pair) - * in the form of `[x, y]` where `x` is the column that - * the head is in and `y` is the row that the head is in. - * - * @group Internal: Types - * @internal - */ -export type CellLocation = [number, number]; - -/** - * The information of a cell such that: - * * `a` = number of blue stones - * * `n` = number of black stones - * * `r` = number of red stones - * * `v` = number of green stones - * - * @group Internal: Type - * @internal - */ -export interface CellInfo { - a: number; - n: number; - r: number; - v: number; -} - -/** - * A cell data definition consists of the location of a cell, and - * the amount of stones for any color (if non zero, undefined may be used - * is those attributes if zero is ought to be used for such color). - * - * @group Internal: Type - * @internal - */ -export interface CellDataDefinition extends Partial { - x: number; - y: number; -} diff --git a/src/GobstonesLang/BoardErrors.ts b/src/GobstonesLang/BoardErrors.ts deleted file mode 100644 index 3ef5701..0000000 --- a/src/GobstonesLang/BoardErrors.ts +++ /dev/null @@ -1,214 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) National University of Quilmes 2018-2024 - * Gobstones (TM) is a trademark of the National University of Quilmes. - * - * This program is free software distributed under the terms of the - * GNU Affero General Public License version 3. - * Additional terms added in compliance to section 7 of such license apply. - * - * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. - * ***************************************************************************** - */ -/** - * @module API.GobstonesLang - * @author Alan Rodas Bonjour - */ - -import { CellInfo, CellLocation } from './BoardDefinition'; - -/** - * The operation attempted to be performed when a {@link InvalidCellReading} error ocurred. - * - * @group API: Options - * @internal - */ -export type CellReadingActionAttempt = 'ReadCell' | 'ReadColumn' | 'ReadRow'; -/** - * The operation attempted to be performed when a {@link LocationFallsOutsideBoard} error ocurred. - * - * @group API: Options - * @internal - */ -export type LocationChangeActionAttempt = 'Move' | 'SetLocation' | 'MoveToEdge'; -/** - * The operation attempted to be performed when a {@link InvalidSizeChange} error ocurred. - * - * @group API: Options - * @internal - */ -export type InvalidSizeChangeAttempt = 'Resize' | 'RemoveRow' | 'RemoveColumn' | 'AddRows' | 'AddColumns'; -/** - * The operation attempted to be performed when a {@link InvalidStonesAmount} error ocurred. - * - * @group API: Options - * @internal - */ -export type StonesChangeActionAttempt = 'AddStones' | 'RemoveStones' | 'SetStones'; - -/** - * The base class of the error hierarchy that is thrown when - * an invalid operation is performed in the {@link Board} - * class and it's associated {@link Cell}. - * - * @group API: Exceptions - */ -export class BoardError extends Error { - /** A boolean that specifies the instance as an error. Always `true`. */ - public readonly isError: boolean; - - public constructor(name: string, message: string) { - super(message); - this.name = name; - this.isError = true; - Object.setPrototypeOf(this, BoardError.prototype); - } -} - -/** - * This error is thrown when attempting to create a board - * with invalid data. - * - * @group API: Exceptions - */ -export class InvalidBoardDescription extends BoardError { - /** The height attempted to create the board with */ - private height: number; - /** The width attempted to create the board with */ - private width: number; - /** The cell location attempted to create the board with */ - private cellLocation: CellLocation; - - public constructor(height: number, width: number, cellLocation: CellLocation) { - super( - 'InvalidBoardDescription', - `The values used to create the board are invalid. ` + - ` height: ${height}, width: ${width}, cell location: ${cellLocation}` - ); - this.height = height; - this.width = width; - this.cellLocation = cellLocation; - Object.setPrototypeOf(this, InvalidBoardDescription.prototype); - } -} - -/** - * This error is thrown when attempting to read a cell, a - * column or a row but an invalid location is given. - * - * @group API: Exceptions - */ -export class InvalidCellReading extends BoardError { - /** The action attempted to be performed */ - public readonly attempt: CellReadingActionAttempt; - /** The coordinate the head was attempted to set into */ - public readonly failingCoordinate: CellLocation; - - public constructor(attempt: CellReadingActionAttempt, failingCoordinate: CellLocation) { - super( - 'InvalidCellReading', - `The attempt of ${attempt} failed for coordinate [${failingCoordinate[0]}, ${failingCoordinate[1]}].` - ); - this.attempt = attempt; - this.failingCoordinate = failingCoordinate; - Object.setPrototypeOf(this, InvalidCellReading.prototype); - } -} - -/** - * This error is thrown when attempting to move the head, but an - * invalid location is given. - * - * @group API: Exceptions - */ -export class LocationFallsOutsideBoard extends BoardError { - /** The action attempted to be performed */ - public readonly attempt: LocationChangeActionAttempt; - /** The coordinate the head was attempted to set into */ - public readonly failingCoordinate: CellLocation; - /** The previous coordinate the head was in */ - public readonly previousCoordinate: CellLocation; - - public constructor( - attempt: LocationChangeActionAttempt, - failingCoordinate: CellLocation, - previousCoordinate: CellLocation - ) { - super( - 'LocationFallsOutsideBoard', - `The attempt of ${attempt} from [${previousCoordinate[0]}, ` + - `${previousCoordinate[1]}] falls outside the board on ` + - `coordinate [${failingCoordinate[0]}, ${failingCoordinate[1]}].` - ); - this.attempt = attempt; - this.failingCoordinate = failingCoordinate; - this.previousCoordinate = previousCoordinate; - Object.setPrototypeOf(this, LocationFallsOutsideBoard.prototype); - } -} - -/** - * This error is thrown when attempting to change the size of the board, - * but an invalid size is given - * - * @group API: Exceptions - */ -export class InvalidSizeChange extends BoardError { - /** The action attempted to be performed */ - public readonly attempt: InvalidSizeChangeAttempt; - /** The previous width of the board */ - public readonly previousWidth: number; - /** The previous height of the board */ - public readonly previousHeight: number; - /** The new width of the board */ - public readonly newWidth: number; - /** The new height of the board */ - public readonly newHeight: number; - - public constructor( - attempt: InvalidSizeChangeAttempt, - previousWidth: number, - previousHeight: number, - newWidth: number, - newHeight: number - ) { - super( - 'InvalidSizeChange', - `The attempt of changing size by ${attempt} from width ${previousWidth}, ` + - `and height ${previousHeight} ends in an invalid board of width ` + - `${newWidth} and height ${newHeight}.` - ); - this.attempt = attempt; - this.previousWidth = previousWidth; - this.previousHeight = previousHeight; - this.newWidth = newWidth; - this.newHeight = newHeight; - Object.setPrototypeOf(this, InvalidSizeChange.prototype); - } -} - -/** - * This error is thrown when attempting to change the stones amount - * with an invalid amount of stone (negative amount). - * - * @group API: Exceptions - */ -export class InvalidStonesAmount extends BoardError { - /** The action attempted to be performed */ - public readonly attempt: StonesChangeActionAttempt; - /** The color of the stones attempted to change */ - public readonly color: string; - /** The number of stones that was attempted to set */ - public readonly amount: number; - /** The state the cell was previously in */ - public readonly previousCellState: CellInfo; - - public constructor(attempt: StonesChangeActionAttempt, color: string, amount: number, previousCellState: CellInfo) { - super('InvalidStonesAmount', `The attempt of ${attempt} failed for color ${color} given ${amount}.`); - this.attempt = attempt; - this.color = color; - this.amount = amount; - this.previousCellState = previousCellState; - Object.setPrototypeOf(this, InvalidStonesAmount.prototype); - } -} diff --git a/src/GobstonesLang/Cell.ts b/src/GobstonesLang/Cell.ts deleted file mode 100644 index 0be6b3c..0000000 --- a/src/GobstonesLang/Cell.ts +++ /dev/null @@ -1,709 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) National University of Quilmes 2018-2024 - * Gobstones (TM) is a trademark of the National University of Quilmes. - * - * This program is free software distributed under the terms of the - * GNU Affero General Public License version 3. - * Additional terms added in compliance to section 7 of such license apply. - * - * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. - * ***************************************************************************** - */ -/** - * @module API.GobstonesLang - * @author Alan Rodas Bonjour - */ - -import { Board } from './Board'; -import { CellDataDefinition, CellInfo, CellLocation } from './BoardDefinition'; -import { InvalidStonesAmount, StonesChangeActionAttempt } from './BoardErrors'; -import { Color } from './Color'; -import { Direction } from './Direction'; - -import { EventEmitter } from '../Events/EventEmitter'; -import { expect } from '../Expectations'; - -/** - * This object contains the default values for a {@link Board} and it's cells. - * When a specific value is not given, the defaults are used. - * - * @group Internal: Default Values - * @internal - */ -const Defaults = { - [Color.BLUE]: 0, - [Color.BLACK]: 0, - [Color.RED]: 0, - [Color.GREEN]: 0 -} as unknown as CellInfo; - -/** - * This type represents the function that acts as a callback of - * the {@link Cell.onStonesChanged} event. Such an event is thrown by - * an instance of a cell whenever an operation is performed such - * that the cell changes the amount of cells. - * - * This function receives 3 arguments: - * * the location of the cell as x, y coordinates. - * * The amount of the stones in the cell, after the change ocurred. - * * The previous amount of stones the cell had. - * - * @see {@link Cell.onStonesChanged} for more information. - * - * @group API: Events - * @event - */ -export type OnCellStonesChanged = ( - cellLocation: { x: number; y: number }, - stones: { - [Color.BLUE]: number; - [Color.BLACK]: number; - [Color.RED]: number; - [Color.GREEN]: number; - }, - previousStones: { - [Color.BLUE]: number; - [Color.BLACK]: number; - [Color.RED]: number; - [Color.GREEN]: number; - } -) => void; - -/** - * This interface contains the signature of the - * events that a Cell can throw. - * - * @group Internal: Events - * @internal - */ -export interface CellEvents { - [Cell.onStonesChanged]: OnCellStonesChanged; -} - -/** - * A cell - * - * @group API: Main - */ -export class Cell extends EventEmitter implements CellInfo { - /** - * This event is thrown whenever an action that alters the stones in - * the cell is performed. Listeners of this action are expected to conform to - * {@link OnCellStonesChanged}. - * - * The actions that trigger this callback include: - * * Setting the {@link a} attribute of an instance. - * * Setting the {@link n} attribute of an instance. - * * Setting the {@link r} attribute of an instance. - * * Setting the {@link v} attribute of an instance. - * * Calling {@link setStonesOf} on an instance with any color and value. - * * Calling {@link addStones} on an instance with any color and value. - * * Calling {@link removeStones} on an instance with any color and value. - * * Calling {@link empty} on an instance. - * - * @event - */ - public static readonly onStonesChanged: unique symbol = Symbol('onStonesChanged'); - - /** - * A reference to the board. A cell cannot exist independently from a Board, - * they both know each other and work together to achieve the different actions. - */ - private board: Board; - - /** - * The amount of blue stones of this cell. - */ - private blueStones: number; - /** - * The amount of black stones of this cell. - */ - private blackStones: number; - /** - * The amount of red stones of this cell. - */ - private redStones: number; - /** - * The amount of green stones of this cell. - */ - private greenStones: number; - - /** - * The X coordinate of the location of this cell within the board. - */ - private locationX: number; - /** - * The Y coordinate of the location of this cell within the board. - */ - private locationY: number; - - /** - * Create a new instance of a cell with the given cell information. - * A cell should be given the {@link Board} it belongs to, as a cell cannot exist - * without a board. Additionally, at least the [x, y] coordinate of the cell - * within the board should be passed as the cell's information, and optionally, - * the amount of stones of the different colors in case they are not zero. - * - * When creating a cell and passing color information, you should prefer using - * the Color enum as a key, instead of the enum value as a string. - * @example - * ``` - * new Cell(board, { x: 3, y: 2, [Color.Red]: 5, [Color.Green]: 1 }); - * ``` - * This allows for abstracting away the inner representation of the enum, and allow - * for changes in the future without impacting your code. - * - * @param board The board this cell belongs to. - * @param cellInfo The information for this cell, at least the X and Y coordinates, - * and optionally, amount of stones for each color. - */ - public constructor(board: Board, cellInfo: CellDataDefinition) { - super(); - this.board = board; - - this.locationX = cellInfo.x; - this.locationY = cellInfo.y; - - this.blueStones = cellInfo[Color.BLUE] ?? Defaults[Color.BLUE]; - this.blackStones = cellInfo[Color.BLACK] ?? Defaults[Color.BLACK]; - this.redStones = cellInfo[Color.RED] ?? Defaults[Color.RED]; - this.greenStones = cellInfo[Color.GREEN] ?? Defaults[Color.GREEN]; - } - - /* ************* Accessors ************** */ - - /** - * Get or set the X location of this cell within the board. - * - * @warning The getter can be used always. The setter on the other hand - * although exported, should not be used, as it's usage is reserved - * for internal actions of the board only (It's used exclusively on - * recalculating coordinates when the board resizes). Avoid the - * setter at all cost. - * - * @param value The new value for the X coordinate. - * - * @returns This cells X coordinate within the board - */ - public get x(): number { - return this.locationX; - } - public set x(value: number) { - this.locationX = value; - } - - /** - * Get or set the Y location of this cell within the board. - * - * @warning The getter can be used always. The setter on the other hand - * although exported, should not be used, as it's usage is reserved - * for internal actions of the board only (It's used exclusively on - * recalculating coordinates when the board resizes). Avoid the - * setter at all cost. - * - * @param value The new value for the Y coordinate. - * - * @returns This cells Y coordinate within the board - */ - public get y(): number { - return this.locationY; - } - public set y(value: number) { - this.locationY = value; - } - - /** - * Get the amount of {@link Color.Blue | blue} stones of this cell. - * Or instead, set the amount of stones. - * - * @deprecated This is retain only for compatibility reasons. - * If you need to access the amount of stones of a color, - * use {@link getStonesOf} instead, passing the color as an argument. - * So the preferred method for blue stones should be - * ``` - * cell.getStonesOf(Color.Blue); - * ``` - * For setting the stones of a color, use {@link setStonesOf} instead, - * with the color and the new desired value. The preferred way for - * blue stones should be: - * ``` - * cell.setStonesOf(Color.Blue, amount); - * ``` - * - * @throws {@link InvalidStonesAmount} with the attempt set to `SetStones` - * if the new amount of stones is lower than zero. - * - * @param value The new amount of {@link Color.Blue | blue} stones - * - * @returns The number of stones of {@link Color.Blue}. - */ - public get a(): number { - return this.getStonesOf(Color.Blue); - } - /* istanbul ignore next */ - public set a(value: number) { - this.setStonesOf(Color.Blue, value); - } - - /** - * Get the amount of {@link Color.Black | black} stones of this cell. - * Or instead, set the amount of stones. - * - * @deprecated This is retain only for compatibility reasons. - * If you need to access the amount of stones of a color, - * use {@link getStonesOf} instead, passing the color as an argument. - * So the preferred method for black stones should be - * ``` - * cell.getStonesOf(Color.Black); - * ``` - * For setting the stones of a color, use {@link setStonesOf} instead, - * with the color and the new desired value. The preferred way for - * black stones should be: - * ``` - * cell.setStonesOf(Color.Black, amount); - * ``` - * - * @throws {@link InvalidStonesAmount} with the attempt set to `SetStones` - * if the new amount of stones is lower than zero. - * - * @param value The new amount of {@link Color.Black | black} stones - * - * @returns The number of stones of {@link Color.Black}. - */ - public get n(): number { - return this.getStonesOf(Color.Black); - } - /* istanbul ignore next */ - public set n(value: number) { - this.setStonesOf(Color.Black, value); - } - - /** - * Get the amount of {@link Color.Red | red} stones of this cell. - * Or instead, set the amount of stones. - * - * @deprecated This is retain only for compatibility reasons. - * If you need to access the amount of stones of a color, - * use {@link getStonesOf} instead, passing the color as an argument. - * So the preferred method for red stones should be - * ``` - * cell.getStonesOf(Color.Red); - * ``` - * For setting the stones of a color, use {@link setStonesOf} instead, - * with the color and the new desired value. The preferred way for - * red stones should be: - * ``` - * cell.setStonesOf(Color.Red, amount); - * ``` - * - * @throws {@link InvalidStonesAmount} with the attempt set to `SetStones` - * if the new amount of stones is lower than zero. - * - * @param value The new amount of {@link Color.Red | red} stones - * - * @returns The number of stones of {@link Color.Red}. - */ - public get r(): number { - return this.getStonesOf(Color.Red); - } - /* istanbul ignore next */ - public set r(value: number) { - this.setStonesOf(Color.Red, value); - } - - /** - * Get the amount of {@link Color.Green | green} stones of this cell. - * Or instead, set the amount of stones. - * - * @deprecated This is retain only for compatibility reasons. - * If you need to access the amount of stones of a color, - * use {@link getStonesOf} instead, passing the color as an argument. - * So the preferred method for green stones should be - * ``` - * cell.getStonesOf(Color.Green); - * ``` - * For setting the stones of a color, use {@link setStonesOf} instead, - * with the color and the new desired value. The preferred way for - * green stones should be: - * ``` - * cell.setStonesOf(Color.Green, amount); - * ``` - * - * @throws {@link InvalidStonesAmount} with the attempt set to `SetStones` - * if the new amount of stones is lower than zero. - * - * @param value The new amount of {@link Color.Green | green} stones - * - * @returns The number of stones of {@link Color.Green}. - */ - public get v(): number { - return this.getStonesOf(Color.Green); - } - /* istanbul ignore next */ - public set v(value: number) { - this.setStonesOf(Color.Green, value); - } - - /** - * Get the current X, Y coordinate of the cell within the board, - * as a two element array in the form `[x, y]`. - * - * @returns A two element array with the X and Y location of - * this cell within the board. - */ - public get location(): CellLocation { - return [this.x, this.y]; - } - - /* ************* Cloning ************** */ - - /** - * Clone this cell. Pass a board in order to set the - * associated board of the cloned cell to that element. - * - * @param cloneBoard The Board the cloned cell will be associated to. - * @returns A new {@link Cell} - */ - public clone(newBoard: Board): Cell { - return new Cell(newBoard, { - x: this.x, - y: this.y, - [Color.BLUE]: this.getStonesOf(Color.Blue), - [Color.BLACK]: this.getStonesOf(Color.Black), - [Color.RED]: this.getStonesOf(Color.Red), - [Color.GREEN]: this.getStonesOf(Color.Green) - }); - } - - /* ************* Managing & Querying Stones ************** */ - - /** - * Answer wether this cell contains stones of the given color. - * That is, if the amount of stones of the given color is greater - * than zero. - * - * @param color The color of the stones to check for existence. - * - * @returns `true` if the cell has stones of the given color, `false` otherwise. - */ - public hasStonesOf(color: Color): boolean { - return this.innerGetStones(color) > 0; - } - - /** - * Answer with the amount of stones of the given color in this cell. - * - * @param color The color of the stones to retrieve the amount. - * - * @returns The amount of stones of the given color. - */ - public getStonesOf(color: Color): number { - return this.innerGetStones(color); - } - - /** - * Set the amount of stones of the given color in this cell. - * - * @throws {@link InvalidStonesAmount} with the attempt set to `SetStones` - * if the new amount of stones is lower than zero. - * - * @param color The color of the stones to set the amount. - * @param amount The amount of stones to set. - */ - public setStonesOf(color: Color, amount: number): void { - this.innerSetStones(color, amount, 'SetStones'); - } - - /** - * Add a given amount of stones of a particular color to this cell. - * - * @throws {@link InvalidStonesAmount} with the attempt set to `AddStones` - * if the new amount of stones is lower or equal than zero. - * - * @param color The color of the stones to add to. - * @param amount The amount of stones to add. - */ - public addStones(color: Color, amount: number = 1): void { - expect(amount) - .toBeGreaterThan(0) - .orThrow(new InvalidStonesAmount('AddStones', color.toString(), amount, this)); - this.innerSetStones(color, this.getStonesOf(color) + amount, 'AddStones'); - } - - /** - * Remove a given amount of stones of a particular color to this cell. - * - * @throws {@link InvalidStonesAmount} with the attempt set to `AddStones` - * if the new amount of stones is lower or equal than zero, or if the - * amount of stones to remove is greater than the amount currently in - * the cell. - * - * @param color The color of the stones to remove to. - * @param amount The amount of stones to remove. - */ - public removeStones(color: Color, amount: number = 1): void { - expect(amount) - .toBeGreaterThan(0) - .orThrow(new InvalidStonesAmount('RemoveStones', color.toString(), amount, this)); - this.innerSetStones(color, this.getStonesOf(color) - amount, 'RemoveStones'); - } - - /** - * Answer wether this cell is empty, that is, it has no stones of any color. - * In other words, the amount of stones of any color is zero. - * - * @returns `true` if the cell is empty, `false` otherwise. - */ - public isEmpty(): boolean { - let hasNone = true; - Color.foreach((color) => { - if (this.hasStonesOf(color)) { - hasNone = false; - } - }); - return hasNone; - } - - /** - * Answer wether this cell has any stones, that is, it has any stones of any color. - * In other words, the amount of stones of any color is other than zero. - * - * @returns `true` if the cell has stones, `false` otherwise. - */ - public hasStones(): boolean { - return !this.isEmpty(); - } - - /** - * Answer the amount of stones of this cell. That is the total amount of - * stones, adding the number of stones of every color. - * - * @returns The total number of stones of the cell. - */ - public getStonesAmount(): number { - let total = 0; - Color.foreach((color) => { - total += this.getStonesOf(color); - }); - return total; - } - - /** - * Empty the cell, leaving the stone amount for each color to zero. - */ - public empty(): void { - this.blueStones = 0; - this.blackStones = 0; - this.redStones = 0; - this.greenStones = 0; - } - - /* ************* Querying Location ************** */ - - /** - * Answer wether this cell is the cell below the head's location - * within the board. - * - * @returns `true` if the cell is below the head's location, `false` otherwise. - */ - public isHeadLocation(): boolean { - return this.board.headX === this.x && this.board.headY === this.y; - } - - /** - * Answer wether this cell is at the border to the given direction - * at the board. - * - * @returns `true` if the cell is at the border to the given direction, `false` otherwise. - */ - public isAtBorderAt(direction: Direction): boolean { - switch (direction) { - case Direction.North: - return this.y === this.board.height - 1; - case Direction.South: - return this.y === 0; - case Direction.East: - return this.x === this.board.width - 1; - case Direction.West: - return this.x === 0; - /* istanbul ignore next */ - default: - return false; - } - } - - /** - * Return the neighbor of this cell to the given direction, that is, another cell. - * If the cell is at the border at the given direction, then `undefined` is returned. - * - * @param direction The direction to which to get the neighbor from. - * - * @returns The neighbor cell at the given direction, or `undefined` - * if the neighbor does not exist - */ - public neighborTo(direction: Direction): Cell | undefined { - const [deltaX, deltaY] = this.innerGetDeltaByDirection(direction, 1); - - if (this.isAtBorderAt(direction)) return undefined; - return this.board.getCell(this.x + deltaX, this.y + deltaY); - } - - /** - * Return the neighbor of this cell to the given diagonal, that is, another cell. - * If the cell is at the border at the given diagonal, then `undefined` is returned. - * - * @param vertical The vertical direction to which to get the neighbor from. - * @param horizontal The horizontal direction to which to get the neighbor from. - * - * @returns The neighbor cell at the given diagonal, or `undefined` - * if the neighbor does not exist. - */ - public neighborDiagonalTo(vertical: Direction, horizontal: Direction): Cell | undefined { - if (this.isAtBorderAt(vertical) || this.isAtBorderAt(horizontal)) return undefined; - - const verticalDelta = this.innerGetDeltaByDirection(vertical, 1)[1]; - const horizontalDelta = this.innerGetDeltaByDirection(horizontal, 1)[0]; - - return this.board.getCell(this.x + horizontalDelta, this.y + verticalDelta); - } - - /** - * Retrieve all the neighbors of the current cell. Only existing neighbors are - * returned. The returned neighbors may be 'orthogonal' (the default), where - * only orthogonal neighbors are returned, 'diagonal', where only diagonal - * diagonal neighbors are returned, or 'both' where both orthogonal and diagonal - * neighbors are returned. - * - * @param location One of 'orthogonal', 'diagonal' or 'both'. - * - * @returns A list of neighbors of the current cell. - */ - public neighbors(location: 'orthogonal' | 'diagonal' | 'both' = 'orthogonal'): Cell[] { - const neighbors: Cell[] = []; - Direction.foreach((dir) => { - const nextDir = Direction.next(dir); - if (!this.isAtBorderAt(dir) && location !== 'diagonal') { - neighbors.push(this.neighborTo(dir) as Cell); - } - if (!this.isAtBorderAt(dir) && !this.isAtBorderAt(nextDir) && location !== 'orthogonal') { - neighbors.push( - this.neighborDiagonalTo( - Direction.isVertical(dir) ? dir : nextDir, - Direction.isVertical(dir) ? nextDir : dir - ) as Cell - ); - } - }); - return neighbors; - } - - /** - * Retrieve a string representation of the cell, mainly for debugging purposes. - */ - /* istanbul ignore next */ - public toString(): string { - return ( - `x: ${this.x} y: ${this.y} > ` + - `${this.getStonesOf(Color.Blue)} B ${this.getStonesOf(Color.Black)} K ` + - `${this.getStonesOf(Color.Red)} R ${this.getStonesOf(Color.Green)} G` - ); - } - - /** - * Retrieve the amount of stones of a given color for this cell. - * - * @param color The color to retrieve the amount of. - */ - private innerGetStones(color: Color): number { - switch (color) { - case Color.Blue: - return this.blueStones; - case Color.Black: - return this.blackStones; - case Color.Red: - return this.redStones; - case Color.Green: - return this.greenStones; - /* istanbul ignore next */ - default: - return 0; - } - } - - /** - * Retrieve a two element array with the delta value of a neighbor cell to a - * specific direction. The delta is considered using the `deltaValue` argument, - * thus increasing or decreasing by that amount depending on the given direction. - * @param direction The direction to use to calculate the delta value. - * @param deltaValue The delta number to use when calculating. - */ - private innerGetDeltaByDirection(direction: Direction, deltaValue: number): [number, number] { - switch (direction) { - case Direction.East: - return [deltaValue, 0]; - case Direction.West: - return [-deltaValue, 0]; - case Direction.North: - return [0, deltaValue]; - case Direction.South: - return [0, -deltaValue]; - /* istanbul ignore next */ - default: - return [0, 0]; - } - } - - /** - * Set the amount of stones of the given color for this cell, to the given amount. - * - * @throws {@link InvalidStonesAmount} with the given performed action, when the - * amount of stones to set is lower than zero. - * - * @param color The color of the stones to set. - * @param amount The amount to set the stones of that color. - * @param performedAction The attempt to set the error to in case of failure. - */ - private innerSetStones(color: Color, amount: number, performedAction: StonesChangeActionAttempt): void { - expect(amount) - .toBeGreaterThanOrEqual(0) - .orThrow(new InvalidStonesAmount(performedAction, color.toString(), amount, this)); - const oldBlueStones = this.blueStones; - const oldBlackStones = this.blackStones; - const oldRedStones = this.redStones; - const oldGreenStones = this.greenStones; - switch (color) { - case Color.Blue: - this.blueStones = amount; - break; - case Color.Black: - this.blackStones = amount; - break; - case Color.Red: - this.redStones = amount; - break; - case Color.Green: - this.greenStones = amount; - break; - /* istanbul ignore next */ - default: - break; - } - this.emit( - Cell.onStonesChanged, - { x: this.locationX, y: this.locationY }, - { - [Color.BLUE]: this.blueStones, - [Color.BLACK]: this.blackStones, - [Color.RED]: this.redStones, - [Color.GREEN]: this.greenStones - }, - { - [Color.BLUE]: oldBlueStones, - [Color.BLACK]: oldBlackStones, - [Color.RED]: oldRedStones, - [Color.GREEN]: oldGreenStones - } - ); - } -} diff --git a/src/GobstonesLang/Color.ts b/src/GobstonesLang/Color.ts deleted file mode 100644 index ed3ea9d..0000000 --- a/src/GobstonesLang/Color.ts +++ /dev/null @@ -1,244 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) National University of Quilmes 2018-2024 - * Gobstones (TM) is a trademark of the National University of Quilmes. - * - * This program is free software distributed under the terms of the - * GNU Affero General Public License version 3. - * Additional terms added in compliance to section 7 of such license apply. - * - * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. - * ***************************************************************************** - */ -/** - * @module API.GobstonesLang - * @author Alan Rodas Bonjour - */ - -/** - * This enum represent the valid Gobstones Colors. - * It provides a set of singleton cases, for each possible value, - * as well as static accessors for each case name string (that should - * be used as key for representing directions). - * - * Additionally it provides handy methods for operations that are common - * with colors in Gobstones, such as asking for the first, the last, - * the next or the previous. - * - * Note that colors are sorted in the following order, from first to last. - * * Color.Blue - * * Color.Black - * * Color.Red - * * Color.Green - * - * Always prefer using the provided static names as object keys when needed, - * such as: - * -``` -{ - [Color.BLUE]: 5, - [Color.BLACK]: 3, - [Color.RED]: 7, - [Color.GREEN]: 0 -} -``` - * - * @group API: Main - */ -export class Color { - /** - * The Blue Color internal value. - * @private - */ - public static BLUE = 'a' as const; - - /** - * The Black Color internal value. - * @private - */ - public static BLACK = 'n' as const; - - /** - * The Red Color internal value. - * @private - */ - public static RED = 'r' as const; - - /** - * The Green Color internal value. - * @private - */ - public static GREEN = 'v' as const; - - /** - * The Blue color. Use whenever a reference to Blue is needed. - */ - public static Blue = new Color(Color.BLUE); - - /** - * The Black color. Use whenever a reference to Black is needed. - */ - public static Black = new Color(Color.BLACK); - - /** - * The Red color. Use whenever a reference to Red is needed. - */ - public static Red = new Color(Color.RED); - - /** - * The Green color. Use whenever a reference to Green is needed. - */ - public static Green = new Color(Color.GREEN); - - /** - * The smallest Color possible, currently {@link Color.Blue} - * - * @returns The smallest color. - */ - public static min(): Color { - return Color.Blue; - } - - /** - * The biggest Color possible, currently {@link Color.Green} - * - * @returns The biggest color. - */ - public static max(): Color { - return Color.Green; - } - - /** - * The next Color of a given Color. Colors are sorted - * in the following way, from first to last: - * * Color.Blue - * * Color.Black - * * Color.Red - * * Color.Green - * - * And they are cyclic, that is, the next color of Green is Blue. - * - * @param color The color to obtain the next value from. - * - * @returns The next color of the given one. - */ - public static next(color: Color): Color { - switch (color) { - case Color.Blue: - return Color.Black; - case Color.Black: - return Color.Red; - case Color.Red: - return Color.Green; - case Color.Green: - return Color.Blue; - /* istanbul ignore next */ - default: - return undefined as unknown as Color; - } - } - - /** - * The next Color of a given Color. Color are sorted - * in the following way, from last to first: - * * Color.Green - * * Color.Red - * * Color.Black - * * Color.Blue - * - * And they are cyclic, that is, the previous color of Blue is Green. - * - * @param color The color to obtain the previous value from. - * - * @returns The previous color of the given one. - */ - public static previous(color: Color): Color { - switch (color) { - case Color.Blue: - return Color.Green; - case Color.Black: - return Color.Blue; - case Color.Red: - return Color.Black; - case Color.Green: - return Color.Red; - /* istanbul ignore next */ - default: - return undefined as unknown as Color; - } - } - - /** - * Iterate over all the colors, in their defined order, from the smallest, - * to the biggest, performing the callback over each color. A function that - * expects a color and returns void is expected as an argument. - * - * @param f The callback to call on each iteration. - */ - public static foreach(f: (color: Color) => void): void { - let current = Color.min(); - while (current !== Color.max()) { - f(current); - current = Color.next(current); - } - f(current); - } - - /** - * The internal value of the color. - */ - private innerValue: string; - - /** - * Create a new instance of a color. This is private, as - * colors are already provided in a singleton like pattern. - */ - private constructor(value: string) { - this.innerValue = value; - } - - /** - * The internal value of this element. - * @private - */ - public get value(): string { - return this.innerValue; - } - - /** - * The next Color of this color. Colors are sorted - * in the following way, from first to last: - * * Color.Blue - * * Color.Black - * * Color.Red - * * Color.Green - * - * And they are cyclic, that is, the next color of Green is Blue. - * - * @returns The next color of the given one. - */ - public next(): Color { - return Color.next(this); - } - - /** - * The next Color of this color. Color are sorted - * in the following way, from last to first: - * * Color.Green - * * Color.Red - * * Color.Black - * * Color.Blue - * - * And they are cyclic, that is, the previous color of Blue is Green. - * - * @returns The previous color of the given one. - */ - public previous(): Color { - return Color.previous(this); - } - - /** @inheritdoc */ - public toString(): string { - return this.innerValue; - } -} diff --git a/src/GobstonesLang/Direction.ts b/src/GobstonesLang/Direction.ts deleted file mode 100644 index 92a0a94..0000000 --- a/src/GobstonesLang/Direction.ts +++ /dev/null @@ -1,328 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) National University of Quilmes 2018-2024 - * Gobstones (TM) is a trademark of the National University of Quilmes. - * - * This program is free software distributed under the terms of the - * GNU Affero General Public License version 3. - * Additional terms added in compliance to section 7 of such license apply. - * - * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. - * ***************************************************************************** - */ -/** - * @module API.GobstonesLang - * @author Alan Rodas Bonjour - */ - -/** - * This class represents the valid Gobstones Directions. - * It provides a set of singleton cases, for each possible value, - * as well as static accessors for each case name string (that should - * be used as key for representing directions). - * - * Additionally it provides handy methods for operations that are common - * with directions in Gobstones, such as asking for the first, the last, - * the next, the previous or the opposite direction. - * - * Note that directions are sorted in the following order, from first to last. - * * Direction.North - * * Direction.East - * * Direction.South - * * Direction.West - * - * Always prefer using the provided static names as object keys when needed, - * such as: - * -``` -{ - [Direction.NORTH]: 'Some value', - [Direction.SOUTH]: 'Some other value' -} -``` - * - * @group API: Main - */ -export class Direction { - /** - * The North Direction internal value. - * @private - */ - public static NORTH = 'n' as const; - - /** - * The East Direction internal value. - * @private - */ - public static EAST = 'e' as const; - - /** - * The South Direction internal value. - * @private - */ - public static SOUTH = 's' as const; - - /** - * The West Direction internal value. - * @private - */ - public static WEST = 'w' as const; - - /** - * The North direction. Use whenever a reference to North is needed. - */ - public static North = new Direction(Direction.NORTH); - - /** - * The East direction. Use whenever a reference to East is needed. - */ - public static East = new Direction(Direction.EAST); - - /** - * The South direction. Use whenever a reference to South is needed. - */ - public static South = new Direction(Direction.SOUTH); - - /** - * The West direction. Use whenever a reference to West is needed. - */ - public static West = new Direction(Direction.WEST); - - /** - * The smallest Direction possible, currently {@link Direction.North} - * - * @returns The smallest direction. - */ - public static min(): Direction { - return Direction.North; - } - - /** - * The biggest Direction possible, currently {@link Direction.West} - * - * @returns The biggest direction. - */ - public static max(): Direction { - return Direction.West; - } - - /** - * The next Direction of a given Direction. Directions are sorted - * in the following way, from first to last: - * * Direction.North - * * Direction.East - * * Direction.South - * * Direction.West - * - * And they are cyclic, that is, the next direction of West is North. - * - * @param dir The direction to obtain the next value from. - * - * @returns The next direction of the given one. - */ - public static next(dir: Direction): Direction { - switch (dir) { - case Direction.North: - return Direction.East; - case Direction.East: - return Direction.South; - case Direction.South: - return Direction.West; - case Direction.West: - return Direction.North; - /* istanbul ignore next */ - default: - return undefined as unknown as Direction; - } - } - - /** - * The next Direction of a given Direction. Directions are sorted - * in the following way, from last to first: - * * Direction.West - * * Direction.South - * * Direction.East - * * Direction.North - * - * And they are cyclic, that is, the previous direction of North is West. - * - * @param dir The direction to obtain the previous value from. - * - * @returns The previous direction of the given one. - */ - public static previous(dir: Direction): Direction { - switch (dir) { - case Direction.North: - return Direction.West; - case Direction.East: - return Direction.North; - case Direction.South: - return Direction.East; - case Direction.West: - return Direction.South; - /* istanbul ignore next */ - default: - return undefined as unknown as Direction; - } - } - - /** - * The opposite Direction of a given Direction. Directions are opposed - * to each other in pairs, those being: - * * Direction.West is opposite to Direction.East and vice versa - * * Direction.North is opposite to Direction.South and vice versa - * - * @param dir The direction to obtain the opposite value from. - * - * @returns The opposite direction of the given one. - */ - public static opposite(dir: Direction): Direction { - switch (dir) { - case Direction.North: - return Direction.South; - case Direction.East: - return Direction.West; - case Direction.South: - return Direction.North; - case Direction.West: - return Direction.East; - /* istanbul ignore next */ - default: - return undefined as unknown as Direction; - } - } - - /** - * Answer wether or not the given direction is vertical, - * that is, one of Direction.North or Direction.South. - * - * @param dir The direction to find out if it's vertical. - * - * @returns `true` if it's vertical, `false` otherwise. - */ - public static isVertical(dir: Direction): boolean { - return dir === Direction.North || dir === Direction.South; - } - - /** - * Answer wether or not the given direction is horizontal, - * that is, one of Direction.East or Direction.West. - * - * @param dir The direction to find out if it's horizontal. - * - * @returns `true` if it's horizontal, `false` otherwise. - */ - public static isHorizontal(dir: Direction): boolean { - return !Direction.isVertical(dir); - } - - /** - * Iterate over all the directions, in their defined order, from the smallest, - * to the biggest, performing the callback over each direction. A function that - * expects a direction and returns void is expected as an argument. - * - * @param f The callback to call on each iteration. - */ - public static foreach(f: (dir: Direction) => void): void { - let current = Direction.min(); - while (current !== Direction.max()) { - f(current); - current = Direction.next(current); - } - f(current); - } - - /** - * The internal value of the direction. - */ - private innerValue: string; - - /** - * Create a new instance of a direction. This is private, as - * directions are already provided in a singleton like pattern. - */ - private constructor(value: string) { - this.innerValue = value; - } - - /** - * The internal value of this element. - * @private - */ - public get value(): string { - return this.innerValue; - } - - /** - * The next Direction of this direction. Directions are sorted - * in the following way, from first to last: - * * Direction.North - * * Direction.East - * * Direction.South - * * Direction.West - * - * And they are cyclic, that is, the next direction of West is North. - * - * @param dir The direction to obtain the next value from. - * - * @returns The next direction of the given one. - */ - public next(): Direction { - return Direction.next(this); - } - - /** - * The next Direction of this one. Directions are sorted - * in the following way, from last to first: - * * Direction.West - * * Direction.South - * * Direction.East - * * Direction.North - * - * And they are cyclic, that is, the previous direction of North is West. - * - * @param dir The direction to obtain the previous value from. - * - * @returns The previous direction of the given one. - */ - public previous(): Direction { - return Direction.previous(this); - } - - /** - * The opposite Direction of this one. Directions are opposed - * to each other in pairs, those being: - * * Direction.West is opposite to Direction.East and vice versa - * * Direction.North is opposite to Direction.South and vice versa - * - * @returns The opposite direction of the given one. - */ - public opposite(): Direction { - return Direction.opposite(this); - } - - /** - * Answer wether or not this direction is vertical, - * that is, one of Direction.North or Direction.South. - * - * @returns `true` if it's vertical, `false` otherwise. - */ - public isVertical(): boolean { - return Direction.isVertical(this); - } - - /** - * Answer wether or not this direction is horizontal, - * that is, one of Direction.East or Direction.West. - * - * @returns `true` if it's horizontal, `false` otherwise. - */ - public isHorizontal(): boolean { - return Direction.isHorizontal(this); - } - - /** @inheritdoc */ - public toString(): string { - return this.innerValue; - } -} diff --git a/src/GobstonesLang/IdentifierRegexp.ts b/src/GobstonesLang/IdentifierRegexp.ts deleted file mode 100644 index b27bfe1..0000000 --- a/src/GobstonesLang/IdentifierRegexp.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) National University of Quilmes 2018-2024 - * Gobstones (TM) is a trademark of the National University of Quilmes. - * - * This program is free software distributed under the terms of the - * GNU Affero General Public License version 3. - * Additional terms added in compliance to section 7 of such license apply. - * - * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. - * ***************************************************************************** - */ -/** - * @module API.GobstonesLang - * @author Alan Rodas Bonjour - */ -/* eslint-disable max-len */ - -/** - * Numbers are only ASCII characters for digits - * - * @group API: Regexp - */ -export const number = /[0-9][0-9]*/; - -/** - * Any word starting with an uppercase letter (or titlecase for some languages), - * that may be followed by any amount of unicode letters or decimal numbers, or ASCII underscore. - * - * ES Form: - * ``` - * /(\p{Uppercase_Letter}|\p{Titlecase_Letter})[\p{Letter}\p{Decimal_Number}_]* /u; - * ``` - * - * @group API: Regexp - */ -export const upperId = - /((?:[A-Z\xC0-\xD6\xD8-\xDE\u0100\u0102\u0104\u0106\u0108\u010A\u010C\u010E\u0110\u0112\u0114\u0116\u0118\u011A\u011C\u011E\u0120\u0122\u0124\u0126\u0128\u012A\u012C\u012E\u0130\u0132\u0134\u0136\u0139\u013B\u013D\u013F\u0141\u0143\u0145\u0147\u014A\u014C\u014E\u0150\u0152\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0162\u0164\u0166\u0168\u016A\u016C\u016E\u0170\u0172\u0174\u0176\u0178\u0179\u017B\u017D\u0181\u0182\u0184\u0186\u0187\u0189-\u018B\u018E-\u0191\u0193\u0194\u0196-\u0198\u019C\u019D\u019F\u01A0\u01A2\u01A4\u01A6\u01A7\u01A9\u01AC\u01AE\u01AF\u01B1-\u01B3\u01B5\u01B7\u01B8\u01BC\u01C4\u01C7\u01CA\u01CD\u01CF\u01D1\u01D3\u01D5\u01D7\u01D9\u01DB\u01DE\u01E0\u01E2\u01E4\u01E6\u01E8\u01EA\u01EC\u01EE\u01F1\u01F4\u01F6-\u01F8\u01FA\u01FC\u01FE\u0200\u0202\u0204\u0206\u0208\u020A\u020C\u020E\u0210\u0212\u0214\u0216\u0218\u021A\u021C\u021E\u0220\u0222\u0224\u0226\u0228\u022A\u022C\u022E\u0230\u0232\u023A\u023B\u023D\u023E\u0241\u0243-\u0246\u0248\u024A\u024C\u024E\u0370\u0372\u0376\u037F\u0386\u0388-\u038A\u038C\u038E\u038F\u0391-\u03A1\u03A3-\u03AB\u03CF\u03D2-\u03D4\u03D8\u03DA\u03DC\u03DE\u03E0\u03E2\u03E4\u03E6\u03E8\u03EA\u03EC\u03EE\u03F4\u03F7\u03F9\u03FA\u03FD-\u042F\u0460\u0462\u0464\u0466\u0468\u046A\u046C\u046E\u0470\u0472\u0474\u0476\u0478\u047A\u047C\u047E\u0480\u048A\u048C\u048E\u0490\u0492\u0494\u0496\u0498\u049A\u049C\u049E\u04A0\u04A2\u04A4\u04A6\u04A8\u04AA\u04AC\u04AE\u04B0\u04B2\u04B4\u04B6\u04B8\u04BA\u04BC\u04BE\u04C0\u04C1\u04C3\u04C5\u04C7\u04C9\u04CB\u04CD\u04D0\u04D2\u04D4\u04D6\u04D8\u04DA\u04DC\u04DE\u04E0\u04E2\u04E4\u04E6\u04E8\u04EA\u04EC\u04EE\u04F0\u04F2\u04F4\u04F6\u04F8\u04FA\u04FC\u04FE\u0500\u0502\u0504\u0506\u0508\u050A\u050C\u050E\u0510\u0512\u0514\u0516\u0518\u051A\u051C\u051E\u0520\u0522\u0524\u0526\u0528\u052A\u052C\u052E\u0531-\u0556\u10A0-\u10C5\u10C7\u10CD\u13A0-\u13F5\u1C90-\u1CBA\u1CBD-\u1CBF\u1E00\u1E02\u1E04\u1E06\u1E08\u1E0A\u1E0C\u1E0E\u1E10\u1E12\u1E14\u1E16\u1E18\u1E1A\u1E1C\u1E1E\u1E20\u1E22\u1E24\u1E26\u1E28\u1E2A\u1E2C\u1E2E\u1E30\u1E32\u1E34\u1E36\u1E38\u1E3A\u1E3C\u1E3E\u1E40\u1E42\u1E44\u1E46\u1E48\u1E4A\u1E4C\u1E4E\u1E50\u1E52\u1E54\u1E56\u1E58\u1E5A\u1E5C\u1E5E\u1E60\u1E62\u1E64\u1E66\u1E68\u1E6A\u1E6C\u1E6E\u1E70\u1E72\u1E74\u1E76\u1E78\u1E7A\u1E7C\u1E7E\u1E80\u1E82\u1E84\u1E86\u1E88\u1E8A\u1E8C\u1E8E\u1E90\u1E92\u1E94\u1E9E\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EBE\u1EC0\u1EC2\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u1EF6\u1EF8\u1EFA\u1EFC\u1EFE\u1F08-\u1F0F\u1F18-\u1F1D\u1F28-\u1F2F\u1F38-\u1F3F\u1F48-\u1F4D\u1F59\u1F5B\u1F5D\u1F5F\u1F68-\u1F6F\u1FB8-\u1FBB\u1FC8-\u1FCB\u1FD8-\u1FDB\u1FE8-\u1FEC\u1FF8-\u1FFB\u2102\u2107\u210B-\u210D\u2110-\u2112\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u2130-\u2133\u213E\u213F\u2145\u2183\u2C00-\u2C2E\u2C60\u2C62-\u2C64\u2C67\u2C69\u2C6B\u2C6D-\u2C70\u2C72\u2C75\u2C7E-\u2C80\u2C82\u2C84\u2C86\u2C88\u2C8A\u2C8C\u2C8E\u2C90\u2C92\u2C94\u2C96\u2C98\u2C9A\u2C9C\u2C9E\u2CA0\u2CA2\u2CA4\u2CA6\u2CA8\u2CAA\u2CAC\u2CAE\u2CB0\u2CB2\u2CB4\u2CB6\u2CB8\u2CBA\u2CBC\u2CBE\u2CC0\u2CC2\u2CC4\u2CC6\u2CC8\u2CCA\u2CCC\u2CCE\u2CD0\u2CD2\u2CD4\u2CD6\u2CD8\u2CDA\u2CDC\u2CDE\u2CE0\u2CE2\u2CEB\u2CED\u2CF2\uA640\uA642\uA644\uA646\uA648\uA64A\uA64C\uA64E\uA650\uA652\uA654\uA656\uA658\uA65A\uA65C\uA65E\uA660\uA662\uA664\uA666\uA668\uA66A\uA66C\uA680\uA682\uA684\uA686\uA688\uA68A\uA68C\uA68E\uA690\uA692\uA694\uA696\uA698\uA69A\uA722\uA724\uA726\uA728\uA72A\uA72C\uA72E\uA732\uA734\uA736\uA738\uA73A\uA73C\uA73E\uA740\uA742\uA744\uA746\uA748\uA74A\uA74C\uA74E\uA750\uA752\uA754\uA756\uA758\uA75A\uA75C\uA75E\uA760\uA762\uA764\uA766\uA768\uA76A\uA76C\uA76E\uA779\uA77B\uA77D\uA77E\uA780\uA782\uA784\uA786\uA78B\uA78D\uA790\uA792\uA796\uA798\uA79A\uA79C\uA79E\uA7A0\uA7A2\uA7A4\uA7A6\uA7A8\uA7AA-\uA7AE\uA7B0-\uA7B4\uA7B6\uA7B8\uA7BA\uA7BC\uA7BE\uA7C2\uA7C4-\uA7C7\uA7C9\uA7F5\uFF21-\uFF3A]|\uD801[\uDC00-\uDC27\uDCB0-\uDCD3]|\uD803[\uDC80-\uDCB2]|\uD806[\uDCA0-\uDCBF]|\uD81B[\uDE40-\uDE5F]|\uD835[\uDC00-\uDC19\uDC34-\uDC4D\uDC68-\uDC81\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB5\uDCD0-\uDCE9\uDD04\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD38\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD6C-\uDD85\uDDA0-\uDDB9\uDDD4-\uDDED\uDE08-\uDE21\uDE3C-\uDE55\uDE70-\uDE89\uDEA8-\uDEC0\uDEE2-\uDEFA\uDF1C-\uDF34\uDF56-\uDF6E\uDF90-\uDFA8\uDFCA]|\uD83A[\uDD00-\uDD21])|[\u01C5\u01C8\u01CB\u01F2\u1F88-\u1F8F\u1F98-\u1F9F\u1FA8-\u1FAF\u1FBC\u1FCC\u1FFC])(?:[0-9A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u0660-\u0669\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08C7\u0904-\u0939\u093D\u0950\u0958-\u0961\u0966-\u096F\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AE6-\u0AEF\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0BE6-\u0BEF\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C66-\u0C6F\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D66-\u0D6F\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DE6-\u0DEF\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F20-\u0F29\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F-\u1049\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1090-\u1099\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u17E0-\u17E9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A16\u1A20-\u1A54\u1A80-\u1A89\u1A90-\u1A99\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B50-\u1B59\u1B83-\u1BA0\u1BAE-\u1BE5\u1C00-\u1C23\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\u9FFC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7CA\uA7F5-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8D0-\uA8D9\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA900-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF-\uA9D9\uA9E0-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD23\uDD30-\uDD39\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC66-\uDC6F\uDC83-\uDCAF\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD03-\uDD26\uDD36-\uDD3F\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDD0-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDEF0-\uDEF9\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC50-\uDC59\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE50-\uDE59\uDE80-\uDEAA\uDEB8\uDEC0-\uDEC9\uDF00-\uDF1A\uDF30-\uDF39]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCE9\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC50-\uDC59\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDDA0-\uDDA9\uDEE0-\uDEF2\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE7F\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD1E\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDEC0-\uDEEB\uDEF0-\uDEF9]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43\uDD4B\uDD50-\uDD59]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDD\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])*/; - -/** - * Any word starting with an lowercase letter, that may be followed - * by any amount of unicode letters or decimal numbers, or ASCII underscore. - * - * ES Form: - * ``` - * /\p{Lowercase_Letter}[\p{Letter}\p{Decimal_Number}_]* /u; - * ``` - * - * @group API: Regexp - */ -export const lowerId = - /(?:[a-z\xB5\xDF-\xF6\xF8-\xFF\u0101\u0103\u0105\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131\u0133\u0135\u0137\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146\u0148\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9\u01BA\u01BD-\u01BF\u01C6\u01C9\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC\u01DD\u01DF\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF\u01F0\u01F3\u01F5\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02AF\u0371\u0373\u0377\u037B-\u037D\u0390\u03AC-\u03CE\u03D0\u03D1\u03D5-\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB\u03FC\u0430-\u045F\u0461\u0463\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4\u04C6\u04C8\u04CA\u04CC\u04CE\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B\u051D\u051F\u0521\u0523\u0525\u0527\u0529\u052B\u052D\u052F\u0560-\u0588\u10D0-\u10FA\u10FD-\u10FF\u13F8-\u13FD\u1C80-\u1C88\u1D00-\u1D2B\u1D6B-\u1D77\u1D79-\u1D9A\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4\u1FB6\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6\u1FC7\u1FD0-\u1FD3\u1FD6\u1FD7\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6\u1FF7\u210A\u210E\u210F\u2113\u212F\u2134\u2139\u213C\u213D\u2146-\u2149\u214E\u2184\u2C30-\u2C5E\u2C61\u2C65\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73\u2C74\u2C76-\u2C7B\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F\uA691\uA693\uA695\uA697\uA699\uA69B\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F\uA771-\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791\uA793-\uA795\uA797\uA799\uA79B\uA79D\uA79F\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7AF\uA7B5\uA7B7\uA7B9\uA7BB\uA7BD\uA7BF\uA7C3\uA7C8\uA7CA\uA7F6\uA7FA\uAB30-\uAB5A\uAB60-\uAB68\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF41-\uFF5A]|\uD801[\uDC28-\uDC4F\uDCD8-\uDCFB]|\uD803[\uDCC0-\uDCF2]|\uD806[\uDCC0-\uDCDF]|\uD81B[\uDE60-\uDE7F]|\uD835[\uDC1A-\uDC33\uDC4E-\uDC54\uDC56-\uDC67\uDC82-\uDC9B\uDCB6-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDCCF\uDCEA-\uDD03\uDD1E-\uDD37\uDD52-\uDD6B\uDD86-\uDD9F\uDDBA-\uDDD3\uDDEE-\uDE07\uDE22-\uDE3B\uDE56-\uDE6F\uDE8A-\uDEA5\uDEC2-\uDEDA\uDEDC-\uDEE1\uDEFC-\uDF14\uDF16-\uDF1B\uDF36-\uDF4E\uDF50-\uDF55\uDF70-\uDF88\uDF8A-\uDF8F\uDFAA-\uDFC2\uDFC4-\uDFC9\uDFCB]|\uD83A[\uDD22-\uDD43])(?:[0-9A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u0660-\u0669\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08C7\u0904-\u0939\u093D\u0950\u0958-\u0961\u0966-\u096F\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AE6-\u0AEF\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0BE6-\u0BEF\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C66-\u0C6F\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D66-\u0D6F\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DE6-\u0DEF\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F20-\u0F29\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F-\u1049\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1090-\u1099\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u17E0-\u17E9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A16\u1A20-\u1A54\u1A80-\u1A89\u1A90-\u1A99\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B50-\u1B59\u1B83-\u1BA0\u1BAE-\u1BE5\u1C00-\u1C23\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\u9FFC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7CA\uA7F5-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8D0-\uA8D9\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA900-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF-\uA9D9\uA9E0-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD23\uDD30-\uDD39\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC66-\uDC6F\uDC83-\uDCAF\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD03-\uDD26\uDD36-\uDD3F\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDD0-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDEF0-\uDEF9\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC50-\uDC59\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE50-\uDE59\uDE80-\uDEAA\uDEB8\uDEC0-\uDEC9\uDF00-\uDF1A\uDF30-\uDF39]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCE9\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC50-\uDC59\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDDA0-\uDDA9\uDEE0-\uDEF2\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE7F\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD1E\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDEC0-\uDEEB\uDEF0-\uDEF9]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43\uDD4B\uDD50-\uDD59]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDD\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])*/; - -/** - * Any word starting with a non upper, title or lowercase letter (that is, a letter - * in a scripting that do not distinguishes between upper and lowercase forms), - * that may be followed by any amount of unicode letters or decimal numbers, or ASCII underscore. - * - * ES Form: - * ``` - * /\p{Other_Letter}[\p{Letter}\p{Decimal_Number}_]* /u; - * ``` - * - * @group API: Regexp - */ -export const nonAlphabeticId = - /(?:[\xAA\xBA\u01BB\u01C0-\u01C3\u0294\u05D0-\u05EA\u05EF-\u05F2\u0620-\u063F\u0641-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u0800-\u0815\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08C7\u0904-\u0939\u093D\u0950\u0958-\u0961\u0972-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E45\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17DC\u1820-\u1842\u1844-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C77\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u2135-\u2138\u2D30-\u2D67\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3006\u303C\u3041-\u3096\u309F\u30A1-\u30FA\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\u9FFC\uA000-\uA014\uA016-\uA48C\uA4D0-\uA4F7\uA500-\uA60B\uA610-\uA61F\uA62A\uA62B\uA66E\uA6A0-\uA6E5\uA78F\uA7F7\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9E0-\uA9E4\uA9E7-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA6F\uAA71-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB\uAADC\uAAE0-\uAAEA\uAAF2\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF66-\uFF6F\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC50-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDD00-\uDD23\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDEB8\uDF00-\uDF1A]|\uD806[\uDC00-\uDC2B\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDEE0-\uDEF2\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF4A\uDF50]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD1E\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD838[\uDD00-\uDD2C\uDD4E\uDEC0-\uDEEB]|\uD83A[\uDC00-\uDCC4]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDEDD\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])(?:[0-9A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u0660-\u0669\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08C7\u0904-\u0939\u093D\u0950\u0958-\u0961\u0966-\u096F\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AE6-\u0AEF\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0BE6-\u0BEF\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C66-\u0C6F\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D66-\u0D6F\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DE6-\u0DEF\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F20-\u0F29\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F-\u1049\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1090-\u1099\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u17E0-\u17E9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A16\u1A20-\u1A54\u1A80-\u1A89\u1A90-\u1A99\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B50-\u1B59\u1B83-\u1BA0\u1BAE-\u1BE5\u1C00-\u1C23\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\u9FFC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7CA\uA7F5-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8D0-\uA8D9\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA900-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF-\uA9D9\uA9E0-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD23\uDD30-\uDD39\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC66-\uDC6F\uDC83-\uDCAF\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD03-\uDD26\uDD36-\uDD3F\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDD0-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDEF0-\uDEF9\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC50-\uDC59\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE50-\uDE59\uDE80-\uDEAA\uDEB8\uDEC0-\uDEC9\uDF00-\uDF1A\uDF30-\uDF39]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCE9\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC50-\uDC59\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDDA0-\uDDA9\uDEE0-\uDEF2\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE7F\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD1E\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDEC0-\uDEEB\uDEF0-\uDEF9]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43\uDD4B\uDD50-\uDD59]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDD\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])*/; - -/** - * Any word starting with an underscore and followed by a non upper, title or lowercase - * letter (that is, a letter in a scripting that do not distinguishes between upper and - * lowercase forms), that may be followed by any amount of unicode letters or decimal - * numbers, or ASCII underscore. - * - * ES Form: - * ``` - * /_\p{Other_Letter}[\p{Letter}\p{Decimal_Number}_]* /u; - * ``` - * - * @group API: Regexp - */ -export const sigiledNonAlphabeticId = - /_(?:[\xAA\xBA\u01BB\u01C0-\u01C3\u0294\u05D0-\u05EA\u05EF-\u05F2\u0620-\u063F\u0641-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u0800-\u0815\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08C7\u0904-\u0939\u093D\u0950\u0958-\u0961\u0972-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E45\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17DC\u1820-\u1842\u1844-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C77\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u2135-\u2138\u2D30-\u2D67\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3006\u303C\u3041-\u3096\u309F\u30A1-\u30FA\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\u9FFC\uA000-\uA014\uA016-\uA48C\uA4D0-\uA4F7\uA500-\uA60B\uA610-\uA61F\uA62A\uA62B\uA66E\uA6A0-\uA6E5\uA78F\uA7F7\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9E0-\uA9E4\uA9E7-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA6F\uAA71-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB\uAADC\uAAE0-\uAAEA\uAAF2\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF66-\uFF6F\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC50-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDD00-\uDD23\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDEB8\uDF00-\uDF1A]|\uD806[\uDC00-\uDC2B\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDEE0-\uDEF2\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF4A\uDF50]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD1E\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD838[\uDD00-\uDD2C\uDD4E\uDEC0-\uDEEB]|\uD83A[\uDC00-\uDCC4]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDEDD\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])(?:[0-9A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u0660-\u0669\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08C7\u0904-\u0939\u093D\u0950\u0958-\u0961\u0966-\u096F\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AE6-\u0AEF\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0BE6-\u0BEF\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C66-\u0C6F\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D66-\u0D6F\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DE6-\u0DEF\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F20-\u0F29\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F-\u1049\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1090-\u1099\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u17E0-\u17E9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A16\u1A20-\u1A54\u1A80-\u1A89\u1A90-\u1A99\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B50-\u1B59\u1B83-\u1BA0\u1BAE-\u1BE5\u1C00-\u1C23\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\u9FFC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7CA\uA7F5-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8D0-\uA8D9\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA900-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF-\uA9D9\uA9E0-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD23\uDD30-\uDD39\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC66-\uDC6F\uDC83-\uDCAF\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD03-\uDD26\uDD36-\uDD3F\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDD0-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDEF0-\uDEF9\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC50-\uDC59\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE50-\uDE59\uDE80-\uDEAA\uDEB8\uDEC0-\uDEC9\uDF00-\uDF1A\uDF30-\uDF39]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCE9\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC50-\uDC59\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDDA0-\uDDA9\uDEE0-\uDEF2\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE7F\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD1E\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDEC0-\uDEEB\uDEF0-\uDEF9]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43\uDD4B\uDD50-\uDD59]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDD\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])*/; - -/** - * This helper matches any of the above identifiers, that is, anything that is a word. - * - * ES Form: - * ``` - * /(\p{Letter}|_)[\p{Letter}\p{Decimal_Number}_]* /u; - * ``` - * - * @group API: Regexp - */ -export const identifier = - /((?:[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08C7\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\u9FFC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7CA\uA7F5-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD23\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDEB8\uDF00-\uDF1A]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCDF\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDEE0-\uDEF2\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE7F\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD1E\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD4E\uDEC0-\uDEEB]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43\uDD4B]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDEDD\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])|_)(?:[0-9A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u0660-\u0669\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08C7\u0904-\u0939\u093D\u0950\u0958-\u0961\u0966-\u096F\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AE6-\u0AEF\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0BE6-\u0BEF\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C66-\u0C6F\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D66-\u0D6F\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DE6-\u0DEF\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F20-\u0F29\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F-\u1049\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1090-\u1099\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u17E0-\u17E9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A16\u1A20-\u1A54\u1A80-\u1A89\u1A90-\u1A99\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B50-\u1B59\u1B83-\u1BA0\u1BAE-\u1BE5\u1C00-\u1C23\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\u9FFC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7CA\uA7F5-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8D0-\uA8D9\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA900-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF-\uA9D9\uA9E0-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD23\uDD30-\uDD39\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC66-\uDC6F\uDC83-\uDCAF\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD03-\uDD26\uDD36-\uDD3F\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDD0-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDEF0-\uDEF9\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC50-\uDC59\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE50-\uDE59\uDE80-\uDEAA\uDEB8\uDEC0-\uDEC9\uDF00-\uDF1A\uDF30-\uDF39]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCE9\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC50-\uDC59\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDDA0-\uDDA9\uDEE0-\uDEF2\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE7F\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD1E\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDEC0-\uDEEB\uDEF0-\uDEF9]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43\uDD4B\uDD50-\uDD59]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDD\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])*/; diff --git a/src/GobstonesLang/index.ts b/src/GobstonesLang/index.ts deleted file mode 100644 index 0ec968c..0000000 --- a/src/GobstonesLang/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) National University of Quilmes 2018-2024 - * Gobstones (TM) is a trademark of the National University of Quilmes. - * - * This program is free software distributed under the terms of the - * GNU Affero General Public License version 3. - * Additional terms added in compliance to section 7 of such license apply. - * - * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. - * ***************************************************************************** - */ -/** - * This module provides the {@link Board} class, which models a Gobstones Board - * and all the associated behavior. - * - * The {@link Board} class is expected to be used all through the Gobstones Platform - * and all Gobstones plugins whenever a Board should be used (e.g. the - * [gobstones-gbb-parser](https://github.com/gobstones/gobstones-gbb-parser) - * returns a Board from this module) - * - * Toghether with such class it provides additional helper classes for it's behavior - * such as {@link Cell}, {@link Color} and {@link Direction}, a set of error classes - * that may occur when invalid arguments are given. - * - * This module also provides a set of types that identify the information of a board - * and it's parts (cell locations, cell contents and others). - * This types are used by the Gobstones Interpreter and the Gobstones GBB Parser, - * and it's main type is implemented by the {@link Board} class. Yet, this - * definitions are internal to Gobstones Projects, and their usage should - * be avoided as most as possible. External projects such as plugins should - * be avoided in favor of using the {@link Board} class. - * - * @module API.GobstonesLang - * @author Alan Rodas Bonjour - */ - -export * from './Board'; -export * from './Cell'; -export * from './Color'; -export * from './Direction'; -export * from './BoardErrors'; -export * from './BoardDefinition'; diff --git a/src/History/Changeable.ts b/src/History/Changeable.ts index db5264d..e9ab318 100644 --- a/src/History/Changeable.ts +++ b/src/History/Changeable.ts @@ -10,6 +10,12 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + +/** + * @module History + * @author Pablo E. --Fidel-- Martínez López + */ + /** * A {@link Changeable} is a data structure used to register changes over a certain value * of a given type. diff --git a/src/History/Compactable.ts b/src/History/Compactable.ts index 356f8ae..eef87bc 100644 --- a/src/History/Compactable.ts +++ b/src/History/Compactable.ts @@ -10,6 +10,12 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + +/** + * @module History + * @author Pablo E. --Fidel-- Martínez López + */ + /** * A {@link Compactable} is a data structure that changes over time, and incorporates the possibility * to compact all changes, leaving the structure as if the current value was used at creation. diff --git a/src/History/History.ts b/src/History/History.ts index e3eb04c..a66b54c 100644 --- a/src/History/History.ts +++ b/src/History/History.ts @@ -10,6 +10,12 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + +/** + * @module History + * @author Pablo E. --Fidel-- Martínez López + */ + import { Changeable } from './Changeable'; import { Compactable } from './Compactable'; import { Transactional } from './Transactional'; @@ -112,11 +118,12 @@ export class History implements Changeable, Compactable, Undoable, Tran * the current version of it. * Also transactions can be started and rolled back, and all changes can be compacted. * - * @param e the initial value of the history. + * @param e - the initial value of the history. */ public constructor(e: A) { this._initializeWith(e); } + // ---------------------------------------------------- // #endregion } API: Constructor // ---------------------------------------------------- @@ -144,6 +151,7 @@ export class History implements Changeable, Compactable, Undoable, Tran this._changes.splice(newSize, size - newSize); } } + // ---------------------------------------------------- // #endregion } API: Change management // ---------------------------------------------------- @@ -206,6 +214,7 @@ export class History implements Changeable, Compactable, Undoable, Tran public numRedos(): number { return this._changes.length - this._currentValueIndex - 1; } + // ---------------------------------------------------- // #endregion } Undoing operations // ---------------------------------------------------- diff --git a/src/History/Transactional.ts b/src/History/Transactional.ts index 2d10390..c21ac5d 100644 --- a/src/History/Transactional.ts +++ b/src/History/Transactional.ts @@ -10,6 +10,12 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + +/** + * @module History + * @author Pablo E. --Fidel-- Martínez López + */ + /** * A {@link Transactional} is a data structure that changes over time, with the possibility * to group changes and later rollback those groups of changes as a whole. diff --git a/src/History/Undoable.ts b/src/History/Undoable.ts index 1a199fc..8eda27c 100644 --- a/src/History/Undoable.ts +++ b/src/History/Undoable.ts @@ -10,6 +10,12 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + +/** + * @module History + * @author Pablo E. --Fidel-- Martínez López + */ + /** * An {@link Undoable} is a data structure that changes over time, and incorporates the possibility * to undo some changes and later redo them (if no other changing operation was done in between). diff --git a/src/History/index.ts b/src/History/index.ts index 868a709..1a15e89 100644 --- a/src/History/index.ts +++ b/src/History/index.ts @@ -10,6 +10,15 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + +/** + * This module provides a set of function, interfaces and classes that allow + * to manage an object through a history, that is, save it's state, allow to + * undo state transformations, redo them, and so on. + * + * @module History + * @author Pablo E. --Fidel-- Martínez López + */ export * from './History'; export * from './Changeable'; export * from './Compactable'; diff --git a/src/SourceReader/SourcePositions/AbstractDocumentSourcePosition.ts b/src/SourceReader/SourcePositions/AbstractDocumentSourcePosition.ts index 2e0987f..4893e32 100644 --- a/src/SourceReader/SourcePositions/AbstractDocumentSourcePosition.ts +++ b/src/SourceReader/SourcePositions/AbstractDocumentSourcePosition.ts @@ -10,17 +10,16 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.SourceReader + * @module SourceReader/SourcePositions * @author Alan Rodas Bonjour */ + import { AbstractKnownSourcePosition } from './AbstractKnownSourcePosition'; import { SourceReader } from '../SourceReader'; -// =============================================== -// #region AbstractDocumentSourcePosition { -// ----------------------------------------------- /** * An {@link AbstractDocumentSourcePosition} points to a particular position, * that is well known, and belongs to a particular document. @@ -42,40 +41,26 @@ import { SourceReader } from '../SourceReader'; * can answer, such as inquiring for the document name, and the contents * of the document. * - * @group Internals: Source Positions - * @private + * @privateRemarks + * Among the different operations provided in this class there are a few + * operations used to determine context of the source input surrounding + * the current position: + * {@link AbstractDocumentSourcePosition.documentContextBefore | documentContextBefore}, and + * {@link AbstractDocumentSourcePosition.documentContextAfter | documentContextAfter}. + * The implementation of all these is achieved by a + * [Template Method Pattern](https://en.wikipedia.org/wiki/Template_method_pattern) + * to provide a common validation and different logics depending on the actual subclass. + * Protected methods + * {@link SourceReader/SourcePositions.AbstractKnownSourcePosition._documentContextBefore | _documentContextBefore }, + * and + * {@link SourceReader/SourcePositions.AbstractKnownSourcePosition._documentContextAfter | _documentContextAfter }, + * are used to implement the aforementioned pattern. */ export abstract class AbstractDocumentSourcePosition extends AbstractKnownSourcePosition { - // =============================================== - // #region Implementation Details { - // ----------------------------------------------- - /** - * Among the different operations provided in this class there are a few - * operations used to determine context of the source input surrounding - * the current position: - * {@link AbstractDocumentSourcePosition.documentContextBefore | documentContextBefore}, and - * {@link AbstractDocumentSourcePosition.documentContextAfter | documentContextAfter}. - * The implementation of all these is achieved by a - * [Template Method Pattern](https://en.wikipedia.org/wiki/Template_method_pattern) - * to provide a common validation and different logics depending on the actual subclass. - * Protected methods - * {@link AbstractKnownSourcePosition._documentContextBefore | _documentContextBefore }, - * and - * {@link AbstractKnownSourcePosition._documentContextAfter | _documentContextAfter }, - * are used to implement the aforementioned pattern. - * - * @group Internal: Implementation Details - * @private - */ - // ----------------------------------------------- - // #endregion } Implementation Details - // =============================================== - // =============================================== // #region API: Properties { // ----------------------------------------------- /** - * @group API: Properties * @inheritdoc */ public readonly isEndOfInput = false; @@ -95,14 +80,14 @@ export abstract class AbstractDocumentSourcePosition extends AbstractKnownSource * * all numbers are >= 0 * * numbers are consistent with the reader state * - * @param sourceReader The {@link SourceReader} of the input this position belongs to. - * @param line The line number of this position in the current input. + * @param sourceReader - The {@link SourceReader} of the input this position belongs to. + * @param line - The line number of this position in the current input. * It will be modified only by the constructor. * **INVARIANT:** `line >=1`, and it is a valid line in that reader. - * @param column The column number of this position in the current input. + * @param column - The column number of this position in the current input. * It will be modified only by the constructor. * **INVARIANT:** `column >= 1` and it is a valid column in that reader. - * @param regions The regions the position in the current input belongs to. + * @param regions - The regions the position in the current input belongs to. * It will be modified only by the constructor. * **INVARIANT:** the regions are valid in the position's reader. * @param _documentIndex The index with information about the input document @@ -116,22 +101,15 @@ export abstract class AbstractDocumentSourcePosition extends AbstractKnownSource * **INVARIANT:** `visibleCharIndex >= 0` and it is a valid index in * that reader. * - * @group Internal: Constructors * @private */ public constructor( sourceReader: SourceReader, - /** @group API: Access */ public readonly line: number, - /** @group API: Access */ public readonly column: number, - /** @group API: Access */ public readonly regions: string[], - /** @group Internal: Properties @private */ public readonly _documentIndex: number, - /** @group Internal: Properties @private */ public readonly _charIndex: number, - /** @group Internal: Properties @private */ public readonly _visibleCharIndex: number ) { super(sourceReader); @@ -142,7 +120,6 @@ export abstract class AbstractDocumentSourcePosition extends AbstractKnownSource /** * @inheritdoc - * @group API: Contents access */ public get fullDocumentContents(): string { return this.sourceReader._fullDocumentContentsAt(this._documentIndex); @@ -150,7 +127,6 @@ export abstract class AbstractDocumentSourcePosition extends AbstractKnownSource /** * @inheritdoc - * @group API: Access */ public get documentName(): string { return this.sourceReader._documentNameAt(this._documentIndex); @@ -158,7 +134,6 @@ export abstract class AbstractDocumentSourcePosition extends AbstractKnownSource /** * @inheritdoc - * @group API: Contents access */ public get visibleDocumentContents(): string { return this.sourceReader._visibleDocumentContentsAt(this._documentIndex); @@ -176,10 +151,8 @@ export abstract class AbstractDocumentSourcePosition extends AbstractKnownSource * of {@link AbstractDocumentSourcePosition.documentContextBefore | documentContextBefore}. * It must be reimplemented by subclasses. * - * @throws {@link InvalidOperationAtUnknownPositionError} if the position is unknown. - * @throws {@link InvalidOperationAtEOIError} if the at the end of input. - * - * @group Internal: Helpers + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} if the position is unknown. + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} if the at the end of input. */ protected abstract _documentContextBefore(lines: number): string[]; @@ -194,10 +167,8 @@ export abstract class AbstractDocumentSourcePosition extends AbstractKnownSource * of {@link AbstractDocumentSourcePosition.documentContextBefore | documentContextBefore}. * It must be reimplemented by subclasses. * - * @throws {@link InvalidOperationAtUnknownPositionError} if the position is unknown. - * @throws {@link InvalidOperationAtEOIError} if the at the end of input. - * - * @group Internal: Helpers + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} if the position is unknown. + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} if the at the end of input. */ protected abstract _documentContextAfter(lines: number): string[]; @@ -215,7 +186,6 @@ export abstract class AbstractDocumentSourcePosition extends AbstractKnownSource /** * @inheritdoc - * @group API: Contents access */ public documentContextBefore(lines: number): string[] { return this._documentContextBefore(lines); @@ -223,7 +193,6 @@ export abstract class AbstractDocumentSourcePosition extends AbstractKnownSource /** * @inheritdoc - * @group API: Contents access */ public documentContextAfter(lines: number): string[] { return this._documentContextAfter(lines); @@ -237,8 +206,7 @@ export abstract class AbstractDocumentSourcePosition extends AbstractKnownSource // ----------------------------------------------- /** * @inheritdoc - * @group Internal: Helpers - * @private + * @internal */ public _internalDocumentIndex(): number { return this._documentIndex; @@ -246,8 +214,7 @@ export abstract class AbstractDocumentSourcePosition extends AbstractKnownSource /** * @inheritdoc - * @group Internal: Helpers - * @private + * @internal */ public _internalCharacterIndex(visible: boolean): number { return visible ? this._visibleCharIndex : this._charIndex; @@ -255,7 +222,6 @@ export abstract class AbstractDocumentSourcePosition extends AbstractKnownSource /** * @inheritdoc - * @group Internal: Helpers */ protected _fullContentsTo(to: AbstractKnownSourcePosition): string { return this.sourceReader._inputFromToIn( @@ -269,7 +235,6 @@ export abstract class AbstractDocumentSourcePosition extends AbstractKnownSource /** * @inheritdoc - * @group Internal: Helpers */ protected _visibleContentsTo(to: AbstractKnownSourcePosition): string { return this.sourceReader._inputFromToIn( @@ -284,6 +249,3 @@ export abstract class AbstractDocumentSourcePosition extends AbstractKnownSource // #endregion } Internal: Helpers // =============================================== } -// ----------------------------------------------- -// #endregion } AbstractDocumentSourcePosition -// =============================================== diff --git a/src/SourceReader/SourcePositions/AbstractKnownSourcePosition.ts b/src/SourceReader/SourcePositions/AbstractKnownSourcePosition.ts index ecdcd94..b648540 100644 --- a/src/SourceReader/SourcePositions/AbstractKnownSourcePosition.ts +++ b/src/SourceReader/SourcePositions/AbstractKnownSourcePosition.ts @@ -10,8 +10,9 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.SourceReader + * @module SourceReader/SourcePositions * @author Alan Rodas Bonjour */ @@ -22,9 +23,6 @@ import { expect } from '../../Expectations'; import { SourceReader } from '../SourceReader'; import { InvalidOperationAtUnknownPositionError, MismatchedInputsError } from '../SourceReaderErrors'; -// =============================================== -// #region AbstractKnownSourcePosition { -// ----------------------------------------------- /** * A {@link AbstractKnownSourcePosition} points to a position in a specific * {@link SourceReader}. It should only be created using the @@ -43,9 +41,6 @@ import { InvalidOperationAtUnknownPositionError, MismatchedInputsError } from '. * As all instances of {@link AbstractKnownSourcePosition} represent a known * position, they may be queried to obtain the line, column, regions and * surrounding contents. - * - * @group Internals: Source Positions - * @private */ export abstract class AbstractKnownSourcePosition extends AbstractSourcePosition { // =============================================== @@ -71,7 +66,6 @@ export abstract class AbstractKnownSourcePosition extends AbstractSourcePosition * {@link AbstractKnownSourcePosition._fullContentsFrom | _fullContentsFrom }, * are used to implement the aforementioned pattern. * - * @group Internal: Implementation Details * @private */ // ----------------------------------------------- @@ -83,13 +77,11 @@ export abstract class AbstractKnownSourcePosition extends AbstractSourcePosition // ----------------------------------------------- /** * @inheritdoc - * @group API: Properties */ public abstract readonly isEndOfInput: boolean; /** * @inheritdoc - * @group API: Properties */ public readonly isUnknown = false; @@ -108,15 +100,9 @@ export abstract class AbstractKnownSourcePosition extends AbstractSourcePosition * * all numbers are >= 0 * * numbers are consistent with the reader's state * - * @param sourceReader The {@link SourceReader} of the input this position belongs to. - * - * @group Internal: Constructors - * @private + * @param sourceReader - The {@link SourceReader} of the input this position belongs to. */ - public constructor( - /** @group API: Access */ - public readonly sourceReader: SourceReader - ) { + public constructor(public readonly sourceReader: SourceReader) { super(); } // ----------------------------------------------- @@ -131,8 +117,7 @@ export abstract class AbstractKnownSourcePosition extends AbstractSourcePosition * corresponds is stored. * It may be one longer that the lenght, in case the position is EOI. * - * @group Internal: Helpers - * @private + * @internal */ public abstract _internalDocumentIndex(): number; @@ -141,8 +126,7 @@ export abstract class AbstractKnownSourcePosition extends AbstractSourcePosition * character which this positions corresponds to is stored. * It may be one longer thant the lenght, in case the position is EOD. * - * @group Internal: Helpers - * @private + * @internal */ public abstract _internalCharacterIndex(visible: boolean): number; @@ -159,12 +143,10 @@ export abstract class AbstractKnownSourcePosition extends AbstractSourcePosition * **PRECONDITION:** both positions correspond to the same reader (not * validated, as it is a protected operation). * - * @param to A {@link AbstractKnownSourcePosition} related with the same + * @param to - A {@link AbstractKnownSourcePosition} related with the same * {@link SourceReader} that the receiver. * It indicates a final position to consult (not included), * where the receiver is the first. - * - * @group Internal: Helpers */ protected abstract _fullContentsTo(to: AbstractKnownSourcePosition): string; @@ -181,18 +163,15 @@ export abstract class AbstractKnownSourcePosition extends AbstractSourcePosition * **PRECONDITION:** both positions correspond to the same reader (not * validated, as it is a protected operation). * - * @param to A {@link AbstractKnownSourcePosition} related with the same + * @param to - A {@link AbstractKnownSourcePosition} related with the same * {@link SourceReader} that the receiver. * It indicates the final position to consult (not included), * where the receiver is the first. - * - * @group Internal: Helpers */ protected abstract _visibleContentsTo(to: AbstractKnownSourcePosition): string; /** * @inheritdoc - * @group API: Contents access */ public fullContentsFrom(from: SourcePosition): string { this._validateSourceReaders(from, 'fullContentsFrom', 'AbstractKnownSourcePosition'); @@ -201,7 +180,6 @@ export abstract class AbstractKnownSourcePosition extends AbstractSourcePosition /** * @inheritdoc - * @group API: Contents access */ public fullContentsTo(to: SourcePosition): string { this._validateSourceReaders(to, 'fullContentsTo', 'AbstractKnownSourcePosition'); @@ -210,7 +188,6 @@ export abstract class AbstractKnownSourcePosition extends AbstractSourcePosition /** * @inheritdoc - * @group API: Contents access */ public visibleContentsTo(to: SourcePosition): string { this._validateSourceReaders(to, 'visibleContentsTo', 'AbstractKnownSourcePosition'); @@ -219,7 +196,6 @@ export abstract class AbstractKnownSourcePosition extends AbstractSourcePosition /** * @inheritdoc - * @group API: Contents access */ public visibleContentsFrom(from: SourcePosition): string { this._validateSourceReaders(from, 'visibleContentsFrom', 'AbstractKnownSourcePosition'); @@ -239,12 +215,10 @@ export abstract class AbstractKnownSourcePosition extends AbstractSourcePosition * * Implements a common validation for the Template Method Pattern. * - * @throws {@link InvalidOperationAtUnknownPositionError} + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} * if the receiver or the argument positions are unknown. - * @throws {@link MismatchedInputsError} + * @throws {@link SourceReader/Errors.MismatchedInputsError} * if the receiver and the argument positions do not belong to the same reader. - * - * @group Internal: Helpers */ protected _validateSourceReaders(that: SourcePosition, operation: string, context: string): void { expect(that.isUnknown).toBeFalse().orThrow(new InvalidOperationAtUnknownPositionError(operation, context)); @@ -267,12 +241,10 @@ export abstract class AbstractKnownSourcePosition extends AbstractSourcePosition * * both positions correspond to the same reader * (not validated, as it is a protected operation). * - * @param from An {@link AbstractKnownSourcePosition} related with the same + * @param from - An {@link AbstractKnownSourcePosition} related with the same * {@link SourceReader} that the receiver. * It indicates the starting position to consult, * where the receiver is the last (not included). - * - * @group Internal: Helpers */ protected _fullContentsFrom(from: AbstractKnownSourcePosition): string { return this.sourceReader._inputFromToIn( @@ -297,12 +269,10 @@ export abstract class AbstractKnownSourcePosition extends AbstractSourcePosition * **PRECONDITION:** both positions correspond to the same reader (not * validated, as it is a protected operation). * - * @param from A {@link AbstractKnownSourcePosition} related with the same - * {@link SourceReader} that the receiver. + * @param from - A {@link AbstractKnownSourcePosition} related with the same + * {@link SourceReader.SourceReader} that the receiver. * It indicates the starting position to consult, * where the receiver is the last (not included). - * - * @group Internal: Helpers */ protected _visibleContentsFrom(from: AbstractKnownSourcePosition): string { return this.sourceReader._inputFromToIn( @@ -317,6 +287,3 @@ export abstract class AbstractKnownSourcePosition extends AbstractSourcePosition // #endregion } Internal: Helpers // =============================================== } -// ----------------------------------------------- -// #endregion } AbstractKnownSourcePosition -// =============================================== diff --git a/src/SourceReader/SourcePositions/AbstractSourcePosition.ts b/src/SourceReader/SourcePositions/AbstractSourcePosition.ts index 3f188f3..ae53a26 100644 --- a/src/SourceReader/SourcePositions/AbstractSourcePosition.ts +++ b/src/SourceReader/SourcePositions/AbstractSourcePosition.ts @@ -10,10 +10,12 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.SourceReader + * @module SourceReader/SourcePositions * @author Alan Rodas Bonjour */ + import { SourcePosition } from './SourcePosition'; /** @@ -21,98 +23,70 @@ import { SourcePosition } from './SourcePosition'; * its purpose is to be the top of the hierarchy of different kinds of positions. * Subclasses determine if the position is unknown or known, and in this last case, * its different types. - * - * @group Internals: Source Positions - * @private */ export abstract class AbstractSourcePosition implements SourcePosition { - /** - * @group Internal: Constructors - * @private - */ - // This empty declaration is needed for its documentation. - // If the documentation does not appear, it takes the default (public, Constructor) - // instead of the same as all the others in the hierarchy. - // eslint-disable-next-line @typescript-eslint/no-empty-function - public constructor() {} /** * @inheritdoc - * @group API: Properties */ public abstract get isUnknown(): boolean; /** * @inheritdoc - * @group API: Properties */ public abstract get isEndOfInput(): boolean; /** * @inheritdoc - * @group API: Properties */ public abstract get isEndOfDocument(): boolean; /** * @inheritdoc - * @group API: Access */ public abstract get line(): number; /** * @inheritdoc - * @group API: Access */ public abstract get column(): number; /** * @inheritdoc - * @group API: Access */ public abstract get regions(): string[]; /** * @inheritdoc - * @group API: Access */ public abstract get documentName(): string; /** * @inheritdoc - * @group API: Contents access */ public abstract get fullDocumentContents(): string; /** * @inheritdoc - * @group API: Contents access */ public abstract get visibleDocumentContents(): string; /** * @inheritdoc - * @group API: Printing */ public abstract toString(): string; /** * @inheritdoc - * @group API: Contents access */ public abstract fullContentsFrom(from: SourcePosition): string; /** * @inheritdoc - * @group API: Contents access */ public abstract fullContentsTo(from: SourcePosition): string; /** * @inheritdoc - * @group API: Contents access */ public abstract visibleContentsFrom(from: SourcePosition): string; /** * @inheritdoc - * @group API: Contents access */ public abstract visibleContentsTo(to: SourcePosition): string; /** * @inheritdoc - * @group API: Contents access */ public abstract documentContextBefore(lines: number): string[]; /** * @inheritdoc - * @group API: Contents access */ public abstract documentContextAfter(lines: number): string[]; } diff --git a/src/SourceReader/SourcePositions/DocumentSourcePosition.ts b/src/SourceReader/SourcePositions/DocumentSourcePosition.ts index 97ffd39..0dfa9fd 100644 --- a/src/SourceReader/SourcePositions/DocumentSourcePosition.ts +++ b/src/SourceReader/SourcePositions/DocumentSourcePosition.ts @@ -10,41 +10,27 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.SourceReader + * @module SourceReader/SourcePositions * @author Alan Rodas Bonjour */ + import { AbstractDocumentSourcePosition } from './AbstractDocumentSourcePosition'; import { SourcePosition } from './SourcePosition'; import { SourceReader } from '../SourceReader'; -// =============================================== -// #region DocumentSourcePosition { -// ----------------------------------------------- /** * A {@link DocumentSourcePosition} points to a particular position, different * from EndOfDocument, inside a particular document. - * - * @group Internals: Source Positions - * @private */ export class DocumentSourcePosition extends AbstractDocumentSourcePosition implements SourcePosition { - // =============================================== - // #region API: Properties { - // ----------------------------------------------- /** * @inheritdoc - * @group API: Properties */ public readonly isEndOfDocument: boolean = false; - // ----------------------------------------------- - // #endregion } API: Properties - // =============================================== - // =============================================== - // #region Internal: Constructors { - // ----------------------------------------------- /** * Constructs a defined position different from the end of a document in an * input source. @@ -53,8 +39,6 @@ export class DocumentSourcePosition extends AbstractDocumentSourcePosition imple * **PRECONDITIONS:** (not verified during execution) * * all numbers are >= 0 * * numbers are consistent with the reader state - * @group Internal: Constructors - * @private */ public constructor( sourceReader: SourceReader, @@ -67,16 +51,9 @@ export class DocumentSourcePosition extends AbstractDocumentSourcePosition imple ) { super(sourceReader, line, column, regions, documentIndex, charIndex, visibleCharIndex); } - // ----------------------------------------------- - // #endregion } Internal: Constructors - // =============================================== - // =============================================== - // #region API: Printing { - // ----------------------------------------------- /** * @inheritdoc - * @group API: Printing */ public toString(): string { // if ( @@ -89,16 +66,9 @@ export class DocumentSourcePosition extends AbstractDocumentSourcePosition imple // } return `@<${this.documentName}:${this.line},${this.column}>`; } - // ----------------------------------------------- - // #endregion } API: Printing - // =============================================== - // =============================================== - // #region Internal: Helpers { - // ----------------------------------------------- /** * @inheritdoc - * @group Internal: Helpers */ protected _documentContextBefore(lines: number): string[] { return this.sourceReader._documentContextBeforeOf( @@ -110,7 +80,6 @@ export class DocumentSourcePosition extends AbstractDocumentSourcePosition imple /** * @inheritdoc - * @group Internal: Helpers */ protected _documentContextAfter(lines: number): string[] { return this.sourceReader._documentContextAfterOf( @@ -119,10 +88,4 @@ export class DocumentSourcePosition extends AbstractDocumentSourcePosition imple lines ); } - // ----------------------------------------------- - // #endregion } Internal: Helpers - // =============================================== } -// ----------------------------------------------- -// #endregion } EndOfDocumentSourcePosition -// =============================================== diff --git a/src/SourceReader/SourcePositions/EndOfDocumentSourcePosition.ts b/src/SourceReader/SourcePositions/EndOfDocumentSourcePosition.ts index 15c0335..51ae1a5 100644 --- a/src/SourceReader/SourcePositions/EndOfDocumentSourcePosition.ts +++ b/src/SourceReader/SourcePositions/EndOfDocumentSourcePosition.ts @@ -10,8 +10,9 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.SourceReader + * @module SourceReader/SourcePositions * @author Alan Rodas Bonjour */ import { AbstractDocumentSourcePosition } from './AbstractDocumentSourcePosition'; @@ -19,9 +20,6 @@ import { SourcePosition } from './SourcePosition'; import { SourceReader } from '../SourceReader'; -// =============================================== -// #region EndOfDocumentSourcePosition { -// ----------------------------------------------- /** * An {@link EndOfDocumentSourcePosition} points to a position that is right * after the last character in a specific document of a {@link SourceReader}. @@ -30,26 +28,13 @@ import { SourceReader } from '../SourceReader'; * next document. * It is a special position, because it does not point to a particular position * inside a document in the source input, but to the end of one of the documents in it. - * - * @group Internals: Source Positions - * @private */ export class EndOfDocumentSourcePosition extends AbstractDocumentSourcePosition implements SourcePosition { - // =============================================== - // #region API: Properties { - // ----------------------------------------------- /** * @inheritdoc - * @group API: Properties */ public readonly isEndOfDocument: boolean = true; - // ----------------------------------------------- - // #endregion } API: Properties - // =============================================== - // =============================================== - // #region Internal: Constructors { - // ----------------------------------------------- /** * Constructs an end of document position in an input source. * It is intended to be used only by {@link SourceReader}. @@ -58,29 +43,26 @@ export class EndOfDocumentSourcePosition extends AbstractDocumentSourcePosition * * all numbers are >= 0 * * numbers are consistent with the reader state * - * @param sourceReader The {@link SourceReader} of the input this position belongs to. - * @param line The line number of this position in the current input. + * @param sourceReader - The {@link SourceReader} of the input this position belongs to. + * @param line - The line number of this position in the current input. * It will be modified only by the constructor. * **INVARIANT:** `line >=1`, and it is a valid line in that reader. - * @param column The column number of this position in the current input. + * @param column - The column number of this position in the current input. * It will be modified only by the constructor. * **INVARIANT:** `column >= 1` and it is a valid column in that reader. - * @param regions The regions the position in the current input belongs to. + * @param regions - The regions the position in the current input belongs to. * It will be modified only by the constructor. * **INVARIANT:** the regions are valid in the position's reader. - * @param documentIndex The index with information about the input document + * @param documentIndex - The index with information about the input document * in the `_sourceReader`. **INVARIANT**: `documentIndex >= 0` and it * is a valid index in that reader. - * @param charIndex The index with information about the exact char pointed + * @param charIndex - The index with information about the exact char pointed * to by this position in the input document. **INVARIANT:** * `charIndex >= 0` and it is a valid index in that reader. - * @param visibleCharIndex The index with information about the exact char + * @param visibleCharIndex - The index with information about the exact char * pointed to by this position in the visible input document. * **INVARIANT:** `visibleCharIndex >= 0` and it is a valid index in * that reader. - * - * @group Internal: Contructors - * @private */ public constructor( sourceReader: SourceReader, @@ -93,30 +75,16 @@ export class EndOfDocumentSourcePosition extends AbstractDocumentSourcePosition ) { super(sourceReader, line, column, regions, documentIndex, charIndex, visibleCharIndex); } - // ----------------------------------------------- - // #endregion } Internal: Constructors - // =============================================== - // =============================================== - // #region API: Printing { - // ----------------------------------------------- /** * @inheritdoc - * @group API: Printing */ public toString(): string { return '@'; } - // ----------------------------------------------- - // #endregion } API: Printing - // =============================================== - // =============================================== - // #region Internal: Helpers { - // ----------------------------------------------- /** * @inheritdoc - * @group Internal: Helpers */ protected _documentContextBefore(lines: number): string[] { return this.sourceReader._documentContextBeforeOf( @@ -128,15 +96,8 @@ export class EndOfDocumentSourcePosition extends AbstractDocumentSourcePosition /** * @inheritdoc - * @group Internal: Helpers */ - protected _documentContextAfter(lines: number): string[] { + protected _documentContextAfter(_lines: number): string[] { return ['']; } - // ----------------------------------------------- - // #endregion } Internal: Helpers - // =============================================== } -// ----------------------------------------------- -// #endregion } EndOfDocumentSourcePosition -// =============================================== diff --git a/src/SourceReader/SourcePositions/EndOfInputSourcePosition.ts b/src/SourceReader/SourcePositions/EndOfInputSourcePosition.ts index 5b79219..dec9481 100644 --- a/src/SourceReader/SourcePositions/EndOfInputSourcePosition.ts +++ b/src/SourceReader/SourcePositions/EndOfInputSourcePosition.ts @@ -10,10 +10,12 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.SourceReader + * @module SourceReader/SourcePositions * @author Alan Rodas Bonjour */ + import { AbstractKnownSourcePosition } from './AbstractKnownSourcePosition'; import { SourcePosition } from './SourcePosition'; @@ -26,9 +28,6 @@ import { InvalidOperationAtEOIError } from '../SourceReaderErrors'; * That position is reached when all input documents have been processed. * It is a special position, because it does not point to a particular position * inside a document in the source input, but to the end of it. - * - * @group Internals: Source Positions - * @private */ export class EndOfInputSourcePosition extends AbstractKnownSourcePosition implements SourcePosition { // =============================================== @@ -54,9 +53,7 @@ export class EndOfInputSourcePosition extends AbstractKnownSourcePosition implem * * all numbers are >= 0 * * numbers are consistent with the reader state * - * @param sourceReader The {@link SourceReader} of the input this position belongs to. - * @group Internal: Constructors - * @private + * @param sourceReader - The {@link SourceReader} of the input this position belongs to. */ public constructor(sourceReader: SourceReader) { super(sourceReader); @@ -70,7 +67,6 @@ export class EndOfInputSourcePosition extends AbstractKnownSourcePosition implem // ----------------------------------------------- /** * @inheritdoc - * @group API: Properties */ public get isEndOfDocument(): boolean { throw new InvalidOperationAtEOIError('isEndOfDocument', 'EndOfInputSourcePosition'); @@ -84,7 +80,6 @@ export class EndOfInputSourcePosition extends AbstractKnownSourcePosition implem // ----------------------------------------------- /** * @inheritdoc - * @group API: Access */ public get line(): number { throw new InvalidOperationAtEOIError('line', 'EndOfInputSourcePosition'); @@ -92,7 +87,6 @@ export class EndOfInputSourcePosition extends AbstractKnownSourcePosition implem /** * @inheritdoc - * @group API: Access */ public get column(): number { throw new InvalidOperationAtEOIError('column', 'EndOfInputSourcePosition'); @@ -100,7 +94,6 @@ export class EndOfInputSourcePosition extends AbstractKnownSourcePosition implem /** * @inheritdoc - * @group API: Access */ public get regions(): string[] { throw new InvalidOperationAtEOIError('regions', 'EndOfInputSourcePosition'); @@ -108,7 +101,6 @@ export class EndOfInputSourcePosition extends AbstractKnownSourcePosition implem /** * @inheritdoc - * @group API: Access */ public get documentName(): string { throw new InvalidOperationAtEOIError('documentName', 'EndOfInputSourcePosition'); @@ -122,7 +114,6 @@ export class EndOfInputSourcePosition extends AbstractKnownSourcePosition implem // ----------------------------------------------- /** * @inheritdoc - * @group API: Contents access */ public get fullDocumentContents(): string { throw new InvalidOperationAtEOIError('fullDocumentContents', 'EndOfInputSourcePosition'); @@ -130,7 +121,6 @@ export class EndOfInputSourcePosition extends AbstractKnownSourcePosition implem /** * @inheritdoc - * @group API: Contents access */ public get visibleDocumentContents(): string { throw new InvalidOperationAtEOIError('visibleDocumentContents', 'EndOfInputSourcePosition'); @@ -138,17 +128,15 @@ export class EndOfInputSourcePosition extends AbstractKnownSourcePosition implem /** * @inheritdoc - * @group API: Contents access */ - public documentContextBefore(lines: number): string[] { + public documentContextBefore(_lines: number): string[] { throw new InvalidOperationAtEOIError('documentContextBefore', 'EndOfInputSourcePosition'); } /** * @inheritdoc - * @group API: Contents access */ - public documentContextAfter(lines: number): string[] { + public documentContextAfter(_lines: number): string[] { throw new InvalidOperationAtEOIError('documentContextAfter', 'EndOfInputSourcePosition'); } // ----------------------------------------------- @@ -160,22 +148,17 @@ export class EndOfInputSourcePosition extends AbstractKnownSourcePosition implem // ----------------------------------------------- /** * @inheritdoc - * @group API: Printing */ public toString(): string { return '@'; } - // ----------------------------------------------- - // #endregion } API: Printing - // =============================================== // =============================================== // #region Internal: Helpers { // ----------------------------------------------- /** * @inheritdoc - * @group Internal: Helpers - * @private + * @internal */ public _internalDocumentIndex(): number { return this.sourceReader.documentsNames.length - 1; @@ -183,26 +166,23 @@ export class EndOfInputSourcePosition extends AbstractKnownSourcePosition implem /** * @inheritdoc - * @group Internal: Helpers - * @private + * @internal */ - public _internalCharacterIndex(visible: boolean): number { + public _internalCharacterIndex(_visible: boolean): number { return this.sourceReader._fullDocumentContentsAt(this._internalDocumentIndex()).length; } /** * @inheritdoc - * @group Internal: Helpers */ - protected _fullContentsTo(to: AbstractKnownSourcePosition): string { + protected _fullContentsTo(_to: AbstractKnownSourcePosition): string { return ''; } /** * @inheritdoc - * @group Internal: Helpers */ - protected _visibleContentsTo(to: AbstractKnownSourcePosition): string { + protected _visibleContentsTo(_to: AbstractKnownSourcePosition): string { return ''; } // ----------------------------------------------- diff --git a/src/SourceReader/SourcePositions/SourcePosition.ts b/src/SourceReader/SourcePositions/SourcePosition.ts index ea9e475..3821c0a 100644 --- a/src/SourceReader/SourcePositions/SourcePosition.ts +++ b/src/SourceReader/SourcePositions/SourcePosition.ts @@ -10,23 +10,12 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.SourceReader + * @module SourceReader/SourcePositions * @author Alan Rodas Bonjour */ -// These imports are needed for typedoc to find the references -import { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - InvalidOperationAtUnknownPositionError, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - InvalidOperationAtEOIError, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - MismatchedInputsError -} from '../SourceReaderErrors'; -// =============================================== -// #region SourcePosition { -// ----------------------------------------------- /** * {@link SourcePosition}s point to particular positions in the source given by a * {@link SourceReader}. @@ -44,7 +33,6 @@ import { * A typical use of {@link SourcePosition}s is relating nodes of an AST * representation of code to particular positions in the string version of the * source code (that may come from several input documents). - * @group API: Main */ export interface SourcePosition { // =============================================== @@ -53,8 +41,6 @@ export interface SourcePosition { /** * Answers if this position correspond to one in some {@link SourceReader}, * or if it is unknown. - * - * @group API: Properties */ readonly isUnknown: boolean; @@ -66,9 +52,7 @@ export interface SourcePosition { * **PRECONDITIONS:** * * the position is known * - * @throws {@link InvalidOperationAtUnknownPositionError} if the position is unknown. - * - * @group API: Properties + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} if the position is unknown. */ readonly isEndOfInput: boolean; @@ -83,10 +67,8 @@ export interface SourcePosition { * * the position is not unknown * * the position is not at the end of input * - * @throws {@link InvalidOperationAtUnknownPositionError} if the position is unknown. - * @throws {@link InvalidOperationAtEOIError} if the position is at the end of input. - * - * @group API: Properties + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} if the position is unknown. + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} if the position is at the end of input. */ readonly isEndOfDocument: boolean; // ----------------------------------------------- @@ -103,10 +85,8 @@ export interface SourcePosition { * * the position is not unknown * * the position is not at the end of input * - * @throws {@link InvalidOperationAtUnknownPositionError} if the position is unknown. - * @throws {@link InvalidOperationAtEOIError} if the position is at the end of input. - * - * @group API: Access + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} if the position is unknown. + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} if the position is at the end of input. */ readonly documentName: string; @@ -119,10 +99,8 @@ export interface SourcePosition { * * **INVARIANT:** `line >=1`, and it is a valid line in that reader. * - * @throws {@link InvalidOperationAtUnknownPositionError} if the position is unknown. - * @throws {@link InvalidOperationAtEOIError} if the position is at the end of input. - * - * @group API: Access + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} if the position is unknown. + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} if the position is at the end of input. */ readonly line: number; @@ -134,10 +112,8 @@ export interface SourcePosition { * * the position is not at the end of input * * **INVARIANT:** `column >= 1` and it is a valid column in that reader. - * @throws {@link InvalidOperationAtUnknownPositionError} if the position is unknown. - * @throws {@link InvalidOperationAtEOIError} if the position is at the end of input. - * - * @group API: Access + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} if the position is unknown. + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} if the position is at the end of input. */ readonly column: number; @@ -149,10 +125,8 @@ export interface SourcePosition { * * the position is not at the end of input * * **INVARIANT:** the regions are valid in the position's reader. - * @throws {@link InvalidOperationAtUnknownPositionError} if the position is unknown. - * @throws {@link InvalidOperationAtEOIError} if the position is at the end of input. - * - * @group API: Access + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} if the position is unknown. + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} if the position is at the end of input. */ readonly regions: string[]; // ----------------------------------------------- @@ -170,12 +144,10 @@ export interface SourcePosition { * * the position is not unknown * * the position is not at the end of input * - * @throws {@link InvalidOperationAtUnknownPositionError} + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} * if the receiver is unknown. - * @throws {@link InvalidOperationAtEOIError} + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} * if the receiver is the end of input. - * - * @group API: Contents access */ readonly fullDocumentContents: string; @@ -186,12 +158,10 @@ export interface SourcePosition { * * the position is not unknown * * the position is not at the end of input * - * @throws {@link InvalidOperationAtUnknownPositionError} + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} * if the receiver is unknown. - * @throws {@link InvalidOperationAtEOIError} + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} * if the receiver is the end of input. - * - * @group API: Contents access */ readonly visibleDocumentContents: string; @@ -206,19 +176,17 @@ export interface SourcePosition { * * both positions correspond to the same reader * * the receiver is not at the end of input * - * @param from + * @param from - * A {@link SourcePosition} related with the same {@link SourceReader} * that the receiver. * It indicates a starting position to consult, where the receiver is the last. * - * @throws {@link InvalidOperationAtUnknownPositionError} + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} * if the receiver or the argument positions are unknown. - * @throws {@link MismatchedInputsError} + * @throws {@link SourceReader/Errors.MismatchedInputsError} * if the receiver and the argument positions do not belong to the same reader. - * @throws {@link InvalidOperationAtEOIError} + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} * if the receiver is the end of input. - * - * @group API: Contents access */ fullContentsFrom(from: SourcePosition): string; @@ -232,19 +200,17 @@ export interface SourcePosition { * * both positions correspond to the same reader * * the receiver is not at the end of input * - * @param to + * @param to - * A {@link SourcePosition} related with the same {@link SourceReader} that * the receiver. * It indicates a final position to consult, where the receiver is the first. * - * @throws {@link InvalidOperationAtUnknownPositionError} + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} * if the receiver or the argument positions are unknown. - * @throws {@link MismatchedInputsError} + * @throws {@link SourceReader/Errors.MismatchedInputsError} * if the receiver and the argument positions do not belong to the same reader. - * @throws {@link InvalidOperationAtEOIError} + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} * if the receiver is the end of input. - * - * @group API: Contents access */ fullContentsTo(to: SourcePosition): string; @@ -258,19 +224,17 @@ export interface SourcePosition { * * both positions correspond to the same reader * * the receiver is not at the end of input * - * @param from + * @param from - * A {@link SourcePosition} related with the same {@link SourceReader} that * the receiver. * It indicates a starting position to consult, where the receiver is the last. * - * @throws {@link InvalidOperationAtUnknownPositionError} + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} * if the receiver or the argument positions are unknown. - * @throws {@link MismatchedInputsError} + * @throws {@link SourceReader/Errors.MismatchedInputsError} * if the receiver and the argument positions do not belong to the same reader. - * @throws {@link InvalidOperationAtEOIError} + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} * if the receiver is the end of input. - * - * @group API: Contents access */ visibleContentsFrom(from: SourcePosition): string; @@ -284,19 +248,17 @@ export interface SourcePosition { * * both positions correspond to the same reader * * the receiver is not at the end of input * - * @param to + * @param to - * A {@link SourcePosition} related with the same {@link SourceReader} that * the receiver. * It indicates a final position to consult, where the receiver is the first. * - * @throws {@link InvalidOperationAtUnknownPositionError} + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} * if the receiver or the argument positions are unknown. - * @throws {@link MismatchedInputsError} + * @throws {@link SourceReader/Errors.MismatchedInputsError} * if the receiver and the argument positions do not belong to the same reader. - * @throws {@link InvalidOperationAtEOIError} + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} * if the receiver is the end of input. - * - * @group API: Contents access */ visibleContentsTo(to: SourcePosition): string; @@ -310,10 +272,8 @@ export interface SourcePosition { * * the position is not unknown * * the position is not at the end of input * - * @throws {@link InvalidOperationAtUnknownPositionError} if the position is unknown. - * @throws {@link InvalidOperationAtEOIError} if the position is at the end of input. - * - * @group API: Contents access + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} if the position is unknown. + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} if the position is at the end of input. */ documentContextBefore(lines: number): string[]; @@ -327,10 +287,8 @@ export interface SourcePosition { * * the position is not unknown * * the position is not at the end of input * - * @throws {@link InvalidOperationAtUnknownPositionError} if the position is unknown. - * @throws {@link InvalidOperationAtEOIError} if the position is at the end of input. - * - * @group API: Contents access + * @throws {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} if the position is unknown. + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} if the position is at the end of input. */ documentContextAfter(lines: number): string[]; // ----------------------------------------------- @@ -343,14 +301,9 @@ export interface SourcePosition { /** * Gives a string representation of the position. * It is NOT useful for persistence, as it may loose information. - * - * @group API: Printing */ toString(): string; // ----------------------------------------------- // #endregion } API: Printing // =============================================== } -// ----------------------------------------------- -// #endregion } SourcePosition -// =============================================== diff --git a/src/SourceReader/SourcePositions/SourcePositions.ts b/src/SourceReader/SourcePositions/SourcePositions.ts index 242255c..577692d 100644 --- a/src/SourceReader/SourcePositions/SourcePositions.ts +++ b/src/SourceReader/SourcePositions/SourcePositions.ts @@ -10,10 +10,12 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.SourceReader + * @module SourceReader/SourcePositions * @author Alan Rodas Bonjour */ + import { DocumentSourcePosition } from './DocumentSourcePosition'; import { EndOfDocumentSourcePosition } from './EndOfDocumentSourcePosition'; import { EndOfInputSourcePosition } from './EndOfInputSourcePosition'; @@ -25,9 +27,6 @@ import { SourceReader } from '../SourceReader'; * The constant implementing the * [Abstract Factory Pattern](https://en.wikipedia.org/wiki/Abstract_factory_pattern) * for {@link SourcePosition}s. - * - * @group Internal: Main - * @private */ export const SourcePositions = { Unknown: () => UnknownSourcePosition.instance, diff --git a/src/SourceReader/SourcePositions/SourceSpan.ts b/src/SourceReader/SourcePositions/SourceSpan.ts index d6d9e56..ad3e0f8 100644 --- a/src/SourceReader/SourcePositions/SourceSpan.ts +++ b/src/SourceReader/SourcePositions/SourceSpan.ts @@ -10,51 +10,39 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.SourceReader + * @module SourceReader/SourcePositions * @author Alan Rodas Bonjour */ + import { SourcePosition } from './SourcePosition'; import { SourcePositions } from './SourcePositions'; /** - * A {@link SourceSpan} delimitates a span in a {@link SourceInput}. + * A {@link SourceSpan} delimitates a span in a {@link SourceReader.SourceInput}. * To do that, it has {@link SourceSpan.start | start} and {@link SourceSpan.end | end} positions, indicating * where the span of input starts and ends, respectively. * * **REPRESENTATION INVARIANT: (not verified)** * * both start and end positions belongs to the same {@link SourceReader} * * start position appears before end position, if both of them are not Unkwnown. - * @group API: Main + * + * @privateRemarks + * Properties {@link SourceSpan.start | start} and {@link SourceSpan.end | end} indicate + * where the span of input starts and ends, respectively. + * If the span only spans over 1 characters, the end position may not be specified. + * If the start position is unknown, the end position should also be unknown. + * Those conditions are guaranteed on construction. */ export class SourceSpan { - // =============================================== - // #region Implementation Details { - // ----------------------------------------------- - /** - * Properties {@link SourceSpan.start | start} and {@link SourceSpan.end | end} indicate - * where the span of input starts and ends, respectively. - * If the span only spans over 1 characters, the end position may not be specified. - * If the start position is unknown, the end position should also be unknown. - * Those conditions are guaranteed on construction. - * @group Implementation Details - * @private - */ - // ----------------------------------------------- - // #endregion } Implementation Details - // =============================================== - - // =============================================== - // #region API { - // ----------------------------------------------- /** * The start position of the span. - * @group API */ public readonly start: SourcePosition = SourcePositions.Unknown(); + /** * The end position of the span. - * @group API */ public readonly end: SourcePosition = SourcePositions.Unknown(); @@ -64,13 +52,9 @@ export class SourceSpan { * If end is unknown but not the start, they are made equal (the span is a single position). * If start is unknown but not the end, the end is considered an incorrect value, * and made unknown. - * @group API */ public constructor(start?: SourcePosition, end?: SourcePosition) { this.start = start ?? SourcePositions.Unknown(); - this.end = start ? end ?? this.start : this.start; + this.end = start ? (end ?? this.start) : this.start; } - // ----------------------------------------------- - // #endregion } Implementation Details - // =============================================== } diff --git a/src/SourceReader/SourcePositions/UnknownSourcePosition.ts b/src/SourceReader/SourcePositions/UnknownSourcePosition.ts index b3f90c2..be94944 100644 --- a/src/SourceReader/SourcePositions/UnknownSourcePosition.ts +++ b/src/SourceReader/SourcePositions/UnknownSourcePosition.ts @@ -10,18 +10,17 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.SourceReader + * @module SourceReader/SourcePositions * @author Alan Rodas Bonjour */ + import { AbstractSourcePosition } from './AbstractSourcePosition'; import { SourcePosition } from './SourcePosition'; import { InvalidOperationAtUnknownPositionError } from '../SourceReaderErrors'; -// =============================================== -// #region UnknownSourcePosition { -// ----------------------------------------------- /** * An {@link UnknownSourcePosition} represents an unknown source position, * that is, it does not point to any position in any source reader. @@ -37,9 +36,6 @@ import { InvalidOperationAtUnknownPositionError } from '../SourceReaderErrors'; * This class has a single instance, accessible through the * {@link UnknownSourcePosition.instance | instance} static field, * and cannot be further instantiated. - * - * @group Internals: Source Positions - * @private */ export class UnknownSourcePosition extends AbstractSourcePosition implements SourcePosition { // =============================================== @@ -47,8 +43,6 @@ export class UnknownSourcePosition extends AbstractSourcePosition implements Sou // ----------------------------------------------- /** * Returns the single instance of this class. - * @group Internal: Constructors - * @private */ public static readonly instance = new UnknownSourcePosition(); // ----------------------------------------------- @@ -60,7 +54,6 @@ export class UnknownSourcePosition extends AbstractSourcePosition implements Sou // ----------------------------------------------- /** * @inheritdoc - * @group API: Properties */ public readonly isUnknown: boolean = true; // ----------------------------------------------- @@ -73,9 +66,6 @@ export class UnknownSourcePosition extends AbstractSourcePosition implements Sou /** * Returns an instance of this class. * Made private to follow the singleton pattern. - * - * @group Internal: Constructors - * @private */ private constructor() { super(); @@ -89,7 +79,6 @@ export class UnknownSourcePosition extends AbstractSourcePosition implements Sou // ----------------------------------------------- /** * @inheritdoc - * @group API: Properties */ public get isEndOfInput(): boolean { throw new InvalidOperationAtUnknownPositionError('isEndOfInput', 'UnknownSourcePosition'); @@ -97,7 +86,6 @@ export class UnknownSourcePosition extends AbstractSourcePosition implements Sou /** * @inheritdoc - * @group API: Properties */ public get isEndOfDocument(): boolean { throw new InvalidOperationAtUnknownPositionError('isEndOfDocument', 'UnknownSourcePosition'); @@ -111,7 +99,6 @@ export class UnknownSourcePosition extends AbstractSourcePosition implements Sou // ----------------------------------------------- /** * @inheritdoc - * @group API: Access */ public get line(): number { throw new InvalidOperationAtUnknownPositionError('line', 'UnknownSourcePosition'); @@ -119,7 +106,6 @@ export class UnknownSourcePosition extends AbstractSourcePosition implements Sou /** * @inheritdoc - * @group API: Access */ public get column(): number { throw new InvalidOperationAtUnknownPositionError('column', 'UnknownSourcePosition'); @@ -135,7 +121,6 @@ export class UnknownSourcePosition extends AbstractSourcePosition implements Sou /** * @inheritdoc - * @group API: Access */ public get documentName(): string { throw new InvalidOperationAtUnknownPositionError('documentName', 'UnknownSourcePosition'); @@ -149,7 +134,6 @@ export class UnknownSourcePosition extends AbstractSourcePosition implements Sou // ----------------------------------------------- /** * @inheritdoc - * @group API: Content access */ public get fullDocumentContents(): string { throw new InvalidOperationAtUnknownPositionError('fullDocumentContents', 'UnknownSourcePosition'); @@ -157,7 +141,6 @@ export class UnknownSourcePosition extends AbstractSourcePosition implements Sou /** * @inheritdoc - * @group API: Content access */ public get visibleDocumentContents(): string { throw new InvalidOperationAtUnknownPositionError('visibleDocumentContents', 'UnknownSourcePosition'); @@ -165,49 +148,43 @@ export class UnknownSourcePosition extends AbstractSourcePosition implements Sou /** * @inheritdoc - * @group API: Content access */ - public fullContentsFrom(from: SourcePosition): string { + public fullContentsFrom(_from: SourcePosition): string { throw new InvalidOperationAtUnknownPositionError('fullContentsFrom', 'UnknownSourcePosition'); } /** * @inheritdoc - * @group API: Content access */ - public fullContentsTo(from: SourcePosition): string { + public fullContentsTo(_from: SourcePosition): string { throw new InvalidOperationAtUnknownPositionError('fullContentsTo', 'UnknownSourcePosition'); } /** * @inheritdoc - * @group API: Content access */ - public visibleContentsFrom(from: SourcePosition): string { + public visibleContentsFrom(_from: SourcePosition): string { throw new InvalidOperationAtUnknownPositionError('visibleContentsFrom', 'UnknownSourcePosition'); } /** * @inheritdoc - * @group API: Content access */ - public visibleContentsTo(from: SourcePosition): string { + public visibleContentsTo(_from: SourcePosition): string { throw new InvalidOperationAtUnknownPositionError('visibleContentsTo', 'UnknownSourcePosition'); } /** * @inheritdoc - * @group API: Content access */ - public documentContextBefore(lines: number): string[] { + public documentContextBefore(_lines: number): string[] { throw new InvalidOperationAtUnknownPositionError('documentContextBefore', 'UnknownSourcePosition'); } /** * @inheritdoc - * @group API: Content access */ - public documentContextAfter(lines: number): string[] { + public documentContextAfter(_lines: number): string[] { throw new InvalidOperationAtUnknownPositionError('documentContextAfter', 'UnknownSourcePosition'); } // ----------------------------------------------- @@ -219,7 +196,6 @@ export class UnknownSourcePosition extends AbstractSourcePosition implements Sou // ----------------------------------------------- /** * @inheritdoc - * @group API: Printing */ public toString(): string { return '@'; @@ -228,6 +204,3 @@ export class UnknownSourcePosition extends AbstractSourcePosition implements Sou // #endregion } API: Printing // =============================================== } -// ----------------------------------------------- -// #endregion } UnknownSourcePosition -// =============================================== diff --git a/src/SourceReader/SourcePositions/index.ts b/src/SourceReader/SourcePositions/index.ts index c5febfd..cde03bc 100644 --- a/src/SourceReader/SourcePositions/index.ts +++ b/src/SourceReader/SourcePositions/index.ts @@ -11,8 +11,14 @@ * ***************************************************************************** */ /** - * @module API.SourceReader + * This module exposes the source position elements that are used by the + * {@link SourceReader}. This module is internal, thus, the user should not + * instantiate the classes here but through the {@link SourcePositions} factory. + * + * @module SourceReader/SourcePositions * @author Alan Rodas Bonjour + * + * @internal */ export * from './SourceSpan'; export * from './SourcePosition'; diff --git a/src/SourceReader/SourceReader.ts b/src/SourceReader/SourceReader.ts index f9ea2e5..05b7599 100644 --- a/src/SourceReader/SourceReader.ts +++ b/src/SourceReader/SourceReader.ts @@ -10,8 +10,9 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.SourceReader + * @module SourceReader * @author Pablo E. --Fidel-- Martínez López, */ import { SourcePosition, SourcePositions } from './SourcePositions'; @@ -53,8 +54,6 @@ import { and, expect } from '../Expectations'; * ``` * new SourceReader(input, '\n'); * ``` - * - * @group API: Main */ export type SourceInput = string | Record | string[]; // ----------------------------------------------- @@ -64,7 +63,7 @@ export type SourceInput = string | Record | string[]; // =============================================== // #region class SourceReader { TO DO: Update // ----------------------------------------------- -/** TO DO: Update +/** * A {@link SourceReader} allows you to read input from some source, either one * single document of content or several named or index source documents, in * such a way that each character read registers its position in the source as a @@ -81,13 +80,13 @@ export type SourceInput = string | Record | string[]; * parts with ease. * * A {@link SourceReader} is created using a {@link SourceInput} and then - * {@link SourcePosition}s can be read from it. + * {@link SourceReader/SourcePositions.SourcePosition}s can be read from it. * Possible interactions with a {@link SourceReader} include: * - peek a character, with {@link SourceReader.peek | peek}, * - check if a given strings occurs at the beginning of the text in the * current document, without skipping it, with * {@link SourceReader.startsWith | startsWith}, - * - get the current position as a {@link SourcePosition}, with + * - get the current position as a {@link SourceReader/SourcePositions.SourcePosition}, with * {@link SourceReader.getPosition | getPosition}, * - detect if the end of input was reached, with * {@link SourceReader.atEndOfInput | atEndOfInput}, @@ -119,8 +118,6 @@ export type SourceInput = string | Record | string[]; * A {@link SourceReader} also has a special position, * {@link SourceReader.UnknownPosition | UnknownPosition}, as a static member of * the class, indicating that the position is not known. - * This special position can also be obtaind from instances using - * {@link SourceReader.getUnknownPosition | getUnknownPosition}. * * Characters from the input are classified as either visible or non visible. * Visible characters affect the line and column calculation, and, conversely, non visible @@ -211,92 +208,80 @@ export type SourceInput = string | Record | string[]; * For that reason document is better to use {@link SourceReader.startsWith | startsWith} * to verify if the input starts with some character (or string), when peeking * for something specific. - * @group API: Main + * + * @privateRemarks + * The implementation of {@link SourceReader} keeps: + * * an object associating input document names to input document contents, + * {@link SourceReader._documents | _documents}, + * * an object associating input document names to visible input document + * contents, + * {@link SourceReader._visibleDocumentContents | _visibleDocumentContents}, + * * an array of the keys of that object for sequential access, + * {@link SourceReader.documentsNames | _documentsNames}, + * * an index to the current input document in the array of inputs names, + * {@link SourceReader._documentIndex | _documentIndex}, + * * an index to the current visible input document in the array of inputs + * names (because it may be different from the document index), + * {@link SourceReader._charIndex | _charIndex}, + * * the current line and column in the current input document, + * {@link SourceReader._line | _line} and + * {@link SourceReader._column | _column}, + * * a stack of strings representing the regions' IDs, + * {@link SourceReader._regions | _regions}, and + * * the characters used to determine line ends, + * {@link SourceReader.lineEnders | _lineEnders}. + * + * The object of {@link SourceReader._documents | _documents } cannot be + * empty (with no input document), and all the {@link SourceInput} forms are + * converted to `Record` for ease of access. + * The {@link SourceReader._charIndex | _charIndex } either points to a valid + * position in an input document, or at the end of an input document, or the + * end of input was reached (that is, when there are no more input documents + * to read). + * + * Line and column numbers are adjusted depending on which characters are + * considered as ending a line, as given by the property + * {@link SourceReader.lineEnders | _lineEnders}, and which characters are + * considered visible, as indicating by the user through + * {@link SourceReader.skip | skip}. + * When changing from one document to the next, line and column numbers are reset. + * + * The visible input is conformed by those characters of the input that has + * been skipped normally. As visible and non visible characters can be + * interleaved with no restrictions, it is better to keep a copy of the + * visible parts: characters are copied to the visible inputs attribute when + * skipped normally. Visible inputs always have a copy of those characters + * that have been processed as visible; unprocessed characters do not appear + * (yet) on visible inputs. + * + * This class is tightly coupled with {@link SourceReader/SourcePositions.SourcePosition}'s implementations, + * because of instances of that class represent different positions in the source + * inputs kept by a {@link SourceReader}. + * The operations + * {@link SourceReader._documentNameAt | _documentNameAt}, + * {@link SourceReader._visibleDocumentContentsAt | _visibleDocumentContentsAt}, + * {@link SourceReader._fullDocumentContentsAt | _fullDocumentContentsAt}, + * {@link SourceReader._inputFromToIn | _inputFromToIn}, + * {@link SourceReader._documentContextBeforeOf | _fullInputFromTo} and + * {@link SourceReader._documentContextAfterOf | _fullDocumentContentsAt} + * are meant to be used only by {@link SourceReader/SourcePositions.SourcePosition}, to complete + * their operations, and so they are grouped as Protected. + * + * The remaining auxiliary operations are meant for internal usage, to + * provide readability or to avoid code duplication. + * The auxiliary operation {@link SourceReader._cloneRegions | _cloneRegions } + * is needed because each new position produced with + * {@link SourceReader.getPosition | getPosition } need to have a snapshot + * of the region stack, and not a mutable reference. */ export class SourceReader { - // =============================================== - // #region Implementation Details { TO DO: Update - // ----------------------------------------------- - /** TO DO: Update - * The implementation of {@link SourceReader} keeps: - * * an object associating input document names to input document contents, - * {@link SourceReader._documents | _documents}, - * * an object associating input document names to visible input document - * contents, - * {@link SourceReader._visibleDocumentContents | _visibleDocumentContents}, - * * an array of the keys of that object for sequential access, - * {@link SourceReader.documentsNames | _documentsNames}, - * * an index to the current input document in the array of inputs names, - * {@link SourceReader._documentIndex | _documentIndex}, - * * an index to the current visible input document in the array of inputs - * names (because it may be different from the document index), - * {@link SourceReader._charIndex | _charIndex}, - * * the current line and column in the current input document, - * {@link SourceReader._line | _line} and - * {@link SourceReader._column | _column}, - * * a stack of strings representing the regions' IDs, - * {@link SourceReader._regions | _regions}, and - * * the characters used to determine line ends, - * {@link SourceReader.lineEnders | _lineEnders}. - * - * The object of {@link SourceReader._documents | _documents } cannot be - * empty (with no input document), and all the {@link SourceInput} forms are - * converted to `Record` for ease of access. - * The {@link SourceReader._charIndex | _charIndex } either points to a valid - * position in an input document, or at the end of an input document, or the - * end of input was reached (that is, when there are no more input documents - * to read). - * - * Line and column numbers are adjusted depending on which characters are - * considered as ending a line, as given by the property - * {@link SourceReader.lineEnders | _lineEnders}, and which characters are - * considered visible, as indicating by the user through - * {@link SourceReader.skip | skip}. - * When changing from one document to the next, line and column numbers are reset. - * - * The visible input is conformed by those characters of the input that has - * been skipped normally. As visible and non visible characters can be - * interleaved with no restrictions, it is better to keep a copy of the - * visible parts: characters are copied to the visible inputs attribute when - * skipped normally. Visible inputs always have a copy of those characters - * that have been processed as visible; unprocessed characters do not appear - * (yet) on visible inputs. - * - * This class is tightly coupled with {@link SourcePosition}'s implementations, - * because of instances of that class represent different positions in the source - * inputs kept by a {@link SourceReader}. - * The operations - * {@link SourceReader._documentNameAt | _documentNameAt}, - * {@link SourceReader._visibleDocumentContentsAt | _visibleDocumentContentsAt} and - * {@link SourceReader._visibleInputFromTo | _visibleInputFromTo }, - * {@link SourceReader._fullDocumentContentsAt | _fullDocumentContentsAt} and - * {@link SourceReader._fullInputFromTo | _fullInputFromTo}, and - * {@link SourceReader._documentContextBeforeOf | _fullInputFromTo} and - * {@link SourceReader._documentContextAfterOf | _fullDocumentContentsAt} - * are meant to be used only by {@link SourcePosition}, to complete - * their operations, and so they are grouped as Protected. - * - * The remaining auxiliary operations are meant for internal usage, to - * provide readability or to avoid code duplication. - * The auxiliary operation {@link SourceReader._cloneRegions | _cloneRegions } - * is needed because each new position produced with - * {@link SourceReader.getPosition | getPosition } need to have a snapshot - * of the region stack, and not a mutable reference. - * - * @group Implementation Details - * @private - */ - // ----------------------------------------------- - // #endregion } Implementation Details - // =============================================== - // =============================================== // #region API: Static Properties { // ----------------------------------------------- /** * A special position indicating that the position is not known. * - * @group API: Static Properties + * @group Properties (Static) */ public static readonly UnknownPosition: SourcePosition = SourcePositions.Unknown(); @@ -304,7 +289,7 @@ export class SourceReader { * The string to use as a name for unnamed input documents. * It is intended to be used only by instances. * - * @group API: Static Properties + * @group Properties (Static) */ public static readonly defaultDocumentNamePrefix: string = 'doc'; // ----------------------------------------------- @@ -316,16 +301,12 @@ export class SourceReader { // ----------------------------------------------- /** * The names with which input documents are identified. - * - * @group API: Properties */ public readonly documentsNames: string[]; /** * The characters used to indicate the end of a line. * These characters affect the calculation of line and column numbers for positions. - * - * @group API: Properties */ public readonly lineEnders: string; // ----------------------------------------------- @@ -341,7 +322,6 @@ export class SourceReader { * * **INVARIANT:** it is always and object (not a string). * - * @group Implementation: Properties * @private */ private _documents: Record; @@ -354,7 +334,6 @@ export class SourceReader { * * **INVARIANT:** `0 <= _documentIndex <= _documentsNames.length` * - * @group Implementation: Properties * @private */ private _documentIndex: number; @@ -366,7 +345,6 @@ export class SourceReader { * * if `_documentIndex < _documentsNames.length` * then `0 <= _charIndex < _documents[_documentsNames[_documentIndex]].length` * - * @group Implementation: Properties * @private */ private _charIndex: number; @@ -381,7 +359,6 @@ export class SourceReader { * * the values of each key are contained in the values of the * corresponding key at `_documents` * - * @group Implementation: Properties * @private */ private _visibleDocumentContents: Record; @@ -394,7 +371,6 @@ export class SourceReader { * * if `_documentIndex < _documentsNames.length` * then `_line < _documents[_documentsNames[_documentIndex]].length` * - * @group Implementation: Properties * @private */ private _line: number; @@ -407,7 +383,6 @@ export class SourceReader { * * if `_documentIndex < _documentsNames.length` * then `_column < _documents[_documentsNames[_documentIndex]].length` * - * @group Implementation: Properties * @private */ private _column: number; @@ -415,7 +390,6 @@ export class SourceReader { /** * The active regions in the current input document. * - * @group Implementation: Properties * @private */ private _regions: string[]; @@ -437,16 +411,14 @@ export class SourceReader { * * **PRECONDITION:** there is at least one input document. * - * @param input The source input. See {@link SourceInput} for explanation + * @param input - The source input. See {@link SourceInput} for explanation * and examples of how to understand this parameter. - * @param lineEnders A string of which characters will be used to determine + * @param lineEnders - A string of which characters will be used to determine * the end of a line. * - * @throws {@link NoInputError} if the arguments are undefined or has no documents. - * - * @group API: Creation + * @throws {@link SourceReader/Errors.NoInputError} if the arguments are undefined or has no documents. */ - public constructor(input: SourceInput, lineEnders: string = '\n') { + public constructor(input: SourceInput, lineEnders = '\n') { // No input document is not a valid option and( expect(input).not.toBeUndefined(), @@ -459,13 +431,13 @@ export class SourceReader { if (typeof input === 'string') { // Single unnamed input case: // will be referred as "doc1" afterwards. - this._documents = { [SourceReader.defaultDocumentNamePrefix + 1]: input }; + this._documents = { [SourceReader.defaultDocumentNamePrefix + String(1)]: input }; } else if (typeof input === 'object' && Array.isArray(input)) { // Multiple unnamed input case: // will be referred as "doc1", "doc2", ..., "docN" afterwards. const tmp = {}; for (let i = 0; i < input.length; i++) { - tmp[SourceReader.defaultDocumentNamePrefix + (i + 1)] = input[i]; + tmp[SourceReader.defaultDocumentNamePrefix + String(i + 1)] = input[i]; } this._documents = tmp; } else { @@ -500,7 +472,7 @@ export class SourceReader { /** * Answers if there are no more characters to read from the input. * - * @group API: Access + * @group Functions: Access */ public atEndOfInput(): boolean { return !this._hasMoreDocuments(); @@ -509,7 +481,7 @@ export class SourceReader { /** * Answers if there are no more characters to read from the current document. * - * @group API: Access + * @group Functions: Access */ public atEndOfDocument(): boolean { return this._hasMoreDocuments() && !this._hasMoreCharsAtCurrentDocument(); @@ -520,10 +492,10 @@ export class SourceReader { * * **PRECONDITION:** `!this.atEndOfInput()` * - * @throws {@link InvalidOperationAtEOIError} + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} * if the source reader is at EndOfDocument in the current position. * - * @group API: Access + * @group Functions: Access */ public currentDocumentName(): string { expect(this._hasMoreDocuments()).toBeTrue().orThrow(new InvalidOperationAtEOIError('peek', 'SourceReader')); @@ -536,12 +508,12 @@ export class SourceReader { * * **PRECONDITION:** `!this.atEndOfInput() && !this.atEndOfDocument` * - * @throws {@link InvalidOperationAtEODError} + * @throws {@link SourceReader/Errors.InvalidOperationAtEODError} * if the source reader is at EndOfInput in the current position. - * @throws {@link InvalidOperationAtEOIError} + * @throws {@link SourceReader/Errors.InvalidOperationAtEOIError} * if the source reader is at EndOfDocument in the current position. * - * @group API: Access + * @group Functions: Access */ public peek(): string { expect(this._hasMoreDocuments()).toBeTrue().orThrow(new InvalidOperationAtEOIError('peek', 'SourceReader')); @@ -557,9 +529,9 @@ export class SourceReader { * documents -- that is, only the current input document is checked. * See {@link SourceReader} documentation for an example. * - * @param str The string to verify the current input, starting at the current char. + * @param str - The string to verify the current input, starting at the current char. * - * @group API: Access + * @group Functions: Access */ public startsWith(str: string): boolean { // The input ALWAYS starts with nothing, even at the end of input @@ -582,14 +554,14 @@ export class SourceReader { } /** - * Gives the current position as a {@link SourcePosition}. + * Gives the current position as a {@link SourceReader/SourcePositions.SourcePosition}. * See {@link SourceReader} documentation for an example. * * **NOTE:** * the special positions at the end of each input document, and at the end of the - * input can be accessed by {@link SourceReader.getPosition}, but they cannot be peeked. + * input can be accessed by {@link SourceReader/SourcePositions.SourceReader.getPosition}, but they cannot be peeked. * - * @group API: Access + * @group Functions: Access */ public getPosition(): SourcePosition { /* istanbul ignore next */ @@ -644,18 +616,18 @@ export class SourceReader { * * See {@link SourceReader} for an example of visible `skip`s. * - * @param howMuch An indication of how many characters have to be skipped. + * @param howMuch - An indication of how many characters have to be skipped. * It may be given as a number or as a string. In this last * case, the length of the string is used (the contents are * ignored). If it is not given, it is assumed 1. - * @param silently A boolean indicating if the skip must be silent. If it is + * @param silently - A boolean indicating if the skip must be silent. If it is * not given, it is assumed `false`, that is, a visible * skip. If the skip is visible, the char is added to the * visible input. * - * @group API: Modification + * @group Functions: Modification */ - public skip(howMuch: number | string = 1, silently: boolean = false): void { + public skip(howMuch: number | string = 1, silently = false): void { const amountToSkip: number = typeof howMuch === 'string' ? howMuch.length : howMuch; for (let i = 0; i < amountToSkip && this._hasMoreDocuments(); i++) { this._skipOne(silently); @@ -671,17 +643,17 @@ export class SourceReader { * satisfy the predicate. * It does not go beyond the end of the current document, if starting inside one. * - * @param contCondition A predicate on strings, indicating the chars to read. - * @param silently A boolean indicating if the reading must be silent. If it + * @param contCondition - A predicate on strings, indicating the chars to read. + * @param silently - A boolean indicating if the reading must be silent. If it * is not given, it is assumed `false`, that is, a visible * read. If the read is visible, the char is added to the * visible input. - * @result The string read from the initial position until the character that do not + * @returns The string read from the initial position until the character that do not * satisfy the condition or the end of the current string. * - * @group API: Modification + * @group Functions: Modification */ - public takeWhile(contCondition: (ch: string) => boolean, silently: boolean = false): string { + public takeWhile(contCondition: (ch: string) => boolean, silently = false): string { if (!this._hasMoreDocuments() || !this._hasMoreCharsAtCurrentDocument()) { return ''; } @@ -703,7 +675,7 @@ export class SourceReader { * Pushes a region in the stack of regions. * It does not work at the EndOfInput or the EndOfDocument (it does nothing). * - * @group API: Modification + * @group Functions: Modification */ public beginRegion(regionId: string): void { // Optimized by inlining: if (!this.atEndOfInput() && !this.atEndOfDocument()) @@ -715,7 +687,7 @@ export class SourceReader { /** * Pops a region from the stack of regions. * It does nothing if there are no regions in the stack. - * @group API: Modification + * @group Functions: Modification */ public endRegion(): void { if (this._regions.length > 0) { @@ -731,7 +703,7 @@ export class SourceReader { // ----------------------------------------------- /** * Gives the name of the input document at the given index. - * It is intended to be used only by {@link SourcePosition}s. + * It is intended to be used only by {@linkSourceReader/SourcePositions.SourcePosition}s. * * **PRECONDITION:** * `index <= this._documentsNames.length` (not verified) @@ -739,8 +711,8 @@ export class SourceReader { * As it is a protected operation, it is not expectable to receive invalid indexes. * It is not taken into account which are the results if that happens. * - * @group Implementation: Protected for Source Positions - * @private + * @group Functions: Querying + * @internal */ public _documentNameAt(index: number): string { return this.documentsNames[index]; @@ -749,7 +721,7 @@ export class SourceReader { /** * Gives the contents of the input document at the given index, both visible * and non-visible. - * It is intended to be used only by {@link SourcePosition}s. + * It is intended to be used only by {@link SourceReader/SourcePositions.SourcePosition}s. * * **PRECONDITION:** * `index < this._documentsNames.length` (not verified) @@ -757,8 +729,8 @@ export class SourceReader { * As it is a protected operation, it is not expectable to receive invalid indexes. * It is not taken into account which are the results if that happens. * - * @group Implementation: Protected for Source Positions - * @private + * @group Functions: Querying + * @internal */ public _fullDocumentContentsAt(index: number): string { return this._documents[this.documentsNames[index]]; @@ -766,7 +738,7 @@ export class SourceReader { /** * Gives the contents of the visible input document at the given index. It - * is intended to be used only by {@link SourcePosition}s. + * is intended to be used only by {@link SourceReader/SourcePositions.SourcePosition}s. * * **PRECONDITION:** * `index < this._documentsNames.length` (not verified). @@ -774,8 +746,8 @@ export class SourceReader { * As it is a protected operation, it is not expectable to receive invalid indexes. * It is not taken into account which are the results if that happens. * - * @group Implementation: Protected for Source Positions - * @private + * @group Functions: Querying + * @internal */ public _visibleDocumentContentsAt(index: number): string { return this._visibleDocumentContents[this.documentsNames[index]]; @@ -784,8 +756,8 @@ export class SourceReader { /** * Returns the next character in the reader. * - * @group Implementation: Protected for Source Positions - * @private + * @group Functions: Querying + * @internal */ public _peek(): string { return this._fullDocumentContentsAt(this._documentIndex)[this._charIndex]; @@ -810,8 +782,8 @@ export class SourceReader { * * The index of the document is valid (not checked) * * The number of lines is not lower than 0 (not checked) * - * @group Implementation: Protected for Source Positions - * @private + * @group Functions: Querying + * @internal */ public _documentContextBeforeOf(docIndex: number, charIndex: number, lines: number): string[] { const docContents: string = this._fullDocumentContentsAt(docIndex); @@ -843,8 +815,8 @@ export class SourceReader { * * The char at the given position is the first one in the solution. * - * @group Implementation: Protected for Source Positions - * @private + * @group Functions: Querying + * @internal */ public _documentContextAfterOf(docIndex: number, charIndex: number, lines: number): string[] { const docContents: string = this._fullDocumentContentsAt(docIndex); @@ -877,8 +849,8 @@ export class SourceReader { * **PRECONDITIONS:** * * both positions correspond to this reader (and so are >= 0 -- not verified) * - * @group Implementation: Auxiliaries - * @private + * @group Functions: Auxiliaries + * @internal */ public _inputFromToIn( inputFrom: number, @@ -920,9 +892,9 @@ export class SourceReader { * * **PRECONDITION:** `!this.atEndOfInput()` (not verified) * - * @param silently A boolean indicating if the skip must be silent. + * @param silently - A boolean indicating if the skip must be silent. * - * @group Implementation: Auxiliaries + * @group Function: Auxiliaries * @private */ private _skipOne(silently: boolean): void { @@ -952,7 +924,7 @@ export class SourceReader { * * **PRECONDITION:** `!this.atEndOfInput() && this.atEndOfDocument()` * - * @group Implementation: Auxiliaries + * @group Function: Auxiliaries * @private */ private _skipToNextDocument(): void { @@ -969,7 +941,7 @@ export class SourceReader { /** * Answers if there are more input documents to be read. * - * @group Implementation: Auxiliaries + * @group Function: Auxiliaries * @private */ private _hasMoreDocuments(): boolean { @@ -981,7 +953,7 @@ export class SourceReader { * * **PRECONDITION:** `this._hasMoreDocuments()` * - * @group Implementation: Auxiliaries + * @group Function: Auxiliaries * @private */ private _hasMoreCharsAtCurrentDocument(): boolean { @@ -992,7 +964,7 @@ export class SourceReader { * Answers if the given char is recognized as an end of line indicator, * according to the configuration of the reader. * - * @group Implementation: Auxiliaries + * @group Function: Auxiliaries * @private */ private _isEndOfLine(ch: string): boolean { @@ -1001,11 +973,11 @@ export class SourceReader { /** * Gives a clone of the stack of regions. - * Auxiliary for {@link SourceReader.getPosition | getPosition}. - * It is necessary because regions of {@link SourcePosition} must correspond + * Auxiliary for {@link SourceReader/SourcePositions.SourceReader.getPosition | getPosition}. + * It is necessary because regions of {@link SourceReader/SourcePositions.SourcePosition} must correspond * to those at that position and do not change with changes in reader state. * - * @group Implementation: Auxiliaries + * @group Function: Auxiliaries * @private */ private _cloneRegions(): string[] { diff --git a/src/SourceReader/SourceReaderErrors.ts b/src/SourceReader/SourceReaderErrors.ts index 64cc93a..4b341f0 100644 --- a/src/SourceReader/SourceReaderErrors.ts +++ b/src/SourceReader/SourceReaderErrors.ts @@ -10,16 +10,19 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.SourceReader + * This module exports error classes used by the {@link SourceReader.SourceReader} module. + * + * @module SourceReader/Errors * @author Pablo E. --Fidel-- Martínez López, */ + /** * The superclass for all {@link SourceReader} errors. - * It provides internationalization of error messages through a {@link API.Translator!Translator}. * It also restores the prototype chain. * - * @group API: Main + * @group Errors */ export class SourceReaderError extends Error { /** @@ -29,7 +32,7 @@ export class SourceReaderError extends Error { * from this level are supposed to be catched and rethrown or handled * properly. * - * @param message A string message to show. + * @param message - A string message to show. */ public constructor(message: string) { super(message); @@ -41,7 +44,7 @@ export class SourceReaderError extends Error { * The error to produce when a {@link SourceReader} is called with no input * (an empty object or array). * - * @group API: Errors + * @group Errors */ export class NoInputError extends SourceReaderError { /** @@ -56,15 +59,15 @@ export class NoInputError extends SourceReaderError { * The error to produce when two positions related with different readers are * used to determine a portion of the contents. * - * @group API: Errors + * @group Errors */ export class MismatchedInputsError extends SourceReaderError { /** * The constructor for * {@link MismatchedInputsError | MismatchedInputsError} errors. * - * @param operation A string indicating which function inform as the producer of the error. - * @param context A string indicating the context in which the function produce the error. + * @param operation - A string indicating which function inform as the producer of the error. + * @param context - A string indicating the context in which the function produce the error. */ public constructor( public readonly operation: string, @@ -78,14 +81,14 @@ export class MismatchedInputsError extends SourceReaderError { * The error to produce when a function that is not supposed to be used at * an unknown position, but was called. * - * @group API: Errors + * @group Errors */ export class InvalidOperationAtUnknownPositionError extends SourceReaderError { /** - * The constructor for {@link InvalidOperationAtUnknownPositionError} errors. + * The constructor for {@link SourceReader/Errors.InvalidOperationAtUnknownPositionError} errors. * - * @param operation A string indicating which function produced the error. - * @param context A string indicating the context in which the function produced the error. + * @param operation - A string indicating which function produced the error. + * @param context - A string indicating the context in which the function produced the error. */ public constructor( public readonly operation: string, @@ -98,14 +101,14 @@ export class InvalidOperationAtUnknownPositionError extends SourceReaderError { /** * The error to produce when a function that is not supposed to be used at EndOfInput is called. * - * @group API: Errors + * @group Errors */ export class InvalidOperationAtEOIError extends SourceReaderError { /** - * The constructor for {@link InvalidOperationAtEOIError} errors. + * The constructor for {@link SourceReader/Errors.InvalidOperationAtEOIError} errors. * - * @param operation A string indicating which function produced the error. - * @param context A string indicating the context in which the function produced the error. + * @param operation - A string indicating which function produced the error. + * @param context - A string indicating the context in which the function produced the error. */ public constructor( public readonly operation: string, @@ -118,14 +121,14 @@ export class InvalidOperationAtEOIError extends SourceReaderError { /** * The error to produce when a function that is not supposed to be used at EndOfDocument is called. * - * @group API: Errors + * @group Errors */ export class InvalidOperationAtEODError extends SourceReaderError { /** * The constructor for {@link InvalidOperationAtEODError} errors. * - * @param operation A string indicating which function produced the error. - * @param context A string indicating the context in which the function produced the error. + * @param operation - A string indicating which function produced the error. + * @param context - A string indicating the context in which the function produced the error. */ public constructor( public readonly operation: string, diff --git a/src/SourceReader/index.ts b/src/SourceReader/index.ts index 8b67e0a..96e53dd 100644 --- a/src/SourceReader/index.ts +++ b/src/SourceReader/index.ts @@ -10,10 +10,7 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ -/** - * @module API.SourceReader - * @author Alan Rodas Bonjour - */ + /** * A {@link SourceReader} allows you to read input from some source, either one single document of * content or several named or indexed source documents, in such a way that each character read @@ -27,23 +24,20 @@ * * to allow the relationship of parts of the input with identifiers naming "regions", thus * making it possible for external tools to identify those parts with ease. * - * A {@link SourceReader} is created using a {@link SourceInput} and then {@link SourcePosition}s, - * in particular {@link KnownSourcePosition}s, can be read from it. + * A {@link SourceReader} is created using a {@link SourceInput} and then {@link SourceReader/SourcePositions.SourcePosition}s, + * in particular {@link SourceReader/SourcePositions.AbstractKnownSourcePosition}s, can be read from it. * Possible interactions with a {@link SourceReader} includes: - * - {@link SourceReader.peek | peek}, peeking a character, - * - {@link SourceReader.startsWith | startsWith}, checking if a given strings occurs at the + * * {@link SourceReader.peek | peek}, peeking a character, + * * {@link SourceReader.startsWith | startsWith}, checking if a given strings occurs at the * beginning of the text in the current document, without skipping it, - * - {@link SourceReader.getPosition | getPosition} - * - {@link SourceReader.getDocumentPosition | getDocumentPosition}, getting the current position - * as a {@link KnownSourcePosition} or (provided the end of input was not reached) - * {@link DocumentSourcePosition}, respectively, - * - {@link SourceReader.atEndOfInput | atEndOfInput}, detecting if the end of input was reached, - * - {@link SourceReader.atEndOfDocument | atEndOfDocument}, detecting if the end of the current + * * {@link SourceReader.getPosition | getPosition} + * * {@link SourceReader.atEndOfInput | atEndOfInput}, detecting if the end of input was reached, + * * {@link SourceReader.atEndOfDocument | atEndOfDocument}, detecting if the end of the current * document was reached, - * - {@link SourceReader.skip | skip}, skipping one or more characters, - * - {@link SourceReader.takeWhile | takeWhile}, reading some characters from the current document + * * {@link SourceReader.skip | skip}, skipping one or more characters, + * * {@link SourceReader.takeWhile | takeWhile}, reading some characters from the current document * based on a condition, and - * - {@link SourceReader.beginRegion | beginRegion} and + * * {@link SourceReader.beginRegion | beginRegion} and * {@link SourceReader.endRegion | endRegion}, manipulating "regions". * When reading from sources with multiple documents of input, skipping moves inside a document * until there are no more characters, then an end of document position is reached (a special @@ -60,25 +54,27 @@ * * ## Source positions * - * {@link SourcePosition}s point to particular positions in the source given by a + * {@link SourceReader/SourcePositions.SourcePosition}s point to particular positions in the source given by a * {@link SourceReader}. - * All {@link SourcePosition}s are created only through {@link SourceReader}. + * All {@link SourceReader/SourcePositions.SourcePosition}s are created only through {@link SourceReader}. * * A source position may be known (pointing to a particular position into a * {@link SourceReader}) or unknown (if a position cannot be determined). - * The boolean property {@link SourcePosition.isUnknown | isUnknown} + * The boolean property {@link SourceReader/SourcePositions.SourcePosition.isUnknown | isUnknown} * indicates which is the case. * - * Additionally, a string representation of any {@link SourcePosition} - * can be obtained through {@link SourcePosition.toString | toString} + * Additionally, a string representation of any {@link SourceReader/SourcePositions.SourcePosition} + * can be obtained through {@link SourceReader/SourcePositions.SourcePosition.toString | toString} * for internal use purposes. * - * A typical use of {@link SourcePosition}s is relating nodes of an AST + * A typical use of {@link SourceReader/SourcePositions.SourcePosition}s is relating nodes of an AST * representation of code to particular positions in the string version of the * source code (that may come from several input documents). * * @module SourceReader + * @author Alan Rodas Bonjour */ + export * from './SourceReader'; export { SourcePosition, SourceSpan } from './SourcePositions'; export * from './SourceReaderErrors'; diff --git a/src/Translations/Translator.ts b/src/Translations/Translator.ts deleted file mode 100644 index 199e04a..0000000 --- a/src/Translations/Translator.ts +++ /dev/null @@ -1,188 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) National University of Quilmes 2018-2024 - * Gobstones (TM) is a trademark of the National University of Quilmes. - * - * This program is free software distributed under the terms of the - * GNU Affero General Public License version 3. - * Additional terms added in compliance to section 7 of such license apply. - * - * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. - * ***************************************************************************** - */ -/** - * @module API.Translator - * @author Alan Rodas Bonjour - */ -import { flatten } from '../Functions/flatten'; - -/** - * A Translator consist of an object that hold the state of the current - * locale being used, and allows for switching between different locales - * and obtain translated strings. - * - * The translator expects a locale to be given as the language definition, - * and, if constructed with the flatten options, flattens it to allow - * dot notation access to the different strings in the locale object. - * - * Note that this object does not provide mechanisms for maintaining - * multiple languages registered, nor should be used to hold the user defined - * language as a state object. Rather, this should be created once, setted - * with the desired language, and used always through the full library. - * - * @group API: Main - */ -export class Translator> { - /** - * All the registered translations avaiable. - */ - private availableTranslations: Record; - /** - * The default locale - */ - private defaultLocale: string; - /** - * The current locale being used. - * Types to any, as may be flatten or not. - */ - private currentLocale: any; - /** - * The current locale name being used. - */ - private currentLocaleName: string; - /** - * Whether or not use flatten when accessing elements to translate. - */ - private flatten: boolean; - - /** - * Create a new instance of this translator that uses the - * - */ - public constructor( - availableTranslations: Record, - defaultLocale: string, - shouldFlat = true, - currentLocale?: string - ) { - if (!availableTranslations) { - throw new Error(`The translations cannot be null nor undefined`); - } - if (!defaultLocale) { - throw new Error(`The default translation cannot be null nor undefined`); - } - if (!availableTranslations[defaultLocale]) { - throw new Error(`The default translation must be one of the translations available`); - } - this.availableTranslations = availableTranslations; - this.defaultLocale = defaultLocale; - this.currentLocaleName = defaultLocale; - this.flatten = shouldFlat; - this.setLocale(currentLocale ?? defaultLocale); - } - - /** - * Get the default locale name - * - * @returns The default locale. - */ - public getDefaultLocale(): string { - return this.defaultLocale; - } - - /** - * Get all the available translations - * - * @returns The default locale. - */ - public getAvailableTranslations(): Record { - return this.availableTranslations; - } - - /** - * Get the current using locale - * - * @returns The current locale name. - */ - public getLocale(): string { - return this.currentLocaleName; - } - - /** - * Answer wether or not a given locale name is registered. - * - * @param locale The locale name to check for existence. - * - * @returns `true` if the locale exists, `false`otherwise. - */ - public hasLocale(locale: string): boolean { - return !!this.availableTranslations[locale]; - } - /** - * Set the current language to the given locale. - * - * @param locale A locale to use as the current language. - */ - public setLocale(locale: string): void { - if (!this.availableTranslations[locale]) { - throw new Error(`The locale "${locale}" is not available`); - } - this.currentLocaleName = locale; - this.currentLocale = this.flatten - ? flatten(this.availableTranslations[this.currentLocaleName]) - : this.availableTranslations[this.currentLocaleName]; - } - - /** - * Translate a specific key to the currently used locale, replacing - * any interpolation matchers by the given interpolations. - * - * @param key The key to use to obtain the translated text - * @param interpolations If given, keys of this object will be used - * to replace any interpolation matcher in the translated text - * (any text in between $\{\}) by the value of the corresponding key. - * - * @returns A translated string - */ - public translate(key: string, interpolations?: Record): string { - let value = this.currentLocale[key]; - if (!value || typeof value !== 'string') { - return key; - } - const replacements = interpolations ?? []; - for (const each in replacements) { - value = value.replace(`\${${each}}`, `${replacements[each]}`); - } - return value; - } - - /** - * Translate a specific key to the currently used locale, by selecting the - * corresponding pluralization, determined by the amount given and replacing - * any interpolation matchers by the given interpolations. - * Pluralization is selected based on the las part of the key in such a form - * that it contains the amount as part of the key, or "n" for other number. - * .e.g. given the key "test.key" plurals attempt to match "test.key.0", - * "test.key.1" and so on, or "test.key.n". - * - * @param amount The amount given for pluralization - * @param key The key to use to obtain the translated text - * @param interpolations If given, keys of this object will be used - * to replace any interpolation matcher in the translated text - * (any text in $\{\}) by the value of the corresponding key. - * - * @returns A translated string - */ - public pluralize(amount: number, key: string, interpolations?: Record): string { - if (!Number.isInteger(amount)) { - throw new Error('pluralization can only be used for integers'); - } - if (this.currentLocale[`${key}.${amount.toString()}`]) { - return this.translate(this.currentLocale[`${key}.${amount.toString()}`], interpolations); - } - if (this.currentLocale[`${key}.n`]) { - return this.translate(this.currentLocale[`${key}.n`], interpolations); - } - return key; - } -} diff --git a/src/Types/AnyFunction.ts b/src/Types/AnyFunction.ts new file mode 100644 index 0000000..871d3b6 --- /dev/null +++ b/src/Types/AnyFunction.ts @@ -0,0 +1,26 @@ +/* + * ***************************************************************************** + * Copyright (C) National University of Quilmes 2018-2024 + * Gobstones (TM) is a trademark of the National University of Quilmes. + * + * This program is free software distributed under the terms of the + * GNU Affero General Public License version 3. + * Additional terms added in compliance to section 7 of such license apply. + * + * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. + * ***************************************************************************** + */ + +/** + * @module Types + * @author Alan Rodas Bonjour + */ + +/** + * This type represents a function that can + * take any number of arguments, and return any value or not return at all. + * It's a useful type definition for places where a function is expected, but + * it's shape may be any. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type AnyFunction = (...args: readonly any[]) => any; diff --git a/src/Types/BiMap.ts b/src/Types/BiMap.ts index f2fcc73..6b7ef7c 100644 --- a/src/Types/BiMap.ts +++ b/src/Types/BiMap.ts @@ -10,8 +10,9 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Types + * @module Types * @author Alan Rodas Bonjour */ @@ -61,10 +62,8 @@ * given key or value in the map where they are values, if they appear there, and the * association of a new pair key-value, deleting the old associations for both, if they exist. * - * @param K The type of the keys. - * @param V The type of the values. - * - * @group API: Main + * @param K - The type of the keys. + * @param V - The type of the values. */ export class BiMap { /** @@ -104,7 +103,7 @@ export class BiMap { * ``` * * @group Constructors - * @param map Optional list of associations to contain in the new BiMap. + * @param map - Optional list of associations to contain in the new BiMap. */ public constructor(map?: [K, V][]) { this.mapKV = new Map(); @@ -139,7 +138,7 @@ export class BiMap { * Answer if this BiMap has the given key associated with a value. * * @group Querying - * @param key The key to search + * @param key - The key to search * @returns true if the key is present, false otherwise. */ public hasKey(key: K): boolean { @@ -151,7 +150,7 @@ export class BiMap { * undefined if the key is not associated with any value. * * @group Querying - * @param key The key to retrieve the associated value + * @param key - The key to retrieve the associated value * @returns true if the key is present, false otherwise. */ public getByKey(key: K): V | undefined { @@ -163,8 +162,8 @@ export class BiMap { * If any of both have previous associations, they are lost. * * @group Manipulation - * @param key The key to associate with the value - * @param value The value to associate with the key + * @param key - The key to associate with the value + * @param value - The value to associate with the key */ public setByKey(key: K, value: V): void { this.biassociateKeyAndValue(key, value); @@ -174,7 +173,7 @@ export class BiMap { * Delete the association between the given key and its value, if it exists. * * @group Manipulation - * @param key The key to delete its association + * @param key - The key to delete its association */ public deleteByKey(key: K): void { this.reverseDeleteKey(key); @@ -185,7 +184,7 @@ export class BiMap { * Answer if this BiMap has the given value associated with a key. * * @group Querying - * @param value The value to search + * @param value - The value to search */ public hasValue(value: V): boolean { return !!this.mapVK.has(value); // !! transforms falsy values into booleans @@ -196,7 +195,7 @@ export class BiMap { * undefined if the value is not associated with any key. * * @group Querying - * @param value The value to retrieve the associated key + * @param value - The value to retrieve the associated key */ public getByValue(value: V): K | undefined { return this.mapVK.get(value); @@ -207,8 +206,8 @@ export class BiMap { * If any of both have previous associations, they are lost. * * @group Manipulation - * @param value The value to associate with the key - * @param key The key to associate with the value + * @param value - The value to associate with the key + * @param key - The key to associate with the value */ public setByValue(value: V, key: K): void { this.biassociateKeyAndValue(key, value); @@ -218,7 +217,7 @@ export class BiMap { * Delete the association between the given key and its value, if it exists. * * @group Manipulation - * @param value The value to delete its association. + * @param value - The value to delete its association. */ public deleteByValue(value: V): void { this.reverseDeleteValue(value); @@ -271,16 +270,18 @@ export class BiMap { * @group Printing */ public toString(): string { - let str: string = 'BiMap:{ '; + let str = 'BiMap:{ '; const entries = this.entries(); if (entries.length > 0) { let k: K; let v: V; for (let i = 0; i < entries.length - 1; i++) { [k, v] = entries[i]; + // eslint-disable-next-line @typescript-eslint/no-base-to-string str += k + ' <-> ' + v + ', '; } [k, v] = entries[entries.length - 1]; + // eslint-disable-next-line @typescript-eslint/no-base-to-string str += k + ' <-> ' + v + ' '; } str += '}'; diff --git a/src/Types/Join.ts b/src/Types/Join.ts new file mode 100644 index 0000000..d40de90 --- /dev/null +++ b/src/Types/Join.ts @@ -0,0 +1,31 @@ +/* + * ***************************************************************************** + * Copyright (C) National University of Quilmes 2018-2024 + * Gobstones (TM) is a trademark of the National University of Quilmes. + * + * This program is free software distributed under the terms of the + * GNU Affero General Public License version 3. + * Additional terms added in compliance to section 7 of such license apply. + * + * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. + * ***************************************************************************** + */ + +/** + * @module Types + * @author Alan Rodas Bonjour + */ + +/** + * This type represents the joining of two types, separated by a string, defaulting to dot. + * + * @remarks + * This type is useful when you have separate field representing types, and you need + * to represent the idea of a full path. So if you have the types `foo` and `bar`, you + * may use Join to provide the type `foo.bar`. + */ +export type Join = K extends string | number + ? P extends string | number + ? `${K}${'' extends P ? '' : S}${P}` + : never + : never; diff --git a/src/Types/Leaves.ts b/src/Types/Leaves.ts new file mode 100644 index 0000000..0f2cd68 --- /dev/null +++ b/src/Types/Leaves.ts @@ -0,0 +1,32 @@ +/* + * ***************************************************************************** + * Copyright (C) National University of Quilmes 2018-2024 + * Gobstones (TM) is a trademark of the National University of Quilmes. + * + * This program is free software distributed under the terms of the + * GNU Affero General Public License version 3. + * Additional terms added in compliance to section 7 of such license apply. + * + * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. + * ***************************************************************************** + */ + +/** + * @module Types + * @author Alan Rodas Bonjour + */ + +/** + * This type represent all the possible leaves of an object type. + * + * @remarks + * A leaf consists of a path that leads ultimately to a simple value. So for example + * let' say we have an object with the form `{ foo: { bar: 5, baz: 'hello' } }`, then + * the Leaves of it's type are `foo.bar` and `foo.baz`. + * + * This type is useful if you need to type things such as paths in an object that + * lead to a particular value, such as the types of a translation in a json file. + */ +export type Leaves = T extends object + ? { [K in keyof T]: `${Exclude}${Leaves extends never ? '' : `.${Leaves}`}` }[keyof T] + : never; diff --git a/src/Types/Paths.ts b/src/Types/Paths.ts new file mode 100644 index 0000000..88b0b42 --- /dev/null +++ b/src/Types/Paths.ts @@ -0,0 +1,32 @@ +/* + * ***************************************************************************** + * Copyright (C) National University of Quilmes 2018-2024 + * Gobstones (TM) is a trademark of the National University of Quilmes. + * + * This program is free software distributed under the terms of the + * GNU Affero General Public License version 3. + * Additional terms added in compliance to section 7 of such license apply. + * + * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. + * ***************************************************************************** + */ + +/** + * @module Types + * @author Alan Rodas Bonjour + */ + +/** + * This type represent all the possible paths of an object type. + * + * @remarks + * A path consists of a path that leads to a simple or complex value. So for example + * let' say we have an object with the form `{ foo: { bar: 5, baz: 'hello' } }`, then + * the Paths of it's type are `foo`, `foo.bar` and `foo.baz`. + * + * This type is useful if you need to type things such as paths in an object that + * lead to a particular value, such as the types of a translation in a json file. + */ +export type Paths = T extends object + ? { [K in keyof T]: `${Exclude}${'' | `.${Paths}`}` }[keyof T] + : never; diff --git a/src/Types/Subset.ts b/src/Types/Subset.ts index 14bf69a..adedbf0 100644 --- a/src/Types/Subset.ts +++ b/src/Types/Subset.ts @@ -11,7 +11,7 @@ * ***************************************************************************** */ /** - * @module API.Types + * @module Types * @author Pablo E. --Fidel-- Martínez López */ @@ -22,8 +22,6 @@ * {@link https://grrr.tech/posts/2021/typescript-partial/ | blog by Harmen Janssen}, posted * on November 29, 2021. * See that blog for an explanation on the code. - * - * @group API: Main */ export type Subset = { [attr in keyof K]?: K[attr] extends object diff --git a/src/Types/WithRequired.ts b/src/Types/WithRequired.ts index a344ca0..bcca886 100644 --- a/src/Types/WithRequired.ts +++ b/src/Types/WithRequired.ts @@ -10,10 +10,12 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** - * @module API.Types + * @module Types * @author Alan Rodas Bonjour */ + /** * A type modifier that allows to construct a generic type that * requires only one property of a given type. @@ -33,7 +35,7 @@ * type UserWithName = WithRequired * ``` * - * @param T The base type. - * @param K the name of the property to require in the new type. + * @param T - The base type. + * @param K - the name of the property to require in the new type. */ export type WithRequired = T & { [P in K]-?: T[P] }; diff --git a/src/Types/index.ts b/src/Types/index.ts index 8d66f92..caebda0 100644 --- a/src/Types/index.ts +++ b/src/Types/index.ts @@ -19,10 +19,13 @@ * This module includes the class {@link BiMap} as a bi-directional map, * and {@link Subset} as a recursive partial type definition. * - * @module API.Types + * @module Types * @author Alan Rodas Bonjour */ export * from './BiMap'; export * from './Subset'; export * from './WithRequired'; +export * from './Paths'; +export * from './Leaves'; +export * from './Join'; diff --git a/src/cli.ts b/src/cli.ts index f2c078a..0992c29 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -17,25 +17,22 @@ * multiple languages, input and output from and to files as well * as stdout, and other perks. * - * @module API.CLI + * @module CLI * @author Alan Rodas Bonjour */ import fs from 'fs'; import commander, { program } from 'commander'; -import { Translator } from './Translations'; import { WithRequired } from './Types'; /** * The general texts that a CLI app uses. - * This include the description texts (Or a description key if a {@link Translator} is provided) - * that are used a the description of the different parts of the CLI. + * This include the description texts that are used as the description of the + * different parts of the CLI. * The `name` and the `versionNumber` are expected to be the app name (No translation * is used, as the name should be the same through all the app), and the `versionNumber` - * should be the version is the major.minor.patch. - * - * @group API: Options + * should be the version in the major.minor.patch format. */ export interface CLIGeneralTexts { /** The application name */ @@ -57,8 +54,6 @@ export interface CLIGeneralTexts { /** * The general flags that a CLI app accepts, when configured to used them. * Note that currently the default flags cannot be changed. - * - * @group API: Options */ export interface CLIGeneralFlags { /** The help flags, both short and long */ @@ -76,26 +71,16 @@ export interface CLIGeneralFlags { /** * A set of options for initially configure a CLI application. * If a translation is given - * - * @group API: Options */ export interface CLIAppOptions { /** - * The description texts (Or a description key if a {@link Translator} is provided) - * that is used is the description of the different parts of the CLI. + * The description texts that is used as the description of the different parts of the CLI. * The `name` and the `versionNumber` are expected to be the app name (No translation * is used, as the name should be the same through all the app), and the `versionNumber` - * should be the version is the major.minor.patch. + * should be the version in the major.minor.patch format. */ texts: CLIGeneralTexts; - /** - * A {@link Translator} used to translate the tool to different locales, both when - * called with a language flag, and automatically at startup by auto-detecting - * the user language by checking OS Environment variables. - */ - translator?: Translator>; - /** * The flag names to use in this application, if the flags differ in any way from * the default ones. @@ -113,8 +98,6 @@ export interface CLIAppOptions { /** * A builder for a CLI command. May be the main command of the app ({@link CLIApp}) * extends this class) or a sub-command. - * - * @group Internal: Types */ export class CLICommandBuilder { private static SHORT_HELP_FLAG = '-h'; @@ -129,24 +112,20 @@ export class CLICommandBuilder { private static LONG_OUTPUT_FLAG = '--out'; protected program: commander.Command; - protected hasAction: boolean = false; - protected currentArgs: any[]; - protected currentOptions: any; + protected hasAction = false; + protected currentArgs: unknown[]; + protected currentOptions: unknown; protected onReadErrorMsg: string; protected options: WithRequired; protected isSubcommand: boolean; - public constructor(cmdrProgram: commander.Command, options: CLIAppOptions, isSubcommand: boolean = false) { + public constructor(cmdrProgram: commander.Command, options: CLIAppOptions, isSubcommand = false) { // Set default flags, or use custom ones const defaultFlags = { help: { short: CLICommandBuilder.SHORT_HELP_FLAG, long: CLICommandBuilder.LONG_HELP_FLAG }, - language: { - short: CLICommandBuilder.SHORT_LANG_FLAG, - long: CLICommandBuilder.LONG_LANG_FLAG - }, version: { short: CLICommandBuilder.SHORT_VERSION_FLAG, long: CLICommandBuilder.LONG_VERSION_FLAG @@ -169,44 +148,34 @@ export class CLICommandBuilder { /** * Make this command to be able to read input from a file. * - * @param description The input flag description or the translation key if a translator is used. - * @param onReadErrorMsg The error message or translation key if a translator is used. + * @param description - The input flag description or the translation key if a translator is used. + * @param onReadErrorMsg - The error message or translation key if a translator is used. */ public input(description: string, onReadErrorMsg: string): this { this.onReadErrorMsg = onReadErrorMsg; - this.program.option( - `${this.options.flags.in.short}, ${this.options.flags.in.long}, `, - this.options.translator ? this.options.translator.translate(description) : description - ); + this.program.option(`${this.options.flags.in.short}, ${this.options.flags.in.long}, `, description); return this; } /** * Make this command to be able to write the output to a file. * - * @param description The output flag description or translation key if a translator is used. + * @param description - The output flag description or translation key if a translator is used. */ public output(description: string): this { - this.program.option( - `${this.options.flags.out.short}, ${this.options.flags.out.long}, `, - this.options.translator ? this.options.translator.translate(description) : description - ); + this.program.option(`${this.options.flags.out.short}, ${this.options.flags.out.long}, `, description); return this; } /** * Add a new option to the command. * - * @param flags The flags to trigger this option - * @param description The description or translation key if a translator is used. - * @param defaultValue A default value. + * @param flags - The flags to trigger this option + * @param description - The description or translation key if a translator is used. + * @param defaultValue - A default value. */ public option(flags: string, description?: string, defaultValue?: string | boolean): this { - this.program.option( - flags, - this.options.translator ? this.options.translator.translate(description ?? '') : description, - defaultValue - ); + this.program.option(flags, description, defaultValue); return this; } @@ -216,13 +185,12 @@ export class CLICommandBuilder { * on the command definition. Mandatory or optional positional arguments are * passed first, while the last element consists of the flags passed to the command) * - * @param f The callback to run when this command is called. + * @param f - The callback to run when this command is called. */ - public action(f: (cliapp: this, ...args: any[]) => void): this { - this.program.action((...options: any[]) => { + public action(f: (cliapp: this, ...args: unknown[]) => void): this { + this.program.action((...options: unknown[]) => { this.currentArgs = options.length >= 2 ? options.slice(0, options.length - 2) : [options[0]]; this.currentOptions = options.length >= 2 ? options[options.length - 2] : options[1]; - this.setCorrectLanguage(this.currentOptions.language); f(this, ...options); }); this.hasAction = true; @@ -236,8 +204,8 @@ export class CLICommandBuilder { * input file if an input was configured. */ public read(): string { - if (this.currentOptions && this.currentOptions.in) { - return this.readFileInput(this.currentOptions.in); + if (this.currentOptions && (this.currentOptions as { in: string }).in) { + return this.readFileInput((this.currentOptions as { in: string }).in); } return this.currentArgs.join(' '); } @@ -248,11 +216,11 @@ export class CLICommandBuilder { * file does not exists, it gets created. If it already exists, then the * output is appended to the previously defined contents of that file. * - * @param data The data to output + * @param data - The data to output */ public write(data: string): void { - if (this.currentOptions && (this.currentOptions as any).out) { - this.writeToFile((this.currentOptions as any).out, data); + if (this.currentOptions && (this.currentOptions as { out: string }).out) { + this.writeToFile((this.currentOptions as { out: string }).out, data); } else { this.writeToConsole(data); } @@ -262,23 +230,18 @@ export class CLICommandBuilder { * Read the contents of a file. Throws error if * the file does not exist. * - * @param fileName The file to read. + * @param fileName - The file to read. */ public readFileInput(fileName: string): string { - this.ensureOrFailAndExit( - fs.existsSync(fileName), - this.options.translator - ? this.options.translator.translate(this.onReadErrorMsg, { fileName }) - : this.onReadErrorMsg - ); + this.ensureOrFailAndExit(fs.existsSync(fileName), this.onReadErrorMsg); return fs.readFileSync(fileName).toString(); } /** * Write a set of contents to a given file. * - * @param fileName The file to write to. - * @param contents The contents to write. + * @param fileName - The file to write to. + * @param contents - The contents to write. */ public writeToFile(fileName: string, contents: string): void { fs.writeFileSync(fileName, contents, { flag: 'a' }); @@ -287,7 +250,7 @@ export class CLICommandBuilder { /** * Write a set of contents to the standard output. * - * @param contents The contents to write. + * @param contents - The contents to write. */ public writeToConsole(contents: string): void { // eslint-disable-next-line no-console @@ -298,8 +261,8 @@ export class CLICommandBuilder { * Ensure a condition is met, and if not, show the given error message, * and exit the application with 1. * - * @param condition The condition that needs to satisfy - * @param error + * @param condition - The condition that needs to satisfy + * @param error - */ public ensureOrFailAndExit(condition: boolean, error: string): void { if (!condition) { @@ -343,42 +306,16 @@ export class CLICommandBuilder { /** * Exit the application with the given value. * - * @param value The value to exit with + * @param value - The value to exit with */ public exit(value: number): void { process.exit(value); } - - /** Set the correct language for this command */ - protected setCorrectLanguage(language?: string): void { - if (language) { - this.validateLanguageFlag(language); - this.options.translator?.setLocale(language); - } - } - - /** Validate that the given language flag, if any, is a valid translation */ - protected validateLanguageFlag(locale: string): void { - const availableLangs = Object.keys(this.options.translator?.getAvailableTranslations() || {}) - .map((e) => '"' + e + '"') - .join(' | '); - this.ensureOrFailAndExit( - this.options.translator !== undefined && this.options.translator.hasLocale(locale), - this.options.translator - ? this.options.translator.translate(this.options.texts.languageError ?? '', { - locale, - availableLangs - }) - : this.options.texts.languageError ?? '' - ); - } } /** * The CLIApp class is the class to extend in order to define your CLI based * application. - * - * @group API: Main */ export class CLIApp extends CLICommandBuilder { /** The arguments passed to the application */ @@ -388,26 +325,19 @@ export class CLIApp extends CLICommandBuilder { super(program, options); this.processArgs = process.argv; - this.setCorrectLanguage(this.getUserEnvLocale()); - this.setLanguageIfConfigured(this.program); - // Set up the program this.program.name(this.options.texts.name); this.program.version( this.options.texts.versionNumber, `${this.options.flags.version.short}, ${this.options.flags.version.long}`, - this.options.translator - ? this.options.translator.translate(this.options.texts.version) - : this.options.texts.version + this.options.texts.version ); this.program.helpOption( `${this.options.flags.help.short}, ${this.options.flags.help.long}`, - this.options.translator - ? this.options.translator.translate(this.options.texts.help) - : this.options.texts.help + this.options.texts.help ); - this.program.addHelpCommand(false); + this.program.helpCommand(false); } /** @@ -416,8 +346,7 @@ export class CLIApp extends CLICommandBuilder { */ public run(): void { if (!this.hasAction) { - this.program.action((options: any) => { - this.setCorrectLanguage(options.language); + this.program.action((_options: unknown) => { this.outputHelpOnNoArgs(); }); } @@ -427,97 +356,24 @@ export class CLIApp extends CLICommandBuilder { /** * Define a new sub-command. * - * @param name The new sub-command name - * @param description The sub-command description, or translation key if a translator is used. - * @param f A callback to construct the newly defined sub-command. + * @param name - The new sub-command name + * @param description - The sub-command description, or translation key if a translator is used. + * @param f - A callback to construct the newly defined sub-command. */ public command(name: string, description: string, f: (cmd: CLICommandBuilder) => void): this { - const newCmd = this.program - .command(name) - .description(this.options.translator ? this.options.translator.translate(description) : description); - this.setLanguageIfConfigured(newCmd); + const newCmd = this.program.command(name).description(description); f(new CLICommandBuilder(newCmd, this.options, true)); return this; } - - /** - * Set the language of the given commander app, to the default, or the given one. - * - * @param cmd The command to set to. - */ - private setLanguageIfConfigured(cmd: commander.Command): void { - if (this.options.translator) { - const language = this.getUserLocale(); - this.setCorrectLanguage(language); - - const availableLangs = Object.keys(this.options.translator.getAvailableTranslations()) - .map((e) => '"' + e + '"') - .join(' | '); - - // Language flag is only set when a translator is being used. - cmd.option( - `${this.options.flags.language.short}, ${this.options.flags.language.long}, `, - this.options.translator.translate(this.options.texts.language ?? '', { - availableLangs - }), - language - ); - } - } - - /** - * Retrieve the current user locale, as retrieved from the environment, - * or the arguments given by the user. - */ - private getUserLocale(): string { - const envLocale = this.getUserEnvLocale(); - if ( - this.processArgs.indexOf(this.options.flags.language.short) >= 0 || - this.processArgs.indexOf(this.options.flags.language.long) >= 0 - ) { - const langIndex = - this.processArgs.indexOf(this.options.flags.language.short) >= 0 - ? this.processArgs.indexOf(this.options.flags.language.short) - : this.processArgs.indexOf(this.options.flags.language.long); - return this.processArgs.length > langIndex ? this.processArgs[langIndex + 1] : envLocale; - } - return envLocale; - } - - /** - * Retrieve the current user locale, as retrieved from the environment. - */ - private getUserEnvLocale(): string { - const env = process.env ?? {}; - // Retrieve locale from environment - const locale: string = env.LANG ?? env.LANGUAGE ?? env.LC_NAME ?? env.LC_ALL ?? env.LC_MESSAGES ?? ''; - // The locale from environment is returned as something like 'es_ES.UTF-8'. - // The encoding is not needed at all for our translation system. - const localeName = locale?.split('.')?.[0]; - // Now generate a list of locales in order of specificity, - // e.g. for 'es_ES' a translation named 'es_ES' should be search first, then 'es'. - const localeList = localeName !== undefined ? [localeName, localeName.split('_')[0]] : []; - // Now check for each language in the list if such a locale exists - if (localeList.length > 0) { - for (const each of localeList) { - if (this.options.translator?.hasLocale(each)) { - return each; - } - } - } - return this.options.translator?.getDefaultLocale() ?? ''; - } } /** * The Type of a CLI application - * - * @group API: Main */ export type cli = CLIApp; /** * Create a new CLI application. - * @param options The application options. + * @param options - The application options. * * @group API: Main */ @@ -529,13 +385,13 @@ export const cli = (options: CLIAppOptions): CLIApp => new CLIApp(options); * This function is useful here as most times you will * want to retrieve data from the package.json file. * - * @param fileLocation The location of the file to read. + * @param fileLocation - The location of the file to read. */ -export function readJSON(fileLocation: string): any { +export const readJSON = (fileLocation: string): unknown => { try { const fileContents = fs.readFileSync(fileLocation); return JSON.parse(fileContents.toString()); } catch { return {}; } -} +}; diff --git a/src/index.ts b/src/index.ts index a7773e6..bbaf640 100755 --- a/src/index.ts +++ b/src/index.ts @@ -10,13 +10,12 @@ * You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE. * ***************************************************************************** */ + /** * @ignore */ -export * from './Types'; -export * from './Functions'; export * from './Events/EventEmitter'; export * from './Expectations'; -export * from './Translations'; +export * from './Functions'; export * from './SourceReader'; -export * from './GobstonesLang'; +export * from './Types'; diff --git a/test/Functions/matrix.test.ts b/test/Functions/Conversion/matrix.test.ts similarity index 97% rename from test/Functions/matrix.test.ts rename to test/Functions/Conversion/matrix.test.ts index a85792b..b56e5d9 100644 --- a/test/Functions/matrix.test.ts +++ b/test/Functions/Conversion/matrix.test.ts @@ -3,7 +3,7 @@ */ import { describe, expect, it } from '@jest/globals'; -import { matrix } from '../../src/Functions/matrix'; +import { matrix } from '../../../src/Functions/Conversion/matrix'; describe(`matrix`, () => { it(`Contains as much columns as the ones indicated on creation`, () => { diff --git a/test/Functions/symbolAsString.test.ts b/test/Functions/Conversion/symbolAsString.test.ts similarity index 91% rename from test/Functions/symbolAsString.test.ts rename to test/Functions/Conversion/symbolAsString.test.ts index 7cb15ee..7a25130 100644 --- a/test/Functions/symbolAsString.test.ts +++ b/test/Functions/Conversion/symbolAsString.test.ts @@ -15,7 +15,7 @@ */ import { describe, expect, it } from '@jest/globals'; -import { symbolAsString } from '../../src/Functions/symbolAsString'; +import { symbolAsString } from '../../../src/Functions/Conversion/symbolAsString'; describe(`matrix`, () => { it('symbolAsString works as expected', () => { diff --git a/test/Functions/ExecutionManagement/debounce.test.ts b/test/Functions/ExecutionManagement/debounce.test.ts new file mode 100644 index 0000000..960ac2e --- /dev/null +++ b/test/Functions/ExecutionManagement/debounce.test.ts @@ -0,0 +1,386 @@ +/** + * @author Alan Rodas Bonjour + */ +import { describe, expect, describe as given, it, jest } from '@jest/globals'; + +import { debounce } from '../../../src/Functions/ExecutionManagement/debounce'; + +describe('debounce', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + afterEach(() => { + jest.resetAllMocks(); + jest.useRealTimers(); + }); + given('general behavior', () => { + it('is a function', () => { + expect(typeof debounce).toBe('function'); + }); + + it('works with multiple independent instances', () => { + jest.useFakeTimers(); + const callback1 = jest.fn(); + const callback2 = jest.fn(); + const fn1 = debounce(callback1, 100); + const fn2 = debounce(callback2, 200); + + fn1(); + fn2(); + jest.advanceTimersByTime(100); + + expect(callback1).toHaveBeenCalledTimes(1); + expect(callback2).toHaveBeenCalledTimes(0); + + jest.advanceTimersByTime(100); + expect(callback2).toHaveBeenCalledTimes(1); + + jest.resetAllMocks(); + jest.useRealTimers(); + }); + + it('executes after timeout with multiple calls', () => { + const callback = jest.fn(); + const fn = debounce(callback, 100); + + fn(); + fn(); + fn(); + jest.advanceTimersByTime(300); + + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('does not execute if debounce method cancelled before execution', () => { + const callback = jest.fn(); + const fn = debounce(callback, 100); + + fn(); + fn.clear(); + jest.advanceTimersByTime(100); + + expect(callback).toHaveBeenCalledTimes(0); + }); + + it('works with non-standard function calls', () => { + const callback = jest.fn(); + const fn = debounce(callback, 100); + const context = { a: 1 }; + + fn.call(context); + fn.apply(context); + jest.advanceTimersByTime(100); + + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('does not run if no calls made', () => { + const callback = jest.fn(); + debounce(callback, 100); + + jest.advanceTimersByTime(100); + + expect(callback).toHaveBeenCalledTimes(0); + }); + + it('does not run if calling flush method without any scheduled execution', () => { + const callback = jest.fn(); + const fn = debounce(callback, 100); + + fn.flush(); + + expect(callback).toHaveBeenCalledTimes(0); + }); + + it('runs immediately if calling the trigger function', () => { + const callback = jest.fn(); + const fn = debounce(callback, 100); + + fn(); + fn.trigger(); + + expect(callback).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(100); + + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('runs immediately if calling the trigger without affecting future calls', () => { + const callback = jest.fn(); + const fn = debounce(callback, 100); + + fn(); + fn.trigger(); + fn(); + + expect(callback).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(100); + + expect(callback).toHaveBeenCalledTimes(2); + }); + }); + + given('immediate execution', () => { + it('should execute immediately when immediate is in options object', () => { + // TODO FAILING + const callback = jest.fn(); + + const fn = debounce(callback, 100, { immediate: true }); + + fn(); + expect(callback).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(100); + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('does not execute immediately when immediate is false', () => { + const callback = jest.fn(); + + const fn = debounce(callback, 100, { immediate: false }); + + fn(); + jest.advanceTimersByTime(50); + expect(callback).toHaveBeenCalledTimes(0); + + jest.advanceTimersByTime(50); + expect(callback).toHaveBeenCalledTimes(1); + }); + }); + + given('execution forced', () => { + it('does not execute prior to timeout', () => { + const callback = jest.fn(); + + const fn = debounce(callback, 100); + + setTimeout(fn, 100); + setTimeout(fn, 150); + + jest.advanceTimersByTime(175); + expect(callback).toHaveBeenCalledTimes(0); + }); + + it('does execute prior to timeout when flushed', () => { + const callback = jest.fn(); + + const fn = debounce(callback, 100); + + setTimeout(fn, 100); + setTimeout(fn, 150); + + jest.advanceTimersByTime(175); + + fn.flush(); + + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('does not execute again after timeout when flushed before the timeout', () => { + const callback = jest.fn(); + + const fn = debounce(callback, 100); + + setTimeout(fn, 100); + setTimeout(fn, 150); + + jest.advanceTimersByTime(175); + + fn.flush(); + + expect(callback).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(225); + + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('does not execute on a timer after being flushed', () => { + const callback = jest.fn(); + + const fn = debounce(callback, 100); + + setTimeout(fn, 100); + setTimeout(fn, 150); + + jest.advanceTimersByTime(175); + + fn.flush(); + + expect(callback).toHaveBeenCalledTimes(1); + + setTimeout(fn, 250); + + jest.advanceTimersByTime(400); + expect(callback).toHaveBeenCalledTimes(2); + }); + + it('does not execute when flushed if nothing was scheduled', () => { + const callback = jest.fn(); + + const fn = debounce(callback, 100); + + fn.flush(); + expect(callback).toHaveBeenCalledTimes(0); + }); + + it('does execute with correct args when called again from within timeout', () => { + // TODO FAILING + const callback = jest.fn<(n: number) => void>((n) => { + --n; + + if (n > 0) { + fn(n); + } + }); + + const fn = debounce(callback, 100); + + fn(3); + + jest.advanceTimersByTime(125); + jest.advanceTimersByTime(250); + jest.advanceTimersByTime(375); + + expect(callback).toHaveBeenCalledTimes(3); + + expect(callback.mock.calls[0]).toStrictEqual([3]); + expect(callback.mock.calls[1]).toStrictEqual([2]); + expect(callback.mock.calls[2]).toStrictEqual([1]); + }); + }); + + given('debounce edge cases', () => { + it('works with zero wait time', () => { + const callback = jest.fn(); + const fn = debounce(callback, 0); + + fn(); + jest.advanceTimersByTime(1); + + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('throws error when negative wait time', () => { + expect(() => { + debounce(() => { + // Empty + }, -100); + }).toThrowError(RangeError); + }); + + it('calls only once on repeated rapid calls', () => { + const callback = jest.fn(); + const fn = debounce(callback, 100); + + fn(); + fn(); + fn(); + jest.advanceTimersByTime(100); + + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('calls only once on single call', () => { + const callback = jest.fn(); + const fn = debounce(callback, 100); + + fn(); + jest.advanceTimersByTime(100); + + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('calls even on long wait time', () => { + const callback = jest.fn(); + const fn = debounce(callback, 10000); + + fn(); + jest.advanceTimersByTime(10000); + + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('function arguments gets preserved on calls', () => { + // TODO FAILING + const callback = jest.fn(); + const fn = debounce(callback, 100); + + fn('test', 123); + jest.advanceTimersByTime(100); + + expect(callback).toBeCalledWith('test', 123); + }); + + it('function context gets preserved on calls', () => { + const callback = jest.fn(); + const context = { a: 1 }; + + // Bind the context to the debounced function + const fn = debounce(callback.bind(context), 100); + + fn(); + jest.advanceTimersByTime(100); + + expect(callback.mock.contexts[0]).toStrictEqual(context); + }); + + it('clear calls makes the function not to be invoked', () => { + const callback = jest.fn(); + const fn = debounce(callback, 100); + + fn(); + fn.clear(); + jest.advanceTimersByTime(100); + + expect(callback).toHaveBeenCalledTimes(0); + }); + + it('passing a non-function produces an error', () => { + expect(() => { + debounce(123 as unknown as any, 100); + }).toThrowError(TypeError); + }); + }); + + given('a particular type of context', () => { + it('throws an error if debounced method is called with different contexts', () => { + function MyClass(): void { + // Empty + } + + MyClass.prototype.debounced = debounce(() => { + // Empty + }); + + const instance1 = new MyClass(); + const instance2 = new MyClass(); + + instance1.debounced(); + + expect(() => { + instance2.debounced(); + // Error, as the method is called with different contexts. + }).toThrow(); + }); + }); + + given('a particular time', () => { + it('debounces with fast timeout', () => { + const callback = jest.fn(); + + const fn = debounce(callback, 100); + + setTimeout(fn, 100); + setTimeout(fn, 150); + setTimeout(fn, 200); + setTimeout(fn, 250); + + jest.advanceTimersByTime(350); + + expect(callback).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/test/Functions/ExecutionManagement/throttle.test.ts b/test/Functions/ExecutionManagement/throttle.test.ts new file mode 100644 index 0000000..40bc85e --- /dev/null +++ b/test/Functions/ExecutionManagement/throttle.test.ts @@ -0,0 +1,312 @@ +/** + * @author Alan Rodas Bonjour + */ +import { describe, expect, describe as given, it } from '@jest/globals'; + +import { throttle } from '../../../src/Functions/ExecutionManagement/throttle'; + +describe('throttle', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + afterEach(() => { + jest.resetAllMocks(); + jest.useRealTimers(); + }); + given('general behavior', () => { + it('is a function', () => { + expect(typeof throttle).toBe('function'); + }); + + it('fails if non function is passed as first argument', () => { + expect(() => { + throttle(undefined as any, 1); + }).toThrowError(TypeError); + }); + + it('fails if a negative number is passed as second argument', () => { + expect(() => { + throttle(jest.fn(), -1); + }).toThrowError(RangeError); + }); + + it('is called at most once per interval', () => { + const callback = jest.fn(); + const wait = 100; + const total = 300; + const throttled = throttle(callback, wait); + const interval = setInterval(throttled, 20); + + jest.advanceTimersByTime(300); + + clearInterval(interval); + + // Using floor since the first call happens immediately + const expectedCalls = 1 + Math.floor((total - wait) / wait); + expect(callback).toHaveBeenCalledTimes(expectedCalls); + }); + + it('executes final call after wait time', () => { + const callback = jest.fn(); + const wait = 100; + const throttled = throttle(callback, wait); + throttled(); + throttled(); + + expect(callback).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(wait + 10); + + expect(callback).toHaveBeenCalledTimes(2); + }); + + it('preserves last context upon calls', () => { + let context; + const wait = 100; + const throttled = throttle(function () { + // eslint-disable-next-line no-invalid-this, @typescript-eslint/no-this-alias + context = this; + }, wait); + + const foo = {}; + const bar = {}; + throttled.call(foo); + throttled.call(bar); + + expect(context).toStrictEqual(foo); + + jest.advanceTimersByTime(wait + 5); + + expect(context).toStrictEqual(bar); + }); + + it('preserves last arguments upon calls', () => { + let args; + const wait = 100; + const throttled = throttle((...localArguments) => { + args = localArguments; + }, wait); + + throttled(1); + throttled(2); + + expect(args[0]).toBe(1); + + jest.advanceTimersByTime(wait + 5); + + expect(args[0]).toBe(2); + }); + + it('handles rapid succession calls', () => { + const callback = jest.fn(); + const wait = 50; + const throttled = throttle(callback, wait); + + throttled(); + throttled(); + throttled(); + + expect(callback).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(wait + 10); + + expect(callback).toHaveBeenCalledTimes(2); + }); + + it('responds to different arguments', () => { + let lastArg; + const wait = 50; + const throttled = throttle((arg) => { + lastArg = arg; + }, wait); + + throttled(1); + throttled(2); + throttled(3); + + expect(lastArg).toBe(1); + + jest.advanceTimersByTime(wait + 10); + + expect(lastArg).toBe(3); + }); + + it('handles repeated calls post-wait', () => { + const callback = jest.fn(); + const wait = 50; + const throttled = throttle(callback, wait); + + throttled(); + jest.advanceTimersByTime(wait + 10); + throttled(); + + expect(callback).toHaveBeenCalledTimes(2); + }); + + it('does not call function within wait time', () => { + const callback = jest.fn(); + const wait = 100; + const throttled = throttle(callback, wait); + + throttled(); + jest.advanceTimersByTime(wait / 2); + throttled(); + + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('calls function immediately each time if given zero wait time', () => { + const callback = jest.fn(); + const wait = 0; + const throttled = throttle(callback, wait); + + throttled(); + throttled(); + throttled(); + + expect(callback).toHaveBeenCalledTimes(3); + }); + + it('delays subsequent calls appropriately with large wait time', () => { + const callback = jest.fn(); + const wait = 1000; // 1 second + const throttled = throttle(callback, wait); + + throttled(); + expect(callback).toHaveBeenCalledTimes(1); + + // Attempt a call before the wait time elapses + jest.advanceTimersByTime(500); + throttled(); + expect(callback).toHaveBeenCalledTimes(1); + + // Check after the wait time + jest.advanceTimersByTime(600); // Total 1100ms + expect(callback).toHaveBeenCalledTimes(2); + }); + + it('handles calls from different contexts', () => { + const wait = 100; + + const throttled = throttle(function () { + // eslint-disable-next-line no-invalid-this + this.callCount++; + }, wait); + + const objectA = { callCount: 0 }; + const objectB = { callCount: 0 }; + + throttled.call(objectA); + throttled.call(objectB); + + expect(objectA.callCount).toBe(1); + expect(objectB.callCount).toBe(0); + + jest.advanceTimersByTime(wait + 10); + + expect(objectB.callCount).toBe(1); + }); + + it('allows immediate invocation after wait time from last call', () => { + const callback = jest.fn(); + const wait = 100; + const throttled = throttle(callback, wait); + + throttled(); + jest.advanceTimersByTime(wait + 10); + throttled(); + + expect(callback).toHaveBeenCalledTimes(2); + }); + + it('handles rapid calls with short delays', () => { + const callback = jest.fn(); + const wait = 100; + const throttled = throttle(callback, wait); + + throttled(); + jest.advanceTimersByTime(30); + throttled(); + jest.advanceTimersByTime(30); + throttled(); + + expect(callback).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(wait); + + expect(callback).toHaveBeenCalledTimes(2); + }); + + it('behaves correctly with extremely short wait times', () => { + const callback = jest.fn(); + const wait = 1; // 1 millisecond + const throttled = throttle(callback, wait); + + throttled(); + throttled(); + throttled(); + + jest.advanceTimersByTime(5); + + expect(callback).toHaveBeenCalled(); + }); + + it('simultaneous throttled functions with different wait times operate independently', () => { + const callback1 = jest.fn(); + const callback2 = jest.fn(); + const wait1 = 50; + const wait2 = 150; + const throttled1 = throttle(callback1, wait1); + const throttled2 = throttle(callback2, wait2); + + throttled1(); + throttled2(); + + jest.advanceTimersByTime(60); + throttled1(); + throttled2(); + + expect(callback1).toHaveBeenCalledTimes(2); + expect(callback2).toHaveBeenCalledTimes(1); + }); + + it('only apply effects once per interval on functions with side effects', () => { + let sideEffectCounter = 0; + const incrementSideEffect = (): void => { + sideEffectCounter++; + }; + + const wait = 100; + const throttledIncrement = throttle(incrementSideEffect, wait); + + throttledIncrement(); + throttledIncrement(); + throttledIncrement(); + + expect(sideEffectCounter).toBe(1); + + jest.advanceTimersByTime(wait + 10); + + expect(sideEffectCounter).toBe(2); + }); + + it('handles system time changes', () => { + const callback = jest.fn(); + const wait = 100; + const throttled = throttle(callback, wait); + + const originalNow = Date.now; + Date.now = () => originalNow() + 1000; // Simulate a time jump forward + + throttled(); + throttled(); + + Date.now = originalNow; // Reset Date.now to original + + expect(callback).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(wait); + expect(callback).toHaveBeenCalledTimes(2); + }); + }); +}); diff --git a/test/Functions/deepStringAssign.test.ts b/test/Functions/ObjectManipulation/deepStringAssign.test.ts similarity index 95% rename from test/Functions/deepStringAssign.test.ts rename to test/Functions/ObjectManipulation/deepStringAssign.test.ts index 69a4ed5..b901df6 100644 --- a/test/Functions/deepStringAssign.test.ts +++ b/test/Functions/ObjectManipulation/deepStringAssign.test.ts @@ -3,8 +3,8 @@ */ import { beforeEach, describe, describe as given, expect, it } from '@jest/globals'; -import { deepStringAssign } from '../../src/Functions/deepStringAssign'; -import { Subset } from '../../src/Types/Subset'; +import { deepStringAssign } from '../../../src/Functions/ObjectManipulation/deepStringAssign'; +import { Subset } from '../../../src/Types/Subset'; interface A { a1: string; diff --git a/test/Functions/flatten.test.ts b/test/Functions/ObjectManipulation/flatten.test.ts similarity index 99% rename from test/Functions/flatten.test.ts rename to test/Functions/ObjectManipulation/flatten.test.ts index bccc144..44dda87 100644 --- a/test/Functions/flatten.test.ts +++ b/test/Functions/ObjectManipulation/flatten.test.ts @@ -3,7 +3,7 @@ */ import { describe, expect, it } from '@jest/globals'; -import { flatten, unflatten } from '../../src/Functions/flatten'; +import { flatten, unflatten } from '../../../src/Functions/ObjectManipulation/flatten'; const primitives = { String: 'good morning', diff --git a/test/Functions/deepEquals.test.ts b/test/Functions/Querying/deepEquals.test.ts similarity index 99% rename from test/Functions/deepEquals.test.ts rename to test/Functions/Querying/deepEquals.test.ts index db120bb..565512e 100644 --- a/test/Functions/deepEquals.test.ts +++ b/test/Functions/Querying/deepEquals.test.ts @@ -3,7 +3,7 @@ */ import { describe, expect, it } from '@jest/globals'; -import { deepEquals } from '../../src/Functions/deepEquals'; +import { deepEquals } from '../../../src/Functions/Querying/deepEquals'; const given = describe; diff --git a/test/GobstonesLang/Board.test.ts b/test/GobstonesLang/Board.test.ts deleted file mode 100644 index 8e8d485..0000000 --- a/test/GobstonesLang/Board.test.ts +++ /dev/null @@ -1,753 +0,0 @@ -/** - * @author Alan Rodas Bonjour - */ -import { beforeEach, describe, expect, it } from '@jest/globals'; - -import { matrix } from '../../src/Functions/matrix'; -import { Board } from '../../src/GobstonesLang/Board'; -import { Cell } from '../../src/GobstonesLang/Cell'; -import { Color } from '../../src/GobstonesLang/Color'; -import { Direction } from '../../src/GobstonesLang/Direction'; - -let board: Board; - -describe(`Board`, () => { - it(`Has sensitive defaults`, () => { - board = new Board(); - expect(board.width).toBe(4); - expect(board.height).toBe(4); - expect(board.head).toStrictEqual([0, 0]); - }); - - beforeEach(() => { - // 5x7 board with head in center, red in first row, - // green in first column, 5 blue and 3 black in center - board = new Board( - 5, - 7, - [2, 3], - [ - { x: 0, y: 0, [Color.RED]: 1, [Color.GREEN]: 1 }, - { x: 1, y: 0, [Color.RED]: 1 }, - { x: 2, y: 0, [Color.RED]: 1 }, - { x: 3, y: 0, [Color.RED]: 1 }, - { x: 4, y: 0, [Color.RED]: 1 }, - { x: 0, y: 1, [Color.GREEN]: 1 }, - { x: 0, y: 2, [Color.GREEN]: 1 }, - { x: 0, y: 3, [Color.GREEN]: 1 }, - { x: 0, y: 4, [Color.GREEN]: 1 }, - { x: 0, y: 5, [Color.GREEN]: 1 }, - { x: 0, y: 6, [Color.GREEN]: 1 }, - { x: 2, y: 3, [Color.BLUE]: 5, [Color.BLACK]: 3 } - ] - ); - }); - - it(`Clones as another board correctly`, () => { - const newBoard = board.clone(); - - expect(newBoard).not.toBe(board); - expect(newBoard.width).toBe(board.width); - expect(newBoard.height).toBe(board.height); - expect(newBoard.headX).toBe(board.headX); - expect(newBoard.headY).toBe(board.headY); - newBoard.foreachCells((cell, j, i) => { - expect(cell.getStonesOf(Color.Blue)).toBe(board.getCell(i, j).getStonesOf(Color.Blue)); - expect(cell.getStonesOf(Color.Black)).toBe(board.getCell(i, j).getStonesOf(Color.Black)); - expect(cell.getStonesOf(Color.Red)).toBe(board.getCell(i, j).getStonesOf(Color.Red)); - expect(cell.getStonesOf(Color.Green)).toEqual(board.getCell(i, j).getStonesOf(Color.Green)); - }); - }); - - it(`Answers that it's a board`, () => { - expect(board.isBoard).toBe(true); - }); - - it(`Answers it's format correctly`, () => { - expect(board.format).toBe('GBB/1.0'); - }); - - it(`Answers it's size correctly`, () => { - expect(board.width).toBe(5); - expect(board.height).toBe(7); - }); - - it(`Answers it's head position correctly`, () => { - expect(board.head[0]).toBe(2); - expect(board.head[1]).toBe(3); - expect(board.headX).toBe(2); - expect(board.headY).toBe(3); - expect(board.head[0]).toBe(board.headX); - expect(board.head[1]).toBe(board.headY); - }); - - it(`Answers with a cell data correctly`, () => { - // 0,0, origin cell - expect(board.getCell(0, 0).x).toBe(0); - expect(board.getCell(0, 0).y).toBe(0); - expect(board.getCell(0, 0).isEmpty()).toBe(false); - expect(board.getCell(0, 0).getStonesOf(Color.Blue)).toBe(0); - expect(board.getCell(0, 0).getStonesOf(Color.Black)).toBe(0); - expect(board.getCell(0, 0).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(0, 0).getStonesOf(Color.Green)).toBe(1); - expect(board.getCell(0, 0).hasStonesOf(Color.Blue)).toBe(false); - expect(board.getCell(0, 0).hasStonesOf(Color.Black)).toBe(false); - expect(board.getCell(0, 0).hasStonesOf(Color.Red)).toBe(true); - expect(board.getCell(0, 0).hasStonesOf(Color.Green)).toBe(true); - // 2,3, center cell - expect(board.getCell(2, 3).x).toBe(2); - expect(board.getCell(2, 3).y).toBe(3); - expect(board.getCell(2, 3).isEmpty()).toBe(false); - expect(board.getCell(2, 3).getStonesOf(Color.Blue)).toBe(5); - expect(board.getCell(2, 3).getStonesOf(Color.Black)).toBe(3); - expect(board.getCell(2, 3).getStonesOf(Color.Red)).toBe(0); - expect(board.getCell(2, 3).getStonesOf(Color.Green)).toBe(0); - expect(board.getCell(2, 3).hasStonesOf(Color.Blue)).toBe(true); - expect(board.getCell(2, 3).hasStonesOf(Color.Black)).toBe(true); - expect(board.getCell(2, 3).hasStonesOf(Color.Red)).toBe(false); - expect(board.getCell(2, 3).hasStonesOf(Color.Green)).toBe(false); - // 4,6 top-right corner - expect(board.getCell(4, 6).x).toBe(4); - expect(board.getCell(4, 6).y).toBe(6); - expect(board.getCell(4, 6).isEmpty()).toBe(true); - expect(board.getCell(4, 6).getStonesOf(Color.Blue)).toBe(0); - expect(board.getCell(4, 6).getStonesOf(Color.Black)).toBe(0); - expect(board.getCell(4, 6).getStonesOf(Color.Red)).toBe(0); - expect(board.getCell(4, 6).getStonesOf(Color.Green)).toBe(0); - expect(board.getCell(4, 6).hasStonesOf(Color.Blue)).toBe(false); - expect(board.getCell(4, 6).hasStonesOf(Color.Black)).toBe(false); - expect(board.getCell(4, 6).hasStonesOf(Color.Red)).toBe(false); - expect(board.getCell(4, 6).hasStonesOf(Color.Green)).toBe(false); - - expect(board.getCell().getStonesOf(Color.Blue)).toBe(5); - expect(board.getCell().getStonesOf(Color.Black)).toBe(3); - expect(board.getCell().getStonesOf(Color.Red)).toBe(0); - expect(board.getCell().getStonesOf(Color.Green)).toBe(0); - }); - - it(`Answers with a column data correctly`, () => { - const firstColumn = board.getColumn(0); - for (const cell of firstColumn) { - expect(cell.hasStonesOf(Color.Green)).toBe(true); - expect(cell.getStonesOf(Color.Green)).toBe(1); - } - - const lastColumn = board.getColumn(4); - for (const cell of lastColumn) { - if (cell.y !== 0) { - expect(cell.isEmpty()).toBe(true); - } else { - expect(cell.hasStonesOf(Color.Red)).toBe(true); - expect(cell.getStonesOf(Color.Red)).toBe(1); - } - } - }); - - it(`Answers with a row data correctly`, () => { - const firstRow = board.getRow(0); - for (const cell of firstRow) { - expect(cell.hasStonesOf(Color.Red)).toBe(true); - expect(cell.getStonesOf(Color.Red)).toBe(1); - } - - const lastRow = board.getRow(6); - for (const cell of lastRow) { - if (cell.x !== 0) { - expect(cell.isEmpty()).toBe(true); - } else { - expect(cell.hasStonesOf(Color.Green)).toBe(true); - expect(cell.getStonesOf(Color.Green)).toBe(1); - } - } - }); - - it(`Answers with all columns correctly`, () => { - const columns = board.getColumns(); - expect(columns.length).toBe(5); - for (let i = 0; i < columns.length; i++) { - expect(columns[i]).toStrictEqual(board.getColumn(i)); - } - }); - - it(`Answers with all rows correctly`, () => { - const rows = board.getRows(); - expect(rows.length).toBe(7); - for (let i = 0; i < rows.length; i++) { - expect(rows[i]).toStrictEqual(board.getRow(i)); - } - }); - - it(`Answers with a correct result when folding over cells`, () => { - const totalOfRed = board.foldCells((total: number, cell: Cell) => total + cell.getStonesOf(Color.Red), 0); - expect(totalOfRed).toBe(5); - - const totalOfGreen = board.foldCells((total: number, cell: Cell) => total + cell.getStonesOf(Color.Green), 0); - expect(totalOfGreen).toBe(7); - - const totalOfStones = board.foldCells((total: number, cell: Cell) => total + cell.getStonesAmount(), 0); - expect(totalOfStones).toBe(20); - }); - - it(`Answers with a correct result when mapping over cells`, () => { - const matrixWithRed = board.mapCells((cell: Cell) => cell.getStonesOf(Color.Red)); - expect(matrixWithRed).toStrictEqual(matrix(5, 7, (_, y) => (y === 0 ? 1 : 0))); - - const matrixWithGreen = board.mapCells((cell: Cell) => cell.getStonesOf(Color.Green)); - expect(matrixWithGreen).toStrictEqual(matrix(5, 7, (x, _) => (x === 0 ? 1 : 0))); - - const matrixWithStoneAmount = board.mapCells((cell: Cell) => cell.getStonesAmount()); - expect(matrixWithStoneAmount).toStrictEqual( - matrix(5, 7, (x, y) => (x === 0 && y === 0 ? 2 : x === 0 ? 1 : y === 0 ? 1 : x === 2 && y === 3 ? 8 : 0)) - ); - }); - - it(`Answers with a correct result when filtering over cells`, () => { - const onlyCellsWithRed = board.filterCells((cell: Cell) => cell.hasStonesOf(Color.Red)); - expect(onlyCellsWithRed).toStrictEqual([ - board.getCell(0, 0), - board.getCell(1, 0), - board.getCell(2, 0), - board.getCell(3, 0), - board.getCell(4, 0) - ]); - - const onlyCellsWithGreen = board.filterCells((cell: Cell) => cell.hasStonesOf(Color.Green)); - expect(onlyCellsWithGreen).toStrictEqual([ - board.getCell(0, 0), - board.getCell(0, 1), - board.getCell(0, 2), - board.getCell(0, 3), - board.getCell(0, 4), - board.getCell(0, 5), - board.getCell(0, 6) - ]); - - const onlyCellsWithBlueAnBlack = board.filterCells( - (cell: Cell) => cell.hasStonesOf(Color.Blue) && cell.hasStonesOf(Color.Black) - ); - expect(onlyCellsWithBlueAnBlack[0]).toBe(board.getCell(2, 3)); - }); - - it(`Answers with a correct result when doing foreach over cells`, () => { - let x = 0; - let y = 0; - const adder = (): void => { - x++; - }; - const partialAdder = (cell: Cell): void => { - if (cell.x > 2 && cell.y > 3) { - y++; - } - }; - board.foreachCells(adder); - board.foreachCells(partialAdder); - - expect(x).toBe(35); - expect(y).toBe(6); - }); - - it(`Cleans the board correctly`, () => { - board.clean(); - board.foreachCells((cell: Cell) => { - expect(cell.isEmpty()).toBe(true); - }); - }); - - it(`Never fails when attempting to set a valid head location`, () => { - expect(() => { - board.head = [0, 0]; - }).not.toThrow(); - expect(() => { - board.head = [4, 0]; - }).not.toThrow(); - expect(() => { - board.head = [0, 6]; - }).not.toThrow(); - expect(() => { - board.head = [4, 6]; - }).not.toThrow(); - expect(() => { - board.head = [2, 2]; - }).not.toThrow(); - expect(() => { - board.head = [4, 3]; - }).not.toThrow(); - expect(() => { - board.head = [2, 6]; - }).not.toThrow(); - }); - - it(`Sets the head to the correct location when setting`, () => { - board.head = [0, 0]; - expect(board.headX).toBe(0); - expect(board.headY).toBe(0); - - board.head = [4, 0]; - expect(board.headX).toBe(4); - expect(board.headY).toBe(0); - - board.head = [0, 6]; - expect(board.headX).toBe(0); - expect(board.headY).toBe(6); - - board.head = [4, 6]; - expect(board.headX).toBe(4); - expect(board.headY).toBe(6); - }); - - // eslint-disable-next-line max-len - it(`Throws LocationFallsOutsideBoard with attempt SetLocation when attempting to move to an invalid location`, () => { - expect(() => { - board.head = [-1, 0]; - }).toThrow(); - try { - board.head = [-1, 0]; - } catch (err) { - expect(err.name).toBe('LocationFallsOutsideBoard'); - expect(err.attempt).toBe('SetLocation'); - } - - expect(() => { - board.head = [3, -1]; - }).toThrow(); - try { - board.head = [3, -1]; - } catch (err) { - expect(err.name).toBe('LocationFallsOutsideBoard'); - expect(err.attempt).toBe('SetLocation'); - } - - expect(() => { - board.head = [5, 2]; - }).toThrow(); - try { - board.head = [5, 2]; - } catch (err) { - expect(err.name).toBe('LocationFallsOutsideBoard'); - expect(err.attempt).toBe('SetLocation'); - } - - expect(() => { - board.head = [4, 7]; - }).toThrow(); - try { - board.head = [4, 7]; - } catch (err) { - expect(err.name).toBe('LocationFallsOutsideBoard'); - expect(err.attempt).toBe('SetLocation'); - } - }); - - it(`Moves the head correctly when not falling outside the board`, () => { - board.moveHeadTo(Direction.East); - expect(board.headX).toBe(3); - expect(board.headY).toBe(3); - - board.moveHeadTo(Direction.East); - expect(board.headX).toBe(4); - expect(board.headY).toBe(3); - - board.moveHeadTo(Direction.West); - expect(board.headX).toBe(3); - expect(board.headY).toBe(3); - - board.moveHeadTo(Direction.South); - expect(board.headX).toBe(3); - expect(board.headY).toBe(2); - - board.moveHeadTo(Direction.South); - expect(board.headX).toBe(3); - expect(board.headY).toBe(1); - - board.moveHeadTo(Direction.North); - expect(board.headX).toBe(3); - expect(board.headY).toBe(2); - - board.moveHeadTo(Direction.North); - expect(board.headX).toBe(3); - expect(board.headY).toBe(3); - - board.moveHeadTo(Direction.North); - expect(board.headX).toBe(3); - expect(board.headY).toBe(4); - }); - - it(`Throws LocationFallsOutsideBoard with attempt Move when attempting to move to an invalid location`, () => { - // Set head in origin - board.head = [0, 0]; - - expect(() => { - board.moveHeadTo(Direction.West); - }).toThrow(); - try { - board.moveHeadTo(Direction.West); - } catch (err) { - expect(err.name).toBe('LocationFallsOutsideBoard'); - expect(err.attempt).toBe('Move'); - } - - expect(() => { - board.moveHeadTo(Direction.South); - }).toThrow(); - try { - board.moveHeadTo(Direction.South); - } catch (err) { - expect(err.name).toBe('LocationFallsOutsideBoard'); - expect(err.attempt).toBe('Move'); - } - - // Set on North-East corner - board.head = [4, 6]; - - expect(() => { - board.moveHeadTo(Direction.North); - }).toThrow(); - try { - board.moveHeadTo(Direction.North); - } catch (err) { - expect(err.name).toBe('LocationFallsOutsideBoard'); - expect(err.attempt).toBe('Move'); - } - - expect(() => { - board.moveHeadTo(Direction.East); - }).toThrow(); - try { - board.moveHeadTo(Direction.East); - } catch (err) { - expect(err.name).toBe('LocationFallsOutsideBoard'); - expect(err.attempt).toBe('Move'); - } - }); - - it(`Moves the head to the edge correctly`, () => { - board.moveHeadToEdgeAt(Direction.East); - expect(board.headX).toBe(4); - expect(board.headY).toBe(3); - - board.moveHeadToEdgeAt(Direction.West); - expect(board.headX).toBe(0); - expect(board.headY).toBe(3); - - board.moveHeadToEdgeAt(Direction.South); - expect(board.headX).toBe(0); - expect(board.headY).toBe(0); - - board.moveHeadToEdgeAt(Direction.North); - expect(board.headX).toBe(0); - expect(board.headY).toBe(6); - }); - - it(`Changes the size to a bigger one correctly when added at end`, () => { - const currentHead = board.head; - board.changeSizeTo(6, 8); - expect(board.width).toBe(6); - expect(board.height).toBe(8); - expect(board.headX).toBe(currentHead[0]); - expect(board.headY).toBe(currentHead[1]); - expect(board.getCell(5, 7)).toBeDefined(); - expect(board.getCell(5, 7).isEmpty()).toBe(true); - expect(board.getCell(0, 0)).toBeDefined(); - expect(board.getCell(0, 0).isEmpty()).toBe(false); - expect(board.getCell(0, 0).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(0, 0).getStonesOf(Color.Green)).toBe(1); - }); - - it(`Changes the size to a bigger one correctly when added at beginning`, () => { - const currentHead = board.head; - board.changeSizeTo(6, 8, true); - expect(board.width).toBe(6); - expect(board.height).toBe(8); - expect(board.headX).toBe(currentHead[0] + 1); - expect(board.headY).toBe(currentHead[1] + 1); - expect(board.getCell(0, 0)).toBeDefined(); - expect(board.getCell(0, 0).isEmpty()).toBe(true); - expect(board.getCell(1, 1).isEmpty()).toBe(false); - expect(board.getCell(1, 1).getStonesOf(Color.Blue)).toBe(0); - expect(board.getCell(1, 1).getStonesOf(Color.Black)).toBe(0); - expect(board.getCell(1, 1).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(1, 1).getStonesOf(Color.Green)).toBe(1); - }); - - it(`Changes the size to a smaller one correctly when removed from end`, () => { - const currentHead = board.head; - board.changeSizeTo(3, 4); - expect(board.width).toBe(3); - expect(board.height).toBe(4); - expect(board.headX).toBe(currentHead[0]); - expect(board.headY).toBe(currentHead[1]); - expect(() => board.getCell(3, 4)).toThrow(); - expect(board.getCell(0, 0)).toBeDefined(); - expect(board.getCell(0, 0).isEmpty()).toBe(false); - expect(board.getCell(0, 0).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(0, 0).getStonesOf(Color.Green)).toBe(1); - }); - - it(`Changes the size to a smaller one correctly when removed from beginning`, () => { - board.changeSizeTo(3, 4, true); - expect(board.width).toBe(3); - expect(board.height).toBe(4); - expect(board.headX).toBe(0); - expect(board.headY).toBe(0); - expect(board.getCell(0, 0)).toBeDefined(); - expect(board.getCell(0, 0).isEmpty()).toBe(false); - expect(board.getCell(0, 0).getStonesOf(Color.Blue)).toBe(5); - expect(board.getCell(0, 0).getStonesOf(Color.Black)).toBe(3); - }); - - it(`Changes the size to a smaller board from end also changes head if needed`, () => { - board.changeSizeTo(2, 2, false); - expect(board.width).toBe(2); - expect(board.height).toBe(2); - expect(board.headX).toBe(1); - expect(board.headY).toBe(1); - }); - - it(`Changes the size to a smaller board from beginning also changes head if needed`, () => { - board.changeSizeTo(2, 2, true); - expect(board.width).toBe(2); - expect(board.height).toBe(2); - expect(board.headX).toBe(0); - expect(board.headY).toBe(0); - }); - - it(`Adds a row to the South acts as changing size from beginning`, () => { - const currentHead = board.head; - board.addRow(Direction.South); - expect(board.width).toBe(5); - expect(board.height).toBe(8); - expect(board.headX).toBe(currentHead[0]); - expect(board.headY).toBe(currentHead[1] + 1); - expect(board.getCell(0, 1).isEmpty()).toBe(false); - expect(board.getCell(0, 1).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(0, 1).getStonesOf(Color.Green)).toBe(1); - expect(board.getCell(0, 0).isEmpty()).toBe(true); - }); - - it(`Adds multiple rows to the South acts as changing size from beginning`, () => { - const currentHead = board.head; - board.addRows(3, Direction.South); - expect(board.width).toBe(5); - expect(board.height).toBe(10); - expect(board.headX).toBe(currentHead[0]); - expect(board.headY).toBe(currentHead[1] + 3); - expect(board.getCell(0, 3).isEmpty()).toBe(false); - expect(board.getCell(0, 3).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(0, 3).getStonesOf(Color.Green)).toBe(1); - expect(board.getCell(0, 0).isEmpty()).toBe(true); - }); - - it(`Adds a row to the North acts as changing size from end`, () => { - const currentHead = board.head; - board.addRow(Direction.North); - expect(board.width).toBe(5); - expect(board.height).toBe(8); - expect(board.headX).toBe(currentHead[0]); - expect(board.headY).toBe(currentHead[1]); - expect(board.getCell(0, 0).isEmpty()).toBe(false); - expect(board.getCell(0, 0).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(0, 0).getStonesOf(Color.Green)).toBe(1); - expect(board.getCell(0, 7).isEmpty()).toBe(true); - }); - - it(`Adds multiple rows to the North acts as changing size from end`, () => { - const currentHead = board.head; - board.addRows(3, Direction.North); - expect(board.width).toBe(5); - expect(board.height).toBe(10); - expect(board.headX).toBe(currentHead[0]); - expect(board.headY).toBe(currentHead[1]); - expect(board.getCell(0, 0).isEmpty()).toBe(false); - expect(board.getCell(0, 0).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(0, 0).getStonesOf(Color.Green)).toBe(1); - expect(board.getCell(0, 7).isEmpty()).toBe(true); - expect(board.getCell(0, 8).isEmpty()).toBe(true); - expect(board.getCell(0, 9).isEmpty()).toBe(true); - }); - - it(`Adds a column to the West acts as changing size from beginning`, () => { - const currentHead = board.head; - board.addColumn(Direction.West); - expect(board.width).toBe(6); - expect(board.height).toBe(7); - expect(board.headX).toBe(currentHead[0] + 1); - expect(board.headY).toBe(currentHead[1]); - expect(board.getCell(1, 0).isEmpty()).toBe(false); - expect(board.getCell(1, 0).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(1, 0).getStonesOf(Color.Green)).toBe(1); - expect(board.getCell(0, 0).isEmpty()).toBe(true); - }); - - it(`Adds multiple columns to the West acts as changing size from beginning`, () => { - const currentHead = board.head; - board.addColumns(3, Direction.West); - expect(board.width).toBe(8); - expect(board.height).toBe(7); - expect(board.headX).toBe(currentHead[0] + 3); - expect(board.headY).toBe(currentHead[1]); - expect(board.getCell(3, 0).isEmpty()).toBe(false); - expect(board.getCell(3, 0).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(3, 0).getStonesOf(Color.Green)).toBe(1); - expect(board.getCell(0, 0).isEmpty()).toBe(true); - }); - - it(`Adds a column to the East acts as changing size from end`, () => { - const currentHead = board.head; - board.addColumn(Direction.East); - expect(board.width).toBe(6); - expect(board.height).toBe(7); - expect(board.headX).toBe(currentHead[0]); - expect(board.headY).toBe(currentHead[1]); - expect(board.getCell(0, 0).isEmpty()).toBe(false); - expect(board.getCell(0, 0).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(0, 0).getStonesOf(Color.Green)).toBe(1); - expect(board.getCell(5, 0).isEmpty()).toBe(true); - }); - - it(`Adds multiple columns to the East acts as changing size from end`, () => { - const currentHead = board.head; - board.addColumns(3, Direction.East); - expect(board.width).toBe(8); - expect(board.height).toBe(7); - expect(board.headX).toBe(currentHead[0]); - expect(board.headY).toBe(currentHead[1]); - expect(board.getCell(0, 0).isEmpty()).toBe(false); - expect(board.getCell(0, 0).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(0, 0).getStonesOf(Color.Green)).toBe(1); - expect(board.getCell(5, 0).isEmpty()).toBe(true); - expect(board.getCell(6, 0).isEmpty()).toBe(true); - expect(board.getCell(7, 0).isEmpty()).toBe(true); - }); - - it(`Removes a row to the South acts as changing size from beginning`, () => { - const currentHead = board.head; - board.removeRow(Direction.South); - expect(board.width).toBe(5); - expect(board.height).toBe(6); - expect(board.headX).toBe(currentHead[0]); - expect(board.headY).toBe(currentHead[1] - 1); - expect(board.getCell(2, 2).isEmpty()).toBe(false); - expect(board.getCell(2, 2).getStonesOf(Color.Blue)).toBe(5); - expect(board.getCell(2, 2).getStonesOf(Color.Black)).toBe(3); - }); - - it(`Removes multiple rows to the South acts as changing size from beginning`, () => { - const currentHead = board.head; - board.removeRows(3, Direction.South); - expect(board.width).toBe(5); - expect(board.height).toBe(4); - expect(board.headX).toBe(currentHead[0]); - expect(board.headY).toBe(currentHead[1] - 3); - expect(board.getCell(2, 0).isEmpty()).toBe(false); - expect(board.getCell(2, 0).getStonesOf(Color.Blue)).toBe(5); - expect(board.getCell(2, 0).getStonesOf(Color.Black)).toBe(3); - }); - - it(`Removes a row to the North acts as changing size from end`, () => { - const currentHead = board.head; - board.removeRow(Direction.North); - expect(board.width).toBe(5); - expect(board.height).toBe(6); - expect(board.headX).toBe(currentHead[0]); - expect(board.headY).toBe(currentHead[1]); - expect(board.getCell(0, 0).isEmpty()).toBe(false); - expect(board.getCell(0, 0).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(0, 0).getStonesOf(Color.Green)).toBe(1); - expect(board.getCell(2, 3).isEmpty()).toBe(false); - expect(board.getCell(2, 3).getStonesOf(Color.Blue)).toBe(5); - expect(board.getCell(2, 3).getStonesOf(Color.Black)).toBe(3); - }); - - it(`Removes multiple rows to the North acts as changing size from end`, () => { - const currentHead = board.head; - board.removeRows(3, Direction.North); - expect(board.width).toBe(5); - expect(board.height).toBe(4); - expect(board.headX).toBe(currentHead[0]); - expect(board.headY).toBe(currentHead[1]); - expect(board.getCell(0, 0).isEmpty()).toBe(false); - expect(board.getCell(0, 0).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(0, 0).getStonesOf(Color.Green)).toBe(1); - expect(board.getCell(2, 3).isEmpty()).toBe(false); - expect(board.getCell(2, 3).getStonesOf(Color.Blue)).toBe(5); - expect(board.getCell(2, 3).getStonesOf(Color.Black)).toBe(3); - }); - - it(`Removes a column to the West acts as changing size from beginning`, () => { - const currentHead = board.head; - board.removeColumn(Direction.West); - expect(board.width).toBe(4); - expect(board.height).toBe(7); - expect(board.headX).toBe(currentHead[0] - 1); - expect(board.headY).toBe(currentHead[1]); - expect(board.getCell(1, 3).isEmpty()).toBe(false); - expect(board.getCell(1, 3).getStonesOf(Color.Blue)).toBe(5); - expect(board.getCell(1, 3).getStonesOf(Color.Black)).toBe(3); - }); - - it(`Removes multiple columns to the West acts as changing size from beginning`, () => { - const currentHead = board.head; - board.removeColumns(3, Direction.West); - expect(board.width).toBe(2); - expect(board.height).toBe(7); - expect(board.headX).toBe(0); - expect(board.headY).toBe(currentHead[1]); - expect(board.getCell(0, 3).isEmpty()).toBe(true); - }); - - it(`Removes a column to the East acts as changing size from end`, () => { - const currentHead = board.head; - board.removeColumn(Direction.East); - expect(board.width).toBe(4); - expect(board.height).toBe(7); - expect(board.headX).toBe(currentHead[0]); - expect(board.headY).toBe(currentHead[1]); - expect(board.getCell(0, 0).isEmpty()).toBe(false); - expect(board.getCell(0, 0).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(0, 0).getStonesOf(Color.Green)).toBe(1); - expect(board.getCell(2, 3).isEmpty()).toBe(false); - expect(board.getCell(2, 3).getStonesOf(Color.Blue)).toBe(5); - expect(board.getCell(2, 3).getStonesOf(Color.Black)).toBe(3); - }); - - it(`Removes multiple columns to the East acts as changing size from end`, () => { - const currentHead = board.head; - board.removeColumns(3, Direction.East); - expect(board.width).toBe(2); - expect(board.height).toBe(7); - expect(board.headX).toBe(1); - expect(board.headY).toBe(currentHead[1]); - expect(board.getCell(0, 0).isEmpty()).toBe(false); - expect(board.getCell(0, 0).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(0, 0).getStonesOf(Color.Green)).toBe(1); - expect(board.getCell(1, 3).isEmpty()).toBe(true); - }); - - it(`Behaves equally when setting width than when adding columns to East`, () => { - const currentHead = board.head; - board.width = 2; - expect(board.width).toBe(2); - expect(board.height).toBe(7); - expect(board.headX).toBe(1); - expect(board.headY).toBe(currentHead[1]); - expect(board.getCell(0, 0).isEmpty()).toBe(false); - expect(board.getCell(0, 0).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(0, 0).getStonesOf(Color.Green)).toBe(1); - expect(board.getCell(1, 3).isEmpty()).toBe(true); - }); - - it(`Behaves equally when setting height than when adding rows to North`, () => { - const currentHead = board.head; - board.height = 5; - expect(board.width).toBe(5); - expect(board.height).toBe(5); - expect(board.headX).toBe(currentHead[0]); - expect(board.headY).toBe(currentHead[1]); - expect(board.getCell(0, 0).isEmpty()).toBe(false); - expect(board.getCell(0, 0).getStonesOf(Color.Red)).toBe(1); - expect(board.getCell(0, 0).getStonesOf(Color.Green)).toBe(1); - expect(board.getCell(2, 3).isEmpty()).toBe(false); - expect(board.getCell(2, 3).getStonesOf(Color.Blue)).toBe(5); - expect(board.getCell(2, 3).getStonesOf(Color.Black)).toBe(3); - }); -}); diff --git a/test/GobstonesLang/Cell.test.ts b/test/GobstonesLang/Cell.test.ts deleted file mode 100644 index a1f4e0f..0000000 --- a/test/GobstonesLang/Cell.test.ts +++ /dev/null @@ -1,447 +0,0 @@ -/** - * @author Alan Rodas Bonjour - */ -import { beforeEach, describe, expect, it } from '@jest/globals'; - -import { Board } from '../../src/GobstonesLang/Board'; -import { Cell } from '../../src/GobstonesLang/Cell'; -import { Color } from '../../src/GobstonesLang/Color'; -import { Direction } from '../../src/GobstonesLang/Direction'; - -let board: Board; -let headCell: Cell; -let cornerCell: Cell; -let originCell: Cell; - -describe(`Cell`, () => { - beforeEach(() => { - // 5x7 board with head in center, red in first row, - // green in first column, 5 blue and 3 black in center - board = new Board( - 5, - 7, - [2, 3], - [ - { x: 0, y: 0, [Color.RED]: 1, [Color.GREEN]: 1 }, - { x: 1, y: 0, [Color.RED]: 1 }, - { x: 2, y: 0, [Color.RED]: 1 }, - { x: 3, y: 0, [Color.RED]: 1 }, - { x: 4, y: 0, [Color.RED]: 1 }, - { x: 0, y: 1, [Color.GREEN]: 1 }, - { x: 0, y: 2, [Color.GREEN]: 1 }, - { x: 0, y: 3, [Color.GREEN]: 1 }, - { x: 0, y: 4, [Color.GREEN]: 1 }, - { x: 0, y: 5, [Color.GREEN]: 1 }, - { x: 0, y: 6, [Color.GREEN]: 1 }, - { x: 2, y: 3, [Color.BLUE]: 5, [Color.BLACK]: 3 } - ] - ); - headCell = board.getCell(2, 3); - cornerCell = board.getCell(4, 6); - originCell = board.getCell(0, 0); - }); - - it(`Clones as another cell correctly`, () => { - const newCornerCell = cornerCell.clone(board); - expect(newCornerCell).not.toBe(cornerCell); - expect(newCornerCell.x).toBe(cornerCell.x); - expect(newCornerCell.y).toBe(cornerCell.y); - expect(newCornerCell.getStonesOf(Color.Blue)).toBe(cornerCell.getStonesOf(Color.Blue)); - expect(newCornerCell.getStonesOf(Color.Black)).toBe(cornerCell.getStonesOf(Color.Black)); - expect(newCornerCell.getStonesOf(Color.Red)).toBe(cornerCell.getStonesOf(Color.Red)); - expect(newCornerCell.getStonesOf(Color.Green)).toBe(cornerCell.getStonesOf(Color.Green)); - - const newHeadCell = headCell.clone(board); - expect(newHeadCell).not.toBe(headCell); - expect(newHeadCell.x).toEqual(headCell.x); - expect(newHeadCell.y).toEqual(headCell.y); - expect(newHeadCell.getStonesOf(Color.Blue)).toEqual(headCell.getStonesOf(Color.Blue)); - expect(newHeadCell.getStonesOf(Color.Black)).toEqual(headCell.getStonesOf(Color.Black)); - expect(newHeadCell.getStonesOf(Color.Red)).toEqual(headCell.getStonesOf(Color.Red)); - expect(newHeadCell.getStonesOf(Color.Green)).toEqual(headCell.getStonesOf(Color.Green)); - expect(newHeadCell.isHeadLocation()).toBe(true); - }); - - it(`Answers with the correct location as within the board`, () => { - expect(headCell.x).toBe(2); - expect(headCell.y).toBe(3); - expect(headCell.location).toStrictEqual([2, 3]); - - expect(cornerCell.x).toBe(4); - expect(cornerCell.y).toBe(6); - expect(cornerCell.location).toStrictEqual([4, 6]); - }); - - it(`Answers with the correct amount of stones for each color`, () => { - expect(headCell.getStonesOf(Color.Blue)).toBe(5); - expect(headCell.getStonesOf(Color.Black)).toBe(3); - expect(headCell.getStonesOf(Color.Red)).toBe(0); - expect(headCell.getStonesOf(Color.Green)).toBe(0); - - expect(cornerCell.getStonesOf(Color.Blue)).toBe(0); - expect(cornerCell.getStonesOf(Color.Black)).toBe(0); - expect(cornerCell.getStonesOf(Color.Red)).toBe(0); - expect(cornerCell.getStonesOf(Color.Green)).toBe(0); - }); - - it(`Allows to access colors throw properties for retro compatibility`, () => { - expect(headCell.a).toBe(headCell.getStonesOf(Color.Blue)); - expect(headCell.n).toBe(headCell.getStonesOf(Color.Black)); - expect(headCell.r).toBe(headCell.getStonesOf(Color.Red)); - expect(headCell.v).toBe(headCell.getStonesOf(Color.Green)); - - expect(cornerCell.a).toBe(cornerCell.getStonesOf(Color.Blue)); - expect(cornerCell.n).toBe(cornerCell.getStonesOf(Color.Black)); - expect(cornerCell.r).toBe(cornerCell.getStonesOf(Color.Red)); - expect(cornerCell.v).toBe(cornerCell.getStonesOf(Color.Green)); - }); - - it(`Answers with the correct amount of stones for any color`, () => { - expect(headCell.getStonesAmount()).toBe(8); - - expect(cornerCell.getStonesAmount()).toBe(0); - }); - - it(`Answers with true when it has stones of a given color or false otherwise`, () => { - expect(headCell.hasStonesOf(Color.Blue)).toBe(true); - expect(headCell.hasStonesOf(Color.Black)).toBe(true); - expect(headCell.hasStonesOf(Color.Red)).toBe(false); - expect(headCell.hasStonesOf(Color.Green)).toBe(false); - - expect(cornerCell.hasStonesOf(Color.Blue)).toBe(false); - expect(cornerCell.hasStonesOf(Color.Black)).toBe(false); - expect(cornerCell.hasStonesOf(Color.Red)).toBe(false); - expect(cornerCell.hasStonesOf(Color.Green)).toBe(false); - }); - - it(`Answers to isEmpty correctly`, () => { - expect(headCell.isEmpty()).toBe(false); - expect(cornerCell.isEmpty()).toBe(true); - }); - - it(`Answers to hasStones correctly`, () => { - expect(headCell.hasStones()).toBe(true); - expect(cornerCell.hasStones()).toBe(false); - }); - - it(`Empties when empty is called`, () => { - headCell.empty(); - expect(headCell.isEmpty()).toBe(true); - - cornerCell.empty(); - expect(cornerCell.isEmpty()).toBe(true); - }); - - it(`Answers if it is the head cell correctly, depending on the board`, () => { - expect(headCell.isHeadLocation()).toBe(true); - expect(cornerCell.isHeadLocation()).toBe(false); - - board.moveHeadTo(Direction.North); - expect(headCell.isHeadLocation()).toBe(false); - - board.moveHeadToEdgeAt(Direction.North); - board.moveHeadToEdgeAt(Direction.East); - expect(cornerCell.isHeadLocation()).toBe(true); - }); - - it(`Sets the amount of stones correctly for zero or positive values`, () => { - headCell.setStonesOf(Color.Blue, 6); - headCell.setStonesOf(Color.Black, 7); - headCell.setStonesOf(Color.Red, 8); - headCell.setStonesOf(Color.Green, 9); - - expect(headCell.getStonesOf(Color.Blue)).toBe(6); - expect(headCell.getStonesOf(Color.Black)).toBe(7); - expect(headCell.getStonesOf(Color.Red)).toBe(8); - expect(headCell.getStonesOf(Color.Green)).toBe(9); - - headCell.setStonesOf(Color.Blue, 66); - headCell.setStonesOf(Color.Black, 77); - headCell.setStonesOf(Color.Red, 88); - headCell.setStonesOf(Color.Green, 99); - - expect(headCell.getStonesOf(Color.Blue)).toBe(66); - expect(headCell.getStonesOf(Color.Black)).toBe(77); - expect(headCell.getStonesOf(Color.Red)).toBe(88); - expect(headCell.getStonesOf(Color.Green)).toBe(99); - - headCell.setStonesOf(Color.Blue, 66666); - headCell.setStonesOf(Color.Black, 77777); - headCell.setStonesOf(Color.Red, 88888); - headCell.setStonesOf(Color.Green, 99999); - - expect(headCell.getStonesOf(Color.Blue)).toBe(66666); - expect(headCell.getStonesOf(Color.Black)).toBe(77777); - expect(headCell.getStonesOf(Color.Red)).toBe(88888); - expect(headCell.getStonesOf(Color.Green)).toBe(99999); - }); - - it(`Sets the amount of stones correctly for really big values`, () => { - headCell.setStonesOf(Color.Blue, 2147483647); - headCell.setStonesOf(Color.Black, 2 ** 31); - headCell.setStonesOf(Color.Red, 2 ** 32); - headCell.setStonesOf(Color.Green, 2 ** 150); - - expect(headCell.getStonesOf(Color.Blue)).toBe(2147483647); - expect(headCell.getStonesOf(Color.Black)).toBe(2147483648); - expect(headCell.getStonesOf(Color.Red)).toBe(4294967296); - expect(headCell.getStonesOf(Color.Green)).toBe(1427247692705959881058285969449495136382746624); - }); - - it(`Throws an InvalidStonesAmount with the attempt set to SetStones with negatives`, () => { - expect(() => headCell.setStonesOf(Color.Blue, -1)).toThrow(); - expect(() => headCell.setStonesOf(Color.Blue, -1000)).toThrow(); - expect(() => headCell.setStonesOf(Color.Blue, -1 * 2 ** 150)).toThrow(); - - try { - headCell.setStonesOf(Color.Blue, -1); - } catch (err) { - expect(err.name).toBe('InvalidStonesAmount'); - expect(err.attempt).toBe('SetStones'); - } - try { - headCell.setStonesOf(Color.Blue, -1000); - } catch (err) { - expect(err.name).toBe('InvalidStonesAmount'); - expect(err.attempt).toBe('SetStones'); - } - try { - headCell.setStonesOf(Color.Blue, -1 * 2 ** 150); - } catch (err) { - expect(err.name).toBe('InvalidStonesAmount'); - expect(err.attempt).toBe('SetStones'); - } - }); - - it(`Adds a stone adds one single stone if no amount is given`, () => { - headCell.addStones(Color.Blue); - headCell.addStones(Color.Black); - headCell.addStones(Color.Red); - headCell.addStones(Color.Green); - - expect(headCell.getStonesOf(Color.Blue)).toBe(6); - expect(headCell.getStonesOf(Color.Black)).toBe(4); - expect(headCell.getStonesOf(Color.Red)).toBe(1); - expect(headCell.getStonesOf(Color.Green)).toBe(1); - }); - - it(`Adds a stone adds the given amount of stones`, () => { - headCell.addStones(Color.Blue, 3); - headCell.addStones(Color.Black, 4); - headCell.addStones(Color.Red, 5); - headCell.addStones(Color.Green, 6); - - expect(headCell.getStonesOf(Color.Blue)).toBe(8); - expect(headCell.getStonesOf(Color.Black)).toBe(7); - expect(headCell.getStonesOf(Color.Red)).toBe(5); - expect(headCell.getStonesOf(Color.Green)).toBe(6); - }); - - it(`Removes a stone removes one single stone if no amount is given`, () => { - headCell.removeStones(Color.Blue); - headCell.removeStones(Color.Black); - - expect(headCell.getStonesOf(Color.Blue)).toBe(4); - expect(headCell.getStonesOf(Color.Black)).toBe(2); - }); - - it(`Removes a stone removes the given amount`, () => { - headCell.removeStones(Color.Blue, 3); - headCell.removeStones(Color.Black, 2); - - expect(headCell.getStonesOf(Color.Blue)).toBe(2); - expect(headCell.getStonesOf(Color.Black)).toBe(1); - }); - - it(`Removes throws InvalidStonesAmount with RemoveStones if more than present removed`, () => { - expect(() => headCell.removeStones(Color.Blue, 6)).toThrow(); - expect(() => headCell.removeStones(Color.Black, 4)).toThrow(); - expect(() => headCell.removeStones(Color.Red, 1)).toThrow(); - expect(() => headCell.removeStones(Color.Green, 1)).toThrow(); - - try { - headCell.removeStones(Color.Blue, 6); - } catch (err) { - expect(err.name).toBe('InvalidStonesAmount'); - expect(err.attempt).toBe('RemoveStones'); - } - try { - headCell.removeStones(Color.Black, 4); - } catch (err) { - expect(err.name).toBe('InvalidStonesAmount'); - expect(err.attempt).toBe('RemoveStones'); - } - try { - headCell.removeStones(Color.Red, 1); - } catch (err) { - expect(err.name).toBe('InvalidStonesAmount'); - expect(err.attempt).toBe('RemoveStones'); - } - try { - headCell.removeStones(Color.Green, 1); - } catch (err) { - expect(err.name).toBe('InvalidStonesAmount'); - expect(err.attempt).toBe('RemoveStones'); - } - }); - - it(`Adds throws InvalidStonesAmount with attempt AddStones if < 1 value given`, () => { - expect(() => headCell.addStones(Color.Blue, 0)).toThrow(); - expect(() => headCell.addStones(Color.Blue, -10)).toThrow(); - - try { - headCell.addStones(Color.Blue, 0); - } catch (err) { - expect(err.name).toBe('InvalidStonesAmount'); - expect(err.attempt).toBe('AddStones'); - } - try { - headCell.addStones(Color.Blue, -10); - } catch (err) { - expect(err.name).toBe('InvalidStonesAmount'); - expect(err.attempt).toBe('AddStones'); - } - }); - - it(`Removes throws InvalidStonesAmount with attempt RemoveStones if < 1 value given`, () => { - expect(() => headCell.removeStones(Color.Blue, 0)).toThrow(); - expect(() => headCell.removeStones(Color.Blue, -10)).toThrow(); - - try { - headCell.removeStones(Color.Blue, 0); - } catch (err) { - expect(err.name).toBe('InvalidStonesAmount'); - expect(err.attempt).toBe('RemoveStones'); - } - try { - headCell.removeStones(Color.Blue, -10); - } catch (err) { - expect(err.name).toBe('InvalidStonesAmount'); - expect(err.attempt).toBe('RemoveStones'); - } - }); - - it(`Answers correctly when asked if it's at border`, () => { - Direction.foreach((dir) => { - expect(headCell.isAtBorderAt(dir)).toBe(false); - }); - - expect(cornerCell.isAtBorderAt(Direction.North)).toBe(true); - expect(cornerCell.isAtBorderAt(Direction.East)).toBe(true); - expect(cornerCell.isAtBorderAt(Direction.South)).toBe(false); - expect(cornerCell.isAtBorderAt(Direction.West)).toBe(false); - - expect(originCell.isAtBorderAt(Direction.North)).toBe(false); - expect(originCell.isAtBorderAt(Direction.East)).toBe(false); - expect(originCell.isAtBorderAt(Direction.South)).toBe(true); - expect(originCell.isAtBorderAt(Direction.West)).toBe(true); - }); - - it(`Answers correctly when asked for the neighbor`, () => { - expect(headCell.neighborTo(Direction.North)).toBe(board.getCell(2, 4)); - expect(headCell.neighborTo(Direction.East)).toBe(board.getCell(3, 3)); - expect(headCell.neighborTo(Direction.South)).toBe(board.getCell(2, 2)); - expect(headCell.neighborTo(Direction.West)).toBe(board.getCell(1, 3)); - - expect(cornerCell.neighborTo(Direction.South)).toBe(board.getCell(4, 5)); - expect(cornerCell.neighborTo(Direction.West)).toBe(board.getCell(3, 6)); - - expect(originCell.neighborTo(Direction.North)).toBe(board.getCell(0, 1)); - expect(originCell.neighborTo(Direction.East)).toBe(board.getCell(1, 0)); - }); - - it(`Answers correctly when asked for the diagonal neighbor`, () => { - // 2, 3 - expect(headCell.neighborDiagonalTo(Direction.North, Direction.East)).toBe(board.getCell(3, 4)); - expect(headCell.neighborDiagonalTo(Direction.North, Direction.West)).toBe(board.getCell(1, 4)); - expect(headCell.neighborDiagonalTo(Direction.South, Direction.East)).toBe(board.getCell(3, 2)); - expect(headCell.neighborDiagonalTo(Direction.South, Direction.West)).toBe(board.getCell(1, 2)); - - expect(cornerCell.neighborDiagonalTo(Direction.South, Direction.West)).toBe(board.getCell(3, 5)); - - expect(originCell.neighborDiagonalTo(Direction.North, Direction.East)).toBe(board.getCell(1, 1)); - }); - - it(`Answers with undefined when neighbors do not exist`, () => { - expect(cornerCell.neighborTo(Direction.North)).toBe(undefined); - expect(cornerCell.neighborTo(Direction.East)).toBe(undefined); - - expect(originCell.neighborTo(Direction.South)).toBe(undefined); - expect(originCell.neighborTo(Direction.West)).toBe(undefined); - }); - - it(`Answers with undefined when diagonal neighbor do not exist`, () => { - expect(cornerCell.neighborDiagonalTo(Direction.South, Direction.East)).toBe(undefined); - expect(cornerCell.neighborDiagonalTo(Direction.North, Direction.West)).toBe(undefined); - expect(cornerCell.neighborDiagonalTo(Direction.North, Direction.East)).toBe(undefined); - - expect(originCell.neighborDiagonalTo(Direction.South, Direction.West)).toBe(undefined); - expect(originCell.neighborDiagonalTo(Direction.North, Direction.West)).toBe(undefined); - expect(originCell.neighborDiagonalTo(Direction.South, Direction.East)).toBe(undefined); - }); - - it(`Answers with orthogonal neighbors by default`, () => { - expect(headCell.neighbors()).toStrictEqual(headCell.neighbors('orthogonal')); - }); - - it(`Answers neighbors are to all directions when orthogonal given`, () => { - const headNeighbors = headCell.neighbors('orthogonal'); - expect(headNeighbors.length).toBe(4); - expect(headNeighbors[0]).toBe(headCell.neighborTo(Direction.North)); - expect(headNeighbors[1]).toBe(headCell.neighborTo(Direction.East)); - expect(headNeighbors[2]).toBe(headCell.neighborTo(Direction.South)); - expect(headNeighbors[3]).toBe(headCell.neighborTo(Direction.West)); - - const cornerNeighbors = cornerCell.neighbors('orthogonal'); - expect(cornerNeighbors.length).toBe(2); - expect(cornerNeighbors[0]).toBe(cornerCell.neighborTo(Direction.South)); - expect(cornerNeighbors[1]).toBe(cornerCell.neighborTo(Direction.West)); - - const originNeighbors = originCell.neighbors('orthogonal'); - expect(originNeighbors.length).toBe(2); - expect(originNeighbors[0]).toBe(originCell.neighborTo(Direction.North)); - expect(originNeighbors[1]).toBe(originCell.neighborTo(Direction.East)); - }); - - it(`Answers neighbors are to all corners when diagonal given`, () => { - const headNeighbors = headCell.neighbors('diagonal'); - expect(headNeighbors.length).toBe(4); - expect(headNeighbors[0]).toBe(headCell.neighborDiagonalTo(Direction.North, Direction.East)); - expect(headNeighbors[1]).toBe(headCell.neighborDiagonalTo(Direction.South, Direction.East)); - expect(headNeighbors[2]).toBe(headCell.neighborDiagonalTo(Direction.South, Direction.West)); - expect(headNeighbors[3]).toBe(headCell.neighborDiagonalTo(Direction.North, Direction.West)); - - const cornerNeighbors = cornerCell.neighbors('diagonal'); - expect(cornerNeighbors.length).toBe(1); - expect(cornerNeighbors[0]).toBe(cornerCell.neighborDiagonalTo(Direction.South, Direction.West)); - - const originNeighbors = originCell.neighbors('diagonal'); - expect(originNeighbors.length).toBe(1); - expect(originNeighbors[0]).toBe(originCell.neighborDiagonalTo(Direction.North, Direction.East)); - }); - - it(`Answers neighbors are to all directions and corners when both given`, () => { - const headNeighbors = headCell.neighbors('both'); - expect(headNeighbors.length).toBe(8); - expect(headNeighbors[0]).toBe(headCell.neighborTo(Direction.North)); - expect(headNeighbors[1]).toBe(headCell.neighborDiagonalTo(Direction.North, Direction.East)); - expect(headNeighbors[2]).toBe(headCell.neighborTo(Direction.East)); - expect(headNeighbors[3]).toBe(headCell.neighborDiagonalTo(Direction.South, Direction.East)); - expect(headNeighbors[4]).toBe(headCell.neighborTo(Direction.South)); - expect(headNeighbors[5]).toBe(headCell.neighborDiagonalTo(Direction.South, Direction.West)); - expect(headNeighbors[6]).toBe(headCell.neighborTo(Direction.West)); - expect(headNeighbors[7]).toBe(headCell.neighborDiagonalTo(Direction.North, Direction.West)); - - const cornerNeighbors = cornerCell.neighbors('both'); - expect(cornerNeighbors.length).toBe(3); - expect(cornerNeighbors[0]).toBe(cornerCell.neighborTo(Direction.South)); - expect(cornerNeighbors[1]).toBe(cornerCell.neighborDiagonalTo(Direction.South, Direction.West)); - expect(cornerNeighbors[2]).toBe(cornerCell.neighborTo(Direction.West)); - - const originNeighbors = originCell.neighbors('both'); - expect(originNeighbors.length).toBe(3); - expect(originNeighbors[0]).toBe(originCell.neighborTo(Direction.North)); - expect(originNeighbors[1]).toBe(originCell.neighborDiagonalTo(Direction.North, Direction.East)); - expect(originNeighbors[2]).toBe(originCell.neighborTo(Direction.East)); - }); -}); diff --git a/test/GobstonesLang/Color.test.ts b/test/GobstonesLang/Color.test.ts deleted file mode 100644 index 0ca78bf..0000000 --- a/test/GobstonesLang/Color.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @author Alan Rodas Bonjour - */ -import { describe, expect, it } from '@jest/globals'; - -import { Color } from '../../src/GobstonesLang/Color'; - -describe(`Color`, () => { - it(`Answers min and max correctly`, () => { - expect(Color.min()).toBe(Color.Blue); - expect(Color.max()).toBe(Color.Green); - }); - - it(`Answers with next correctly`, () => { - expect(Color.next(Color.Blue)).toBe(Color.Black); - expect(Color.next(Color.Black)).toBe(Color.Red); - expect(Color.next(Color.Red)).toBe(Color.Green); - expect(Color.next(Color.Green)).toBe(Color.Blue); - expect(Color.Blue.next()).toBe(Color.Black); - expect(Color.Black.next()).toBe(Color.Red); - expect(Color.Red.next()).toBe(Color.Green); - expect(Color.Green.next()).toBe(Color.Blue); - }); - - it(`Answers with previous correctly`, () => { - expect(Color.previous(Color.Blue)).toBe(Color.Green); - expect(Color.previous(Color.Black)).toBe(Color.Blue); - expect(Color.previous(Color.Red)).toBe(Color.Black); - expect(Color.previous(Color.Green)).toBe(Color.Red); - expect(Color.Blue.previous()).toBe(Color.Green); - expect(Color.Black.previous()).toBe(Color.Blue); - expect(Color.Red.previous()).toBe(Color.Black); - expect(Color.Green.previous()).toBe(Color.Red); - }); - - it(`Iterates in the same order than next`, () => { - let currentColor = Color.Blue; - Color.foreach((c) => { - expect(c).toBe(currentColor); - currentColor = Color.next(currentColor); - }); - }); - - it(`Answers it's string name correctly`, () => { - expect(Color.Blue.toString()).toBe(Color.BLUE); - expect(Color.Black.toString()).toBe(Color.BLACK); - expect(Color.Red.toString()).toBe(Color.RED); - expect(Color.Green.toString()).toBe(Color.GREEN); - }); - - it(`Answers it's value correctly`, () => { - expect(Color.Blue.value).toBe(Color.BLUE); - expect(Color.Black.value).toBe(Color.BLACK); - expect(Color.Red.value).toBe(Color.RED); - expect(Color.Green.value).toBe(Color.GREEN); - }); -}); diff --git a/test/GobstonesLang/Direction.test.ts b/test/GobstonesLang/Direction.test.ts deleted file mode 100644 index 0055495..0000000 --- a/test/GobstonesLang/Direction.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * @author Alan Rodas Bonjour - */ -import { describe, expect, it } from '@jest/globals'; - -import { Direction } from '../../src/GobstonesLang/Direction'; - -describe(`A Direction should`, () => { - it(`Answers min and max correctly`, () => { - expect(Direction.min()).toBe(Direction.North); - expect(Direction.max()).toBe(Direction.West); - }); - - it(`Answers with next correctly`, () => { - expect(Direction.next(Direction.North)).toBe(Direction.East); - expect(Direction.next(Direction.East)).toBe(Direction.South); - expect(Direction.next(Direction.South)).toBe(Direction.West); - expect(Direction.next(Direction.West)).toBe(Direction.North); - expect(Direction.North.next()).toBe(Direction.East); - expect(Direction.East.next()).toBe(Direction.South); - expect(Direction.South.next()).toBe(Direction.West); - expect(Direction.West.next()).toBe(Direction.North); - }); - - it(`Answers with previous correctly`, () => { - expect(Direction.previous(Direction.North)).toBe(Direction.West); - expect(Direction.previous(Direction.East)).toBe(Direction.North); - expect(Direction.previous(Direction.South)).toBe(Direction.East); - expect(Direction.previous(Direction.West)).toBe(Direction.South); - expect(Direction.North.previous()).toBe(Direction.West); - expect(Direction.East.previous()).toBe(Direction.North); - expect(Direction.South.previous()).toBe(Direction.East); - expect(Direction.West.previous()).toBe(Direction.South); - }); - - it(`Iterates in the same order than next`, () => { - let currentDirection = Direction.North; - Direction.foreach((c) => { - expect(c).toBe(currentDirection); - currentDirection = Direction.next(currentDirection); - }); - }); - - it(`Answers with opposite correctly`, () => { - expect(Direction.opposite(Direction.North)).toBe(Direction.South); - expect(Direction.opposite(Direction.East)).toBe(Direction.West); - expect(Direction.opposite(Direction.South)).toBe(Direction.North); - expect(Direction.opposite(Direction.West)).toBe(Direction.East); - expect(Direction.North.opposite()).toBe(Direction.South); - expect(Direction.East.opposite()).toBe(Direction.West); - expect(Direction.South.opposite()).toBe(Direction.North); - expect(Direction.West.opposite()).toBe(Direction.East); - }); - - it(`Answers with isVertical correctly`, () => { - expect(Direction.isVertical(Direction.North)).toBe(true); - expect(Direction.isVertical(Direction.East)).toBe(false); - expect(Direction.isVertical(Direction.South)).toBe(true); - expect(Direction.isVertical(Direction.West)).toBe(false); - expect(Direction.North.isVertical()).toBe(true); - expect(Direction.East.isVertical()).toBe(false); - expect(Direction.South.isVertical()).toBe(true); - expect(Direction.West.isVertical()).toBe(false); - }); - - it(`Answers with isHorizontal correctly`, () => { - expect(Direction.isHorizontal(Direction.North)).toBe(false); - expect(Direction.isHorizontal(Direction.East)).toBe(true); - expect(Direction.isHorizontal(Direction.South)).toBe(false); - expect(Direction.isHorizontal(Direction.West)).toBe(true); - expect(Direction.North.isHorizontal()).toBe(false); - expect(Direction.East.isHorizontal()).toBe(true); - expect(Direction.South.isHorizontal()).toBe(false); - expect(Direction.West.isHorizontal()).toBe(true); - }); - - it(`Answers it's string name correctly`, () => { - expect(Direction.North.toString()).toBe(Direction.NORTH); - expect(Direction.East.toString()).toBe(Direction.EAST); - expect(Direction.South.toString()).toBe(Direction.SOUTH); - expect(Direction.West.toString()).toBe(Direction.WEST); - }); - - it(`Answers it's value correctly`, () => { - expect(Direction.North.value).toBe(Direction.NORTH); - expect(Direction.East.value).toBe(Direction.EAST); - expect(Direction.South.value).toBe(Direction.SOUTH); - expect(Direction.West.value).toBe(Direction.WEST); - }); -}); diff --git a/test/Translations/Translator.test.ts b/test/Translations/Translator.test.ts deleted file mode 100644 index d02a844..0000000 --- a/test/Translations/Translator.test.ts +++ /dev/null @@ -1,226 +0,0 @@ -/** - * @author Alan Rodas Bonjour - */ -import { beforeEach, describe, expect, it } from '@jest/globals'; - -import { Translator } from '../../src/Translations/Translator'; - -const given = describe; - -interface Locale { - msg1: string; - msg2: string; - deep1: { - msg1: string; - msg2: string; - deep2: { - msg1: string; - }; - }; - plural: string; - 'plural.1': string; - 'plural.2': string; - 'plural.n': string; -} - -const lang1: Locale = { - msg1: 'lang1-msg1', - msg2: 'lang1-msg2', - deep1: { - msg1: 'lang1-deep1-msg1', - msg2: 'lang1-deep1-msg2 ${key}', - deep2: { - msg1: 'lang1-deep1-deep2-msg1' - } - }, - plural: 'lang1-plural', - 'plural.1': 'lang1-plural1', - 'plural.2': 'lang1-plural2', - 'plural.n': 'lang1-pluralN' -}; - -const lang2: Locale = { - msg1: 'lang2-msg1', - msg2: 'lang2-msg2', - deep1: { - msg1: 'lang2-deep1-msg1', - msg2: 'lang2-deep1-msg2 ${key}', - deep2: { - msg1: 'lang2-deep1-deep2-msg1' - } - }, - plural: 'lang2-plural', - 'plural.1': 'lang2-plural1', - 'plural.2': 'lang2-plural2', - 'plural.n': 'lang2-pluralN' -}; - -describe(`Translator`, () => { - it(`Throws error when constructing with undefined translations or default`, () => { - expect(() => new Translator(undefined as any, 'lang1')).toThrow(); - // eslint-disable-next-line no-null/no-null - expect(() => new Translator(null as any, 'lang1')).toThrow(); - expect(() => new Translator({ lang1, lang2 }, undefined as any)).toThrow(); - // eslint-disable-next-line no-null/no-null - expect(() => new Translator({ lang1, lang2 }, null as any)).toThrow(); - }); - it(`Throws error when given an invalid locale as default when constructing`, () => { - expect(() => new Translator({ lang1, lang2 }, 'lang3', true, 'lang2')).toThrow(); - }); - - given('an unflatten translation', () => { - let unflatTranslator: Translator; - - beforeEach(() => { - unflatTranslator = new Translator({ lang1, lang2 }, 'lang1', false); - }); - describe('getAvailableTranslations', () => { - it(`Returns all available translations correctly`, () => { - expect(unflatTranslator.getAvailableTranslations()).toStrictEqual({ lang1, lang2 }); - }); - }); - describe('getDefaultLocale', () => { - it(`Returns default locale correctly`, () => { - expect(unflatTranslator.getDefaultLocale()).toBe('lang1'); - }); - }); - describe('getLocale', () => { - it(`Return current locale as default locale, unless otherwise specified`, () => { - expect(unflatTranslator.getLocale()).toBe('lang1'); - const translatorWithOtherLocale = new Translator({ lang1, lang2 }, 'lang1', false, 'lang2'); - expect(translatorWithOtherLocale.getLocale()).toBe('lang2'); - }); - }); - describe('hasLocale', () => { - it(`Answers if a locale is loaded correctly`, () => { - expect(unflatTranslator.hasLocale('lang1')).toBe(true); - expect(unflatTranslator.hasLocale('lang2')).toBe(true); - expect(unflatTranslator.hasLocale('lang3')).toBe(false); - }); - }); - describe('setLocale', () => { - it(`Sets a locale as current correctly`, () => { - unflatTranslator.setLocale('lang2'); - expect(unflatTranslator.getLocale()).toBe('lang2'); - unflatTranslator.setLocale('lang1'); - expect(unflatTranslator.getLocale()).toBe('lang1'); - }); - it(`Throws error when attempting to set an invalid locale`, () => { - expect(() => unflatTranslator.setLocale('lang3')).toThrow(); - }); - }); - - describe('translate', () => { - it(`Retrieves the key when a translation is not found`, () => { - expect(unflatTranslator.translate('nonexistent')).toBe('nonexistent'); - }); - it(`Translates only top level`, () => { - expect(unflatTranslator.translate('msg1')).toBe('lang1-msg1'); - expect(unflatTranslator.translate('msg2')).toBe('lang1-msg2'); - expect(unflatTranslator.translate('deep1.msg1')).toBe('deep1.msg1'); - }); - }); - }); - - given('a flatten translation', () => { - let flatTranslator: Translator; - - beforeEach(() => { - flatTranslator = new Translator({ lang1, lang2 }, 'lang1', true); - }); - describe('getAvailableTranslations', () => { - it(`Returns all available translations correctly`, () => { - expect(flatTranslator.getAvailableTranslations()).toStrictEqual({ lang1, lang2 }); - }); - }); - describe('getDefaultLocale', () => { - it(`Returns default locale correctly`, () => { - expect(flatTranslator.getDefaultLocale()).toBe('lang1'); - }); - }); - describe('getLocale', () => { - it(`Return current locale as default locale, unless otherwise specified`, () => { - expect(flatTranslator.getLocale()).toBe('lang1'); - const translatorWithOtherLocale = new Translator({ lang1, lang2 }, 'lang1', true, 'lang2'); - expect(translatorWithOtherLocale.getLocale()).toBe('lang2'); - }); - }); - describe('hasLocale', () => { - it(`Answers if a locale is loaded correctly`, () => { - expect(flatTranslator.hasLocale('lang1')).toBe(true); - expect(flatTranslator.hasLocale('lang2')).toBe(true); - expect(flatTranslator.hasLocale('lang3')).toBe(false); - }); - }); - describe('setLocale', () => { - it(`Sets a locale as current correctly`, () => { - flatTranslator.setLocale('lang2'); - expect(flatTranslator.getLocale()).toBe('lang2'); - flatTranslator.setLocale('lang1'); - expect(flatTranslator.getLocale()).toBe('lang1'); - }); - it(`Throws error when attempting to set an invalid locale`, () => { - expect(() => flatTranslator.setLocale('lang3')).toThrow(); - }); - }); - - describe('translate', () => { - it(`Translates correctly with the currently used language for top level`, () => { - expect(flatTranslator.translate('msg1')).toBe('lang1-msg1'); - expect(flatTranslator.translate('msg2')).toBe('lang1-msg2'); - flatTranslator.setLocale('lang2'); - expect(flatTranslator.translate('msg1')).toBe('lang2-msg1'); - expect(flatTranslator.translate('msg2')).toBe('lang2-msg2'); - }); - it(`Translates correctly with currently used language for top deep elements`, () => { - expect(flatTranslator.translate('deep1.msg1')).toBe('lang1-deep1-msg1'); - expect(flatTranslator.translate('deep1.msg2')).toBe('lang1-deep1-msg2 ${key}'); - expect(flatTranslator.translate('deep1.deep2.msg1')).toBe('lang1-deep1-deep2-msg1'); - flatTranslator.setLocale('lang2'); - expect(flatTranslator.translate('deep1.msg1')).toBe('lang2-deep1-msg1'); - expect(flatTranslator.translate('deep1.msg2')).toBe('lang2-deep1-msg2 ${key}'); - expect(flatTranslator.translate('deep1.deep2.msg1')).toBe('lang2-deep1-deep2-msg1'); - }); - it(`Translates using the given interpolations`, () => { - expect(flatTranslator.translate('deep1.msg1', { key: 'test' })).toBe('lang1-deep1-msg1'); - expect(flatTranslator.translate('deep1.msg2', { key: 'test' })).toBe('lang1-deep1-msg2 test'); - expect(flatTranslator.translate('deep1.msg2', { nonexistent: 'test' })).toBe('lang1-deep1-msg2 ${key}'); - }); - it(`Retrieves the key when a translation is not found`, () => { - expect(flatTranslator.translate('nonexistent')).toBe('nonexistent'); - expect(flatTranslator.translate('deep1.nonexistent')).toBe('deep1.nonexistent'); - }); - }); - }); - given('A translator for pluralizations', () => { - let unflatTranslator: Translator; - - beforeEach(() => { - unflatTranslator = new Translator({ lang1, lang2 }, 'lang1', false); - }); - describe('pluralize', () => { - it(`Should pluralized based on key and number`, () => { - expect(unflatTranslator.pluralize(1, 'plural')).toBe('lang1-plural1'); - expect(unflatTranslator.pluralize(2, 'plural')).toBe('lang1-plural2'); - unflatTranslator.setLocale('lang2'); - expect(unflatTranslator.pluralize(1, 'plural')).toBe('lang2-plural1'); - expect(unflatTranslator.pluralize(2, 'plural')).toBe('lang2-plural2'); - }); - - it(`Return the value in N, if any other amount not specified given`, () => { - expect(unflatTranslator.pluralize(3, 'plural')).toBe('lang1-pluralN'); - }); - - it(`Return the key for non pluralizable keys`, () => { - expect(unflatTranslator.pluralize(3, 'stuff')).toBe('stuff'); - }); - - it(`Return the non plural value if no plural is given`, () => { - expect(unflatTranslator.translate('plural')).toBe('lang1-plural'); - }); - it(`Cannot pluralize if number is not integer`, () => { - expect(() => unflatTranslator.pluralize(1.5, 'plural')).toThrow(); - }); - }); - }); -}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..31d2c57 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "ts-node": { + "compilerOptions": { + "module": "commonjs" + } + }, + "compilerOptions": { + "target": "es2015", + "rootDir": "./src", + "esModuleInterop": true, + "allowJs": true, + "sourceMap": true, + "declaration": true, + "declarationMap": true, + "declarationDir": "./dist/typings", + "moduleResolution": "node", + "resolveJsonModule": true, + "stripInternal": true, + "composite": false, + "skipLibCheck": true, + "jsx": "react", + "module": "ESNext" + }, + "include": ["./src/**/*"] +}