From 68557a083e973310e92f83927d1d2e96b9a57eb3 Mon Sep 17 00:00:00 2001 From: Frank Weigel Date: Tue, 15 Jun 2021 18:42:25 +0200 Subject: [PATCH] [FIX] Encapsulate JS parsing and switch from esprima to espree A new module lib/lbt/utils/parseJS is introduced that exposes a parseJS function and a Syntax object with all AST node types. The new module is used in all places where JavaSCript code is parsed (incl. tests). The module uses espree v6 for parsing instead of esprima. This is the maximum version that can be used without introducing breaking changes to the tooling (higher versions of espree require higher node versions than documented in our package.json). The set of allowed parser options is limited to ease a later migration to another parser. Fixes https://github.com/SAP/ui5-tooling/issues/354 . --- lib/lbt/analyzer/FioriElementsAnalyzer.js | 5 +- lib/lbt/analyzer/JSModuleAnalyzer.js | 3 +- lib/lbt/analyzer/SmartTemplateAnalyzer.js | 5 +- lib/lbt/analyzer/XMLCompositeAnalyzer.js | 3 +- lib/lbt/analyzer/analyzeLibraryJS.js | 5 +- lib/lbt/bundle/Builder.js | 5 +- lib/lbt/calls/SapUiDefine.js | 2 +- lib/lbt/resources/ResourcePool.js | 4 +- lib/lbt/utils/ASTUtils.js | 2 +- lib/lbt/utils/parseJS.js | 28 +++++++ package-lock.json | 73 +++++++++++++++---- package.json | 2 +- .../lib/lbt/analyzer/FioriElementsAnalyzer.js | 16 ++-- test/lib/lbt/analyzer/JSModuleAnalyzer.js | 6 +- .../lib/lbt/analyzer/SmartTemplateAnalyzer.js | 20 ++--- test/lib/lbt/analyzer/XMLCompositeAnalyzer.js | 14 ++-- test/lib/lbt/calls/SapUiDefine.js | 5 +- test/lib/lbt/utils/ASTUtils.js | 62 +++++++++------- 18 files changed, 167 insertions(+), 93 deletions(-) create mode 100644 lib/lbt/utils/parseJS.js diff --git a/lib/lbt/analyzer/FioriElementsAnalyzer.js b/lib/lbt/analyzer/FioriElementsAnalyzer.js index e4cf9ebb6..bfa8b52d5 100644 --- a/lib/lbt/analyzer/FioriElementsAnalyzer.js +++ b/lib/lbt/analyzer/FioriElementsAnalyzer.js @@ -60,8 +60,7 @@ const ModuleName = require("../utils/ModuleName"); const SapUiDefine = require("../calls/SapUiDefine"); -const esprima = require("esprima"); -const {Syntax} = esprima; +const {parseJS, Syntax} = require("../utils/parseJS"); const {getValue, isMethodCall, isString} = require("../utils/ASTUtils"); const log = require("@ui5/logger").getLogger("lbt:analyzer:FioriElementAnalyzer"); @@ -147,7 +146,7 @@ class FioriElementsAnalyzer { // console.log("analyzing template component %s", moduleName); const resource = await this._pool.findResource(moduleName); const code = await resource.buffer(); - const ast = esprima.parseScript(code.toString()); + const ast = parseJS(code); const defaultTemplateName = this._analyzeAST(moduleName, ast); const templateName = (pageConfig.component && pageConfig.component.settings && pageConfig.component.settings.templateName) || defaultTemplateName; diff --git a/lib/lbt/analyzer/JSModuleAnalyzer.js b/lib/lbt/analyzer/JSModuleAnalyzer.js index e6dc31957..defd87eb4 100644 --- a/lib/lbt/analyzer/JSModuleAnalyzer.js +++ b/lib/lbt/analyzer/JSModuleAnalyzer.js @@ -1,7 +1,6 @@ "use strict"; -const esprima = require("esprima"); -const {Syntax} = esprima; +const {Syntax} = require("../utils/parseJS"); const escope = require("escope"); const ModuleName = require("../utils/ModuleName"); const {Format: ModuleFormat} = require("../resources/ModuleInfo"); diff --git a/lib/lbt/analyzer/SmartTemplateAnalyzer.js b/lib/lbt/analyzer/SmartTemplateAnalyzer.js index 90c7d4a90..edfd64527 100644 --- a/lib/lbt/analyzer/SmartTemplateAnalyzer.js +++ b/lib/lbt/analyzer/SmartTemplateAnalyzer.js @@ -27,8 +27,7 @@ const ModuleName = require("../utils/ModuleName"); const SapUiDefine = require("../calls/SapUiDefine"); -const esprima = require("esprima"); -const {Syntax} = esprima; +const {parseJS, Syntax} = require("../utils/parseJS"); const {getValue, isMethodCall, isString} = require("../utils/ASTUtils"); const log = require("@ui5/logger").getLogger("lbt:analyzer:SmartTemplateAnalyzer"); @@ -109,7 +108,7 @@ class TemplateComponentAnalyzer { try { const resource = await this._pool.findResource(moduleName); const code = await resource.buffer(); - const ast = esprima.parseScript(code.toString()); + const ast = parseJS(code); const defaultTemplateName = this._analyzeAST(moduleName, ast); const templateName = (pageConfig.component && pageConfig.component.settings && pageConfig.component.settings.templateName) || defaultTemplateName; diff --git a/lib/lbt/analyzer/XMLCompositeAnalyzer.js b/lib/lbt/analyzer/XMLCompositeAnalyzer.js index 868da94d6..4fd79d295 100644 --- a/lib/lbt/analyzer/XMLCompositeAnalyzer.js +++ b/lib/lbt/analyzer/XMLCompositeAnalyzer.js @@ -1,7 +1,6 @@ "use strict"; -const esprima = require("esprima"); -const {Syntax} = esprima; +const {Syntax} = require("../utils/parseJS"); const SapUiDefine = require("../calls/SapUiDefine"); const {getValue, isMethodCall, isString} = require("../utils/ASTUtils"); const ModuleName = require("../utils/ModuleName"); diff --git a/lib/lbt/analyzer/analyzeLibraryJS.js b/lib/lbt/analyzer/analyzeLibraryJS.js index 8c0e2ab7d..95c761163 100644 --- a/lib/lbt/analyzer/analyzeLibraryJS.js +++ b/lib/lbt/analyzer/analyzeLibraryJS.js @@ -1,6 +1,5 @@ "use strict"; -const esprima = require("esprima"); -const {Syntax} = esprima; +const {parseJS, Syntax} = require("../utils/parseJS"); const {getPropertyKey, isMethodCall, isIdentifier, getStringArray} = require("../utils/ASTUtils"); const VisitorKeys = require("estraverse").VisitorKeys; @@ -63,7 +62,7 @@ async function analyze(resource) { } const code = await resource.getBuffer(); - visit( esprima.parseScript(code.toString()) ); + visit( parseJS(code) ); return libInfo; } diff --git a/lib/lbt/bundle/Builder.js b/lib/lbt/bundle/Builder.js index 2c79ed53b..9e090e953 100644 --- a/lib/lbt/bundle/Builder.js +++ b/lib/lbt/bundle/Builder.js @@ -4,8 +4,7 @@ const terser = require("terser"); const {pd} = require("pretty-data"); -const esprima = require("esprima"); -const {Syntax} = esprima; +const {parseJS, Syntax} = require("../utils/parseJS"); // const MOZ_SourceMap = require("source-map"); const {isMethodCall} = require("../utils/ASTUtils"); @@ -508,7 +507,7 @@ function rewriteDefine(targetBundleFormat, code, moduleName) { let ast; const codeStr = code.toString(); try { - ast = esprima.parseScript(codeStr, {range: true}); + ast = parseJS(codeStr, {range: true}); } catch (e) { log.error("error while parsing %s: %s", moduleName, e.message); return; diff --git a/lib/lbt/calls/SapUiDefine.js b/lib/lbt/calls/SapUiDefine.js index 7387a4504..f9e70e9b7 100644 --- a/lib/lbt/calls/SapUiDefine.js +++ b/lib/lbt/calls/SapUiDefine.js @@ -1,6 +1,6 @@ "use strict"; -const {Syntax} = require("esprima"); +const {Syntax} = require("../utils/parseJS"); const ModuleName = require("../utils/ModuleName"); const {isString, isBoolean} = require("../utils/ASTUtils"); diff --git a/lib/lbt/resources/ResourcePool.js b/lib/lbt/resources/ResourcePool.js index 67f4630c5..39dc95f17 100644 --- a/lib/lbt/resources/ResourcePool.js +++ b/lib/lbt/resources/ResourcePool.js @@ -4,7 +4,7 @@ const fs = require("fs"); const path = require("path"); */ -const esprima = require("esprima"); +const {parseJS} = require("../utils/parseJS"); const ComponentAnalyzer = require("../analyzer/ComponentAnalyzer"); const SmartTemplateAnalyzer = require("../analyzer/SmartTemplateAnalyzer"); const FioriElementsAnalyzer = require("../analyzer/FioriElementsAnalyzer"); @@ -67,7 +67,7 @@ async function determineDependencyInfo(resource, rawInfo, pool) { const promises = []; let ast; try { - ast = esprima.parseScript(code.toString(), {comment: true}); + ast = parseJS(code, {comment: true}); } catch (err) { log.error("failed to parse %s: %s", resource.name, err.message); } diff --git a/lib/lbt/utils/ASTUtils.js b/lib/lbt/utils/ASTUtils.js index dc8ec8dee..13d747536 100644 --- a/lib/lbt/utils/ASTUtils.js +++ b/lib/lbt/utils/ASTUtils.js @@ -1,6 +1,6 @@ "use strict"; -const {Syntax} = require("esprima"); +const {Syntax} = require("../utils/parseJS"); /** * Checks whether the given node is a string literal. diff --git a/lib/lbt/utils/parseJS.js b/lib/lbt/utils/parseJS.js new file mode 100644 index 000000000..c5772e3bf --- /dev/null +++ b/lib/lbt/utils/parseJS.js @@ -0,0 +1,28 @@ +"use strict"; + +const espree = require("espree"); +const {Syntax} = espree; + +const defaultOptions = { + comment: false, + ecmaVersion: 2020, + range: false, + sourceType: "script", +}; + +const allowedOptions = Object.keys(defaultOptions); + +function parseJS(code, options = {}) { + const firstUnsupportedOption = + Object.keys(options).find((name) => !allowedOptions.includes(name)); + if (firstUnsupportedOption != null) { + throw new TypeError(`Option ${firstUnsupportedOption} is not one of ${allowedOptions})`); + } + + return espree.parse(code, Object.assign({}, defaultOptions, options)); +} + +module.exports = { + parseJS, + Syntax +}; diff --git a/package-lock.json b/package-lock.json index 946917344..01f1b4233 100644 --- a/package-lock.json +++ b/package-lock.json @@ -385,6 +385,29 @@ "strip-json-comments": "^3.1.1" }, "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + } + }, "globals": { "version": "13.9.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", @@ -674,8 +697,7 @@ "acorn-jsx": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==" }, "acorn-walk": { "version": "8.1.0", @@ -2407,6 +2429,12 @@ "@babel/highlight": "^7.10.4" } }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", @@ -2419,6 +2447,25 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, "globals": { "version": "13.9.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", @@ -2516,34 +2563,32 @@ "dev": true }, "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" }, "dependencies": { "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" }, "eslint-visitor-keys": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" } } }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true }, "esquery": { "version": "1.4.0", diff --git a/package.json b/package.json index d683bea1c..9d41868cd 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "cheerio": "1.0.0-rc.9", "escape-unicode": "^0.2.0", "escope": "^3.6.0", - "esprima": "^4.0.1", + "espree": "^6.2.1", "estraverse": "5.1.0", "globby": "^11.0.3", "graceful-fs": "^4.2.6", diff --git a/test/lib/lbt/analyzer/FioriElementsAnalyzer.js b/test/lib/lbt/analyzer/FioriElementsAnalyzer.js index 5c810cdf8..c8fba2b36 100644 --- a/test/lib/lbt/analyzer/FioriElementsAnalyzer.js +++ b/test/lib/lbt/analyzer/FioriElementsAnalyzer.js @@ -1,7 +1,7 @@ const test = require("ava"); const FioriElementsAnalyzer = require("../../../../lib/lbt/analyzer/FioriElementsAnalyzer"); const sinon = require("sinon"); -const esprima = require("esprima"); +const parseJS = require("../../../../lib/lbt/utils/parseJS"); test("analyze: with Component.js", async (t) => { const emptyPool = {}; @@ -143,7 +143,7 @@ test.serial("_analyzeTemplateComponent: Manifest with TemplateAssembler code", a const analyzer = new FioriElementsAnalyzer(mockPool); const stubAnalyzeAST = sinon.stub(analyzer, "_analyzeAST").returns("mytpl"); - const stubParse = sinon.stub(esprima, "parse").returns(""); + const stubParse = sinon.stub(parseJS, "parseJS").returns(""); await analyzer._analyzeTemplateComponent("pony", {}, moduleInfo); @@ -177,7 +177,7 @@ test.serial("_analyzeTemplateComponent: no default template name", async (t) => const analyzer = new FioriElementsAnalyzer(mockPool); const stubAnalyzeAST = sinon.stub(analyzer, "_analyzeAST").returns(""); - const stubParse = sinon.stub(esprima, "parse").returns(""); + const stubParse = sinon.stub(parseJS, "parseJS").returns(""); await analyzer._analyzeTemplateComponent("pony", {}, moduleInfo); @@ -206,7 +206,7 @@ test.serial("_analyzeTemplateComponent: with template name from pageConfig", asy const analyzer = new FioriElementsAnalyzer(mockPool); const stubAnalyzeAST = sinon.stub(analyzer, "_analyzeAST").returns(""); - const stubParse = sinon.stub(esprima, "parse").returns(""); + const stubParse = sinon.stub(parseJS, "parseJS").returns(""); await analyzer._analyzeTemplateComponent("pony", { component: { @@ -239,7 +239,7 @@ test("_analyzeAST: get template name from ast", async (t) => { "manifest": "json" } });});`; - const ast = esprima.parse(code); + const ast = parseJS.parseJS(code); const analyzer = new FioriElementsAnalyzer(); @@ -269,7 +269,7 @@ test("_analyzeAST: no template name from ast", async (t) => { "manifest": "json" } });});`; - const ast = esprima.parse(code); + const ast = parseJS.parseJS(code); const analyzer = new FioriElementsAnalyzer(); @@ -297,7 +297,7 @@ test("_analyzeTemplateClassDefinition: get template name from metadata", async ( "manifest": "json" } };`; - const ast = esprima.parse(code); + const ast = parseJS.parseJS(code); const expression = ast.body[0].declarations[0].init; const analyzer = new FioriElementsAnalyzer(); @@ -319,7 +319,7 @@ test("_analyzeTemplateClassDefinition: no string template name from metadata", a "manifest": "json" } };`; - const ast = esprima.parse(code); + const ast = parseJS.parseJS(code); const expression = ast.body[0].declarations[0].init; const analyzer = new FioriElementsAnalyzer(); diff --git a/test/lib/lbt/analyzer/JSModuleAnalyzer.js b/test/lib/lbt/analyzer/JSModuleAnalyzer.js index 5015b5024..1a4152226 100644 --- a/test/lib/lbt/analyzer/JSModuleAnalyzer.js +++ b/test/lib/lbt/analyzer/JSModuleAnalyzer.js @@ -1,7 +1,7 @@ const test = require("ava"); const fs = require("fs"); const path = require("path"); -const esprima = require("esprima"); +const {parseJS} = require("../../../../lib/lbt/utils/parseJS"); const ModuleInfo = require("../../../../lib/lbt/resources/ModuleInfo"); const JSModuleAnalyzer = require("../../../../lib/lbt/analyzer/JSModuleAnalyzer"); @@ -51,7 +51,7 @@ function analyze(file, name) { } function analyzeString(content, name) { - const ast = esprima.parseScript(content, {comment: true}); + const ast = parseJS(content, {comment: true}); const info = new ModuleInfo(name); new JSModuleAnalyzer().analyze(ast, name, info); return info; @@ -594,7 +594,7 @@ test("Toplevel define", (t) => { }); test("Invalid ui5 bundle comment", (t) => { - const content = `/@ui5-bundles sap/ui/thirdparty/xxx.js + const content = `//@ui5-bundles sap/ui/thirdparty/xxx.js if(!('xxx'in Node.prototype)){} //@ui5-bundle-raw-includes sap/ui/thirdparty/aaa.js (function(g,f){g.AAA=f();}(this,(function(){}))); diff --git a/test/lib/lbt/analyzer/SmartTemplateAnalyzer.js b/test/lib/lbt/analyzer/SmartTemplateAnalyzer.js index 1c8195c0c..7f6dc8b1f 100644 --- a/test/lib/lbt/analyzer/SmartTemplateAnalyzer.js +++ b/test/lib/lbt/analyzer/SmartTemplateAnalyzer.js @@ -2,7 +2,7 @@ const test = require("ava"); const SmartTemplateAnalyzer = require("../../../../lib/lbt/analyzer/SmartTemplateAnalyzer"); const ModuleInfo = require("../../../../lib/lbt/resources/ModuleInfo"); const sinon = require("sinon"); -const esprima = require("esprima"); +const parseJS = require("../../../../lib/lbt/utils/parseJS"); test("analyze: with Component.js", async (t) => { @@ -210,7 +210,7 @@ test.serial("_analyzeTemplateComponent: Manifest with TemplateAssembler code", a const analyzer = new SmartTemplateAnalyzer(mockPool); const stubAnalyzeAST = sinon.stub(analyzer, "_analyzeAST").returns("mytpl"); - const stubParse = sinon.stub(esprima, "parse").returns(""); + const stubParse = sinon.stub(parseJS, "parseJS").returns(""); await analyzer._analyzeTemplateComponent("pony", {}, moduleInfo); @@ -247,7 +247,7 @@ test.serial("_analyzeTemplateComponent: no default template name", async (t) => const analyzer = new SmartTemplateAnalyzer(mockPool); const stubAnalyzeAST = sinon.stub(analyzer, "_analyzeAST").returns(""); - const stubParse = sinon.stub(esprima, "parse").returns(""); + const stubParse = sinon.stub(parseJS, "parseJS").returns(""); await analyzer._analyzeTemplateComponent("pony", {}, moduleInfo); @@ -279,7 +279,7 @@ test.serial("_analyzeTemplateComponent: with template name from pageConfig", asy const analyzer = new SmartTemplateAnalyzer(mockPool); const stubAnalyzeAST = sinon.stub(analyzer, "_analyzeAST").returns(""); - const stubParse = sinon.stub(esprima, "parse").returns(""); + const stubParse = sinon.stub(parseJS, "parseJS").returns(""); await analyzer._analyzeTemplateComponent("pony", { component: { @@ -316,7 +316,7 @@ test.serial("_analyzeTemplateComponent: dependency not found", async (t) => { const analyzer = new SmartTemplateAnalyzer(mockPool); const stubAnalyzeAST = sinon.stub(analyzer, "_analyzeAST").returns(""); - const stubParse = sinon.stub(esprima, "parse").returns(""); + const stubParse = sinon.stub(parseJS, "parseJS").returns(""); const error = await t.throwsAsync(analyzer._analyzeTemplateComponent("pony", { component: { @@ -353,7 +353,7 @@ test.serial("_analyzeTemplateComponent: dependency not found is ignored", async const analyzer = new SmartTemplateAnalyzer(mockPool); const stubAnalyzeAST = sinon.stub(analyzer, "_analyzeAST").returns(""); - const stubParse = sinon.stub(esprima, "parse").returns(""); + const stubParse = sinon.stub(parseJS, "parseJS").returns(""); await analyzer._analyzeTemplateComponent("pony", { component: { @@ -385,7 +385,7 @@ test("_analyzeAST: get template name from ast", async (t) => { "manifest": "json" } });});`; - const ast = esprima.parse(code); + const ast = parseJS.parseJS(code); const analyzer = new SmartTemplateAnalyzer(); @@ -416,7 +416,7 @@ test("_analyzeAST: no template name from ast", async (t) => { "manifest": "json" } });});`; - const ast = esprima.parse(code); + const ast = parseJS.parseJS(code); const analyzer = new SmartTemplateAnalyzer(); @@ -489,7 +489,7 @@ test("_analyzeTemplateClassDefinition: get template name from metadata", async ( "manifest": "json" } };`; - const ast = esprima.parse(code); + const ast = parseJS.parseJS(code); const expression = ast.body[0].declarations[0].init; const analyzer = new SmartTemplateAnalyzer(); @@ -511,7 +511,7 @@ test("_analyzeTemplateClassDefinition: no string template name from metadata", a "manifest": "json" } };`; - const ast = esprima.parse(code); + const ast = parseJS.parseJS(code); const expression = ast.body[0].declarations[0].init; const analyzer = new SmartTemplateAnalyzer(); diff --git a/test/lib/lbt/analyzer/XMLCompositeAnalyzer.js b/test/lib/lbt/analyzer/XMLCompositeAnalyzer.js index 59a225241..157966a89 100644 --- a/test/lib/lbt/analyzer/XMLCompositeAnalyzer.js +++ b/test/lib/lbt/analyzer/XMLCompositeAnalyzer.js @@ -1,5 +1,5 @@ const test = require("ava"); -const esprima = require("esprima"); +const {parseJS} = require("../../../../lib/lbt/utils/parseJS"); const sinon = require("sinon"); const XMLCompositeAnalyzer = require("../../../../lib/lbt/analyzer/XMLCompositeAnalyzer"); const ModuleInfo = require("../../../../lib/lbt/resources/ModuleInfo"); @@ -13,7 +13,7 @@ test("integration: XMLComposite code", async (t) => { return ButtonList; });`; - const ast = esprima.parse(code); + const ast = parseJS(code); const analyzer = new XMLCompositeAnalyzer(); const name = "composites.ButtonList"; @@ -31,7 +31,7 @@ test("analyze: not an XMLComposite module", async (t) => { return {}; });`; - const ast = esprima.parse(code); + const ast = parseJS(code); const moduleInfo = { addDependency: function() {} @@ -55,7 +55,7 @@ test("analyze: XMLComposite VariableDeclaration code", async (t) => { return ButtonList; });`; - const ast = esprima.parse(code); + const ast = parseJS(code); const moduleInfo = { addDependency: function() {} @@ -85,7 +85,7 @@ test("analyze: XMLComposite Expression code", async (t) => { jQuery.sap.test = XMLComposite.extend("composites.ButtonList", {}); });`; - const ast = esprima.parse(code); + const ast = parseJS(code); const moduleInfo = { addDependency: function() {} @@ -109,7 +109,7 @@ test("analyze: XMLComposite Expression code", async (t) => { test("_checkForXMLCClassDefinition: string argument and object expression", async (t) => { const code = `XMLComposite.extend("composites.ButtonList", {})`; - const ast = esprima.parse(code); + const ast = parseJS(code); const analyzer = new XMLCompositeAnalyzer(); const stubAnalyzeXMLCClassDefinition = sinon.stub(analyzer, "_analyzeXMLCClassDefinition").returns("cow"); @@ -123,7 +123,7 @@ test("_checkForXMLCClassDefinition: string argument and object expression", asyn test("_analyzeXMLCClassDefinition: name retrieval", async (t) => { const code = `test({fragment: "cat"})`; - const ast = esprima.parse(code); + const ast = parseJS(code); const analyzer = new XMLCompositeAnalyzer(); const result = analyzer._analyzeXMLCClassDefinition(ast.body[0].expression.arguments[0]); diff --git a/test/lib/lbt/calls/SapUiDefine.js b/test/lib/lbt/calls/SapUiDefine.js index 7a9a9a6e7..ed700de4a 100644 --- a/test/lib/lbt/calls/SapUiDefine.js +++ b/test/lib/lbt/calls/SapUiDefine.js @@ -1,11 +1,10 @@ const test = require("ava"); -const esprima = require("esprima"); -const Syntax = esprima.Syntax; +const {parseJS, Syntax} = require("../../../../lib/lbt/utils/parseJS"); const SapUiDefineCall = require("../../../../lib/lbt/calls/SapUiDefine"); function parse(code) { - const ast = esprima.parseScript(code); + const ast = parseJS(code); return ast.body[0].expression; } diff --git a/test/lib/lbt/utils/ASTUtils.js b/test/lib/lbt/utils/ASTUtils.js index 9eab3837c..6f9d8f8ca 100644 --- a/test/lib/lbt/utils/ASTUtils.js +++ b/test/lib/lbt/utils/ASTUtils.js @@ -1,12 +1,20 @@ const test = require("ava"); -const esprima = require("esprima"); +const {parseJS} = require("../../../../lib/lbt/utils/parseJS"); const ASTUtils = require("../../../../lib/lbt/utils/ASTUtils"); +/* + * remove start/end properties before comparing AST nodes + */ +const cleanse = (node) => { + delete node.start; + delete node.end; + return node; +}; test("isString", (t) => { t.false(ASTUtils.isString(null)); - const literal = esprima.parse("'testValue47'").body[0].expression; + const literal = parseJS("'testValue47'").body[0].expression; t.true(ASTUtils.isString(literal), "is a literal"); t.true(ASTUtils.isString(literal, "testValue47"), "is a literal and its value matches"); @@ -17,10 +25,10 @@ test("isString", (t) => { test("isBoolean", (t) => { t.false(ASTUtils.isString(null)); - const trueLiteral = esprima.parse("true").body[0].expression; - const falseLiteral = esprima.parse("false").body[0].expression; - const stringLiteral = esprima.parse("'some string'").body[0].expression; - const call = esprima.parse("setTimeout()").body[0]; + const trueLiteral = parseJS("true").body[0].expression; + const falseLiteral = parseJS("false").body[0].expression; + const stringLiteral = parseJS("'some string'").body[0].expression; + const call = parseJS("setTimeout()").body[0]; t.true(ASTUtils.isBoolean(trueLiteral), "is a boolean literal"); t.true(ASTUtils.isBoolean(falseLiteral), "is a boolean literal"); @@ -33,12 +41,12 @@ test("isBoolean", (t) => { }); test("isIdentifier", (t) => { - const literal = esprima.parse("'testValue47'").body[0].expression; + const literal = parseJS("'testValue47'").body[0].expression; t.false(ASTUtils.isIdentifier(literal), "A literal is not an identifier"); - const identifier = esprima.parse("testValue47").body[0].expression; + const identifier = parseJS("testValue47").body[0].expression; t.true(ASTUtils.isIdentifier(identifier, ["*"], "asterisk matches any string")); t.true(ASTUtils.isIdentifier(identifier, ["testValue47"], "value matches")); @@ -52,14 +60,14 @@ test("isIdentifier", (t) => { test("isNamedObject", (t) => { - const identifier = esprima.parse("testValue47").body[0].expression; + const identifier = parseJS("testValue47").body[0].expression; t.true(ASTUtils.isNamedObject(identifier, ["testValue47"], 1), "object with depths 1 is named testValue47"); t.false(ASTUtils.isNamedObject(identifier, ["testValue47"], 2), "object with depths 2 is not named testValue47"); t.false(ASTUtils.isNamedObject(identifier, ["testValue47"], 0), "object with depths 0 is not named testValue47"); - const member = esprima.parse("x.testValue47").body[0].expression; + const member = parseJS("x.testValue47").body[0].expression; t.true(ASTUtils.isNamedObject(member, ["x", "testValue47"], 2), "object with depths 1 is named x and with depths 2 testValue47"); t.false(ASTUtils.isNamedObject(member, ["x", "testValue47"], 1), "object with depths 1 is not named testValue47"); @@ -67,25 +75,25 @@ test("isNamedObject", (t) => { }); test("isMethodCall", (t) => { - const identifier = esprima.parse("testValue47").body[0].expression; + const identifier = parseJS("testValue47").body[0].expression; t.false(ASTUtils.isMethodCall(identifier), "identifier testValue47 is not a method call"); - const methodCall = esprima.parse("testValue47()").body[0].expression; + const methodCall = parseJS("testValue47()").body[0].expression; t.true(ASTUtils.isMethodCall(methodCall, ["testValue47"]), "testValue47 is a method call"); t.false(ASTUtils.isMethodCall(methodCall, ["myOtherValue47"]), "myOtherValue47 is not a method call"); t.false(ASTUtils.isMethodCall(methodCall, ["*"]), "* is not a method call"); }); test("getStringArray", (t) => { - const array = esprima.parse("['a', 5]").body[0].expression; + const array = parseJS("['a', 5]").body[0].expression; const error = t.throws(() => { ASTUtils.getStringArray(array); }, {instanceOf: TypeError}, "array contains a number"); t.deepEqual(error.message, "array element is not a string literal:Literal"); - const stringArray = esprima.parse("['a', 'x']").body[0].expression; + const stringArray = parseJS("['a', 'x']").body[0].expression; t.deepEqual(ASTUtils.getStringArray(stringArray), ["a", "x"], "array contains only strings"); }); @@ -95,39 +103,39 @@ test("getLocation", (t) => { test("getPropertyKey", (t) => { // quoted key - const quotedProperties = esprima.parse("var myVar = {'a':'x'}").body[0].declarations[0].init.properties; + const quotedProperties = parseJS("var myVar = {'a':'x'}").body[0].declarations[0].init.properties; t.deepEqual(ASTUtils.getPropertyKey(quotedProperties[0]), "a", "sole property key is 'a'"); // unquoted key - const unQuotedProperties = esprima.parse("var myVar = {a:'x'}").body[0].declarations[0].init.properties; + const unQuotedProperties = parseJS("var myVar = {a:'x'}").body[0].declarations[0].init.properties; t.deepEqual(ASTUtils.getPropertyKey(unQuotedProperties[0]), "a", "sole property key is 'a'"); // quoted key with dash - const dashedProperties = esprima.parse("var myVar = {'my-var': 47}").body[0].declarations[0].init.properties; + const dashedProperties = parseJS("var myVar = {'my-var': 47}").body[0].declarations[0].init.properties; t.deepEqual(ASTUtils.getPropertyKey(dashedProperties[0]), "my-var", "sole property key is 'my-var'"); }); test("findOwnProperty", (t) => { - const literal = esprima.parse("'x'").body[0].expression; + const literal = cleanse(parseJS("'x'").body[0].expression); // quoted - const object = esprima.parse("var myVar = {'a':'x'}").body[0].declarations[0].init; - t.deepEqual(ASTUtils.findOwnProperty(object, "a"), literal, "object property a's value is literal 'x'"); + const object = parseJS("var myVar = {'a':'x'}").body[0].declarations[0].init; + t.deepEqual(cleanse(ASTUtils.findOwnProperty(object, "a")), literal, "object property a's value is literal 'x'"); // unquoted - const object2 = esprima.parse("var myVar = {a:'x'}").body[0].declarations[0].init; - t.deepEqual(ASTUtils.findOwnProperty(object2, "a"), literal, "object property a's value is literal 'x'"); + const object2 = parseJS("var myVar = {a:'x'}").body[0].declarations[0].init; + t.deepEqual(cleanse(ASTUtils.findOwnProperty(object2, "a")), literal, "object property a's value is literal 'x'"); }); test("getValue", (t) => { t.falsy(ASTUtils.getValue(null, [])); t.falsy(ASTUtils.getValue(null, ["a"])); - const literal = esprima.parse("'x'").body[0].expression; - const object = esprima.parse("var myVar = {'a':'x'}").body[0].declarations[0].init; + const literal = cleanse(parseJS("'x'").body[0].expression); - t.deepEqual(ASTUtils.getValue(object, ["a"]), literal, "object property a's value is literal 'x'"); + const object = parseJS("var myVar = {'a':'x'}").body[0].declarations[0].init; + t.deepEqual(cleanse(ASTUtils.getValue(object, ["a"])), literal, "object property a's value is literal 'x'"); - const object2 = esprima.parse("var myVar = {a:'x'}").body[0].declarations[0].init; - t.deepEqual(ASTUtils.getValue(object2, ["a"]), literal, "object property a's value is literal 'x'"); + const object2 = parseJS("var myVar = {a:'x'}").body[0].declarations[0].init; + t.deepEqual(cleanse(ASTUtils.getValue(object2, ["a"])), literal, "object property a's value is literal 'x'"); });