├── .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 |
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 |
--------------------------------------------------------------------------------