Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Support ES2022 language features #848

Merged
merged 3 commits into from
Nov 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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";
codeworrior marked this conversation as resolved.
Show resolved Hide resolved

// 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");
});