Skip to content

Commit

Permalink
[FEATURE] Support ES2022 language features (#848)
Browse files Browse the repository at this point in the history
JIRA: CPOUI5FOUNDATION-591
  • Loading branch information
matz3 authored Nov 29, 2022
1 parent 798fc1d commit f9b8457
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 13 deletions.
24 changes: 12 additions & 12 deletions lib/lbt/analyzer/JSModuleAnalyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,18 @@ const EnrichedVisitorKeys = (function() {
* All properties in an object pattern are executed.
*/
ObjectPattern: [], // properties
// PrivateIdentifier: [], // will come with ES2022
PrivateIdentifier: [],
Program: [],
Property: [],
// PropertyDefinition: [], // will come with ES2022
PropertyDefinition: [],
/*
* argument of the rest element is always executed under the same condition as the rest element itself
*/
RestElement: [], // argument
ReturnStatement: [],
SequenceExpression: [],
SpreadElement: [], // the argument of the spread operator always needs to be evaluated - argument
// StaticBlock: [], // will come with ES2022
StaticBlock: [],
Super: [],
SwitchStatement: [],
SwitchCase: ["test", "consequent"], // test and consequent are executed only conditionally
Expand Down Expand Up @@ -196,15 +196,6 @@ const EnrichedVisitorKeys = (function() {
return;
}

// Ignore new ES2022 syntax as we currently use ES2021 (see parseUtils.js)
if (
type === "PrivateIdentifier" ||
type === "PropertyDefinition" ||
type === "StaticBlock"
) {
return;
}

const visitorKeys = VisitorKeys[type];
const condKeys = TempKeys[type];
if ( condKeys === undefined ) {
Expand Down Expand Up @@ -535,6 +526,15 @@ class JSModuleAnalyzer {
}
break;

case Syntax.PropertyDefinition:

// Instance properties (static=false) are only initialized when an instance is created (conditional)
// but a computed key is always evaluated on class initialization (eager)
visit(node.key, conditional);
visit(node.value, !node.static || conditional);

break;

default:
if ( condKeys == null ) {
log.error("Unhandled AST node type " + node.type, node);
Expand Down
2 changes: 2 additions & 0 deletions lib/lbt/utils/ASTUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export function isMethodCall(node, methodPath) {
}

export function isNamedObject(node, objectPath, length) {
// TODO: Support PrivateIdentifier (foo.#bar)

// console.log("checking for named object ", node, objectPath, length);
while ( length > 1 &&
node.type === Syntax.MemberExpression &&
Expand Down
2 changes: 1 addition & 1 deletion lib/lbt/utils/parseUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function parseJS(code, userOptions = {}) {
// allowed options and their defaults
const options = {
comment: false,
ecmaVersion: 2021, // NOTE: Adopt JSModuleAnalyzer.js to allow new Syntax when upgrading to newer ECMA versions
ecmaVersion: 2022, // NOTE: Adopt JSModuleAnalyzer.js to allow new Syntax when upgrading to newer ECMA versions
range: false,
sourceType: "script",
};
Expand Down
64 changes: 64 additions & 0 deletions test/lib/lbt/analyzer/JSModuleAnalyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,70 @@ test("LogicalExpression", (t) => {
`ui5 module`);
});

test("ES2022: PrivateIdentifier, PropertyDefinition, StaticBlock", (t) => {
const content = `
sap.ui.define(['require', 'static/module1'], (require) => {
class TestES2022 {
// Eager dependencies
static {
const staticModule2 = sap.ui.requireSync('static/module2');
}
static publicStaticField = sap.ui.requireSync('static/module3');
static #privateStaticField = sap.ui.requireSync('static/module4');
static [sap.ui.requireSync('static/module5')] = "module5";
// Even though the field is on instance level, the computed key is evaluated when the class is declared
[sap.ui.requireSync('static/module6')] = "module6";
// Conditional dependencies
publicField = sap.ui.requireSync('conditional/module1');
#privateField = sap.ui.requireSync('conditional/module2');
#privateMethod() {
sap.ui.requireSync('conditional/module3')
}
static #privateStaticMethod() {
sap.ui.requireSync('conditional/module4')
}
}
});`;
const info = analyzeString(content, "modules/ES2022.js");

const expected = [
"conditional/module1.js",
"conditional/module2.js",
"conditional/module3.js",
"conditional/module4.js",
"static/module1.js",
"static/module2.js",
"static/module3.js",
"static/module4.js",
"static/module5.js",
"static/module6.js",
"ui5loader-autoconfig.js",
];
const actual = info.dependencies.sort();
t.deepEqual(actual, expected, "module dependencies should match");
expected.forEach((dep) => {
t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep),
`only dependencies to 'conditional/*' modules should be conditional (${dep})`);
t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep),
`all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`);
});
t.false(info.dynamicDependencies,
`no use of dynamic dependencies should have been detected`);
t.false(info.rawModule,
`ui5 module`);
});

test("Dynamic import (declare/require)", async (t) => {
const info = await analyze("modules/declare_dynamic_require.js");
t.true(info.dynamicDependencies,
Expand Down
6 changes: 6 additions & 0 deletions test/lib/lbt/utils/parseUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ test("successful parse step (ES2021 features)", (t) => {
t.true(ast != null && typeof ast === "object");
t.is(ast.type, "Program");
});

test("successful parse step (ES2022 features)", (t) => {
const ast = parseJS("class X { #foo; }"); // Private class field
t.true(ast != null && typeof ast === "object");
t.is(ast.type, "Program");
});

0 comments on commit f9b8457

Please sign in to comment.