├── .github ├── FUNDING.yml └── workflows │ └── workflow.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── documentation └── asset │ ├── logo-social-preview.png │ ├── ts-evaluator-logo.png │ └── ts-evaluator-logo.svg ├── eslint.config.js ├── package.json ├── pnpm-lock.yaml ├── sandhog.config.js ├── src ├── index.ts ├── interpreter │ ├── environment │ │ ├── browser │ │ │ ├── browser-globals.ts │ │ │ └── lib │ │ │ │ └── raf.ts │ │ ├── create-sanitized-environment.ts │ │ ├── ecma │ │ │ └── ecma-globals.ts │ │ ├── environment-preset-kind.ts │ │ ├── i-create-sanitized-environment-options.ts │ │ ├── i-environment.ts │ │ └── node │ │ │ ├── node-built-ins-and-globals.ts │ │ │ ├── node-cjs-globals.ts │ │ │ └── node-esm-globals.ts │ ├── error │ │ ├── async-iterator-not-supported-error │ │ │ ├── async-iterator-not-supported-error.ts │ │ │ └── i-async-iterator-not-supported-error-options.ts │ │ ├── evaluation-error │ │ │ ├── evaluation-error-intent.ts │ │ │ ├── evaluation-error.ts │ │ │ └── i-evaluation-error-options.ts │ │ ├── missing-catch-or-finally-after-try-error │ │ │ ├── i-missing-catch-or-finally-after-try-error-options.ts │ │ │ └── missing-catch-or-finally-after-try-error.ts │ │ ├── module-not-found-error │ │ │ ├── i-module-not-found-error-options.ts │ │ │ └── module-not-found-error.ts │ │ ├── not-callable-error │ │ │ ├── i-not-callable-error-options.ts │ │ │ └── not-callable-error.ts │ │ ├── policy-error │ │ │ ├── i-policy-error-options.ts │ │ │ ├── io-error │ │ │ │ ├── i-io-error-options.ts │ │ │ │ └── io-error.ts │ │ │ ├── max-op-duration-exceeded-error │ │ │ │ ├── i-max-op-duration-exceeded-error-options.ts │ │ │ │ └── max-op-duration-exceeded-error.ts │ │ │ ├── max-ops-exceeded-error │ │ │ │ ├── i-max-ops-exceeded-error-options.ts │ │ │ │ └── max-ops-exceeded-error.ts │ │ │ ├── network-error │ │ │ │ ├── i-network-error-options.ts │ │ │ │ └── network-error.ts │ │ │ ├── non-deterministic-error │ │ │ │ ├── i-non-deterministic-error-options.ts │ │ │ │ └── non-deterministic-error.ts │ │ │ ├── policy-error.ts │ │ │ └── process-error │ │ │ │ ├── i-process-error-options.ts │ │ │ │ └── process-error.ts │ │ ├── undefined-identifier-error │ │ │ ├── i-undefined-identifier-error-options.ts │ │ │ └── undefined-identifier-error.ts │ │ ├── undefined-left-value-error │ │ │ ├── i-undefined-left-value-error-options.ts │ │ │ └── undefined-left-value-error.ts │ │ ├── unexpected-node-error │ │ │ ├── i-unexpected-node-error-options.ts │ │ │ └── unexpected-node-error.ts │ │ └── unexpected-syntax-error │ │ │ ├── i-unexpected-syntax-error-options.ts │ │ │ └── unexpected-syntax-error.ts │ ├── evaluate-options.ts │ ├── evaluate-result.ts │ ├── evaluate.ts │ ├── evaluator │ │ ├── evaluate-array-binding-pattern.ts │ │ ├── evaluate-array-literal-expression.ts │ │ ├── evaluate-arrow-function-expression.ts │ │ ├── evaluate-as-expression.ts │ │ ├── evaluate-await-expression.ts │ │ ├── evaluate-big-int-literal.ts │ │ ├── evaluate-binary-expression.ts │ │ ├── evaluate-binding-element.ts │ │ ├── evaluate-binding-name.ts │ │ ├── evaluate-block.ts │ │ ├── evaluate-boolean-literal.ts │ │ ├── evaluate-break-statement.ts │ │ ├── evaluate-call-expression.ts │ │ ├── evaluate-case-block.ts │ │ ├── evaluate-case-clause.ts │ │ ├── evaluate-catch-clause.ts │ │ ├── evaluate-class-declaration.ts │ │ ├── evaluate-class-expression.ts │ │ ├── evaluate-computed-property-name.ts │ │ ├── evaluate-conditional-expression.ts │ │ ├── evaluate-constructor-declaration.ts │ │ ├── evaluate-continue-statement.ts │ │ ├── evaluate-declaration.ts │ │ ├── evaluate-decorator.ts │ │ ├── evaluate-default-clause.ts │ │ ├── evaluate-element-access-expression.ts │ │ ├── evaluate-enum-declaration.ts │ │ ├── evaluate-enum-member.ts │ │ ├── evaluate-expression-statement.ts │ │ ├── evaluate-expression.ts │ │ ├── evaluate-for-in-statement.ts │ │ ├── evaluate-for-of-statement.ts │ │ ├── evaluate-for-statement.ts │ │ ├── evaluate-function-declaration.ts │ │ ├── evaluate-function-expression.ts │ │ ├── evaluate-get-accessor-declaration.ts │ │ ├── evaluate-identifier.ts │ │ ├── evaluate-if-statement.ts │ │ ├── evaluate-import-clause.ts │ │ ├── evaluate-import-declaration.ts │ │ ├── evaluate-import-equals-declaration.ts │ │ ├── evaluate-import-specifier.ts │ │ ├── evaluate-interface-declaration.ts │ │ ├── evaluate-meta-property.ts │ │ ├── evaluate-method-declaration.ts │ │ ├── evaluate-module-declaration.ts │ │ ├── evaluate-namespace-import.ts │ │ ├── evaluate-new-expression.ts │ │ ├── evaluate-node-with-argument.ts │ │ ├── evaluate-node-with-value.ts │ │ ├── evaluate-node.ts │ │ ├── evaluate-non-null-expression.ts │ │ ├── evaluate-null-literal.ts │ │ ├── evaluate-numeric-literal.ts │ │ ├── evaluate-object-binding-pattern.ts │ │ ├── evaluate-object-literal-expression.ts │ │ ├── evaluate-omitted-expression.ts │ │ ├── evaluate-parameter-declaration.ts │ │ ├── evaluate-parameter-declarations.ts │ │ ├── evaluate-parenthesized-expression.ts │ │ ├── evaluate-postfix-unary-expression.ts │ │ ├── evaluate-prefix-unary-expression.ts │ │ ├── evaluate-property-access-expression.ts │ │ ├── evaluate-property-assignment.ts │ │ ├── evaluate-property-declaration.ts │ │ ├── evaluate-property-name.ts │ │ ├── evaluate-regular-expression-literal.ts │ │ ├── evaluate-return-statement.ts │ │ ├── evaluate-set-accessor-declaration.ts │ │ ├── evaluate-shorthand-property-assignment.ts │ │ ├── evaluate-source-file-as-namespace-object.ts │ │ ├── evaluate-spread-assignment.ts │ │ ├── evaluate-spread-element.ts │ │ ├── evaluate-statement.ts │ │ ├── evaluate-string-literal.ts │ │ ├── evaluate-super-expression.ts │ │ ├── evaluate-switch-statement.ts │ │ ├── evaluate-template-expression.ts │ │ ├── evaluate-this-expression.ts │ │ ├── evaluate-throw-statement.ts │ │ ├── evaluate-try-statement.ts │ │ ├── evaluate-type-alias-declaration.ts │ │ ├── evaluate-type-assertion-expression.ts │ │ ├── evaluate-type-of-expression.ts │ │ ├── evaluate-variable-declaration-list.ts │ │ ├── evaluate-variable-declaration.ts │ │ ├── evaluate-variable-statement.ts │ │ ├── evaluate-void-expression.ts │ │ ├── evaluate-while-statement.ts │ │ ├── evaluator-options.ts │ │ ├── node-evaluator │ │ │ ├── create-node-evaluator.ts │ │ │ ├── i-create-node-evaluator-options.ts │ │ │ └── node-evaluator.ts │ │ └── simple │ │ │ ├── evaluate-simple-literal-result.ts │ │ │ └── evaluate-simple-literal.ts │ ├── lexical-environment │ │ ├── clone-lexical-environment.ts │ │ ├── get-dot-path-from-node.ts │ │ ├── i-create-lexical-environment-options.ts │ │ ├── i-set-in-lexical-environment-options.ts │ │ └── lexical-environment.ts │ ├── literal │ │ └── literal.ts │ ├── logger │ │ ├── log-level.ts │ │ └── logger.ts │ ├── policy │ │ ├── console │ │ │ ├── console-map.ts │ │ │ └── is-console-operation.ts │ │ ├── evaluate-policy.ts │ │ ├── io │ │ │ ├── io-map.ts │ │ │ ├── is-io-read.ts │ │ │ └── is-io-write.ts │ │ ├── is-trap-condition-met.ts │ │ ├── module │ │ │ └── built-in-module-map.ts │ │ ├── network │ │ │ ├── is-network-operation.ts │ │ │ └── network-map.ts │ │ ├── nondeterministic │ │ │ ├── is-nondeterministic.ts │ │ │ └── nondeterministic-map.ts │ │ ├── policy-trap-kind.ts │ │ ├── process │ │ │ ├── is-process-exit-operation.ts │ │ │ ├── is-process-spawn-child-operation.ts │ │ │ └── process-map.ts │ │ └── trap-condition-map.ts │ ├── proxy │ │ ├── create-policy-proxy.ts │ │ ├── i-create-policy-proxy-options.ts │ │ └── policy-proxy-hook.ts │ ├── reporting │ │ ├── i-reporting-options.ts │ │ └── reported-error-set.ts │ ├── stack │ │ ├── stack.ts │ │ └── traversal-stack │ │ │ └── statement-traversal-stack.ts │ └── util │ │ ├── array │ │ └── ensure-array.ts │ │ ├── break │ │ └── break-symbol.ts │ │ ├── class │ │ ├── generate-class-declaration.ts │ │ └── i-generate-class-declaration-options.ts │ │ ├── continue │ │ └── continue-symbol.ts │ │ ├── declaration │ │ ├── get-declaration-name.ts │ │ └── is-declaration.ts │ │ ├── descriptor │ │ └── merge-descriptors.ts │ │ ├── expression │ │ ├── expression-contains-super-keyword.ts │ │ └── is-expression.ts │ │ ├── flags │ │ └── is-var-declaration.ts │ │ ├── function │ │ └── is-bind-call-apply.ts │ │ ├── iterable │ │ └── is-iterable.ts │ │ ├── loader │ │ ├── optional-peer-dependency-loader.ts │ │ └── require-module.ts │ │ ├── modifier │ │ └── has-modifier.ts │ │ ├── module │ │ ├── get-implementation-for-declaration-within-declaration-file.ts │ │ └── get-resolved-module-name.ts │ │ ├── node │ │ ├── find-nearest-parent-node-of-kind.ts │ │ ├── get-inner-node.ts │ │ ├── is-boolean-literal.ts │ │ ├── is-node.ts │ │ ├── is-null-literal.ts │ │ ├── is-super-expression.ts │ │ ├── is-this-expression.ts │ │ └── modifier-util.ts │ │ ├── object │ │ └── subtract.ts │ │ ├── path │ │ └── generate-random-path.ts │ │ ├── print │ │ └── print-with-deep-removed-properties.ts │ │ ├── proxy │ │ └── can-be-observed.ts │ │ ├── reporting │ │ └── report-error.ts │ │ ├── return │ │ └── return-symbol.ts │ │ ├── statement │ │ └── is-statement.ts │ │ ├── static │ │ └── in-static-context.ts │ │ ├── super │ │ └── super-symbol.ts │ │ ├── syntax-kind │ │ └── stringify-syntax-kind.ts │ │ ├── this │ │ └── this-symbol.ts │ │ ├── try │ │ └── try-symbol.ts │ │ └── tslib │ │ └── tslib-util.ts └── type │ ├── file-system.ts │ ├── jsdom.ts │ └── ts.ts ├── test ├── array-binding-pattern │ └── array-binding-pattern.test.ts ├── array-literal-expression │ └── array-literal-expression.test.ts ├── assignments │ └── assignments.test.ts ├── await-expression │ └── await-expression.test.ts ├── binary-expression │ └── binary-expression.test.ts ├── call-expression │ └── call-bind-apply.test.ts ├── class-declaration │ └── class-declaration.test.ts ├── class-expression │ └── class-expression.test.ts ├── conditional-expression │ └── conditional-expression.test.ts ├── decorator │ └── decorator.test.ts ├── enum-declaration │ └── enum-declaration.test.ts ├── environment │ ├── browser.test.ts │ ├── node-cjs.test.ts │ └── node-esm.test.ts ├── error-handling │ └── error-handling.test.ts ├── for-in │ └── for-in.test.ts ├── for-of │ └── for-of.test.ts ├── for │ └── for.test.ts ├── function-declaration │ ├── arithmetic.test.ts │ └── recursion.test.ts ├── get-accessor-declaration │ └── get-accessor-declaration.test.ts ├── hoisting │ └── hoisting.test.ts ├── import-declaration │ └── import-declaration.test.ts ├── interface-declaration │ └── interface-declaration.test.ts ├── logical-assignment │ └── logical-assignment.test.ts ├── method-declaration │ └── method-declaration.test.ts ├── new-target │ └── new-target.test.ts ├── nullish-coalescing │ └── nullish-coalescing.test.ts ├── object-binding-pattern │ └── object-binding-pattern.test.ts ├── object-literal-expression │ └── object-literal-expression.test.ts ├── optional-chaining │ └── optional-chaining.test.ts ├── policy │ └── policy.test.ts ├── postfix-unary-expression │ └── postfix-unary-expression.test.ts ├── property-declaration │ └── property-declaration.test.ts ├── setup │ ├── cached-fs.ts │ ├── cached-worker.ts │ ├── create-compiler-host.ts │ ├── create-virtual-file-system.ts │ ├── execute-program.ts │ ├── test-context.ts │ ├── test-file.ts │ ├── test-result.ts │ ├── test-runner.ts │ └── test-setup.ts ├── spread-assignment │ └── spread-assignment.test.ts ├── spread-element │ └── spread-element.test.ts ├── switch │ └── switch.test.ts ├── try-catch │ └── try-catch.test.ts ├── type-alias-declaration │ └── type-alias-declaration.test.ts ├── type-of-expression │ └── type-of-expression.test.ts ├── void-expression │ └── void-expression.test.ts └── while │ └── while.test.ts └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: wessberg 2 | patreon: wessberg 3 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Main Workflow 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | run: 7 | name: Run 8 | 9 | runs-on: ${{ matrix.os }} 10 | 11 | strategy: 12 | matrix: 13 | os: [windows-latest, macos-latest, ubuntu-latest] 14 | node: [21, 22] 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@master 19 | 20 | - name: Setup Node.js 21 | uses: actions/setup-node@master 22 | with: 23 | node-version: ${{ matrix.node }} 24 | 25 | - name: Setup pnpm 26 | run: npm install pnpm -g 27 | 28 | - name: Install 29 | run: pnpm install 30 | 31 | - name: Lint 32 | run: pnpm run lint 33 | 34 | - name: Test 35 | run: pnpm test 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /compiled/ 2 | /.idea/ 3 | /.cache/ 4 | /.vscode/ 5 | *.log 6 | /test-app/ 7 | /logs/ 8 | npm-debug.log* 9 | /lib-cov/ 10 | /coverage/ 11 | /.nyc_output/ 12 | /.grunt/ 13 | *.7z 14 | *.dmg 15 | *.gz 16 | *.iso 17 | *.jar 18 | *.rar 19 | *.tar 20 | *.zip 21 | .tgz 22 | .env 23 | .DS_Store 24 | .DS_Store? 25 | ._* 26 | .Spotlight-V100 27 | .Trashes 28 | ehthumbs.db 29 | Thumbs.db 30 | *.pem 31 | *.p12 32 | *.crt 33 | *.csr 34 | /node_modules/ 35 | /dist/ 36 | package-lock.json -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged --quiet -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | You are more than welcome to contribute to `ts-evaluator` in any way you please, including: 2 | 3 | - Updating documentation. 4 | - Fixing spelling and grammar 5 | - Adding tests 6 | - Fixing issues and suggesting new features 7 | - Blogging, tweeting, and creating tutorials about `ts-evaluator` 8 | - Reaching out to [@FredWessberg](https://twitter.com/FredWessberg) on Twitter 9 | - Submit an issue or a Pull Request 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2024 [Frederik Wessberg](mailto:frederikwessberg@hotmail.com) ([@FredWessberg](https://twitter.com/FredWessberg)) ([Website](https://github.com/wessberg)) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /documentation/asset/logo-social-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wessberg/ts-evaluator/39f61d531fff2542234df9127644d614119d07c5/documentation/asset/logo-social-preview.png -------------------------------------------------------------------------------- /documentation/asset/ts-evaluator-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wessberg/ts-evaluator/39f61d531fff2542234df9127644d614119d07c5/documentation/asset/ts-evaluator-logo.png -------------------------------------------------------------------------------- /documentation/asset/ts-evaluator-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | evaluator 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import shared from "@wessberg/ts-config/eslint.config.js"; 2 | 3 | export default [ 4 | ...shared, 5 | { 6 | rules: { 7 | "@typescript-eslint/no-unsafe-assignment": "off", 8 | "@typescript-eslint/no-unsafe-return": "off", 9 | "@typescript-eslint/no-unsafe-argument": "off", 10 | "@typescript-eslint/no-unsafe-call": "off", 11 | "@typescript-eslint/no-unsafe-member-access": "off", 12 | "@typescript-eslint/require-await": "off", 13 | "@typescript-eslint/no-unnecessary-condition": "off" 14 | } 15 | } 16 | ]; 17 | -------------------------------------------------------------------------------- /sandhog.config.js: -------------------------------------------------------------------------------- 1 | import baseConfig from "@wessberg/ts-config/sandhog.config.js"; 2 | 3 | export default { 4 | ...baseConfig, 5 | logo: { 6 | url: "https://raw.githubusercontent.com/wessberg/ts-evaluator/master/documentation/asset/ts-evaluator-logo.png", 7 | height: 120 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export {evaluate} from "./interpreter/evaluate.js"; 2 | export type {EvaluateResult} from "./interpreter/evaluate-result.js"; 3 | export type {EvaluateOptions} from "./interpreter/evaluate-options.js"; 4 | 5 | // Logging 6 | export * from "./interpreter/logger/log-level.js"; 7 | 8 | // Environment 9 | export type * from "./interpreter/environment/environment-preset-kind.js"; 10 | export type * from "./interpreter/environment/i-environment.js"; 11 | 12 | // Errors 13 | export * from "./interpreter/error/evaluation-error/evaluation-error.js"; 14 | export * from "./interpreter/error/missing-catch-or-finally-after-try-error/missing-catch-or-finally-after-try-error.js"; 15 | export * from "./interpreter/error/module-not-found-error/module-not-found-error.js"; 16 | export * from "./interpreter/error/not-callable-error/not-callable-error.js"; 17 | export * from "./interpreter/error/policy-error/policy-error.js"; 18 | export * from "./interpreter/error/undefined-identifier-error/undefined-identifier-error.js"; 19 | export * from "./interpreter/error/undefined-left-value-error/undefined-left-value-error.js"; 20 | export * from "./interpreter/error/unexpected-syntax-error/unexpected-syntax-error.js"; 21 | export * from "./interpreter/error/unexpected-node-error/unexpected-node-error.js"; 22 | export * from "./interpreter/error/policy-error/io-error/io-error.js"; 23 | export * from "./interpreter/error/policy-error/max-ops-exceeded-error/max-ops-exceeded-error.js"; 24 | export * from "./interpreter/error/policy-error/max-op-duration-exceeded-error/max-op-duration-exceeded-error.js"; 25 | export * from "./interpreter/error/policy-error/network-error/network-error.js"; 26 | export * from "./interpreter/error/policy-error/non-deterministic-error/non-deterministic-error.js"; 27 | export * from "./interpreter/error/policy-error/process-error/process-error.js"; 28 | 29 | // Reporting 30 | export type {BindingReportCallback, IReportingOptions, ReportingOptions} from "./interpreter/reporting/i-reporting-options.js"; 31 | -------------------------------------------------------------------------------- /src/interpreter/environment/browser/browser-globals.ts: -------------------------------------------------------------------------------- 1 | import {rafImplementation} from "./lib/raf.js"; 2 | import {loadJsdom} from "../../util/loader/optional-peer-dependency-loader.js"; 3 | import {subtract} from "../../util/object/subtract.js"; 4 | import {ECMA_GLOBALS} from "../ecma/ecma-globals.js"; 5 | 6 | export const BROWSER_GLOBALS = () => { 7 | const {JSDOM} = loadJsdom(true); 8 | const {window} = new JSDOM("", {url: "https://example.com"}); 9 | 10 | const ecmaGlobals = ECMA_GLOBALS(); 11 | 12 | // Add requestAnimationFrame/cancelAnimationFrame if missing 13 | if (window.requestAnimationFrame == null) { 14 | const raf = rafImplementation(window as unknown as Window & typeof globalThis); 15 | Object.defineProperties(window, Object.getOwnPropertyDescriptors(raf)); 16 | } 17 | 18 | // Add all missing Ecma Globals to the JSDOM window 19 | const missingEcmaGlobals = subtract(ecmaGlobals, window); 20 | if (Object.keys(missingEcmaGlobals).length > 0) { 21 | Object.defineProperties(window, Object.getOwnPropertyDescriptors(ecmaGlobals)); 22 | } 23 | 24 | return window; 25 | }; 26 | -------------------------------------------------------------------------------- /src/interpreter/environment/browser/lib/raf.ts: -------------------------------------------------------------------------------- 1 | export interface IRafImplementationNamespace { 2 | requestAnimationFrame(callback: FrameRequestCallback): number; 3 | cancelAnimationFrame(handle: number): void; 4 | } 5 | 6 | /** 7 | * Returns an object containing the properties that are relevant to 'requestAnimationFrame' and 'requestIdleCallback' 8 | */ 9 | export function rafImplementation(global: typeof window): IRafImplementationNamespace { 10 | let lastTime = 0; 11 | 12 | const _requestAnimationFrame = function requestAnimationFrame(callback: FrameRequestCallback): number { 13 | const currTime = new Date().getTime(); 14 | 15 | const timeToCall = Math.max(0, 16 - (currTime - lastTime)); 16 | 17 | const id = global.setTimeout(function () { 18 | callback(currTime + timeToCall); 19 | }, timeToCall); 20 | 21 | lastTime = currTime + timeToCall; 22 | 23 | return id; 24 | }; 25 | 26 | const _cancelAnimationFrame = function cancelAnimationFrame(id: number): void { 27 | clearTimeout(id); 28 | }; 29 | 30 | return { 31 | requestAnimationFrame: _requestAnimationFrame, 32 | cancelAnimationFrame: _cancelAnimationFrame 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/interpreter/environment/environment-preset-kind.ts: -------------------------------------------------------------------------------- 1 | export type EnvironmentPresetKind = "NONE" | "ECMA" | "BROWSER" | "NODE" | "NODE_CJS" | "NODE_ESM"; 2 | -------------------------------------------------------------------------------- /src/interpreter/environment/i-create-sanitized-environment-options.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatePolicySanitized} from "../policy/evaluate-policy.js"; 2 | import type {IndexLiteral} from "../literal/literal.js"; 3 | 4 | export interface ICreateSanitizedEnvironmentOptions { 5 | policy: EvaluatePolicySanitized; 6 | env: IndexLiteral; 7 | } 8 | -------------------------------------------------------------------------------- /src/interpreter/environment/i-environment.ts: -------------------------------------------------------------------------------- 1 | import type {EnvironmentPresetKind} from "./environment-preset-kind.js"; 2 | import type {LexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 3 | 4 | export interface IEnvironment { 5 | preset: EnvironmentPresetKind; 6 | extra: LexicalEnvironment["env"]; 7 | } 8 | -------------------------------------------------------------------------------- /src/interpreter/environment/node/node-built-ins-and-globals.ts: -------------------------------------------------------------------------------- 1 | import type {BuiltInModuleMap} from "../../policy/module/built-in-module-map.js"; 2 | 3 | export type NodeBuiltInsAndGlobals = BuiltInModuleMap & typeof global; 4 | -------------------------------------------------------------------------------- /src/interpreter/environment/node/node-cjs-globals.ts: -------------------------------------------------------------------------------- 1 | import {mergeDescriptors} from "../../util/descriptor/merge-descriptors.js"; 2 | import {ECMA_GLOBALS} from "../ecma/ecma-globals.js"; 3 | import {subtract} from "../../util/object/subtract.js"; 4 | import path from "crosspath"; 5 | import {requireModule} from "../../util/loader/require-module.js"; 6 | 7 | export const NODE_CJS_GLOBALS = () => { 8 | const ecmaGlobals = ECMA_GLOBALS(); 9 | const merged = mergeDescriptors(subtract(global, ecmaGlobals), ecmaGlobals, { 10 | require: requireModule, 11 | process, 12 | __dirname: (fileName: string) => path.native.normalize(path.native.dirname(fileName)), 13 | __filename: (fileName: string) => path.native.normalize(fileName) 14 | }); 15 | 16 | Object.defineProperties(merged, { 17 | global: { 18 | get(): typeof merged { 19 | return merged; 20 | } 21 | }, 22 | globalThis: { 23 | get(): typeof merged { 24 | return merged; 25 | } 26 | } 27 | }); 28 | 29 | return merged; 30 | }; 31 | -------------------------------------------------------------------------------- /src/interpreter/environment/node/node-esm-globals.ts: -------------------------------------------------------------------------------- 1 | import {mergeDescriptors} from "../../util/descriptor/merge-descriptors.js"; 2 | import {ECMA_GLOBALS} from "../ecma/ecma-globals.js"; 3 | import {subtract} from "../../util/object/subtract.js"; 4 | import path from "crosspath"; 5 | export const NODE_ESM_GLOBALS = () => { 6 | const ecmaGlobals = ECMA_GLOBALS(); 7 | const merged = mergeDescriptors(subtract(global, ecmaGlobals), ecmaGlobals, { 8 | import: { 9 | meta: { 10 | url: (fileName: string) => { 11 | const normalized = path.normalize(fileName); 12 | return `file:///${normalized.startsWith(`/`) ? normalized.slice(1) : normalized}`; 13 | } 14 | } 15 | }, 16 | process 17 | }); 18 | 19 | Object.defineProperties(merged, { 20 | global: { 21 | get(): typeof merged { 22 | return merged; 23 | } 24 | }, 25 | globalThis: { 26 | get(): typeof merged { 27 | return merged; 28 | } 29 | } 30 | }); 31 | 32 | return merged; 33 | }; 34 | -------------------------------------------------------------------------------- /src/interpreter/error/async-iterator-not-supported-error/async-iterator-not-supported-error.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | import {EvaluationError} from "../evaluation-error/evaluation-error.js"; 3 | import type {IAsyncIteratorNotSupportedErrorOptions} from "./i-async-iterator-not-supported-error-options.js"; 4 | 5 | /** 6 | * An Error that can be thrown when an async iteration operation is attempted 7 | */ 8 | export class AsyncIteratorNotSupportedError extends EvaluationError { 9 | constructor({message = `It is not possible to evaluate an async iterator'`, typescript, environment}: IAsyncIteratorNotSupportedErrorOptions) { 10 | super({ 11 | message, 12 | environment, 13 | node: typescript.factory?.createEmptyStatement() ?? (typescript as unknown as TS.NodeFactory).createEmptyStatement() 14 | }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/interpreter/error/async-iterator-not-supported-error/i-async-iterator-not-supported-error-options.ts: -------------------------------------------------------------------------------- 1 | import type {IEvaluationErrorOptions} from "../evaluation-error/i-evaluation-error-options.js"; 2 | import type {TS} from "../../../type/ts.js"; 3 | 4 | export interface IAsyncIteratorNotSupportedErrorOptions extends Omit { 5 | typescript: typeof TS; 6 | } 7 | -------------------------------------------------------------------------------- /src/interpreter/error/evaluation-error/evaluation-error-intent.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | import type {NextEvaluatorOptions} from "../../evaluator/evaluator-options.js"; 3 | import type {EvaluationError} from "./evaluation-error.js"; 4 | 5 | type EvaluationErrorIntentCallback = (node: TS.Node, options: NextEvaluatorOptions) => T; 6 | 7 | export class EvaluationErrorIntent { 8 | constructor(private readonly intent: EvaluationErrorIntentCallback) {} 9 | construct(node: TS.Node, options: NextEvaluatorOptions): T { 10 | return this.intent(node, options); 11 | } 12 | } 13 | 14 | export function isEvaluationErrorIntent(item: unknown): item is EvaluationErrorIntent { 15 | return typeof item === "object" && item != null && item instanceof EvaluationErrorIntent; 16 | } 17 | 18 | export function maybeThrow(node: TS.Node, options: NextEvaluatorOptions, value: Value | EvaluationErrorIntent): Value | EvaluationError { 19 | return isEvaluationErrorIntent(value) ? options.throwError(value.construct(node, options)) : value; 20 | } 21 | -------------------------------------------------------------------------------- /src/interpreter/error/evaluation-error/evaluation-error.ts: -------------------------------------------------------------------------------- 1 | import type {IEvaluationErrorOptions} from "./i-evaluation-error-options.js"; 2 | import type {TS} from "../../../type/ts.js"; 3 | import type {LexicalEnvironment} from "../../lexical-environment/lexical-environment.js"; 4 | 5 | export type ThrowError = (error: EvaluationError) => EvaluationError; 6 | 7 | /** 8 | * A Base class for EvaluationErrors 9 | */ 10 | export class EvaluationError extends Error { 11 | /** 12 | * The node that caused or thew the error 13 | */ 14 | readonly node: TS.Node; 15 | readonly environment: LexicalEnvironment; 16 | 17 | constructor({node, environment, message}: IEvaluationErrorOptions) { 18 | super(message); 19 | Error.captureStackTrace(this, this.constructor); 20 | this.node = node; 21 | this.environment = environment; 22 | } 23 | } 24 | 25 | export function isEvaluationError(item: unknown): item is EvaluationError { 26 | return typeof item === "object" && item != null && item instanceof EvaluationError; 27 | } 28 | -------------------------------------------------------------------------------- /src/interpreter/error/evaluation-error/i-evaluation-error-options.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | import type {LexicalEnvironment} from "../../lexical-environment/lexical-environment.js"; 3 | 4 | export interface IEvaluationErrorOptions { 5 | node: TS.Node; 6 | environment: LexicalEnvironment; 7 | message?: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/interpreter/error/missing-catch-or-finally-after-try-error/i-missing-catch-or-finally-after-try-error-options.ts: -------------------------------------------------------------------------------- 1 | import type {IEvaluationErrorOptions} from "../evaluation-error/i-evaluation-error-options.js"; 2 | import type {TS} from "../../../type/ts.js"; 3 | 4 | export interface IMissingCatchOrFinallyAfterTryErrorOptions extends IEvaluationErrorOptions { 5 | node: TS.TryStatement; 6 | } 7 | -------------------------------------------------------------------------------- /src/interpreter/error/missing-catch-or-finally-after-try-error/missing-catch-or-finally-after-try-error.ts: -------------------------------------------------------------------------------- 1 | import {EvaluationError} from "../evaluation-error/evaluation-error.js"; 2 | import type {IMissingCatchOrFinallyAfterTryErrorOptions} from "./i-missing-catch-or-finally-after-try-error-options.js"; 3 | import type {TS} from "../../../type/ts.js"; 4 | 5 | /** 6 | * An Error that can be thrown when a TryStatement is encountered without neither a catch {...} nor a finally {...} block 7 | */ 8 | export class MissingCatchOrFinallyAfterTryError extends EvaluationError { 9 | /** 10 | * The TryStatement that lacks a catch/finally block 11 | */ 12 | declare readonly node: TS.TryStatement; 13 | 14 | constructor({node, environment, message = `Missing catch or finally after try`}: IMissingCatchOrFinallyAfterTryErrorOptions) { 15 | super({node, environment, message}); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/interpreter/error/module-not-found-error/i-module-not-found-error-options.ts: -------------------------------------------------------------------------------- 1 | import type {IEvaluationErrorOptions} from "../evaluation-error/i-evaluation-error-options.js"; 2 | 3 | export interface IModuleNotFoundErrorOptions extends IEvaluationErrorOptions { 4 | path: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/interpreter/error/module-not-found-error/module-not-found-error.ts: -------------------------------------------------------------------------------- 1 | import {EvaluationError} from "../evaluation-error/evaluation-error.js"; 2 | import type {IModuleNotFoundErrorOptions} from "./i-module-not-found-error-options.js"; 3 | 4 | /** 5 | * An Error that can be thrown when a moduleSpecifier couldn't be resolved 6 | */ 7 | export class ModuleNotFoundError extends EvaluationError { 8 | /** 9 | * The path/moduleName that could not be resolved 10 | */ 11 | readonly path: string; 12 | 13 | constructor({path, node, environment, message = `Module '${path}' could not be resolved'`}: IModuleNotFoundErrorOptions) { 14 | super({message, environment, node}); 15 | this.path = path; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/interpreter/error/not-callable-error/i-not-callable-error-options.ts: -------------------------------------------------------------------------------- 1 | import type {IEvaluationErrorOptions} from "../evaluation-error/i-evaluation-error-options.js"; 2 | import type {Literal} from "../../literal/literal.js"; 3 | 4 | export interface INotCallableErrorOptions extends IEvaluationErrorOptions { 5 | value: Literal; 6 | } 7 | -------------------------------------------------------------------------------- /src/interpreter/error/not-callable-error/not-callable-error.ts: -------------------------------------------------------------------------------- 1 | import {EvaluationError} from "../evaluation-error/evaluation-error.js"; 2 | import type {INotCallableErrorOptions} from "./i-not-callable-error-options.js"; 3 | import type {Literal} from "../../literal/literal.js"; 4 | import {stringifyLiteral} from "../../literal/literal.js"; 5 | 6 | /** 7 | * An Error that can be thrown when a value is attempted to be called, but isn't callable 8 | */ 9 | export class NotCallableError extends EvaluationError { 10 | /** 11 | * The non-callable value 12 | */ 13 | readonly value: Literal; 14 | 15 | constructor({value, node, environment, message = `${stringifyLiteral(value)} is not a function'`}: INotCallableErrorOptions) { 16 | super({message, environment, node}); 17 | this.value = value; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/interpreter/error/policy-error/i-policy-error-options.ts: -------------------------------------------------------------------------------- 1 | import type {IEvaluationErrorOptions} from "../evaluation-error/i-evaluation-error-options.js"; 2 | import type {EvaluatePolicySanitized} from "../../policy/evaluate-policy.js"; 3 | 4 | export interface IPolicyErrorOptions extends IEvaluationErrorOptions { 5 | violation: keyof EvaluatePolicySanitized; 6 | } 7 | -------------------------------------------------------------------------------- /src/interpreter/error/policy-error/io-error/i-io-error-options.ts: -------------------------------------------------------------------------------- 1 | import type {IEvaluationErrorOptions} from "../../evaluation-error/i-evaluation-error-options.js"; 2 | import type {EvaluateIOPolicy} from "../../../policy/evaluate-policy.js"; 3 | 4 | export interface IIoErrorOptions extends IEvaluationErrorOptions { 5 | kind: keyof EvaluateIOPolicy; 6 | } 7 | -------------------------------------------------------------------------------- /src/interpreter/error/policy-error/io-error/io-error.ts: -------------------------------------------------------------------------------- 1 | import type {IIoErrorOptions} from "./i-io-error-options.js"; 2 | import {PolicyError} from "../policy-error.js"; 3 | import type {EvaluateIOPolicy} from "../../../policy/evaluate-policy.js"; 4 | 5 | /** 6 | * An Error that can be thrown when an IO operation is attempted to be executed that is in violation of the context policy 7 | */ 8 | export class IoError extends PolicyError { 9 | /** 10 | * The kind of IO operation that was violated 11 | */ 12 | readonly kind: keyof EvaluateIOPolicy; 13 | 14 | constructor({node, environment, kind, message = `${kind} operations are in violation of the policy`}: IIoErrorOptions) { 15 | super({violation: "io", message, environment, node}); 16 | this.kind = kind; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/interpreter/error/policy-error/max-op-duration-exceeded-error/i-max-op-duration-exceeded-error-options.ts: -------------------------------------------------------------------------------- 1 | import type {IEvaluationErrorOptions} from "../../evaluation-error/i-evaluation-error-options.js"; 2 | 3 | export interface IMaxOpDurationExceededErrorOptions extends IEvaluationErrorOptions { 4 | duration: number; 5 | } 6 | -------------------------------------------------------------------------------- /src/interpreter/error/policy-error/max-op-duration-exceeded-error/max-op-duration-exceeded-error.ts: -------------------------------------------------------------------------------- 1 | import type {IMaxOpDurationExceededErrorOptions} from "./i-max-op-duration-exceeded-error-options.js"; 2 | import {PolicyError} from "../policy-error.js"; 3 | 4 | /** 5 | * An Error that can be thrown when the maximum amount of operations dictated by the policy is exceeded 6 | */ 7 | export class MaxOpDurationExceededError extends PolicyError { 8 | /** 9 | * The total duration of an operation that was being performed before exceeding the limit 10 | */ 11 | readonly duration: number; 12 | 13 | constructor({duration, environment, node, message = `Maximum operation duration exceeded: ${duration}`}: IMaxOpDurationExceededErrorOptions) { 14 | super({violation: "maxOpDuration", message, node, environment}); 15 | this.duration = duration; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/interpreter/error/policy-error/max-ops-exceeded-error/i-max-ops-exceeded-error-options.ts: -------------------------------------------------------------------------------- 1 | import type {IEvaluationErrorOptions} from "../../evaluation-error/i-evaluation-error-options.js"; 2 | 3 | export interface IMaxOpsExceededErrorOptions extends IEvaluationErrorOptions { 4 | ops: number; 5 | } 6 | -------------------------------------------------------------------------------- /src/interpreter/error/policy-error/max-ops-exceeded-error/max-ops-exceeded-error.ts: -------------------------------------------------------------------------------- 1 | import type {IMaxOpsExceededErrorOptions} from "./i-max-ops-exceeded-error-options.js"; 2 | import {PolicyError} from "../policy-error.js"; 3 | 4 | /** 5 | * An Error that can be thrown when the maximum amount of operations dictated by the policy is exceeded 6 | */ 7 | export class MaxOpsExceededError extends PolicyError { 8 | /** 9 | * The amount of operations performed before creating this error instance 10 | */ 11 | readonly ops: number; 12 | 13 | constructor({ops, node, environment, message = `Maximum ops exceeded: ${ops}`}: IMaxOpsExceededErrorOptions) { 14 | super({violation: "maxOps", message, node, environment}); 15 | this.ops = ops; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/interpreter/error/policy-error/network-error/i-network-error-options.ts: -------------------------------------------------------------------------------- 1 | import type {IEvaluationErrorOptions} from "../../evaluation-error/i-evaluation-error-options.js"; 2 | 3 | export interface INetworkErrorOptions extends IEvaluationErrorOptions { 4 | operation: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/interpreter/error/policy-error/network-error/network-error.ts: -------------------------------------------------------------------------------- 1 | import type {INetworkErrorOptions} from "./i-network-error-options.js"; 2 | import {PolicyError} from "../policy-error.js"; 3 | 4 | /** 5 | * An Error that can be thrown when a network operation is attempted to be executed that is in violation of the context policy 6 | */ 7 | export class NetworkError extends PolicyError { 8 | /** 9 | * The kind of operation that was attempted to be performed but was in violation of the policy 10 | */ 11 | readonly operation: string; 12 | 13 | constructor({operation, node, environment, message = `The operation: '${operation}' is performing network activity. That is in violation of the policy`}: INetworkErrorOptions) { 14 | super({violation: "deterministic", message, node, environment}); 15 | 16 | this.operation = operation; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/interpreter/error/policy-error/non-deterministic-error/i-non-deterministic-error-options.ts: -------------------------------------------------------------------------------- 1 | import type {IEvaluationErrorOptions} from "../../evaluation-error/i-evaluation-error-options.js"; 2 | 3 | export interface INonDeterministicErrorOptions extends IEvaluationErrorOptions { 4 | operation: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/interpreter/error/policy-error/non-deterministic-error/non-deterministic-error.ts: -------------------------------------------------------------------------------- 1 | import type {INonDeterministicErrorOptions} from "./i-non-deterministic-error-options.js"; 2 | import {PolicyError} from "../policy-error.js"; 3 | 4 | /** 5 | * An Error that can be thrown when something nondeterministic is attempted to be evaluated and has been disallowed to be so 6 | */ 7 | export class NonDeterministicError extends PolicyError { 8 | /** 9 | * The kind of operation that was attempted to be performed but was in violation of the policy 10 | */ 11 | readonly operation: string; 12 | 13 | constructor({operation, node, environment, message = `The operation: '${operation}' is nondeterministic. That is in violation of the policy`}: INonDeterministicErrorOptions) { 14 | super({violation: "deterministic", message, node, environment}); 15 | 16 | this.operation = operation; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/interpreter/error/policy-error/policy-error.ts: -------------------------------------------------------------------------------- 1 | import {EvaluationError} from "../evaluation-error/evaluation-error.js"; 2 | import type {IPolicyErrorOptions} from "./i-policy-error-options.js"; 3 | import type {EvaluatePolicySanitized} from "../../policy/evaluate-policy.js"; 4 | 5 | /** 6 | * An Error that can be thrown when a policy is violated 7 | */ 8 | export class PolicyError extends EvaluationError { 9 | /** 10 | * The kind of policy violation encountered 11 | */ 12 | readonly violation: keyof EvaluatePolicySanitized; 13 | 14 | constructor({violation, node, environment, message}: IPolicyErrorOptions) { 15 | super({node, environment, message: `[${violation}]: ${message}`}); 16 | this.violation = violation; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/interpreter/error/policy-error/process-error/i-process-error-options.ts: -------------------------------------------------------------------------------- 1 | import type {IEvaluationErrorOptions} from "../../evaluation-error/i-evaluation-error-options.js"; 2 | import type {EvaluateProcessPolicy} from "../../../policy/evaluate-policy.js"; 3 | 4 | export interface IProcessErrorOptions extends IEvaluationErrorOptions { 5 | kind: keyof EvaluateProcessPolicy; 6 | } 7 | -------------------------------------------------------------------------------- /src/interpreter/error/policy-error/process-error/process-error.ts: -------------------------------------------------------------------------------- 1 | import type {IProcessErrorOptions} from "./i-process-error-options.js"; 2 | import {PolicyError} from "../policy-error.js"; 3 | import type {EvaluateProcessPolicy} from "../../../policy/evaluate-policy.js"; 4 | 5 | /** 6 | * An Error that can be thrown when a Process operation is attempted to be executed that is in violation of the context policy 7 | */ 8 | export class ProcessError extends PolicyError { 9 | /** 10 | * The kind of process operation that was violated 11 | */ 12 | readonly kind: keyof EvaluateProcessPolicy; 13 | 14 | constructor({kind, node, environment, message = `${kind} operations are in violation of the policy`}: IProcessErrorOptions) { 15 | super({violation: "process", message, node, environment}); 16 | this.kind = kind; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/interpreter/error/undefined-identifier-error/i-undefined-identifier-error-options.ts: -------------------------------------------------------------------------------- 1 | import type {IEvaluationErrorOptions} from "../evaluation-error/i-evaluation-error-options.js"; 2 | import type {TS} from "../../../type/ts.js"; 3 | 4 | export interface IUndefinedIdentifierErrorOptions extends IEvaluationErrorOptions { 5 | node: TS.Identifier | TS.PrivateIdentifier; 6 | } 7 | -------------------------------------------------------------------------------- /src/interpreter/error/undefined-identifier-error/undefined-identifier-error.ts: -------------------------------------------------------------------------------- 1 | import {EvaluationError} from "../evaluation-error/evaluation-error.js"; 2 | import type {IUndefinedIdentifierErrorOptions} from "./i-undefined-identifier-error-options.js"; 3 | import type {TS} from "../../../type/ts.js"; 4 | 5 | /** 6 | * An Error that can be thrown when an undefined identifier is encountered 7 | */ 8 | export class UndefinedIdentifierError extends EvaluationError { 9 | /** 10 | * The identifier that is undefined in the context that created this error 11 | */ 12 | declare readonly node: TS.Identifier | TS.PrivateIdentifier; 13 | 14 | constructor({node, environment, message = `'${node.text}' is not defined'`}: IUndefinedIdentifierErrorOptions) { 15 | super({message, environment, node}); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/interpreter/error/undefined-left-value-error/i-undefined-left-value-error-options.ts: -------------------------------------------------------------------------------- 1 | import type {IEvaluationErrorOptions} from "../evaluation-error/i-evaluation-error-options.js"; 2 | 3 | export interface IUndefinedLeftValueErrorOptions extends IEvaluationErrorOptions {} 4 | -------------------------------------------------------------------------------- /src/interpreter/error/undefined-left-value-error/undefined-left-value-error.ts: -------------------------------------------------------------------------------- 1 | import {EvaluationError} from "../evaluation-error/evaluation-error.js"; 2 | import type {IUndefinedLeftValueErrorOptions} from "./i-undefined-left-value-error-options.js"; 3 | 4 | /** 5 | * An Error that can be thrown when an undefined leftValue is encountered 6 | */ 7 | export class UndefinedLeftValueError extends EvaluationError { 8 | constructor({node, environment, message = `'No leftValue could be determined'`}: IUndefinedLeftValueErrorOptions) { 9 | super({message, environment, node}); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/interpreter/error/unexpected-node-error/i-unexpected-node-error-options.ts: -------------------------------------------------------------------------------- 1 | import type {IEvaluationErrorOptions} from "../evaluation-error/i-evaluation-error-options.js"; 2 | import type {TS} from "../../../type/ts.js"; 3 | 4 | export interface IUnexpectedNodeErrorOptions extends IEvaluationErrorOptions { 5 | typescript: typeof TS; 6 | } 7 | -------------------------------------------------------------------------------- /src/interpreter/error/unexpected-node-error/unexpected-node-error.ts: -------------------------------------------------------------------------------- 1 | import {EvaluationError} from "../evaluation-error/evaluation-error.js"; 2 | import type {IUnexpectedNodeErrorOptions} from "./i-unexpected-node-error-options.js"; 3 | 4 | /** 5 | * An Error that can be thrown when an unexpected node is encountered 6 | */ 7 | export class UnexpectedNodeError extends EvaluationError { 8 | constructor({node, environment, typescript, message = `Unexpected Node: '${typescript.SyntaxKind[node.kind]}'`}: IUnexpectedNodeErrorOptions) { 9 | super({message, node, environment}); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/interpreter/error/unexpected-syntax-error/i-unexpected-syntax-error-options.ts: -------------------------------------------------------------------------------- 1 | import type {IEvaluationErrorOptions} from "../evaluation-error/i-evaluation-error-options.js"; 2 | 3 | export interface IUnexpectedSyntaxErrorOptions extends IEvaluationErrorOptions {} 4 | -------------------------------------------------------------------------------- /src/interpreter/error/unexpected-syntax-error/unexpected-syntax-error.ts: -------------------------------------------------------------------------------- 1 | import {EvaluationError} from "../evaluation-error/evaluation-error.js"; 2 | import type {IUnexpectedSyntaxErrorOptions} from "./i-unexpected-syntax-error-options.js"; 3 | 4 | /** 5 | * An Error that can be thrown when a certain usage is to be considered a SyntaxError 6 | */ 7 | export class UnexpectedSyntaxError extends EvaluationError { 8 | constructor({node, environment, message = `'SyntaxError'`}: IUnexpectedSyntaxErrorOptions) { 9 | super({message, environment, node}); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/interpreter/evaluate-options.ts: -------------------------------------------------------------------------------- 1 | import type {LogLevelKind} from "./logger/log-level.js"; 2 | import type {EvaluatePolicy} from "./policy/evaluate-policy.js"; 3 | import type {IEnvironment} from "./environment/i-environment.js"; 4 | import type {ReportingOptions} from "./reporting/i-reporting-options.js"; 5 | import type {TS} from "../type/ts.js"; 6 | 7 | export interface EvaluateOptions { 8 | node: TS.Statement | TS.Declaration | TS.Expression; 9 | typeChecker?: TS.TypeChecker; 10 | typescript?: typeof TS; 11 | environment?: Partial; 12 | logLevel?: LogLevelKind; 13 | policy?: Partial; 14 | reporting?: ReportingOptions; 15 | 16 | /** 17 | * A record of implementations for module specifiers that will override whatever is resolvable via 18 | * traditional require(...) evaluation. 19 | * Useful when/if you want to shim other modules inside the compilation unit contex of the evaluation, 20 | * much like local identifiers can be overridden with the `environment` option. 21 | */ 22 | moduleOverrides?: Record; 23 | } 24 | -------------------------------------------------------------------------------- /src/interpreter/evaluate-result.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluationError} from "./error/evaluation-error/evaluation-error.js"; 2 | 3 | export interface IEvaluateResultBase { 4 | success: boolean; 5 | } 6 | 7 | export interface IEvaluateSuccessResult extends IEvaluateResultBase { 8 | success: true; 9 | value: unknown; 10 | } 11 | 12 | export interface IEvaluateFailureResult extends IEvaluateResultBase { 13 | success: false; 14 | reason: EvaluationError; 15 | } 16 | 17 | export type EvaluateResult = IEvaluateSuccessResult | IEvaluateFailureResult; 18 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-array-binding-pattern.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, an ArrayBindingPattern, based on an initializer 7 | */ 8 | export function evaluateArrayBindingPattern({node, evaluate, ...options}: EvaluatorOptions, rightHandValue: Iterable): void { 9 | const iterator = rightHandValue[Symbol.iterator](); 10 | let elementsCursor = 0; 11 | 12 | while (elementsCursor < node.elements.length) { 13 | const {done, value} = iterator.next(); 14 | if (done === true) break; 15 | 16 | const nextElement = node.elements[elementsCursor++]; 17 | if (nextElement == null) continue; 18 | 19 | evaluate.nodeWithArgument(nextElement, value, options); 20 | 21 | if (options.getCurrentError() != null) { 22 | return; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-array-literal-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import {getFromLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 4 | import {isIterable} from "../util/iterable/is-iterable.js"; 5 | import type {TS} from "../../type/ts.js"; 6 | 7 | /** 8 | * Evaluates, or attempts to evaluate, a ArrayLiteralExpression 9 | */ 10 | export function evaluateArrayLiteralExpression(options: EvaluatorOptions): Literal { 11 | const {node, environment, evaluate, typescript, getCurrentError} = options; 12 | // Get the Array constructor from the realm - not that of the executing context. Otherwise, instanceof checks would fail 13 | const arrayCtor = getFromLexicalEnvironment(node, environment, "Array")!.literal as ArrayConstructor; 14 | const value: Literal[] = arrayCtor.of(); 15 | 16 | for (const element of node.elements) { 17 | const nextValue = evaluate.expression(element, options); 18 | if (getCurrentError() != null) { 19 | return; 20 | } 21 | 22 | if (typescript.isSpreadElement(element) && isIterable(nextValue)) { 23 | value.push(...nextValue); 24 | } else { 25 | value.push(nextValue); 26 | } 27 | } 28 | 29 | return value; 30 | } 31 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-as-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, an AsExpression 7 | */ 8 | export function evaluateAsExpression({node, evaluate, ...options}: EvaluatorOptions): Literal { 9 | return evaluate.expression(node.expression, options); 10 | } 11 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-await-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import {MaxOpDurationExceededError} from "../error/policy-error/max-op-duration-exceeded-error/max-op-duration-exceeded-error.js"; 4 | import type {TS} from "../../type/ts.js"; 5 | 6 | /** 7 | * Evaluates, or attempts to evaluate, an AwaitExpression 8 | */ 9 | export async function evaluateAwaitExpression(options: EvaluatorOptions): Promise { 10 | const {node, environment, evaluate, policy, throwError, getCurrentError} = options; 11 | // If a maximum duration for any operation is given, set a timeout that will throw a PolicyError when and if the duration is exceeded. 12 | const timeout = 13 | policy.maxOpDuration === Infinity 14 | ? undefined 15 | : setTimeout(() => { 16 | throwError(new MaxOpDurationExceededError({duration: policy.maxOpDuration, node, environment})); 17 | }, policy.maxOpDuration); 18 | 19 | const result = evaluate.expression(node.expression, options) as Promise; 20 | 21 | // Make sure to clear the timeout if it exists to avoid throwing unnecessarily 22 | if (timeout != null) clearTimeout(timeout); 23 | 24 | if (getCurrentError() != null) { 25 | return; 26 | } 27 | 28 | // Return the evaluated result 29 | return result; 30 | } 31 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-big-int-literal.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import {getFromLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 4 | import type {TS} from "../../type/ts.js"; 5 | 6 | /** 7 | * Evaluates, or attempts to evaluate, a BigIntLiteral 8 | */ 9 | export function evaluateBigIntLiteral({node, environment}: EvaluatorOptions): Literal { 10 | // Use BigInt from the Realm instead of the executing context such that instanceof checks won't fail, etc. 11 | const _BigInt = getFromLexicalEnvironment(node, environment, "BigInt")!.literal as BigIntConstructor; 12 | 13 | // BigInt allows taking in strings, but they must appear as BigInt literals (e.g. "2n" is not allowed, but "2" is) 14 | return _BigInt(node.text.endsWith("n") ? node.text.slice(0, -1) : node.text); 15 | } 16 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-binding-name.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import {setInLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 4 | import type {TS} from "../../type/ts.js"; 5 | 6 | /** 7 | * Evaluates, or attempts to evaluate, a BindingName, based on an initializer 8 | */ 9 | export function evaluateBindingName({node, evaluate, typescript, logger, ...options}: EvaluatorOptions, rightHandValue: Literal): void { 10 | // If the declaration binds a simple identifier, bind that text to the environment 11 | if (typescript.isIdentifier(node) || typescript.isPrivateIdentifier?.(node)) { 12 | setInLexicalEnvironment({...options, node, path: node.text, value: rightHandValue, newBinding: true}); 13 | logger.logBinding(node.text, rightHandValue, "evaluateBindingName"); 14 | } else { 15 | evaluate.nodeWithArgument(node, rightHandValue, options); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-block.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {LexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 3 | import {pathInLexicalEnvironmentEquals} from "../lexical-environment/lexical-environment.js"; 4 | import {cloneLexicalEnvironment} from "../lexical-environment/clone-lexical-environment.js"; 5 | import {BREAK_SYMBOL} from "../util/break/break-symbol.js"; 6 | import {CONTINUE_SYMBOL} from "../util/continue/continue-symbol.js"; 7 | import {RETURN_SYMBOL} from "../util/return/return-symbol.js"; 8 | import {isSuperExpression} from "../util/node/is-super-expression.js"; 9 | import type {TS} from "../../type/ts.js"; 10 | 11 | /** 12 | * Evaluates, or attempts to evaluate, a Block 13 | */ 14 | export function evaluateBlock(options: EvaluatorOptions): void { 15 | const {node, environment, typescript, evaluate, getCurrentError} = options; 16 | // Prepare a lexical environment for the Block context 17 | const localLexicalEnvironment: LexicalEnvironment = cloneLexicalEnvironment(environment, node); 18 | 19 | for (let i = 0; i < node.statements.length; i++) { 20 | const statement = node.statements[i]; 21 | if (statement == null) continue; 22 | 23 | // Don't execute 'super()' within Constructor Blocks since this is handled in another level 24 | if ( 25 | typescript.isConstructorDeclaration(node.parent) && 26 | i === 0 && 27 | typescript.isExpressionStatement(statement) && 28 | typescript.isCallExpression(statement.expression) && 29 | isSuperExpression(statement.expression.expression, typescript) 30 | ) { 31 | continue; 32 | } 33 | 34 | evaluate.statement(statement, {...options, environment: localLexicalEnvironment}); 35 | if (getCurrentError() != null) break; 36 | 37 | // Check if a 'break', 'continue', or 'return' statement has been encountered, break the block 38 | if (pathInLexicalEnvironmentEquals(node, localLexicalEnvironment, true, BREAK_SYMBOL, CONTINUE_SYMBOL, RETURN_SYMBOL)) { 39 | break; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-boolean-literal.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a BooleanLiteral 7 | */ 8 | export function evaluateBooleanLiteral({node, typescript}: EvaluatorOptions>): Literal { 9 | return node.kind === typescript.SyntaxKind.TrueKeyword; 10 | } 11 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-break-statement.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import {setInLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 3 | import {BREAK_SYMBOL} from "../util/break/break-symbol.js"; 4 | import type {TS} from "../../type/ts.js"; 5 | 6 | /** 7 | * Evaluates, or attempts to evaluate, a BreakStatement 8 | */ 9 | export function evaluateBreakStatement(options: EvaluatorOptions): void { 10 | setInLexicalEnvironment({...options, path: BREAK_SYMBOL, value: true}); 11 | } 12 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-call-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import {isLazyCall} from "../literal/literal.js"; 4 | import {NotCallableError} from "../error/not-callable-error/not-callable-error.js"; 5 | import {getFromLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 6 | import {THIS_SYMBOL} from "../util/this/this-symbol.js"; 7 | import {expressionContainsSuperKeyword} from "../util/expression/expression-contains-super-keyword.js"; 8 | import type {TS} from "../../type/ts.js"; 9 | import {maybeThrow} from "../error/evaluation-error/evaluation-error-intent.js"; 10 | 11 | /** 12 | * Evaluates, or attempts to evaluate, a CallExpression 13 | */ 14 | export function evaluateCallExpression(options: EvaluatorOptions): Literal { 15 | const {node, environment, evaluate, throwError, typescript, logger, getCurrentError} = options; 16 | const evaluatedArgs: Literal[] = []; 17 | 18 | for (let i = 0; i < node.arguments.length; i++) { 19 | const argument = node.arguments[i]; 20 | if (argument == null) continue; 21 | evaluatedArgs[i] = evaluate.expression(argument, options); 22 | if (getCurrentError() != null) { 23 | return; 24 | } 25 | } 26 | 27 | // Evaluate the expression 28 | const expressionResult = evaluate.expression(node.expression, options) as CallableFunction | undefined; 29 | 30 | if (getCurrentError() != null) { 31 | return; 32 | } 33 | 34 | if (isLazyCall(expressionResult)) { 35 | const currentThisBinding = expressionContainsSuperKeyword(node.expression, typescript) ? getFromLexicalEnvironment(node, environment, THIS_SYMBOL) : undefined; 36 | const value = expressionResult.invoke(currentThisBinding != null ? currentThisBinding.literal : undefined, ...evaluatedArgs); 37 | 38 | if (getCurrentError() != null) { 39 | return; 40 | } 41 | 42 | logger.logResult(value, "CallExpression"); 43 | 44 | return value; 45 | } 46 | 47 | // Otherwise, assume that the expression still needs calling 48 | else { 49 | // Unless optional chaining is being used, throw a NotCallableError 50 | if (node.questionDotToken == null && typeof expressionResult !== "function") { 51 | return throwError(new NotCallableError({value: expressionResult, node: node.expression, environment})); 52 | } 53 | 54 | const value = typeof expressionResult !== "function" ? undefined : maybeThrow(node, options, expressionResult(...evaluatedArgs)); 55 | 56 | if (getCurrentError() != null) { 57 | return; 58 | } 59 | 60 | logger.logResult(value, "CallExpression"); 61 | return value; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-case-block.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import {pathInLexicalEnvironmentEquals, setInLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 3 | import {cloneLexicalEnvironment} from "../lexical-environment/clone-lexical-environment.js"; 4 | import {BREAK_SYMBOL} from "../util/break/break-symbol.js"; 5 | import {CONTINUE_SYMBOL} from "../util/continue/continue-symbol.js"; 6 | import {RETURN_SYMBOL} from "../util/return/return-symbol.js"; 7 | import type {Literal} from "../literal/literal.js"; 8 | import type {TS} from "../../type/ts.js"; 9 | 10 | /** 11 | * Evaluates, or attempts to evaluate, a CaseBlock, based on a switch expression 12 | */ 13 | export function evaluateCaseBlock(options: EvaluatorOptions, switchExpression: Literal): void { 14 | const {node, evaluate, environment, getCurrentError} = options; 15 | // Prepare a lexical environment for the case block 16 | const localEnvironment = cloneLexicalEnvironment(environment, node); 17 | const nextOptions = {...options, environment: localEnvironment}; 18 | // Define a new binding for a break symbol within the environment 19 | setInLexicalEnvironment({...nextOptions, path: BREAK_SYMBOL, value: false, newBinding: true}); 20 | 21 | for (const clause of node.clauses) { 22 | evaluate.nodeWithArgument(clause, switchExpression, nextOptions); 23 | 24 | if (getCurrentError() != null) { 25 | return; 26 | } 27 | 28 | // Check if a 'break', 'continue', or 'return' statement has been encountered, break the block 29 | if (pathInLexicalEnvironmentEquals(node, localEnvironment, true, BREAK_SYMBOL, CONTINUE_SYMBOL, RETURN_SYMBOL)) { 30 | break; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-case-clause.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import {pathInLexicalEnvironmentEquals} from "../lexical-environment/lexical-environment.js"; 3 | import {BREAK_SYMBOL} from "../util/break/break-symbol.js"; 4 | import {CONTINUE_SYMBOL} from "../util/continue/continue-symbol.js"; 5 | import {RETURN_SYMBOL} from "../util/return/return-symbol.js"; 6 | import type {Literal} from "../literal/literal.js"; 7 | import type {TS} from "../../type/ts.js"; 8 | 9 | /** 10 | * Evaluates, or attempts to evaluate, a CaseClause, based on a switch expression 11 | */ 12 | export function evaluateCaseClause({node, evaluate, ...options}: EvaluatorOptions, switchExpression: Literal): void { 13 | const {getCurrentError} = options; 14 | const expressionResult = evaluate.expression(node.expression, options); 15 | // Stop immediately if the expression doesn't match the switch expression 16 | if (expressionResult !== switchExpression || getCurrentError() != null) return; 17 | 18 | for (const statement of node.statements) { 19 | evaluate.statement(statement, options); 20 | 21 | if (getCurrentError() != null) { 22 | return; 23 | } 24 | 25 | // Check if a 'break', 'continue', or 'return' statement has been encountered, break the block 26 | if (pathInLexicalEnvironmentEquals(node, options.environment, true, BREAK_SYMBOL, CONTINUE_SYMBOL, RETURN_SYMBOL)) { 27 | break; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-catch-clause.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import {cloneLexicalEnvironment} from "../lexical-environment/clone-lexical-environment.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a CatchClause, based on a given Error 7 | */ 8 | export function evaluateCatchClause(options: EvaluatorOptions, ex: Error): void { 9 | const {node, evaluate, environment, getCurrentError} = options; 10 | // If a catch binding is provided, we must provide a local lexical environment for the CatchBlock 11 | const catchEnvironment = node.variableDeclaration == null ? environment : cloneLexicalEnvironment(environment, node); 12 | const nextOptions = {...options, environment: catchEnvironment}; 13 | 14 | // Evaluate the catch binding, if any is provided 15 | if (node.variableDeclaration != null) { 16 | evaluate.nodeWithArgument(node.variableDeclaration, ex, nextOptions); 17 | 18 | if (getCurrentError() != null) { 19 | return; 20 | } 21 | } 22 | 23 | // Evaluate the block 24 | evaluate.statement(node.block, nextOptions); 25 | } 26 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-computed-property-name.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a ComputedPropertyName 7 | */ 8 | export function evaluateComputedPropertyName({node, evaluate, ...options}: EvaluatorOptions): Literal { 9 | return evaluate.expression(node.expression, options); 10 | } 11 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-conditional-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a ConditionalExpression 7 | */ 8 | export function evaluateConditionalExpression({node, evaluate, ...options}: EvaluatorOptions): Literal { 9 | const {getCurrentError} = options; 10 | const conditionValue = evaluate.expression(node.condition, options); 11 | 12 | if (getCurrentError() != null) { 13 | return; 14 | } 15 | 16 | // We have to perform a loose boolean expression here to conform with actual spec behavior 17 | if (conditionValue) { 18 | // Proceed with the truthy branch 19 | return evaluate.expression(node.whenTrue, options); 20 | } 21 | 22 | // Proceed with the falsy branch 23 | return evaluate.expression(node.whenFalse, options); 24 | } 25 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-continue-statement.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import {setInLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 3 | import {CONTINUE_SYMBOL} from "../util/continue/continue-symbol.js"; 4 | import type {TS} from "../../type/ts.js"; 5 | 6 | /** 7 | * Evaluates, or attempts to evaluate, a ContinueStatement 8 | */ 9 | export function evaluateContinueStatement(options: EvaluatorOptions): void { 10 | setInLexicalEnvironment({...options, path: CONTINUE_SYMBOL, value: true}); 11 | } 12 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-declaration.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import {evaluateNode} from "./evaluate-node.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Will get a literal value for the given Declaration. If it doesn't succeed, the value will be 'undefined' 7 | */ 8 | export function evaluateDeclaration(options: EvaluatorOptions): void { 9 | options.logger.logNode(options.node, options.typescript); 10 | 11 | evaluateNode(options); 12 | } 13 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-decorator.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {IndexLiteral} from "../literal/literal.js"; 3 | import {stringifyLiteral} from "../literal/literal.js"; 4 | import {NotCallableError} from "../error/not-callable-error/not-callable-error.js"; 5 | import type {TS} from "../../type/ts.js"; 6 | import {__decorate, __param} from "../util/tslib/tslib-util.js"; 7 | import type {EvaluationError} from "../error/evaluation-error/evaluation-error.js"; 8 | 9 | /** 10 | * Evaluates, or attempts to evaluate, a Decorator 11 | */ 12 | export function evaluateDecorator(options: EvaluatorOptions, [parent, propertyName, index]: [IndexLiteral, string?, number?]): EvaluationError | undefined { 13 | const {node, evaluate, environment, throwError, stack, getCurrentError} = options; 14 | const decoratorImplementation = evaluate.expression(node.expression, options); 15 | 16 | if (getCurrentError() != null) { 17 | return; 18 | } 19 | 20 | if (typeof decoratorImplementation !== "function") { 21 | return throwError( 22 | new NotCallableError({ 23 | node, 24 | environment, 25 | value: decoratorImplementation, 26 | message: `${stringifyLiteral(decoratorImplementation)} is not a valid decorator implementation'` 27 | }) 28 | ); 29 | } 30 | 31 | stack.push(__decorate([index != null ? __param(index, decoratorImplementation) : decoratorImplementation], parent, propertyName)); 32 | return; 33 | } 34 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-default-clause.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import {pathInLexicalEnvironmentEquals} from "../lexical-environment/lexical-environment.js"; 3 | import {BREAK_SYMBOL} from "../util/break/break-symbol.js"; 4 | import {CONTINUE_SYMBOL} from "../util/continue/continue-symbol.js"; 5 | import {RETURN_SYMBOL} from "../util/return/return-symbol.js"; 6 | import type {TS} from "../../type/ts.js"; 7 | 8 | /** 9 | * Evaluates, or attempts to evaluate, a DefaultClause, based on a switch expression 10 | */ 11 | export function evaluateDefaultClause(options: EvaluatorOptions): void { 12 | const {node, evaluate, environment, getCurrentError} = options; 13 | for (const statement of node.statements) { 14 | evaluate.statement(statement, options); 15 | 16 | if (getCurrentError() != null) { 17 | return; 18 | } 19 | 20 | // Check if a 'break', 'continue', or 'return' statement has been encountered, break the block 21 | if (pathInLexicalEnvironmentEquals(node, environment, true, BREAK_SYMBOL, CONTINUE_SYMBOL, RETURN_SYMBOL)) { 22 | break; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-element-access-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {IndexLiteral, IndexLiteralKey, LazyCall, Literal} from "../literal/literal.js"; 3 | import {LAZY_CALL_FLAG, LiteralFlagKind} from "../literal/literal.js"; 4 | import {isBindCallApply} from "../util/function/is-bind-call-apply.js"; 5 | import type {TS} from "../../type/ts.js"; 6 | import {maybeThrow} from "../error/evaluation-error/evaluation-error-intent.js"; 7 | 8 | /** 9 | * Evaluates, or attempts to evaluate, a ElementAccessExpression 10 | */ 11 | export function evaluateElementAccessExpression(options: EvaluatorOptions): Literal { 12 | const {node, environment, evaluate, statementTraversalStack, typescript, getCurrentError} = options; 13 | const expressionResult = evaluate.expression(node.expression, options) as IndexLiteral; 14 | 15 | if (getCurrentError() != null) { 16 | return; 17 | } 18 | 19 | const argumentExpressionResult = evaluate.expression(node.argumentExpression, options) as IndexLiteralKey; 20 | 21 | if (getCurrentError() != null) { 22 | return; 23 | } 24 | 25 | const match = 26 | node.questionDotToken != null && expressionResult == null 27 | ? // If optional chaining are being used and the expressionResult is undefined or null, assign undefined to 'match' 28 | undefined 29 | : expressionResult[argumentExpressionResult]; 30 | 31 | // If it is a function, wrap it in a lazy call to preserve implicit this bindings. This is to avoid losing the this binding or having to 32 | // explicitly bind a 'this' value 33 | if (typeof match === "function" && statementTraversalStack.includes(typescript.SyntaxKind.CallExpression)) { 34 | return { 35 | [LAZY_CALL_FLAG]: LiteralFlagKind.CALL, 36 | invoke: (overriddenThis: Record | CallableFunction | undefined, ...args: Literal[]) => 37 | maybeThrow( 38 | node, 39 | options, 40 | overriddenThis != null && !isBindCallApply(match, environment) 41 | ? // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type 42 | (expressionResult[argumentExpressionResult] as Function).call(overriddenThis, ...args) 43 | : (expressionResult[argumentExpressionResult] as CallableFunction)(...args) 44 | ) 45 | } as LazyCall; 46 | } else return match; 47 | } 48 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-enum-declaration.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {IndexLiteral} from "../literal/literal.js"; 3 | import {getFromLexicalEnvironment, setInLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 4 | import type {TS} from "../../type/ts.js"; 5 | 6 | /** 7 | * Evaluates, or attempts to evaluate, an EnumDeclaration 8 | */ 9 | export function evaluateEnumDeclaration(options: EvaluatorOptions): void { 10 | const {node, environment, evaluate, stack, getCurrentError} = options; 11 | 12 | // Create a new ObjectLiteral based on the Object implementation from the Realm since this must not be the same as in the parent executing context 13 | // Otherwise, instanceof checks would fail 14 | const objectCtor = getFromLexicalEnvironment(node, environment, "Object")!.literal as ObjectConstructor; 15 | const enumDeclaration: IndexLiteral = objectCtor.create(objectCtor.prototype); 16 | const name = node.name.text; 17 | 18 | // Bind the Enum to the lexical environment as a new binding 19 | setInLexicalEnvironment({...options, path: name, value: enumDeclaration, newBinding: true}); 20 | 21 | for (const member of node.members) { 22 | evaluate.nodeWithArgument(member, enumDeclaration, options); 23 | 24 | if (getCurrentError() != null) { 25 | return; 26 | } 27 | } 28 | 29 | enumDeclaration.toString = () => `[Enum: ${name}]`; 30 | 31 | // Push the Enum declaration on to the Stack 32 | stack.push(enumDeclaration); 33 | } 34 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-enum-member.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {IndexLiteral, IndexLiteralKey} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, an EnumMember 7 | */ 8 | export function evaluateEnumMember(options: EvaluatorOptions, parent: IndexLiteral): void { 9 | const {node, typeChecker, evaluate, getCurrentError} = options; 10 | let constantValue = typeChecker?.getConstantValue(node); 11 | 12 | // If the constant value is not defined, that must be due to the type checker either not being given or functioning incorrectly. 13 | // Calculate it manually instead 14 | if (constantValue == null) { 15 | if (node.initializer != null) { 16 | constantValue = evaluate.expression(node.initializer, options) as string | number | undefined; 17 | 18 | if (getCurrentError() != null) { 19 | return; 20 | } 21 | } else { 22 | const siblings = node.parent.members; 23 | 24 | const thisIndex = siblings.findIndex(member => member === node); 25 | const beforeSiblings = siblings.slice(0, thisIndex); 26 | let traversal = 0; 27 | 28 | for (const sibling of [...beforeSiblings].reverse()) { 29 | traversal++; 30 | if (sibling.initializer != null) { 31 | const siblingConstantValue = evaluate.expression(sibling.initializer, options) as string | number | undefined; 32 | 33 | if (getCurrentError() != null) { 34 | return; 35 | } 36 | 37 | if (typeof siblingConstantValue === "number") { 38 | constantValue = siblingConstantValue + traversal; 39 | break; 40 | } 41 | } 42 | } 43 | 44 | if (constantValue == null) { 45 | constantValue = thisIndex; 46 | } 47 | } 48 | } 49 | 50 | const propertyName = evaluate.nodeWithValue(node.name, options) as IndexLiteralKey; 51 | 52 | if (getCurrentError() != null) { 53 | return; 54 | } 55 | 56 | // If it is a String enum, all keys will be initialized to strings 57 | if (typeof constantValue === "string") { 58 | parent[propertyName] = constantValue; 59 | } else { 60 | parent[(parent[propertyName] = constantValue ?? 0)] = propertyName; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-expression-statement.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {TS} from "../../type/ts.js"; 3 | 4 | /** 5 | * Evaluates, or attempts to evaluate, an ExpressionStatement 6 | */ 7 | export function evaluateExpressionStatement({node, evaluate, stack, ...options}: EvaluatorOptions): void { 8 | const result = evaluate.expression(node.expression, options); 9 | if (options.getCurrentError() != null) { 10 | return; 11 | } 12 | stack.push(result); 13 | } 14 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import {evaluateNode} from "./evaluate-node.js"; 4 | import type {TS} from "../../type/ts.js"; 5 | 6 | /** 7 | * Will get a literal value for the given Expression. If it doesn't succeed, the value will be 'undefined' 8 | */ 9 | export function evaluateExpression(options: EvaluatorOptions): Literal { 10 | const {getCurrentError} = options; 11 | options.logger.logNode(options.node, options.typescript); 12 | const value = evaluateNode(options) as Promise; 13 | 14 | if (getCurrentError() != null) { 15 | return; 16 | } 17 | 18 | // Report intermediate results 19 | if (options.reporting.reportIntermediateResults != null) { 20 | options.reporting.reportIntermediateResults({ 21 | node: options.node, 22 | value 23 | }); 24 | } 25 | 26 | return value; 27 | } 28 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-if-statement.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {TS} from "../../type/ts.js"; 3 | import type {EvaluationError} from "../error/evaluation-error/evaluation-error.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, an IfStatement 7 | */ 8 | export function evaluateIfStatement({node, evaluate, ...options}: EvaluatorOptions): EvaluationError | undefined { 9 | const {getCurrentError} = options; 10 | 11 | const expressionValue = evaluate.expression(node.expression, options); 12 | 13 | if (getCurrentError() != null) { 14 | return; 15 | } 16 | 17 | // We have to perform a loose boolean expression here to conform with actual spec behavior 18 | if (expressionValue) { 19 | // Proceed with the truthy branch 20 | evaluate.statement(node.thenStatement, options); 21 | 22 | if (getCurrentError() != null) { 23 | return; 24 | } 25 | } 26 | 27 | // Proceed with the falsy branch 28 | else if (node.elseStatement != null) { 29 | return evaluate.statement(node.elseStatement, options) as undefined; 30 | } 31 | 32 | return; 33 | } 34 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-import-clause.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {TS} from "../../type/ts.js"; 3 | 4 | /** 5 | * Evaluates, or attempts to evaluate, an ImportClause. 6 | * It will only initialize the bindings inside the lexical environment, but not resolve them, since we rely on the TypeChecker to resolve symbols across SourceFiles, 7 | * rather than manually parsing and resolving imports/exports 8 | */ 9 | export function evaluateImportClause({node, evaluate, ...options}: EvaluatorOptions): void { 10 | const {getCurrentError} = options; 11 | if (node.name != null) { 12 | evaluate.declaration(node.name, options); 13 | 14 | if (getCurrentError() != null) { 15 | return; 16 | } 17 | } 18 | 19 | if (node.namedBindings != null) { 20 | if ("elements" in node.namedBindings) { 21 | for (const importSpecifier of node.namedBindings.elements) { 22 | evaluate.declaration(importSpecifier, options); 23 | 24 | if (getCurrentError() != null) { 25 | return; 26 | } 27 | } 28 | } else { 29 | evaluate.declaration(node.namedBindings.name, options); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-import-declaration.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {TS} from "../../type/ts.js"; 3 | 4 | /** 5 | * Evaluates, or attempts to evaluate, an ImportDeclaration (which is actually a Statement). 6 | */ 7 | export function evaluateImportDeclaration({node, evaluate, ...options}: EvaluatorOptions): void { 8 | if (node.importClause == null) return; 9 | evaluate.declaration(node.importClause, options); 10 | } 11 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-import-equals-declaration.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {TS} from "../../type/ts.js"; 3 | 4 | /** 5 | * Evaluates, or attempts to evaluate, an ImportEqualsDeclaration (which is actually a Statement). 6 | * It will be a noop, since we rely on the TypeChecker to resolve symbols across SourceFiles, 7 | * rather than manually parsing and resolving imports/exports 8 | */ 9 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 10 | export function evaluateImportEqualsDeclaration(_options: EvaluatorOptions): void { 11 | // Noop 12 | } 13 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-import-specifier.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {TS} from "../../type/ts.js"; 3 | 4 | /** 5 | * Evaluates, or attempts to evaluate, an ImportSpecifier. 6 | * It will only initialize the bindings inside the lexical environment, but not resolve them, since we rely on the TypeChecker to resolve symbols across SourceFiles, 7 | * rather than manually parsing and resolving imports/exports 8 | */ 9 | export function evaluateImportSpecifier({node, evaluate, ...options}: EvaluatorOptions): void { 10 | evaluate.declaration(node.propertyName ?? node.name, options); 11 | } 12 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-interface-declaration.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {TS} from "../../type/ts.js"; 3 | 4 | /** 5 | * Evaluates, or attempts to evaluate, a TypeAliasDeclaration 6 | */ 7 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 8 | export function evaluateInterfaceDeclaration(_options: EvaluatorOptions): void { 9 | return; 10 | } 11 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-meta-property.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {TS} from "../../type/ts.js"; 3 | import {getFromLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 4 | import type {Literal} from "../literal/literal.js"; 5 | import {UnexpectedSyntaxError} from "../error/unexpected-syntax-error/unexpected-syntax-error.js"; 6 | 7 | /** 8 | * Evaluates, or attempts to evaluate, a MetaProperty. 9 | */ 10 | export function evaluateMetaProperty({node, typescript, throwError, environment}: EvaluatorOptions): Literal | undefined { 11 | switch (node.keywordToken) { 12 | case typescript.SyntaxKind.NewKeyword: { 13 | switch (node.name.text) { 14 | case "target": 15 | return getFromLexicalEnvironment(node, environment, "[[NewTarget]]")?.literal; 16 | default: 17 | return throwError(new UnexpectedSyntaxError({node: node.name, environment})); 18 | } 19 | } 20 | 21 | case typescript.SyntaxKind.ImportKeyword: { 22 | switch (node.name.text) { 23 | case "meta": 24 | return getFromLexicalEnvironment(node, environment, "import.meta")?.literal; 25 | default: 26 | return throwError(new UnexpectedSyntaxError({node: node.name, environment})); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-module-declaration.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import {getImplementationForDeclarationWithinDeclarationFile} from "../util/module/get-implementation-for-declaration-within-declaration-file.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a ModuleDeclaration 7 | */ 8 | export function evaluateModuleDeclaration(options: EvaluatorOptions): void { 9 | const {getCurrentError, stack} = options; 10 | const result = getImplementationForDeclarationWithinDeclarationFile(options); 11 | 12 | if (getCurrentError() != null) { 13 | return; 14 | } 15 | stack.push(result); 16 | } 17 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-namespace-import.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {TS} from "../../type/ts.js"; 3 | 4 | /** 5 | * Evaluates, or attempts to evaluate, a NamespaceImport. 6 | * It will only initialize the bindings inside the lexical environment, but not resolve them, since we rely on the TypeChecker to resolve symbols across SourceFiles, 7 | * rather than manually parsing and resolving imports/exports 8 | */ 9 | export function evaluateNamespaceImport({node, evaluate, ...options}: EvaluatorOptions): void { 10 | evaluate.declaration(node.name, options); 11 | } 12 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-new-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | import {setInLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 5 | import {maybeThrow} from "../error/evaluation-error/evaluation-error-intent.js"; 6 | 7 | /** 8 | * Evaluates, or attempts to evaluate, a NewExpression 9 | */ 10 | export function evaluateNewExpression({node, evaluate, ...options}: EvaluatorOptions): Literal { 11 | const {getCurrentError} = options; 12 | const evaluatedArgs: Literal[] = []; 13 | 14 | if (node.arguments != null) { 15 | for (let i = 0; i < node.arguments.length; i++) { 16 | evaluatedArgs[i] = evaluate.expression(node.arguments[i]!, options); 17 | if (getCurrentError() != null) { 18 | return; 19 | } 20 | } 21 | } 22 | 23 | // Evaluate the expression 24 | const expressionResult = evaluate.expression(node.expression, options) as new (...args: Literal[]) => Literal; 25 | 26 | if (getCurrentError() != null) { 27 | return; 28 | } 29 | 30 | // If the expression evaluated to a function, mark it as the [[NewTarget]], as per https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-getnewtarget 31 | if (typeof expressionResult === "function") { 32 | setInLexicalEnvironment({...options, node, path: "[[NewTarget]]", value: expressionResult, newBinding: true}); 33 | } 34 | 35 | return maybeThrow(node, options, new expressionResult(...evaluatedArgs)); 36 | } 37 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-node-with-value.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {NodeWithValue} from "./node-evaluator/node-evaluator.js"; 4 | import {evaluatePropertyName} from "./evaluate-property-name.js"; 5 | import {UnexpectedNodeError} from "../error/unexpected-node-error/unexpected-node-error.js"; 6 | 7 | /** 8 | * Evaluates a given node with the provided argument 9 | */ 10 | export function evaluateNodeWithValue(options: EvaluatorOptions): Literal { 11 | options.logger.logNode(options.node, options.typescript, "nodeWithValue"); 12 | const {node, ...rest} = options; 13 | 14 | // Until #37135 is resolved, isPropertyName will return false for PrivateIdentifiers (even though they are actually PropertyNames) 15 | if (options.typescript.isPropertyName(node) || options.typescript.isPrivateIdentifier(node)) { 16 | return evaluatePropertyName({node, ...rest}); 17 | } 18 | 19 | return options.throwError(new UnexpectedNodeError({node, environment: options.environment, typescript: options.typescript})); 20 | } 21 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-non-null-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a NonNullExpression 7 | */ 8 | export function evaluateNonNullExpression({node, evaluate, ...options}: EvaluatorOptions): Literal { 9 | return evaluate.expression(node.expression, options); 10 | } 11 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-null-literal.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a NullLiteral 7 | */ 8 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 9 | export function evaluateNullLiteral(_options: EvaluatorOptions): Literal { 10 | return null; 11 | } 12 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-numeric-literal.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a NumericLiteral 7 | */ 8 | export function evaluateNumericLiteral({node}: EvaluatorOptions): Literal { 9 | return Number(node.text); 10 | } 11 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-object-binding-pattern.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, an ObjectBindingPattern, based on an initializer 7 | */ 8 | export function evaluateObjectBindingPattern({node, evaluate, ...options}: EvaluatorOptions, rightHandValue: Literal): void { 9 | for (const element of node.elements) { 10 | evaluate.nodeWithArgument(element, rightHandValue, options); 11 | 12 | if (options.getCurrentError() != null) { 13 | return; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-object-literal-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {IndexLiteral, Literal} from "../literal/literal.js"; 3 | import {getFromLexicalEnvironment, setInLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 4 | import {THIS_SYMBOL} from "../util/this/this-symbol.js"; 5 | import type {TS} from "../../type/ts.js"; 6 | 7 | /** 8 | * Evaluates, or attempts to evaluate, a ObjectLiteralExpression 9 | */ 10 | export function evaluateObjectLiteralExpression(options: EvaluatorOptions): Literal { 11 | const {node, evaluate, environment, getCurrentError} = options; 12 | // Create a new ObjectLiteral based on the Object implementation from the Realm since this must not be the same as in the parent executing context 13 | // Otherwise, instanceof checks would fail 14 | const objectCtor = getFromLexicalEnvironment(node, environment, "Object")!.literal as ObjectConstructor; 15 | const value: IndexLiteral = objectCtor.create(objectCtor.prototype); 16 | 17 | // Mark the object as the 'this' value of the scope 18 | setInLexicalEnvironment({...options, path: THIS_SYMBOL, value, newBinding: true}); 19 | 20 | for (const property of node.properties) { 21 | evaluate.nodeWithArgument(property, value, options); 22 | 23 | if (getCurrentError() != null) { 24 | return; 25 | } 26 | } 27 | 28 | return value; 29 | } 30 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-omitted-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {TS} from "../../type/ts.js"; 3 | 4 | /** 5 | * Evaluates, or attempts to evaluate, a OmittedExpression 6 | */ 7 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 8 | export function evaluateOmittedExpression(_options: EvaluatorOptions): undefined { 9 | return undefined; 10 | } 11 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-parameter-declaration.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a ParameterDeclaration 7 | */ 8 | export function evaluateParameterDeclaration({node, evaluate, logger, ...options}: EvaluatorOptions, boundArgument: Literal): void { 9 | // Use the bound argument if it is given unless it is nullable and the node itself has an initializer 10 | const boundValue = boundArgument != null || node.initializer === undefined ? boundArgument : evaluate.expression(node.initializer, options); 11 | 12 | if (options.getCurrentError() != null) { 13 | return; 14 | } 15 | 16 | logger.logBinding(node.name.getText(), boundValue, "evaluateParameterDeclaration"); 17 | evaluate.nodeWithArgument(node.name, boundValue, options); 18 | } 19 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-parameter-declarations.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {IndexLiteral, Literal} from "../literal/literal.js"; 3 | import {hasModifier} from "../util/modifier/has-modifier.js"; 4 | import {getFromLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 5 | import type {TS} from "../../type/ts.js"; 6 | 7 | /** 8 | * Evaluates, or attempts to evaluate, a NodeArray of ParameterDeclarations 9 | */ 10 | export function evaluateParameterDeclarations(options: EvaluatorOptions>, boundArguments: Literal[], context?: IndexLiteral): void { 11 | const {node, evaluate, environment, typescript, getCurrentError} = options; 12 | // 'this' is a special parameter which is removed from the emitted results 13 | const parameters = node.filter(param => !(typescript.isIdentifier(param.name) && param.name.text === "this")); 14 | 15 | for (let i = 0; i < parameters.length; i++) { 16 | const parameter = parameters[i]!; 17 | 18 | // It it is a spread element, it should receive all arguments from the current index. 19 | if (parameter.dotDotDotToken != null) { 20 | evaluate.nodeWithArgument(parameter, boundArguments.slice(i), options); 21 | 22 | if (getCurrentError() != null) { 23 | return; 24 | } 25 | 26 | // Spread elements must always be the last parameter 27 | break; 28 | } else { 29 | evaluate.nodeWithArgument(parameter, boundArguments[i], options); 30 | 31 | if (getCurrentError() != null) { 32 | return; 33 | } 34 | 35 | // If a context is given, and if a [public|protected|private] keyword is in front of the parameter, the initialized value should be 36 | // set on the context as an instance property 37 | if ( 38 | context != null && 39 | typescript.isIdentifier(parameter.name) && 40 | (hasModifier(parameter, typescript.SyntaxKind.PublicKeyword) || 41 | hasModifier(parameter, typescript.SyntaxKind.ProtectedKeyword) || 42 | hasModifier(parameter, typescript.SyntaxKind.PrivateKeyword)) 43 | ) { 44 | const value = getFromLexicalEnvironment(parameter, environment, parameter.name.text); 45 | if (value != null) { 46 | context[parameter.name.text] = value.literal; 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-parenthesized-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a ParenthesizedExpression 7 | */ 8 | export function evaluateParenthesizedExpression({node, evaluate, ...options}: EvaluatorOptions): Literal { 9 | return evaluate.expression(node.expression, options); 10 | } 11 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-postfix-unary-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import {getRelevantDictFromLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 4 | import {UnexpectedNodeError} from "../error/unexpected-node-error/unexpected-node-error.js"; 5 | import type {TS} from "../../type/ts.js"; 6 | 7 | /** 8 | * Evaluates, or attempts to evaluate, a PostfixUnaryExpression 9 | */ 10 | export function evaluatePostfixUnaryExpression(options: EvaluatorOptions): Literal { 11 | const {evaluate, node, environment, typescript, throwError, reporting} = options; 12 | 13 | // Make sure to evaluate the operand to ensure that it is found in the lexical environment 14 | evaluate.expression(node.operand, options); 15 | 16 | switch (node.operator) { 17 | case typescript.SyntaxKind.PlusPlusToken: { 18 | // If the Operand isn't an identifier, this will be a SyntaxError 19 | if (!typescript.isIdentifier(node.operand) && !typescript.isPrivateIdentifier?.(node.operand)) { 20 | return throwError(new UnexpectedNodeError({node: node.operand, environment, typescript})); 21 | } 22 | 23 | // Find the value associated with the identifier within the environment. 24 | const value = (getRelevantDictFromLexicalEnvironment(environment, node.operand.text)![node.operand.text]! as number)++; 25 | 26 | // Inform reporting hooks if any is given 27 | if (reporting.reportBindings != null) { 28 | reporting.reportBindings({path: node.operand.text, value, node}); 29 | } 30 | return value; 31 | } 32 | 33 | case typescript.SyntaxKind.MinusMinusToken: { 34 | // If the Operand isn't an identifier, this will be a SyntaxError 35 | if (!typescript.isIdentifier(node.operand) && !typescript.isPrivateIdentifier?.(node.operand)) { 36 | return throwError(new UnexpectedNodeError({node: node.operand, environment, typescript})); 37 | } 38 | 39 | // Find the value associated with the identifier within the environment. 40 | const value = (getRelevantDictFromLexicalEnvironment(environment, node.operand.text)![node.operand.text]! as number)--; 41 | 42 | // Inform reporting hooks if any is given 43 | if (reporting.reportBindings != null) { 44 | reporting.reportBindings({path: node.operand.text, value, node}); 45 | } 46 | return value; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-property-access-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {IndexLiteral, LazyCall, Literal} from "../literal/literal.js"; 3 | import {LAZY_CALL_FLAG, LiteralFlagKind} from "../literal/literal.js"; 4 | import {isBindCallApply} from "../util/function/is-bind-call-apply.js"; 5 | import type {TS} from "../../type/ts.js"; 6 | import {maybeThrow} from "../error/evaluation-error/evaluation-error-intent.js"; 7 | 8 | /** 9 | * Evaluates, or attempts to evaluate, a PropertyAccessExpression 10 | */ 11 | export function evaluatePropertyAccessExpression(options: EvaluatorOptions): Literal { 12 | const {evaluate, node, statementTraversalStack, environment, typescript, getCurrentError} = options; 13 | const expressionResult = evaluate.expression(node.expression, options) as IndexLiteral; 14 | 15 | if (expressionResult == null || getCurrentError() != null) { 16 | return; 17 | } 18 | 19 | const match = 20 | node.questionDotToken != null && expressionResult == null 21 | ? // If optional chaining are being used and the expressionResult is undefined or null, assign undefined to 'match' 22 | undefined 23 | : expressionResult[node.name.text]; 24 | 25 | // If it is a function, wrap it in a lazy call to preserve implicit 'this' bindings. This is to avoid losing the 'this' binding or having to 26 | // explicitly bind a 'this' value 27 | if (typeof match === "function" && statementTraversalStack.includes(typescript.SyntaxKind.CallExpression)) { 28 | return { 29 | [LAZY_CALL_FLAG]: LiteralFlagKind.CALL, 30 | invoke: (overriddenThis: Record | CallableFunction | undefined, ...args: Literal[]) => 31 | maybeThrow( 32 | node, 33 | options, 34 | overriddenThis != null && !isBindCallApply(match, environment) 35 | ? // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type 36 | (expressionResult[node.name.text] as Function).call(overriddenThis, ...args) 37 | : (expressionResult[node.name.text] as CallableFunction)(...args) 38 | ) 39 | } as LazyCall; 40 | } else return match; 41 | } 42 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-property-assignment.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {IndexLiteral, IndexLiteralKey} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a PropertyAssignment, before applying it on the given parent 7 | */ 8 | export function evaluatePropertyAssignment({node, evaluate, ...options}: EvaluatorOptions, parent: IndexLiteral): void { 9 | const initializer = evaluate.expression(node.initializer, options); 10 | 11 | if (options.getCurrentError() != null) { 12 | return; 13 | } 14 | // Compute the property name 15 | const propertyNameResult = evaluate.nodeWithValue(node.name, options) as IndexLiteralKey; 16 | 17 | if (options.getCurrentError() != null) { 18 | return; 19 | } 20 | 21 | parent[propertyNameResult] = initializer; 22 | } 23 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-property-declaration.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {IndexLiteral, IndexLiteralKey} from "../literal/literal.js"; 3 | import {inStaticContext} from "../util/static/in-static-context.js"; 4 | import type {TS} from "../../type/ts.js"; 5 | import {canHaveDecorators, getDecorators} from "../util/node/modifier-util.js"; 6 | 7 | /** 8 | * Evaluates, or attempts to evaluate, a PropertyDeclaration, before applying it on the given parent 9 | */ 10 | export function evaluatePropertyDeclaration({node, evaluate, typescript, stack, ...options}: EvaluatorOptions, parent?: IndexLiteral): void { 11 | const {getCurrentError} = options; 12 | 13 | // Compute the property name 14 | const propertyNameResult = evaluate.nodeWithValue(node.name, options) as IndexLiteralKey; 15 | 16 | if (getCurrentError() != null) { 17 | return; 18 | } 19 | 20 | if (parent == null) { 21 | evaluate.declaration(node.parent, options); 22 | 23 | if (getCurrentError() != null) { 24 | return; 25 | } 26 | 27 | const updatedParent = stack.pop() as CallableFunction & IndexLiteral; 28 | const isStatic = inStaticContext(node, typescript); 29 | stack.push(isStatic ? updatedParent[propertyNameResult] : updatedParent.prototype[propertyNameResult]); 30 | return; 31 | } 32 | 33 | parent[propertyNameResult] = node.initializer == null ? undefined : evaluate.expression(node.initializer, options); 34 | 35 | if (getCurrentError() != null) { 36 | return; 37 | } 38 | 39 | if (canHaveDecorators(node, typescript)) { 40 | for (const decorator of getDecorators(node, typescript) ?? []) { 41 | evaluate.nodeWithArgument(decorator, [parent, propertyNameResult], options); 42 | 43 | if (getCurrentError() != null) { 44 | return; 45 | } 46 | 47 | // Pop the stack. We don't need the value it has left on the Stack 48 | stack.pop(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-property-name.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {IndexLiteralKey, Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a PropertyName 7 | */ 8 | export function evaluatePropertyName({node, evaluate, typescript, ...options}: EvaluatorOptions): Literal { 9 | return ( 10 | typescript.isComputedPropertyName(node) 11 | ? evaluate.expression(node.expression, options) 12 | : typescript.isIdentifier(node) || typescript.isPrivateIdentifier?.(node) 13 | ? node.text 14 | : evaluate.expression(node as TS.StringLiteral | TS.NumericLiteral, options) 15 | ) as IndexLiteralKey; 16 | } 17 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-regular-expression-literal.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import {getFromLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 4 | import type {TS} from "../../type/ts.js"; 5 | 6 | /** 7 | * Evaluates, or attempts to evaluate, a RegularExpressionLiteral 8 | */ 9 | export function evaluateRegularExpressionLiteral({node, environment}: EvaluatorOptions): Literal { 10 | const functionCtor = getFromLexicalEnvironment(node, environment, "Function")!.literal as FunctionConstructor; 11 | return new functionCtor(`return ${node.text}`)(); 12 | } 13 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-return-statement.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import {setInLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 3 | import {RETURN_SYMBOL} from "../util/return/return-symbol.js"; 4 | import type {TS} from "../../type/ts.js"; 5 | 6 | /** 7 | * Evaluates, or attempts to evaluate, a ReturnStatement 8 | */ 9 | export function evaluateReturnStatement({node, evaluate, stack, ...options}: EvaluatorOptions): void { 10 | const {getCurrentError} = options; 11 | setInLexicalEnvironment({...options, environment: options.environment, path: RETURN_SYMBOL, value: true, node}); 12 | 13 | // If it is a simple 'return', return undefined 14 | if (node.expression == null) { 15 | stack.push(undefined); 16 | } else { 17 | const result = evaluate.expression(node.expression, options); 18 | 19 | if (getCurrentError() != null) { 20 | return; 21 | } 22 | stack.push(result); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-shorthand-property-assignment.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {IndexLiteral} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a ShorthandPropertyAssignment, before applying it on the given parent 7 | */ 8 | export function evaluateShorthandPropertyAssignment({node, evaluate, ...options}: EvaluatorOptions, parent: IndexLiteral): void { 9 | const {getCurrentError} = options; 10 | const identifier = node.name.text; 11 | const initializer = evaluate.expression(node.name, options); 12 | 13 | if (getCurrentError() != null) { 14 | return; 15 | } 16 | 17 | parent[identifier] = initializer; 18 | } 19 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-source-file-as-namespace-object.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {IndexLiteral} from "../literal/literal.js"; 3 | import {getFromLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 4 | import type {TS} from "../../type/ts.js"; 5 | 6 | /** 7 | * Evaluates, or attempts to evaluate, a SourceFile as a namespace object 8 | */ 9 | export function evaluateSourceFileAsNamespaceObject(options: EvaluatorOptions): void { 10 | const {node, evaluate, environment, typeChecker, stack, getCurrentError} = options; 11 | // Create a new ObjectLiteral based on the Object implementation from the Realm since this must not be the same as in the parent executing context 12 | // Otherwise, instanceof checks would fail 13 | const objectCtor = getFromLexicalEnvironment(node, environment, "Object")!.literal as ObjectConstructor; 14 | const namespaceObject: IndexLiteral = objectCtor.create(objectCtor.prototype); 15 | 16 | const moduleSymbol = typeChecker?.getSymbolAtLocation(node); 17 | if (moduleSymbol != null) { 18 | const exports = moduleSymbol.exports; 19 | if (exports != null) { 20 | for (const [identifier, symbol] of exports.entries() as IterableIterator<[string, TS.Symbol]>) { 21 | const valueDeclaration = symbol.valueDeclaration; 22 | if (valueDeclaration == null) return; 23 | 24 | evaluate.declaration(valueDeclaration, options); 25 | 26 | if (getCurrentError() != null) { 27 | return; 28 | } 29 | 30 | namespaceObject[identifier] = stack.pop(); 31 | } 32 | } 33 | } 34 | stack.push(namespaceObject); 35 | } 36 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-spread-assignment.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {IndexLiteral} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a SpreadAssignment, before applying it on the given parent 7 | */ 8 | export function evaluateSpreadAssignment({node, evaluate, ...options}: EvaluatorOptions, parent: IndexLiteral): void { 9 | const entries = evaluate.expression(node.expression, options) as IndexLiteral; 10 | 11 | if (options.getCurrentError() != null) { 12 | return; 13 | } 14 | 15 | Object.assign(parent, entries); 16 | } 17 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-spread-element.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a SpreadElement, before applying it on the given parent 7 | */ 8 | export function evaluateSpreadElement({node, evaluate, ...options}: EvaluatorOptions): Literal[] { 9 | return evaluate.expression(node.expression, options) as Literal[]; 10 | } 11 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-statement.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import {evaluateNode} from "./evaluate-node.js"; 3 | import {createStatementTraversalStack} from "../stack/traversal-stack/statement-traversal-stack.js"; 4 | import type {TS} from "../../type/ts.js"; 5 | 6 | /** 7 | * Will get a literal value for the given Statement. If it doesn't succeed, the value will be 'undefined' 8 | */ 9 | export function evaluateStatement(options: EvaluatorOptions): void { 10 | options.logger.logNode(options.node, options.typescript); 11 | 12 | // Create a new Statement traversal stack (since this is a new statement) 13 | options.statementTraversalStack = createStatementTraversalStack(); 14 | 15 | evaluateNode(options); 16 | } 17 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-string-literal.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a StringLiteralLike 7 | */ 8 | export function evaluateStringLiteral({node}: EvaluatorOptions): Literal { 9 | return node.text; 10 | } 11 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-super-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import {getFromLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 4 | import {SUPER_SYMBOL} from "../util/super/super-symbol.js"; 5 | import type {TS} from "../../type/ts.js"; 6 | 7 | /** 8 | * Evaluates, or attempts to evaluate, a SuperExpression 9 | */ 10 | export function evaluateSuperExpression({node, environment}: EvaluatorOptions): Literal { 11 | const match = getFromLexicalEnvironment(node, environment, SUPER_SYMBOL); 12 | return match == null ? undefined : match.literal; 13 | } 14 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-switch-statement.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {TS} from "../../type/ts.js"; 3 | 4 | /** 5 | * Evaluates, or attempts to evaluate, a SwitchStatement 6 | */ 7 | export function evaluateSwitchStatement({node, evaluate, ...options}: EvaluatorOptions): void { 8 | const expressionResult = evaluate.expression(node.expression, options); 9 | 10 | if (options.getCurrentError() != null) { 11 | return; 12 | } 13 | evaluate.nodeWithArgument(node.caseBlock, expressionResult, options); 14 | } 15 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-template-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a TemplateExpression 7 | */ 8 | export function evaluateTemplateExpression({node, evaluate, ...options}: EvaluatorOptions): Literal { 9 | let str = ""; 10 | str += node.head.text; 11 | for (const span of node.templateSpans) { 12 | const expression = evaluate.expression(span.expression, options) as string; 13 | 14 | if (options.getCurrentError() != null) { 15 | return; 16 | } 17 | 18 | str += expression; 19 | str += span.literal.text; 20 | } 21 | return str; 22 | } 23 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-this-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import {getFromLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 4 | import {THIS_SYMBOL} from "../util/this/this-symbol.js"; 5 | import type {TS} from "../../type/ts.js"; 6 | 7 | /** 8 | * Evaluates, or attempts to evaluate, a ThisExpression 9 | */ 10 | export function evaluateThisExpression({node, environment}: EvaluatorOptions): Literal { 11 | const match = getFromLexicalEnvironment(node, environment, THIS_SYMBOL); 12 | return match == null ? undefined : match.literal; 13 | } 14 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-throw-statement.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {TS} from "../../type/ts.js"; 3 | import type {EvaluationError} from "../error/evaluation-error/evaluation-error.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a ThrowStatement 7 | */ 8 | export function evaluateThrowStatement({node, evaluate, ...options}: EvaluatorOptions): EvaluationError | undefined { 9 | const {getCurrentError, throwError} = options; 10 | const result = evaluate.expression(node.expression, options) as EvaluationError; 11 | 12 | if (getCurrentError() != null) { 13 | return; 14 | } 15 | 16 | return throwError(result); 17 | } 18 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-try-statement.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import {MissingCatchOrFinallyAfterTryError} from "../error/missing-catch-or-finally-after-try-error/missing-catch-or-finally-after-try-error.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | import type {EvaluationError} from "../error/evaluation-error/evaluation-error.js"; 5 | 6 | /** 7 | * Evaluates, or attempts to evaluate, a TryStatement 8 | */ 9 | export function evaluateTryStatement(options: EvaluatorOptions): EvaluationError | undefined { 10 | const {node, evaluate, environment, throwError} = options; 11 | 12 | let error: EvaluationError | undefined; 13 | 14 | const executeTry = () => { 15 | try { 16 | return evaluate.statement(node.tryBlock, { 17 | ...options, 18 | throwError: ex => { 19 | error = ex; 20 | return ex; 21 | }, 22 | getCurrentError: () => error 23 | }); 24 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 25 | } catch (ex: any) { 26 | error = ex; 27 | } 28 | }; 29 | 30 | const executeCatch = (ex: Error) => { 31 | // The CatchClause will declare an environment of its own 32 | evaluate.nodeWithArgument(node.catchClause!, ex, options); 33 | }; 34 | 35 | const executeFinally = () => { 36 | let finallyError: EvaluationError | undefined; 37 | 38 | // The Block will declare an environment of its own 39 | evaluate.statement(node.finallyBlock!, { 40 | ...options, 41 | throwError: ex => { 42 | finallyError = ex; 43 | // Also set it on the upper context 44 | options.throwError(ex); 45 | return ex; 46 | }, 47 | getCurrentError: () => finallyError 48 | }); 49 | }; 50 | 51 | // A TryStatement must have either a catch or a finally block 52 | if (node.catchClause == null && node.finallyBlock == null) { 53 | return throwError(new MissingCatchOrFinallyAfterTryError({node, environment})); 54 | } 55 | 56 | // Follows the form: try {...} catch {...} 57 | else if (node.catchClause != null && node.finallyBlock == null) { 58 | executeTry(); 59 | 60 | if (error != null) { 61 | executeCatch(error); 62 | } 63 | } 64 | 65 | // Follows the form: try {...} catch {...} finally {...} 66 | else if (node.catchClause != null && node.finallyBlock != null) { 67 | executeTry(); 68 | if (error != null) { 69 | executeCatch(error); 70 | } 71 | executeFinally(); 72 | } 73 | 74 | // Follows the form: try {...} finally {...} 75 | else if (node.catchClause == null && node.finallyBlock != null) { 76 | executeTry(); 77 | if (error != null) { 78 | throwError(error); 79 | } 80 | 81 | executeFinally(); 82 | } 83 | 84 | return; 85 | } 86 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-type-alias-declaration.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {TS} from "../../type/ts.js"; 3 | 4 | /** 5 | * Evaluates, or attempts to evaluate, a TypeAliasDeclaration 6 | */ 7 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 8 | export function evaluateTypeAliasDeclaration(_options: EvaluatorOptions): void { 9 | return; 10 | } 11 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-type-assertion-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a TypeAssertion 7 | */ 8 | export function evaluateTypeAssertion({node, evaluate, ...options}: EvaluatorOptions): Literal { 9 | return evaluate.expression(node.expression, options); 10 | } 11 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-type-of-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a TypeOfExpression 7 | */ 8 | export function evaluateTypeOfExpression({evaluate, node, ...options}: EvaluatorOptions): Literal { 9 | const result = evaluate.expression(node.expression, options); 10 | 11 | if (options.getCurrentError() != null) { 12 | return; 13 | } 14 | return typeof result; 15 | } 16 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-variable-declaration-list.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {TS} from "../../type/ts.js"; 3 | 4 | /** 5 | * Evaluates, or attempts to evaluate, a VariableDeclarationList 6 | */ 7 | export function evaluateVariableDeclarationList({node, evaluate, ...options}: EvaluatorOptions): void { 8 | for (const declaration of node.declarations) { 9 | evaluate.declaration(declaration, options); 10 | 11 | if (options.getCurrentError() != null) { 12 | return; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-variable-declaration.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import {EvaluationError} from "../error/evaluation-error/evaluation-error.js"; 3 | import type {Literal} from "../literal/literal.js"; 4 | import type {TS} from "../../type/ts.js"; 5 | 6 | /** 7 | * Evaluates, or attempts to evaluate, a VariableDeclaration 8 | */ 9 | export function evaluateVariableDeclaration(options: EvaluatorOptions, initializer?: Literal): EvaluationError | undefined { 10 | const {node, environment, evaluate, stack, typescript, throwError, getCurrentError} = options; 11 | 12 | const initializerResult = 13 | initializer ?? 14 | (node.initializer == null 15 | ? // A VariableDeclaration with no initializer is implicitly bound to 'undefined' 16 | undefined 17 | : evaluate.expression(node.initializer, options)); 18 | 19 | if (getCurrentError() != null) { 20 | return; 21 | } 22 | 23 | // There's no way of destructuring a nullish value 24 | if (initializerResult == null && !typescript.isIdentifier(node.name)) { 25 | return throwError(new EvaluationError({node, environment})); 26 | } 27 | 28 | // Evaluate the binding name 29 | evaluate.nodeWithArgument(node.name, initializerResult, options); 30 | 31 | if (getCurrentError() != null) { 32 | return; 33 | } 34 | 35 | stack.push(initializerResult); 36 | return; 37 | } 38 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-variable-statement.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import {evaluateVariableDeclarationList} from "./evaluate-variable-declaration-list.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a VariableStatement 7 | */ 8 | export function evaluateVariableStatement({node, ...rest}: EvaluatorOptions): void { 9 | evaluateVariableDeclarationList({node: node.declarationList, ...rest}); 10 | } 11 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-void-expression.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import type {Literal} from "../literal/literal.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | /** 6 | * Evaluates, or attempts to evaluate, a VoidExpression 7 | */ 8 | export function evaluateVoidExpression({node, evaluate, ...options}: EvaluatorOptions): Literal { 9 | evaluate.expression(node.expression, options); 10 | // The void operator evaluates the expression and then returns undefined 11 | return undefined; 12 | } 13 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluate-while-statement.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "./evaluator-options.js"; 2 | import {cloneLexicalEnvironment} from "../lexical-environment/clone-lexical-environment.js"; 3 | import {pathInLexicalEnvironmentEquals, setInLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 4 | import {BREAK_SYMBOL} from "../util/break/break-symbol.js"; 5 | import {CONTINUE_SYMBOL} from "../util/continue/continue-symbol.js"; 6 | import {RETURN_SYMBOL} from "../util/return/return-symbol.js"; 7 | import type {TS} from "../../type/ts.js"; 8 | 9 | /** 10 | * Evaluates, or attempts to evaluate, a WhileStatement 11 | */ 12 | export function evaluateWhileStatement(options: EvaluatorOptions): void { 13 | const {node, environment, evaluate, logger, typescript, getCurrentError} = options; 14 | let condition = evaluate.expression(node.expression, options) as boolean; 15 | 16 | if (getCurrentError() != null) { 17 | return; 18 | } 19 | 20 | while (condition) { 21 | // Prepare a lexical environment for the current iteration 22 | const iterationEnvironment = cloneLexicalEnvironment(environment, node); 23 | const iterationOptions = {...options, environment: iterationEnvironment}; 24 | 25 | // Define a new binding for a break symbol within the environment 26 | setInLexicalEnvironment({...iterationOptions, path: BREAK_SYMBOL, value: false, newBinding: true}); 27 | 28 | // Define a new binding for a continue symbol within the environment 29 | setInLexicalEnvironment({...iterationOptions, path: CONTINUE_SYMBOL, value: false, newBinding: true}); 30 | 31 | // Execute the Statement 32 | evaluate.statement(node.statement, iterationOptions); 33 | 34 | if (getCurrentError() != null) { 35 | return; 36 | } 37 | 38 | // Check if a 'break' statement has been encountered and break if so 39 | if (pathInLexicalEnvironmentEquals(node, iterationEnvironment, true, BREAK_SYMBOL)) { 40 | logger.logBreak(node, typescript); 41 | break; 42 | } else if (pathInLexicalEnvironmentEquals(node, iterationEnvironment, true, RETURN_SYMBOL)) { 43 | logger.logReturn(node, typescript); 44 | return; 45 | } 46 | 47 | condition = evaluate.expression(node.expression, options) as boolean; 48 | 49 | if (getCurrentError() != null) { 50 | return; 51 | } 52 | 53 | // Always re-evaluate the condition before continuing 54 | if (pathInLexicalEnvironmentEquals(node, iterationEnvironment, true, CONTINUE_SYMBOL)) { 55 | logger.logContinue(node, typescript); 56 | // noinspection UnnecessaryContinueJS 57 | continue; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/evaluator-options.ts: -------------------------------------------------------------------------------- 1 | import type {LexicalEnvironment} from "../lexical-environment/lexical-environment.js"; 2 | import type {NodeEvaluator} from "./node-evaluator/node-evaluator.js"; 3 | import type {Logger} from "../logger/logger.js"; 4 | import type {StatementTraversalStack} from "../stack/traversal-stack/statement-traversal-stack.js"; 5 | import type {Stack} from "../stack/stack.js"; 6 | import type {EvaluatePolicySanitized} from "../policy/evaluate-policy.js"; 7 | import type {ReportingOptionsSanitized} from "../reporting/i-reporting-options.js"; 8 | import type {TS} from "../../type/ts.js"; 9 | import type {EvaluationError, ThrowError} from "../error/evaluation-error/evaluation-error.js"; 10 | 11 | export interface NextEvaluatorOptions { 12 | environment: LexicalEnvironment; 13 | moduleOverrides?: Record; 14 | throwError: ThrowError; 15 | getCurrentError(): EvaluationError | undefined; 16 | statementTraversalStack: StatementTraversalStack; 17 | } 18 | 19 | export interface EvaluatorOptions> extends NextEvaluatorOptions { 20 | typescript: typeof TS; 21 | node: T; 22 | evaluate: NodeEvaluator; 23 | typeChecker?: TS.TypeChecker; 24 | stack: Stack; 25 | logger: Logger; 26 | policy: EvaluatePolicySanitized; 27 | reporting: ReportingOptionsSanitized; 28 | } 29 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/node-evaluator/i-create-node-evaluator-options.ts: -------------------------------------------------------------------------------- 1 | import type {Logger} from "../../logger/logger.js"; 2 | import type {Stack} from "../../stack/stack.js"; 3 | import type {EvaluatePolicySanitized} from "../../policy/evaluate-policy.js"; 4 | import type {ReportingOptionsSanitized} from "../../reporting/i-reporting-options.js"; 5 | import type {TS} from "../../../type/ts.js"; 6 | import type {EvaluationError, ThrowError} from "../../error/evaluation-error/evaluation-error.js"; 7 | import type {LexicalEnvironment} from "../../lexical-environment/lexical-environment.js"; 8 | import type {StatementTraversalStack} from "../../stack/traversal-stack/statement-traversal-stack.js"; 9 | 10 | export interface ICreateNodeEvaluatorOptions { 11 | typeChecker?: TS.TypeChecker; 12 | typescript: typeof TS; 13 | policy: EvaluatePolicySanitized; 14 | reporting: ReportingOptionsSanitized; 15 | moduleOverrides?: Record; 16 | logger: Logger; 17 | stack: Stack; 18 | statementTraversalStack: StatementTraversalStack; 19 | environment: LexicalEnvironment; 20 | throwError: ThrowError; 21 | getCurrentError(): EvaluationError | undefined; 22 | } 23 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/node-evaluator/node-evaluator.ts: -------------------------------------------------------------------------------- 1 | import type {Literal} from "../../literal/literal.js"; 2 | import type {TS} from "../../../type/ts.js"; 3 | import type {NextEvaluatorOptions} from "../evaluator-options.js"; 4 | import type {EvaluationError} from "../../error/evaluation-error/evaluation-error.js"; 5 | 6 | export type NodeWithValue = TS.PropertyName; 7 | 8 | export type StatementEvaluator = (node: TS.Statement, nextOptions: NextEvaluatorOptions) => void; 9 | export type DeclarationEvaluator = (node: TS.Declaration, nextOptions: NextEvaluatorOptions) => void; 10 | export type NodeEvaluatorWithArgument = (node: TS.Node, arg: Literal, nextOptions: NextEvaluatorOptions) => void; 11 | export type ExpressionEvaluator = (node: TS.Expression | TS.PrivateIdentifier, nextOptions: NextEvaluatorOptions) => Literal | EvaluationError; 12 | export type NodeWithValueEvaluator = (node: NodeWithValue, nextOptions: NextEvaluatorOptions) => Literal | EvaluationError; 13 | 14 | export interface NodeEvaluator { 15 | statement: StatementEvaluator; 16 | expression: ExpressionEvaluator; 17 | declaration: DeclarationEvaluator; 18 | nodeWithArgument: NodeEvaluatorWithArgument; 19 | nodeWithValue: NodeWithValueEvaluator; 20 | } 21 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/simple/evaluate-simple-literal-result.ts: -------------------------------------------------------------------------------- 1 | import type {Literal} from "../../literal/literal.js"; 2 | 3 | export interface IEvaluateSimpleLiteralResultBase { 4 | success: boolean; 5 | } 6 | 7 | export interface IEvaluateSimpleLiteralSuccessResult extends IEvaluateSimpleLiteralResultBase { 8 | success: true; 9 | value: Literal; 10 | } 11 | 12 | export interface IEvaluateSimpleLiteralFailureResult extends IEvaluateSimpleLiteralResultBase { 13 | success: false; 14 | } 15 | 16 | export type EvaluateSimpleLiteralResult = IEvaluateSimpleLiteralSuccessResult | IEvaluateSimpleLiteralFailureResult; 17 | -------------------------------------------------------------------------------- /src/interpreter/evaluator/simple/evaluate-simple-literal.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluateSimpleLiteralResult} from "./evaluate-simple-literal-result.js"; 2 | import {isBooleanLiteral} from "../../util/node/is-boolean-literal.js"; 3 | import {isNullLiteral} from "../../util/node/is-null-literal.js"; 4 | import type {TS} from "../../../type/ts.js"; 5 | 6 | /** 7 | * This is a tiny function that avoids the costs of building up an evaluation environment 8 | * for the interpreter. If the node is a simple literal, it will return its' value. 9 | */ 10 | export function evaluateSimpleLiteral(node: TS.Node, typescript: typeof TS): EvaluateSimpleLiteralResult { 11 | if (typescript.isStringLiteralLike(node)) return {success: true, value: node.text}; 12 | else if (isBooleanLiteral(node, typescript)) return {success: true, value: node.kind === typescript.SyntaxKind.TrueKeyword}; 13 | // eslint-disable-next-line @typescript-eslint/no-implied-eval 14 | else if (typescript.isRegularExpressionLiteral(node)) return {success: true, value: new Function(`return ${node.text}`)()}; 15 | else if (typescript.isNumericLiteral(node)) return {success: true, value: Number(node.text)}; 16 | else if (typescript.isBigIntLiteral?.(node)) return {success: true, value: BigInt(node.text)}; 17 | else if (typescript.isIdentifier(node) && node.text === "Infinity") return {success: true, value: Infinity}; 18 | else if (typescript.isIdentifier(node) && node.text === "NaN") return {success: true, value: NaN}; 19 | else if (typescript.isIdentifier(node) && node.text === "null") return {success: true, value: null}; 20 | else if (typescript.isIdentifier(node) && node.text === "undefined") return {success: true, value: undefined}; 21 | else if (isNullLiteral(node, typescript)) return {success: true, value: null}; 22 | else return {success: false}; 23 | } 24 | -------------------------------------------------------------------------------- /src/interpreter/lexical-environment/clone-lexical-environment.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../type/ts.js"; 2 | import type {LexicalEnvironment} from "./lexical-environment.js"; 3 | 4 | /** 5 | * Clones the given LexicalEnvironment 6 | */ 7 | export function cloneLexicalEnvironment(environment: LexicalEnvironment, startingNode: TS.Node): LexicalEnvironment { 8 | return { 9 | parentEnv: environment, 10 | startingNode, 11 | env: {} 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/interpreter/lexical-environment/get-dot-path-from-node.ts: -------------------------------------------------------------------------------- 1 | import {isThisExpression} from "../util/node/is-this-expression.js"; 2 | import {THIS_SYMBOL} from "../util/this/this-symbol.js"; 3 | import {isSuperExpression} from "../util/node/is-super-expression.js"; 4 | import {SUPER_SYMBOL} from "../util/super/super-symbol.js"; 5 | import type {EvaluatorOptions} from "../evaluator/evaluator-options.js"; 6 | import type {TS} from "../../type/ts.js"; 7 | 8 | /** 9 | * Gets the path to "dot" into an object with based on the node. For example, if the node is a simple identifier, say, 'foo', the dot path is simply "foo". 10 | * And, if it is a PropertyAccessExpression, that path may be "console.log" for example 11 | */ 12 | export function getDotPathFromNode(options: EvaluatorOptions): string | undefined { 13 | const {node, evaluate, typescript} = options; 14 | if (typescript.isIdentifier(node)) { 15 | return node.text; 16 | } else if (typescript.isPrivateIdentifier?.(node)) { 17 | return node.text; 18 | } else if (isThisExpression(node, typescript)) { 19 | return THIS_SYMBOL; 20 | } else if (isSuperExpression(node, typescript)) { 21 | return SUPER_SYMBOL; 22 | } else if (typescript.isParenthesizedExpression(node)) { 23 | return getDotPathFromNode({...options, node: node.expression}); 24 | } else if ( 25 | typescript.isTypeAssertionExpression?.(node) || 26 | (!("isTypeAssertionExpression" in typescript) && (typescript as {isTypeAssertion: typeof TS.isTypeAssertionExpression}).isTypeAssertion(node)) 27 | ) { 28 | return getDotPathFromNode({...options, node: node.expression}); 29 | } else if (typescript.isPropertyAccessExpression(node)) { 30 | let leftHand = getDotPathFromNode({...options, node: node.expression}); 31 | if (leftHand == null) leftHand = evaluate.expression(node.expression, options) as string; 32 | let rightHand = getDotPathFromNode({...options, node: node.name}); 33 | if (rightHand == null) rightHand = evaluate.expression(node.name, options) as string; 34 | 35 | if (leftHand == null || rightHand == null) return undefined; 36 | return `${leftHand}.${rightHand}`; 37 | } else if (typescript.isElementAccessExpression(node)) { 38 | let leftHand = getDotPathFromNode({...options, node: node.expression}); 39 | if (leftHand == null) leftHand = evaluate.expression(node.expression, options) as string; 40 | const rightHand = evaluate.expression(node.argumentExpression, options) as string; 41 | 42 | if (leftHand == null || rightHand == null) return undefined; 43 | return `${leftHand}.${rightHand}`; 44 | } else if (typescript.isFunctionDeclaration(node)) { 45 | if (node.name == null) return undefined; 46 | return node.name.text; 47 | } 48 | 49 | return undefined; 50 | } 51 | -------------------------------------------------------------------------------- /src/interpreter/lexical-environment/i-create-lexical-environment-options.ts: -------------------------------------------------------------------------------- 1 | import type {IEnvironment} from "../environment/i-environment.js"; 2 | import type {EvaluatePolicySanitized} from "../policy/evaluate-policy.js"; 3 | import type {TS} from "../../type/ts.js"; 4 | 5 | export interface ICreateLexicalEnvironmentOptions { 6 | startingNode: TS.Node; 7 | inputEnvironment: IEnvironment; 8 | policy: EvaluatePolicySanitized; 9 | } 10 | -------------------------------------------------------------------------------- /src/interpreter/lexical-environment/i-set-in-lexical-environment-options.ts: -------------------------------------------------------------------------------- 1 | import type {Literal} from "../literal/literal.js"; 2 | import type {LexicalEnvironment} from "./lexical-environment.js"; 3 | import type {ReportingOptionsSanitized} from "../reporting/i-reporting-options.js"; 4 | import type {TS} from "../../type/ts.js"; 5 | 6 | export interface ISetInLexicalEnvironmentOptions { 7 | environment: LexicalEnvironment; 8 | path: string; 9 | value: Literal; 10 | reporting: ReportingOptionsSanitized; 11 | node: TS.Node; 12 | newBinding?: boolean; 13 | } 14 | -------------------------------------------------------------------------------- /src/interpreter/literal/literal.ts: -------------------------------------------------------------------------------- 1 | export const enum LiteralFlagKind { 2 | CALL 3 | } 4 | 5 | export const LAZY_CALL_FLAG = "___lazyCallFlag"; 6 | 7 | export interface LazyCall { 8 | [LAZY_CALL_FLAG]: LiteralFlagKind; 9 | invoke(...args: Literal[]): Literal; 10 | } 11 | 12 | /** 13 | * Returns true if the given literal is a lazy call 14 | */ 15 | export function isLazyCall(literal: Literal): literal is LazyCall { 16 | return literal != null && typeof literal === "object" && LAZY_CALL_FLAG in literal; 17 | } 18 | 19 | export type Literal = object | CallableFunction | string | number | boolean | symbol | bigint | null | undefined; 20 | export interface LiteralMatch { 21 | literal: Literal; 22 | } 23 | export type IndexLiteralKey = string; 24 | export type IndexLiteral = Record; 25 | 26 | /** 27 | * Stringifies the given literal 28 | */ 29 | export function stringifyLiteral(literal: Literal): string { 30 | if (literal === undefined) return "undefined"; 31 | else if (literal === null) return "null"; 32 | else if (typeof literal === "string") return `"${literal}"`; 33 | // eslint-disable-next-line @typescript-eslint/no-base-to-string 34 | return literal.toString(); 35 | } 36 | -------------------------------------------------------------------------------- /src/interpreter/logger/log-level.ts: -------------------------------------------------------------------------------- 1 | export const enum LogLevelKind { 2 | SILENT = 0, 3 | INFO = 1, 4 | VERBOSE = 2, 5 | DEBUG = 3 6 | } 7 | -------------------------------------------------------------------------------- /src/interpreter/policy/console/console-map.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention */ 2 | import {PolicyTrapKind} from "../policy-trap-kind.js"; 3 | import type {TrapConditionMap} from "../trap-condition-map.js"; 4 | import type {NodeBuiltInsAndGlobals} from "../../environment/node/node-built-ins-and-globals.js"; 5 | 6 | /** 7 | * A Map between built-in modules (as well as 'console' and the operations that print to console 8 | */ 9 | export const CONSOLE_MAP: TrapConditionMap = { 10 | "node:console": "console", 11 | console: { 12 | [PolicyTrapKind.APPLY]: true 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/interpreter/policy/console/is-console-operation.ts: -------------------------------------------------------------------------------- 1 | import type {PolicyProxyHookOptions} from "../../proxy/policy-proxy-hook.js"; 2 | import {isTrapConditionMet} from "../is-trap-condition-met.js"; 3 | import {CONSOLE_MAP} from "./console-map.js"; 4 | import type {NodeBuiltInsAndGlobals} from "../../environment/node/node-built-ins-and-globals.js"; 5 | 6 | /** 7 | * Returns true if the given item represents an operation that prints to console 8 | */ 9 | export function isConsoleOperation(item: PolicyProxyHookOptions): boolean { 10 | return isTrapConditionMet(CONSOLE_MAP, true, item); 11 | } 12 | -------------------------------------------------------------------------------- /src/interpreter/policy/evaluate-policy.ts: -------------------------------------------------------------------------------- 1 | export interface EvaluateIOPolicy { 2 | read: boolean; 3 | write: boolean; 4 | } 5 | 6 | export interface EvaluateProcessPolicy { 7 | exit: boolean; 8 | spawnChild: boolean; 9 | } 10 | 11 | export interface EvaluatePolicy { 12 | io: boolean | EvaluateIOPolicy; 13 | process: boolean | EvaluateProcessPolicy; 14 | network: boolean; 15 | console: boolean; 16 | deterministic: boolean; 17 | maxOps: number; 18 | maxOpDuration: number; 19 | } 20 | 21 | export interface EvaluatePolicySanitized { 22 | io: EvaluateIOPolicy; 23 | process: EvaluateProcessPolicy; 24 | network: boolean; 25 | console: boolean; 26 | deterministic: boolean; 27 | maxOps: number; 28 | maxOpDuration: number; 29 | } 30 | -------------------------------------------------------------------------------- /src/interpreter/policy/io/is-io-read.ts: -------------------------------------------------------------------------------- 1 | import {IO_MAP} from "./io-map.js"; 2 | import {isTrapConditionMet} from "../is-trap-condition-met.js"; 3 | import type {PolicyProxyHookOptions} from "../../proxy/policy-proxy-hook.js"; 4 | import type {NodeBuiltInsAndGlobals} from "../../environment/node/node-built-ins-and-globals.js"; 5 | 6 | /** 7 | * Returns true if the given member represents a READ operation from IO 8 | */ 9 | export function isIoRead(item: PolicyProxyHookOptions): boolean { 10 | return isTrapConditionMet(IO_MAP, "read", item); 11 | } 12 | -------------------------------------------------------------------------------- /src/interpreter/policy/io/is-io-write.ts: -------------------------------------------------------------------------------- 1 | import type {PolicyProxyHookOptions} from "../../proxy/policy-proxy-hook.js"; 2 | import {isTrapConditionMet} from "../is-trap-condition-met.js"; 3 | import {IO_MAP} from "./io-map.js"; 4 | import type {NodeBuiltInsAndGlobals} from "../../environment/node/node-built-ins-and-globals.js"; 5 | 6 | /** 7 | * Returns true if the given member represents a WRITE operation from IO 8 | */ 9 | export function isIoWrite(item: PolicyProxyHookOptions): boolean { 10 | return isTrapConditionMet(IO_MAP, "write", item); 11 | } 12 | -------------------------------------------------------------------------------- /src/interpreter/policy/network/is-network-operation.ts: -------------------------------------------------------------------------------- 1 | import type {PolicyProxyHookOptions} from "../../proxy/policy-proxy-hook.js"; 2 | import {isTrapConditionMet} from "../is-trap-condition-met.js"; 3 | import {NETWORK_MAP} from "./network-map.js"; 4 | import type {NodeBuiltInsAndGlobals} from "../../environment/node/node-built-ins-and-globals.js"; 5 | 6 | /** 7 | * Returns true if the given item represents a network operation 8 | */ 9 | export function isNetworkOperation(item: PolicyProxyHookOptions): boolean { 10 | return isTrapConditionMet(NETWORK_MAP, true, item); 11 | } 12 | -------------------------------------------------------------------------------- /src/interpreter/policy/nondeterministic/is-nondeterministic.ts: -------------------------------------------------------------------------------- 1 | import type {PolicyProxyHookOptions} from "../../proxy/policy-proxy-hook.js"; 2 | import {NONDETERMINISTIC_MAP} from "./nondeterministic-map.js"; 3 | import {isTrapConditionMet} from "../is-trap-condition-met.js"; 4 | import type {NodeBuiltInsAndGlobals} from "../../environment/node/node-built-ins-and-globals.js"; 5 | 6 | /** 7 | * Returns true if the given path represents something that is nondeterministic. 8 | */ 9 | export function isNonDeterministic(item: PolicyProxyHookOptions): boolean { 10 | return isTrapConditionMet(NONDETERMINISTIC_MAP, true, item); 11 | } 12 | -------------------------------------------------------------------------------- /src/interpreter/policy/nondeterministic/nondeterministic-map.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention */ 2 | import {PolicyTrapKind} from "../policy-trap-kind.js"; 3 | import type {TrapConditionMap} from "../trap-condition-map.js"; 4 | import {NETWORK_MAP} from "../network/network-map.js"; 5 | import type {NodeBuiltInsAndGlobals} from "../../environment/node/node-built-ins-and-globals.js"; 6 | 7 | /** 8 | * A Map between built-in identifiers and the members that produce non-deterministic results. 9 | */ 10 | export const NONDETERMINISTIC_MAP: TrapConditionMap = { 11 | // Any network operation will always be non-deterministic 12 | ...NETWORK_MAP, 13 | Math: { 14 | random: { 15 | [PolicyTrapKind.APPLY]: true 16 | } 17 | }, 18 | Date: { 19 | now: { 20 | [PolicyTrapKind.APPLY]: true 21 | }, 22 | // Dates that receive no arguments are nondeterministic since they care about "now" and will evaluate to a new value for each invocation 23 | [PolicyTrapKind.CONSTRUCT]: (...args) => args.length === 0 && !(args[0] instanceof Date) 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/interpreter/policy/policy-trap-kind.ts: -------------------------------------------------------------------------------- 1 | export const enum PolicyTrapKind { 2 | GET = "__$$_PROXY_GET", 3 | APPLY = "__$$_PROXY_APPLY", 4 | CONSTRUCT = "__$$_PROXY_CONSTRUCT" 5 | } 6 | 7 | /** 8 | * Stringifies the given PolicyTrapKind on the given path 9 | */ 10 | export function stringifyPolicyTrapKindOnPath(kind: PolicyTrapKind, path: string): string { 11 | switch (kind) { 12 | case PolicyTrapKind.GET: 13 | return `get ${path}`; 14 | 15 | case PolicyTrapKind.APPLY: 16 | return `${path}(...)`; 17 | 18 | case PolicyTrapKind.CONSTRUCT: 19 | return `new ${path}(...)`; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/interpreter/policy/process/is-process-exit-operation.ts: -------------------------------------------------------------------------------- 1 | import type {PolicyProxyHookOptions} from "../../proxy/policy-proxy-hook.js"; 2 | import {isTrapConditionMet} from "../is-trap-condition-met.js"; 3 | import {PROCESS_MAP} from "./process-map.js"; 4 | import type {NodeBuiltInsAndGlobals} from "../../environment/node/node-built-ins-and-globals.js"; 5 | 6 | /** 7 | * Returns true if the given item represents a process operation that exits the process 8 | */ 9 | export function isProcessExitOperation(item: PolicyProxyHookOptions): boolean { 10 | return isTrapConditionMet(PROCESS_MAP, "exit", item); 11 | } 12 | -------------------------------------------------------------------------------- /src/interpreter/policy/process/is-process-spawn-child-operation.ts: -------------------------------------------------------------------------------- 1 | import type {PolicyProxyHookOptions} from "../../proxy/policy-proxy-hook.js"; 2 | import {isTrapConditionMet} from "../is-trap-condition-met.js"; 3 | import {PROCESS_MAP} from "./process-map.js"; 4 | import type {NodeBuiltInsAndGlobals} from "../../environment/node/node-built-ins-and-globals.js"; 5 | 6 | /** 7 | * Returns true if the given item represents a process operation that spawns a child 8 | */ 9 | export function isProcessSpawnChildOperation(item: PolicyProxyHookOptions): boolean { 10 | return isTrapConditionMet(PROCESS_MAP, "spawnChild", item); 11 | } 12 | -------------------------------------------------------------------------------- /src/interpreter/policy/process/process-map.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention */ 2 | import {PolicyTrapKind} from "../policy-trap-kind.js"; 3 | import type {TrapConditionMap} from "../trap-condition-map.js"; 4 | import type {EvaluateProcessPolicy} from "../evaluate-policy.js"; 5 | import type {NodeBuiltInsAndGlobals} from "../../environment/node/node-built-ins-and-globals.js"; 6 | 7 | /** 8 | * A Map between built-in modules (as well as 'process' and the kind of IO operations their members performs 9 | */ 10 | export const PROCESS_MAP: TrapConditionMap = { 11 | "node:process": "process", 12 | process: { 13 | exit: { 14 | [PolicyTrapKind.APPLY]: "exit" 15 | } 16 | }, 17 | 18 | // Everything inside child_process is just one big violation of this policy 19 | "node:child_process": "child_process", 20 | child_process: { 21 | [PolicyTrapKind.APPLY]: "spawnChild" 22 | }, 23 | 24 | "node:cluster": "cluster", 25 | cluster: { 26 | Worker: { 27 | [PolicyTrapKind.CONSTRUCT]: "spawnChild" 28 | } 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/interpreter/policy/trap-condition-map.ts: -------------------------------------------------------------------------------- 1 | import type {PolicyTrapKind} from "./policy-trap-kind.js"; 2 | 3 | export type TrapConditionFunction = (...args: unknown[]) => ConditionType; 4 | export type TrapCondition = ConditionType | TrapConditionFunction; 5 | 6 | export type PolicyTrapKindToTrapConditionMap = { 7 | [key in PolicyTrapKind]?: TrapCondition; 8 | }; 9 | 10 | export type TrapConditionMap = { 11 | [Key in keyof T]?: TrapConditionMapValue; 12 | }; 13 | 14 | export type TrapConditionMemberMap = { 15 | [Key in keyof T]?: TrapConditionMapValue; 16 | }; 17 | 18 | export type TrapConditionMapValue = 19 | | TrapCondition 20 | | TrapConditionMemberMap 21 | | PolicyTrapKindToTrapConditionMap 22 | 23 | /** 24 | * Useful if two modules are identical and should follow the same rules 25 | */ 26 | | keyof Parent 27 | | undefined; 28 | 29 | /** 30 | * Returns true if the given item is a TrapCondition 31 | */ 32 | export function isTrapCondition(item: unknown, condition: ConditionType): item is TrapCondition { 33 | // noinspection SuspiciousTypeOfGuard 34 | return typeof item === typeof condition || typeof item === "function"; 35 | } 36 | 37 | /** 38 | * Returns true if the given item is a TrapCondition 39 | */ 40 | export function isTrapConditionFunction(item: TrapConditionMapValue): item is TrapConditionFunction { 41 | return typeof item === "function"; 42 | } 43 | -------------------------------------------------------------------------------- /src/interpreter/proxy/i-create-policy-proxy-options.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatePolicySanitized} from "../policy/evaluate-policy.js"; 2 | import type {PolicyProxyHook} from "./policy-proxy-hook.js"; 3 | 4 | export interface ICreatePolicyProxyOptions { 5 | item: T; 6 | scope: string; 7 | policy: EvaluatePolicySanitized; 8 | hook: PolicyProxyHook; 9 | } 10 | -------------------------------------------------------------------------------- /src/interpreter/proxy/policy-proxy-hook.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluationErrorIntent} from "../error/evaluation-error/evaluation-error-intent.js"; 2 | import type {EvaluatePolicySanitized} from "../policy/evaluate-policy.js"; 3 | import type {PolicyTrapKind} from "../policy/policy-trap-kind.js"; 4 | 5 | export interface IPolicyProxyHookOptions { 6 | kind: PolicyTrapKind; 7 | policy: EvaluatePolicySanitized; 8 | path: string; 9 | } 10 | 11 | export interface IPolicyProxyGetHookOptions extends IPolicyProxyHookOptions { 12 | kind: PolicyTrapKind.GET; 13 | target: T; 14 | } 15 | 16 | export interface IPolicyProxyConstructHookOptions extends IPolicyProxyHookOptions { 17 | kind: PolicyTrapKind.CONSTRUCT; 18 | target: T; 19 | argArray: unknown[]; 20 | newTarget: unknown; 21 | } 22 | 23 | export interface IPolicyProxyApplyHookOptions extends IPolicyProxyHookOptions { 24 | kind: PolicyTrapKind.APPLY; 25 | target: T; 26 | thisArg: unknown; 27 | argArray: unknown[]; 28 | } 29 | 30 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 31 | export type PolicyProxyHookOptions> = IPolicyProxyGetHookOptions | IPolicyProxyApplyHookOptions | IPolicyProxyConstructHookOptions; 32 | export type PolicyProxyHook = (options: PolicyProxyHookOptions) => boolean | EvaluationErrorIntent; 33 | -------------------------------------------------------------------------------- /src/interpreter/reporting/i-reporting-options.ts: -------------------------------------------------------------------------------- 1 | import type {ReportedErrorSet} from "./reported-error-set.js"; 2 | import type {TS} from "../../type/ts.js"; 3 | 4 | export interface IBindingReportEntry { 5 | path: string; 6 | value: unknown; 7 | node: TS.Node; 8 | } 9 | 10 | export interface ITraversalReportEntry { 11 | node: TS.Node; 12 | } 13 | 14 | export interface IIntermediateResultReportEntry { 15 | node: TS.Expression | TS.PrivateIdentifier; 16 | value: unknown; 17 | } 18 | 19 | export interface IErrorReportEntry { 20 | node: TS.Node; 21 | error: Error; 22 | } 23 | 24 | export type BindingReportCallback = (entry: IBindingReportEntry) => void | Promise; 25 | export type ErrorReportCallback = (entry: IErrorReportEntry) => void | Promise; 26 | export type IntermediateResultReportCallback = (entry: IIntermediateResultReportEntry) => void | Promise; 27 | export type TraversalReportCallback = (entry: ITraversalReportEntry) => void | Promise; 28 | 29 | export interface IReportingOptions { 30 | reportBindings: BindingReportCallback; 31 | reportTraversal: TraversalReportCallback; 32 | reportIntermediateResults: IntermediateResultReportCallback; 33 | reportErrors: ErrorReportCallback; 34 | } 35 | 36 | export type ReportingOptions = Partial; 37 | 38 | export interface ReportingOptionsSanitized extends ReportingOptions { 39 | reportedErrorSet: ReportedErrorSet; 40 | } 41 | -------------------------------------------------------------------------------- /src/interpreter/reporting/reported-error-set.ts: -------------------------------------------------------------------------------- 1 | export type ReportedErrorSet = WeakSet; 2 | 3 | /** 4 | * Creates and returns a Set of Errors that has been seen and has been reported 5 | */ 6 | export function createReportedErrorSet(): ReportedErrorSet { 7 | return new WeakSet(); 8 | } 9 | -------------------------------------------------------------------------------- /src/interpreter/stack/stack.ts: -------------------------------------------------------------------------------- 1 | import type {Literal} from "../literal/literal.js"; 2 | 3 | export interface Stack { 4 | readonly length: number; 5 | readonly lastItem: StackEntry | undefined; 6 | [Symbol.iterator](): IterableIterator; 7 | push(...values: StackEntry[]): number; 8 | pop(): StackEntry | undefined; 9 | } 10 | 11 | export type StackEntry = Literal; 12 | 13 | /** 14 | * Creates a Stack 15 | */ 16 | export function createStack(): Stack { 17 | const stack: StackEntry[] = []; 18 | 19 | return { 20 | /** 21 | * Gets an iterator for the Stack 22 | */ 23 | [Symbol.iterator]() { 24 | return stack[Symbol.iterator](); 25 | }, 26 | 27 | /** 28 | * Gets the length of the Stack 29 | */ 30 | get length() { 31 | return stack.length; 32 | }, 33 | 34 | /** 35 | * Gets the last item of the Stack 36 | */ 37 | get lastItem() { 38 | return stack[stack.length - 1]; 39 | }, 40 | 41 | /** 42 | * Pushes the given StackEntries on to the Stack 43 | */ 44 | push(...values: StackEntry[]) { 45 | return stack.push(...values); 46 | }, 47 | 48 | /** 49 | * Pops the last item from the stack 50 | 51 | */ 52 | pop() { 53 | return stack.pop(); 54 | } 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /src/interpreter/stack/traversal-stack/statement-traversal-stack.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | 3 | export type StatementTraversalStack = TS.SyntaxKind[]; 4 | 5 | /** 6 | * Creates a StatementTraversalStack 7 | */ 8 | export function createStatementTraversalStack(): StatementTraversalStack { 9 | return []; 10 | } 11 | -------------------------------------------------------------------------------- /src/interpreter/util/array/ensure-array.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Ensures that the given item is in fact an array 3 | */ 4 | export function ensureArray(item: T | T[]): T[] { 5 | return Array.isArray(item) ? item : [item]; 6 | } 7 | -------------------------------------------------------------------------------- /src/interpreter/util/break/break-symbol.ts: -------------------------------------------------------------------------------- 1 | export const BREAK_SYMBOL = "[break]"; 2 | -------------------------------------------------------------------------------- /src/interpreter/util/class/generate-class-declaration.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-implied-eval */ 2 | import type {IGenerateClassDeclarationOptions} from "./i-generate-class-declaration-options.js"; 3 | 4 | /** 5 | * A function that uses 'new Function' to auto-generate a class with a dynamic name and extended type 6 | */ 7 | export function generateClassDeclaration({ 8 | name, 9 | extendedType, 10 | ctor = () => { 11 | // Noop 12 | } 13 | }: Partial): CallableFunction { 14 | if (extendedType == null) { 15 | return new Function( 16 | "ctor", 17 | `return class ${name ?? ""} {constructor () {const ctorReturnValue = ctor.call(this, ...arguments); if (ctorReturnValue != null) return ctorReturnValue;}}` 18 | )(ctor); 19 | } else { 20 | return new Function( 21 | "extendedType", 22 | "ctor", 23 | `return class ${ 24 | name ?? "" 25 | } extends extendedType {constructor () {super(...arguments); const ctorReturnValue = ctor.call(this, ...arguments); if (ctorReturnValue != null) return ctorReturnValue;}}` 26 | )(extendedType, ctor); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/interpreter/util/class/i-generate-class-declaration-options.ts: -------------------------------------------------------------------------------- 1 | import type {Literal} from "../../literal/literal.js"; 2 | 3 | export interface IGenerateClassDeclarationOptions { 4 | name: string; 5 | extendedType: Literal; 6 | ctor: CallableFunction; 7 | } 8 | -------------------------------------------------------------------------------- /src/interpreter/util/continue/continue-symbol.ts: -------------------------------------------------------------------------------- 1 | export const CONTINUE_SYMBOL = "[continue]"; 2 | -------------------------------------------------------------------------------- /src/interpreter/util/declaration/get-declaration-name.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluatorOptions} from "../../evaluator/evaluator-options.js"; 2 | import {UnexpectedNodeError} from "../../error/unexpected-node-error/unexpected-node-error.js"; 3 | import type {TS} from "../../../type/ts.js"; 4 | import type {EvaluationError} from "../../error/evaluation-error/evaluation-error.js"; 5 | 6 | /** 7 | * Gets the name of the given declaration 8 | */ 9 | export function getDeclarationName(options: EvaluatorOptions): string | number | EvaluationError | undefined { 10 | const {node, evaluate, environment, typescript, throwError} = options; 11 | const name = typescript.getNameOfDeclaration(node); 12 | if (name == null) return undefined; 13 | 14 | if (typescript.isIdentifier(name)) { 15 | return name.text; 16 | } else if (typescript.isPrivateIdentifier?.(name)) { 17 | return name.text; 18 | } else if (typescript.isStringLiteralLike(name)) { 19 | return name.text; 20 | } else if (typescript.isNumericLiteral(name)) { 21 | return Number(name.text); 22 | } else if (typescript.isComputedPropertyName(name)) { 23 | return evaluate.expression(name.expression, options) as ReturnType; 24 | } else { 25 | return throwError(new UnexpectedNodeError({node: name, environment, typescript})); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/interpreter/util/declaration/is-declaration.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | 3 | /** 4 | * Returns true if the given Node is a Declaration 5 | * Uses an internal non-exposed Typescript helper to decide whether or not the Node is a declaration 6 | */ 7 | export function isDeclaration(node: TS.Node, typescript: typeof TS): node is TS.Declaration { 8 | return (typescript as unknown as {isDeclaration(node: TS.Node): boolean}).isDeclaration(node); 9 | } 10 | 11 | export function isNamedDeclaration(node: TS.Node | TS.NamedDeclaration, typescript: typeof TS): node is TS.NamedDeclaration { 12 | if (typescript.isPropertyAccessExpression(node)) return false; 13 | return "name" in node && node.name != null; 14 | } 15 | -------------------------------------------------------------------------------- /src/interpreter/util/descriptor/merge-descriptors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Merges all of the given descriptors 3 | */ 4 | export function mergeDescriptors(a: A): A; 5 | export function mergeDescriptors(a: A, b: B): A & B; 6 | export function mergeDescriptors(a: A, b: B, c: C): A & B & C; 7 | export function mergeDescriptors(a: A, b?: B, c?: C): A & B & C { 8 | const newObj = {} as A & B & C; 9 | const normalizedB = b ?? {}; 10 | const normalizedC = c ?? {}; 11 | [a, normalizedB, normalizedC].forEach(item => Object.defineProperties(newObj, Object.getOwnPropertyDescriptors(item))); 12 | return newObj; 13 | } 14 | -------------------------------------------------------------------------------- /src/interpreter/util/expression/expression-contains-super-keyword.ts: -------------------------------------------------------------------------------- 1 | import {isSuperExpression} from "../node/is-super-expression.js"; 2 | import type {TS} from "../../../type/ts.js"; 3 | 4 | /** 5 | * Returns true if the given expression contains a 'super' keyword 6 | */ 7 | export function expressionContainsSuperKeyword(expression: TS.Expression | TS.PrivateIdentifier, typescript: typeof TS): boolean { 8 | if (isSuperExpression(expression, typescript)) return true; 9 | else if (typescript.isPropertyAccessExpression(expression)) { 10 | return expressionContainsSuperKeyword(expression.expression, typescript) || expressionContainsSuperKeyword(expression.name, typescript); 11 | } else if (typescript.isElementAccessExpression(expression)) { 12 | return expressionContainsSuperKeyword(expression.expression, typescript) || expressionContainsSuperKeyword(expression.argumentExpression, typescript); 13 | } else if (typescript.isParenthesizedExpression(expression)) return expressionContainsSuperKeyword(expression.expression, typescript); 14 | else if (typescript.isAsExpression(expression)) return expressionContainsSuperKeyword(expression.expression, typescript); 15 | else if ( 16 | typescript.isTypeAssertionExpression?.(expression) || 17 | (!("isTypeAssertionExpression" in typescript) && (typescript as {isTypeAssertion: typeof TS.isTypeAssertionExpression}).isTypeAssertion(expression)) 18 | ) { 19 | return expressionContainsSuperKeyword(expression.expression, typescript); 20 | } else { 21 | return false; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/interpreter/util/expression/is-expression.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | 3 | /** 4 | * Returns true if the given Node is an Expression. 5 | * Uses an internal non-exposed Typescript helper to decide whether or not the Node is an Expression 6 | */ 7 | export function isExpression(node: TS.Node, typescript: typeof TS): node is TS.Expression { 8 | return (typescript as unknown as {isExpressionNode(node: TS.Node): boolean}).isExpressionNode(node) || typescript.isIdentifier(node) || typescript.isPrivateIdentifier?.(node); 9 | } 10 | -------------------------------------------------------------------------------- /src/interpreter/util/flags/is-var-declaration.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | 3 | /** 4 | * Returns true if the given VariableDeclarationList is declared with a 'var' keyword 5 | */ 6 | export function isVarDeclaration(declarationList: TS.VariableDeclarationList, typescript: typeof TS): boolean { 7 | return declarationList.flags !== typescript.NodeFlags.Const && declarationList.flags !== typescript.NodeFlags.Let; 8 | } 9 | -------------------------------------------------------------------------------- /src/interpreter/util/function/is-bind-call-apply.ts: -------------------------------------------------------------------------------- 1 | import type {LexicalEnvironment} from "../../lexical-environment/lexical-environment.js"; 2 | import {getFromLexicalEnvironment} from "../../lexical-environment/lexical-environment.js"; 3 | 4 | /** 5 | * Returns true if the given function is either Function.prototype.bind, Function.prototype.call, or Function.prototype.apply 6 | */ 7 | export function isBindCallApply(func: CallableFunction, environment?: LexicalEnvironment): boolean { 8 | switch (func) { 9 | case Function.prototype.bind: 10 | case Function.prototype.call: 11 | case Function.prototype.apply: 12 | return true; 13 | } 14 | 15 | if (environment != null) { 16 | const _Function = getFromLexicalEnvironment(undefined, environment, "Function")!.literal as CallableFunction; 17 | switch (func) { 18 | case _Function.prototype.bind: 19 | case _Function.prototype.call: 20 | case _Function.prototype.apply: 21 | return true; 22 | } 23 | } 24 | return false; 25 | } 26 | -------------------------------------------------------------------------------- /src/interpreter/util/iterable/is-iterable.ts: -------------------------------------------------------------------------------- 1 | import type {Literal} from "../../literal/literal.js"; 2 | 3 | /** 4 | * Returns true if the given item is an Iterable 5 | */ 6 | export function isIterable(item: Literal): item is Iterable { 7 | return item != null && (item as Iterable)[Symbol.iterator] != null; 8 | } 9 | -------------------------------------------------------------------------------- /src/interpreter/util/loader/optional-peer-dependency-loader.ts: -------------------------------------------------------------------------------- 1 | import type {JSDOM} from "../../../type/jsdom.js"; 2 | import {requireModule} from "./require-module.js"; 3 | 4 | /** 5 | * The jsdom module is optionally imported on-demand as needed 6 | */ 7 | let jsdomModule: typeof JSDOM | undefined; 8 | 9 | export function loadJsdom(required: true): typeof JSDOM; 10 | export function loadJsdom(required: false): typeof JSDOM | undefined; 11 | export function loadJsdom(required?: boolean): typeof JSDOM | undefined; 12 | export function loadJsdom(required = false): typeof JSDOM | undefined { 13 | return (jsdomModule ??= loadModules("evaluate against a browser environment", required, "jsdom")); 14 | } 15 | 16 | function loadModules(description: string, required: boolean, moduleSpecifier = description): T | undefined { 17 | try { 18 | return requireModule(moduleSpecifier) as T; 19 | } catch { 20 | if (required) { 21 | throw new ReferenceError(`You must install the peer dependency '${moduleSpecifier}' in order to ${description} with ts-evaluator`); 22 | } 23 | return undefined; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/interpreter/util/loader/require-module.ts: -------------------------------------------------------------------------------- 1 | import {createRequire} from "module"; 2 | 3 | // Until import.meta.resolve becomes stable, we'll have to do this instead 4 | export const requireModule = createRequire(import.meta.url); 5 | -------------------------------------------------------------------------------- /src/interpreter/util/modifier/has-modifier.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | 3 | /** 4 | * Returns true if the given Node has the given kind of Modifier 5 | */ 6 | export function hasModifier(node: TS.Node | TS.ModifierLike[], modifier: TS.Modifier["kind"]): boolean { 7 | const modifiers = Array.isArray(node) ? node : "modifiers" in node && Array.isArray(node.modifiers) ? (node.modifiers as readonly TS.ModifierLike[]) : undefined; 8 | return Boolean(modifiers?.some(m => m.kind === modifier)); 9 | } 10 | -------------------------------------------------------------------------------- /src/interpreter/util/module/get-resolved-module-name.ts: -------------------------------------------------------------------------------- 1 | import path from "crosspath"; 2 | import type {TS} from "../../../type/ts.js"; 3 | import type {EvaluatorOptions} from "../../evaluator/evaluator-options.js"; 4 | 5 | export function getResolvedModuleName(moduleSpecifier: string, options: EvaluatorOptions): string { 6 | const {node, typescript} = options; 7 | if (!typescript.isExternalModuleNameRelative(moduleSpecifier)) { 8 | return moduleSpecifier; 9 | } 10 | 11 | const parentPath = node.getSourceFile().fileName; 12 | return path.join(path.dirname(parentPath), moduleSpecifier); 13 | } 14 | -------------------------------------------------------------------------------- /src/interpreter/util/node/get-inner-node.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | 3 | export function getInnerNode(node: TS.Node, typescript: typeof TS): T { 4 | if (typescript.isParenthesizedExpression(node)) return getInnerNode(node.expression, typescript); 5 | else if (typescript.isAsExpression(node)) return getInnerNode(node.expression, typescript); 6 | else if ( 7 | typescript.isTypeAssertionExpression?.(node) || 8 | (!("isTypeAssertionExpression" in typescript) && (typescript as {isTypeAssertion: typeof TS.isTypeAssertionExpression}).isTypeAssertion(node)) 9 | ) { 10 | return getInnerNode(node.expression, typescript); 11 | } else { 12 | return node as T; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/interpreter/util/node/is-boolean-literal.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | 3 | /** 4 | * Returns true if the given node is a BooleanLiteral 5 | */ 6 | export function isBooleanLiteral(node: {kind: TS.SyntaxKind}, typescript: typeof TS): node is TS.Token { 7 | return node.kind === typescript.SyntaxKind.TrueKeyword || node.kind === typescript.SyntaxKind.FalseKeyword; 8 | } 9 | -------------------------------------------------------------------------------- /src/interpreter/util/node/is-node.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | 3 | export function isTypescriptNode(node: unknown): node is T { 4 | return node != null && typeof node === "object" && "kind" in node && "flags" in node && "pos" in node && "end" in node; 5 | } 6 | -------------------------------------------------------------------------------- /src/interpreter/util/node/is-null-literal.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | 3 | /** 4 | * Returns true if the given node is a NullLiteral 5 | */ 6 | export function isNullLiteral(node: TS.Node, typescript: typeof TS): node is TS.NullLiteral { 7 | return node.kind === typescript.SyntaxKind.NullKeyword; 8 | } 9 | -------------------------------------------------------------------------------- /src/interpreter/util/node/is-super-expression.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | 3 | /** 4 | * Returns true if the given node is a SuperExpression 5 | */ 6 | export function isSuperExpression(node: TS.Node, typescript: typeof TS): node is TS.SuperExpression { 7 | return node.kind === typescript.SyntaxKind.SuperKeyword; 8 | } 9 | -------------------------------------------------------------------------------- /src/interpreter/util/node/is-this-expression.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | 3 | /** 4 | * Returns true if the given node is a ThisExpression 5 | */ 6 | export function isThisExpression(node: TS.Node, typescript: typeof TS): node is TS.ThisExpression { 7 | return node.kind === typescript.SyntaxKind.ThisKeyword; 8 | } 9 | -------------------------------------------------------------------------------- /src/interpreter/util/node/modifier-util.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | 3 | export function canHaveModifiers(node: TS.Node, typescript: typeof TS): node is TS.HasModifiers { 4 | if ("canHaveModifiers" in typescript) { 5 | return typescript.canHaveModifiers(node); 6 | } else { 7 | return true; 8 | } 9 | } 10 | export function getModifiers(node: TS.HasModifiers, typescript: typeof TS): readonly TS.Modifier[] | undefined { 11 | if ("getModifiers" in typescript) { 12 | return typescript.getModifiers(node); 13 | } else { 14 | return node.modifiers?.filter(modifier => !("expression" in modifier)) as readonly TS.Modifier[] | undefined; 15 | } 16 | } 17 | 18 | export function canHaveDecorators(node: TS.Node, typescript: typeof TS): node is TS.HasDecorators { 19 | if ("canHaveDecorators" in typescript) { 20 | return typescript.canHaveDecorators(node); 21 | } else { 22 | return true; 23 | } 24 | } 25 | export function getDecorators(node: TS.HasDecorators, typescript: typeof TS): readonly TS.Decorator[] | undefined { 26 | if ("getDecorators" in typescript) { 27 | return typescript.getDecorators(node); 28 | } else { 29 | const legacyDecorators = "decorators" in node && Array.isArray(node.decorators) ? node.decorators : undefined; 30 | const decoratorModifierLikes = node.modifiers?.filter(modifier => "expression" in modifier) as readonly TS.Decorator[] | undefined; 31 | return [...(legacyDecorators ?? []), ...(decoratorModifierLikes ?? [])]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/interpreter/util/object/subtract.ts: -------------------------------------------------------------------------------- 1 | export type Subtract> = { 2 | [Key in Exclude]: T[Key]; 3 | }; 4 | 5 | /** 6 | * Excludes the properties of B from A 7 | */ 8 | export function subtract>(a: A, b: B): Subtract { 9 | const newA = {} as Exclude; 10 | Object.getOwnPropertyNames(a).forEach(name => { 11 | if (!(name in b)) { 12 | Object.defineProperty(newA, name, Object.getOwnPropertyDescriptor(a, name)!); 13 | } 14 | }); 15 | return newA; 16 | } 17 | -------------------------------------------------------------------------------- /src/interpreter/util/path/generate-random-path.ts: -------------------------------------------------------------------------------- 1 | export interface RandomPathOptions { 2 | extension: string; 3 | prefix: string; 4 | suffix: string; 5 | } 6 | export function generateRandomPath({extension = "", prefix = "__#auto-generated-", suffix = String(Math.floor(Math.random() * 100000))}: Partial = {}) { 7 | return `${prefix}${suffix}${extension}`; 8 | } 9 | -------------------------------------------------------------------------------- /src/interpreter/util/print/print-with-deep-removed-properties.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Prints the given Node 3 | */ 4 | export function printWithDeepRemovedProperties(node: T | undefined, ...properties: string[]): void { 5 | if (node === undefined) return console.log(undefined); 6 | if (properties.length === 0) return console.log(node); 7 | 8 | console.log(deepCloneWithRemovedProperty(node, properties as (keyof T)[])); 9 | } 10 | 11 | /** 12 | * Deep-clones the given object, and removes the provided property names along the way 13 | * from property values 14 | */ 15 | function deepCloneWithRemovedProperty(obj: T, properties: (keyof T)[], seenNestedObjects = new Set<{}>()): U { 16 | if (seenNestedObjects.has(obj)) return "[Circular]" as unknown as U; 17 | 18 | seenNestedObjects.add(obj); 19 | const shallowClone = Array.isArray(obj) ? [...obj] : {...obj}; 20 | properties.forEach(property => delete (shallowClone as T)[property]); 21 | 22 | if (Array.isArray(shallowClone)) { 23 | shallowClone.forEach((item, index) => { 24 | if (typeof item === "object" && item != null) { 25 | shallowClone[index] = deepCloneWithRemovedProperty(item as T, properties, seenNestedObjects); 26 | } else { 27 | shallowClone[index] = item; 28 | } 29 | }); 30 | } else { 31 | Object.entries(shallowClone).forEach(([key, value]) => { 32 | if (typeof value === "object" && value != null) { 33 | shallowClone[key as keyof T] = deepCloneWithRemovedProperty(value, properties, seenNestedObjects); 34 | } else { 35 | shallowClone[key as keyof T] = value; 36 | } 37 | }); 38 | } 39 | return shallowClone as unknown as U; 40 | } 41 | -------------------------------------------------------------------------------- /src/interpreter/util/proxy/can-be-observed.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if the provided value is ObjectLike 3 | * 4 | * @param value 5 | * @returns 6 | */ 7 | export function isObjectLike(value: T): boolean { 8 | return value != null && (typeof value === "function" || typeof value === "object"); 9 | } 10 | 11 | /** 12 | * Returns true if the given value can be observed 13 | * 14 | * @param value 15 | * @returns 16 | */ 17 | export function canBeObserved(value: T): boolean { 18 | return isObjectLike(value); 19 | } 20 | -------------------------------------------------------------------------------- /src/interpreter/util/reporting/report-error.ts: -------------------------------------------------------------------------------- 1 | import type {ReportingOptionsSanitized} from "../../reporting/i-reporting-options.js"; 2 | import {EvaluationError} from "../../error/evaluation-error/evaluation-error.js"; 3 | import type {TS} from "../../../type/ts.js"; 4 | 5 | /** 6 | * Reports an error 7 | */ 8 | export function reportError(reporting: ReportingOptionsSanitized, error: Error, node: TS.Node): void { 9 | // Report the error if a reporter is hooked up 10 | if (reporting.reportErrors != null && !reporting.reportedErrorSet.has(error)) { 11 | reporting.reportedErrorSet.add(error); 12 | reporting.reportErrors({ 13 | error: error, 14 | node: error instanceof EvaluationError ? error.node : node 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/interpreter/util/return/return-symbol.ts: -------------------------------------------------------------------------------- 1 | export const RETURN_SYMBOL = "[return]"; 2 | -------------------------------------------------------------------------------- /src/interpreter/util/statement/is-statement.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | 3 | /** 4 | * Returns true if the given Node is a Statement 5 | * Uses an internal non-exposed Typescript helper to decide whether or not the Node is an Expression 6 | */ 7 | export function isStatement(node: TS.Node, typescript: typeof TS): node is TS.Statement { 8 | return (typescript as unknown as {isStatementButNotDeclaration(node: TS.Node): boolean}).isStatementButNotDeclaration(node); 9 | } 10 | -------------------------------------------------------------------------------- /src/interpreter/util/static/in-static-context.ts: -------------------------------------------------------------------------------- 1 | import {hasModifier} from "../modifier/has-modifier.js"; 2 | import type {TS} from "../../../type/ts.js"; 3 | 4 | /** 5 | * Returns true if the given Node exists within a static context 6 | */ 7 | export function inStaticContext(node: TS.Node, typescript: typeof TS): boolean { 8 | let currentNode = node; 9 | while (currentNode != null && !typescript.isSourceFile(currentNode)) { 10 | if (hasModifier(currentNode, typescript.SyntaxKind.StaticKeyword)) return true; 11 | currentNode = currentNode.parent; 12 | } 13 | return false; 14 | } 15 | -------------------------------------------------------------------------------- /src/interpreter/util/super/super-symbol.ts: -------------------------------------------------------------------------------- 1 | export const SUPER_SYMBOL = "super"; 2 | -------------------------------------------------------------------------------- /src/interpreter/util/syntax-kind/stringify-syntax-kind.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../../type/ts.js"; 2 | 3 | /** 4 | * Stringifies the given SyntaxKind 5 | */ 6 | export function stringifySyntaxKind(kind: TS.SyntaxKind, typescript: typeof TS): string { 7 | if (kind === typescript.SyntaxKind.NumericLiteral) return "NumericLiteral"; 8 | return typescript.SyntaxKind[kind]; 9 | } 10 | -------------------------------------------------------------------------------- /src/interpreter/util/this/this-symbol.ts: -------------------------------------------------------------------------------- 1 | export const THIS_SYMBOL = "this"; 2 | -------------------------------------------------------------------------------- /src/interpreter/util/try/try-symbol.ts: -------------------------------------------------------------------------------- 1 | export const TRY_SYMBOL = "[try]"; 2 | -------------------------------------------------------------------------------- /src/interpreter/util/tslib/tslib-util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is ported over from tslib to avoid having it as a runtime dependency 3 | */ 4 | // eslint-disable-next-line @typescript-eslint/naming-convention 5 | export function __decorate(decorators: CallableFunction[], target: T, key?: PropertyKey, desc?: PropertyDescriptor) { 6 | const c = arguments.length; 7 | let r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key!)) : desc; 8 | let d; 9 | 10 | for (let i = decorators.length - 1; i >= 0; i--) if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; 11 | // eslint-disable-next-line no-sequences 12 | return c > 3 && r && Object.defineProperty(target, key!, r), r; 13 | } 14 | 15 | /** 16 | * This is ported over from tslib to avoid having it as a runtime dependency 17 | */ 18 | // eslint-disable-next-line @typescript-eslint/naming-convention 19 | export function __param(paramIndex: number, decorator: CallableFunction): CallableFunction { 20 | return function (target: T, key: PropertyKey) { 21 | decorator(target, key, paramIndex); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/type/file-system.ts: -------------------------------------------------------------------------------- 1 | import type * as fs from "fs"; 2 | 3 | export type ReadonlyFileSystem = Pick; 4 | 5 | export interface SafeReadonlyFileSystem extends ReadonlyFileSystem { 6 | safeStatSync: (path: string) => fs.Stats | undefined; 7 | safeReadFileSync: (path: string) => Buffer | undefined; 8 | } 9 | 10 | export type FileSystem = ReadonlyFileSystem & Pick; 11 | export type SafeFileSystem = SafeReadonlyFileSystem & Pick; 12 | -------------------------------------------------------------------------------- /src/type/jsdom.ts: -------------------------------------------------------------------------------- 1 | import type * as JSDOM from "jsdom"; 2 | export type {JSDOM}; 3 | -------------------------------------------------------------------------------- /src/type/ts.ts: -------------------------------------------------------------------------------- 1 | import type * as TS from "typescript"; 2 | export type {TS}; 3 | -------------------------------------------------------------------------------- /test/array-binding-pattern/array-binding-pattern.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can handle ArrayBindingPatterns in VariableDeclarations. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | (() => { 10 | const [, two] = [1, 2, 3]; 11 | return two; 12 | })(); 13 | `, 14 | "(() =>", 15 | {typescript, useTypeChecker} 16 | ); 17 | 18 | if (!result.success) assert.fail(result.reason.stack); 19 | else assert.deepEqual(result.value, 2); 20 | }); 21 | 22 | test("Can handle ArrayBindingPatterns in VariableDeclarations. #2", "*", (_, {typescript, useTypeChecker}) => { 23 | const {result} = executeProgram( 24 | // language=TypeScript 25 | ` 26 | (() => { 27 | const [{foo}] = [{foo: 2}]; 28 | return foo; 29 | })(); 30 | `, 31 | "(() =>", 32 | {typescript, useTypeChecker} 33 | ); 34 | 35 | if (!result.success) assert.fail(result.reason.stack); 36 | else assert.deepEqual(result.value, 2); 37 | }); 38 | 39 | test("Can handle ArrayBindingPatterns in VariableDeclarations. #3", "*", (_, {typescript, useTypeChecker}) => { 40 | const {result} = executeProgram( 41 | // language=TypeScript 42 | ` 43 | (() => { 44 | const {foo: [, two]} = {foo: [1, 2, 3]} 45 | return two; 46 | })(); 47 | `, 48 | "(() =>", 49 | {typescript, useTypeChecker} 50 | ); 51 | 52 | if (!result.success) assert.fail(result.reason.stack); 53 | else assert.deepEqual(result.value, 2); 54 | }); 55 | 56 | test("Can handle ArrayBindingPatterns in ParameterDeclarations. #1", "*", (_, {typescript, useTypeChecker}) => { 57 | const {result} = executeProgram( 58 | // language=TypeScript 59 | ` 60 | (([, {destructured: alias}]) => { 61 | return alias; 62 | })([1, {destructured: 2}, 3]); 63 | `, 64 | "(([, {destructured: alias}]) =>", 65 | {typescript, useTypeChecker} 66 | ); 67 | 68 | if (!result.success) assert.fail(result.reason.stack); 69 | else assert.deepEqual(result.value, 2); 70 | }); 71 | -------------------------------------------------------------------------------- /test/array-literal-expression/array-literal-expression.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can handle ArrayLiteralExpressions. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | (["foo", "bar"]) 10 | `, 11 | "([", 12 | {typescript, useTypeChecker} 13 | ); 14 | 15 | if (!result.success) assert.fail(result.reason.stack); 16 | else { 17 | assert.deepEqual(result.value as string[], ["foo", "bar"]); 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /test/assignments/assignments.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can evaluate a CallExpression for a function with variable assignments. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | function square (a: number): number { 10 | const alias = a; 11 | const returnValue = alias ** 2; 12 | return returnValue; 13 | } 14 | 15 | square(2); 16 | `, 17 | "square(", 18 | {typescript, useTypeChecker} 19 | ); 20 | 21 | if (!result.success) assert.fail(result.reason.stack); 22 | else assert.deepEqual(result.value, 4); 23 | }); 24 | 25 | test("Can evaluate a CallExpression for a function with variable assignments. #2", "*", (_, {typescript, useTypeChecker}) => { 26 | const {result} = executeProgram( 27 | ` 28 | const mapOfMaps: Map> = new Map(); 29 | 30 | function getMapForKey(key: string): string { 31 | 32 | let innerMap = mapOfMaps.get(key); 33 | if (innerMap == null) { 34 | innerMap = new Map(); 35 | mapOfMaps.set(key, innerMap); 36 | } 37 | 38 | return innerMap; 39 | } 40 | 41 | mapOfMaps.set("foo", new Map()); 42 | getMapForKey("foo"); 43 | `, 44 | "getMapForKey(", 45 | {typescript, useTypeChecker} 46 | ); 47 | 48 | if (!result.success) assert.fail(result.reason.stack); 49 | else assert.deepEqual(result.value, new Map()); 50 | }); 51 | -------------------------------------------------------------------------------- /test/await-expression/await-expression.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can evaluate an AwaitExpression #1", "*", async (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | ` 8 | async function myAsyncFunction (): Promise { 9 | return new Promise(resolve => setTimeout(() => resolve(1000), 1)); 10 | } 11 | 12 | (async () => { 13 | return await myAsyncFunction(); 14 | })(); 15 | `, 16 | "return await myAsyncFunction()", 17 | {typescript, useTypeChecker} 18 | ); 19 | 20 | if (!result.success) assert.fail(result.reason.stack); 21 | else assert.deepEqual(await result.value, 1000); 22 | }); 23 | -------------------------------------------------------------------------------- /test/call-expression/call-bind-apply.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can evaluate a CallExpression that is called with another 'this' value. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | const myObj = { 10 | someProp: 2 11 | }; 12 | 13 | function myFunc (this: typeof myObj, a: number): number { 14 | return a + this.someProp; 15 | } 16 | 17 | myFunc.call(myObj, 2); 18 | `, 19 | "myFunc.call(", 20 | {typescript, useTypeChecker} 21 | ); 22 | 23 | if (!result.success) assert.fail(result.reason.stack); 24 | else assert.deepEqual(result.value, 4); 25 | }); 26 | 27 | test("Can evaluate a CallExpression that is called with another 'this' value. #2", "*", (_, {typescript, useTypeChecker}) => { 28 | const {result} = executeProgram( 29 | // language=TypeScript 30 | ` 31 | const myObj = { 32 | someProp: 2 33 | }; 34 | 35 | function myFunc (this: typeof myObj, a: number): number { 36 | return a + this.someProp; 37 | } 38 | 39 | myFunc.bind(myObj, 2)(); 40 | `, 41 | "myFunc.bind(", 42 | {typescript, useTypeChecker} 43 | ); 44 | 45 | if (!result.success) assert.fail(result.reason.stack); 46 | else assert.deepEqual(result.value, 4); 47 | }); 48 | 49 | test("Can evaluate a CallExpression that is called with another 'this' value. #3", "*", (_, {typescript, useTypeChecker}) => { 50 | const {result} = executeProgram( 51 | // language=TypeScript 52 | ` 53 | const myObj = { 54 | someProp: 2 55 | }; 56 | 57 | function myFunc (this: typeof myObj, a: number): number { 58 | return a + this.someProp; 59 | } 60 | 61 | myFunc.apply(myObj, [2]); 62 | `, 63 | "myFunc.apply(", 64 | {typescript, useTypeChecker} 65 | ); 66 | 67 | if (!result.success) assert.fail(result.reason.stack); 68 | else assert.deepEqual(result.value, 4); 69 | }); 70 | -------------------------------------------------------------------------------- /test/class-expression/class-expression.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can handle ClassExpressions. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | (() => class { 10 | })(); 11 | `, 12 | "(() =>", 13 | {typescript, useTypeChecker} 14 | ); 15 | 16 | if (!result.success) assert.fail(result.reason.stack); 17 | else { 18 | assert(typeof result.value === "function"); 19 | } 20 | }); 21 | 22 | test("Can handle ClassExpressions that extends from other named classes. #1", "*", (_, {typescript, useTypeChecker}) => { 23 | const {result} = executeProgram( 24 | // language=TypeScript 25 | ` 26 | class A { 27 | } 28 | 29 | (() => [A, class extends A {}])(); 30 | `, 31 | "(() =>", 32 | {typescript, useTypeChecker} 33 | ); 34 | 35 | if (!result.success) assert.fail(result.reason.stack); 36 | else if (!Array.isArray(result.value)) assert.fail(); 37 | else { 38 | const [A, B] = result.value; 39 | assert(Object.getPrototypeOf(B) === A); 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /test/conditional-expression/conditional-expression.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can handle ConditionalExpressions. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | // noinspection BadExpressionStatementJS 7 | const {result} = executeProgram( 8 | // language=TypeScript 9 | ` 10 | // noinspection RedundantConditionalExpressionJS 11 | (() => 2 + 2 === 5 ? true : false)() 12 | `, 13 | "(() =>", 14 | {typescript, useTypeChecker} 15 | ); 16 | 17 | if (!result.success) assert.fail(result.reason.stack); 18 | else { 19 | assert.deepEqual(result.value, false); 20 | } 21 | }); 22 | 23 | test("Can handle ConditionalExpressions. #2", "*", (_, {typescript, useTypeChecker}) => { 24 | // noinspection BadExpressionStatementJS 25 | const {result} = executeProgram( 26 | // language=TypeScript 27 | ` 28 | // noinspection RedundantConditionalExpressionJS 29 | (() => 2 + 2 === 4 ? true : false)() 30 | `, 31 | "(() =>", 32 | {typescript, useTypeChecker} 33 | ); 34 | 35 | if (!result.success) assert.fail(result.reason.stack); 36 | else { 37 | assert.deepEqual(result.value, true); 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /test/environment/browser.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can handle a Browser environment. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | (() => { 10 | document.body.setAttribute("foo", "bar"); 11 | return document.body.getAttribute("foo"); 12 | })(); 13 | `, 14 | "(() =>", 15 | { 16 | typescript, 17 | useTypeChecker, 18 | environment: { 19 | preset: "BROWSER" 20 | } 21 | } 22 | ); 23 | 24 | if (!result.success) assert.fail(result.reason.stack); 25 | else { 26 | assert.deepEqual(result.value, "bar"); 27 | } 28 | }); 29 | 30 | test("Can handle a Browser environment. #2", "*", (_, {typescript, useTypeChecker}) => { 31 | const {result} = executeProgram( 32 | // language=TypeScript 33 | ` 34 | (() => { 35 | return window.requestAnimationFrame(() => { 36 | }); 37 | })(); 38 | `, 39 | "(() =>", 40 | { 41 | typescript, 42 | useTypeChecker, 43 | environment: { 44 | preset: "BROWSER" 45 | } 46 | } 47 | ); 48 | 49 | if (!result.success) assert.fail(result.reason.stack); 50 | else { 51 | assert.deepEqual(result.value, 1); 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /test/environment/node-cjs.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import path from "crosspath"; 4 | import {executeProgram} from "../setup/execute-program.js"; 5 | 6 | test("Can handle the '__dirname' and '__filename' meta properties in a CommonJS-based Node environment. #1", "*", (_, {typescript, useTypeChecker}) => { 7 | const {result, setup} = executeProgram( 8 | // language=TypeScript 9 | { 10 | text: ` 11 | (() => { 12 | return {dirname: __dirname, filename: __filename}; 13 | })();`, 14 | fileName: "bar.ts" 15 | }, 16 | "(() =>", 17 | { 18 | cwd: "/Users/someone/development/foo", 19 | typescript, 20 | useTypeChecker, 21 | environment: { 22 | preset: "NODE" 23 | } 24 | } 25 | ); 26 | 27 | if (!result.success) assert.fail(result.reason.stack); 28 | else { 29 | assert.deepEqual(result.value, {dirname: path.native.join(setup.fileStructure.dir.src), filename: path.native.join(setup.fileStructure.dir.src, "bar.ts")}); 30 | } 31 | }); 32 | 33 | test("Can handle the '__dirname' and '__filename' meta properties in a CommonJS-based Node environment. #2", "*", (_, {typescript, useTypeChecker}) => { 34 | const {result, setup} = executeProgram( 35 | // language=TypeScript 36 | { 37 | text: ` 38 | (() => { 39 | return {dirname: __dirname, filename: __filename}; 40 | })();`, 41 | fileName: "bar.ts" 42 | }, 43 | "(() =>", 44 | { 45 | cwd: "/Users/someone/development/foo", 46 | typescript, 47 | useTypeChecker, 48 | environment: { 49 | preset: "NODE_CJS" 50 | } 51 | } 52 | ); 53 | 54 | if (!result.success) assert.fail(result.reason.stack); 55 | else { 56 | assert.deepEqual(result.value, {dirname: path.native.join(setup.fileStructure.dir.src), filename: path.native.join(setup.fileStructure.dir.src, "bar.ts")}); 57 | } 58 | }); 59 | 60 | test("Can handle 'process.cwd()' in a CommonJS-based Node environment. #1", "*", (_, {typescript, useTypeChecker}) => { 61 | const {result} = executeProgram( 62 | // language=TypeScript 63 | ` 64 | (() => { 65 | return process.cwd(); 66 | })(); 67 | `, 68 | "(() =>", 69 | {typescript, useTypeChecker} 70 | ); 71 | 72 | if (!result.success) assert.fail(result.reason.stack); 73 | else { 74 | assert.deepEqual(result.value, process.cwd()); 75 | } 76 | }); 77 | -------------------------------------------------------------------------------- /test/environment/node-esm.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import path from "crosspath"; 4 | import {executeProgram} from "../setup/execute-program.js"; 5 | 6 | test("Can handle the import.meta.url meta property in an ESM-based Node environment. #1", "*", (_, {typescript, useTypeChecker}) => { 7 | const {result, setup} = executeProgram( 8 | // language=TypeScript 9 | { 10 | text: ` 11 | (() => { 12 | return {filename: import.meta.url}; 13 | })();`, 14 | fileName: "bar.ts" 15 | }, 16 | "(() =>", 17 | { 18 | cwd: "/Users/someone/development/foo", 19 | typescript, 20 | useTypeChecker, 21 | environment: { 22 | preset: "NODE_ESM" 23 | } 24 | } 25 | ); 26 | 27 | if (!result.success) assert.fail(result.reason.stack); 28 | else { 29 | assert.deepEqual(result.value, {filename: `file://${path.join(setup.fileStructure.dir.src, "bar.ts")}`}); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /test/error-handling/error-handling.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Errors will be caught and set as the 'reason' property on returned values. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | assert.doesNotThrow(() => 7 | executeProgram( 8 | ` 9 | const foo = require("./somethingthatdoesnotexist.js"); 10 | `, 11 | "foo", 12 | {typescript, useTypeChecker} 13 | ) 14 | ); 15 | }); 16 | -------------------------------------------------------------------------------- /test/for-in/for-in.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can evaluate a CallExpression with a ForInStatement. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | function myFunc (): string { 10 | let str = ""; 11 | let obj = {a: "", b: "", c: ""}; 12 | for (const foo in obj) { 13 | str += foo; 14 | } 15 | return str; 16 | } 17 | 18 | myFunc(); 19 | `, 20 | "myFunc(", 21 | {typescript, useTypeChecker} 22 | ); 23 | 24 | if (!result.success) assert.fail(result.reason.stack); 25 | else assert.deepEqual(result.value, "abc"); 26 | }); 27 | -------------------------------------------------------------------------------- /test/for-of/for-of.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can evaluate a CallExpression with a ForOfStatement. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | function myFunc (): number { 10 | let sum = 0; 11 | for (const foo of [1, 2, 3]) { 12 | sum += foo; 13 | } 14 | return sum; 15 | } 16 | 17 | myFunc(); 18 | `, 19 | "myFunc(", 20 | {typescript, useTypeChecker} 21 | ); 22 | 23 | if (!result.success) assert.fail(result.reason.stack); 24 | else assert.deepEqual(result.value, 6); 25 | }); 26 | 27 | test("Can evaluate a CallExpression with a ForOfStatement and a break statement. #1", "*", (_, {typescript, useTypeChecker}) => { 28 | const {result} = executeProgram( 29 | // language=TypeScript 30 | ` 31 | function myFunc (): number { 32 | let sum = 0; 33 | for (const foo of [1, 2, 3]) { 34 | if (foo === 3) break; 35 | sum += foo; 36 | } 37 | return sum; 38 | } 39 | 40 | myFunc(); 41 | `, 42 | "myFunc(", 43 | {typescript, useTypeChecker} 44 | ); 45 | 46 | if (!result.success) assert.fail(result.reason.stack); 47 | else assert.deepEqual(result.value, 3); 48 | }); 49 | 50 | test("Can evaluate a CallExpression with a ForOfStatement and a continue statement. #1", "*", (_, {typescript, useTypeChecker}) => { 51 | const {result} = executeProgram( 52 | // language=TypeScript 53 | ` 54 | function myFunc (): number { 55 | let sum = 0; 56 | for (const foo of [1, 2, 3]) { 57 | if (foo === 1) continue; 58 | sum += foo; 59 | } 60 | return sum; 61 | } 62 | 63 | myFunc(); 64 | `, 65 | "myFunc(", 66 | {typescript, useTypeChecker} 67 | ); 68 | 69 | if (!result.success) assert.fail(result.reason.stack); 70 | else assert.deepEqual(result.value, 5); 71 | }); 72 | 73 | test("Can evaluate a CallExpression with a ForOfStatement and a return statement. #1", "*", (_, {typescript, useTypeChecker}) => { 74 | const {result} = executeProgram( 75 | // language=TypeScript 76 | ` 77 | function myFunc (): number { 78 | let sum = 0; 79 | for (const foo of [1, 2, 3]) { 80 | if (foo === 3) return sum; 81 | sum += foo; 82 | } 83 | return -1; 84 | } 85 | 86 | myFunc(); 87 | `, 88 | "myFunc(", 89 | {typescript, useTypeChecker} 90 | ); 91 | 92 | if (!result.success) assert.fail(result.reason.stack); 93 | else assert.deepEqual(result.value, 3); 94 | }); 95 | -------------------------------------------------------------------------------- /test/for/for.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can evaluate a CallExpression with a ForStatement. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | function myFunc (): number { 10 | const arr = [1, 2, 3]; 11 | let sum = 0; 12 | for (let i = 0; i < arr.length; i++) { 13 | sum += arr[i]; 14 | } 15 | return sum; 16 | } 17 | 18 | myFunc(); 19 | `, 20 | "myFunc(", 21 | {typescript, useTypeChecker} 22 | ); 23 | 24 | if (!result.success) assert.fail(result.reason.stack); 25 | else assert.deepEqual(result.value, 6); 26 | }); 27 | 28 | test("Can evaluate a CallExpression with a ForStatement. #2", "*", (_, {typescript, useTypeChecker}) => { 29 | const {result} = executeProgram( 30 | // language=TypeScript 31 | ` 32 | function myFunc (): number { 33 | const arr = [1, 2, 3]; 34 | let sum = 0; 35 | for (let i = 0; i < arr.length; i++) { 36 | if (arr[i] === 2) continue; 37 | sum += arr[i]; 38 | } 39 | return sum; 40 | } 41 | 42 | myFunc(); 43 | `, 44 | "myFunc(", 45 | {typescript, useTypeChecker} 46 | ); 47 | 48 | if (!result.success) assert.fail(result.reason.stack); 49 | else assert.deepEqual(result.value, 4); 50 | }); 51 | 52 | test("Can evaluate a CallExpression with a ForStatement. #3", "*", (_, {typescript, useTypeChecker}) => { 53 | const {result} = executeProgram( 54 | // language=TypeScript 55 | ` 56 | function myFunc (): number { 57 | const arr = [1, 2, 3]; 58 | let sum = 0; 59 | for (let i = 0; i < arr.length; i++) { 60 | if (arr[i] === 2) break; 61 | sum += arr[i]; 62 | } 63 | return sum; 64 | } 65 | 66 | myFunc(); 67 | `, 68 | "myFunc(", 69 | {typescript, useTypeChecker} 70 | ); 71 | 72 | if (!result.success) assert.fail(result.reason.stack); 73 | else assert.deepEqual(result.value, 1); 74 | }); 75 | -------------------------------------------------------------------------------- /test/function-declaration/recursion.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can evaluate a CallExpression for a recursive function. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | 10 | function fibonacci (num: number, memo: { [key: number]: number } = {}): number { 11 | if (memo[num]) return memo[num]; 12 | if (num <= 1) return 1; 13 | 14 | return memo[num] = fibonacci(num - 1, memo) + fibonacci(num - 2, memo); 15 | } 16 | 17 | fibonacci(5); 18 | `, 19 | "fibonacci(5", 20 | {typescript, useTypeChecker} 21 | ); 22 | 23 | if (!result.success) assert.fail(result.reason.stack); 24 | else assert.deepEqual(result.value, 8); 25 | }); 26 | -------------------------------------------------------------------------------- /test/get-accessor-declaration/get-accessor-declaration.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can evaluate and retrieve a GetAccessorDeclaration. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | class Foo { 10 | get something () { 11 | return 2; 12 | } 13 | } 14 | `, 15 | "get", 16 | {typescript, useTypeChecker} 17 | ); 18 | 19 | if (!result.success) assert.fail(result.reason.stack); 20 | else { 21 | assert.deepEqual(result.value, 2); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /test/interface-declaration/interface-declaration.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Understands InterfaceDeclarations. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | (() => { 10 | interface HelloWorld { 11 | foo: string; 12 | } 13 | const a: HelloWorld = {foo: "hello world"}; 14 | return a; 15 | })(); 16 | `, 17 | "(() =>", 18 | {typescript, useTypeChecker} 19 | ); 20 | 21 | if (!result.success) assert.fail(result.reason.stack); 22 | else { 23 | assert.deepEqual(result.value, {foo: "hello world"}); 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /test/method-declaration/method-declaration.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can evaluate and retrieve a MethodDeclaration. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | class Foo { 10 | add (a: number, b: number): number { 11 | return a + b; 12 | } 13 | } 14 | `, 15 | "add (", 16 | {typescript, useTypeChecker} 17 | ); 18 | 19 | if (!result.success) assert.fail(result.reason.stack); 20 | else { 21 | assert(typeof result.value === "function"); 22 | } 23 | }); 24 | 25 | test("Can evaluate and retrieve a private MethodDeclaration. #1", ">=3.8", (_, {typescript, useTypeChecker}) => { 26 | const {result} = executeProgram( 27 | // language=TypeScript 28 | ` 29 | class Foo { 30 | #add (a: number, b: number): number { 31 | return a + b; 32 | } 33 | } 34 | `, 35 | "#add (", 36 | {typescript, useTypeChecker} 37 | ); 38 | 39 | if (!result.success) assert.fail(result.reason.stack); 40 | else { 41 | assert(typeof result.value === "function"); 42 | } 43 | }); 44 | 45 | test("Can evaluate and retrieve the result of calling a private MethodDeclaration. #1", ">=3.8", (_, {typescript, useTypeChecker}) => { 46 | const {result} = executeProgram( 47 | // language=TypeScript 48 | ` 49 | class Foo { 50 | static #add (...numbers: number[]): number { 51 | return numbers.reduce((a, b) => a + b, 0); 52 | } 53 | 54 | add (a: number, b: number): number { 55 | return Foo.#add(a, b); 56 | } 57 | } 58 | const foo = new Foo(); 59 | const result = foo.add(2, 2); 60 | `, 61 | "result", 62 | {typescript, useTypeChecker} 63 | ); 64 | 65 | if (!result.success) assert.fail(result.reason.stack); 66 | else { 67 | assert.deepEqual(result.value, 4); 68 | } 69 | }); 70 | 71 | test("Can evaluate and retrieve the result of calling a private MethodDeclaration. #2", ">=3.8", (_, {typescript, useTypeChecker}) => { 72 | const {result} = executeProgram( 73 | // language=TypeScript 74 | ` 75 | class Foo { 76 | get #secretNumber (): number { 77 | return 42; 78 | } 79 | 80 | addToSecretNumber (num: number): number { 81 | return num + this.#secretNumber; 82 | } 83 | } 84 | const foo = new Foo(); 85 | const result = foo.addToSecretNumber(2); 86 | `, 87 | "result", 88 | {typescript, useTypeChecker} 89 | ); 90 | 91 | if (!result.success) assert.fail(result.reason.stack); 92 | else { 93 | assert.deepEqual(result.value, 44); 94 | } 95 | }); 96 | -------------------------------------------------------------------------------- /test/new-target/new-target.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can handle new.target syntax. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | ` 8 | let result: boolean|undefined; 9 | function Foo() { 10 | if (!new.target) result = false; 11 | else result = true; 12 | } 13 | (() => { 14 | new Foo(); 15 | return result; 16 | })(); 17 | `, 18 | "(() =>", 19 | {typescript, useTypeChecker} 20 | ); 21 | 22 | if (!result.success) assert.fail(result.reason.stack); 23 | else { 24 | assert.deepEqual(result.value, true); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /test/nullish-coalescing/nullish-coalescing.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Supports nullish coalescing with null-like values. #1", ">=3.7", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | const foo = ""; 10 | const bar = foo ?? "bar"; 11 | `, 12 | `foo ?? "bar"`, 13 | {typescript, useTypeChecker} 14 | ); 15 | 16 | if (!result.success) assert.fail(result.reason.stack); 17 | else { 18 | assert.deepEqual(result.value, ""); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /test/object-binding-pattern/object-binding-pattern.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can handle ObjectBindingPatterns in VariableDeclarations. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | (() => { 10 | const {prop} = {prop: 123}; 11 | return prop; 12 | })(); 13 | `, 14 | "(() =>", 15 | {typescript, useTypeChecker} 16 | ); 17 | 18 | if (!result.success) assert.fail(result.reason.stack); 19 | else assert.deepEqual(result.value, 123); 20 | }); 21 | 22 | test("Can handle ObjectBindingPatterns in VariableDeclarations. #2", "*", (_, {typescript, useTypeChecker}) => { 23 | const {result} = executeProgram( 24 | // language=TypeScript 25 | ` 26 | (() => { 27 | const {prop: {otherProp}} = {prop: {otherProp: 245}}; 28 | return otherProp; 29 | })(); 30 | `, 31 | "(() =>", 32 | {typescript, useTypeChecker} 33 | ); 34 | 35 | if (!result.success) assert.fail(result.reason.stack); 36 | else assert.deepEqual(result.value, 245); 37 | }); 38 | 39 | test("Can handle ObjectBindingPatterns in ParameterDeclarations. #1", "*", (_, {typescript, useTypeChecker}) => { 40 | const {result} = executeProgram( 41 | // language=TypeScript 42 | ` 43 | (({foo}) => { 44 | return foo; 45 | })({foo: 2}); 46 | `, 47 | "(({foo}) =>", 48 | {typescript, useTypeChecker} 49 | ); 50 | 51 | if (!result.success) assert.fail(result.reason.stack); 52 | else assert.deepEqual(result.value, 2); 53 | }); 54 | 55 | test("Can handle ObjectBindingPatterns in ParameterDeclarations. #2", "*", (_, {typescript, useTypeChecker}) => { 56 | const {result} = executeProgram( 57 | // language=TypeScript 58 | ` 59 | (({foo: alias}) => { 60 | return alias; 61 | })({foo: 2}); 62 | `, 63 | "(({foo: alias}) =>", 64 | {typescript, useTypeChecker} 65 | ); 66 | 67 | if (!result.success) assert.fail(result.reason.stack); 68 | else assert.deepEqual(result.value, 2); 69 | }); 70 | -------------------------------------------------------------------------------- /test/optional-chaining/optional-chaining.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Supports optional CallExpressions. #1", ">=3.7", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | const foo = {bar: {baz: undefined}}; 10 | const bar = foo.bar.baz?.(); 11 | `, 12 | "foo.bar.baz?.()", 13 | {typescript, useTypeChecker} 14 | ); 15 | 16 | if (!result.success) assert.fail(result.reason.stack); 17 | else { 18 | assert.deepEqual(result.value, undefined); 19 | } 20 | }); 21 | 22 | test("Supports optional PropertyAccessExpressions. #1", ">=3.7", (_, {typescript, useTypeChecker}) => { 23 | const {result} = executeProgram( 24 | // language=TypeScript 25 | ` 26 | const foo = {bar: undefined}; 27 | const bar = foo.bar?.baz; 28 | `, 29 | "foo.bar?.baz", 30 | {typescript, useTypeChecker} 31 | ); 32 | 33 | if (!result.success) assert.fail(result.reason.stack); 34 | else { 35 | assert.deepEqual(result.value, undefined); 36 | } 37 | }); 38 | 39 | test("Supports optional ElementAccessExpressions. #1", ">=3.7", (_, {typescript, useTypeChecker}) => { 40 | const {result} = executeProgram( 41 | // language=TypeScript 42 | ` 43 | const foo = {bar: undefined}; 44 | const bar = foo.bar?.["baz"]; 45 | `, 46 | `foo.bar?.["baz"]`, 47 | {typescript, useTypeChecker} 48 | ); 49 | 50 | if (!result.success) assert.fail(result.reason.stack); 51 | else { 52 | assert.deepEqual(result.value, undefined); 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /test/postfix-unary-expression/postfix-unary-expression.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can handle PostfixUnaryExpressions. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | // noinspection BadExpressionStatementJS 7 | const {result} = executeProgram( 8 | // language=TypeScript 9 | ` 10 | let i = 0; 11 | 12 | function foo () { 13 | return i++; 14 | } 15 | 16 | (() => foo())(); 17 | `, 18 | "(() =>", 19 | {typescript, useTypeChecker} 20 | ); 21 | 22 | if (!result.success) assert.fail(result.reason.stack); 23 | else { 24 | assert.deepEqual(result.value, 0); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /test/property-declaration/property-declaration.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can evaluate and retrieve a PropertyDeclaration. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | class Foo { 10 | private someInstanceProp = 2; 11 | } 12 | `, 13 | "someInstanceProp", 14 | {typescript, useTypeChecker} 15 | ); 16 | 17 | if (!result.success) assert.fail(result.reason.stack); 18 | else { 19 | assert.deepEqual(result.value, 2); 20 | } 21 | }); 22 | 23 | test("Can evaluate and retrieve a private PropertyDeclaration. #1", ">=3.8", (_, {typescript, useTypeChecker}) => { 24 | const {result} = executeProgram( 25 | ` 26 | class Foo { 27 | #someInstanceProp = 2; 28 | } 29 | `, 30 | "#someInstanceProp", 31 | {typescript, useTypeChecker} 32 | ); 33 | 34 | if (!result.success) assert.fail(result.reason.stack); 35 | else { 36 | assert.deepEqual(result.value, 2); 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /test/setup/cached-fs.ts: -------------------------------------------------------------------------------- 1 | import type {TS} from "../../src/type/ts.js"; 2 | import type {FileSystem} from "../../src/type/file-system.js"; 3 | import type {CachedWorkerOptions} from "./cached-worker.js"; 4 | import {CachedWorker} from "./cached-worker.js"; 5 | 6 | export interface CachedFsWorkerOptions extends CachedWorkerOptions { 7 | fs: TS.System | FileSystem; 8 | } 9 | 10 | export class CachedFs extends CachedWorker { 11 | readFile(file: string): string | undefined { 12 | return this.work(file, () => { 13 | if ("readFileSync" in this.options.fs) { 14 | try { 15 | return this.options.fs.readFileSync(file, "utf8"); 16 | } catch { 17 | return undefined; 18 | } 19 | } else { 20 | return this.options.fs.readFile(file); 21 | } 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/setup/cached-worker.ts: -------------------------------------------------------------------------------- 1 | export interface CachedWorkerOptions {} 2 | 3 | export class CachedWorker { 4 | private readonly cache = new Map(); 5 | 6 | constructor(protected readonly options: Options) {} 7 | 8 | work(key: string, job: () => T): T { 9 | if (this.cache.has(key)) { 10 | return this.cache.get(key) as T; 11 | } 12 | const result = job(); 13 | this.cache.set(key, result); 14 | return result; 15 | } 16 | 17 | delete(key: string): boolean { 18 | return this.cache.delete(key); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/setup/create-virtual-file-system.ts: -------------------------------------------------------------------------------- 1 | import path from "crosspath"; 2 | import type {FileSystem} from "../../src/type/file-system.js"; 3 | import type {TestFileRecord} from "./test-file.js"; 4 | import {Volume, createFsFromVolume} from "memfs"; 5 | 6 | export function createVirtualFileSystem(files: TestFileRecord[]): FileSystem { 7 | const vol = new Volume(); 8 | for (const file of files) { 9 | vol.mkdirSync(path.native.dirname(file.fileName), {recursive: true}); 10 | vol.writeFileSync(path.native.normalize(file.fileName), file.text); 11 | } 12 | 13 | return createFsFromVolume(vol) as unknown as FileSystem; 14 | } 15 | -------------------------------------------------------------------------------- /test/setup/test-context.ts: -------------------------------------------------------------------------------- 1 | import type {PartialExcept} from "helpertypes"; 2 | import type {EvaluateOptions} from "../../src/interpreter/evaluate-options.js"; 3 | import {LogLevelKind} from "../../src/interpreter/logger/log-level.js"; 4 | import type {TS} from "../../src/type/ts.js"; 5 | 6 | const _process = process; 7 | export interface TestContext extends PartialExcept, "typescript"> { 8 | cwd: string; 9 | useTypeChecker: boolean; 10 | compilerOptions?: Partial; 11 | } 12 | 13 | export function createTestContext({ 14 | typescript, 15 | environment, 16 | compilerOptions, 17 | useTypeChecker = true, 18 | moduleOverrides = {}, 19 | cwd = _process.cwd(), 20 | policy: { 21 | deterministic = true, 22 | maxOps = Infinity, 23 | maxOpDuration = Infinity, 24 | console = false, 25 | network = false, 26 | io = { 27 | read: true, 28 | write: false 29 | }, 30 | process = { 31 | exit: false, 32 | spawnChild: false 33 | } 34 | } = {}, 35 | reporting, 36 | logLevel = LogLevelKind.SILENT 37 | }: PartialExcept): TestContext { 38 | return { 39 | cwd, 40 | typescript, 41 | environment, 42 | moduleOverrides, 43 | reporting, 44 | useTypeChecker, 45 | compilerOptions, 46 | policy: { 47 | maxOps, 48 | maxOpDuration, 49 | deterministic, 50 | io, 51 | process, 52 | network, 53 | console 54 | }, 55 | logLevel 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /test/setup/test-result.ts: -------------------------------------------------------------------------------- 1 | import type {EvaluateResult} from "../../src/interpreter/evaluate-result.js"; 2 | import type {TestSetup} from "./test-setup.js"; 3 | 4 | export interface TestResult { 5 | result: EvaluateResult; 6 | setup: TestSetup; 7 | } 8 | -------------------------------------------------------------------------------- /test/setup/test-setup.ts: -------------------------------------------------------------------------------- 1 | import type {TestContext} from "./test-context.js"; 2 | import {createTestContext} from "./test-context.js"; 3 | import type {FileSystem} from "../../src/type/file-system.js"; 4 | import type {TestFile, TestFileEntry, TestFileStructure} from "./test-file.js"; 5 | import {createTestFileStructure} from "./test-file.js"; 6 | import {createVirtualFileSystem} from "./create-virtual-file-system.js"; 7 | import type {TS} from "../../src/type/ts.js"; 8 | import {createCompilerHost} from "./create-compiler-host.js"; 9 | import type {MaybeArray, PartialExcept} from "helpertypes"; 10 | 11 | export interface TestSetup { 12 | context: TestContext; 13 | fileSystem: FileSystem; 14 | fileStructure: TestFileStructure; 15 | compilerHost: TS.CompilerHost; 16 | } 17 | 18 | export function createTestSetup(inputFiles: MaybeArray, entry: TestFileEntry, options: PartialExcept): TestSetup { 19 | const context = createTestContext(options); 20 | const fileStructure = createTestFileStructure(inputFiles, entry, context); 21 | const fileSystem = createVirtualFileSystem(fileStructure.files); 22 | const compilerHost = createCompilerHost({ 23 | fileSystem, 24 | typescript: context.typescript, 25 | cwd: fileStructure.dir.root 26 | }); 27 | return { 28 | context, 29 | fileStructure, 30 | fileSystem, 31 | compilerHost 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /test/spread-assignment/spread-assignment.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can handle Spread assignments to objects. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | (() => { 10 | const a = { 11 | a: 1, 12 | b: 2 13 | }; 14 | const b = { 15 | ...a, 16 | c: 3 17 | } 18 | return b; 19 | })(); 20 | `, 21 | "(() =>", 22 | {typescript, useTypeChecker} 23 | ); 24 | 25 | if (!result.success) assert.fail(result.reason.stack); 26 | else assert.deepEqual(result.value as {a: number; b: number; c: number}, {a: 1, b: 2, c: 3}); 27 | }); 28 | -------------------------------------------------------------------------------- /test/spread-element/spread-element.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can handle Spread Elements in arrays. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | (() => { 10 | const a = [1, 2]; 11 | const b = [...a, 3]; 12 | return b; 13 | })(); 14 | `, 15 | "(() =>", 16 | {typescript, useTypeChecker} 17 | ); 18 | 19 | if (!result.success) assert.fail(result.reason.stack); 20 | else assert.deepEqual(result.value as number[], [1, 2, 3]); 21 | }); 22 | 23 | test("Can handle Spread Elements in CallExpressions. #1", "*", (_, {typescript, useTypeChecker}) => { 24 | const {result} = executeProgram( 25 | // language=TypeScript 26 | ` 27 | (() => { 28 | function foo (...args: [number, number, string]): string { 29 | const [first, second, third] = args; 30 | return (third.toUpperCase() + "-" + (first ** second)); 31 | } 32 | 33 | return foo(2, 2, "foo") 34 | })(); 35 | `, 36 | "(() =>", 37 | {typescript, useTypeChecker} 38 | ); 39 | 40 | if (!result.success) assert.fail(result.reason.stack); 41 | else assert.deepEqual(result.value as string, "FOO-4"); 42 | }); 43 | -------------------------------------------------------------------------------- /test/type-alias-declaration/type-alias-declaration.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Understands TypeAliasDeclarations. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | (() => { 10 | type HelloWorld = "hello world"; 11 | let a: HelloWorld; 12 | a = "hello world"; 13 | return a; 14 | })(); 15 | `, 16 | "(() =>", 17 | {typescript, useTypeChecker} 18 | ); 19 | 20 | if (!result.success) assert.fail(result.reason.stack); 21 | else { 22 | assert.deepEqual(result.value, "hello world"); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /test/type-of-expression/type-of-expression.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can evaluate a TypeOfExpression #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | (() => { 10 | let a = BigInt(2); 11 | return typeof a; 12 | })(); 13 | `, 14 | "(() =>", 15 | {typescript, useTypeChecker} 16 | ); 17 | 18 | if (!result.success) assert.fail(result.reason.stack); 19 | else assert.deepEqual(result.value, "bigint"); 20 | }); 21 | 22 | test("Can evaluate a TypeOfExpression #2", "*", (_, {typescript, useTypeChecker}) => { 23 | const {result} = executeProgram( 24 | // language=TypeScript 25 | ` 26 | (() => { 27 | let a = BigInt(2); 28 | if (typeof a === "bigint") return "foo"; 29 | else return "bar"; 30 | })(); 31 | `, 32 | "(() =>", 33 | {typescript, useTypeChecker} 34 | ); 35 | 36 | if (!result.success) assert.fail(result.reason.stack); 37 | else assert.deepEqual(result.value, "foo"); 38 | }); 39 | -------------------------------------------------------------------------------- /test/void-expression/void-expression.test.ts: -------------------------------------------------------------------------------- 1 | import {test} from "../setup/test-runner.js"; 2 | import assert from "node:assert"; 3 | import {executeProgram} from "../setup/execute-program.js"; 4 | 5 | test("Can evaluate VoidExpressions #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | (() => { 10 | let something = 0; 11 | const update = () => something++; 12 | (() => void update())(); 13 | return something; 14 | })(); 15 | `, 16 | "(() =>", 17 | {typescript, useTypeChecker} 18 | ); 19 | 20 | if (!result.success) assert.fail(result.reason.stack); 21 | else assert.deepEqual(result.value, 1); 22 | }); 23 | 24 | test("Can evaluate VoidExpressions #2", "*", (_, {typescript, useTypeChecker}) => { 25 | const {result} = executeProgram( 26 | // language=TypeScript 27 | ` 28 | (() => { 29 | let something = 0; 30 | const update = () => something++; 31 | return (() => void update())(); 32 | })(); 33 | `, 34 | "(() =>", 35 | {typescript, useTypeChecker} 36 | ); 37 | 38 | if (!result.success) assert.fail(result.reason.stack); 39 | else assert.deepEqual(result.value, undefined); 40 | }); 41 | 42 | test("Can evaluate VoidExpressions #3", "*", (_, {typescript, useTypeChecker}) => { 43 | const {result} = executeProgram( 44 | // language=TypeScript 45 | ` 46 | (() => { 47 | // noinspection JSUnusedAssignment 48 | let a = 0; 49 | let b = void (a = 1); 50 | return [a, b]; 51 | })(); 52 | `, 53 | "(() =>", 54 | {typescript, useTypeChecker} 55 | ); 56 | 57 | if (!result.success) assert.fail(result.reason.stack); 58 | else assert.deepEqual(result.value, [1, undefined]); 59 | }); 60 | -------------------------------------------------------------------------------- /test/while/while.test.ts: -------------------------------------------------------------------------------- 1 | import {executeProgram} from "../setup/execute-program.js"; 2 | import {test} from "../setup/test-runner.js"; 3 | import assert from "node:assert"; 4 | 5 | test("Can evaluate a CallExpression with a WhileStatement. #1", "*", (_, {typescript, useTypeChecker}) => { 6 | const {result} = executeProgram( 7 | // language=TypeScript 8 | ` 9 | function myFunc (): number { 10 | 11 | let sum = 0; 12 | while (sum < 10) sum++; 13 | return sum; 14 | } 15 | 16 | myFunc(); 17 | `, 18 | "myFunc(", 19 | {typescript, useTypeChecker} 20 | ); 21 | 22 | if (!result.success) assert.fail(result.reason.stack); 23 | else assert.deepEqual(result.value, 10); 24 | }); 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@wessberg/ts-config/tsconfig.json", 3 | "include": ["src/**/*.*", "test/**/*.*", "loader.cjs", "sandhog.config.js"], 4 | "exclude": ["dist/*.*"], 5 | "compilerOptions": { 6 | "importHelpers": false 7 | } 8 | } 9 | --------------------------------------------------------------------------------