diff --git a/.eslintrc.json b/.eslintrc.json index 0e59a38d..e095356b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,6 +12,7 @@ "node": true, "es6": true }, + "parser": "@typescript-eslint/parser", "parserOptions": { "sourceType": "script", "ecmaVersion": 2020 diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 5ec16ee1..2c1d20d9 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -17,7 +17,7 @@ jobs: - name: Setup node uses: actions/setup-node@v2 with: - node-version: '16' + node-version: '18' - name: Install dependencies run: npm ci diff --git a/.github/workflows/enforce-format.yml b/.github/workflows/enforce-format.yml index 733533da..71ecec4e 100644 --- a/.github/workflows/enforce-format.yml +++ b/.github/workflows/enforce-format.yml @@ -17,7 +17,7 @@ jobs: - name: Setup node uses: actions/setup-node@v2 with: - node-version: '16' + node-version: '18' - name: Install dependencies run: npm ci diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c38f6d03..e863432e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,7 +14,7 @@ jobs: - name: Setup node uses: actions/setup-node@v2 with: - node-version: '16' + node-version: '18' registry-url: 'https://registry.npmjs.org' - name: Install dependencies diff --git a/.github/workflows/update-docs.yml b/.github/workflows/update-docs.yml index 21abf8d5..a3dca9a0 100644 --- a/.github/workflows/update-docs.yml +++ b/.github/workflows/update-docs.yml @@ -16,7 +16,7 @@ jobs: - name: Setup node uses: actions/setup-node@v2 with: - node-version: '16' + node-version: '18' - name: Install dependencies run: npm ci diff --git a/package-lock.json b/package-lock.json index 283df5bf..8794ebdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,18 +37,18 @@ "@types/jsdom": "^16.2.13", "@types/node": "^13.1.8", "@types/parse5": "^6.0.2", - "@typescript-eslint/eslint-plugin": "^6.19.0", - "@typescript-eslint/parser": "^6.19.0", + "@typescript-eslint/eslint-plugin": "^8.6.0", + "@typescript-eslint/parser": "^8.6.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "mocha": "^5.2.0", "prettier": "^3.2.3", "safe-publish-latest": "^1.1.4", - "typescript": "^5.1.6" + "typescript": "^5.5.4" }, "engines": { - "node": ">= 12 || ^11.10.1 || ^10.13 || ^8.10" + "node": ">= 18" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -232,10 +232,11 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", - "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -294,21 +295,24 @@ } }, "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" }, @@ -321,6 +325,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -342,10 +347,12 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", @@ -428,12 +435,6 @@ "@types/tough-cookie": "*" } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, "node_modules/@types/node": { "version": "13.13.52", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", @@ -446,12 +447,6 @@ "integrity": "sha512-+hQX+WyJAOne7Fh3zF5CxPemILIbuhNcqHHodzK9caYOLnC8pD5efmPleRnw0z++LfKUC/sVNMwk0Gap+B0baA==", "dev": true }, - "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", - "dev": true - }, "node_modules/@types/tough-cookie": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", @@ -459,33 +454,32 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.0.tgz", - "integrity": "sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz", + "integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.19.0", - "@typescript-eslint/type-utils": "6.19.0", - "@typescript-eslint/utils": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/type-utils": "8.6.0", + "@typescript-eslint/utils": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -494,26 +488,27 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.0.tgz", - "integrity": "sha512-1DyBLG5SH7PYCd00QlroiW60YJ4rWMuUGa/JBV0iZuqi4l4IK3twKPq5ZkEebmGqRjXWVgsUzfd3+nZveewgow==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz", + "integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "6.19.0", - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/typescript-estree": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0", + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -522,16 +517,17 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.0.tgz", - "integrity": "sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz", + "integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0" + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -539,26 +535,24 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.0.tgz", - "integrity": "sha512-mcvS6WSWbjiSxKCwBcXtOM5pRkPQ6kcDds/juxcy/727IQr3xMEcwr/YLHW2A2+Fp5ql6khjbKBzOyjuPqGi/w==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz", + "integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "6.19.0", - "@typescript-eslint/utils": "6.19.0", + "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/utils": "8.6.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, "peerDependenciesMeta": { "typescript": { "optional": true @@ -566,12 +560,13 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.0.tgz", - "integrity": "sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz", + "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==", "dev": true, + "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -579,22 +574,23 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.0.tgz", - "integrity": "sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz", + "integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -611,15 +607,17 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -631,41 +629,40 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.0.tgz", - "integrity": "sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz", + "integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.19.0", - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/typescript-estree": "6.19.0", - "semver": "^7.5.4" + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/typescript-estree": "8.6.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz", - "integrity": "sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz", + "integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.19.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "8.6.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -802,15 +799,6 @@ "node": ">=6" } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1180,18 +1168,6 @@ "node": ">=0.3.1" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -1314,16 +1290,17 @@ } }, "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -1644,9 +1621,10 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -1834,26 +1812,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", @@ -1996,10 +1954,11 @@ } }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -2289,18 +2248,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2653,15 +2600,6 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -2963,13 +2901,11 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -3004,15 +2940,6 @@ "node": ">=8" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3207,12 +3134,13 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", - "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" @@ -3248,10 +3176,11 @@ } }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3486,12 +3415,6 @@ "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", "dev": true }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/yargs": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", @@ -3724,9 +3647,9 @@ } }, "@eslint-community/regexpp": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", - "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", "dev": true }, "@eslint/eslintrc": { @@ -3773,18 +3696,18 @@ } }, "@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true }, "@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" }, @@ -3807,9 +3730,9 @@ "dev": true }, "@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, "@nodelib/fs.scandir": { @@ -3875,12 +3798,6 @@ "@types/tough-cookie": "*" } }, - "@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, "@types/node": { "version": "13.13.52", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", @@ -3893,12 +3810,6 @@ "integrity": "sha512-+hQX+WyJAOne7Fh3zF5CxPemILIbuhNcqHHodzK9caYOLnC8pD5efmPleRnw0z++LfKUC/sVNMwk0Gap+B0baA==", "dev": true }, - "@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", - "dev": true - }, "@types/tough-cookie": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", @@ -3906,79 +3817,77 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.0.tgz", - "integrity": "sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz", + "integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==", "dev": true, "requires": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.19.0", - "@typescript-eslint/type-utils": "6.19.0", - "@typescript-eslint/utils": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/type-utils": "8.6.0", + "@typescript-eslint/utils": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" } }, "@typescript-eslint/parser": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.0.tgz", - "integrity": "sha512-1DyBLG5SH7PYCd00QlroiW60YJ4rWMuUGa/JBV0iZuqi4l4IK3twKPq5ZkEebmGqRjXWVgsUzfd3+nZveewgow==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz", + "integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "6.19.0", - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/typescript-estree": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0", + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.0.tgz", - "integrity": "sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz", + "integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==", "dev": true, "requires": { - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0" + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0" } }, "@typescript-eslint/type-utils": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.0.tgz", - "integrity": "sha512-mcvS6WSWbjiSxKCwBcXtOM5pRkPQ6kcDds/juxcy/727IQr3xMEcwr/YLHW2A2+Fp5ql6khjbKBzOyjuPqGi/w==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz", + "integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "6.19.0", - "@typescript-eslint/utils": "6.19.0", + "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/utils": "8.6.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" } }, "@typescript-eslint/types": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.0.tgz", - "integrity": "sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz", + "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.0.tgz", - "integrity": "sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz", + "integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==", "dev": true, "requires": { - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "dependencies": { "brace-expansion": { @@ -3991,9 +3900,9 @@ } }, "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -4002,28 +3911,25 @@ } }, "@typescript-eslint/utils": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.0.tgz", - "integrity": "sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz", + "integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.19.0", - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/typescript-estree": "6.19.0", - "semver": "^7.5.4" + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/typescript-estree": "8.6.0" } }, "@typescript-eslint/visitor-keys": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz", - "integrity": "sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz", + "integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==", "dev": true, "requires": { - "@typescript-eslint/types": "6.19.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "8.6.0", + "eslint-visitor-keys": "^3.4.3" } }, "@ungap/structured-clone": { @@ -4125,12 +4031,6 @@ "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==" }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4423,15 +4323,6 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -4526,16 +4417,16 @@ } }, "eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -4750,9 +4641,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -4901,20 +4792,6 @@ "type-fest": "^0.20.2" } }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, "graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", @@ -5021,9 +4898,9 @@ } }, "ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true }, "import-fresh": { @@ -5245,15 +5122,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5532,12 +5400,6 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -5736,13 +5598,10 @@ } }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true }, "set-blocking": { "version": "2.0.0", @@ -5765,12 +5624,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5924,9 +5777,9 @@ } }, "ts-api-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", - "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, "requires": {} }, @@ -5951,9 +5804,9 @@ "dev": true }, "typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true }, "typical": { @@ -6124,12 +5977,6 @@ "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", "dev": true }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "yargs": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", diff --git a/package.json b/package.json index bad95510..b0400eb6 100644 --- a/package.json +++ b/package.json @@ -72,15 +72,15 @@ "@types/jsdom": "^16.2.13", "@types/node": "^13.1.8", "@types/parse5": "^6.0.2", - "@typescript-eslint/eslint-plugin": "^6.19.0", - "@typescript-eslint/parser": "^6.19.0", + "@typescript-eslint/eslint-plugin": "^8.6.0", + "@typescript-eslint/parser": "^8.6.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "mocha": "^5.2.0", "prettier": "^3.2.3", "safe-publish-latest": "^1.1.4", - "typescript": "^5.1.6" + "typescript": "^5.5.4" }, "prettier": { "singleQuote": true, @@ -88,6 +88,6 @@ "printWidth": 100 }, "engines": { - "node": ">= 12 || ^11.10.1 || ^10.13 || ^8.10" + "node": ">= 18" } } diff --git a/src/Spec.ts b/src/Spec.ts index f9d79406..1183114c 100644 --- a/src/Spec.ts +++ b/src/Spec.ts @@ -526,12 +526,13 @@ export default class Spec { this.setReplacementAlgorithmOffsets(); - this.autolink(); - if (this.opts.lintSpec) { this.log('Checking types...'); typecheck(this); } + + this.autolink(); + this.log('Propagating effect annotations...'); this.propagateEffects(); this.log('Annotating external links...'); @@ -1887,7 +1888,7 @@ function getBoilerplate(file: string) { if (fs.lstatSync(file).isFile()) { boilerplateFile = file; } - } catch (error) { + } catch { boilerplateFile = path.join(__dirname, '../boilerplate', `${file}.html`); } diff --git a/src/cli.ts b/src/cli.ts index 6516c880..e33f3506 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -136,7 +136,7 @@ const build = debounce(async function build() { } else { opts.extraBiblios!.push(bib); } - } catch (e) { + } catch { fail(`could not find biblio ${toResolve}`); } } @@ -149,7 +149,7 @@ const build = debounce(async function build() { let formatter; try { formatter = require(toResolve); - } catch (e) { + } catch { fail(`could not find formatter ${errorFormatter}`); } const warnings: EcmarkupError[] = []; diff --git a/src/formatter/ecmarkup.ts b/src/formatter/ecmarkup.ts index 6514420a..298f1e00 100644 --- a/src/formatter/ecmarkup.ts +++ b/src/formatter/ecmarkup.ts @@ -187,7 +187,7 @@ export async function printElement( let parsed; try { parsed = parseAlgorithm(contents); - } catch (e) { + } catch { // TODO error location information, ugh bad(node, 'failed to parse algorithm'); } @@ -202,7 +202,7 @@ export async function printElement( let parsed; try { parsed = parseFragment(contents); - } catch (e) { + } catch { // TODO error location information, ugh bad(node, 'failed to parse emu-eqn'); } @@ -404,7 +404,7 @@ async function printChildNodes( let parsed; try { parsed = parseFragment(value); - } catch (e) { + } catch { parsed = null; } if (parsed) { diff --git a/src/type-logic.ts b/src/type-logic.ts new file mode 100644 index 00000000..15f1fecd --- /dev/null +++ b/src/type-logic.ts @@ -0,0 +1,576 @@ +import type Biblio from './Biblio'; +import type { Type as BiblioType } from './Biblio'; +import type { Expr, NonSeq } from './expr-parser'; + +type Type = + | { kind: 'unknown' } // top + | { kind: 'never' } // bottom + | { kind: 'union'; of: NonUnion[] } // constraint: nothing in the union dominates anything else in the union + | { kind: 'list'; of: Type } + | { kind: 'record' } // TODO more precision + | { kind: 'normal completion'; of: Type } + | { kind: 'abrupt completion' } + | { kind: 'real' } + | { kind: 'integer' } + | { kind: 'non-negative integer' } + | { kind: 'negative integer' } + | { kind: 'positive integer' } + | { kind: 'concrete real'; value: string } + | { kind: 'ES value' } + | { kind: 'string' } + | { kind: 'number' } + | { kind: 'integral number' } + | { kind: 'bigint' } + | { kind: 'boolean' } + | { kind: 'null' } + | { kind: 'undefined' } + | { kind: 'concrete string'; value: string } + | { kind: 'concrete number'; value: number } + | { kind: 'concrete bigint'; value: bigint } + | { kind: 'concrete boolean'; value: boolean } + | { kind: 'enum value'; value: string }; +type NonUnion = Exclude; +const simpleKinds = new Set([ + 'unknown', + 'never', + 'record', + 'abrupt completion', + 'real', + 'integer', + 'non-negative integer', + 'negative integer', + 'positive integer', + 'ES value', + 'string', + 'number', + 'integral number', + 'bigint', + 'boolean', + 'null', + 'undefined', +]); +const dominateGraph: Partial> = { + // @ts-expect-error TS does not know about __proto__ + __proto__: null, + record: ['normal completion', 'abrupt completion'], + real: [ + 'integer', + 'non-negative integer', + 'negative integer', + 'positive integer', + 'concrete real', + ], + integer: ['non-negative integer', 'negative integer', 'positive integer'], + 'non-negative integer': ['positive integer'], + 'ES value': [ + 'string', + 'number', + 'integral number', + 'bigint', + 'boolean', + 'null', + 'undefined', + 'concrete string', + 'concrete number', + 'concrete bigint', + 'concrete boolean', + ], + string: ['concrete string'], + number: ['integral number', 'concrete number'], + bigint: ['concrete bigint'], + boolean: ['concrete boolean'], +}; +/* +The type lattice used here is very simple (aside from explicit unions). +As such we mostly only need to define the `dominates` relationship and apply trivial rules: +if `x` dominates `y`, then `join(x,y) = x` and `meet(x,y) = y`; if neither dominates the other than the join is top and the meet is bottom. +Unions/lists/completions take a little more work. +*/ +export function dominates(a: Type, b: Type): boolean { + if (a.kind === 'unknown' || b.kind === 'never') { + return true; + } + if (b.kind === 'union') { + return b.of.every(t => dominates(a, t)); + } + if (a.kind === 'union') { + // not necessarily true for arbitrary lattices, but true for ours + return a.of.some(t => dominates(t, b)); + } + if ( + (a.kind === 'list' && b.kind === 'list') || + (a.kind === 'normal completion' && b.kind === 'normal completion') + ) { + return dominates(a.of, b.of); + } + if (simpleKinds.has(a.kind) && simpleKinds.has(b.kind) && a.kind === b.kind) { + return true; + } + if (dominateGraph[a.kind]?.includes(b.kind) ?? false) { + return true; + } + if (a.kind === 'integer' && b.kind === 'concrete real') { + return !b.value.includes('.'); + } + if (a.kind === 'integral number' && b.kind === 'concrete number') { + return Number.isFinite(b.value) && b.value === Math.round(b.value); + } + if (a.kind === 'non-negative integer' && b.kind === 'concrete real') { + return !b.value.includes('.') && b.value[0] !== '-'; + } + if (a.kind === 'negative integer' && b.kind === 'concrete real') { + return !b.value.includes('.') && b.value[0] === '-'; + } + if (a.kind === 'positive integer' && b.kind === 'concrete real') { + return !b.value.includes('.') && b.value[0] !== '-' && b.value !== '0'; + } + if ( + a.kind === b.kind && + [ + 'concrete string', + 'concrete number', + 'concrete real', + 'concrete bigint', + 'concrete boolean', + 'enum value', + ].includes(a.kind) + ) { + // @ts-expect-error TS is not quite smart enough for this + return Object.is(a.value, b.value); + } + return false; +} +function addToUnion(types: NonUnion[], type: NonUnion): Type { + if (type.kind === 'normal completion') { + const existingNormalCompletionIndex = types.findIndex(t => t.kind === 'normal completion'); + if (existingNormalCompletionIndex !== -1) { + // prettier-ignore + const joined = join(types[existingNormalCompletionIndex], type) as Type & { kind: 'normal completion' }; + if (types.length === 1) { + return joined; + } + const typesCopy = [...types]; + typesCopy.splice(existingNormalCompletionIndex, 1, joined); + return { kind: 'union', of: typesCopy }; + } + } + + if (types.some(t => dominates(t, type))) { + return { kind: 'union', of: types }; + } + const live = types.filter(t => !dominates(type, t)); + if (live.length === 0) { + return type; + } + return { kind: 'union', of: [...live, type] }; +} + +export function join(a: Type, b: Type): Type { + if (dominates(a, b)) { + return a; + } + if (dominates(b, a)) { + return b; + } + if (b.kind === 'union') { + [a, b] = [b, a]; + } + if (a.kind === 'union') { + if (b.kind === 'union') { + return b.of.reduce( + (acc: Type, t) => (acc.kind === 'union' ? addToUnion(acc.of, t) : join(acc, t)), + a, + ); + } + return addToUnion(a.of, b); + } + if ( + (a.kind === 'list' && b.kind === 'list') || + (a.kind === 'normal completion' && b.kind === 'normal completion') + ) { + return { kind: a.kind, of: join(a.of, b.of) }; + } + return { kind: 'union', of: [a, b as NonUnion] }; +} + +export function meet(a: Type, b: Type): Type { + if (dominates(a, b)) { + return b; + } + if (dominates(b, a)) { + return a; + } + if (a.kind !== 'union' && b.kind === 'union') { + [a, b] = [b, a]; + } + if (a.kind === 'union') { + // union is join. meet distributes over join. + return a.of.map(t => meet(t, b)).reduce(join); + } + if ( + (a.kind === 'list' && b.kind === 'list') || + (a.kind === 'normal completion' && b.kind === 'normal completion') + ) { + return { kind: a.kind, of: meet(a.of, b.of) }; + } + return { kind: 'never' }; +} + +export function serialize(type: Type): string { + switch (type.kind) { + case 'unknown': { + return 'unknown'; + } + case 'never': { + return 'never'; + } + case 'union': { + const parts = type.of.map(serialize); + if (parts.length > 2) { + return parts.slice(0, -1).join(', ') + ', or ' + parts[parts.length - 1]; + } + return parts[0] + ' or ' + parts[1]; + } + case 'list': { + if (type.of.kind === 'never') { + return 'empty List'; + } + return 'List of ' + serialize(type.of); + } + case 'record': { + return 'Record'; + } + case 'normal completion': { + if (type.of.kind === 'never') { + return 'never'; + } else if (type.of.kind === 'unknown') { + return 'a normal completion'; + } + return 'a normal completion containing ' + serialize(type.of); + } + case 'abrupt completion': { + return 'an abrupt completion'; + } + case 'real': { + return 'mathematical value'; + } + case 'integer': + case 'non-negative integer': + case 'negative integer': + case 'positive integer': + case 'null': + case 'undefined': { + return type.kind; + } + case 'concrete string': { + return `"${type.value}"`; + } + case 'concrete real': { + return type.value; + } + case 'concrete boolean': { + return `${type.value}`; + } + case 'enum value': { + return `~${type.value}~`; + } + case 'concrete number': { + if (Object.is(type.value, 0 / 0)) { + return '*NaN*'; + } + let repr; + if (Object.is(type.value, -0)) { + repr = '-0'; + } else if (type.value === 0) { + repr = '+0'; + } else if (type.value === 2e308) { + repr = '+∞'; + } else if (type.value === -2e308) { + repr = '-∞'; + } else if (type.value > 4503599627370495.5) { + repr = String(BigInt(type.value)); + } else { + repr = String(type.value); + } + return `*${repr}*𝔽`; + } + case 'concrete bigint': { + return `*${type.value}*`; + } + case 'ES value': { + return 'ECMAScript language value'; + } + case 'boolean': { + return 'Boolean'; + } + case 'string': { + return 'String'; + } + case 'number': { + return 'Number'; + } + case 'integral number': { + return 'integral Number'; + } + case 'bigint': { + return 'BigInt'; + } + } +} + +export function typeFromExpr(expr: Expr, biblio: Biblio): Type { + seq: if (expr.name === 'seq') { + const items = stripWhitespace(expr.items); + if (items.length === 1) { + expr = items[0]; + break seq; + } + + if ( + items.length === 2 && + items[0].name === 'star' && + items[0].contents[0].name === 'text' && + items[1].name === 'text' + ) { + switch (items[1].contents) { + case '𝔽': { + const text = items[0].contents[0].contents; + let value; + if (text === '-0') { + value = -0; + } else if (text === '+∞') { + value = 2e308; + } else if (text === '-∞') { + value = -2e308; + } else { + value = parseFloat(text); + } + return { kind: 'concrete number', value }; + } + case 'ℤ': { + return { kind: 'concrete bigint', value: BigInt(items[0].contents[0].contents) }; + } + } + } + + if (items[0]?.name === 'text' && ['!', '?'].includes(items[0].contents.trim())) { + const remaining = stripWhitespace(items.slice(1)); + if (remaining.length === 1 && ['call', 'sdo-call'].includes(remaining[0].name)) { + const callType = typeFromExpr(remaining[0], biblio); + if (isCompletion(callType)) { + const normal: Type = + callType.kind === 'normal completion' + ? callType.of + : callType.kind === 'abrupt completion' + ? // the expression `? _abrupt_` strictly speaking has type `never` + // however we mostly use `never` to mean user error, so use unknown instead + // this should only ever happen after `Return` + { kind: 'unknown' } + : callType.of.find(k => k.kind === 'normal completion')?.of ?? { kind: 'unknown' }; + return normal; + } + } + } + } + + switch (expr.name) { + case 'text': { + const text = expr.contents.trim(); + if (/^-?[0-9]+(\.[0-9]+)?$/.test(text)) { + return { kind: 'concrete real', value: text }; + } + break; + } + case 'list': { + return { + kind: 'list', + of: expr.elements.map(t => typeFromExpr(t, biblio)).reduce(join, { kind: 'never' }), + }; + } + case 'record': { + return { kind: 'record' }; + } + case 'call': + case 'sdo-call': { + const { callee } = expr; + if (!(callee.length === 1 && callee[0].name === 'text')) { + break; + } + const calleeName = callee[0].contents; + + const biblioEntry = biblio.byAoid(calleeName); + if (biblioEntry?.signature?.return == null) { + break; + } + return typeFromExprType(biblioEntry.signature.return); + } + case 'tilde': { + if (expr.contents.length === 1 && expr.contents[0].name === 'text') { + return { kind: 'enum value', value: expr.contents[0].contents }; + } + break; + } + case 'star': { + if (expr.contents.length === 1 && expr.contents[0].name === 'text') { + const text = expr.contents[0].contents; + if (text === 'null') { + return { kind: 'null' }; + } else if (text === 'undefined') { + return { kind: 'undefined' }; + } else if (text === 'NaN') { + return { kind: 'concrete number', value: 0 / 0 }; + } else if (text === 'true') { + return { kind: 'concrete boolean', value: true }; + } else if (text === 'false') { + return { kind: 'concrete boolean', value: false }; + } else if (text.startsWith('"') && text.endsWith('"')) { + return { kind: 'concrete string', value: text.slice(1, -1) }; + } + } + + break; + } + } + return { kind: 'unknown' }; +} +export function typeFromExprType(type: BiblioType): Type { + switch (type.kind) { + case 'union': { + return type.types.map(typeFromExprType).reduce(join); + } + case 'list': { + return { + kind: 'list', + of: type.elements == null ? { kind: 'unknown' } : typeFromExprType(type.elements), + }; + } + case 'completion': { + if (type.completionType === 'abrupt') { + return { kind: 'abrupt completion' }; + } else { + const normalType: Type = + type.typeOfValueIfNormal == null + ? { kind: 'unknown' } + : typeFromExprType(type.typeOfValueIfNormal); + if (type.completionType === 'normal') { + return { kind: 'normal completion', of: normalType }; + } else if (type.completionType === 'mixed') { + return { + kind: 'union', + of: [{ kind: 'normal completion', of: normalType }, { kind: 'abrupt completion' }], + }; + } else { + throw new Error('unreachable: completion kind ' + type.completionType); + } + } + } + case 'opaque': { + const text = type.type; + if (text.startsWith('"') && text.endsWith('"')) { + return { kind: 'concrete string', value: text.slice(1, -1) }; + } + if (text.startsWith('~') && text.endsWith('~')) { + return { kind: 'enum value', value: text.slice(1, -1) }; + } + if (/^-?[0-9]+(\.[0-9]+)?$/.test(text)) { + return { kind: 'concrete real', value: text }; + } + if (text.startsWith('*') && text.endsWith('*𝔽')) { + const innerText = text.slice(1, -14); + let value; + if (innerText === '-0') { + value = -0; + } else if (innerText === '+∞') { + value = 2e308; + } else if (innerText === '-∞') { + value = -2e308; + } else { + value = parseFloat(innerText); + } + return { kind: 'concrete number', value }; + } + if (text === '*NaN*') { + return { kind: 'concrete number', value: 0 / 0 }; + } + if (text.startsWith('*') && text.endsWith('*')) { + return { kind: 'concrete bigint', value: BigInt(text.slice(1, -14)) }; + } + if (text === 'an ECMAScript language value' || text === 'ECMAScript language values') { + return { kind: 'ES value' }; + } + if (text === 'a String' || text === 'Strings') { + return { kind: 'string' }; + } + if (text === 'a Number' || text === 'Numbers') { + return { kind: 'number' }; + } + if (text === 'a Boolean' || text === 'Booleans') { + return { kind: 'boolean' }; + } + if (text === 'a BigInt' || text === 'BigInts') { + return { kind: 'bigint' }; + } + if (text === 'an integral Number' || text === 'integral Numbers') { + return { kind: 'integral number' }; + } + if (text === 'a mathematical value' || text === 'mathematical values') { + return { kind: 'real' }; + } + if (text === 'an integer' || text === 'integers') { + return { kind: 'integer' }; + } + if (text === 'a non-negative integer' || text === 'non-negative integers') { + return { kind: 'non-negative integer' }; + } + if (text === 'a negative integer' || text === 'negative integers') { + return { kind: 'negative integer' }; + } + if (text === 'a positive integer' || text === 'positive integers') { + return { kind: 'positive integer' }; + } + if (text === 'a time value' || text === 'time values') { + return { + kind: 'union', + of: [{ kind: 'integral number' }, { kind: 'concrete number', value: 0 / 0 }], + }; + } + if (text === '*null*') { + return { kind: 'null' }; + } + if (text === '*undefined*') { + return { kind: 'undefined' }; + } + break; + } + case 'unused': { + // this is really only a return type, but might as well handle it + return { kind: 'enum value', value: '~unused~' }; + } + } + return { kind: 'unknown' }; +} + +export function isCompletion( + type: Type, +): type is Type & { kind: 'normal completion' | 'abrupt completion' | 'union' } { + return ( + type.kind === 'normal completion' || + type.kind === 'abrupt completion' || + (type.kind === 'union' && type.of.some(isCompletion)) + ); +} + +export function stripWhitespace(items: NonSeq[]) { + items = [...items]; + while (items[0]?.name === 'text' && /^\s+$/.test(items[0].contents)) { + items.shift(); + } + while ( + items[items.length - 1]?.name === 'text' && + // @ts-expect-error + /^\s+$/.test(items[items.length - 1].contents) + ) { + items.pop(); + } + return items; +} diff --git a/src/type-parser.ts b/src/type-parser.ts index 0408110d..476afc57 100644 --- a/src/type-parser.ts +++ b/src/type-parser.ts @@ -283,7 +283,7 @@ function join(a: Type | null, b: Type | null): Type | null { function squashUnionTypes(unionTypes: Type[]): Type { const out = unionTypes.flatMap(t => (t.kind === 'union' ? t.types : [t])); if (out.every(t => t.kind === 'completion')) { - return (out as Extract[]).reduce((a, b) => { + return out.reduce((a, b) => { if (a.completionType !== 'abrupt' && b.completionType !== 'abrupt') { return { kind: 'completion', diff --git a/src/typechecker.ts b/src/typechecker.ts index b1237145..c6b4ddb0 100644 --- a/src/typechecker.ts +++ b/src/typechecker.ts @@ -2,10 +2,182 @@ import type { OrderedListNode } from 'ecmarkdown'; import type { AlgorithmElementWithTree } from './Algorithm'; import type { AlgorithmBiblioEntry, Type as BiblioType } from './Biblio'; import type Spec from './Spec'; -import type { Expr, NonSeq, PathItem, Seq } from './expr-parser'; +import type { Expr, PathItem, Seq } from './expr-parser'; import { walk as walkExpr, parse as parseExpr, isProsePart } from './expr-parser'; import { offsetToLineAndColumn, zip } from './utils'; -import type Biblio from './Biblio'; +import { + typeFromExpr, + typeFromExprType, + meet, + serialize, + dominates, + stripWhitespace, +} from './type-logic'; + +type OnlyPerformedMap = Map; +type AlwaysAssertedToBeNormalMap = Map; + +const getExpressionVisitor = + ( + spec: Spec, + warn: (offset: number, message: string) => void, + onlyPerformed: OnlyPerformedMap, + alwaysAssertedToBeNormal: AlwaysAssertedToBeNormalMap, + ) => + (expr: Expr, path: PathItem[]) => { + if (expr.name !== 'call' && expr.name !== 'sdo-call') { + return; + } + + const { callee, arguments: args } = expr; + if (!(callee.length === 1 && callee[0].name === 'text')) { + return; + } + const calleeName = callee[0].contents; + + const biblioEntry = spec.biblio.byAoid(calleeName); + if (biblioEntry == null) { + if (!['toUppercase', 'toLowercase'].includes(calleeName)) { + // TODO make the spec not do this + warn(callee[0].location.start.offset, `could not find definition for ${calleeName}`); + } + return; + } + + if (biblioEntry.kind === 'syntax-directed operation' && expr.name === 'call') { + warn( + callee[0].location.start.offset, + `${calleeName} is a syntax-directed operation and should not be invoked like a regular call`, + ); + } else if ( + biblioEntry.kind != null && + biblioEntry.kind !== 'syntax-directed operation' && + expr.name === 'sdo-call' + ) { + warn( + callee[0].location.start.offset, + `${calleeName} is not a syntax-directed operation but here is being invoked as one`, + ); + } + + if (biblioEntry.signature == null) { + return; + } + const { signature } = biblioEntry; + const min = signature.parameters.length; + const max = min + signature.optionalParameters.length; + if (args.length < min || args.length > max) { + const count = `${min}${min === max ? '' : `-${max}`}`; + const message = `${calleeName} takes ${count} argument${count === '1' ? '' : 's'}, but this invocation passes ${args.length}`; + warn(callee[0].location.start.offset, message); + } else { + const params = signature.parameters.concat(signature.optionalParameters); + for (const [arg, param] of zip(args, params, true)) { + if (param.type == null) continue; + const argType = typeFromExpr(arg, spec.biblio); + const paramType = typeFromExprType(param.type); + + // often we can't infer the argument precisely, so we check only that the intersection is nonempty rather than that the argument type is a subtype of the parameter type + const intersection = meet(argType, paramType); + + if ( + intersection.kind === 'never' || + (intersection.kind === 'list' && + intersection.of.kind === 'never' && + // if the meet is list, and we're passing a concrete list, it had better be empty + argType.kind === 'list' && + argType.of.kind !== 'never') + ) { + const argDescriptor = + argType.kind.startsWith('concrete') || + argType.kind === 'enum value' || + argType.kind === 'null' || + argType.kind === 'undefined' + ? `(${serialize(argType)})` + : `type (${serialize(argType)})`; + + let hint = ''; + if (argType.kind === 'concrete number' && dominates({ kind: 'real' }, paramType)) { + hint = + '\nhint: you passed an ES language Number, but this position takes a mathematical value'; + } else if (argType.kind === 'concrete real' && dominates({ kind: 'number' }, paramType)) { + hint = + '\nhint: you passed a mathematical value, but this position takes an ES language Number'; + } else if (argType.kind === 'concrete real' && dominates({ kind: 'bigint' }, paramType)) { + hint = + '\nhint: you passed a mathematical value, but this position takes an ES language BigInt'; + } + + const items = stripWhitespace(arg.items); + warn( + items[0].location.start.offset, + `argument ${argDescriptor} does not look plausibly assignable to parameter type (${serialize(paramType)})${hint}`, + ); + } + } + } + + const { return: returnType } = signature; + if (returnType == null) { + return; + } + + const consumedAsCompletion = isConsumedAsCompletion(expr, path); + + // checks elsewhere ensure that well-formed documents never have a union of completion and non-completion, so checking the first child suffices + // TODO: this is for 'a break completion or a throw completion', which is kind of a silly union; maybe address that in some other way? + const isCompletion = + returnType.kind === 'completion' || + (returnType.kind === 'union' && returnType.types[0].kind === 'completion'); + if ( + ['Completion', 'ThrowCompletion', 'NormalCompletion', 'ReturnCompletion'].includes(calleeName) + ) { + if (consumedAsCompletion) { + warn( + callee[0].location.start.offset, + `${calleeName} clearly creates a Completion Record; it does not need to be marked as such, and it would not be useful to immediately unwrap its result`, + ); + } + } else if (isCompletion && !consumedAsCompletion) { + warn( + callee[0].location.start.offset, + `${calleeName} returns a Completion Record, but is not consumed as if it does`, + ); + } else if (!isCompletion && consumedAsCompletion) { + warn( + callee[0].location.start.offset, + `${calleeName} does not return a Completion Record, but is consumed as if it does`, + ); + } + if (returnType.kind === 'unused' && !isCalledAsPerform(expr, path, false)) { + warn( + callee[0].location.start.offset, + `${calleeName} does not return a meaningful value and should only be invoked as \`Perform ${calleeName}(...).\``, + ); + } + + if (onlyPerformed.has(calleeName) && onlyPerformed.get(calleeName) !== 'top') { + const old = onlyPerformed.get(calleeName); + const performed = isCalledAsPerform(expr, path, true); + if (!performed) { + onlyPerformed.set(calleeName, 'top'); + } else if (old === null) { + onlyPerformed.set(calleeName, 'only performed'); + } + } + if ( + alwaysAssertedToBeNormal.has(calleeName) && + alwaysAssertedToBeNormal.get(calleeName) !== 'top' + ) { + const old = alwaysAssertedToBeNormal.get(calleeName); + const asserted = isAssertedToBeNormal(expr, path); + if (!asserted) { + alwaysAssertedToBeNormal.set(calleeName, 'top'); + } else if (old === null) { + alwaysAssertedToBeNormal.set(calleeName, 'always asserted normal'); + } + } + }; export function typecheck(spec: Spec) { const isUnused = (t: BiblioType) => @@ -15,10 +187,10 @@ export function typecheck(spec: Spec) { const AOs = spec.biblio .localEntries() .filter(e => e.type === 'op' && e.signature?.return != null) as AlgorithmBiblioEntry[]; - const onlyPerformed: Map = new Map( + const onlyPerformed: OnlyPerformedMap = new Map( AOs.filter(e => !isUnused(e.signature!.return!)).map(a => [a.aoid, null]), ); - const alwaysAssertedToBeNormal: Map = new Map( + const alwaysAssertedToBeNormal: AlwaysAssertedToBeNormalMap = new Map( // prettier-ignore AOs .filter(e => e.signature!.return!.kind === 'completion' && !e.skipGlobalChecks) @@ -38,179 +210,23 @@ export function typecheck(spec: Spec) { continue; } const originalHtml = (node as AlgorithmElementWithTree).originalHtml; - - const expressionVisitor = (expr: Expr, path: PathItem[]) => { - if (expr.name !== 'call' && expr.name !== 'sdo-call') { - return; - } - - const { callee, arguments: args } = expr; - if (!(callee.length === 1 && callee[0].name === 'text')) { - return; - } - const calleeName = callee[0].contents; - - const warn = (message: string) => { - const { line, column } = offsetToLineAndColumn( - originalHtml, - callee[0].location.start.offset, - ); - spec.warn({ - type: 'contents', - ruleId: 'typecheck', - message, - node, - nodeRelativeLine: line, - nodeRelativeColumn: column, - }); - }; - - const biblioEntry = spec.biblio.byAoid(calleeName); - if (biblioEntry == null) { - if (!['toUppercase', 'toLowercase'].includes(calleeName)) { - // TODO make the spec not do this - warn(`could not find definition for ${calleeName}`); - } - return; - } - - if (biblioEntry.kind === 'syntax-directed operation' && expr.name === 'call') { - warn( - `${calleeName} is a syntax-directed operation and should not be invoked like a regular call`, - ); - } else if ( - biblioEntry.kind != null && - biblioEntry.kind !== 'syntax-directed operation' && - expr.name === 'sdo-call' - ) { - warn(`${calleeName} is not a syntax-directed operation but here is being invoked as one`); - } - - if (biblioEntry.signature == null) { - return; - } - const { signature } = biblioEntry; - const min = signature.parameters.length; - const max = min + signature.optionalParameters.length; - if (args.length < min || args.length > max) { - const count = `${min}${min === max ? '' : `-${max}`}`; - const message = `${calleeName} takes ${count} argument${count === '1' ? '' : 's'}, but this invocation passes ${args.length}`; - warn(message); - } else { - const params = signature.parameters.concat(signature.optionalParameters); - for (const [arg, param] of zip(args, params, true)) { - if (param.type == null) continue; - const argType = typeFromExpr(arg, spec.biblio); - const paramType = typeFromExprType(param.type); - - // often we can't infer the argument precisely, so we check only that the intersection is nonempty rather than that the argument type is a subtype of the parameter type - const intersection = meet(argType, paramType); - - if ( - intersection.kind === 'never' || - (intersection.kind === 'list' && - intersection.of.kind === 'never' && - // if the meet is list, and we're passing a concrete list, it had better be empty - argType.kind === 'list' && - argType.of.kind !== 'never') - ) { - const items = stripWhitespace(arg.items); - const { line, column } = offsetToLineAndColumn( - originalHtml, - items[0].location.start.offset, - ); - const argDescriptor = - argType.kind.startsWith('concrete') || - argType.kind === 'enum value' || - argType.kind === 'null' || - argType.kind === 'undefined' - ? `(${serialize(argType)})` - : `type (${serialize(argType)})`; - - let hint = ''; - if (argType.kind === 'concrete number' && dominates({ kind: 'real' }, paramType)) { - hint = - '\nhint: you passed an ES language Number, but this position takes a mathematical value'; - } else if ( - argType.kind === 'concrete real' && - dominates({ kind: 'number' }, paramType) - ) { - hint = - '\nhint: you passed a mathematical value, but this position takes an ES language Number'; - } else if ( - argType.kind === 'concrete real' && - dominates({ kind: 'bigint' }, paramType) - ) { - hint = - '\nhint: you passed a mathematical value, but this position takes an ES language BigInt'; - } - - spec.warn({ - type: 'contents', - ruleId: 'typecheck', - message: `argument ${argDescriptor} does not look plausibly assignable to parameter type (${serialize(paramType)})${hint}`, - node, - nodeRelativeLine: line, - nodeRelativeColumn: column, - }); - } - } - } - - const { return: returnType } = signature; - if (returnType == null) { - return; - } - - const consumedAsCompletion = isConsumedAsCompletion(expr, path); - - // checks elsewhere ensure that well-formed documents never have a union of completion and non-completion, so checking the first child suffices - // TODO: this is for 'a break completion or a throw completion', which is kind of a silly union; maybe address that in some other way? - const isCompletion = - returnType.kind === 'completion' || - (returnType.kind === 'union' && returnType.types[0].kind === 'completion'); - // prettier-ignore - if (['Completion', 'ThrowCompletion', 'NormalCompletion', 'ReturnCompletion'].includes(calleeName)) { - if (consumedAsCompletion) { - warn( - `${calleeName} clearly creates a Completion Record; it does not need to be marked as such, and it would not be useful to immediately unwrap its result`, - ); - } - } else if (isCompletion && !consumedAsCompletion) { - warn(`${calleeName} returns a Completion Record, but is not consumed as if it does`); - } else if (!isCompletion && consumedAsCompletion) { - warn(`${calleeName} does not return a Completion Record, but is consumed as if it does`); - } - if (returnType.kind === 'unused' && !isCalledAsPerform(expr, path, false)) { - warn( - `${calleeName} does not return a meaningful value and should only be invoked as \`Perform ${calleeName}(...).\``, - ); - } - - if (onlyPerformed.has(calleeName) && onlyPerformed.get(calleeName) !== 'top') { - const old = onlyPerformed.get(calleeName); - const performed = isCalledAsPerform(expr, path, true); - if (!performed) { - onlyPerformed.set(calleeName, 'top'); - } else if (old === null) { - onlyPerformed.set(calleeName, 'only performed'); - } - } - if ( - alwaysAssertedToBeNormal.has(calleeName) && - alwaysAssertedToBeNormal.get(calleeName) !== 'top' - ) { - const old = alwaysAssertedToBeNormal.get(calleeName); - const asserted = isAssertedToBeNormal(expr, path); - if (!asserted) { - alwaysAssertedToBeNormal.set(calleeName, 'top'); - } else if (old === null) { - alwaysAssertedToBeNormal.set(calleeName, 'always asserted normal'); - } - } + const warn = (offset: number, message: string) => { + const { line, column } = offsetToLineAndColumn(originalHtml, offset); + spec.warn({ + type: 'contents', + ruleId: 'typecheck', + message, + node, + nodeRelativeLine: line, + nodeRelativeColumn: column, + }); }; + const walkLines = (list: OrderedListNode) => { for (const line of list.contents) { + // we already parsed in collect-algorithm-diagnostics, but that was before generating the biblio + // the biblio affects the parse of calls, so we need to re-do it + // TODO do collect-algorithm-diagnostics after generating the biblio, somehow? const item = parseExpr(line.contents, opNames); if (item.name === 'failure') { const { line, column } = offsetToLineAndColumn(originalHtml, item.offset); @@ -223,7 +239,7 @@ export function typecheck(spec: Spec) { nodeRelativeColumn: column, }); } else { - walkExpr(expressionVisitor, item); + walkExpr(getExpressionVisitor(spec, warn, onlyPerformed, alwaysAssertedToBeNormal), item); } if (line.sublist?.name === 'ol') { walkLines(line.sublist); @@ -361,537 +377,3 @@ function textFromPreviousPart(seq: Seq, index: number): string | null { } return text; } - -function stripWhitespace(items: NonSeq[]) { - items = [...items]; - while (items[0]?.name === 'text' && /^\s+$/.test(items[0].contents)) { - items.shift(); - } - while ( - items[items.length - 1]?.name === 'text' && - // @ts-expect-error - /^\s+$/.test(items[items.length - 1].contents) - ) { - items.pop(); - } - return items; -} - -type Type = - | { kind: 'unknown' } // top - | { kind: 'never' } // bottom - | { kind: 'union'; of: NonUnion[] } // constraint: nothing in the union dominates anything else in the union - | { kind: 'list'; of: Type } - | { kind: 'record' } // TODO more precision - | { kind: 'completion'; of: Type } - | { kind: 'real' } - | { kind: 'integer' } - | { kind: 'non-negative integer' } - | { kind: 'negative integer' } - | { kind: 'positive integer' } - | { kind: 'concrete real'; value: string } - | { kind: 'ES value' } - | { kind: 'string' } - | { kind: 'number' } - | { kind: 'integral number' } - | { kind: 'bigint' } - | { kind: 'boolean' } - | { kind: 'null' } - | { kind: 'undefined' } - | { kind: 'concrete string'; value: string } - | { kind: 'concrete number'; value: number } - | { kind: 'concrete bigint'; value: bigint } - | { kind: 'concrete boolean'; value: boolean } - | { kind: 'enum value'; value: string }; - -type NonUnion = Exclude; - -const simpleKinds = new Set([ - 'unknown', - 'never', - 'record', - 'real', - 'integer', - 'non-negative integer', - 'negative integer', - 'positive integer', - 'ES value', - 'string', - 'number', - 'integral number', - 'bigint', - 'boolean', - 'null', - 'undefined', -]); - -const dominateGraph: Partial> = { - // @ts-expect-error TS does not know about __proto__ - __proto__: null, - record: ['completion'], - real: [ - 'integer', - 'non-negative integer', - 'negative integer', - 'positive integer', - 'concrete real', - ], - integer: ['non-negative integer', 'negative integer', 'positive integer'], - 'non-negative integer': ['positive integer'], - 'ES value': [ - 'string', - 'number', - 'integral number', - 'bigint', - 'boolean', - 'null', - 'undefined', - 'concrete string', - 'concrete number', - 'concrete bigint', - 'concrete boolean', - ], - string: ['concrete string'], - number: ['integral number', 'concrete number'], - bigint: ['concrete bigint'], - boolean: ['concrete boolean'], -}; - -/* -The type lattice used here is very simple (aside from explicit unions). -As such we mostly only need to define the `dominates` relationship and apply trivial rules: -if `x` dominates `y`, then `join(x,y) = x` and `meet(x,y) = y`; if neither dominates the other than the join is top and the meet is bottom. -Unions/lists/completions take a little more work. -*/ - -function dominates(a: Type, b: Type): boolean { - if (a.kind === 'unknown' || b.kind === 'never') { - return true; - } - if (b.kind === 'union') { - return b.of.every(t => dominates(a, t)); - } - if (a.kind === 'union') { - // not necessarily true for arbitrary lattices, but true for ours - return a.of.some(t => dominates(t, b)); - } - if ( - (a.kind === 'list' && b.kind === 'list') || - (a.kind === 'completion' && b.kind === 'completion') - ) { - return dominates(a.of, b.of); - } - if (simpleKinds.has(a.kind) && simpleKinds.has(b.kind) && a.kind === b.kind) { - return true; - } - if (dominateGraph[a.kind]?.includes(b.kind) ?? false) { - return true; - } - if (a.kind === 'integer' && b.kind === 'concrete real') { - return !b.value.includes('.'); - } - if (a.kind === 'integral number' && b.kind === 'concrete number') { - return Number.isFinite(b.value) && b.value === Math.round(b.value); - } - if (a.kind === 'non-negative integer' && b.kind === 'concrete real') { - return !b.value.includes('.') && b.value[0] !== '-'; - } - if (a.kind === 'negative integer' && b.kind === 'concrete real') { - return !b.value.includes('.') && b.value[0] === '-'; - } - if (a.kind === 'positive integer' && b.kind === 'concrete real') { - return !b.value.includes('.') && b.value[0] !== '-' && b.value !== '0'; - } - if ( - a.kind === b.kind && - [ - 'concrete string', - 'concrete number', - 'concrete real', - 'concrete bigint', - 'concrete boolean', - 'enum value', - ].includes(a.kind) - ) { - // @ts-expect-error TS is not quite smart enough for this - return Object.is(a.value, b.value); - } - return false; -} - -function addToUnion(types: NonUnion[], type: NonUnion): Type { - if (types.some(t => dominates(t, type))) { - return { kind: 'union', of: types }; - } - const live = types.filter(t => !dominates(type, t)); - if (live.length === 0) { - return type; - } - return { kind: 'union', of: [...live, type] }; -} - -export function join(a: Type, b: Type): Type { - if (dominates(a, b)) { - return a; - } - if (dominates(b, a)) { - return b; - } - if (b.kind === 'union') { - [a, b] = [b, a]; - } - if (a.kind === 'union') { - if (b.kind === 'union') { - return b.of.reduce( - (acc: Type, t) => (acc.kind === 'union' ? addToUnion(acc.of, t) : join(acc, t)), - a, - ); - } - return addToUnion(a.of, b); - } - if ( - (a.kind === 'list' && b.kind === 'list') || - (a.kind === 'completion' && b.kind === 'completion') - ) { - return { kind: a.kind, of: join(a.of, b.of) }; - } - return { kind: 'union', of: [a, b as NonUnion] }; -} - -export function meet(a: Type, b: Type): Type { - if (dominates(a, b)) { - return b; - } - if (dominates(b, a)) { - return a; - } - if (a.kind !== 'union' && b.kind === 'union') { - [a, b] = [b, a]; - } - if (a.kind === 'union') { - // union is join. meet distributes over join. - return a.of.map(t => meet(t, b)).reduce(join); - } - if ( - (a.kind === 'list' && b.kind === 'list') || - (a.kind === 'completion' && b.kind === 'completion') - ) { - return { kind: a.kind, of: meet(a.of, b.of) }; - } - return { kind: 'never' }; -} - -function serialize(type: Type): string { - switch (type.kind) { - case 'unknown': { - return 'unknown'; - } - case 'never': { - return 'never'; - } - case 'union': { - const parts = type.of.map(serialize); - if (parts.length > 2) { - return parts.slice(0, -1).join(', ') + ', or ' + parts[parts.length - 1]; - } - return parts[0] + ' or ' + parts[1]; - } - case 'list': { - if (type.of.kind === 'never') { - return 'empty List'; - } - return 'List of ' + serialize(type.of); - } - case 'record': { - return 'Record'; - } - case 'completion': { - if (type.of.kind === 'never') { - return 'an abrupt Completion Record'; - } else if (type.of.kind === 'unknown') { - return 'a Completion Record'; - } - return 'a Completion Record normally holding ' + serialize(type.of); - } - case 'real': { - return 'mathematical value'; - } - case 'integer': - case 'non-negative integer': - case 'negative integer': - case 'positive integer': - case 'null': - case 'undefined': { - return type.kind; - } - case 'concrete string': { - return `"${type.value}"`; - } - case 'concrete real': { - return type.value; - } - case 'concrete boolean': { - return `${type.value}`; - } - case 'enum value': { - return `~${type.value}~`; - } - case 'concrete number': { - if (Object.is(type.value, 0 / 0)) { - return '*NaN*'; - } - let repr; - if (Object.is(type.value, -0)) { - repr = '-0'; - } else if (type.value === 0) { - repr = '+0'; - } else if (type.value === 2e308) { - repr = '+∞'; - } else if (type.value === -2e308) { - repr = '-∞'; - } else if (type.value > 4503599627370495.5) { - repr = String(BigInt(type.value)); - } else { - repr = String(type.value); - } - return `*${repr}*𝔽`; - } - case 'concrete bigint': { - return `*${type.value}*`; - } - case 'ES value': { - return 'ECMAScript language value'; - } - case 'boolean': { - return 'Boolean'; - } - case 'string': { - return 'String'; - } - case 'number': { - return 'Number'; - } - case 'integral number': { - return 'integral Number'; - } - case 'bigint': { - return 'BigInt'; - } - } -} - -export function typeFromExpr(expr: Expr, biblio: Biblio): Type { - seq: if (expr.name === 'seq') { - const items = stripWhitespace(expr.items); - if (items.length === 1) { - expr = items[0]; - break seq; - } - - if ( - items.length === 2 && - items[0].name === 'star' && - items[0].contents[0].name === 'text' && - items[1].name === 'text' - ) { - switch (items[1].contents) { - case '𝔽': { - const text = items[0].contents[0].contents; - let value; - if (text === '-0') { - value = -0; - } else if (text === '+∞') { - value = 2e308; - } else if (text === '-∞') { - value = -2e308; - } else { - value = parseFloat(text); - } - return { kind: 'concrete number', value }; - } - case 'ℤ': { - return { kind: 'concrete bigint', value: BigInt(items[0].contents[0].contents) }; - } - } - } - - if (items[0]?.name === 'text' && ['!', '?'].includes(items[0].contents.trim())) { - const remaining = stripWhitespace(items.slice(1)); - if (remaining.length === 1 && ['call', 'sdo-call'].includes(remaining[0].name)) { - const callType = typeFromExpr(remaining[0], biblio); - if (callType.kind === 'completion') { - return callType.of; - } - } - } - } - - switch (expr.name) { - case 'text': { - const text = expr.contents.trim(); - if (/^-?[0-9]+(\.[0-9]+)?$/.test(text)) { - return { kind: 'concrete real', value: text }; - } - break; - } - case 'list': { - return { - kind: 'list', - of: expr.elements.map(t => typeFromExpr(t, biblio)).reduce(join, { kind: 'never' }), - }; - } - case 'record': { - return { kind: 'record' }; - } - case 'call': - case 'sdo-call': { - const { callee } = expr; - if (!(callee.length === 1 && callee[0].name === 'text')) { - break; - } - const calleeName = callee[0].contents; - - const biblioEntry = biblio.byAoid(calleeName); - if (biblioEntry?.signature?.return == null) { - break; - } - return typeFromExprType(biblioEntry.signature.return); - } - case 'tilde': { - if (expr.contents.length === 1 && expr.contents[0].name === 'text') { - return { kind: 'enum value', value: expr.contents[0].contents }; - } - break; - } - case 'star': { - if (expr.contents.length === 1 && expr.contents[0].name === 'text') { - const text = expr.contents[0].contents; - if (text === 'null') { - return { kind: 'null' }; - } else if (text === 'undefined') { - return { kind: 'undefined' }; - } else if (text === 'NaN') { - return { kind: 'concrete number', value: 0 / 0 }; - } else if (text === 'true') { - return { kind: 'concrete boolean', value: true }; - } else if (text === 'false') { - return { kind: 'concrete boolean', value: false }; - } else if (text.startsWith('"') && text.endsWith('"')) { - return { kind: 'concrete string', value: text.slice(1, -1) }; - } - } - - break; - } - } - return { kind: 'unknown' }; -} - -function typeFromExprType(type: BiblioType): Type { - switch (type.kind) { - case 'union': { - return type.types.map(typeFromExprType).reduce(join); - } - case 'list': { - return { - kind: 'list', - of: type.elements == null ? { kind: 'unknown' } : typeFromExprType(type.elements), - }; - } - case 'completion': { - if (type.completionType === 'abrupt') { - return { kind: 'completion', of: { kind: 'never' } }; - } - return { - kind: 'completion', - of: - type.typeOfValueIfNormal == null - ? { kind: 'unknown' } - : typeFromExprType(type.typeOfValueIfNormal), - }; - } - case 'opaque': { - const text = type.type; - if (text.startsWith('"') && text.endsWith('"')) { - return { kind: 'concrete string', value: text.slice(1, -1) }; - } - if (text.startsWith('~') && text.endsWith('~')) { - return { kind: 'enum value', value: text.slice(1, -1) }; - } - if (/^-?[0-9]+(\.[0-9]+)?$/.test(text)) { - return { kind: 'concrete real', value: text }; - } - if (text.startsWith('*') && text.endsWith('*𝔽')) { - const innerText = text.slice(1, -14); - let value; - if (innerText === '-0') { - value = -0; - } else if (innerText === '+∞') { - value = 2e308; - } else if (innerText === '-∞') { - value = -2e308; - } else { - value = parseFloat(innerText); - } - return { kind: 'concrete number', value }; - } - if (text === '*NaN*') { - return { kind: 'concrete number', value: 0 / 0 }; - } - if (text.startsWith('*') && text.endsWith('*')) { - return { kind: 'concrete bigint', value: BigInt(text.slice(1, -14)) }; - } - if (text === 'an ECMAScript language value' || text === 'ECMAScript language values') { - return { kind: 'ES value' }; - } - if (text === 'a String' || text === 'Strings') { - return { kind: 'string' }; - } - if (text === 'a Number' || text === 'Numbers') { - return { kind: 'number' }; - } - if (text === 'a Boolean' || text === 'Booleans') { - return { kind: 'boolean' }; - } - if (text === 'a BigInt' || text === 'BigInts') { - return { kind: 'bigint' }; - } - if (text === 'an integral Number' || text === 'integral Numbers') { - return { kind: 'integral number' }; - } - if (text === 'a mathematical value' || text === 'mathematical values') { - return { kind: 'real' }; - } - if (text === 'an integer' || text === 'integers') { - return { kind: 'integer' }; - } - if (text === 'a non-negative integer' || text === 'non-negative integers') { - return { kind: 'non-negative integer' }; - } - if (text === 'a negative integer' || text === 'negative integers') { - return { kind: 'negative integer' }; - } - if (text === 'a positive integer' || text === 'positive integers') { - return { kind: 'positive integer' }; - } - if (text === 'a time value' || text === 'time values') { - return { - kind: 'union', - of: [{ kind: 'integral number' }, { kind: 'concrete number', value: 0 / 0 }], - }; - } - if (text === '*null*') { - return { kind: 'null' }; - } - if (text === '*undefined*') { - return { kind: 'undefined' }; - } - break; - } - case 'unused': { - // this is really only a return type, but might as well handle it - return { kind: 'enum value', value: '~unused~' }; - } - } - return { kind: 'unknown' }; -} diff --git a/test/typecheck.js b/test/typecheck.js index 90d33cec..a1be0d57 100644 --- a/test/typecheck.js +++ b/test/typecheck.js @@ -1511,7 +1511,7 @@ describe('type system', () => { await assertTypeError( 'an ECMAScript language value', 'NormalCompletion(42)', - 'argument type (a Completion Record) does not look plausibly assignable to parameter type (ECMAScript language value)', + 'argument type (a normal completion) does not look plausibly assignable to parameter type (ECMAScript language value)', [completionBiblio], ); @@ -1528,14 +1528,14 @@ describe('type system', () => { await assertTypeError( 'either a normal completion containing a Boolean or an abrupt completion', '*false*', - 'argument (false) does not look plausibly assignable to parameter type (a Completion Record normally holding Boolean)', + 'argument (false) does not look plausibly assignable to parameter type (a normal completion containing Boolean or an abrupt completion)', [completionBiblio], ); await assertTypeError( 'a Boolean', 'NormalCompletion(*false*)', - 'argument type (a Completion Record) does not look plausibly assignable to parameter type (Boolean)', + 'argument type (a normal completion) does not look plausibly assignable to parameter type (Boolean)', [completionBiblio], ); diff --git a/tsconfig.test.json b/tsconfig.test.json index 236105ee..dd0bb73c 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -2,6 +2,7 @@ "extends": "./tsconfig", "include": ["lib/**/*"], "compilerOptions": { + "outDir": "this needs to be set so it does not ignore lib", "noEmit": true, } }