Use a babel plugin to put expression in assert statements
diff --git a/ui/config/babel-plugin-assert-messages.js b/ui/config/babel-plugin-assert-messages.js new file mode 100644 index 0000000..b11ba64 --- /dev/null +++ b/ui/config/babel-plugin-assert-messages.js
@@ -0,0 +1,92 @@ +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * Babel plugin that automatically adds descriptive messages to assertion + * functions. Transforms calls like `assertExists(foo.bar)` into + * `assertExists(foo.bar, 'foo.bar')` so that error messages show what + * expression failed. + */ + +const ASSERTION_FUNCTIONS = new Set([ + 'assertExists', + 'assertTrue', + 'assertFalse', + 'assertDefined', + 'assertUnreachable', + 'assertIsInstanceOf', +]); + +module.exports = function assertMessagesPlugin({types: t}) { + function getFunctionName(callee) { + // Simple identifier: assertExists(...) + if (t.isIdentifier(callee)) { + return callee.name; + } + // Member expression: logging.assertExists(...) or foo.bar.assertExists(...) + if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) { + return callee.property.name; + } + // Indirect call pattern: (0, logging.assertExists)(...) + // This is common after tsc/commonjs transforms + if (t.isSequenceExpression(callee)) { + const expressions = callee.expressions; + if (expressions.length === 2) { + const lastExpr = expressions[1]; + if (t.isMemberExpression(lastExpr) && t.isIdentifier(lastExpr.property)) { + return lastExpr.property.name; + } + } + } + return null; + } + + return { + name: 'assert-messages', + visitor: { + CallExpression(path) { + const callee = path.node.callee; + const funcName = getFunctionName(callee); + + // Check if it's one of our assertion functions + if (!funcName || !ASSERTION_FUNCTIONS.has(funcName)) { + return; + } + + const args = path.node.arguments; + + // If no arguments, skip + if (args.length === 0) { + return; + } + + // If last argument is already a string literal, assume it's a message + const lastArg = args[args.length - 1]; + if (t.isStringLiteral(lastArg)) { + return; + } + + // Extract source text directly from the original code + const firstArg = args[0]; + const code = path.hub.file.code; + if (!code || firstArg.start == null || firstArg.end == null) { + return; // Can't get source text, leave the call unchanged + } + + const sourceText = code.slice(firstArg.start, firstArg.end); + args.push(t.stringLiteral(sourceText)); + }, + }, + }; +};
diff --git a/ui/config/rollup.config.js b/ui/config/rollup.config.js index a781e68..420e2f8 100644 --- a/ui/config/rollup.config.js +++ b/ui/config/rollup.config.js
@@ -13,6 +13,7 @@ // limitations under the License. const {uglify} = require('rollup-plugin-uglify'); +const babel = require('@rollup/plugin-babel'); const commonjs = require('@rollup/plugin-commonjs'); const nodeResolve = require('@rollup/plugin-node-resolve'); const path = require('path'); @@ -154,6 +155,17 @@ ], }), + // Add descriptive messages to assertion functions. + babel.babel({ + babelHelpers: 'bundled', + babelrc: false, + configFile: false, + plugins: [path.join(__dirname, 'babel-plugin-assert-messages.js')], + extensions: ['.js'], + include: ['**/*.js'], + exclude: ['node_modules/**'], + }), + // Translate source maps to point back to the .ts sources. sourcemaps(),
diff --git a/ui/package.json b/ui/package.json index 74447ac..48c4ba7 100644 --- a/ui/package.json +++ b/ui/package.json
@@ -53,9 +53,11 @@ "zod": "^4.3.5" }, "devDependencies": { + "@babel/core": "^7.28.6", "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.6.0", "@playwright/test": "^1.47.0", + "@rollup/plugin-babel": "^6.1.0", "@rollup/plugin-commonjs": "^26.0.1", "@rollup/plugin-node-resolve": "^15.2.3", "@types/jest": "^29.5.12",
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index 9abd878..5e6ad1a 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml
@@ -139,6 +139,9 @@ version: 4.3.5 devDependencies: + '@babel/core': + specifier: ^7.28.6 + version: 7.28.6 '@eslint/eslintrc': specifier: ^3.1.0 version: 3.1.0 @@ -148,6 +151,9 @@ '@playwright/test': specifier: ^1.47.0 version: 1.47.0 + '@rollup/plugin-babel': + specifier: ^6.1.0 + version: 6.1.0(@babel/core@7.28.6)(rollup@2.79.1) '@rollup/plugin-commonjs': specifier: ^26.0.1 version: 26.0.1(rollup@2.79.1) @@ -229,14 +235,6 @@ packages: - /@ampproject/remapping@2.2.1: - resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.18 - dev: true - /@babel/code-frame@7.22.5: resolution: {integrity: sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==} engines: {node: '>=6.9.0'} @@ -248,58 +246,39 @@ resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 dev: true - /@babel/compat-data@7.22.5: - resolution: {integrity: sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/compat-data@7.24.7: - resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/core@7.22.5: - resolution: {integrity: sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==} + /@babel/code-frame@7.28.6: + resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} engines: {node: '>=6.9.0'} dependencies: - '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.22.5 - '@babel/helper-compilation-targets': 7.22.5(@babel/core@7.22.5) - '@babel/helper-module-transforms': 7.22.5 - '@babel/helpers': 7.22.5 - '@babel/parser': 7.22.5 - '@babel/template': 7.22.5 - '@babel/traverse': 7.22.5 - '@babel/types': 7.22.5 - convert-source-map: 1.9.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 dev: true - /@babel/core@7.24.7: - resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==} + /@babel/compat-data@7.28.6: + resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/core@7.28.6: + resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} engines: {node: '>=6.9.0'} dependencies: - '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helpers': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/template': 7.24.7 - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 gensync: 1.0.0-beta.2 @@ -309,98 +288,63 @@ - supports-color dev: true - /@babel/generator@7.22.5: - resolution: {integrity: sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.5 - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.18 - jsesc: 2.5.2 - dev: true - /@babel/generator@7.24.7: resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.7 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 + '@babel/types': 7.28.6 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 jsesc: 2.5.2 dev: true - /@babel/helper-compilation-targets@7.22.5(@babel/core@7.22.5): - resolution: {integrity: sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.22.5 - '@babel/core': 7.22.5 - '@babel/helper-validator-option': 7.22.5 - browserslist: 4.23.1 - lru-cache: 5.1.1 - semver: 6.3.1 - dev: true - - /@babel/helper-compilation-targets@7.24.7: - resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} + /@babel/generator@7.28.6: + resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/compat-data': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - browserslist: 4.23.1 - lru-cache: 5.1.1 - semver: 6.3.1 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 dev: true - /@babel/helper-environment-visitor@7.22.5: - resolution: {integrity: sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==} + /@babel/helper-compilation-targets@7.28.6: + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} + dependencies: + '@babel/compat-data': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 dev: true /@babel/helper-environment-visitor@7.24.7: resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.7 - dev: true - - /@babel/helper-function-name@7.22.5: - resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 + '@babel/types': 7.28.6 dev: true /@babel/helper-function-name@7.24.7: resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 dev: true - /@babel/helper-hoist-variables@7.22.5: - resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} + /@babel/helper-globals@7.28.0: + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.7 dev: true /@babel/helper-hoist-variables@7.24.7: resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.7 - dev: true - - /@babel/helper-module-imports@7.22.5: - resolution: {integrity: sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.28.6 dev: true /@babel/helper-module-imports@7.24.7: @@ -413,34 +357,26 @@ - supports-color dev: true - /@babel/helper-module-transforms@7.22.5: - resolution: {integrity: sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==} + /@babel/helper-module-imports@7.28.6: + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-environment-visitor': 7.22.5 - '@babel/helper-module-imports': 7.22.5 - '@babel/helper-simple-access': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.5 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/template': 7.22.5 - '@babel/traverse': 7.22.5 - '@babel/types': 7.22.5 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 transitivePeerDependencies: - supports-color dev: true - /@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7): - resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} + /@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6): + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/helper-validator-identifier': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color dev: true @@ -455,266 +391,204 @@ engines: {node: '>=6.9.0'} dev: true - /@babel/helper-simple-access@7.22.5: - resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.7 - dev: true - - /@babel/helper-simple-access@7.24.7: - resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-split-export-declaration@7.22.5: - resolution: {integrity: sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.7 - dev: true - /@babel/helper-split-export-declaration@7.24.7: resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.28.6 dev: true - /@babel/helper-string-parser@7.22.5: - resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} - engines: {node: '>=6.9.0'} - /@babel/helper-string-parser@7.24.7: resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} engines: {node: '>=6.9.0'} dev: true + /@babel/helper-string-parser@7.27.1: + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + /@babel/helper-validator-identifier@7.27.1: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} + dev: true - /@babel/helper-validator-option@7.22.5: - resolution: {integrity: sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==} + /@babel/helper-validator-identifier@7.28.5: + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + /@babel/helper-validator-option@7.27.1: + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} dev: true - /@babel/helper-validator-option@7.24.7: - resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/helpers@7.22.5: - resolution: {integrity: sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==} + /@babel/helpers@7.28.6: + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.22.5 - '@babel/traverse': 7.22.5 - '@babel/types': 7.22.5 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helpers@7.24.7: - resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 dev: true /@babel/highlight@7.22.5: resolution: {integrity: sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 chalk: 2.4.2 js-tokens: 4.0.0 dev: true - /@babel/parser@7.22.5: - resolution: {integrity: sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.22.5 - /@babel/parser@7.24.7: resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.22.5 + '@babel/types': 7.28.6 dev: true - /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.22.5): + /@babel/parser@7.28.6: + resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.28.6 + + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.6): resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.24.7 dev: true - /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.22.5): + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.6): resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.24.7 dev: true - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.22.5): + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.6): resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.24.7 dev: true - /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.22.5): + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.6): resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.24.7 dev: true - /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.22.5): + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.6): resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.24.7 dev: true - /@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.22.5): + /@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.28.6): resolution: {integrity: sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.24.7 dev: true - /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.22.5): + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.6): resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.24.7 dev: true - /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.22.5): + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.6): resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.24.7 dev: true - /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.22.5): + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.6): resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.24.7 dev: true - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.22.5): + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.6): resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.24.7 dev: true - /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.22.5): + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.6): resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.24.7 dev: true - /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.22.5): + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.6): resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.24.7 dev: true - /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.22.5): + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.6): resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.24.7 dev: true - /@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.22.5): + /@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.28.6): resolution: {integrity: sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.24.7 dev: true - /@babel/template@7.22.5: - resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==} + /@babel/template@7.28.6: + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.24.7 - '@babel/types': 7.22.5 - dev: true - - /@babel/template@7.24.7: - resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - dev: true - - /@babel/traverse@7.22.5: - resolution: {integrity: sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.22.5 - '@babel/helper-environment-visitor': 7.22.5 - '@babel/helper-function-name': 7.22.5 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.5 - '@babel/parser': 7.24.7 - '@babel/types': 7.22.5 - debug: 4.4.3 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color + '@babel/code-frame': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 dev: true /@babel/traverse@7.24.7: @@ -735,13 +609,20 @@ - supports-color dev: true - /@babel/types@7.22.5: - resolution: {integrity: sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==} + /@babel/traverse@7.28.6: + resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-string-parser': 7.22.5 - '@babel/helper-validator-identifier': 7.27.1 - to-fast-properties: 2.0.0 + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + dev: true /@babel/types@7.24.7: resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} @@ -752,6 +633,13 @@ to-fast-properties: 2.0.0 dev: true + /@babel/types@7.28.6: + resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + /@bcoe/v8-coverage@0.2.3: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true @@ -1295,7 +1183,7 @@ '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.31 '@types/node': 20.14.9 chalk: 4.1.2 collect-v8-coverage: 1.0.1 @@ -1329,7 +1217,7 @@ resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.31 callsites: 3.1.0 graceful-fs: 4.2.11 dev: true @@ -1358,9 +1246,9 @@ resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.31 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 2.0.0 @@ -1389,13 +1277,11 @@ chalk: 4.1.2 dev: true - /@jridgewell/gen-mapping@0.3.3: - resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} - engines: {node: '>=6.0.0'} + /@jridgewell/gen-mapping@0.3.13: + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 dev: true /@jridgewell/gen-mapping@0.3.5: @@ -1404,6 +1290,13 @@ dependencies: '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.31 + dev: true + + /@jridgewell/remapping@2.3.5: + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + dependencies: + '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 dev: true @@ -1412,29 +1305,17 @@ engines: {node: '>=6.0.0'} dev: true - /@jridgewell/set-array@1.1.2: - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} - engines: {node: '>=6.0.0'} - dev: true - /@jridgewell/set-array@1.2.1: resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} dev: true - /@jridgewell/sourcemap-codec@1.4.14: - resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - dev: true - /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} dev: true - /@jridgewell/trace-mapping@0.3.18: - resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} - dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 + /@jridgewell/sourcemap-codec@1.5.5: + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} dev: true /@jridgewell/trace-mapping@0.3.25: @@ -1444,6 +1325,13 @@ '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /@jridgewell/trace-mapping@0.3.31: + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + /@jsdoc/salty@0.2.5: resolution: {integrity: sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==} engines: {node: '>=v12.0.0'} @@ -1631,6 +1519,27 @@ - supports-color dev: true + /@rollup/plugin-babel@6.1.0(@babel/core@7.28.6)(rollup@2.79.1): + resolution: {integrity: sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@types/babel__core': ^7.1.9 + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + '@types/babel__core': + optional: true + rollup: + optional: true + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.24.7 + '@rollup/pluginutils': 5.2.0(rollup@2.79.1) + rollup: 2.79.1 + transitivePeerDependencies: + - supports-color + dev: true + /@rollup/plugin-commonjs@26.0.1(rollup@2.79.1): resolution: {integrity: sha512-UnsKoZK6/aGIH6AdkptXhNvhaqftcjq3zZdT+LY5Ftms6JR06nADcDsYp5hTU9E2lbJUEOhdlY5J4DNTneM+jQ==} engines: {node: '>=16.0.0 || 14 >= 14.17'} @@ -1720,7 +1629,6 @@ estree-walker: 2.0.2 picomatch: 4.0.3 rollup: 2.79.1 - dev: false /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -1750,8 +1658,8 @@ /@types/babel__core@7.20.1: resolution: {integrity: sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==} dependencies: - '@babel/parser': 7.24.7 - '@babel/types': 7.22.5 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 '@types/babel__traverse': 7.20.1 @@ -1760,20 +1668,20 @@ /@types/babel__generator@7.6.4: resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.28.6 dev: true /@types/babel__template@7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 dev: true /@types/babel__traverse@7.20.1: resolution: {integrity: sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==} dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.28.6 dev: true /@types/chrome@0.0.268: @@ -2439,17 +2347,17 @@ resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} dev: true - /babel-jest@29.7.0(@babel/core@7.22.5): + /babel-jest@29.7.0(@babel/core@7.28.6): resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.8.0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.1 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.22.5) + babel-preset-jest: 29.6.3(@babel/core@7.28.6) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -2474,41 +2382,41 @@ resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 '@types/babel__core': 7.20.1 '@types/babel__traverse': 7.20.1 dev: true - /babel-preset-current-node-syntax@1.0.1(@babel/core@7.22.5): + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.28.6): resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.22.5 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.22.5) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.22.5) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.22.5) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.22.5) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.22.5) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.22.5) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.5) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.22.5) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.22.5) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.22.5) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.5) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.22.5) + '@babel/core': 7.28.6 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.6) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.6) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.6) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.6) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.6) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.6) dev: true - /babel-preset-jest@29.6.3(@babel/core@7.22.5): + /babel-preset-jest@29.6.3(@babel/core@7.28.6): resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.5) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.28.6) dev: true /balanced-match@1.0.2: @@ -2596,6 +2504,11 @@ /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + /baseline-browser-mapping@2.9.15: + resolution: {integrity: sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==} + hasBin: true + dev: true + /basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} @@ -2660,6 +2573,18 @@ update-browserslist-db: 1.0.16(browserslist@4.23.1) dev: true + /browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + baseline-browser-mapping: 2.9.15 + caniuse-lite: 1.0.30001765 + electron-to-chromium: 1.5.267 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + dev: true + /bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} dependencies: @@ -2737,6 +2662,10 @@ resolution: {integrity: sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==} dev: true + /caniuse-lite@1.0.30001765: + resolution: {integrity: sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ==} + dev: true + /catharsis@0.9.0: resolution: {integrity: sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==} engines: {node: '>= 10'} @@ -2886,10 +2815,6 @@ engines: {node: '>= 0.6'} dev: false - /convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - dev: true - /convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true @@ -3409,6 +3334,10 @@ resolution: {integrity: sha512-GVulpHjFu1Y9ZvikvbArHmAhZXtm3wHlpjTMcXNGKl4IQ4jMQjlnz8yMQYYqdLHKi/jEL2+CBC2akWVCoIGUdw==} dev: true + /electron-to-chromium@1.5.267: + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + dev: true + /emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} engines: {node: '>=12'} @@ -4532,8 +4461,8 @@ resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} dependencies: - '@babel/core': 7.24.7 - '@babel/parser': 7.24.7 + '@babel/core': 7.28.6 + '@babel/parser': 7.28.6 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 6.3.1 @@ -4545,8 +4474,8 @@ resolution: {integrity: sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==} engines: {node: '>=10'} dependencies: - '@babel/core': 7.24.7 - '@babel/parser': 7.24.7 + '@babel/core': 7.28.6 + '@babel/parser': 7.28.6 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 7.7.2 @@ -4676,11 +4605,11 @@ ts-node: optional: true dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.28.6 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 '@types/node': 20.14.9 - babel-jest: 29.7.0(@babel/core@7.22.5) + babel-jest: 29.7.0(@babel/core@7.28.6) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 @@ -4818,7 +4747,7 @@ resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.28.6 '@jest/types': 29.6.3 '@types/stack-utils': 2.0.1 chalk: 4.1.2 @@ -4943,15 +4872,15 @@ resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/core': 7.22.5 - '@babel/generator': 7.22.5 - '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.22.5) - '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.22.5) - '@babel/types': 7.22.5 + '@babel/core': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.28.6) + '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.28.6) + '@babel/types': 7.28.6 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.5) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.28.6) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -5091,7 +5020,7 @@ engines: {node: '>=12.0.0'} hasBin: true dependencies: - '@babel/parser': 7.22.5 + '@babel/parser': 7.28.6 '@jsdoc/salty': 0.2.5 '@types/markdown-it': 12.2.3 bluebird: 3.7.2 @@ -5155,6 +5084,12 @@ hasBin: true dev: true + /jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + dev: true + /json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} dependencies: @@ -5518,6 +5453,10 @@ resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} dev: true + /node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + dev: true + /noice-json-rpc@1.2.0: resolution: {integrity: sha512-Wm+otW+drKzdqlSPoSwj34tUEq/Xj1gX6Cr2avrykvTW4IY7d3ngLmP+PErALzS0s9nYRokXvYDM54sbFvLlDA==} dev: false @@ -5682,7 +5621,7 @@ resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.28.6 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -5749,7 +5688,6 @@ /picomatch@4.0.3: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - dev: false /pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} @@ -6559,6 +6497,7 @@ /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} + dev: true /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} @@ -6700,6 +6639,17 @@ picocolors: 1.1.1 dev: true + /update-browserslist-db@1.2.3(browserslist@4.28.1): + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + dev: true + /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: @@ -6741,7 +6691,7 @@ resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} dependencies: - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.31 '@types/istanbul-lib-coverage': 2.0.4 convert-source-map: 2.0.0 dev: true
diff --git a/ui/src/base/logging.ts b/ui/src/base/logging.ts index 5856774..f4192b6 100644 --- a/ui/src/base/logging.ts +++ b/ui/src/base/logging.ts
@@ -29,42 +29,50 @@ export type ErrorHandler = (err: ErrorDetails) => void; const errorHandlers: ErrorHandler[] = []; -export function assertExists<A>( - value: A | null | undefined, - optMsg?: string, -): A { +export function assertExists<A>(value: A | null | undefined, expr?: string): A { if (value === null || value === undefined) { - throw new Error(optMsg ?? "Value doesn't exist"); + throw new Error(`\`${expr ?? '<expression>'}\` doesn't exist`); } return value; } // assertExists trips over NULLs, but in many contexts NULL is a valid SQL value we have to work with. -export function assertDefined<T>(value: T | undefined): T { - if (value === undefined) throw new Error('Value is undefined'); +export function assertDefined<T>(value: T | undefined, expr?: string): T { + if (value === undefined) { + throw new Error(`\`${expr ?? '<expression>'}\` is undefined`); + } return value; } -export function assertIsInstance<T>( +export function assertIsInstanceOf<T>( value: unknown, clazz: Function, - optMsg?: string, + expr?: string, ): T { - assertTrue( - value instanceof clazz, - optMsg ?? `Value is not an instance of ${clazz.name}`, - ); + if (!(value instanceof clazz)) { + throw new Error( + `\`${expr ?? '<expression>'}\` is not an instance of ${clazz.name}`, + ); + } return value as T; } -export function assertTrue(value: boolean, optMsg?: string) { +export function assertTrue(value: boolean, expr?: string) { if (!value) { - throw new Error(optMsg ?? 'Failed assertion'); + throw new Error(`\`${expr ?? '<expression>'}\` is falsy`); } } -export function assertFalse(value: boolean, optMsg?: string) { - assertTrue(!value, optMsg); +export function assertFalse(value: boolean, expr?: string) { + if (value) { + throw new Error(`\`${expr ?? '<expression>'}\` is truthy`); + } +} + +// Unconditionally throws an error. Use for code paths that shouldn't be reached +// but where TypeScript can't prove unreachability. +export function fail(message: string): never { + throw new Error(message); } // This function serves two purposes. @@ -74,9 +82,24 @@ // 2) A compile time check where typescript asserts that the value passed can be // cast to the "never" type. // This is useful for ensuring we exhaustively check union types. -export function assertUnreachable(value: never, optMsg?: string): never { +export function assertUnreachable(value: never, expr?: string): never { + let valueStr: string; + try { + valueStr = + JSON.stringify(value, (_, v) => { + if (typeof v === 'bigint') return `${v}n`; + if (typeof v === 'symbol') return v.toString(); + if (typeof v === 'function') { + return `[function ${(v.name as string) || 'anonymous'}]`; + } + return v; + }) ?? String(value); + } catch { + // Circular reference or other issue + valueStr = String(value); + } throw new Error( - optMsg ?? `This code should not be reachable ${value as unknown}`, + `Unreachable code reached when \`${expr ?? '<expression>'}\` = ${valueStr}`, ); }
diff --git a/ui/src/base/logging_unittest.ts b/ui/src/base/logging_unittest.ts new file mode 100644 index 0000000..959816b --- /dev/null +++ b/ui/src/base/logging_unittest.ts
@@ -0,0 +1,253 @@ +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { + assertDefined, + assertExists, + assertFalse, + assertIsInstanceOf, + assertTrue, + assertUnreachable, + fail, +} from './logging'; + +describe('assertExists', () => { + test('returns value when not null or undefined', () => { + expect(assertExists(42)).toBe(42); + expect(assertExists('hello')).toBe('hello'); + expect(assertExists(0)).toBe(0); + expect(assertExists('')).toBe(''); + expect(assertExists(false)).toBe(false); + }); + + test('throws on null', () => { + expect(() => assertExists(null)).toThrow("`<expression>` doesn't exist"); + }); + + test('throws on undefined', () => { + expect(() => assertExists(undefined)).toThrow( + "`<expression>` doesn't exist", + ); + }); + + test('includes description in error message', () => { + expect(() => assertExists(null, 'foo.bar')).toThrow( + "`foo.bar` doesn't exist", + ); + }); +}); + +describe('assertDefined', () => { + test('returns value when defined', () => { + expect(assertDefined(42)).toBe(42); + expect(assertDefined(null)).toBe(null); // null is defined + expect(assertDefined(0)).toBe(0); + expect(assertDefined('')).toBe(''); + }); + + test('throws on undefined', () => { + expect(() => assertDefined(undefined)).toThrow( + '`<expression>` is undefined', + ); + }); + + test('includes description in error message', () => { + expect(() => assertDefined(undefined, 'config.option')).toThrow( + '`config.option` is undefined', + ); + }); +}); + +describe('assertTrue', () => { + test('does not throw when value is true', () => { + expect(() => assertTrue(true)).not.toThrow(); + }); + + test('throws when value is false', () => { + expect(() => assertTrue(false)).toThrow('`<expression>` is falsy'); + }); + + test('includes description in error message', () => { + expect(() => assertTrue(false, 'x > 0')).toThrow('`x > 0` is falsy'); + }); +}); + +describe('assertFalse', () => { + test('does not throw when value is false', () => { + expect(() => assertFalse(false)).not.toThrow(); + }); + + test('throws when value is true', () => { + expect(() => assertFalse(true)).toThrow('`<expression>` is truthy'); + }); + + test('includes description in error message', () => { + expect(() => assertFalse(true, 'isDisabled')).toThrow( + '`isDisabled` is truthy', + ); + }); +}); + +describe('assertIsInstanceOf', () => { + test('returns value when instance matches', () => { + const arr = [1, 2, 3]; + expect(assertIsInstanceOf(arr, Array)).toBe(arr); + + const date = new Date(); + expect(assertIsInstanceOf(date, Date)).toBe(date); + }); + + test('throws when instance does not match', () => { + expect(() => assertIsInstanceOf({}, Array)).toThrow( + '`<expression>` is not an instance of Array', + ); + }); + + test('includes description in error message', () => { + expect(() => assertIsInstanceOf('hello', Array, 'myVar')).toThrow( + '`myVar` is not an instance of Array', + ); + }); +}); + +describe('fail', () => { + test('always throws with the given message', () => { + expect(() => fail('something went wrong')).toThrow('something went wrong'); + }); +}); + +describe('assertUnreachable', () => { + test('throws with value in error message', () => { + // We need to cast to never to test this + const value = 'unexpected' as never; + expect(() => assertUnreachable(value)).toThrow( + 'Unreachable code reached when `<expression>` = "unexpected"', + ); + }); + + test('includes description in error message', () => { + const value = 'oops' as never; + expect(() => assertUnreachable(value, 'mode')).toThrow( + 'Unreachable code reached when `mode` = "oops"', + ); + }); + + test('handles numeric values', () => { + const value = 42 as never; + expect(() => assertUnreachable(value, 'count')).toThrow( + 'Unreachable code reached when `count` = 42', + ); + }); + + test('handles object values', () => { + const value = {foo: 'bar'} as never; + expect(() => assertUnreachable(value, 'obj')).toThrow( + 'Unreachable code reached when `obj` = {"foo":"bar"}', + ); + }); + + test('handles bigint values', () => { + const value = BigInt(123) as never; + expect(() => assertUnreachable(value, 'big')).toThrow( + 'Unreachable code reached when `big` = "123n"', + ); + }); + + test('handles symbol values', () => { + const value = Symbol('test') as never; + expect(() => assertUnreachable(value, 'sym')).toThrow( + 'Unreachable code reached when `sym` = "Symbol(test)"', + ); + }); + + test('handles objects with bigint properties', () => { + const value = {id: BigInt(456)} as never; + expect(() => assertUnreachable(value, 'data')).toThrow( + 'Unreachable code reached when `data` = {"id":"456n"}', + ); + }); + + test('handles complex expression descriptions from babel plugin', () => { + const value = 'unknown_state' as never; + expect(() => assertUnreachable(value, 'config.settings.mode')).toThrow( + 'Unreachable code reached when `config.settings.mode` = "unknown_state"', + ); + }); + + test('handles array access expression descriptions', () => { + const value = 99 as never; + expect(() => assertUnreachable(value, 'items[0].type')).toThrow( + 'Unreachable code reached when `items[0].type` = 99', + ); + }); + + test('handles call expression descriptions', () => { + const value = {invalid: true} as never; + expect(() => assertUnreachable(value, 'getStatus(...)')).toThrow( + 'Unreachable code reached when `getStatus(...)` = {"invalid":true}', + ); + }); + + test('handles function call result as value', () => { + const getValue = () => ({computed: true, status: 'error'}); + expect(() => assertUnreachable(getValue() as never, 'result')).toThrow( + 'Unreachable code reached when `result` = {"computed":true,"status":"error"}', + ); + }); + + test('handles deeply nested object as value', () => { + const value = { + level1: { + level2: { + level3: { + data: 'deep', + }, + }, + }, + } as never; + expect(() => assertUnreachable(value, 'nested')).toThrow( + 'Unreachable code reached when `nested` = {"level1":{"level2":{"level3":{"data":"deep"}}}}', + ); + }); + + test('handles array as value', () => { + const value = [1, 2, 3, 'mixed', {obj: true}] as never; + expect(() => assertUnreachable(value, 'arr')).toThrow( + 'Unreachable code reached when `arr` = [1,2,3,"mixed",{"obj":true}]', + ); + }); + + test('handles inline literal object', () => { + expect(() => + assertUnreachable({type: 'UNKNOWN', payload: null} as never, 'action'), + ).toThrow( + 'Unreachable code reached when `action` = {"type":"UNKNOWN","payload":null}', + ); + }); + + test('handles function value', () => { + const value = function myFunc() {} as never; + expect(() => assertUnreachable(value, 'fn')).toThrow( + 'Unreachable code reached when `fn` = "[function myFunc]"', + ); + }); + + test('handles anonymous function value', () => { + // Use IIFE to create a truly anonymous function (variable assignment names it) + const value = (() => () => {})() as never; + expect(() => assertUnreachable(value, 'fn')).toThrow( + 'Unreachable code reached when `fn` = "[function anonymous]"', + ); + }); +});
diff --git a/ui/src/components/details/thread_slice_details_tab.ts b/ui/src/components/details/thread_slice_details_tab.ts index 9d493a2..9efc87d 100644 --- a/ui/src/components/details/thread_slice_details_tab.ts +++ b/ui/src/components/details/thread_slice_details_tab.ts
@@ -35,7 +35,7 @@ import {DurationWidget} from '../widgets/duration'; import {SliceRef} from '../widgets/slice'; import {Grid, GridCell, GridHeaderCell} from '../../widgets/grid'; -import {assertIsInstance} from '../../base/logging'; +import {assertIsInstanceOf} from '../../base/logging'; import {Trace} from '../../public/trace'; import {TrackEventDetailsPanel} from '../../public/details_panel'; import {TrackEventSelection} from '../../public/selection'; @@ -224,10 +224,10 @@ private readonly attrs: ThreadSliceDetailsPanelAttrs; constructor(trace: Trace, attrs?: ThreadSliceDetailsPanelAttrs) { - // Rationale for the assertIsInstance: ThreadSliceDetailsPanel requires a + // Rationale for the assertIsInstanceOf: ThreadSliceDetailsPanel requires a // TraceImpl (because of flows) but here we must take a Trace interface, // because this track is exposed to plugins (which see only Trace). - this.trace = assertIsInstance(trace, TraceImpl); + this.trace = assertIsInstanceOf(trace, TraceImpl); this.attrs = attrs ?? {}; }
diff --git a/ui/src/frontend/timeline_page/gridline_helper.ts b/ui/src/frontend/timeline_page/gridline_helper.ts index 92ceb4b..33f03ea 100644 --- a/ui/src/frontend/timeline_page/gridline_helper.ts +++ b/ui/src/frontend/timeline_page/gridline_helper.ts
@@ -133,8 +133,8 @@ maxMajorTicks: number, offset: time = Time.ZERO, ): Generator<Tick> { - assertTrue(timeSpan.duration > 0n, 'timeSpan.duration cannot be lte 0'); - assertTrue(maxMajorTicks > 0, 'maxMajorTicks cannot be lte 0'); + assertTrue(timeSpan.duration > 0n); + assertTrue(maxMajorTicks > 0); timeSpan = timeSpan.translate(-offset); const minStepSize = BigInt(
diff --git a/ui/src/plugins/dev.perfetto.AutoPinAndExpandTracks/index.ts b/ui/src/plugins/dev.perfetto.AutoPinAndExpandTracks/index.ts index d6dafff..5982e49 100644 --- a/ui/src/plugins/dev.perfetto.AutoPinAndExpandTracks/index.ts +++ b/ui/src/plugins/dev.perfetto.AutoPinAndExpandTracks/index.ts
@@ -18,7 +18,7 @@ import {PerfettoPlugin} from '../../public/plugin'; import {Track} from '../../public/track'; import {z} from 'zod'; -import {assertIsInstance} from '../../base/logging'; +import {assertIsInstanceOf} from '../../base/logging'; import {RouteArg, RouteArgs} from '../../public/route_schema'; import {arrayEquals} from '../../base/array_utils'; @@ -182,7 +182,7 @@ name: 'Import by name: Pinned tracks', callback: async () => { const files = document.querySelector('.pinned_tracks_import_selector'); - assertIsInstance<HTMLInputElement>(files, HTMLInputElement).click(); + assertIsInstanceOf<HTMLInputElement>(files, HTMLInputElement).click(); }, });
diff --git a/ui/src/plugins/dev.perfetto.Chaos/index.ts b/ui/src/plugins/dev.perfetto.Chaos/index.ts index 1b09f5c..f435d15 100644 --- a/ui/src/plugins/dev.perfetto.Chaos/index.ts +++ b/ui/src/plugins/dev.perfetto.Chaos/index.ts
@@ -16,11 +16,27 @@ import {App} from '../../public/app'; import {addDebugSliceTrack} from '../../components/tracks/debug_tracks'; import {PerfettoPlugin} from '../../public/plugin'; +import { + assertDefined, + assertExists, + assertFalse, + assertIsInstanceOf, + assertTrue, + assertUnreachable, + fail, +} from '../../base/logging'; export default class implements PerfettoPlugin { static readonly id = 'dev.perfetto.Chaos'; static onActivate(ctx: App): void { + const testObject = { + a: 123, + b: null, + c: undefined, + d: 'invalid' as 'a' | 'b', + }; + ctx.commands.registerCommand({ id: 'dev.perfetto.CrashNow', name: 'Chaos: crash now', @@ -28,6 +44,69 @@ throw new Error('Manual crash from dev.perfetto.Chaos#CrashNow'); }, }); + + ctx.commands.registerCommand({ + id: 'dev.perfetto.AssertTrue', + name: 'Chaos: assertTrue failure', + callback: () => { + assertTrue(testObject.a > 200); + }, + }); + + ctx.commands.registerCommand({ + id: 'dev.perfetto.AssertFalse', + name: 'Chaos: assertFalse failure', + callback: () => { + assertFalse(testObject.a < 200); + }, + }); + + ctx.commands.registerCommand({ + id: 'dev.perfetto.AssertExists', + name: 'Chaos: assertExists failure', + callback: () => { + assertExists(testObject.b); + }, + }); + + ctx.commands.registerCommand({ + id: 'dev.perfetto.AssertDefined', + name: 'Chaos: assertDefined failure', + callback: () => { + assertDefined(testObject.c); + }, + }); + + ctx.commands.registerCommand({ + id: 'dev.perfetto.AssertUnreachable', + name: 'Chaos: assertUnreachable failure', + callback: () => { + switch (testObject.d) { + case 'a': + break; + case 'b': + break; + default: + assertUnreachable(testObject.d); + } + }, + }); + + ctx.commands.registerCommand({ + id: 'dev.perfetto.assertIsInstanceOf', + name: 'Chaos: assertIsInstanceOf failure', + callback: () => { + assertIsInstanceOf(testObject, Array); + }, + }); + + ctx.commands.registerCommand({ + id: 'dev.perfetto.Fail', + name: 'Chaos: fail()', + callback: () => { + fail('Intentional failure from Chaos plugin'); + }, + }); } async onTraceLoad(ctx: Trace): Promise<void> {
diff --git a/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_details_panel.ts b/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_details_panel.ts index ff01e91..8664e2a 100644 --- a/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_details_panel.ts +++ b/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_details_panel.ts
@@ -14,7 +14,7 @@ import m from 'mithril'; -import {assertFalse} from '../../base/logging'; +import {fail} from '../../base/logging'; import {createPerfettoTable} from '../../trace_processor/sql_utils'; import {extensions} from '../../components/extensions'; import {time} from '../../base/time'; @@ -508,11 +508,9 @@ case ProfileType.NATIVE_HEAP_PROFILE: return 'Native heap profile'; case ProfileType.PERF_SAMPLE: - assertFalse(false, 'Perf sample not supported'); - return 'Impossible'; + fail('Perf sample not supported'); case ProfileType.INSTRUMENTS_SAMPLE: - assertFalse(false, 'Instruments sample not supported'); - return 'Impossible'; + fail('Instruments sample not supported'); } }