babel-plugin-transform-modules-ui5 7.10.0 → 7.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +72 -0
- package/dist/classes/helpers/classes.js +57 -14
- package/dist/classes/helpers/decorators.js +23 -1
- package/dist/classes/helpers/imports.js +783 -6
- package/dist/classes/helpers/jsdoc.js +10 -0
- package/dist/classes/pre.js +7 -6
- package/dist/classes/visitor.js +197 -8
- package/dist/index.js +50 -2
- package/dist/modules/helpers/exports.js +11 -2
- package/dist/modules/visitor.js +62 -21
- package/dist/utils/ast.js +16 -3
- package/package.json +2 -2
|
@@ -8,6 +8,16 @@ exports.getTags = getTags;
|
|
|
8
8
|
exports.hasJsdocGlobalExportFlag = hasJsdocGlobalExportFlag;
|
|
9
9
|
var _core = require("@babel/core");
|
|
10
10
|
var _commentParser = require("comment-parser");
|
|
11
|
+
// Tags that provide a value for the class info:
|
|
12
|
+
// @alias — recommended for named UI5 extend-classes (Component, View, Controller,
|
|
13
|
+
// Control, …); sets the fully-qualified class name passed to .extend().
|
|
14
|
+
// This is the JSDoc property used by UI5 source code and is the officially
|
|
15
|
+
// recommended way to declare the module name.
|
|
16
|
+
// @name — use when the class has no local name (anonymous class expression, e.g.
|
|
17
|
+
// `return class extends SAPController {}`). @alias requires a local name
|
|
18
|
+
// to alias; @name does not.
|
|
19
|
+
// @namespace — sets the namespace prefix; the ES class name is appended automatically
|
|
20
|
+
// to form the full FQN passed to .extend().
|
|
11
21
|
const classInfoValueTags = ["alias", "name", "namespace"];
|
|
12
22
|
const classInfoBoolTags = ["nonUI5", "controller", "keepConstructor"];
|
|
13
23
|
|
package/dist/classes/pre.js
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.ClassPre =
|
|
6
|
+
exports.ClassPre = ClassPre;
|
|
7
7
|
var _imports = require("./helpers/imports");
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
// Must be a regular function (not arrow) so Babel's plugin lifecycle sets `this` to the
|
|
9
|
+
// plugin state object, giving access to this.opts (the plugin options).
|
|
10
|
+
function ClassPre(file) {
|
|
11
|
+
var _this$opts;
|
|
12
|
+
(0, _imports.saveImports)(file, (_this$opts = this.opts) === null || _this$opts === void 0 ? void 0 : _this$opts.additionalUI5Classes);
|
|
13
|
+
}
|
package/dist/classes/visitor.js
CHANGED
|
@@ -5,10 +5,26 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.ClassTransformVisitor = void 0;
|
|
7
7
|
var _core = require("@babel/core");
|
|
8
|
+
var Path = _interopRequireWildcard(require("path"));
|
|
8
9
|
var th = _interopRequireWildcard(require("../utils/templates"));
|
|
9
10
|
var ast = _interopRequireWildcard(require("../utils/ast"));
|
|
10
11
|
var classes = _interopRequireWildcard(require("./helpers/classes"));
|
|
12
|
+
var _imports = require("./helpers/imports");
|
|
11
13
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
14
|
+
/**
|
|
15
|
+
* Built-in list of UI5 base-class module paths used by autoConvertKnownUI5Classes.
|
|
16
|
+
* A class whose superclass is imported from one of these paths will be auto-converted
|
|
17
|
+
* to .extend() syntax without requiring an explicit @alias/@namespace annotation.
|
|
18
|
+
*/
|
|
19
|
+
const KNOWN_UI5_BASE_CLASSES = ["sap/ui/core/mvc/Controller", "sap/ui/core/mvc/View", "sap/ui/core/mvc/XMLView", "sap/ui/core/mvc/JSView", "sap/ui/core/mvc/HTMLView", "sap/ui/core/mvc/JSONView", "sap/ui/core/UIComponent", "sap/ui/core/Component", "sap/ui/core/Control", "sap/ui/core/Element", "sap/ui/base/ManagedObject", "sap/ui/base/Object", "sap/ui/core/mvc/ControllerExtension"];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Subset of KNOWN_UI5_BASE_CLASSES that are UI5 controller types.
|
|
23
|
+
* Used to auto-set classInfo.controller when no explicit @controller tag is present,
|
|
24
|
+
* so that controller-specific options (moveControllerPropsToOnInit, etc.) activate
|
|
25
|
+
* for classes that extend a controller base class directly.
|
|
26
|
+
*/
|
|
27
|
+
const KNOWN_UI5_CONTROLLER_CLASSES = ["sap/ui/core/mvc/Controller", "sap/ui/core/mvc/ControllerExtension"];
|
|
12
28
|
const CONSTRUCTOR = "constructor";
|
|
13
29
|
const ClassTransformVisitor = exports.ClassTransformVisitor = {
|
|
14
30
|
ImportDeclaration(path) {
|
|
@@ -17,6 +33,9 @@ const ClassTransformVisitor = exports.ClassTransformVisitor = {
|
|
|
17
33
|
ImportDefaultSpecifier(path) {
|
|
18
34
|
this.importNames.push(path.node.local.name);
|
|
19
35
|
},
|
|
36
|
+
ImportSpecifier(path) {
|
|
37
|
+
this.importNames.push(path.node.local.name);
|
|
38
|
+
},
|
|
20
39
|
/**
|
|
21
40
|
* ClassDeclaration visitor.
|
|
22
41
|
* Use both enter() and exit() to track when the visitor is inside a UI5 class,
|
|
@@ -52,7 +71,31 @@ const ClassTransformVisitor = exports.ClassTransformVisitor = {
|
|
|
52
71
|
const name = exp.name || ((_exp$callee = exp.callee) === null || _exp$callee === void 0 ? void 0 : _exp$callee.name);
|
|
53
72
|
return classInfo[name] === undefined;
|
|
54
73
|
});
|
|
55
|
-
|
|
74
|
+
const convertInfo = shouldConvertClass(file, node, opts, classInfo);
|
|
75
|
+
if (convertInfo) {
|
|
76
|
+
var _node$superClass2;
|
|
77
|
+
// Auto-set the controller flag when the filename suffix or superclass
|
|
78
|
+
// import identifies this as a UI5 controller, so that controller-specific
|
|
79
|
+
// options (moveControllerPropsToOnInit, etc.) activate even when the class
|
|
80
|
+
// name does not contain "Controller" and no explicit @controller tag is set.
|
|
81
|
+
if (!classInfo.controller) {
|
|
82
|
+
const filename = file.opts.filename || "";
|
|
83
|
+
if (opts.autoConvertUI5FileTypes !== false && opts.autoConvertControllerClass !== false && /[.]controller[.](js|ts)$/.test(filename)) {
|
|
84
|
+
classInfo.controller = true;
|
|
85
|
+
} else if (convertInfo.isController) {
|
|
86
|
+
// shouldConvertClass already walked the chain and found a controller ancestor.
|
|
87
|
+
classInfo.controller = true;
|
|
88
|
+
} else {
|
|
89
|
+
var _node$superClass;
|
|
90
|
+
// Direct superclass imported from a known controller base-class path.
|
|
91
|
+
const directSuperClassName = (_node$superClass = node.superClass) === null || _node$superClass === void 0 ? void 0 : _node$superClass.name;
|
|
92
|
+
const importDecl = directSuperClassName ? (0, _imports.getImportDeclaration)(filename, directSuperClassName) : null;
|
|
93
|
+
if (importDecl && KNOWN_UI5_CONTROLLER_CLASSES.includes(importDecl.source.value)) {
|
|
94
|
+
classInfo.controller = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
56
99
|
// Save super class name for converting super calls
|
|
57
100
|
this.superClassName = classInfo.superClassName;
|
|
58
101
|
// store the classinfo
|
|
@@ -60,6 +103,18 @@ const ClassTransformVisitor = exports.ClassTransformVisitor = {
|
|
|
60
103
|
this.classInfo = this.classInfo || {};
|
|
61
104
|
this.classInfo[className] = classInfo;
|
|
62
105
|
}
|
|
106
|
+
// Record this file's superclass import source unconditionally so that
|
|
107
|
+
// descendants in other files can use transitive detection even when this
|
|
108
|
+
// class was converted via an explicit annotation (not autoConvertKnownUI5Classes).
|
|
109
|
+
// Keyed by the class's own name so descendants can look up by the binding name
|
|
110
|
+
// they used for the import (which equals the class name in the ancestor file).
|
|
111
|
+
const superClassName = (_node$superClass2 = node.superClass) === null || _node$superClass2 === void 0 ? void 0 : _node$superClass2.name;
|
|
112
|
+
if (superClassName) {
|
|
113
|
+
const importDecl = (0, _imports.getImportDeclaration)(file.opts.filename, superClassName);
|
|
114
|
+
if (importDecl) {
|
|
115
|
+
(0, _imports.saveSuperclassImportSource)(file.opts.filename, className, superClassName, importDecl.source.value);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
63
118
|
}
|
|
64
119
|
},
|
|
65
120
|
exit(path, {
|
|
@@ -194,19 +249,153 @@ function doesClassExtendFromImport(node, imports) {
|
|
|
194
249
|
const superClass = node.superClass;
|
|
195
250
|
return superClass && imports.some(imported => imported === superClass.name);
|
|
196
251
|
}
|
|
252
|
+
function decoratorName(expr) {
|
|
253
|
+
if (!expr) return "?";
|
|
254
|
+
if (_core.types.isIdentifier(expr)) return expr.name;
|
|
255
|
+
if (_core.types.isCallExpression(expr) || _core.types.isOptionalCallExpression(expr)) return decoratorName(expr.callee);
|
|
256
|
+
if (_core.types.isMemberExpression(expr) || _core.types.isOptionalMemberExpression(expr)) {
|
|
257
|
+
const obj = decoratorName(expr.object);
|
|
258
|
+
const prop = _core.types.isIdentifier(expr.property) ? expr.property.name : "?";
|
|
259
|
+
return `${obj}.${prop}`;
|
|
260
|
+
}
|
|
261
|
+
return "?";
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Sentinel objects returned by shouldConvertClass.
|
|
265
|
+
// Using distinct objects (not `true`) lets callers read `isController` without a second walk.
|
|
266
|
+
const CONVERT = {
|
|
267
|
+
isController: false
|
|
268
|
+
};
|
|
269
|
+
const CONVERT_CONTROLLER = {
|
|
270
|
+
isController: true
|
|
271
|
+
};
|
|
197
272
|
function shouldConvertClass(file, node, opts, classInfo) {
|
|
273
|
+
var _node$decorators2, _opts$additionalUI5Cl;
|
|
198
274
|
if (classInfo.nonUI5) {
|
|
199
|
-
return
|
|
275
|
+
return null;
|
|
200
276
|
}
|
|
201
277
|
if (opts.autoConvertAllExtendClasses == true) {
|
|
202
|
-
return
|
|
278
|
+
return CONVERT;
|
|
203
279
|
}
|
|
204
280
|
if (classInfo.name || classInfo.alias || classInfo.controller || classInfo.namespace) {
|
|
205
|
-
return
|
|
281
|
+
return CONVERT;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Filename-suffix opt-outs are authoritative overrides — honor them regardless of
|
|
285
|
+
// whether autoConvertUI5FileTypes is on or off. A user who explicitly disables
|
|
286
|
+
// controller or view conversion must not have it silently re-enabled by the
|
|
287
|
+
// known-class branch or any other detection path.
|
|
288
|
+
{
|
|
289
|
+
const filename = file.opts.filename || "";
|
|
290
|
+
if (/[.]controller[.](js|ts)$/.test(filename)) {
|
|
291
|
+
if (opts.autoConvertUI5FileTypes === false || opts.autoConvertControllerClass === false) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (/[.]view[.](js|ts|jsx|tsx)$/.test(filename)) {
|
|
296
|
+
if (opts.autoConvertUI5FileTypes === false || opts.autoConvertViewClass === false) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Preserve legacy .controller.(js|ts) behaviour exactly: controllers have always
|
|
303
|
+
// been auto-converted without requiring a resolvable namespace, so this runs before
|
|
304
|
+
// the namespace guard below. Views are a new feature and remain subject to the guard.
|
|
305
|
+
if (opts.autoConvertUI5FileTypes !== false && opts.autoConvertControllerClass !== false && /[.]controller[.](js|ts)$/.test(file.opts.filename || "")) {
|
|
306
|
+
return CONVERT_CONTROLLER;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// For all auto-conversion paths below, require a non-empty namespace so the
|
|
310
|
+
// generated FQN is not a bare class name. fileNamespace is derived from the
|
|
311
|
+
// file path relative to sourceRoot (+ namespacePrefix if set).
|
|
312
|
+
if (!classInfo.fileNamespace) {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Don't auto-convert when unknown (non-UI5) decorators remain on the class —
|
|
317
|
+
// those decorators may transform the class in ways incompatible with .extend().
|
|
318
|
+
// Explicit @alias/@namespace annotations (handled above) still take precedence.
|
|
319
|
+
if ((_node$decorators2 = node.decorators) !== null && _node$decorators2 !== void 0 && _node$decorators2.length) {
|
|
320
|
+
var _node$id3;
|
|
321
|
+
const names = node.decorators.map(d => "@" + decoratorName(d.expression)).join(", ");
|
|
322
|
+
console.warn(`[babel-plugin-ui5] Skipping auto-conversion of class "${(_node$id3 = node.id) === null || _node$id3 === void 0 ? void 0 : _node$id3.name}" in "${file.opts.filename}" — ` + `non-UI5 decorator(s) present: ${names}. Add @alias or @namespace to convert it, or @nonUI5 to silence this warning.`);
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Convert classes whose superclass is imported from a known UI5 base-class module.
|
|
327
|
+
// autoConvertKnownUI5Classes defaults to true; opt out by setting it to false.
|
|
328
|
+
// additionalUI5Classes entries are always checked regardless of autoConvertKnownUI5Classes —
|
|
329
|
+
// the two are independent: one controls the built-in list, the other adds custom paths.
|
|
330
|
+
const useBuiltInList = opts.autoConvertKnownUI5Classes !== false;
|
|
331
|
+
const hasAdditionalClasses = !!((_opts$additionalUI5Cl = opts.additionalUI5Classes) !== null && _opts$additionalUI5Cl !== void 0 && _opts$additionalUI5Cl.length);
|
|
332
|
+
if (useBuiltInList || hasAdditionalClasses) {
|
|
333
|
+
const knownPaths = new Set([...(useBuiltInList ? KNOWN_UI5_BASE_CLASSES : []), ...(opts.additionalUI5Classes || [])]);
|
|
334
|
+
if (knownPaths.size) {
|
|
335
|
+
var _node$superClass3, _node$id4;
|
|
336
|
+
const superClassName = (_node$superClass3 = node.superClass) === null || _node$superClass3 === void 0 ? void 0 : _node$superClass3.name;
|
|
337
|
+
const className = (_node$id4 = node.id) === null || _node$id4 === void 0 ? void 0 : _node$id4.name;
|
|
338
|
+
const filename = file.opts.filename;
|
|
339
|
+
const importDecl = (0, _imports.getImportDeclaration)(filename, superClassName);
|
|
340
|
+
if (importDecl) {
|
|
341
|
+
var _opts$sourceFolders, _opts$dependencies;
|
|
342
|
+
const importSource = importDecl.source.value;
|
|
343
|
+
// Record keyed by the class's own name so descendants can look up by the binding
|
|
344
|
+
// name they used in their import (which equals the class name in the ancestor file).
|
|
345
|
+
// Also records the superclass binding name so the transitive chain can be followed.
|
|
346
|
+
(0, _imports.saveSuperclassImportSource)(filename, className, superClassName, importSource);
|
|
347
|
+
// Direct hit — immediate superclass is a known UI5 base class
|
|
348
|
+
if (knownPaths.has(importSource)) {
|
|
349
|
+
var _opts$additionalUI5Cl2;
|
|
350
|
+
if ((_opts$additionalUI5Cl2 = opts.additionalUI5Classes) !== null && _opts$additionalUI5Cl2 !== void 0 && _opts$additionalUI5Cl2.includes(importSource)) {
|
|
351
|
+
(0, _imports.recordAdditionalUI5ClassMatch)(importSource);
|
|
352
|
+
}
|
|
353
|
+
return KNOWN_UI5_CONTROLLER_CLASSES.includes(importSource) ? CONVERT_CONTROLLER : CONVERT;
|
|
354
|
+
}
|
|
355
|
+
// Transitive hit — walk the superclass chain through already-processed local files.
|
|
356
|
+
// isSuperclassTransitivelyKnown returns the matched importSource string (or null).
|
|
357
|
+
const sourceRoot = file.opts.sourceRoot ? Path.resolve(process.cwd(), file.opts.sourceRoot) : process.cwd();
|
|
358
|
+
// Use the effective namespace prefix for the current file (respects sourceFolders).
|
|
359
|
+
// ?? (not ||) so that an explicit "" mapping in sourceFolders is respected —
|
|
360
|
+
// || would treat "" as falsy and fall through to namespacePrefix.
|
|
361
|
+
// ?? (not ||) so that an explicit "" mapping in sourceFolders is respected —
|
|
362
|
+
// || would treat "" as falsy and fall through to namespacePrefix.
|
|
363
|
+
// getEffectiveNamespacePrefix already returns namespacePrefix as a fallback when
|
|
364
|
+
// sourceFolders is absent, so no extra ?? namespacePrefix chaining is needed here.
|
|
365
|
+
const effectivePrefix = (0, _imports.getEffectiveNamespacePrefix)(filename, sourceRoot, opts);
|
|
366
|
+
const transitiveMatch = (0, _imports.isSuperclassTransitivelyKnown)(filename, superClassName, knownPaths, sourceRoot, {
|
|
367
|
+
namespacePrefix: effectivePrefix,
|
|
368
|
+
sourceFolders: (_opts$sourceFolders = opts.sourceFolders) !== null && _opts$sourceFolders !== void 0 ? _opts$sourceFolders : null,
|
|
369
|
+
dependencies: (_opts$dependencies = opts.dependencies) !== null && _opts$dependencies !== void 0 ? _opts$dependencies : null
|
|
370
|
+
});
|
|
371
|
+
if (transitiveMatch) {
|
|
372
|
+
var _opts$additionalUI5Cl3;
|
|
373
|
+
// N2: walk every intermediate local ancestor in the chain and verify that each
|
|
374
|
+
// one has a resolvable namespace (or is a legacy .controller.(js|ts) file that
|
|
375
|
+
// converts without a namespace). An ancestor with an empty namespace that is NOT
|
|
376
|
+
// a controller file remains a native ES class at runtime, so calling .extend()
|
|
377
|
+
// on it would throw.
|
|
378
|
+
//
|
|
379
|
+
// isLocalChainConvertible checks the *full* chain, not only the direct ancestor:
|
|
380
|
+
// a chain MyCtrl→LocalA(has NS)→LocalMid(empty NS)→SAP Controller is rejected
|
|
381
|
+
// because LocalMid would not be converted to .extend().
|
|
382
|
+
if (!(0, _imports.isLocalChainConvertible)(importSource, filename, sourceRoot, effectivePrefix, knownPaths, opts)) {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
// F4: mark only the path that was actually matched, not every additionalUI5Classes
|
|
386
|
+
// entry — to avoid suppressing valid "never matched" warnings for typo paths.
|
|
387
|
+
if ((_opts$additionalUI5Cl3 = opts.additionalUI5Classes) !== null && _opts$additionalUI5Cl3 !== void 0 && _opts$additionalUI5Cl3.includes(transitiveMatch)) {
|
|
388
|
+
(0, _imports.recordAdditionalUI5ClassMatch)(transitiveMatch);
|
|
389
|
+
}
|
|
390
|
+
return KNOWN_UI5_CONTROLLER_CLASSES.includes(transitiveMatch) ? CONVERT_CONTROLLER : CONVERT;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
206
394
|
}
|
|
207
|
-
// Convert
|
|
208
|
-
|
|
209
|
-
|
|
395
|
+
// Convert classes in .view.* files based on filename suffix (requires namespace above).
|
|
396
|
+
// .controller.* is already handled before the namespace guard to preserve legacy behaviour.
|
|
397
|
+
if (opts.autoConvertUI5FileTypes !== false && opts.autoConvertViewClass !== false && /[.]view[.](js|ts|jsx|tsx)$/.test(file.opts.filename || "")) {
|
|
398
|
+
return CONVERT;
|
|
210
399
|
}
|
|
211
|
-
return
|
|
400
|
+
return null;
|
|
212
401
|
}
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
var _visitor = require("./classes/visitor");
|
|
4
4
|
var _pre = require("./classes/pre");
|
|
5
|
+
var _imports = require("./classes/helpers/imports");
|
|
5
6
|
var _visitor2 = require("./modules/visitor");
|
|
7
|
+
var _ast = require("./utils/ast");
|
|
6
8
|
var _wrapper = require("./modules/helpers/wrapper");
|
|
7
9
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
8
10
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
@@ -17,6 +19,19 @@ Babel Types (t.*) https://github.com/babel/babel/tree/master/packages/babel-type
|
|
|
17
19
|
AST Explorer: https://astexplorer.net/
|
|
18
20
|
*/
|
|
19
21
|
|
|
22
|
+
// Accumulates every additionalUI5Classes path seen across all post() invocations.
|
|
23
|
+
// A single process.on('exit') handler (registered once) checks this set so that all
|
|
24
|
+
// configured paths — even those from different plugin instances in the same build — are
|
|
25
|
+
// covered. Without this, the closure in the first post() call would only see the first
|
|
26
|
+
// invocation's options array.
|
|
27
|
+
//
|
|
28
|
+
// NOTE: The warning fires once per Node process (at exit), not once per build. In a
|
|
29
|
+
// long-lived process that runs many builds (watch mode, programmatic transform loops) the
|
|
30
|
+
// paths accumulate across all of them and the warning reports the union at process exit.
|
|
31
|
+
// There is no reliable per-build boundary at the Babel plugin level to scope this to a
|
|
32
|
+
// single build, so the per-process behaviour is intentional for the common one-shot CLI case.
|
|
33
|
+
let allConfiguredUI5ClassPaths = null;
|
|
34
|
+
let exitHandlerRegistered = false;
|
|
20
35
|
module.exports = () => {
|
|
21
36
|
const UI5Visitor = _objectSpread({
|
|
22
37
|
Program: {
|
|
@@ -27,7 +42,7 @@ module.exports = () => {
|
|
|
27
42
|
if (this.ran) return;
|
|
28
43
|
this.ran = true;
|
|
29
44
|
if (opts.onlyConvertNamedClass == false) {
|
|
30
|
-
throw new Error("ERROR: onlyConvertNamedClass=false is no longer supported. Use
|
|
45
|
+
throw new Error("ERROR: onlyConvertNamedClass=false is no longer supported. Use autoConvertAllExtendClasses=true");
|
|
31
46
|
}
|
|
32
47
|
// TODO: enable this in a later version
|
|
33
48
|
// else if (opts.onlyConvertNamedClass == true) {
|
|
@@ -36,8 +51,9 @@ module.exports = () => {
|
|
|
36
51
|
|
|
37
52
|
// Opts
|
|
38
53
|
this.namespacePrefix = opts.namespacePrefix;
|
|
54
|
+
this.dependencies = Array.isArray(opts.dependencies) ? opts.dependencies : [];
|
|
39
55
|
this.noImportInteropPrefixes = opts.noImportInteropPrefixes || ["sap/"];
|
|
40
|
-
this.noImportInteropPrefixesRegexp = new RegExp(this.noImportInteropPrefixes.map(p => `(^${p}.*)`).join("|"));
|
|
56
|
+
this.noImportInteropPrefixesRegexp = new RegExp(this.noImportInteropPrefixes.map(p => `(^${(0, _ast.escapeRegExp)(p)}.*)`).join("|"));
|
|
41
57
|
|
|
42
58
|
// Properties for Module Transform
|
|
43
59
|
this.programNode = path.node;
|
|
@@ -70,6 +86,38 @@ module.exports = () => {
|
|
|
70
86
|
}, _visitor2.ModuleTransformVisitor);
|
|
71
87
|
return {
|
|
72
88
|
pre: _pre.ClassPre,
|
|
89
|
+
post() {
|
|
90
|
+
var _this$opts;
|
|
91
|
+
// PM2: reset the per-cycle eviction flag so that the next watch cycle's first
|
|
92
|
+
// saveImports call re-evicts on-demand ancestor files that may have changed.
|
|
93
|
+
(0, _imports.resetOnDemandEvictionCycle)();
|
|
94
|
+
// F5: use this.opts (plugin options), not the Babel file opts passed as argument.
|
|
95
|
+
// F6: defer the warning to process exit so it fires once after all files are done,
|
|
96
|
+
// avoiding false positives when a valid path is only imported in a later file.
|
|
97
|
+
const additionalUI5Classes = (_this$opts = this.opts) === null || _this$opts === void 0 ? void 0 : _this$opts.additionalUI5Classes;
|
|
98
|
+
if (!(additionalUI5Classes !== null && additionalUI5Classes !== void 0 && additionalUI5Classes.length)) return;
|
|
99
|
+
// Accumulate all configured paths across every post() invocation so that builds
|
|
100
|
+
// using multiple Babel configs or programmatic transforms don't miss any list.
|
|
101
|
+
if (!allConfiguredUI5ClassPaths) allConfiguredUI5ClassPaths = new Set();
|
|
102
|
+
for (const p of additionalUI5Classes) allConfiguredUI5ClassPaths.add(p);
|
|
103
|
+
if (!exitHandlerRegistered) {
|
|
104
|
+
exitHandlerRegistered = true;
|
|
105
|
+
process.on("exit", () => {
|
|
106
|
+
if (!allConfiguredUI5ClassPaths) return;
|
|
107
|
+
const unmatched = (0, _imports.getUnmatchedAdditionalUI5Classes)([...allConfiguredUI5ClassPaths]);
|
|
108
|
+
for (const p of unmatched) {
|
|
109
|
+
console.warn(`\x1b[33mWarn:\x1b[0m additionalUI5Classes: path "\x1b[34m${p}\x1b[0m" was never matched by any import in this build. Check for typos.`);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
},
|
|
73
114
|
visitor: UI5Visitor
|
|
74
115
|
};
|
|
116
|
+
};
|
|
117
|
+
module.exports.resetPluginState = function resetPluginState() {
|
|
118
|
+
allConfiguredUI5ClassPaths = null;
|
|
119
|
+
// Do NOT reset exitHandlerRegistered: the process.on('exit') handler is registered
|
|
120
|
+
// once for the lifetime of the process. Re-registering it after resetPluginState()
|
|
121
|
+
// would cause duplicate warnings (both handlers fire at exit).
|
|
122
|
+
(0, _imports.resetAllCaches)();
|
|
75
123
|
};
|
|
@@ -29,6 +29,12 @@ function collapseNamedExports(programNode, defaultExport, namedExports, opts) {
|
|
|
29
29
|
if (!namedExports.length || opts.noExportExtend) {
|
|
30
30
|
return {
|
|
31
31
|
filteredExports: namedExports,
|
|
32
|
+
// Always include conflictingExports so the consumer (wrapper.js) can call
|
|
33
|
+
// getPropNames() on it. When noExportExtend leaves non-collapsible named
|
|
34
|
+
// exports in filteredExports, the consumer throws the "Unsafe mixing" error
|
|
35
|
+
// and reads conflictingExports — a missing key here produced a garbled
|
|
36
|
+
// "Cannot read properties of undefined (reading 'map')" instead.
|
|
37
|
+
conflictingExports: namedExports,
|
|
32
38
|
newDefaultExportIdentifier: null
|
|
33
39
|
};
|
|
34
40
|
}
|
|
@@ -108,8 +114,11 @@ function areSame(defaultExportProperty, matchingNamedExport) {
|
|
|
108
114
|
}
|
|
109
115
|
} else if (_core.types.isLiteral(defaultExportValue)) {
|
|
110
116
|
// i.e. { a: 1 } or { a: '1' }
|
|
111
|
-
// See if the original declaration for the matching export was for the same literal
|
|
112
|
-
|
|
117
|
+
// See if the original declaration for the matching export was for the same literal.
|
|
118
|
+
// Guard `declaration` the same way the identifier branch above does: specifier
|
|
119
|
+
// exports (`export { a }`) carry no `declaration`, so reading declaration.init
|
|
120
|
+
// would throw.
|
|
121
|
+
if (declaration && _core.types.isLiteral(declaration.init) && declaration.init.value === defaultExportValue.value) {
|
|
113
122
|
// i.e. 'export const a = 1'
|
|
114
123
|
// Same literal. Safe to ignore
|
|
115
124
|
return true;
|
package/dist/modules/visitor.js
CHANGED
|
@@ -23,7 +23,7 @@ const resolveSource = (src, filename) => {
|
|
|
23
23
|
src = src.replace(/\\/g, "/");
|
|
24
24
|
const dir = (0, _path.dirname)(filename);
|
|
25
25
|
const absoluteSrc = (0, _path.join)(dir, src);
|
|
26
|
-
if ((0, _fs.existsSync)(absoluteSrc) && (0, _fs.statSync)(absoluteSrc).isDirectory) {
|
|
26
|
+
if ((0, _fs.existsSync)(absoluteSrc) && (0, _fs.statSync)(absoluteSrc).isDirectory()) {
|
|
27
27
|
if ((0, _fs.existsSync)((0, _path.join)(absoluteSrc, "index.js")) || (0, _fs.existsSync)((0, _path.join)(absoluteSrc, "index.jsx")) || (0, _fs.existsSync)((0, _path.join)(absoluteSrc, "index.ts")) || (0, _fs.existsSync)((0, _path.join)(absoluteSrc, "index.tsx")) || (0, _fs.existsSync)((0, _path.join)(absoluteSrc, "index.mjs")) || (0, _fs.existsSync)((0, _path.join)(absoluteSrc, "index.cjs"))) {
|
|
28
28
|
src = `${src}/index`;
|
|
29
29
|
}
|
|
@@ -33,7 +33,7 @@ const resolveSource = (src, filename) => {
|
|
|
33
33
|
const cleanImportSource = src => src.replace(/(\/)|(-)|(@)/g, "_").replace(/\./g, "");
|
|
34
34
|
const tempModuleName = name => `__${name}`;
|
|
35
35
|
const hasGlobalExportFlag = node => (0, _jsdoc.hasJsdocGlobalExportFlag)(node);
|
|
36
|
-
const addImport = (imports, imp, filename, first) => {
|
|
36
|
+
const addImport = (imports, imp, filename, first, opts = {}) => {
|
|
37
37
|
const existingImport = imports.find(i => i.src === imp.src);
|
|
38
38
|
if (!existingImport) {
|
|
39
39
|
// if a module path ends with the file extension ".js" and it can be resolved to
|
|
@@ -58,9 +58,14 @@ const addImport = (imports, imp, filename, first) => {
|
|
|
58
58
|
} else {
|
|
59
59
|
modulePath = require.resolve(absModuleSrc);
|
|
60
60
|
}
|
|
61
|
-
// detect removal of file extension and log a hint
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
// detect removal of file extension and log a hint.
|
|
62
|
+
// Guard on modulePath: for a relative import whose file doesn't exist it
|
|
63
|
+
// stays undefined (no extension matched) — don't slice, and don't rely on a
|
|
64
|
+
// thrown TypeError for that control flow.
|
|
65
|
+
if (modulePath && modulePath.endsWith(imp.src.split("/").pop())) {
|
|
66
|
+
if (!opts.suppressImportResolutionHints) {
|
|
67
|
+
console.log(`\x1b[33mHint:\x1b[0m Removed file extension for dependency "\x1b[34m${imp.src}\x1b[0m" found in \x1b[90m${filename}\x1b[0m`);
|
|
68
|
+
}
|
|
64
69
|
imp.src = imp.src.slice(0, -3);
|
|
65
70
|
}
|
|
66
71
|
} catch (ex) {
|
|
@@ -68,10 +73,12 @@ const addImport = (imports, imp, filename, first) => {
|
|
|
68
73
|
// can't be resolved with the given file extension, e.g.
|
|
69
74
|
// myns/module.js must be provided as myns/module
|
|
70
75
|
// myns/module.js.js must be provided as myns/module.js
|
|
76
|
+
void ex;
|
|
71
77
|
}
|
|
72
78
|
}
|
|
73
79
|
imports[first ? "unshift" : "push"](imp);
|
|
74
80
|
}
|
|
81
|
+
return existingImport || imp;
|
|
75
82
|
};
|
|
76
83
|
const addModuleImport = (imports, name, filename) => {
|
|
77
84
|
addImport(imports, {
|
|
@@ -169,6 +176,13 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
|
|
|
169
176
|
MODULE: _core.types.identifier(imp.tmpName),
|
|
170
177
|
LOCAL: specifier.local
|
|
171
178
|
}));
|
|
179
|
+
} else if (specifier.local.name !== imp.tmpName) {
|
|
180
|
+
// A previous import or re-export may already have locked the factory
|
|
181
|
+
// parameter name. Preserve this default import's local alias too.
|
|
182
|
+
deconstructors.push(th.buildConstDeclaration({
|
|
183
|
+
NAME: specifier.local,
|
|
184
|
+
VALUE: _core.types.identifier(imp.tmpName)
|
|
185
|
+
}));
|
|
172
186
|
}
|
|
173
187
|
} else if (_core.types.isImportNamespaceSpecifier(specifier)) {
|
|
174
188
|
if (specifiers.length === 1 && !imp.locked) {
|
|
@@ -190,7 +204,7 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
|
|
|
190
204
|
deconstructors.push(th.buildNamedImportDestructor({
|
|
191
205
|
MODULE: _core.types.identifier(imp.tmpName),
|
|
192
206
|
LOCAL: specifier.local,
|
|
193
|
-
IMPORTED: _core.types.stringLiteral(specifier.imported.name)
|
|
207
|
+
IMPORTED: _core.types.stringLiteral(_core.types.isStringLiteral(specifier.imported) ? specifier.imported.value : specifier.imported.name)
|
|
194
208
|
}));
|
|
195
209
|
} else {
|
|
196
210
|
throw path.buildCodeFrameError(`Unknown ImportDeclaration specifier type ${specifier.type}`);
|
|
@@ -214,7 +228,7 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
|
|
|
214
228
|
}
|
|
215
229
|
imp.deconstructors = imp.deconstructors.concat(deconstructors);
|
|
216
230
|
if (!existingImport) {
|
|
217
|
-
addImport(this.imports, imp, filename);
|
|
231
|
+
addImport(this.imports, imp, filename, false, opts);
|
|
218
232
|
}
|
|
219
233
|
},
|
|
220
234
|
/**
|
|
@@ -223,7 +237,8 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
|
|
|
223
237
|
* where a let or var can be defined, and the latest one should be exported.
|
|
224
238
|
*/
|
|
225
239
|
ExportNamedDeclaration(path, {
|
|
226
|
-
filename
|
|
240
|
+
filename,
|
|
241
|
+
opts
|
|
227
242
|
}) {
|
|
228
243
|
const {
|
|
229
244
|
node
|
|
@@ -234,25 +249,47 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
|
|
|
234
249
|
source
|
|
235
250
|
} = node;
|
|
236
251
|
if (this.noWrap) return;
|
|
237
|
-
|
|
252
|
+
if (node.exportKind === "type") return; // TS or Flow-types
|
|
253
|
+
|
|
254
|
+
let fromImport;
|
|
255
|
+
let shouldInterop = false;
|
|
238
256
|
if (source) {
|
|
239
|
-
//
|
|
257
|
+
// Reuse the actual factory parameter if this dependency was imported
|
|
258
|
+
// earlier. Lock new records because the export expressions reference it.
|
|
240
259
|
const src = resolveSource(source.value, filename);
|
|
241
260
|
const name = cleanImportSource(src);
|
|
242
261
|
const tmpName = tempModuleName(name);
|
|
243
|
-
addImport(this.imports, {
|
|
262
|
+
fromImport = addImport(this.imports, {
|
|
244
263
|
src,
|
|
245
264
|
name,
|
|
246
|
-
tmpName
|
|
247
|
-
|
|
248
|
-
|
|
265
|
+
tmpName,
|
|
266
|
+
deconstructors: [],
|
|
267
|
+
locked: true
|
|
268
|
+
}, filename, false, opts);
|
|
269
|
+
fromImport.locked = true;
|
|
270
|
+
shouldInterop = !this.noImportInteropPrefixesRegexp.test(src);
|
|
249
271
|
}
|
|
250
272
|
if (specifiers && specifiers.length) {
|
|
251
273
|
// e.g. export { one, two }
|
|
252
|
-
for (const specifier of
|
|
274
|
+
for (const specifier of specifiers) {
|
|
275
|
+
if (specifier.exportKind === "type") continue; // TS type-only specifier
|
|
276
|
+
let value = _core.types.cloneNode(specifier.local);
|
|
277
|
+
if (fromImport) {
|
|
278
|
+
const importedName = _core.types.isStringLiteral(specifier.local) ? specifier.local.value : specifier.local.name;
|
|
279
|
+
const module = _core.types.identifier(fromImport.tmpName);
|
|
280
|
+
if (importedName === "default") {
|
|
281
|
+
// Transformed UI5 modules can return the default value directly.
|
|
282
|
+
// Use exactly the same interop as a normal default import, without
|
|
283
|
+
// changing the producer's export shape or named export semantics.
|
|
284
|
+
value = shouldInterop ? _core.types.callExpression(_core.types.identifier("_interopRequireDefault"), [module]) : module;
|
|
285
|
+
if (shouldInterop) fromImport.interop = true;
|
|
286
|
+
} else {
|
|
287
|
+
value = _core.types.memberExpression(module, _core.types.cloneNode(specifier.local), _core.types.isStringLiteral(specifier.local));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
253
290
|
this.namedExports.push({
|
|
254
291
|
key: specifier.exported,
|
|
255
|
-
value
|
|
292
|
+
value
|
|
256
293
|
});
|
|
257
294
|
}
|
|
258
295
|
path.remove();
|
|
@@ -327,21 +364,25 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
|
|
|
327
364
|
}
|
|
328
365
|
},
|
|
329
366
|
ExportAllDeclaration(path, {
|
|
330
|
-
filename
|
|
367
|
+
filename,
|
|
368
|
+
opts
|
|
331
369
|
}) {
|
|
332
370
|
if (this.noWrap) return;
|
|
333
371
|
const src = resolveSource(path.node.source.value, filename);
|
|
334
372
|
const name = cleanImportSource(src);
|
|
335
373
|
const tmpName = tempModuleName(name);
|
|
336
|
-
addImport(this.imports, {
|
|
374
|
+
const imp = addImport(this.imports, {
|
|
337
375
|
src,
|
|
338
376
|
name,
|
|
339
|
-
tmpName
|
|
340
|
-
|
|
377
|
+
tmpName,
|
|
378
|
+
deconstructors: [],
|
|
379
|
+
locked: true
|
|
380
|
+
}, filename, false, opts);
|
|
381
|
+
imp.locked = true;
|
|
341
382
|
this.exportAllHelper = true;
|
|
342
383
|
this.namedExports.push({
|
|
343
384
|
all: true,
|
|
344
|
-
value: _core.types.identifier(tmpName)
|
|
385
|
+
value: _core.types.identifier(imp.tmpName)
|
|
345
386
|
});
|
|
346
387
|
path.remove();
|
|
347
388
|
},
|
package/dist/utils/ast.js
CHANGED
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.convertDeclarationToExpression = convertDeclarationToExpression;
|
|
7
7
|
exports.convertFunctionDeclarationToExpression = convertFunctionDeclarationToExpression;
|
|
8
|
+
exports.escapeRegExp = void 0;
|
|
8
9
|
exports.findPropertiesOfNode = findPropertiesOfNode;
|
|
9
10
|
exports.getIdName = getIdName;
|
|
10
11
|
exports.getInternalStaticThingsOfClass = getInternalStaticThingsOfClass;
|
|
@@ -23,6 +24,8 @@ exports.isSuperCallExpression = isSuperCallExpression;
|
|
|
23
24
|
exports.isSuperPrototypeCallOf = isSuperPrototypeCallOf;
|
|
24
25
|
exports.isThisExpressionUsed = isThisExpressionUsed;
|
|
25
26
|
var _core = require("@babel/core");
|
|
27
|
+
const escapeRegExp = s => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
28
|
+
exports.escapeRegExp = escapeRegExp;
|
|
26
29
|
function isObjectAssignOrExtendsExpression(node) {
|
|
27
30
|
return isObjectAssignExpression(node) || isExtendsHelperExpression(node);
|
|
28
31
|
}
|
|
@@ -130,14 +133,24 @@ function getPropertiesOfObjectAssignOrExtendHelper(node, blockScopeNode) {
|
|
|
130
133
|
// Recursive, although props will be empty if arg is an imported object
|
|
131
134
|
return getOtherPropertiesOfIdentifier(blockScopeNode, arg.name);
|
|
132
135
|
}
|
|
133
|
-
|
|
136
|
+
return []; // e.g. Object.create(null) or other CallExpression args contribute no properties
|
|
137
|
+
}).flat(Infinity).filter(Boolean);
|
|
138
|
+
}
|
|
139
|
+
function getPropKeyName(key) {
|
|
140
|
+
if (!key) return undefined;
|
|
141
|
+
if (key.type === "StringLiteral") return key.value;
|
|
142
|
+
if (key.type === "Identifier") return key.name;
|
|
143
|
+
return undefined;
|
|
134
144
|
}
|
|
135
145
|
function getPropNames(props) {
|
|
136
|
-
return props.map(prop => prop.key.
|
|
146
|
+
return props.map(prop => getPropKeyName(prop.key)).filter(Boolean);
|
|
137
147
|
}
|
|
138
148
|
function groupPropertiesByName(properties) {
|
|
139
149
|
return properties && properties.reduce((accumulator, property) => {
|
|
140
|
-
|
|
150
|
+
const name = getPropKeyName(property.key);
|
|
151
|
+
if (name !== undefined) {
|
|
152
|
+
accumulator[name] = property.value;
|
|
153
|
+
}
|
|
141
154
|
return accumulator;
|
|
142
155
|
}, {});
|
|
143
156
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "babel-plugin-transform-modules-ui5",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.11.1",
|
|
4
4
|
"description": "An unofficial babel plugin for SAP UI5.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"repository": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@babel/cli": "7.28.3",
|
|
66
|
-
"@babel/core": "7.
|
|
66
|
+
"@babel/core": "7.29.7",
|
|
67
67
|
"@babel/eslint-parser": "7.28.5",
|
|
68
68
|
"@babel/plugin-proposal-decorators": "7.28.0",
|
|
69
69
|
"@babel/plugin-proposal-object-rest-spread": "7.20.7",
|