fix: update rts logic to use updated shared AST logic (#16849)

* update rts logic to use updated shared AST logic

* Make changes to naming conventions

* Add test cases for RTS and rename ast functions

* Add running jest test to RTS workflow

* Install dependencies and then trigger jest tests in workflow

* Close server connection after test ends

* Remove logs

* Improve jest test descriptions
This commit is contained in:
Ayangade Adeoluwa 2022-09-28 18:28:18 +01:00 committed by GitHub
parent 9d32752abb
commit 610509506e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 2541 additions and 182 deletions

View File

@ -119,6 +119,16 @@ jobs:
echo ::set-output name=version::$next_version-SNAPSHOT echo ::set-output name=version::$next_version-SNAPSHOT
echo ::set-output name=tag::$(echo ${GITHUB_REF:11}) echo ::set-output name=tag::$(echo ${GITHUB_REF:11})
# Install all the dependencies
- name: Install dependencies
if: steps.run_result.outputs.run_result != 'success'
run: yarn install --frozen-lockfile
# Run the Jest tests only if the workflow has been invoked in a PR
- name: Run the jest tests
if: steps.run_result.outputs.run_result != 'success'
run: yarn run test:unit
- name: Build - name: Build
if: steps.run_result.outputs.run_result != 'success' if: steps.run_result.outputs.run_result != 'success'
run: | run: |

View File

@ -7,7 +7,7 @@ import {
getDynamicBindings, getDynamicBindings,
extraLibrariesNames, extraLibrariesNames,
} from "utils/DynamicBindingUtils"; } from "utils/DynamicBindingUtils";
import { extractInfoFromCode } from "@shared/ast"; import { extractIdentifierInfoFromCode } from "@shared/ast";
import { convertPathToString, isWidget } from "../evaluationUtils"; import { convertPathToString, isWidget } from "../evaluationUtils";
import { DataTreeWidget } from "entities/DataTree/dataTreeFactory"; import { DataTreeWidget } from "entities/DataTree/dataTreeFactory";
import { import {
@ -31,7 +31,7 @@ export const extractInfoFromBinding = (
script: string, script: string,
allPaths: Record<string, true>, allPaths: Record<string, true>,
): { validReferences: string[]; invalidReferences: string[] } => { ): { validReferences: string[]; invalidReferences: string[] } => {
const { references } = extractInfoFromCode( const { references } = extractIdentifierInfoFromCode(
script, script,
self.evaluationVersion, self.evaluationVersion,
invalidEntityIdentifiers, invalidEntityIdentifiers,

23
app/rts/jest.config.js Normal file
View File

@ -0,0 +1,23 @@
module.exports = {
roots: ["<rootDir>/src"],
transform: {
"^.+\\.(png|js|ts|tsx)$": "ts-jest",
},
testTimeout: 9000,
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(tsx|ts|js)?$",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node", "css"],
moduleDirectories: ["node_modules", "src", "test"],
moduleNameMapper: {
"@constants/(.*)": ["<rootDir>/src/constants/$1"],
"@services/(.*)": ["<rootDir>/src/services/$1"],
"@middlewares/(.*)": ["<rootDir>/src/middlewares/$1"],
"@controllers/(.*)": ["<rootDir>/src/controllers/$1"],
"@rules/(.*)": ["<rootDir>/src/middlewares/rules/$1"],
"@utils/(.*)": ["<rootDir>/src/utils/$1"],
},
globals: {
"ts-jest": {
isolatedModules: true,
},
},
};

View File

@ -12,17 +12,22 @@
}, },
"devDependencies": { "devDependencies": {
"@types/express": "^4.17.11", "@types/express": "^4.17.11",
"@types/jest": "^29.0.3",
"@types/mongodb": "^3.6.10", "@types/mongodb": "^3.6.10",
"axios": "^0.21.2", "axios": "^0.21.2",
"express": "^4.17.1", "express": "^4.17.1",
"jest": "^29.0.3",
"loglevel": "^1.7.1", "loglevel": "^1.7.1",
"mongodb": "^3.6.4", "mongodb": "^3.6.4",
"socket.io": "^4.5.1", "socket.io": "^4.5.1",
"socket.io-adapter": "^2.3.2", "socket.io-adapter": "^2.3.2",
"source-map-support": "^0.5.19", "source-map-support": "^0.5.19",
"ts-jest": "^29.0.2",
"typescript": "^4.2.3" "typescript": "^4.2.3"
}, },
"scripts": { "scripts": {
"test:unit": "export APPSMITH_API_BASE_URL=http APPSMITH_MONGODB_URI=mongodb && $(npm bin)/jest -b --colors --no-cache --silent --coverage --collectCoverage=true --coverageDirectory='../../' --coverageReporters='json-summary'",
"test:jest": "export APPSMITH_API_BASE_URL=http APPSMITH_MONGODB_URI=mongodb && $(npm bin)/jest --watch ",
"preinstall": "CURRENT_SCOPE=rts node ../shared/build-shared-dep.js", "preinstall": "CURRENT_SCOPE=rts node ../shared/build-shared-dep.js",
"build": "./build.sh", "build": "./build.sh",
"postinstall": "CURRENT_SCOPE=rts node ../shared/install-dependencies.js", "postinstall": "CURRENT_SCOPE=rts node ../shared/install-dependencies.js",
@ -31,6 +36,7 @@
"dependencies": { "dependencies": {
"express-validator": "^6.14.2", "express-validator": "^6.14.2",
"http-status-codes": "^2.2.0", "http-status-codes": "^2.2.0",
"supertest": "^6.2.4",
"tsc-alias": "^1.7.0" "tsc-alias": "^1.7.0"
} }
} }

View File

@ -18,11 +18,11 @@ export default class AstController extends BaseController {
super(); super();
} }
async getDependentIdentifiers(req: Request, res: Response) { async getIdentifierDataFromScript(req: Request, res: Response) {
try { try {
// By default the application eval version is set to be 2 // By default the application eval version is set to be 2
const { script, evalVersion = 2 }: ScriptToIdentifiersType = req.body; const { script, evalVersion = 2 }: ScriptToIdentifiersType = req.body;
const data = await AstService.getIdentifiersFromScript( const data = await AstService.extractIdentifierDataFromScript(
script, script,
evalVersion evalVersion
); );
@ -37,7 +37,7 @@ export default class AstController extends BaseController {
} }
} }
async getMultipleDependentIdentifiers(req: Request, res: Response) { async getIdentifierDataFromMultipleScripts(req: Request, res: Response) {
try { try {
// By default the application eval version is set to be 2 // By default the application eval version is set to be 2
const { scripts, evalVersion = 2 }: MultipleScriptToIdentifiersType = const { scripts, evalVersion = 2 }: MultipleScriptToIdentifiersType =
@ -46,7 +46,10 @@ export default class AstController extends BaseController {
Promise.all( Promise.all(
scripts.map( scripts.map(
async (script) => async (script) =>
await AstService.getIdentifiersFromScript(script, evalVersion) await AstService.extractIdentifierDataFromScript(
script,
evalVersion
)
) )
).then((data) => { ).then((data) => {
return super.sendResponse(res, data); return super.sendResponse(res, data);

View File

@ -1,6 +1,7 @@
import { Response } from "express"; import { Response } from "express";
import { ValidationError } from "express-validator"; import { ValidationError } from "express-validator";
import { StatusCodes } from "http-status-codes"; import { StatusCodes } from "http-status-codes";
import { IdentifierInfo } from "@shared/ast";
type ErrorData = { type ErrorData = {
error: string | string[]; error: string | string[];
@ -16,7 +17,7 @@ type ErrorBag = {
type ResponseData = { type ResponseData = {
success: boolean; success: boolean;
message?: string; message?: string;
data: unknown; //setting unknown for now, to be modified later. data: IdentifierInfo;
}; };
export default class BaseController { export default class BaseController {

View File

@ -8,17 +8,17 @@ const astController = new AstController();
const validator = new Validator(); const validator = new Validator();
router.post( router.post(
"/single-script-identifiers", "/single-script-data",
AstRules.getScriptValidator(), AstRules.getScriptValidator(),
validator.validateRequest, validator.validateRequest,
astController.getDependentIdentifiers astController.getIdentifierDataFromScript
); );
router.post( router.post(
"/multiple-script-identifiers", "/multiple-script-data",
AstRules.getMultipleScriptValidator(), AstRules.getMultipleScriptValidator(),
validator.validateRequest, validator.validateRequest,
astController.getMultipleDependentIdentifiers astController.getIdentifierDataFromMultipleScripts
); );
export default router; export default router;

View File

@ -10,7 +10,7 @@ import { initializeSockets } from "./sockets";
import ast_routes from "./routes/ast_routes"; import ast_routes from "./routes/ast_routes";
const RTS_BASE_PATH = "/rts"; const RTS_BASE_PATH = "/rts";
const RTS_BASE_API_PATH = "/rts-api/v1"; export const RTS_BASE_API_PATH = "/rts-api/v1";
// Setting the logLevel for all log messages // Setting the logLevel for all log messages
const logLevel: LogLevelDesc = (process.env.APPSMITH_LOG_LEVEL || const logLevel: LogLevelDesc = (process.env.APPSMITH_LOG_LEVEL ||
@ -36,32 +36,30 @@ if (API_BASE_URL == null || API_BASE_URL === "") {
const PORT = process.env.PORT || 8091; const PORT = process.env.PORT || 8091;
main(); //Disable x-powered-by header to prevent information disclosure
const app = express();
function main() { app.disable("x-powered-by");
const app = express(); const server = new http.Server(app);
//Disable x-powered-by header to prevent information disclosure const io = new Server(server, {
app.disable("x-powered-by");
const server = new http.Server(app);
const io = new Server(server, {
path: RTS_BASE_PATH, path: RTS_BASE_PATH,
}); });
// Initializing Sockets // Initializing Sockets
initializeSockets(io); initializeSockets(io);
// parse incoming json requests // parse incoming json requests
app.use(express.json({ limit: "5mb" })); app.use(express.json({ limit: "5mb" }));
// Initializing Routes // Initializing Routes
app.use(express.static(path.join(__dirname, "static"))); app.use(express.static(path.join(__dirname, "static")));
app.get("/", (_, res) => { app.get("/", (_, res) => {
res.redirect("/index.html"); res.redirect("/index.html");
}); });
app.use(`${RTS_BASE_API_PATH}/ast`, ast_routes); app.use(`${RTS_BASE_API_PATH}/ast`, ast_routes);
// Run the server // Run the server
server.listen(PORT, () => { server.listen(PORT, () => {
log.info(`RTS version ${buildVersion} running at http://localhost:${PORT}`); log.info(`RTS version ${buildVersion} running at http://localhost:${PORT}`);
}); });
}
export default server;

View File

@ -1,18 +1,20 @@
import { extractInfoFromCode } from "@shared/ast"; import { extractIdentifierInfoFromCode } from "@shared/ast";
export default class AstService { export default class AstService {
static async getIdentifiersFromScript( static async extractIdentifierDataFromScript(
script, script,
evalVersion evalVersion,
invalidIdentifiers = {}
): Promise<any> { ): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
const extractions = extractInfoFromCode( const identifierInfo = extractIdentifierInfoFromCode(
script, script,
evalVersion evalVersion,
invalidIdentifiers
); );
resolve(extractions); resolve(identifierInfo);
} catch (err) { } catch (err) {
reject(err); reject(err);
} }

View File

@ -0,0 +1,67 @@
import app, { RTS_BASE_API_PATH } from "../server";
import supertest from "supertest";
const singleScript = {
script:
"(function abc() { let Api2 = { }; return Api2.data ? str.data + Api1.data : [] })()",
};
const multipleScripts = {
scripts: [
"(function abc() { return Api1.data })() ",
"(function abc() { let str = ''; return str ? Api1.data : [] })()",
],
};
afterAll((done) => {
app.close();
done();
});
describe("AST tests", () => {
it("Checks to see if single script is parsed correctly using the API", async () => {
const expectedResponse = {
references: ["str.data", "Api1.data"],
functionalParams: [],
variables: ["Api2"],
};
await supertest(app)
.post(`${RTS_BASE_API_PATH}/ast/single-script-data`, {
JSON: true,
})
.send(singleScript)
.expect(200)
.then((response) => {
expect(response.body.success).toEqual(true);
expect(response.body.data).toEqual(expectedResponse);
});
});
it("Checks to see if multiple scripts are parsed correctly using the API", async () => {
const expectedResponse = [
{
references: ["Api1.data"],
functionalParams: [],
variables: [],
},
{
references: ["Api1.data"],
functionalParams: [],
variables: ["str"],
},
];
await supertest(app)
.post(`${RTS_BASE_API_PATH}/ast/multiple-script-data`, {
JSON: true,
})
.send(multipleScripts)
.expect(200)
.then((response) => {
expect(response.body.success).toEqual(true);
expect(response.body.data.length).toBeGreaterThan(1);
expect(response.body.data).toEqual(expectedResponse);
});
});
});

View File

@ -14,7 +14,7 @@
"@middlewares/*": ["./src/middlewares/*"], "@middlewares/*": ["./src/middlewares/*"],
"@controllers/*": ["./src/controllers/*"], "@controllers/*": ["./src/controllers/*"],
"@rules/*": ["./src/middlewares/rules/*"], "@rules/*": ["./src/middlewares/rules/*"],
"@utils/*": ["./src/utils/*"], "@utils/*": ["./src/utils/*"]
} }
}, },
"lib": ["es2015"] "lib": ["es2015"]

File diff suppressed because it is too large Load Diff

View File

@ -8,21 +8,27 @@ import {
isPropertyNode, isPropertyNode,
isPropertyAFunctionNode, isPropertyAFunctionNode,
getAST, getAST,
extractInfoFromCode, extractIdentifierInfoFromCode,
extractInvalidTopLevelMemberExpressionsFromCode, extractInvalidTopLevelMemberExpressionsFromCode,
getFunctionalParamsFromNode, getFunctionalParamsFromNode,
isTypeOfFunction, isTypeOfFunction,
MemberExpressionData, MemberExpressionData,
} from './src'; IdentifierInfo,
} from "./src";
// constants // constants
import { ECMA_VERSION, SourceType, NodeTypes } from './src/constants'; import { ECMA_VERSION, SourceType, NodeTypes } from "./src/constants";
// JSObjects // JSObjects
import { parseJSObjectWithAST } from './src/jsObject'; import { parseJSObjectWithAST } from "./src/jsObject";
// types or intefaces should be exported with type keyword, while enums can be exported like normal functions // types or intefaces should be exported with type keyword, while enums can be exported like normal functions
export type { ObjectExpression, PropertyNode, MemberExpressionData }; export type {
ObjectExpression,
PropertyNode,
MemberExpressionData,
IdentifierInfo,
};
export { export {
isIdentifierNode, isIdentifierNode,
@ -32,7 +38,7 @@ export {
isPropertyNode, isPropertyNode,
isPropertyAFunctionNode, isPropertyAFunctionNode,
getAST, getAST,
extractInfoFromCode, extractIdentifierInfoFromCode,
extractInvalidTopLevelMemberExpressionsFromCode, extractInvalidTopLevelMemberExpressionsFromCode,
getFunctionalParamsFromNode, getFunctionalParamsFromNode,
isTypeOfFunction, isTypeOfFunction,

View File

@ -1,85 +1,85 @@
import { extractInfoFromCode } from '../src/index'; import { extractIdentifierInfoFromCode } from "../src/index";
import { parseJSObjectWithAST } from '../src/jsObject'; import { parseJSObjectWithAST } from "../src/jsObject";
describe('getAllIdentifiers', () => { describe("getAllIdentifiers", () => {
it('works properly', () => { it("works properly", () => {
const cases: { script: string; expectedResults: string[] }[] = [ const cases: { script: string; expectedResults: string[] }[] = [
{ {
// Entity reference // Entity reference
script: 'DirectTableReference', script: "DirectTableReference",
expectedResults: ['DirectTableReference'], expectedResults: ["DirectTableReference"],
}, },
{ {
// One level nesting // One level nesting
script: 'TableDataReference.data', script: "TableDataReference.data",
expectedResults: ['TableDataReference.data'], expectedResults: ["TableDataReference.data"],
}, },
{ {
// Deep nesting // Deep nesting
script: 'TableDataDetailsReference.data.details', script: "TableDataDetailsReference.data.details",
expectedResults: ['TableDataDetailsReference.data.details'], expectedResults: ["TableDataDetailsReference.data.details"],
}, },
{ {
// Deep nesting // Deep nesting
script: 'TableDataDetailsMoreReference.data.details.more', script: "TableDataDetailsMoreReference.data.details.more",
expectedResults: ['TableDataDetailsMoreReference.data.details.more'], expectedResults: ["TableDataDetailsMoreReference.data.details.more"],
}, },
{ {
// Deep optional chaining // Deep optional chaining
script: 'TableDataOptionalReference.data?.details.more', script: "TableDataOptionalReference.data?.details.more",
expectedResults: ['TableDataOptionalReference.data'], expectedResults: ["TableDataOptionalReference.data"],
}, },
{ {
// Deep optional chaining with logical operator // Deep optional chaining with logical operator
script: script:
'TableDataOptionalWithLogical.data?.details.more || FallbackTableData.data', "TableDataOptionalWithLogical.data?.details.more || FallbackTableData.data",
expectedResults: [ expectedResults: [
'TableDataOptionalWithLogical.data', "TableDataOptionalWithLogical.data",
'FallbackTableData.data', "FallbackTableData.data",
], ],
}, },
{ {
// null coalescing // null coalescing
script: 'TableDataOptionalWithLogical.data ?? FallbackTableData.data', script: "TableDataOptionalWithLogical.data ?? FallbackTableData.data",
expectedResults: [ expectedResults: [
'TableDataOptionalWithLogical.data', "TableDataOptionalWithLogical.data",
'FallbackTableData.data', "FallbackTableData.data",
], ],
}, },
{ {
// Basic map function // Basic map function
script: 'Table5.data.map(c => ({ name: c.name }))', script: "Table5.data.map(c => ({ name: c.name }))",
expectedResults: ['Table5.data.map'], expectedResults: ["Table5.data.map"],
}, },
{ {
// Literal property search // Literal property search
script: "Table6['data']", script: "Table6['data']",
expectedResults: ['Table6'], expectedResults: ["Table6"],
}, },
{ {
// Deep literal property search // Deep literal property search
script: "TableDataOptionalReference['data'].details", script: "TableDataOptionalReference['data'].details",
expectedResults: ['TableDataOptionalReference'], expectedResults: ["TableDataOptionalReference"],
}, },
{ {
// Array index search // Array index search
script: 'array[8]', script: "array[8]",
expectedResults: ['array[8]'], expectedResults: ["array[8]"],
}, },
{ {
// Deep array index search // Deep array index search
script: 'Table7.data[4]', script: "Table7.data[4]",
expectedResults: ['Table7.data[4]'], expectedResults: ["Table7.data[4]"],
}, },
{ {
// Deep array index search // Deep array index search
script: 'Table7.data[4].value', script: "Table7.data[4].value",
expectedResults: ['Table7.data[4].value'], expectedResults: ["Table7.data[4].value"],
}, },
{ {
// string literal and array index search // string literal and array index search
script: "Table['data'][9]", script: "Table['data'][9]",
expectedResults: ['Table'], expectedResults: ["Table"],
}, },
{ {
// array index and string literal search // array index and string literal search
@ -88,29 +88,29 @@ describe('getAllIdentifiers', () => {
}, },
{ {
// Index identifier search // Index identifier search
script: 'Table8.data[row][name]', script: "Table8.data[row][name]",
expectedResults: ['Table8.data', 'row'], expectedResults: ["Table8.data", "row"],
}, },
{ {
// Index identifier search with global // Index identifier search with global
script: 'Table9.data[appsmith.store.row]', script: "Table9.data[appsmith.store.row]",
expectedResults: ['Table9.data', 'appsmith.store.row'], expectedResults: ["Table9.data", "appsmith.store.row"],
}, },
{ {
// Index literal with further nested lookups // Index literal with further nested lookups
script: 'Table10.data[row].name', script: "Table10.data[row].name",
expectedResults: ['Table10.data', 'row'], expectedResults: ["Table10.data", "row"],
}, },
{ {
// IIFE and if conditions // IIFE and if conditions
script: script:
'(function(){ if(Table11.isVisible) { return Api1.data } else { return Api2.data } })()', "(function(){ if(Table11.isVisible) { return Api1.data } else { return Api2.data } })()",
expectedResults: ['Table11.isVisible', 'Api1.data', 'Api2.data'], expectedResults: ["Table11.isVisible", "Api1.data", "Api2.data"],
}, },
{ {
// Functions and arguments // Functions and arguments
script: 'JSObject1.run(Api1.data, Api2.data)', script: "JSObject1.run(Api1.data, Api2.data)",
expectedResults: ['JSObject1.run', 'Api1.data', 'Api2.data'], expectedResults: ["JSObject1.run", "Api1.data", "Api2.data"],
}, },
{ {
// IIFE - without braces // IIFE - without braces
@ -124,7 +124,7 @@ describe('getAllIdentifiers', () => {
return obj[index] return obj[index]
}()`, }()`,
expectedResults: ['Input1.text'], expectedResults: ["Input1.text"],
}, },
{ {
// IIFE // IIFE
@ -138,7 +138,7 @@ describe('getAllIdentifiers', () => {
return obj[index] return obj[index]
})()`, })()`,
expectedResults: ['Input2.text'], expectedResults: ["Input2.text"],
}, },
{ {
// arrow IIFE - without braces - will fail // arrow IIFE - without braces - will fail
@ -166,19 +166,19 @@ describe('getAllIdentifiers', () => {
return obj[index] return obj[index]
})()`, })()`,
expectedResults: ['Input4.text'], expectedResults: ["Input4.text"],
}, },
{ {
// Direct object access // Direct object access
script: `{ "a": 123 }[Input5.text]`, script: `{ "a": 123 }[Input5.text]`,
expectedResults: ['Input5.text'], expectedResults: ["Input5.text"],
}, },
{ {
// Function declaration and default arguments // Function declaration and default arguments
script: `function run(apiData = Api1.data) { script: `function run(apiData = Api1.data) {
return apiData; return apiData;
}`, }`,
expectedResults: ['Api1.data'], expectedResults: ["Api1.data"],
}, },
{ {
// Function declaration with arguments // Function declaration with arguments
@ -197,7 +197,7 @@ describe('getAllIdentifiers', () => {
row = row += 1; row = row += 1;
} }
}`, }`,
expectedResults: ['Table12.data'], expectedResults: ["Table12.data"],
}, },
{ {
// function with variables // function with variables
@ -209,17 +209,17 @@ describe('getAllIdentifiers', () => {
row = row += 1; row = row += 1;
} }
}`, }`,
expectedResults: ['Table13.data'], expectedResults: ["Table13.data"],
}, },
{ {
// expression with arithmetic operations // expression with arithmetic operations
script: `Table14.data + 15`, script: `Table14.data + 15`,
expectedResults: ['Table14.data'], expectedResults: ["Table14.data"],
}, },
{ {
// expression with logical operations // expression with logical operations
script: `Table15.data || [{}]`, script: `Table15.data || [{}]`,
expectedResults: ['Table15.data'], expectedResults: ["Table15.data"],
}, },
// JavaScript built in classes should not be valid identifiers // JavaScript built in classes should not be valid identifiers
{ {
@ -229,7 +229,7 @@ describe('getAllIdentifiers', () => {
const randomNumber = Math.random(); const randomNumber = Math.random();
return Promise.all([firstApiRun, secondApiRun]) return Promise.all([firstApiRun, secondApiRun])
}()`, }()`,
expectedResults: ['Api1.run', 'Api2.run'], expectedResults: ["Api1.run", "Api2.run"],
}, },
// Global dependencies should not be valid identifiers // Global dependencies should not be valid identifiers
{ {
@ -251,7 +251,7 @@ describe('getAllIdentifiers', () => {
console.log(joinedName) console.log(joinedName)
return Api2.name return Api2.name
}()`, }()`,
expectedResults: ['Api2.name'], expectedResults: ["Api2.name"],
}, },
// identifiers and member expressions derived from params should not be valid identifiers // identifiers and member expressions derived from params should not be valid identifiers
{ {
@ -274,19 +274,19 @@ describe('getAllIdentifiers', () => {
script: `function(){ script: `function(){
return appsmith.user return appsmith.user
}()`, }()`,
expectedResults: ['appsmith.user'], expectedResults: ["appsmith.user"],
}, },
]; ];
cases.forEach((perCase) => { cases.forEach((perCase) => {
const { references } = extractInfoFromCode(perCase.script, 2); const { references } = extractIdentifierInfoFromCode(perCase.script, 2);
expect(references).toStrictEqual(perCase.expectedResults); expect(references).toStrictEqual(perCase.expectedResults);
}); });
}); });
}); });
describe('parseJSObjectWithAST', () => { describe("parseJSObjectWithAST", () => {
it('parse js object', () => { it("parse js object", () => {
const body = `{ const body = `{
myVar1: [], myVar1: [],
myVar2: {}, myVar2: {},
@ -299,25 +299,25 @@ describe('parseJSObjectWithAST', () => {
}`; }`;
const parsedObject = [ const parsedObject = [
{ {
key: 'myVar1', key: "myVar1",
value: '[]', value: "[]",
type: 'ArrayExpression', type: "ArrayExpression",
}, },
{ {
key: 'myVar2', key: "myVar2",
value: '{}', value: "{}",
type: 'ObjectExpression', type: "ObjectExpression",
}, },
{ {
key: 'myFun1', key: "myFun1",
value: '() => {}', value: "() => {}",
type: 'ArrowFunctionExpression', type: "ArrowFunctionExpression",
arguments: [], arguments: [],
}, },
{ {
key: 'myFun2', key: "myFun2",
value: 'async () => {}', value: "async () => {}",
type: 'ArrowFunctionExpression', type: "ArrowFunctionExpression",
arguments: [], arguments: [],
}, },
]; ];
@ -325,7 +325,7 @@ describe('parseJSObjectWithAST', () => {
expect(resultParsedObject).toStrictEqual(parsedObject); expect(resultParsedObject).toStrictEqual(parsedObject);
}); });
it('parse js object with literal', () => { it("parse js object with literal", () => {
const body = `{ const body = `{
myVar1: [], myVar1: [],
myVar2: { myVar2: {
@ -340,25 +340,25 @@ describe('parseJSObjectWithAST', () => {
}`; }`;
const parsedObject = [ const parsedObject = [
{ {
key: 'myVar1', key: "myVar1",
value: '[]', value: "[]",
type: 'ArrayExpression', type: "ArrayExpression",
}, },
{ {
key: 'myVar2', key: "myVar2",
value: '{\n "a": "app"\n}', value: '{\n "a": "app"\n}',
type: 'ObjectExpression', type: "ObjectExpression",
}, },
{ {
key: 'myFun1', key: "myFun1",
value: '() => {}', value: "() => {}",
type: 'ArrowFunctionExpression', type: "ArrowFunctionExpression",
arguments: [], arguments: [],
}, },
{ {
key: 'myFun2', key: "myFun2",
value: 'async () => {}', value: "async () => {}",
type: 'ArrowFunctionExpression', type: "ArrowFunctionExpression",
arguments: [], arguments: [],
}, },
]; ];
@ -366,7 +366,7 @@ describe('parseJSObjectWithAST', () => {
expect(resultParsedObject).toStrictEqual(parsedObject); expect(resultParsedObject).toStrictEqual(parsedObject);
}); });
it('parse js object with variable declaration inside function', () => { it("parse js object with variable declaration inside function", () => {
const body = `{ const body = `{
myFun1: () => { myFun1: () => {
const a = { const a = {
@ -382,7 +382,7 @@ describe('parseJSObjectWithAST', () => {
}`; }`;
const parsedObject = [ const parsedObject = [
{ {
key: 'myFun1', key: "myFun1",
value: `() => { value: `() => {
const a = { const a = {
conditions: [], conditions: [],
@ -391,13 +391,13 @@ describe('parseJSObjectWithAST', () => {
testFunc2: function () {} testFunc2: function () {}
}; };
}`, }`,
type: 'ArrowFunctionExpression', type: "ArrowFunctionExpression",
arguments: [], arguments: [],
}, },
{ {
key: 'myFun2', key: "myFun2",
value: 'async () => {}', value: "async () => {}",
type: 'ArrowFunctionExpression', type: "ArrowFunctionExpression",
arguments: [], arguments: [],
}, },
]; ];
@ -405,7 +405,7 @@ describe('parseJSObjectWithAST', () => {
expect(resultParsedObject).toStrictEqual(parsedObject); expect(resultParsedObject).toStrictEqual(parsedObject);
}); });
it('parse js object with params of all types', () => { it("parse js object with params of all types", () => {
const body = `{ const body = `{
myFun2: async (a,b = Array(1,2,3),c = "", d = [], e = this.myVar1, f = {}, g = function(){}, h = Object.assign({}), i = String(), j = storeValue()) => { myFun2: async (a,b = Array(1,2,3),c = "", d = [], e = this.myVar1, f = {}, g = function(){}, h = Object.assign({}), i = String(), j = storeValue()) => {
//use async-await or promises //use async-await or promises
@ -414,49 +414,49 @@ describe('parseJSObjectWithAST', () => {
const parsedObject = [ const parsedObject = [
{ {
key: 'myFun2', key: "myFun2",
value: value:
'async (a, b = Array(1, 2, 3), c = "", d = [], e = this.myVar1, f = {}, g = function () {}, h = Object.assign({}), i = String(), j = storeValue()) => {}', 'async (a, b = Array(1, 2, 3), c = "", d = [], e = this.myVar1, f = {}, g = function () {}, h = Object.assign({}), i = String(), j = storeValue()) => {}',
type: 'ArrowFunctionExpression', type: "ArrowFunctionExpression",
arguments: [ arguments: [
{ {
paramName: 'a', paramName: "a",
defaultValue: undefined, defaultValue: undefined,
}, },
{ {
paramName: 'b', paramName: "b",
defaultValue: undefined, defaultValue: undefined,
}, },
{ {
paramName: 'c', paramName: "c",
defaultValue: undefined, defaultValue: undefined,
}, },
{ {
paramName: 'd', paramName: "d",
defaultValue: undefined, defaultValue: undefined,
}, },
{ {
paramName: 'e', paramName: "e",
defaultValue: undefined, defaultValue: undefined,
}, },
{ {
paramName: 'f', paramName: "f",
defaultValue: undefined, defaultValue: undefined,
}, },
{ {
paramName: 'g', paramName: "g",
defaultValue: undefined, defaultValue: undefined,
}, },
{ {
paramName: 'h', paramName: "h",
defaultValue: undefined, defaultValue: undefined,
}, },
{ {
paramName: 'i', paramName: "i",
defaultValue: undefined, defaultValue: undefined,
}, },
{ {
paramName: 'j', paramName: "j",
defaultValue: undefined, defaultValue: undefined,
}, },
], ],

View File

@ -1,8 +1,8 @@
import { parse, Node, SourceLocation, Options } from 'acorn'; import { parse, Node, SourceLocation, Options } from "acorn";
import { ancestor, simple } from 'acorn-walk'; import { ancestor, simple } from "acorn-walk";
import { ECMA_VERSION, NodeTypes } from './constants/ast'; import { ECMA_VERSION, NodeTypes } from "./constants/ast";
import { has, isFinite, isString, memoize, toPath } from 'lodash'; import { has, isFinite, isString, memoize, toPath } from "lodash";
import { isTrueObject, sanitizeScript } from './utils'; import { isTrueObject, sanitizeScript } from "./utils";
/* /*
* Valuable links: * Valuable links:
@ -90,7 +90,7 @@ export interface PropertyNode extends Node {
type: NodeTypes.Property; type: NodeTypes.Property;
key: LiteralNode | IdentifierNode; key: LiteralNode | IdentifierNode;
value: Node; value: Node;
kind: 'init' | 'get' | 'set'; kind: "init" | "get" | "set";
} }
// Node with location details // Node with location details
@ -98,7 +98,7 @@ type NodeWithLocation<NodeType> = NodeType & {
loc: SourceLocation; loc: SourceLocation;
}; };
type AstOptions = Omit<Options, 'ecmaVersion'>; type AstOptions = Omit<Options, "ecmaVersion">;
/* We need these functions to typescript casts the nodes with the correct types */ /* We need these functions to typescript casts the nodes with the correct types */
export const isIdentifierNode = (node: Node): node is IdentifierNode => { export const isIdentifierNode = (node: Node): node is IdentifierNode => {
@ -196,23 +196,23 @@ export const getAST = memoize((code: string, options?: AstOptions) =>
* @param code: The piece of script where references need to be extracted from * @param code: The piece of script where references need to be extracted from
*/ */
interface ExtractInfoFromCode { export interface IdentifierInfo {
references: string[]; references: string[];
functionalParams: string[]; functionalParams: string[];
variables: string[]; variables: string[];
} }
export const extractInfoFromCode = ( export const extractIdentifierInfoFromCode = (
code: string, code: string,
evaluationVersion: number, evaluationVersion: number,
invalidIdentifiers?: Record<string, unknown> invalidIdentifiers?: Record<string, unknown>
): ExtractInfoFromCode => { ): IdentifierInfo => {
// List of all references found // List of all references found
const references = new Set<string>(); const references = new Set<string>();
// List of variables declared within the script. All identifiers and member expressions derived from declared variables will be removed // List of variables declared within the script. All identifiers and member expressions derived from declared variables will be removed
const variableDeclarations = new Set<string>(); const variableDeclarations = new Set<string>();
// List of functional params declared within the script. All identifiers and member expressions derived from functional params will be removed // List of functional params declared within the script. All identifiers and member expressions derived from functional params will be removed
let functionalParams = new Set<string>(); let functionalParams = new Set<string>();
let ast: Node = { end: 0, start: 0, type: '' }; let ast: Node = { end: 0, start: 0, type: "" };
try { try {
const sanitizedScript = sanitizeScript(code, evaluationVersion); const sanitizedScript = sanitizeScript(code, evaluationVersion);
/* wrapCode - Wrapping code in a function, since all code/script get wrapped with a function during evaluation. /* wrapCode - Wrapping code in a function, since all code/script get wrapped with a function during evaluation.
@ -377,7 +377,7 @@ export const getFunctionalParamsFromNode = (
const constructFinalMemberExpIdentifier = ( const constructFinalMemberExpIdentifier = (
node: MemberExpressionNode, node: MemberExpressionNode,
child = '' child = ""
): string => { ): string => {
const propertyAccessor = getPropertyAccessor(node.property); const propertyAccessor = getPropertyAccessor(node.property);
if (isIdentifierNode(node.object)) { if (isIdentifierNode(node.object)) {
@ -438,7 +438,7 @@ export const extractInvalidTopLevelMemberExpressionsFromCode = (
const invalidTopLevelMemberExpressions = new Set<MemberExpressionData>(); const invalidTopLevelMemberExpressions = new Set<MemberExpressionData>();
const variableDeclarations = new Set<string>(); const variableDeclarations = new Set<string>();
let functionalParams = new Set<string>(); let functionalParams = new Set<string>();
let ast: Node = { end: 0, start: 0, type: '' }; let ast: Node = { end: 0, start: 0, type: "" };
try { try {
const sanitizedScript = sanitizeScript(code, evaluationVersion); const sanitizedScript = sanitizeScript(code, evaluationVersion);
const wrappedCode = wrapCode(sanitizedScript); const wrappedCode = wrapCode(sanitizedScript);