From ad8bc78f585f13211329ba7345d9c5b2d3b9d201 Mon Sep 17 00:00:00 2001 From: wessberg Date: Sun, 30 Dec 2018 11:07:01 +0100 Subject: [PATCH] fix(bug): fixes an issue with evaluating a ClassMember such as a MethodDeclaration, PropertyDeclaration, or a GetAccessorDeclaration directly. Fixes #7 --- .../evaluate-get-accessor-declaration.ts | 19 ++++++++++++--- .../evaluator/evaluate-method-declaration.ts | 20 ++++++++++++---- src/interpreter/evaluator/evaluate-node.ts | 17 +++++++++++++- .../evaluate-property-declaration.ts | 13 +++++++++-- .../util/expression/is-expression.ts | 3 ++- test/environment/node.test.ts | 19 +++++++++++++++ .../get-accessor-declaration.test.ts | 23 +++++++++++++++++++ .../method-declaration.test.ts | 23 +++++++++++++++++++ .../property-declaration.test.ts | 21 +++++++++++++++++ 9 files changed, 147 insertions(+), 11 deletions(-) create mode 100644 test/get-accessor-declaration/get-accessor-declaration.test.ts create mode 100644 test/method-declaration/method-declaration.test.ts create mode 100644 test/property-declaration/property-declaration.test.ts diff --git a/src/interpreter/evaluator/evaluate-get-accessor-declaration.ts b/src/interpreter/evaluator/evaluate-get-accessor-declaration.ts index 573698d..d5dd7a6 100644 --- a/src/interpreter/evaluator/evaluate-get-accessor-declaration.ts +++ b/src/interpreter/evaluator/evaluate-get-accessor-declaration.ts @@ -1,5 +1,5 @@ import {IEvaluatorOptions} from "./i-evaluator-options"; -import {GetAccessorDeclaration} from "typescript"; +import {GetAccessorDeclaration, isClassLike} from "typescript"; import {LexicalEnvironment, pathInLexicalEnvironmentEquals, setInLexicalEnvironment} from "../lexical-environment/lexical-environment"; import {cloneLexicalEnvironment} from "../lexical-environment/clone-lexical-environment"; import {IndexLiteral, IndexLiteralKey, Literal} from "../literal/literal"; @@ -11,13 +11,26 @@ import {inStaticContext} from "../util/static/in-static-context"; /** * Evaluates, or attempts to evaluate, a GetAccessorDeclaration, before setting it on the given parent * @param {IEvaluatorOptions} options - * @param {IndexLiteral} parent + * @param {IndexLiteral} [parent] */ -export function evaluateGetAccessorDeclaration ({node, environment, evaluate, stack, reporting, statementTraversalStack}: IEvaluatorOptions, parent: IndexLiteral): void { +export function evaluateGetAccessorDeclaration ({node, environment, evaluate, stack, reporting, statementTraversalStack}: IEvaluatorOptions, parent?: IndexLiteral): void { const nameResult = (evaluate.nodeWithValue(node.name, environment, statementTraversalStack)) as IndexLiteralKey; const isStatic = inStaticContext(node); + if (parent == null) { + let updatedParent: Function & IndexLiteral; + if (isClassLike(node.parent)) { + evaluate.declaration(node.parent, environment, statementTraversalStack); + updatedParent = stack.pop() as Function & IndexLiteral; + } + else { + updatedParent = evaluate.expression(node.parent, environment, statementTraversalStack) as Function & IndexLiteral; + } + stack.push(isStatic ? updatedParent[nameResult] : updatedParent.prototype[nameResult]); + return; + } + /** * An implementation of the get accessor */ diff --git a/src/interpreter/evaluator/evaluate-method-declaration.ts b/src/interpreter/evaluator/evaluate-method-declaration.ts index f462720..ee2f2f5 100644 --- a/src/interpreter/evaluator/evaluate-method-declaration.ts +++ b/src/interpreter/evaluator/evaluate-method-declaration.ts @@ -1,5 +1,5 @@ import {IEvaluatorOptions} from "./i-evaluator-options"; -import {isIdentifier, MethodDeclaration, SyntaxKind} from "typescript"; +import {isClassLike, isIdentifier, MethodDeclaration, SyntaxKind} from "typescript"; import {getFromLexicalEnvironment, LexicalEnvironment, pathInLexicalEnvironmentEquals, setInLexicalEnvironment} from "../lexical-environment/lexical-environment"; import {cloneLexicalEnvironment} from "../lexical-environment/clone-lexical-environment"; import {IndexLiteral, IndexLiteralKey, Literal} from "../literal/literal"; @@ -15,13 +15,25 @@ import {hasModifier} from "../util/modifier/has-modifier"; /** * Evaluates, or attempts to evaluate, a MethodDeclaration, before setting it on the given parent * @param {IEvaluatorOptions} options - * @param {IndexLiteral} parent + * @param {IndexLiteral} [parent] */ -export function evaluateMethodDeclaration ({node, environment, evaluate, stack, statementTraversalStack, reporting, ...rest}: IEvaluatorOptions, parent: IndexLiteral): void { - +export function evaluateMethodDeclaration ({node, environment, evaluate, stack, statementTraversalStack, reporting, ...rest}: IEvaluatorOptions, parent?: IndexLiteral): void { const nameResult = (evaluate.nodeWithValue(node.name, environment, statementTraversalStack)) as IndexLiteralKey; const isStatic = inStaticContext(node); + if (parent == null) { + let updatedParent: Function & IndexLiteral; + if (isClassLike(node.parent)) { + evaluate.declaration(node.parent, environment, statementTraversalStack); + updatedParent = stack.pop() as Function & IndexLiteral; + } + else { + updatedParent = evaluate.expression(node.parent, environment, statementTraversalStack) as Function & IndexLiteral; + } + stack.push(isStatic ? updatedParent[nameResult] : updatedParent.prototype[nameResult]); + return; + } + const _methodDeclaration = hasModifier(node, SyntaxKind.AsyncKeyword) ? async function methodDeclaration (this: Literal, ...args: Literal[]) { diff --git a/src/interpreter/evaluator/evaluate-node.ts b/src/interpreter/evaluator/evaluate-node.ts index 470602a..2b9d77d 100644 --- a/src/interpreter/evaluator/evaluate-node.ts +++ b/src/interpreter/evaluator/evaluate-node.ts @@ -1,5 +1,5 @@ import { - isArrayLiteralExpression, isArrowFunction, isAsExpression, isAwaitExpression, isBigIntLiteral, isBinaryExpression, isBlock, isBreakStatement, isCallExpression, isClassDeclaration, isClassExpression, isComputedPropertyName, isConditionalExpression, isConstructorDeclaration, isContinueStatement, isElementAccessExpression, isEnumDeclaration, isExpressionStatement, isForInStatement, isForOfStatement, isForStatement, isFunctionDeclaration, isFunctionExpression, isIdentifier, isIfStatement, isImportDeclaration, isImportEqualsDeclaration, isModuleDeclaration, isNewExpression, isNonNullExpression, isNumericLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isRegularExpressionLiteral, isReturnStatement, isSourceFile, isSpreadElement, isStringLiteralLike, isSwitchStatement, isTemplateExpression, isThrowStatement, isTryStatement, isTypeAssertion, isTypeOfExpression, isVariableDeclaration, isVariableDeclarationList, isVariableStatement, isVoidExpression, isWhileStatement, Node + isArrayLiteralExpression, isArrowFunction, isAsExpression, isAwaitExpression, isBigIntLiteral, isBinaryExpression, isBlock, isBreakStatement, isCallExpression, isClassDeclaration, isClassExpression, isComputedPropertyName, isConditionalExpression, isConstructorDeclaration, isContinueStatement, isElementAccessExpression, isEnumDeclaration, isExpressionStatement, isForInStatement, isForOfStatement, isForStatement, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isIfStatement, isImportDeclaration, isImportEqualsDeclaration, isMethodDeclaration, isModuleDeclaration, isNewExpression, isNonNullExpression, isNumericLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyDeclaration, isRegularExpressionLiteral, isReturnStatement, isSourceFile, isSpreadElement, isStringLiteralLike, isSwitchStatement, isTemplateExpression, isThrowStatement, isTryStatement, isTypeAssertion, isTypeOfExpression, isVariableDeclaration, isVariableDeclarationList, isVariableStatement, isVoidExpression, isWhileStatement, Node } from "typescript"; import {IEvaluatorOptions} from "./i-evaluator-options"; import {evaluateVariableDeclaration} from "./evaluate-variable-declaration"; @@ -63,6 +63,9 @@ import {evaluateThrowStatement} from "./evaluate-throw-statement"; import {evaluateImportEqualsDeclaration} from "./evaluate-import-equals-declaration"; import {evaluateAwaitExpression} from "./evaluate-await-expression"; import {evaluateConditionalExpression} from "./evaluate-conditional-expression"; +import {evaluateMethodDeclaration} from "./evaluate-method-declaration"; +import {evaluatePropertyDeclaration} from "./evaluate-property-declaration"; +import {evaluateGetAccessorDeclaration} from "./evaluate-get-accessor-declaration"; /** * Will get a literal value for the given Node. If it doesn't succeed, the value will be 'undefined' @@ -123,6 +126,18 @@ export function evaluateNode ({node, ...rest}: IEvaluatorOptions): unknown return evaluateTemplateExpression({node, ...rest}); } + else if (isMethodDeclaration(node)) { + return evaluateMethodDeclaration({node, ...rest}); + } + + else if (isPropertyDeclaration(node)) { + return evaluatePropertyDeclaration({node, ...rest}); + } + + else if (isGetAccessorDeclaration(node)) { + return evaluateGetAccessorDeclaration({node, ...rest}); + } + else if (isArrayLiteralExpression(node)) { return evaluateArrayLiteralExpression({node, ...rest}); } diff --git a/src/interpreter/evaluator/evaluate-property-declaration.ts b/src/interpreter/evaluator/evaluate-property-declaration.ts index 224b6c8..0ce12f0 100644 --- a/src/interpreter/evaluator/evaluate-property-declaration.ts +++ b/src/interpreter/evaluator/evaluate-property-declaration.ts @@ -1,17 +1,26 @@ import {IEvaluatorOptions} from "./i-evaluator-options"; import {PropertyDeclaration} from "typescript"; import {IndexLiteral, IndexLiteralKey} from "../literal/literal"; +import {inStaticContext} from "../util/static/in-static-context"; /** * Evaluates, or attempts to evaluate, a PropertyDeclaration, before applying it on the given parent * @param {IEvaluatorOptions} options - * @param {IndexLiteral} parent + * @param {IndexLiteral} [parent] * @returns {Promise} */ -export function evaluatePropertyDeclaration ({environment, node, evaluate, statementTraversalStack, stack}: IEvaluatorOptions, parent: IndexLiteral): void { +export function evaluatePropertyDeclaration ({environment, node, evaluate, statementTraversalStack, stack}: IEvaluatorOptions, parent?: IndexLiteral): void { // Compute the property name const propertyNameResult = (evaluate.nodeWithValue(node.name, environment, statementTraversalStack)) as IndexLiteralKey; + if (parent == null) { + evaluate.declaration(node.parent, environment, statementTraversalStack); + const updatedParent = stack.pop() as Function&IndexLiteral; + const isStatic = inStaticContext(node); + stack.push(isStatic ? updatedParent[propertyNameResult] : updatedParent.prototype[propertyNameResult]); + return; + } + parent[propertyNameResult] = node.initializer == null ? undefined : evaluate.expression(node.initializer, environment, statementTraversalStack); diff --git a/src/interpreter/util/expression/is-expression.ts b/src/interpreter/util/expression/is-expression.ts index 494a599..6d4c955 100644 --- a/src/interpreter/util/expression/is-expression.ts +++ b/src/interpreter/util/expression/is-expression.ts @@ -1,4 +1,5 @@ import * as ts from "typescript"; +import {isIdentifier} from "typescript"; /** * Returns true if the given Node is an Expression. @@ -7,5 +8,5 @@ import * as ts from "typescript"; * @return {node is Expression} */ export function isExpression (node: ts.Node): node is ts.Expression { - return (ts as unknown as {isExpressionNode (node: ts.Node): boolean}).isExpressionNode(node); + return (ts as unknown as {isExpressionNode (node: ts.Node): boolean}).isExpressionNode(node) || isIdentifier(node); } \ No newline at end of file diff --git a/test/environment/node.test.ts b/test/environment/node.test.ts index 5cb8ce3..7af473e 100644 --- a/test/environment/node.test.ts +++ b/test/environment/node.test.ts @@ -26,4 +26,23 @@ test("Can handle the '__dirname' and '__filename' meta properties in a Node envi else { t.deepEqual(result.value, {dirname: "/Users/someone/development/foo", filename: "/Users/someone/development/foo/bar.ts"}); } +}); + +test("Can handle 'process.cwd()' in a Node environment. #1", t => { + const {evaluate} = prepareTest( + // language=TypeScript + ` + (() => { + return process.cwd(); + })(); + `, + "(() =>" + ); + + const result = evaluate(); + + if (!result.success) t.fail(result.reason.stack); + else { + t.deepEqual(result.value, process.cwd()); + } }); \ No newline at end of file diff --git a/test/get-accessor-declaration/get-accessor-declaration.test.ts b/test/get-accessor-declaration/get-accessor-declaration.test.ts new file mode 100644 index 0000000..5516786 --- /dev/null +++ b/test/get-accessor-declaration/get-accessor-declaration.test.ts @@ -0,0 +1,23 @@ +import {test} from "ava"; +import {prepareTest} from "../setup"; + +test("Can evaluate and retrieve a GetAccessorDeclaration. #1", t => { + const {evaluate} = prepareTest( + // language=TypeScript + ` + class Foo { + get something () { + return 2; + } + } + `, + "get" + ); + + const result = evaluate(); + + if (!result.success) t.fail(result.reason.stack); + else { + t.deepEqual(result.value, 2); + } +}); \ No newline at end of file diff --git a/test/method-declaration/method-declaration.test.ts b/test/method-declaration/method-declaration.test.ts new file mode 100644 index 0000000..6501ef3 --- /dev/null +++ b/test/method-declaration/method-declaration.test.ts @@ -0,0 +1,23 @@ +import {test} from "ava"; +import {prepareTest} from "../setup"; + +test("Can evaluate and retrieve a MethodDeclaration. #1", t => { + const {evaluate} = prepareTest( + // language=TypeScript + ` + class Foo { + add (a: number, b: number): number { + return a + b; + } + } + `, + "add (" + ); + + const result = evaluate(); + + if (!result.success) t.fail(result.reason.stack); + else { + t.true(typeof result.value === "function"); + } +}); \ No newline at end of file diff --git a/test/property-declaration/property-declaration.test.ts b/test/property-declaration/property-declaration.test.ts new file mode 100644 index 0000000..936b703 --- /dev/null +++ b/test/property-declaration/property-declaration.test.ts @@ -0,0 +1,21 @@ +import {test} from "ava"; +import {prepareTest} from "../setup"; + +test("Can evaluate and retrieve a PropertyDeclaration. #1", t => { + const {evaluate} = prepareTest( + // language=TypeScript + ` + class Foo { + private someInstanceProp = 2; + } + `, + "someInstanceProp" + ); + + const result = evaluate(); + + if (!result.success) t.fail(result.reason.stack); + else { + t.deepEqual(result.value, 2); + } +}); \ No newline at end of file