From 7d6b3579e9882b1f079e32c93c930624755d7256 Mon Sep 17 00:00:00 2001 From: Dimitri Stallenberg Date: Fri, 29 Sep 2023 09:31:07 +0200 Subject: [PATCH 1/5] 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/5] 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 983fde6ce89cb4f233cf25c05cdbaf905dcc250c Mon Sep 17 00:00:00 2001 From: Dimitri Stallenberg Date: Mon, 2 Oct 2023 14:36:30 +0200 Subject: [PATCH 3/5] feat: refactor targetting --- .../lib/target/Target copy.ts | 68 +++++++++++++++++++ .../lib/target/VisibilityType.ts | 2 - 2 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 libraries/analysis-javascript/lib/target/Target copy.ts diff --git a/libraries/analysis-javascript/lib/target/Target copy.ts b/libraries/analysis-javascript/lib/target/Target copy.ts new file mode 100644 index 000000000..895735a96 --- /dev/null +++ b/libraries/analysis-javascript/lib/target/Target copy.ts @@ -0,0 +1,68 @@ +/* + * Copyright 2020-2021 Delft University of Technology and SynTest contributors + * + * This file is part of SynTest Framework - SynTest Core. + * + * 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 { TargetType } from "@syntest/analysis"; + +import { VisibilityType } from "./VisibilityType"; + +export type Target = { + path: string; + name: string; + subTargets: SubTarget[]; +}; + +export type SubTarget = ClassSubTarget; + +export type BaseSubTarget = { + id: string; + typeId: string; + name: string; +}; + +export type ClassSubTarget = BaseSubTarget & { + type: TargetType.CLASS; + exportId: string; + subTargets: MethodTarget[]; +}; + +export type ObjectSubTarget = SubTarget & { + type: TargetType.OBJECT; + exportId: string; + subTargets: ObjectFunctionTarget[]; +}; + +export type FunctionSubTarget = BaseSubTarget & { + type: TargetType.FUNCTION; + exportId: string; + isAsync: boolean; + subTargets: SubTarget[]; +}; + +export type MethodTarget = BaseSubTarget & { + type: TargetType.METHOD; + isAsync: boolean; + isStatic: boolean; + visibility: VisibilityType; + methodType: "constructor" | "method" | "get" | "set"; + subTargets: SubTarget[]; +}; + +export type ObjectFunctionTarget = BaseSubTarget & { + type: TargetType.OBJECT_FUNCTION; + isAsync: boolean; + subTargets: SubTarget[]; +}; diff --git a/libraries/analysis-javascript/lib/target/VisibilityType.ts b/libraries/analysis-javascript/lib/target/VisibilityType.ts index 6090501c0..8ea92e33d 100644 --- a/libraries/analysis-javascript/lib/target/VisibilityType.ts +++ b/libraries/analysis-javascript/lib/target/VisibilityType.ts @@ -18,7 +18,5 @@ /** * Visibility Types. - * - * @author Dimitri Stallenberg */ export type VisibilityType = "public" | "private" | "protected"; From 092548706c55e10c4e6d460bf5c944cdfcde0135 Mon Sep 17 00:00:00 2001 From: Dimitri Stallenberg Date: Mon, 2 Oct 2023 14:57:44 +0200 Subject: [PATCH 4/5] 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, From 7a3a279569ad673a6fd5b5f1dfd0986b31740702 Mon Sep 17 00:00:00 2001 From: Dimitri Stallenberg Date: Tue, 3 Oct 2023 15:09:17 +0200 Subject: [PATCH 5/5] fix: update --- .../lib/target/Target copy.ts | 68 - .../analysis-javascript/lib/target/Target.ts | 142 +- .../lib/target/TargetFactory.ts | 19 +- .../lib/target/TargetVisitor.ts | 1347 +++-------------- libraries/analysis-javascript/package.json | 2 +- .../test/target/TargetVisitor.test.ts | 139 +- 6 files changed, 369 insertions(+), 1348 deletions(-) delete mode 100644 libraries/analysis-javascript/lib/target/Target copy.ts diff --git a/libraries/analysis-javascript/lib/target/Target copy.ts b/libraries/analysis-javascript/lib/target/Target copy.ts deleted file mode 100644 index 895735a96..000000000 --- a/libraries/analysis-javascript/lib/target/Target copy.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2020-2021 Delft University of Technology and SynTest contributors - * - * This file is part of SynTest Framework - SynTest Core. - * - * 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 { TargetType } from "@syntest/analysis"; - -import { VisibilityType } from "./VisibilityType"; - -export type Target = { - path: string; - name: string; - subTargets: SubTarget[]; -}; - -export type SubTarget = ClassSubTarget; - -export type BaseSubTarget = { - id: string; - typeId: string; - name: string; -}; - -export type ClassSubTarget = BaseSubTarget & { - type: TargetType.CLASS; - exportId: string; - subTargets: MethodTarget[]; -}; - -export type ObjectSubTarget = SubTarget & { - type: TargetType.OBJECT; - exportId: string; - subTargets: ObjectFunctionTarget[]; -}; - -export type FunctionSubTarget = BaseSubTarget & { - type: TargetType.FUNCTION; - exportId: string; - isAsync: boolean; - subTargets: SubTarget[]; -}; - -export type MethodTarget = BaseSubTarget & { - type: TargetType.METHOD; - isAsync: boolean; - isStatic: boolean; - visibility: VisibilityType; - methodType: "constructor" | "method" | "get" | "set"; - subTargets: SubTarget[]; -}; - -export type ObjectFunctionTarget = BaseSubTarget & { - type: TargetType.OBJECT_FUNCTION; - isAsync: boolean; - subTargets: SubTarget[]; -}; diff --git a/libraries/analysis-javascript/lib/target/Target.ts b/libraries/analysis-javascript/lib/target/Target.ts index e8eb5bcf9..4bee4b912 100644 --- a/libraries/analysis-javascript/lib/target/Target.ts +++ b/libraries/analysis-javascript/lib/target/Target.ts @@ -15,85 +15,103 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - SubTarget as CoreSubTarget, - Target as CoreTarget, - TargetType, -} from "@syntest/analysis"; +import { SubTarget as CoreSubTarget, + Target as CoreTarget, TargetType } from "@syntest/analysis"; import { VisibilityType } from "./VisibilityType"; -export interface Target extends CoreTarget { +export class TargetGraph implements CoreTarget { path: string; name: string; - subTargets: SubTarget[]; -} + subTargets: Target[]; // deprecated + targetMap: Map; + childTargetMap: Map + parentTargetMap: Map + + constructor() { + this.targetMap = new Map() + this.childTargetMap = new Map() + this.parentTargetMap = new Map() + } + + // TODO check for cycles + + hasTarget(targetId: string) { + return this.targetMap.has(targetId) + } + + addTarget(target: Target, parent?: Target) { + if (this.targetMap.has(target.id)) { + throw new Error('Each target can only exists once!') + } + this.targetMap.set(target.id, target) + this.childTargetMap.set(target.id, []) + if (parent) { + if (!this.targetMap.has(parent.id)) { + throw new Error('parent does not exist! Targets should be added in order') + } + this.parentTargetMap.set(target.id, parent.id) + this.childTargetMap.get(parent.id).push(target.id) + } + } + + getParentId(target: Target | string): undefined | string { + return typeof target === 'string' ? this.parentTargetMap.get(target) : this.parentTargetMap.get(target.id) + } + + getChildrenIds(target: Target | string): undefined | string[] { + return typeof target === 'string' ? this.childTargetMap.get(target) : this.childTargetMap.get(target.id) + } + + getParent(target: Target | string): undefined | Target { + const parentId = typeof target === 'string' ? this.parentTargetMap.get(target) : this.parentTargetMap.get(target.id) + return parentId ? this.targetMap.get(parentId) : undefined + } + + getChildren(target: Target | string): undefined | Target[] { + const childrenIds = typeof target === 'string' ? this.childTargetMap.get(target) : this.childTargetMap.get(target.id) + return childrenIds ? childrenIds.map((id) => this.targetMap.get(id)) : undefined + } +}; -export interface SubTarget extends CoreSubTarget { - type: TargetType; - id: string; -} +export type Target = RootTarget | CoverageTarget +export type RootTarget = ClassTarget | ObjectTarget | FunctionTarget +export type CoverageTarget = LineTarget | BranchTarget | PathTarget -export interface NamedSubTarget extends SubTarget { - name: string; +export type BaseTarget = CoreSubTarget & { + id: string; typeId: string; -} - -export type Exportable = { - exported: boolean; - // maybe scope? - renamedTo?: string; - module?: boolean; - default?: boolean; + exportId: string; }; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function isExported(target: any): target is Exportable { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - return "exported" in target && target.exported === true; -} - -export interface Callable { - isAsync: boolean; -} - -export interface FunctionTarget extends NamedSubTarget, Exportable, Callable { - type: TargetType.FUNCTION; -} - -export interface ClassTarget extends NamedSubTarget, Exportable { +export type ClassTarget = BaseTarget & { type: TargetType.CLASS; -} +}; -export interface MethodTarget extends NamedSubTarget, Callable { - type: TargetType.METHOD; - classId: string; +export type ObjectTarget = BaseTarget & { + type: TargetType.OBJECT; +}; +export type FunctionTarget = BaseTarget & { + type: TargetType.FUNCTION; + isAsync: boolean; + isStatic: boolean; visibility: VisibilityType; - methodType: "constructor" | "method" | "get" | "set"; - isStatic: boolean; -} - -export interface ObjectTarget extends NamedSubTarget, Exportable { - type: TargetType.OBJECT; -} - -export interface ObjectFunctionTarget extends NamedSubTarget, Callable { - type: TargetType.OBJECT_FUNCTION; - objectId: string; -} +}; -export interface PathTarget extends SubTarget { - type: TargetType.PATH; - ids: string[]; +export type LineTarget = { + id: string + type: TargetType.LINE } -export interface BranchTarget extends SubTarget { - type: TargetType.BRANCH; +export type BranchTarget = { + id: string + type: TargetType.BRANCH + } -export interface LineTarget extends SubTarget { - type: TargetType.LINE; - line: number; -} +export type PathTarget = { + id: string + type: TargetType.PATH +} \ No newline at end of file diff --git a/libraries/analysis-javascript/lib/target/TargetFactory.ts b/libraries/analysis-javascript/lib/target/TargetFactory.ts index c9fa9f2fc..f9659a26c 100644 --- a/libraries/analysis-javascript/lib/target/TargetFactory.ts +++ b/libraries/analysis-javascript/lib/target/TargetFactory.ts @@ -16,22 +16,17 @@ * limitations under the License. */ -import * as path from "node:path"; - import { traverse } from "@babel/core"; import * as t from "@babel/types"; import { TargetFactory as CoreTargetFactory } from "@syntest/analysis"; import { Factory } from "../Factory"; -import { ExportVisitor } from "./export/ExportVisitor"; import { Target } from "./Target"; import { TargetVisitor } from "./TargetVisitor"; /** * TargetFactory for Javascript. - * - * @author Dimitri Stallenberg */ export class TargetFactory extends Factory @@ -44,23 +39,13 @@ export class TargetFactory * @param AST The AST of the target */ extract(filePath: string, AST: t.Node): Target { - // bit sad that we have to do this twice, but we need to know the exports - const exportVisitor = new ExportVisitor(filePath, this.syntaxForgiving); - - traverse(AST, exportVisitor); - - const exports = exportVisitor.exports; - const visitor = new TargetVisitor(filePath, this.syntaxForgiving, exports); + const visitor = new TargetVisitor(filePath, this.syntaxForgiving); traverse(AST, visitor); // we should check wether every export is actually used // TODO - return { - path: filePath, - name: path.basename(filePath), - subTargets: visitor.subTargets, - }; + return visitor._getTargetGraph() } } diff --git a/libraries/analysis-javascript/lib/target/TargetVisitor.ts b/libraries/analysis-javascript/lib/target/TargetVisitor.ts index f2aea46b1..a03711043 100644 --- a/libraries/analysis-javascript/lib/target/TargetVisitor.ts +++ b/libraries/analysis-javascript/lib/target/TargetVisitor.ts @@ -22,1178 +22,295 @@ import { TargetType } from "@syntest/analysis"; import { AbstractSyntaxTreeVisitor } from "@syntest/ast-visitor-javascript"; import { getLogger, Logger } from "@syntest/logging"; -import { computedProperty, unsupportedSyntax } from "../utils/diagnostics"; -import { Export } from "./export/Export"; import { - Callable, ClassTarget, - Exportable, FunctionTarget, - MethodTarget, - NamedSubTarget, - ObjectFunctionTarget, ObjectTarget, - SubTarget, + Target, + TargetGraph, } from "./Target"; export class TargetVisitor extends AbstractSyntaxTreeVisitor { protected static override LOGGER: Logger; - private _logOrFail(message: string): undefined { - if (this.syntaxForgiving) { - TargetVisitor.LOGGER.warn(message); - return undefined; - } else { - throw new Error(message); - } - } - - private _exports: Export[]; - - private _subTargets: SubTarget[]; + // private _logOrFail(message: string): undefined { + // if (this.syntaxForgiving) { + // TargetVisitor.LOGGER.warn(message); + // return undefined; + // } else { + // throw new Error(message); + // } + // } - private _equalObjects: Map>; + private _targetGraph: TargetGraph; + private _parentStack: Target[] - constructor(filePath: string, syntaxForgiving: boolean, exports: Export[]) { + constructor(filePath: string, syntaxForgiving: boolean) { super(filePath, syntaxForgiving); TargetVisitor.LOGGER = getLogger("TargetVisitor"); - this._exports = exports; - this._subTargets = []; - this._equalObjects = new Map(); + this._targetGraph = new TargetGraph(); } - private _getExport(id: string): Export | undefined { - return this._exports.find((x) => { - return x.id === id; - }); + public _getTargetGraph() { + return this._targetGraph } - private _getTargetNameOfDeclaration( - path: NodePath - ): string { - if (path.node.id === null) { - if (path.parentPath.node.type === "ExportDefaultDeclaration") { - // e.g. export default class {} - // e.g. export default function () {} - return "default"; - } else { - // e.g. class {} - // e.g. function () {} - // Should not be possible - return this._logOrFail( - unsupportedSyntax(path.type, this._getNodeId(path)) - ); - } - } else { - // e.g. class x {} - // e.g. function x() {} - return path.node.id.name; - } + private _currentParent(): Target { + return this._parentStack[this._parentStack.length - 1] } - /** - * Get the target name of an expression - * The variable the expression is assigned to is used as the target name - * @param path - * @returns - */ - private _getTargetNameOfExpression(path: NodePath): string { - // e.g. const x = class A {} - // e.g. const x = function A {} - // e.g. const x = () => {} - // we always use x as the target name instead of A - const parentNode = path.parentPath.node; - switch (parentNode.type) { - case "VariableDeclarator": { - // e.g. const ?? = class {} - // e.g. const ?? = function {} - // e.g. const ?? = () => {} - if (parentNode.id.type === "Identifier") { - // e.g. const x = class {} - // e.g. const x = function {} - // e.g. const x = () => {} - return parentNode.id.name; - } else { - // e.g. const {x} = class {} - // e.g. const {x} = function {} - // e.g. const {x} = () => {} - // Should not be possible - return this._logOrFail( - unsupportedSyntax(path.node.type, this._getNodeId(path)) - ); - } - } - case "AssignmentExpression": { - // e.g. ?? = class {} - // e.g. ?? = function {} - // e.g. ?? = () => {} - const assigned = parentNode.left; - if (assigned.type === "Identifier") { - // could also be memberexpression - // e.g. x = class {} - // e.g. x = function {} - // e.g. x = () => {} - return assigned.name; - } else if (assigned.type === "MemberExpression") { - // e.g. x.? = class {} - // e.g. x.? = function {} - // e.g. x.? = () => {} - if (assigned.computed === true) { - if (assigned.property.type.includes("Literal")) { - // e.g. x["y"] = class {} - // e.g. x["y"] = function {} - // e.g. x["y"] = () => {} - return "value" in assigned.property - ? assigned.property.value.toString() - : "null"; - } else { - // e.g. x[y] = class {} - // e.g. x[y] = function {} - // e.g. x[y] = () => {} - return this._logOrFail( - computedProperty(path.node.type, this._getNodeId(path)) - ); - } - } else if (assigned.property.type === "Identifier") { - // e.g. x.y = class {} - // e.g. x.y = function {} - // e.g. x.y = () => {} - if ( - assigned.property.name === "exports" && - assigned.object.type === "Identifier" && - assigned.object.name === "module" - ) { - // e.g. module.exports = class {} - // e.g. module.exports = function {} - // e.g. module.exports = () => {} - return "id" in parentNode.right - ? parentNode.right.id.name - : "anonymousFunction"; - } - return assigned.property.name; - } else { - // e.g. x.? = class {} - // e.g. x.? = function {} - // e.g. x.? = () => {} - // Should not be possible - return this._logOrFail( - unsupportedSyntax(path.node.type, this._getNodeId(path)) - ); - } - } else { - // e.g. {x} = class {} - // e.g. {x} = function {} - // e.g. {x} = () => {} - // Should not be possible - return this._logOrFail( - unsupportedSyntax(path.node.type, this._getNodeId(path)) - ); - } - } - case "ClassProperty": - // e.g. class A { ? = class {} } - // e.g. class A { ? = function () {} } - // e.g. class A { ? = () => {} } - case "ObjectProperty": { - // e.g. {?: class {}} - // e.g. {?: function {}} - // e.g. {?: () => {}} - if (parentNode.key.type === "Identifier") { - // e.g. class A { x = class {} } - // e.g. class A { x = function () {} } - // e.g. class A { x = () => {} } - // e.g. {y: class {}} - // e.g. {y: function {}} - // e.g. {y: () => {}} - return parentNode.key.name; - } else if (parentNode.key.type.includes("Literal")) { - // e.g. class A { "x" = class {} } - // e.g. class A { "x" = function () {} } - // e.g. class A { "x" = () => {} } + public Function = { + enter: (path: NodePath) => { + const currentParent = this._currentParent() - // e.g. {1: class {}} - // e.g. {1: function {}} - // e.g. {1: () => {}} - return "value" in parentNode.key - ? parentNode.key.value.toString() - : "null"; - } else { - // e.g. const {x} = class {} - // e.g. const {x} = function {} - // e.g. const {x} = () => {} - - // e.g. {?: class {}} - // e.g. {?: function {}} - // e.g. {?: () => {}} - // Should not be possible - return this._logOrFail( - unsupportedSyntax(path.node.type, this._getNodeId(path)) - ); - } - } - case "ReturnStatement": - // e.g. return class {} - // e.g. return function () {} - // e.g. return () => {} - case "ArrowFunctionExpression": - // e.g. () => class {} - // e.g. () => function () {} - // e.g. () => () => {} - case "NewExpression": - // e.g. new Class(class {}) // dont think this one is possible but unsure - // e.g. new Class(function () {}) - // e.g. new Class(() => {}) - case "CallExpression": { - // e.g. function(class {}) // dont think this one is possible but unsure - // e.g. function(function () {}) - // e.g. function(() => {}) - return "id" in path.node && path.node.id && "name" in path.node.id - ? path.node.id.name - : "anonymous"; - } - case "ConditionalExpression": { - // e.g. c ? class {} : b - // e.g. c ? function () {} : b - // e.g. c ? () => {} : b - return this._getTargetNameOfExpression(path.parentPath); - } - case "LogicalExpression": { - // e.g. c || class {} - // e.g. c || function () {} - // e.g. c || () => {} - return this._getTargetNameOfExpression(path.parentPath); - } - case "ExportDefaultDeclaration": { - // e.g. export default class {} - // e.g. export default function () {} - // e.g. export default () => {} - return "default"; - } - default: { - // e.g. class {} - // e.g. function () {} - // e.g. () => {} - // Should not be possible - return this._logOrFail( - unsupportedSyntax(parentNode.type, this._getNodeId(path)) - ); - } - } - } - - public FunctionDeclaration: (path: NodePath) => void = - (path) => { - // e.g. function x() {} - const targetName = this._getTargetNameOfDeclaration(path); const id = this._getNodeId(path); - const export_ = this._getExport(id); - - this._extractFromFunction( - path, - id, - id, - targetName, - export_, - false, - false - ); - - path.skip(); - }; - - public ClassDeclaration: (path: NodePath) => void = ( - path - ) => { - // e.g. class A {} - const targetName = this._getTargetNameOfDeclaration(path); - const id = this._getNodeId(path); - const export_ = this._getExport(id); - - this._extractFromClass(path, id, id, targetName, export_); - - path.skip(); - }; - - public FunctionExpression: (path: NodePath) => void = ( - path - ) => { - // only thing left where these can be found is: - // call(function () {}) - const targetName = this._getTargetNameOfExpression(path); - - if (!targetName) { - return; - } - - const id = this._getNodeId(path); - const export_ = this._getExport(id); - - this._extractFromFunction(path, id, id, targetName, export_, false, false); - - path.skip(); - }; - - public ClassExpression: (path: NodePath) => void = ( - path - ) => { - // only thing left where these can be found is: - // call(class {}) - const targetName = this._getTargetNameOfExpression(path); - - if (!targetName) { - return; - } - - const id = this._getNodeId(path); - const export_ = this._getExport(id); - - this._extractFromClass(path, id, id, targetName, export_); - - path.skip(); - }; - - public ArrowFunctionExpression: ( - path: NodePath - ) => void = (path) => { - // only thing left where these can be found is: - // call(() => {}) - const targetName = this._getTargetNameOfExpression(path); - - if (!targetName) { - return; - } - - // TODO is there a difference if the parent is a variable declarator? - - const id = this._getNodeId(path); - const export_ = this._getExport(id); - - this._extractFromFunction(path, id, id, targetName, export_, false, false); - - 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 - ) => { - if (!path.has("init")) { - path.skip(); - return; - } - 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 = initId; - const export_ = this._getExport(id); - - if (init.isFunction()) { - this._extractFromFunction( - init, - id, - typeId, - targetName, - export_, - false, - false - ); - } else if (init.isClass()) { - this._extractFromClass(init, id, typeId, targetName, export_); - } else if (init.isObjectExpression()) { - this._findOrCreateObject(id, typeId, id, targetName); - this._extractFromObjectExpression(init, id); - } else if (init.isIdentifier()) { - this._setEqual(id, initId); - } else { - // TODO - } - - path.skip(); - }; - - public AssignmentExpression: ( - path: NodePath - ) => void = (path) => { - const left = path.get("left"); - const right = path.get("right"); - - const targetName = this._getTargetNameOfExpression(right); - if (!targetName) { - return; - } - let isObject = false; - let isMethod = false; - let superId: string; - - let id: string; - - if (left.isIdentifier()) { - // x = ? - id = this._getBindingId(left); - } else { - // ? = ? - id = this._getBindingId(right); - } - - 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 (!property.isIdentifier() && !left.node.computed) { - // we also dont support a.f() = ? - // or equivalent - path.skip(); - this._logOrFail(unsupportedSyntax(left.type, this._getNodeId(path))); - return; - } - - if (object.isIdentifier()) { - // x.? = ? - // x['?'] = ? - if ( - object.node.name === "exports" || - (object.node.name === "module" && - property.isIdentifier() && - property.node.name === "exports") - ) { - // exports.? = ? - // module.exports = ? - isObject = false; - id = this._getBindingId(right); - } else if ( - (property.isIdentifier() && property.node.name === "prototype") || - (property.isStringLiteral() && property.node.value === "prototype") - ) { - // x.prototype = ? - // x['prototype'] = ? - isObject = true; - superId = this._getBindingId(object); - const typeId = this._getBindingId(right); - - this._findAndReplaceOrCreateClass( - superId, - typeId, - superId, - object.node.name - ); - isMethod = true; - - // // 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()) { - // ?.?.? = ? - const subObject = object.get("object"); - const subProperty = object.get("property"); - // what about module.exports.x - if ( - subObject.isIdentifier() && - ((subProperty.isIdentifier() && - subProperty.node.name === "prototype") || - (subProperty.isStringLiteral() && - subProperty.node.value === "prototype")) - ) { - // x.prototype.? = ? - // x['prototype'].? = ? - superId = this._getBindingId(subObject); - - this._findAndReplaceOrCreateClass( - superId, - superId, - superId, - subObject.node.name - ); - isMethod = true; - } - } else { - path.skip(); - return; - } - } - - const typeId = this._getNodeId(right); - const export_ = this._getExport(isObject || isMethod ? superId : id); - - if (right.isFunction()) { - this._extractFromFunction( - right, - id, - typeId, - targetName, - export_, - isObject, - isMethod, - superId - ); - } else if (right.isClass()) { - this._extractFromClass(right, id, typeId, targetName, export_); - } else if (right.isObjectExpression()) { - this._findOrCreateObject(id, typeId, isObject ? superId : id, targetName); - this._extractFromObjectExpression(right, id); - } else if (right.isIdentifier()) { - this._setEqual(id, this._getBindingId(right)); - } else { - // TODO - } - - 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 = { + const newTarget: FunctionTarget = { 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, - typeId: string, - functionName: string, - export_: Export | undefined, - isObjectFunction: boolean, - isMethod: boolean, - superId?: string - ) { - let target: FunctionTarget | ObjectFunctionTarget | MethodTarget; - - if (isObjectFunction && isMethod) { - throw new Error("Cannot be method and object function"); - } - - if (isObjectFunction) { - if (!superId) { - throw new Error( - "if it is an object function the object id should be given" - ); - } - target = { - id: functionId, - typeId: typeId, - objectId: superId, - name: functionName, - type: TargetType.OBJECT_FUNCTION, - isAsync: path.node.async, - }; - } else if (isMethod) { - if (!superId) { - throw new Error( - "if it is an object function the object id should be given" - ); - } - target = { - id: functionId, - typeId: typeId, - classId: superId, - name: functionName, - type: TargetType.METHOD, - isAsync: path.node.async, - methodType: path.isClassMethod() ? path.node.kind : "method", - visibility: - path.isClassMethod() && path.node.access - ? path.node.access - : "public", - isStatic: - path.isClassMethod() || path.isClassProperty() - ? path.node.static - : false, - }; - } else { - target = { - id: functionId, - typeId: typeId, - name: functionName, + exportId: id, type: TargetType.FUNCTION, - exported: !!export_, - default: export_ ? export_.default : false, - module: export_ ? export_.module : false, isAsync: path.node.async, - }; - } - - this._subTargets.push(target); - - const body = path.get("body"); - - if (Array.isArray(body)) { - throw new TypeError("weird function body"); - } else { - body.visit(); + isStatic: path.isClassMethod() || path.isClassProperty() ? path.node.static : false, + methodType: path.isClassMethod() ? path.node.kind : "method", + visibility: path.isClassMethod() && path.node.access ? path.node.access : "public", + } + this._targetGraph.addTarget(newTarget, currentParent) + this._parentStack.push(newTarget) + }, + exit: (_path: NodePath) => { + // TODO check if it is me + this._parentStack.pop() } } - private _extractFromObjectExpression( - path: NodePath, - objectId: string - ) { - // loop over object properties - for (const property of path.get("properties")) { - if (property.isObjectMethod()) { - if (property.node.computed) { - // e.g. class A { ?() {} } - // unsupported - // not possible i think - this._logOrFail( - computedProperty(property.type, this._getNodeId(property)) - ); - continue; - } - const key = property.get("key"); - if (key.isIdentifier()) { - const targetName = key.node.name; - - const id = this._getNodeId(property); - this._extractFromFunction( - property, - id, - id, - targetName, - undefined, - true, - false, - objectId - ); - } else if (key.isLiteral()) { - const targetName = "value" in key ? String(key.value) : "null"; - - const id = this._getNodeId(property); - this._extractFromFunction( - property, - id, - id, - targetName, - undefined, - true, - false, - objectId - ); - } else { - // not possible i think - this._logOrFail( - unsupportedSyntax(property.node.type, this._getNodeId(property)) - ); - continue; - } - } else if (property.isObjectProperty()) { - const key = property.get("key"); - const value = property.get("value"); + public Class = { + enter: (path: NodePath) => { + const currentParent = this._currentParent() - if (value) { - const id = this._getNodeId(property); - let targetName: string; - if (key.isIdentifier()) { - targetName = key.node.name; - } else if ( - key.isStringLiteral() || - key.isBooleanLiteral() || - key.isNumericLiteral() || - key.isBigIntLiteral() - ) { - targetName = String(key.node.value); - } + const id = this._getNodeId(path); - if (value.isFunction()) { - this._extractFromFunction( - value, - id, - id, - targetName, - undefined, - true, - false, - objectId - ); - } else if (value.isClass()) { - this._extractFromClass(value, id, id, targetName); - } else if (value.isObjectExpression()) { - this._findOrCreateObject(id, id, id, targetName); - this._extractFromObjectExpression(value, id); - } else if (value.isIdentifier()) { - this._setEqual(id, this._getBindingId(value)); - } else { - // TODO - } - } - } else if (property.isSpreadElement()) { - // TODO - // extract the spread element + const newTarget: ClassTarget = { + id: id, + typeId: id, + exportId: id, + type: TargetType.CLASS } + this._targetGraph.addTarget(newTarget, currentParent) + this._parentStack.push(newTarget) + }, + exit: (path: NodePath) => { + // TODO check if it is me + this._parentStack.pop() } } - private _extractFromClass( - path: NodePath, - classId: string, - typeId: string, - className: string, - export_?: Export | undefined - ): void { - const target: ClassTarget = { - id: classId, - typeId: typeId, - name: className, - type: TargetType.CLASS, - exported: !!export_, - default: export_ ? export_.default : false, - module: export_ ? export_.module : false, - }; - - this._subTargets.push(target); - - const body = >path.get("body"); - for (const classBodyAttribute of body.get("body")) { - if (classBodyAttribute.isClassMethod()) { - if (classBodyAttribute.node.key.type !== "Identifier") { - // e.g. class A { ?() {} } - // unsupported - // not possible i think - this._logOrFail( - unsupportedSyntax( - classBodyAttribute.node.type, - this._getNodeId(classBodyAttribute) - ) - ); - continue; - } + public ObjectExpression = { + enter: (path: NodePath) => { + const currentParent = this._currentParent() - const targetName = classBodyAttribute.node.key.name; - - const id = this._getNodeId(classBodyAttribute); - - this._extractFromFunction( - classBodyAttribute, - id, - id, - targetName, - undefined, - false, - true, - classId - ); - } else if (classBodyAttribute.isClassProperty()) { - const key = classBodyAttribute.get("key"); - const value = classBodyAttribute.get("value"); - - if (value) { - const id = this._getNodeId(classBodyAttribute); - let targetName: string; - if (key.isIdentifier()) { - targetName = key.node.name; - } else if ( - key.isStringLiteral() || - key.isBooleanLiteral() || - key.isNumericLiteral() || - key.isBigIntLiteral() - ) { - targetName = String(key.node.value); - } + const id = this._getNodeId(path); - if (value.isFunction()) { - this._extractFromFunction( - value, - id, - id, - targetName, - undefined, - false, - true, - classId - ); - } else if (value.isClass()) { - this._extractFromClass(value, id, id, targetName); - } else if (value.isObjectExpression()) { - this._findOrCreateObject(id, typeId, id, targetName); - this._extractFromObjectExpression(value, id); - } else if (value.isIdentifier()) { - this._setEqual(id, this._getBindingId(value)); - } else { - // TODO - } - } - } else { - return this._logOrFail( - unsupportedSyntax(body.node.type, this._getNodeId(classBodyAttribute)) - ); + const newTarget: ObjectTarget = { + id: id, + typeId: id, + exportId: id, + type: TargetType.OBJECT } + this._targetGraph.addTarget(newTarget, currentParent) + this._parentStack.push(newTarget) + }, + exit: (_path: NodePath) => { + // TODO check if it is me + this._parentStack.pop() } } - 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); - } - } + public ObjectPattern = { + enter: (path: NodePath) => { + const currentParent = this._currentParent() - private _equalize(): SubTarget[] { - const subTargets = [...this._subTargets]; - const originalTargets = [...subTargets]; + const id = this._getNodeId(path); - const processedSets = new Set>(); - console.log(this._equalObjects); - for (const set of this._equalObjects.values()) { - if (processedSets.has(set)) { - continue; + const newTarget: ObjectTarget = { + id: id, + typeId: id, + exportId: id, + type: TargetType.OBJECT } - 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) - ); - - if (subTargetA.length === 0 || subTargetB.length === 0) { - continue; - } + this._targetGraph.addTarget(newTarget, currentParent) + this._parentStack.push(newTarget) + }, + exit: (_path: NodePath) => { + // TODO check if it is me + this._parentStack.pop() + } + } - // if (subTargetA.length !== 1) { - // console.log(subTargetA); - // throw new Error( - // `Should always be 1 but is ${subTargetA.length} ${a}` - // ); - // } + public AssignmentExpression = { + enter: (path: NodePath) => { + const currentParent = this._currentParent() - // if (subTargetB.length !== 1) { - // console.log(subTargetB); - // throw new Error( - // `Should always be 1 but is ${subTargetB.length} ${b}` - // ); - // } + const id = this._getNodeId(path); - 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}` - ); - } - } + const newTarget: ObjectTarget = { + id: id, + typeId: id, + exportId: id, + type: TargetType.OBJECT } + this._targetGraph.addTarget(newTarget, currentParent) + this._parentStack.push(newTarget) + }, + exit: (path: NodePath) => { + // TODO check if it is me + this._parentStack.pop() } - - 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); - } - } + // public AssignmentExpression: ( + // path: NodePath + // ) => void = (path) => { + // const left = path.get("left"); + // const right = path.get("right"); - 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 - ) - ); + // const targetName = this._getTargetNameOfExpression(right); + // if (!targetName) { + // return; + // } + // let isObject = false; + // let isMethod = false; + // let superId: string; + + // let id: string; + + // if (left.isIdentifier()) { + // // x = ? + // id = this._getBindingId(left); + // } else { + // // ? = ? + // id = this._getBindingId(right); + // } - 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); - } - } + // 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 (!property.isIdentifier() && !left.node.computed) { + // // we also dont support a.f() = ? + // // or equivalent + // path.skip(); + // this._logOrFail(unsupportedSyntax(left.type, this._getNodeId(path))); + // return; + // } + + // if (object.isIdentifier()) { + // // x.? = ? + // // x['?'] = ? + // if ( + // object.node.name === "exports" || + // (object.node.name === "module" && + // property.isIdentifier() && + // property.node.name === "exports") + // ) { + // // exports.? = ? + // // module.exports = ? + // isObject = false; + // id = this._getBindingId(right); + // } else if ( + // (property.isIdentifier() && property.node.name === "prototype") || + // (property.isStringLiteral() && property.node.value === "prototype") + // ) { + // // x.prototype = ? + // // x['prototype'] = ? + // isObject = true; + // superId = this._getBindingId(object); + // const typeId = this._getBindingId(right); + + // this._findAndReplaceOrCreateClass( + // superId, + // typeId, + // superId, + // object.node.name + // ); + // isMethod = true; + + // // // 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()) { + // // ?.?.? = ? + // const subObject = object.get("object"); + // const subProperty = object.get("property"); + // // what about module.exports.x + // if ( + // subObject.isIdentifier() && + // ((subProperty.isIdentifier() && + // subProperty.node.name === "prototype") || + // (subProperty.isStringLiteral() && + // subProperty.node.value === "prototype")) + // ) { + // // x.prototype.? = ? + // // x['prototype'].? = ? + // superId = this._getBindingId(subObject); + + // this._findAndReplaceOrCreateClass( + // superId, + // superId, + // superId, + // subObject.node.name + // ); + // isMethod = true; + // } + // } else { + // path.skip(); + // return; + // } + // } - get subTargets(): SubTarget[] { - // for equal objects: - const targets = this._equalize(); + // const typeId = this._getNodeId(right); + // const export_ = this._getExport(isObject || isMethod ? superId : id); + + // if (right.isFunction()) { + // this._extractFromFunction( + // right, + // id, + // typeId, + // targetName, + // export_, + // isObject, + // isMethod, + // superId + // ); + // } else if (right.isClass()) { + // this._extractFromClass(right, id, typeId, targetName, export_); + // } else if (right.isObjectExpression()) { + // this._findOrCreateObject(id, typeId, isObject ? superId : id, targetName); + // this._extractFromObjectExpression(right, id); + // } else if (right.isIdentifier()) { + // this._setEqual(id, this._getBindingId(right)); + // } else { + // // TODO + // } - return ( - targets - .reverse() - // .filter((subTarget, index, self) => { - // if (!("name" in subTarget)) { - // // paths/branches/lines are always unique - // return true; - // } + // path.skip(); + // }; - // // 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 558e8e425..605d74d6d 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 -g 'Prototyped TargetVisitor test'", + "test": "mocha --config ../../.mocharc.json -g 'TargetVisitor'", "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/TargetVisitor.test.ts b/libraries/analysis-javascript/test/target/TargetVisitor.test.ts index 5f1d52a41..e132112a6 100644 --- a/libraries/analysis-javascript/test/target/TargetVisitor.test.ts +++ b/libraries/analysis-javascript/test/target/TargetVisitor.test.ts @@ -24,10 +24,8 @@ import { ExportVisitor } from "../../lib/target/export/ExportVisitor"; import { ClassTarget, FunctionTarget, - MethodTarget, - ObjectFunctionTarget, ObjectTarget, - SubTarget, + Target, } from "../../lib/target/Target"; import { TargetVisitor } from "../../lib/target/TargetVisitor"; @@ -37,83 +35,35 @@ 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); + const visitor = new TargetVisitor("", true); traverse(ast, visitor); - return visitor.subTargets; + return visitor._getTargetGraph(); } function checkFunction( - target: SubTarget, - name: string, - exported: boolean, - isAsync: boolean + target: Target, + isAsync: boolean, + methodType: string, + visibility: string, + isStatic: 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); + expect(functionTarget.methodType).to.equal(methodType); + expect(functionTarget.visibility).to.equal(visibility); + expect(functionTarget.isStatic).to.equal(isStatic); } -function checkObject(target: SubTarget, name: string, exported: boolean): void { +function checkObject(target: Target): 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 { +function checkClass(target: Target): 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("TargetVisitor test", () => { @@ -125,13 +75,14 @@ describe("TargetVisitor test", () => { export { name1 } `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(3); - checkFunction(targets[0], "name1", true, false); - checkFunction(targets[1], "name2", false, true); - checkFunction(targets[2], "name3", false, true); + checkFunction(targets[0], false, 'method', 'public', true); + checkFunction(targets[1], true, 'method', 'public', true); + checkFunction(targets[2], true, 'method', 'public', true); }); it("FunctionExpression: functions overwritten", () => { @@ -141,7 +92,8 @@ describe("TargetVisitor test", () => { export { name1 } `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(1); checkFunction(targets[0], "name1", true, true); @@ -159,7 +111,8 @@ describe("TargetVisitor test", () => { export { name1 } `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(1); @@ -173,7 +126,8 @@ describe("TargetVisitor test", () => { export { name1 } `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(2); @@ -187,7 +141,8 @@ describe("TargetVisitor test", () => { export { x } `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(1); @@ -200,7 +155,8 @@ describe("TargetVisitor test", () => { export { name1 } `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(1); @@ -227,7 +183,8 @@ describe("TargetVisitor test", () => { } `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(8); @@ -302,7 +259,8 @@ describe("TargetVisitor test", () => { const x = () => {} `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(1); @@ -316,7 +274,8 @@ describe("TargetVisitor test", () => { } `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(2); @@ -339,7 +298,8 @@ describe("TargetVisitor test", () => { } `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(2); @@ -362,7 +322,8 @@ describe("TargetVisitor test", () => { } `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(2); @@ -387,7 +348,8 @@ describe("TargetVisitor test", () => { } `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(3); @@ -413,7 +375,8 @@ describe("TargetVisitor test", () => { } `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(3); @@ -440,7 +403,8 @@ describe("TargetVisitor test", () => { exports = obj `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(3); @@ -462,7 +426,8 @@ describe("TargetVisitor test", () => { const x = {} x[y] = function name1() {} `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(1); }); @@ -473,7 +438,8 @@ describe("TargetVisitor test", () => { x.y = function name1() {} `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(2); @@ -488,7 +454,8 @@ describe("TargetVisitor test", () => { x['z'] = async () => {} `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(3); @@ -504,7 +471,8 @@ describe("TargetVisitor test", () => { export { x } `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(2); @@ -519,7 +487,8 @@ describe("TargetVisitor test", () => { module.exports = x `; - const targets = targetHelper(source); + const targetGraph = targetHelper(source); + const targets = [...targetGraph.targetMap.values()] expect(targets.length).to.equal(2);