babel-plugin-transform-modules-ui5 7.9.0 → 7.11.0

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.
@@ -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
 
@@ -3,10 +3,11 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.ClassPre = void 0;
6
+ exports.ClassPre = ClassPre;
7
7
  var _imports = require("./helpers/imports");
8
- const ClassPre = file => {
9
- // save all import declarations before "unneeded" ones are removed by the TypeScript plugin
10
- (0, _imports.saveImports)(file);
11
- };
12
- exports.ClassPre = ClassPre;
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
+ }
@@ -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
- if (shouldConvertClass(file, node, opts, classInfo)) {
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 false;
275
+ return null;
200
276
  }
201
277
  if (opts.autoConvertAllExtendClasses == true) {
202
- return true;
278
+ return CONVERT;
203
279
  }
204
280
  if (classInfo.name || classInfo.alias || classInfo.controller || classInfo.namespace) {
205
- return true;
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 controller classes
208
- if (/.*[.]controller[.](js|ts)$/.test(file.opts.filename) && opts.autoConvertControllerClass !== false) {
209
- return true;
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 false;
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,16 +19,30 @@ 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: {
23
38
  enter(path, {
24
39
  opts
25
40
  }) {
41
+ var _path$parent$comments, _path$parent;
26
42
  if (this.ran) return;
27
43
  this.ran = true;
28
44
  if (opts.onlyConvertNamedClass == false) {
29
- throw new Error("ERROR: onlyConvertNamedClass=false is no longer supported. Use autoConvertAllExtendClassesByDefault=true");
45
+ throw new Error("ERROR: onlyConvertNamedClass=false is no longer supported. Use autoConvertAllExtendClasses=true");
30
46
  }
31
47
  // TODO: enable this in a later version
32
48
  // else if (opts.onlyConvertNamedClass == true) {
@@ -35,8 +51,9 @@ module.exports = () => {
35
51
 
36
52
  // Opts
37
53
  this.namespacePrefix = opts.namespacePrefix;
54
+ this.dependencies = Array.isArray(opts.dependencies) ? opts.dependencies : [];
38
55
  this.noImportInteropPrefixes = opts.noImportInteropPrefixes || ["sap/"];
39
- this.noImportInteropPrefixesRegexp = new RegExp(this.noImportInteropPrefixes.map(p => `(^${p}.*)`).join("|"));
56
+ this.noImportInteropPrefixesRegexp = new RegExp(this.noImportInteropPrefixes.map(p => `(^${(0, _ast.escapeRegExp)(p)}.*)`).join("|"));
40
57
 
41
58
  // Properties for Module Transform
42
59
  this.programNode = path.node;
@@ -48,6 +65,7 @@ module.exports = () => {
48
65
  this.imports = [];
49
66
  this.namedExports = [];
50
67
  this.injectDynamicImportHelper = false;
68
+ this.noWrap = ((_path$parent$comments = (_path$parent = path.parent) === null || _path$parent === void 0 ? void 0 : _path$parent.comments) !== null && _path$parent$comments !== void 0 ? _path$parent$comments : []).some(c => c.type === "CommentBlock" && c.value.trim() === "@ui5-no-wrap");
51
69
 
52
70
  // Properties for Class Transform
53
71
  this.importNames = [];
@@ -68,6 +86,38 @@ module.exports = () => {
68
86
  }, _visitor2.ModuleTransformVisitor);
69
87
  return {
70
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
+ },
71
114
  visitor: UI5Visitor
72
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)();
73
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
- if (_core.types.isLiteral(declaration.init) && declaration.init.value === defaultExportValue.value) {
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;
@@ -10,7 +10,10 @@ var th = _interopRequireWildcard(require("../../utils/templates"));
10
10
  var ast = _interopRequireWildcard(require("../../utils/ast"));
11
11
  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); }
12
12
  function wrap(visitor, programNode, opts) {
13
- var _body;
13
+ var _visitor$parent$comme, _visitor$parent, _body;
14
+ if (hasNoWrapPragma((_visitor$parent$comme = (_visitor$parent = visitor.parent) === null || _visitor$parent === void 0 ? void 0 : _visitor$parent.comments) !== null && _visitor$parent$comme !== void 0 ? _visitor$parent$comme : [])) {
15
+ return;
16
+ }
14
17
  let {
15
18
  defaultExport,
16
19
  exportGlobal,
@@ -48,8 +51,8 @@ function wrap(visitor, programNode, opts) {
48
51
  // is associcated with the program and not the program body, so we need to find it
49
52
  // and move it to the new program body (which is sap.ui.define)
50
53
  if (!copyright) {
51
- var _visitor$parent;
52
- copyright = (_visitor$parent = visitor.parent) === null || _visitor$parent === void 0 || (_visitor$parent = _visitor$parent.comments) === null || _visitor$parent === void 0 ? void 0 : _visitor$parent.find((comment, idx, arr) => {
54
+ var _visitor$parent2;
55
+ copyright = (_visitor$parent2 = visitor.parent) === null || _visitor$parent2 === void 0 || (_visitor$parent2 = _visitor$parent2.comments) === null || _visitor$parent2 === void 0 ? void 0 : _visitor$parent2.find((comment, idx, arr) => {
53
56
  if (comment.value.startsWith("!")) {
54
57
  arr.splice(idx, 1);
55
58
  return true;
@@ -181,6 +184,9 @@ function wrap(visitor, programNode, opts) {
181
184
  function hasUseStrict(node) {
182
185
  return (node.directives || []).some(directive => directive.value.value === "use strict");
183
186
  }
187
+ function hasNoWrapPragma(comments) {
188
+ return comments.some(comment => comment.type === "CommentBlock" && comment.value.trim() === "@ui5-no-wrap");
189
+ }
184
190
  function hasUseSapUiRequire(comments, body, remove) {
185
191
  // detect the @sapUiRequire comment
186
192
  return comments.some(comment => {
@@ -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
- if (modulePath.endsWith(imp.src.split("/").pop())) {
63
- console.log(`\x1b[33mHint:\x1b[0m Removed file extension for dependency "\x1b[34m${imp.src}\x1b[0m" found in \x1b[90m${filename}\x1b[0m`);
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,6 +73,7 @@ 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);
@@ -94,7 +100,8 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
94
100
  node
95
101
  } = path;
96
102
  if (node.importKind === "type") return; // flow-type
97
-
103
+ if (this.noWrap) return;
104
+ if (hasIgnoreImportPragma(node)) return;
98
105
  const {
99
106
  specifiers,
100
107
  source
@@ -189,7 +196,7 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
189
196
  deconstructors.push(th.buildNamedImportDestructor({
190
197
  MODULE: _core.types.identifier(imp.tmpName),
191
198
  LOCAL: specifier.local,
192
- IMPORTED: _core.types.stringLiteral(specifier.imported.name)
199
+ IMPORTED: _core.types.stringLiteral(_core.types.isStringLiteral(specifier.imported) ? specifier.imported.value : specifier.imported.name)
193
200
  }));
194
201
  } else {
195
202
  throw path.buildCodeFrameError(`Unknown ImportDeclaration specifier type ${specifier.type}`);
@@ -213,7 +220,7 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
213
220
  }
214
221
  imp.deconstructors = imp.deconstructors.concat(deconstructors);
215
222
  if (!existingImport) {
216
- addImport(this.imports, imp, filename);
223
+ addImport(this.imports, imp, filename, false, opts);
217
224
  }
218
225
  },
219
226
  /**
@@ -222,7 +229,8 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
222
229
  * where a let or var can be defined, and the latest one should be exported.
223
230
  */
224
231
  ExportNamedDeclaration(path, {
225
- filename
232
+ filename,
233
+ opts
226
234
  }) {
227
235
  const {
228
236
  node
@@ -232,6 +240,7 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
232
240
  declaration,
233
241
  source
234
242
  } = node;
243
+ if (this.noWrap) return;
235
244
  let fromSource = "";
236
245
  if (source) {
237
246
  // e.g. export { one, two } from 'x'
@@ -242,7 +251,7 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
242
251
  src,
243
252
  name,
244
253
  tmpName
245
- }, filename);
254
+ }, filename, false, opts);
246
255
  fromSource = tmpName + ".";
247
256
  }
248
257
  if (specifiers && specifiers.length) {
@@ -289,6 +298,7 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
289
298
  const {
290
299
  node
291
300
  } = path;
301
+ if (this.noWrap) return;
292
302
  let {
293
303
  declaration
294
304
  } = node;
@@ -324,8 +334,10 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
324
334
  }
325
335
  },
326
336
  ExportAllDeclaration(path, {
327
- filename
337
+ filename,
338
+ opts
328
339
  }) {
340
+ if (this.noWrap) return;
329
341
  const src = resolveSource(path.node.source.value, filename);
330
342
  const name = cleanImportSource(src);
331
343
  const tmpName = tempModuleName(name);
@@ -333,7 +345,7 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
333
345
  src,
334
346
  name,
335
347
  tmpName
336
- }, filename);
348
+ }, filename, false, opts);
337
349
  this.exportAllHelper = true;
338
350
  this.namedExports.push({
339
351
  all: true,
@@ -351,7 +363,9 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
351
363
  const {
352
364
  callee
353
365
  } = node;
366
+ if (this.noWrap) return;
354
367
  if (ast.isImport(callee)) {
368
+ if (hasIgnoreImportPragma(path.getStatementParent().node)) return;
355
369
  this.injectDynamicImportHelper = true;
356
370
  path.replaceWith(_objectSpread(_objectSpread({}, node), {}, {
357
371
  callee: _core.types.identifier("__ui5_require_async")
@@ -365,6 +379,7 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
365
379
  const {
366
380
  node
367
381
  } = path;
382
+ if (this.noWrap) return;
368
383
  if ((node === null || node === void 0 || (_node$object = node.object) === null || _node$object === void 0 ? void 0 : _node$object.type) === "MetaProperty") {
369
384
  var _node$property, _node$property2;
370
385
  // replace all "import.meta.url" with "module.url"
@@ -379,4 +394,8 @@ const ModuleTransformVisitor = exports.ModuleTransformVisitor = {
379
394
  }
380
395
  }
381
396
  }
382
- };
397
+ };
398
+ function hasIgnoreImportPragma(node) {
399
+ var _node$leadingComments;
400
+ return ((_node$leadingComments = node.leadingComments) !== null && _node$leadingComments !== void 0 ? _node$leadingComments : []).some(comment => comment.type === "CommentBlock" && comment.value.trim() === "@ui5-ignore-import");
401
+ }
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
- }).flat(Infinity);
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.name);
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
- accumulator[property.key.name] = property.value;
150
+ const name = getPropKeyName(property.key);
151
+ if (name !== undefined) {
152
+ accumulator[name] = property.value;
153
+ }
141
154
  return accumulator;
142
155
  }, {});
143
156
  }