From 7d6b3579e9882b1f079e32c93c930624755d7256 Mon Sep 17 00:00:00 2001 From: Dimitri Stallenberg Date: Fri, 29 Sep 2023 09:31:07 +0200 Subject: [PATCH 1/3] feat: add support for decimaljs --- .../lib/target/export/ExpressionStatement.ts | 1 - .../lib/testbuilding/JavaScriptDecoder.ts | 1 - .../testcase/execution/JavaScriptRunner.ts | 1 + .../lib/testcase/execution/TestExecutor.ts | 25 +++++++++++++------ tools/javascript/lib/JavaScriptLauncher.ts | 1 - 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/libraries/analysis-javascript/lib/target/export/ExpressionStatement.ts b/libraries/analysis-javascript/lib/target/export/ExpressionStatement.ts index 1b0289c03..3e5d64c16 100644 --- a/libraries/analysis-javascript/lib/target/export/ExpressionStatement.ts +++ b/libraries/analysis-javascript/lib/target/export/ExpressionStatement.ts @@ -103,7 +103,6 @@ export function extractExportsFromRightAssignmentExpression( }); } - console.log(exports); return exports; } diff --git a/libraries/search-javascript/lib/testbuilding/JavaScriptDecoder.ts b/libraries/search-javascript/lib/testbuilding/JavaScriptDecoder.ts index 998605a9e..5760320b9 100644 --- a/libraries/search-javascript/lib/testbuilding/JavaScriptDecoder.ts +++ b/libraries/search-javascript/lib/testbuilding/JavaScriptDecoder.ts @@ -125,7 +125,6 @@ export class JavaScriptDecoder implements Decoder { const lines = [ "// Imports", - "require = require('esm')(module)", ...imports, gatherAssertionData ? assertionFunction : "", `describe('SynTest Test Suite', function() {`, diff --git a/libraries/search-javascript/lib/testcase/execution/JavaScriptRunner.ts b/libraries/search-javascript/lib/testcase/execution/JavaScriptRunner.ts index ba41d8b10..b84305b72 100644 --- a/libraries/search-javascript/lib/testcase/execution/JavaScriptRunner.ts +++ b/libraries/search-javascript/lib/testcase/execution/JavaScriptRunner.ts @@ -128,6 +128,7 @@ export class JavaScriptRunner implements EncodingRunner { childProcess.send({ message: "run", silent: this.silenceTestOutput, + esm: true, paths: paths, timeout: this.testTimeout, }); diff --git a/libraries/search-javascript/lib/testcase/execution/TestExecutor.ts b/libraries/search-javascript/lib/testcase/execution/TestExecutor.ts index c5b9eedcb..53409f357 100644 --- a/libraries/search-javascript/lib/testcase/execution/TestExecutor.ts +++ b/libraries/search-javascript/lib/testcase/execution/TestExecutor.ts @@ -34,6 +34,7 @@ export type Message = RunMessage | DoneMessage; export type RunMessage = { message: "run"; silent: boolean; + esm: boolean; paths: string[]; timeout: number; }; @@ -71,11 +72,16 @@ process.on("message", async (data: Message) => { throw new TypeError("Invalid data received from child process"); } if (data.message === "run") { - await runMocha(data.silent, data.paths, data.timeout); + await runMocha(data.silent, data.esm, data.paths, data.timeout); } }); -async function runMocha(silent: boolean, paths: string[], timeout: number) { +async function runMocha( + silent: boolean, + esm: boolean, + paths: string[], + timeout: number +) { const argv: Mocha.MochaOptions = ({ reporter: silent ? SilentMochaReporter : undefined, // diff: false, @@ -90,13 +96,16 @@ async function runMocha(silent: boolean, paths: string[], timeout: number) { }); const mocha = new Mocha(argv); // require('ts-node/register') - // eslint-disable-next-line unicorn/prefer-module - require("regenerator-runtime/runtime"); - // eslint-disable-next-line unicorn/prefer-module, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-var-requires - require("@babel/register")({ + + if (esm) { // eslint-disable-next-line unicorn/prefer-module - presets: [require.resolve("@babel/preset-env")], - }); + require("regenerator-runtime/runtime"); + // eslint-disable-next-line unicorn/prefer-module, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-var-requires + require("@babel/register")({ + // eslint-disable-next-line unicorn/prefer-module + presets: [require.resolve("@babel/preset-env")], + }); + } for (const _path of paths) { // eslint-disable-next-line unicorn/prefer-module diff --git a/tools/javascript/lib/JavaScriptLauncher.ts b/tools/javascript/lib/JavaScriptLauncher.ts index 2c7b343a7..0f38276b1 100644 --- a/tools/javascript/lib/JavaScriptLauncher.ts +++ b/tools/javascript/lib/JavaScriptLauncher.ts @@ -550,7 +550,6 @@ export class JavaScriptLauncher extends Launcher { finalEncodings = new Map( [...newArchives.entries()].map(([target, archive]) => { - console.log("archive size", archive.size); return [target, archive.getEncodings()]; }) ); From 6a7a8cc9ecd9ced2b6e0aa7105a7ad5fd2f98522 Mon Sep 17 00:00:00 2001 From: Dimitri Stallenberg Date: Mon, 2 Oct 2023 14:12:29 +0200 Subject: [PATCH 2/3] feat: add changes --- .../lib/target/TargetVisitor.ts | 563 ++++++++++++++---- libraries/analysis-javascript/package.json | 2 +- .../target/PrototypeTargetVisitor.test.ts | 180 ++++++ tools/javascript/lib/JavaScriptLauncher.ts | 1 + 4 files changed, 621 insertions(+), 125 deletions(-) create mode 100644 libraries/analysis-javascript/test/target/PrototypeTargetVisitor.test.ts diff --git a/libraries/analysis-javascript/lib/target/TargetVisitor.ts b/libraries/analysis-javascript/lib/target/TargetVisitor.ts index 0a1ae465e..905a43a06 100644 --- a/libraries/analysis-javascript/lib/target/TargetVisitor.ts +++ b/libraries/analysis-javascript/lib/target/TargetVisitor.ts @@ -53,11 +53,14 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { private _subTargets: SubTarget[]; + private _equalObjects: Map>; + constructor(filePath: string, syntaxForgiving: boolean, exports: Export[]) { super(filePath, syntaxForgiving); TargetVisitor.LOGGER = getLogger("TargetVisitor"); this._exports = exports; this._subTargets = []; + this._equalObjects = new Map(); } private _getExport(id: string): Export | undefined { @@ -372,6 +375,23 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { path.skip(); }; + // public ObjectExpression: ( + // path: NodePath + // ) => void = (path) => { + // const targetName = this._getTargetNameOfExpression(path); + + // if (!targetName) { + // return; + // } + + // const id = this._getNodeId(path); + // const export_ = this._getExport(id); + + // this._extractFromObjectExpression(path, id, id, targetName, export_); + + // path.skip(); + // }; + public VariableDeclarator: (path: NodePath) => void = ( path ) => { @@ -381,10 +401,11 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { } const idPath = >path.get("id"); const init = path.get("init"); + const initId = this._getNodeId(init); const targetName = idPath.node.name; const id = this._getNodeId(path); - const typeId = this._getNodeId(init); + const typeId = initId; const export_ = this._getExport(id); if (init.isFunction()) { @@ -400,7 +421,10 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { } else if (init.isClass()) { this._extractFromClass(init, id, typeId, targetName, export_); } else if (init.isObjectExpression()) { - this._extractFromObjectExpression(init, id, typeId, targetName, export_); + this._findOrCreateObject(id, typeId, id, targetName); + this._extractFromObjectExpression(init, id); + } else if (init.isIdentifier()) { + this._setEqual(id, initId); } else { // TODO } @@ -414,36 +438,37 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { const left = path.get("left"); const right = path.get("right"); - if ( - !right.isFunction() && - !right.isClass() && - !right.isObjectExpression() - ) { - return; - } - const targetName = this._getTargetNameOfExpression(right); if (!targetName) { return; } let isObject = false; let isMethod = false; - let objectId: string; + let superId: string; + + let id: string; + + if (left.isIdentifier()) { + // x = ? + id = this._getBindingId(left); + } else { + // ? = ? + id = this._getBindingId(right); + } - let id: string = this._getBindingId(left); if (left.isMemberExpression()) { const object = left.get("object"); const property = left.get("property"); if (property.isIdentifier() && left.node.computed) { path.skip(); - this._logOrFail(computedProperty(left.type, this._getNodeId(path))); return; - } else if (!left.get("property").isIdentifier() && !left.node.computed) { + } else if (!property.isIdentifier() && !left.node.computed) { // we also dont support a.f() = ? // or equivalent path.skip(); + this._logOrFail(unsupportedSyntax(left.type, this._getNodeId(path))); return; } @@ -460,28 +485,36 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { // module.exports = ? isObject = false; id = this._getBindingId(right); - } else { + } else if ( + (property.isIdentifier() && property.node.name === "prototype") || + (property.isStringLiteral() && property.node.value === "prototype") + ) { + // x.prototype = ? + // x['prototype'] = ? isObject = true; - objectId = this._getBindingId(object); - // find object - const objectTarget = this._subTargets.find( - (value) => value.id === objectId && value.type === TargetType.OBJECT + superId = this._getBindingId(object); + const typeId = this._getBindingId(right); + + this._findAndReplaceOrCreateClass( + superId, + typeId, + superId, + object.node.name ); + isMethod = true; - if (!objectTarget) { - const export_ = this._getExport(objectId); - // create one if it does not exist - const objectTarget: ObjectTarget = { - id: objectId, - typeId: objectId, - name: object.node.name, - type: TargetType.OBJECT, - exported: !!export_, - default: export_ ? export_.default : false, - module: export_ ? export_.module : false, - }; - this._subTargets.push(objectTarget); - } + // // find object + // this._findOrCreateObject(superId, typeId, superId, object.node.name) + + const prototypeId = this._getBindingId(right); + + this._setEqual(superId, prototypeId); + } else { + // x.x = ? + isObject = true; + superId = this._getBindingId(object); + // find object + this._findOrCreateObject(superId, superId, superId, object.node.name); } } else if (object.isMemberExpression()) { // ?.?.? = ? @@ -490,47 +523,21 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { // what about module.exports.x if ( subObject.isIdentifier() && - subProperty.isIdentifier() && - subProperty.node.name === "prototype" + ((subProperty.isIdentifier() && + subProperty.node.name === "prototype") || + (subProperty.isStringLiteral() && + subProperty.node.value === "prototype")) ) { // x.prototype.? = ? - objectId = this._getBindingId(subObject); - const objectTarget = ( - this._subTargets.find((value) => value.id === objectId) + // x['prototype'].? = ? + superId = this._getBindingId(subObject); + + this._findAndReplaceOrCreateClass( + superId, + superId, + superId, + subObject.node.name ); - - const newTargetClass: ClassTarget = { - id: objectTarget.id, - type: TargetType.CLASS, - name: objectTarget.name, - typeId: objectTarget.id, - exported: objectTarget.exported, - renamedTo: objectTarget.renamedTo, - module: objectTarget.module, - default: objectTarget.default, - }; - - // replace original target by prototype class - this._subTargets[this._subTargets.indexOf(objectTarget)] = - newTargetClass; - - const constructorTarget: MethodTarget = { - id: objectTarget.id, - type: TargetType.METHOD, - name: objectTarget.name, - typeId: objectTarget.id, - methodType: "constructor", - classId: objectTarget.id, - visibility: "public", - isStatic: false, - isAsync: - "isAsync" in objectTarget - ? (objectTarget).isAsync - : false, - }; - - this._subTargets.push(constructorTarget); - isMethod = true; } } else { @@ -540,7 +547,7 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { } const typeId = this._getNodeId(right); - const export_ = this._getExport(isObject ? objectId : id); + const export_ = this._getExport(isObject || isMethod ? superId : id); if (right.isFunction()) { this._extractFromFunction( @@ -551,12 +558,15 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { export_, isObject, isMethod, - objectId + superId ); } else if (right.isClass()) { this._extractFromClass(right, id, typeId, targetName, export_); } else if (right.isObjectExpression()) { - this._extractFromObjectExpression(right, id, typeId, targetName, export_); + this._findOrCreateObject(id, typeId, isObject ? superId : id, targetName); + this._extractFromObjectExpression(right, id); + } else if (right.isIdentifier()) { + this._setEqual(id, this._getBindingId(right)); } else { // TODO } @@ -564,6 +574,136 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { path.skip(); }; + private _findAndReplaceOrCreateClass( + id: string, + typeId: string, + exportId: string, + name: string + ) { + const objectTarget = ( + this._subTargets.find( + (value) => value.id === id && value.type === TargetType.OBJECT + ) + ); + + const functionTarget = ( + this._subTargets.find( + (value) => value.id === id && value.type === TargetType.FUNCTION + ) + ); + + const classTarget = ( + this._subTargets.find( + (value) => value.id === id && value.type === TargetType.CLASS + ) + ); + + if ( + (objectTarget && classTarget) || + (objectTarget && functionTarget) || + (classTarget && functionTarget) + ) { + // only one can exist + throw new Error("should not be possible"); + } + + if (objectTarget) { + const newClassTarget: ClassTarget = { + id: id, + type: TargetType.CLASS, + name: objectTarget.name, + typeId: id, + exported: objectTarget.exported, + renamedTo: objectTarget.renamedTo, + module: objectTarget.module, + default: objectTarget.default, + }; + // replace original target by prototype class + this._subTargets[this._subTargets.indexOf(objectTarget)] = newClassTarget; + + return newClassTarget; + } else if (functionTarget) { + const newClassTarget: ClassTarget = { + id: id, + type: TargetType.CLASS, + name: functionTarget.name, + typeId: id, + exported: functionTarget.exported, + renamedTo: functionTarget.renamedTo, + module: functionTarget.module, + default: functionTarget.default, + }; + // replace original target by prototype class + this._subTargets[this._subTargets.indexOf(functionTarget)] = + newClassTarget; + + const constructorTarget: MethodTarget = { + id: id, + type: TargetType.METHOD, + name: functionTarget.name, + typeId: id, + methodType: "constructor", + classId: newClassTarget.id, + visibility: "public", + isStatic: false, + isAsync: + "isAsync" in functionTarget + ? (functionTarget).isAsync + : false, + }; + + this._subTargets.push(constructorTarget); + + return newClassTarget; + } else if (classTarget) { + // nothing igues? + return classTarget; + } else { + const export_ = this._getExport(exportId); + + const newClassTarget: ClassTarget = { + id: id, + type: TargetType.CLASS, + name: name, + typeId: typeId, + exported: !!export_, + default: export_ ? export_.default : false, + module: export_ ? export_.module : false, + }; + this._subTargets.push(newClassTarget); + return newClassTarget; + } + } + + private _findOrCreateObject( + id: string, + typeId: string, + exportId: string, + name: string + ) { + const objectTarget = this._subTargets.find( + (value) => value.id === id && value.type === TargetType.OBJECT + ); + + if (!objectTarget) { + const export_ = this._getExport(exportId); + // create one if it does not exist + const objectTarget: ObjectTarget = { + id: id, + typeId: typeId, + name: name, + type: TargetType.OBJECT, + exported: !!export_, + default: export_ ? export_.default : false, + module: export_ ? export_.module : false, + }; + this._subTargets.push(objectTarget); + return objectTarget; + } + + return objectTarget; + } + private _extractFromFunction( path: NodePath, functionId: string, @@ -643,23 +783,8 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { private _extractFromObjectExpression( path: NodePath, - objectId: string, - typeId: string, - objectName: string, - export_?: Export + objectId: string ) { - const target: ObjectTarget = { - id: objectId, - typeId: typeId, - name: objectName, - type: TargetType.OBJECT, - exported: !!export_, - default: export_ ? export_.default : false, - module: export_ ? export_.module : false, - }; - - this._subTargets.push(target); - // loop over object properties for (const property of path.get("properties")) { if (property.isObjectMethod()) { @@ -740,7 +865,10 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { } else if (value.isClass()) { this._extractFromClass(value, id, id, targetName); } else if (value.isObjectExpression()) { - this._extractFromObjectExpression(value, id, id, targetName); + this._findOrCreateObject(id, id, id, targetName); + this._extractFromObjectExpression(value, id); + } else if (value.isIdentifier()) { + this._setEqual(id, this._getBindingId(value)); } else { // TODO } @@ -833,7 +961,10 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { } else if (value.isClass()) { this._extractFromClass(value, id, id, targetName); } else if (value.isObjectExpression()) { - this._extractFromObjectExpression(value, id, id, targetName); + this._findOrCreateObject(id, typeId, id, targetName); + this._extractFromObjectExpression(value, id); + } else if (value.isIdentifier()) { + this._setEqual(id, this._getBindingId(value)); } else { // TODO } @@ -846,39 +977,223 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { } } - get subTargets(): SubTarget[] { - return this._subTargets - .reverse() - .filter((subTarget, index, self) => { - if (!("name" in subTarget)) { - // paths/branches/lines are always unique - return true; - } + private _setEqual(idA: string, idB: string) { + if (idA === idB) { + return; + } + if (this._equalObjects.has(idA) && this._equalObjects.has(idB)) { + // merge them + const merged = new Set([ + ...this._equalObjects.get(idA), + ...this._equalObjects.get(idB), + ]); + // update for each entry? + for (const id of merged) { + this._equalObjects.set(id, merged); + } + } else if (this._equalObjects.has(idA)) { + // add b to a + this._equalObjects.get(idA).add(idB); + } else if (this._equalObjects.has(idB)) { + // add a to b + this._equalObjects.get(idB).add(idA); + } else { + // create new set + const set = new Set([idA, idB]); + this._equalObjects.set(idA, set); + this._equalObjects.set(idB, set); + } + } + + private _equalize(): SubTarget[] { + const subTargets = [...this._subTargets]; + const originalTargets = [...subTargets]; + + const processedSets = new Set>(); + console.log(this._equalObjects); + for (const set of this._equalObjects.values()) { + if (processedSets.has(set)) { + continue; + } + processedSets.add(set); + const asArray = [...set]; + for (let index = 0; index < asArray.length; index++) { + for (let index_ = index + 1; index_ < asArray.length; index_++) { + const a = asArray[index]; + const b = asArray[index_]; + + const subTargetA = originalTargets.filter( + (s) => + s.id === a && + (s.type === TargetType.OBJECT || s.type === TargetType.CLASS) + ); + const subTargetB = originalTargets.filter( + (s) => + s.id === b && + (s.type === TargetType.OBJECT || s.type === TargetType.CLASS) + ); - // filter duplicates because of redefinitions - // e.g. let a = 1; a = 2; - // this would result in two subtargets with the same name "a" - // but we only want the last one - return ( - index === - self.findIndex((t) => { - return ( - "name" in t && - t.id === subTarget.id && - t.type === subTarget.type && - t.name === subTarget.name && - (t.type === TargetType.METHOD - ? (t).methodType === - (subTarget).methodType && - (t).isStatic === - (subTarget).isStatic && - (t).classId === - (subTarget).classId - : true) + if (subTargetA.length === 0 || subTargetB.length === 0) { + continue; + } + + if (subTargetA.length !== 1) { + console.log(subTargetA); + throw new Error( + `Should always be 1 but is ${subTargetA.length} ${a}` ); - }) - ); - }) - .reverse(); + } + + if (subTargetB.length !== 1) { + console.log(subTargetB); + throw new Error( + `Should always be 1 but is ${subTargetB.length} ${b}` + ); + } + + if ( + subTargetA[0].type === TargetType.CLASS && + subTargetB[0].type === TargetType.OBJECT + ) { + this._convertMethodsToObjectFunctions( + subTargetA[0], + subTargetB[0], + originalTargets, + subTargets + ); + this._convertObjectFunctionsToMethods( + subTargetB[0], + subTargetA[0], + originalTargets, + subTargets + ); + } else if ( + subTargetA[0].type === TargetType.OBJECT && + subTargetB[0].type === TargetType.CLASS + ) { + this._convertMethodsToObjectFunctions( + subTargetB[0], + subTargetA[0], + originalTargets, + subTargets + ); + this._convertObjectFunctionsToMethods( + subTargetA[0], + subTargetB[0], + originalTargets, + subTargets + ); + } else { + // both objects?? + // both classes?? + throw new Error( + `Cannot both be objects or both classes ${subTargetA[0].id} && ${subTargetB[0].id}` + ); + } + } + } + } + + return subTargets; + } + + private _convertMethodsToObjectFunctions( + parentClass: ClassTarget, + newParentObject: ObjectTarget, + originalSubTargets: SubTarget[], + subTargets: SubTarget[] + ) { + const methods: MethodTarget[] = ( + originalSubTargets.filter( + (v) => + v.type === TargetType.METHOD && + (v).classId === parentClass.id + ) + ); + + for (const method of methods) { + const objectFunction: ObjectFunctionTarget = { + id: method.id, + typeId: method.typeId, + objectId: newParentObject.id, + name: method.name, + type: TargetType.OBJECT_FUNCTION, + isAsync: method.isAsync, + }; + // insert after original + subTargets.splice(subTargets.indexOf(method) + 1, 0, objectFunction); + } + } + + private _convertObjectFunctionsToMethods( + parentObject: ObjectTarget, + newParentClass: ClassTarget, + originalSubTargets: SubTarget[], + subTargets: SubTarget[] + ) { + const objectFunctions: ObjectFunctionTarget[] = ( + originalSubTargets.filter( + (v) => + v.type === TargetType.OBJECT_FUNCTION && + (v).objectId === parentObject.id + ) + ); + + for (const objectFunction of objectFunctions) { + const method: MethodTarget = { + id: objectFunction.id, + typeId: objectFunction.typeId, + classId: newParentClass.id, + name: objectFunction.name, + type: TargetType.METHOD, + isAsync: objectFunction.isAsync, + isStatic: false, + methodType: "method", + visibility: "public", + }; + // insert after original + subTargets.splice(subTargets.indexOf(objectFunction) + 1, 0, method); + } + } + + get subTargets(): SubTarget[] { + // for equal objects: + const targets = this._equalize(); + + return ( + targets + .reverse() + // .filter((subTarget, index, self) => { + // if (!("name" in subTarget)) { + // // paths/branches/lines are always unique + // return true; + // } + + // // filter duplicates because of redefinitions + // // e.g. let a = 1; a = 2; + // // this would result in two subtargets with the same name "a" + // // but we only want the last one + // return ( + // index === + // self.findIndex((t) => { + // return ( + // "name" in t && + // t.id === subTarget.id && + // t.type === subTarget.type && + // t.name === subTarget.name && + // (t.type === TargetType.METHOD + // ? (t).methodType === + // (subTarget).methodType && + // (t).isStatic === + // (subTarget).isStatic && + // (t).classId === + // (subTarget).classId + // : true) + // ); + // }) + // ); + // }) + .reverse() + ); } } diff --git a/libraries/analysis-javascript/package.json b/libraries/analysis-javascript/package.json index 12f6f8365..558e8e425 100644 --- a/libraries/analysis-javascript/package.json +++ b/libraries/analysis-javascript/package.json @@ -40,7 +40,7 @@ "format:check": "prettier --config ../../.prettierrc.json --ignore-path ../../.prettierignore --check .", "lint": "eslint --config ../../.eslintrc.json --ignore-path ../../.eslintignore .", "lint:fix": "eslint --config ../../.eslintrc.json --ignore-path ../../.eslintignore . --fix", - "test": "mocha --config ../../.mocharc.json", + "test": "mocha --config ../../.mocharc.json -g 'Prototyped TargetVisitor test'", "test:coverage": "nyc --reporter=text --reporter=html --reporter=lcov mocha --config ../../.mocharc.json", "test:coverage:ci": "nyc --reporter=lcovonly mocha --config ../../.mocharc.json --reporter json --reporter-option output=test-results.json", "test:watch": "mocha --config ../../.mocharc.json --watch" diff --git a/libraries/analysis-javascript/test/target/PrototypeTargetVisitor.test.ts b/libraries/analysis-javascript/test/target/PrototypeTargetVisitor.test.ts new file mode 100644 index 000000000..b036b5dfb --- /dev/null +++ b/libraries/analysis-javascript/test/target/PrototypeTargetVisitor.test.ts @@ -0,0 +1,180 @@ +/* + * Copyright 2020-2023 Delft University of Technology and SynTest contributors + * + * This file is part of SynTest Framework - SynTest JavaScript. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { traverse } from "@babel/core"; +import { TargetType } from "@syntest/analysis"; +import * as chai from "chai"; + +import { AbstractSyntaxTreeFactory } from "../../lib/ast/AbstractSyntaxTreeFactory"; +import { ExportVisitor } from "../../lib/target/export/ExportVisitor"; +import { + ClassTarget, + MethodTarget, + ObjectFunctionTarget, + ObjectTarget, + SubTarget, +} from "../../lib/target/Target"; +import { TargetVisitor } from "../../lib/target/TargetVisitor"; + +const expect = chai.expect; + +function targetHelper(source: string) { + const generator = new AbstractSyntaxTreeFactory(); + const ast = generator.convert("", source); + + const exportVisitor = new ExportVisitor("", true); + traverse(ast, exportVisitor); + const exports = exportVisitor.exports; + + const visitor = new TargetVisitor("", true, exports); + traverse(ast, visitor); + + return visitor.subTargets; +} +// function checkFunction( +// target: SubTarget, +// name: string, +// exported: boolean, +// isAsync: boolean +// ): void { +// expect(target.type).to.equal(TargetType.FUNCTION); + +// const functionTarget = target; + +// expect(functionTarget.name).to.equal(name); +// expect(functionTarget.exported).to.equal(exported); +// expect(functionTarget.isAsync).to.equal(isAsync); +// } + +function checkObject(target: SubTarget, name: string, exported: boolean): void { + expect(target.type).to.equal(TargetType.OBJECT); + + const objectTarget = target; + + expect(objectTarget.name).to.equal(name); + expect(objectTarget.exported).to.equal(exported); +} + +function checkObjectFunction( + target: SubTarget, + name: string, + objectId: string, + isAsync: boolean +): void { + expect(target.type).to.equal(TargetType.OBJECT_FUNCTION); + + const functionTarget = target; + + expect(functionTarget.name).to.equal(name); + expect(functionTarget.objectId).to.equal(objectId); + expect(functionTarget.isAsync).to.equal(isAsync); +} + +function checkClass(target: SubTarget, name: string, exported: boolean): void { + expect(target.type).to.equal(TargetType.CLASS); + + const classTarget = target; + + expect(classTarget.name).to.equal(name); + expect(classTarget.exported).to.equal(exported); +} + +function checkClassMethod( + target: SubTarget, + name: string, + classId: string, + methodType: string, + visibility: string, + isStatic: boolean, + isAsync: boolean +): void { + expect(target.type).to.equal(TargetType.METHOD); + + const methodTarget = target; + + expect(methodTarget.name).to.equal(name); + expect(methodTarget.classId).to.equal(classId); + expect(methodTarget.methodType).to.equal(methodType); + expect(methodTarget.visibility).to.equal(visibility); + expect(methodTarget.isStatic).to.equal(isStatic); + expect(methodTarget.isAsync).to.equal(isAsync); +} + +describe("Prototyped TargetVisitor test", () => { + it("Test 1", () => { + const source = ` + const x = {} + x.prototype.y = function () {} + x.prototype.z = function () {} + + module.exports = x + `; + + const targets = targetHelper(source); + + expect(targets.length).to.equal(3); + + checkClass(targets[0], "x", true); + checkClassMethod( + targets[1], + "y", + targets[0].id, + "method", + "public", + false, + false + ); + checkClassMethod( + targets[2], + "z", + targets[0].id, + "method", + "public", + false, + false + ); + }); + + it("Test 2", () => { + const source = ` + const x = {} + const y = {} + y.f = function () {} + x.prototype = y + + module.exports = x + `; + + const targets = targetHelper(source); + + expect(targets.length).to.equal(4); + + checkClass(targets[0], "x", true); + checkObject(targets[1], "y", false); + + checkObjectFunction(targets[2], "f", targets[1].id, false); + checkClassMethod( + targets[3], + "f", + targets[0].id, + "method", + "public", + false, + false + ); + }); +}); diff --git a/tools/javascript/lib/JavaScriptLauncher.ts b/tools/javascript/lib/JavaScriptLauncher.ts index 0f38276b1..a714ca0db 100644 --- a/tools/javascript/lib/JavaScriptLauncher.ts +++ b/tools/javascript/lib/JavaScriptLauncher.ts @@ -743,6 +743,7 @@ export class JavaScriptLauncher extends Launcher { .getActionableTargets() .filter((target) => isExported(target)); + console.log(currentSubject.getActionableTargets()); if (rootTargets.length === 0) { JavaScriptLauncher.LOGGER.info( `No actionable exported root targets found for ${target.name} in ${target.path}` From 092548706c55e10c4e6d460bf5c944cdfcde0135 Mon Sep 17 00:00:00 2001 From: Dimitri Stallenberg Date: Mon, 2 Oct 2023 14:57:44 +0200 Subject: [PATCH 3/3] feat: update target visitor --- .../lib/target/TargetVisitor.ts | 26 +++++++++---------- .../lib/testbuilding/JavaScriptDecoder.ts | 4 --- .../lib/testcase/execution/TestExecutor.ts | 2 +- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/libraries/analysis-javascript/lib/target/TargetVisitor.ts b/libraries/analysis-javascript/lib/target/TargetVisitor.ts index 905a43a06..f2aea46b1 100644 --- a/libraries/analysis-javascript/lib/target/TargetVisitor.ts +++ b/libraries/analysis-javascript/lib/target/TargetVisitor.ts @@ -1037,19 +1037,19 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { continue; } - if (subTargetA.length !== 1) { - console.log(subTargetA); - throw new Error( - `Should always be 1 but is ${subTargetA.length} ${a}` - ); - } - - if (subTargetB.length !== 1) { - console.log(subTargetB); - throw new Error( - `Should always be 1 but is ${subTargetB.length} ${b}` - ); - } + // if (subTargetA.length !== 1) { + // console.log(subTargetA); + // throw new Error( + // `Should always be 1 but is ${subTargetA.length} ${a}` + // ); + // } + + // if (subTargetB.length !== 1) { + // console.log(subTargetB); + // throw new Error( + // `Should always be 1 but is ${subTargetB.length} ${b}` + // ); + // } if ( subTargetA[0].type === TargetType.CLASS && diff --git a/libraries/search-javascript/lib/testbuilding/JavaScriptDecoder.ts b/libraries/search-javascript/lib/testbuilding/JavaScriptDecoder.ts index 5760320b9..bf4213c5d 100644 --- a/libraries/search-javascript/lib/testbuilding/JavaScriptDecoder.ts +++ b/libraries/search-javascript/lib/testbuilding/JavaScriptDecoder.ts @@ -74,10 +74,6 @@ export class JavaScriptDecoder implements Decoder { decodings = decodings.slice(0, index); } - if (decodings.length === 0) { - throw new Error("No statements in test case after error reduction"); - } - const metaCommentBlock = this.generateMetaComments(testCase); const testLines: string[] = this.generateTestLines( diff --git a/libraries/search-javascript/lib/testcase/execution/TestExecutor.ts b/libraries/search-javascript/lib/testcase/execution/TestExecutor.ts index 53409f357..93506d405 100644 --- a/libraries/search-javascript/lib/testcase/execution/TestExecutor.ts +++ b/libraries/search-javascript/lib/testcase/execution/TestExecutor.ts @@ -135,7 +135,7 @@ async function runMocha( return { status: status, error: - status === JavaScriptExecutionStatus.FAILED + test && test.err ? { name: test.err.name, message: test.err.message,