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.
- package/CHANGELOG.md +83 -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 +52 -2
- package/dist/modules/helpers/exports.js +11 -2
- package/dist/modules/helpers/wrapper.js +9 -3
- package/dist/modules/visitor.js +32 -13
- 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,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
|
|
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
|
-
|
|
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$
|
|
52
|
-
copyright = (_visitor$
|
|
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 => {
|
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,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
|
-
|
|
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
|
}
|