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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,88 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 7.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#162](https://github.com/ui5-community/babel-plugin-transform-modules-ui5/pull/162) [`db2ad98`](https://github.com/ui5-community/babel-plugin-transform-modules-ui5/commit/db2ad98648ac7971d7b6cc1264b8d7b32d4b0b56) Thanks [@petermuessig](https://github.com/petermuessig)! - feat: auto-detect UI5 extend-classes by filename suffix and known base-class module paths
|
|
8
|
+
|
|
9
|
+
## What's new
|
|
10
|
+
- **Auto-detection by known UI5 base classes**: Classes extending from known UI5 base-class module paths (e.g. `sap/ui/core/mvc/Controller`, `sap/ui/core/Control`, `sap/ui/base/ManagedObject`) are automatically converted to `.extend()` syntax without requiring explicit `@alias` or `@namespace` annotations. Controlled by new `autoConvertKnownUI5Classes` option (defaults to true).
|
|
11
|
+
|
|
12
|
+
- **Transitive inheritance resolution**: Auto-detection walks the superclass chain through local files (order-independent via on-demand ancestor reading from disk) and detects UI5 ancestry even through multiple levels of custom base classes.
|
|
13
|
+
|
|
14
|
+
- **File-type suffix detection**: New `autoConvertUI5FileTypes` option detects and converts classes based on filename patterns (`.controller.js|ts` and `.view.js|ts|jsx|tsx`). Controllers preserve legacy behavior (no namespace required). Views require a resolvable namespace.
|
|
15
|
+
|
|
16
|
+
- **Custom base-class paths**: New `additionalUI5Classes` option allows registering project-specific base-class module paths for auto-detection, supplementing the built-in UI5 class list.
|
|
17
|
+
|
|
18
|
+
- **Cycle detection**: Inheritance chain walking correctly handles cycles (A extends B extends A) via visited-set tracking instead of depth limits.
|
|
19
|
+
|
|
20
|
+
- **Watch-mode cache fixes**: Fixed stale cache entries persisting across rebuilds when files are renamed or deleted mid-watch. The `importDeclarations` cache entry is now cleared (not just overwritten) during re-processing.
|
|
21
|
+
|
|
22
|
+
- **Unmatched path warnings**: Added `console.warn` diagnostic when `additionalUI5Classes` paths are configured but never matched by any import in the build, helping catch typos and misconfigurations.
|
|
23
|
+
|
|
24
|
+
## Breaking changes
|
|
25
|
+
|
|
26
|
+
None. All new features are opt-in or preserve existing behavior.
|
|
27
|
+
|
|
28
|
+
**Internal API note:** `getImportDeclaration` (in `src/classes/helpers/imports.js`) now matches
|
|
29
|
+
`import { Foo as Bar }` by the _local_ binding name (`Bar`) rather than the _imported_ name (`Foo`).
|
|
30
|
+
This is the correct semantics — callers always pass the name as it appears at the use site — but if
|
|
31
|
+
you maintain a fork or plugin that calls this helper directly by the imported name, update those call
|
|
32
|
+
sites to pass the local name instead.
|
|
33
|
+
|
|
34
|
+
- [#168](https://github.com/ui5-community/babel-plugin-transform-modules-ui5/pull/168) [`e31c07f`](https://github.com/ui5-community/babel-plugin-transform-modules-ui5/commit/e31c07f2e47478ae77e00e135d60c4c8fb896c88) Thanks [@petermuessig](https://github.com/petermuessig)! - feat: add `@movePropsToConstructor` per-property annotation to opt out of `moveControllerPropsToOnInit` ([#116](https://github.com/ui5-community/babel-plugin-transform-modules-ui5/issues/116))
|
|
35
|
+
|
|
36
|
+
When `moveControllerPropsToOnInit` is enabled, individual class properties can be kept in the
|
|
37
|
+
constructor by annotating them with `@movePropsToConstructor` (JSDoc block comment or decorator):
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
export default class MyController extends Controller {
|
|
41
|
+
/** Normal prop — moves to onInit as usual */
|
|
42
|
+
prop = 1;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @movePropsToConstructor
|
|
46
|
+
*/
|
|
47
|
+
formatter = { formatValue: (v: string) => v }; // stays in constructor
|
|
48
|
+
|
|
49
|
+
handler = () => true; // moves to onInit as usual
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Patch Changes
|
|
54
|
+
|
|
55
|
+
- [#167](https://github.com/ui5-community/babel-plugin-transform-modules-ui5/pull/167) [`d021d2e`](https://github.com/ui5-community/babel-plugin-transform-modules-ui5/commit/d021d2e6422494931ddeb8944ac443facc2c5e4b) Thanks [@petermuessig](https://github.com/petermuessig)! - fix: avoid crash when a default export uses `Object.assign(Object.create(null), …)` alongside named exports ([#148](https://github.com/ui5-community/babel-plugin-transform-modules-ui5/issues/148))
|
|
56
|
+
|
|
57
|
+
`getPropertiesOfObjectAssignOrExtendHelper` no longer leaks `undefined` entries for call-expression arguments
|
|
58
|
+
(e.g. `Object.create(null)`), which previously caused `Cannot read properties of undefined (reading 'key')` during
|
|
59
|
+
export collapsing.
|
|
60
|
+
|
|
61
|
+
- [#166](https://github.com/ui5-community/babel-plugin-transform-modules-ui5/pull/166) [`506985d`](https://github.com/ui5-community/babel-plugin-transform-modules-ui5/commit/506985d2d5f9004faad9f4cbd9b5e4c04b4bd052) Thanks [@petermuessig](https://github.com/petermuessig)! - fix: handle string-literal import names and object property keys; escape regex metacharacters in noImportInteropPrefixes
|
|
62
|
+
|
|
63
|
+
Picks up the still-relevant fixes from PR [#163](https://github.com/ui5-community/babel-plugin-transform-modules-ui5/issues/163) (thanks @mauriciolauffer):
|
|
64
|
+
|
|
65
|
+
- Named imports using string-literal specifiers (`import { "some-export" as local } from "mod"`) now produce correct destructure output instead of `undefined`.
|
|
66
|
+
- `noImportInteropPrefixes` entries are now regex-escaped before being embedded in the regexp pattern, preventing metacharacters from acting as wildcards.
|
|
67
|
+
- `getPropNames` and `groupPropertiesByName` in `utils/ast.js` now handle `StringLiteral` property keys in addition to `Identifier` keys.
|
|
68
|
+
|
|
69
|
+
## 7.10.0
|
|
70
|
+
|
|
71
|
+
### Minor Changes
|
|
72
|
+
|
|
73
|
+
- [#154](https://github.com/ui5-community/babel-plugin-transform-modules-ui5/pull/154) [`f62f9fb`](https://github.com/ui5-community/babel-plugin-transform-modules-ui5/commit/f62f9fba1a64dea2673662dd163fcc1eb5cff540) Thanks [@bbogovich-sap](https://github.com/bbogovich-sap)! - Add two comment pragmas to opt out of module transformation:
|
|
74
|
+
|
|
75
|
+
- `/* @ui5-no-wrap */` (file level) leaves a module completely unwrapped —
|
|
76
|
+
no `sap.ui.define`/`sap.ui.require` and no import/export rewriting — while
|
|
77
|
+
still stripping TypeScript types. Intended for modules consumed as native
|
|
78
|
+
ES modules, e.g. web workers.
|
|
79
|
+
- `/* @ui5-ignore-import */` (statement level) excludes a single static
|
|
80
|
+
`import` or dynamic `import()` from being converted into a UI5 dependency.
|
|
81
|
+
|
|
82
|
+
Note: `@ui5-ignore-import` on a static import only yields valid output in an
|
|
83
|
+
unwrapped module (no exports, or together with `@ui5-no-wrap`); a leftover
|
|
84
|
+
ES `import` inside a `sap.ui.define` factory is not valid at runtime.
|
|
85
|
+
|
|
3
86
|
## 7.9.0
|
|
4
87
|
|
|
5
88
|
### Minor Changes
|
|
@@ -30,6 +30,7 @@ function convertClassToUI5Extend(path, node, classInfo, extraStaticProps, import
|
|
|
30
30
|
return node;
|
|
31
31
|
}
|
|
32
32
|
const CONTROLLER_EXTENSION_TAG = "transformControllerExtension";
|
|
33
|
+
const KEEP_IN_CONSTRUCTOR_TAG = "movePropsToConstructor";
|
|
33
34
|
const staticMembers = [];
|
|
34
35
|
const classNameIdentifier = node.id;
|
|
35
36
|
const className = classNameIdentifier.name;
|
|
@@ -42,6 +43,8 @@ function convertClassToUI5Extend(path, node, classInfo, extraStaticProps, import
|
|
|
42
43
|
const alwaysMoveInstanceProps = !opts.onlyMoveClassPropsUsingThis;
|
|
43
44
|
const extendProps = [];
|
|
44
45
|
const boundProps = [];
|
|
46
|
+
const constructorBoundProps = []; // props that must stay in constructor even when moveControllerPropsToOnInit is true
|
|
47
|
+
|
|
45
48
|
const CONSTRUCTOR = "constructor";
|
|
46
49
|
const propsByName = {};
|
|
47
50
|
let constructor;
|
|
@@ -187,6 +190,7 @@ function convertClassToUI5Extend(path, node, classInfo, extraStaticProps, import
|
|
|
187
190
|
staticMembers.push(buildMemberAssignmentStatement(classNameIdentifier, member));
|
|
188
191
|
}
|
|
189
192
|
} else {
|
|
193
|
+
var _member$leadingCommen2, _member$decorators2;
|
|
190
194
|
propsByName[memberName] = member.value;
|
|
191
195
|
if (memberName === "constructor") {
|
|
192
196
|
constructorComments = member.leadingComments;
|
|
@@ -195,8 +199,19 @@ function convertClassToUI5Extend(path, node, classInfo, extraStaticProps, import
|
|
|
195
199
|
continue; // don't push to props yet
|
|
196
200
|
}
|
|
197
201
|
}
|
|
202
|
+
// When moveControllerPropsToOnInit is active, a per-property @movePropsToConstructor
|
|
203
|
+
// annotation (JSDoc block comment or decorator) opts that specific property out of the
|
|
204
|
+
// onInit move and keeps it in the constructor instead.
|
|
205
|
+
const keepInConstructor = moveControllerPropsToOnInit && (((_member$leadingCommen2 = member.leadingComments) === null || _member$leadingCommen2 === void 0 ? void 0 : _member$leadingCommen2.some(comment => comment.value.includes("@" + KEEP_IN_CONSTRUCTOR_TAG))) || ((_member$decorators2 = member.decorators) === null || _member$decorators2 === void 0 ? void 0 : _member$decorators2.some(decorator => {
|
|
206
|
+
var _decorator$expression2;
|
|
207
|
+
return ((_decorator$expression2 = decorator.expression) === null || _decorator$expression2 === void 0 ? void 0 : _decorator$expression2.name) === KEEP_IN_CONSTRUCTOR_TAG;
|
|
208
|
+
})));
|
|
198
209
|
if (alwaysMoveInstanceProps || _core.types.isArrowFunctionExpression(member.value) || ast.isThisExpressionUsed(member.value)) {
|
|
199
|
-
|
|
210
|
+
if (keepInConstructor) {
|
|
211
|
+
constructorBoundProps.push(member);
|
|
212
|
+
} else {
|
|
213
|
+
boundProps.push(member);
|
|
214
|
+
}
|
|
200
215
|
} else {
|
|
201
216
|
extendProps.push(buildObjectProperty(member));
|
|
202
217
|
}
|
|
@@ -249,6 +264,10 @@ function convertClassToUI5Extend(path, node, classInfo, extraStaticProps, import
|
|
|
249
264
|
// See if we need either constructor or onInit
|
|
250
265
|
const needsBindingMethod = boundProps.length || moveControllerPropsToOnInit && constructor && !keepConstructor;
|
|
251
266
|
|
|
267
|
+
// See if we need to create a constructor separately for constructorBoundProps
|
|
268
|
+
// (only relevant when moveControllerPropsToOnInit is on and some props opted out).
|
|
269
|
+
const needsConstructorForOverrides = moveControllerPropsToOnInit && constructorBoundProps.length;
|
|
270
|
+
|
|
252
271
|
// See if we need to create a new 'constructor' or 'onInit' method, depending which one we'll bind to.
|
|
253
272
|
if (needsBindingMethod && !bindMethod) {
|
|
254
273
|
const bindToId = _core.types.identifier(bindToMethodName);
|
|
@@ -284,6 +303,32 @@ function convertClassToUI5Extend(path, node, classInfo, extraStaticProps, import
|
|
|
284
303
|
bindMethod.body.body = [...upToSuper, ...mappedProps, ...afterSuper];
|
|
285
304
|
}
|
|
286
305
|
}
|
|
306
|
+
if (needsConstructorForOverrides) {
|
|
307
|
+
// Some props were annotated @movePropsToConstructor — inject them into the
|
|
308
|
+
// constructor even though moveControllerPropsToOnInit is active.
|
|
309
|
+
let constructorMethod = constructor;
|
|
310
|
+
if (!constructorMethod) {
|
|
311
|
+
// No explicit constructor: create an inheriting one and register it.
|
|
312
|
+
const constructorDeclaration = th.buildInheritingConstructor({
|
|
313
|
+
SUPER: _core.types.identifier(superClassName)
|
|
314
|
+
});
|
|
315
|
+
constructorMethod = ast.convertFunctionDeclarationToExpression(constructorDeclaration);
|
|
316
|
+
extendProps.unshift(_core.types.objectProperty(_core.types.identifier(CONSTRUCTOR), constructorMethod));
|
|
317
|
+
} else if (!keepConstructor) {
|
|
318
|
+
// Constructor exists but was merged into onInit — we need to keep it in
|
|
319
|
+
// extend() as well so the constructor-bound props have somewhere to live.
|
|
320
|
+
extendProps.unshift(_core.types.objectProperty(_core.types.identifier(CONSTRUCTOR), constructorMethod));
|
|
321
|
+
}
|
|
322
|
+
const mappedOverrideProps = constructorBoundProps.map(member => buildThisMemberAssignmentStatement(member));
|
|
323
|
+
const superIndex = constructorMethod.body.body.findIndex(node => ast.isSuperCallExpression(node.expression) || ast.isSuperPrototypeCallOf(node.expression, superClassName, CONSTRUCTOR));
|
|
324
|
+
if (superIndex === -1) {
|
|
325
|
+
constructorMethod.body.body.unshift(...mappedOverrideProps);
|
|
326
|
+
} else {
|
|
327
|
+
const upToSuper = constructorMethod.body.body.slice(0, superIndex + 1);
|
|
328
|
+
const afterSuper = constructorMethod.body.body.slice(superIndex + 1);
|
|
329
|
+
constructorMethod.body.body = [...upToSuper, ...mappedOverrideProps, ...afterSuper];
|
|
330
|
+
}
|
|
331
|
+
}
|
|
287
332
|
const extendAssign = th.buildExtendAssign({
|
|
288
333
|
NAME: classNameIdentifier,
|
|
289
334
|
SUPER: superClass,
|
|
@@ -293,6 +338,16 @@ function convertClassToUI5Extend(path, node, classInfo, extraStaticProps, import
|
|
|
293
338
|
});
|
|
294
339
|
return [extendAssign, ...staticMembers];
|
|
295
340
|
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Derives the fully-qualified name (FQN) passed to SAPClass.extend("FQN", ...).
|
|
344
|
+
*
|
|
345
|
+
* Precedence:
|
|
346
|
+
* 1. @alias — full FQN set directly; use for named extend-classes (recommended)
|
|
347
|
+
* 2. @name — full FQN set directly; use for anonymous class expressions where
|
|
348
|
+
* @alias cannot be used (there is no local name to alias)
|
|
349
|
+
* 3. @namespace / fileNamespace — namespace prefix; the ES class name is appended
|
|
350
|
+
*/
|
|
296
351
|
function getFullyQualifiedName(classInfo) {
|
|
297
352
|
if (classInfo.alias) return classInfo.alias;
|
|
298
353
|
if (classInfo.name) return classInfo.name;
|
|
@@ -336,19 +391,7 @@ function getFileBaseNamespace(path, pluginOpts) {
|
|
|
336
391
|
const opts = path.hub.file.opts;
|
|
337
392
|
const filename = _path.default.resolve(opts.filename);
|
|
338
393
|
const sourceRoot = opts.sourceRoot ? _path.default.resolve(process.cwd(), opts.sourceRoot) : process.cwd();
|
|
339
|
-
|
|
340
|
-
const filenameRelative = _path.default.relative(sourceRoot, filename);
|
|
341
|
-
const {
|
|
342
|
-
dir
|
|
343
|
-
} = _path.default.parse(filenameRelative);
|
|
344
|
-
const namespaceParts = dir.split(_path.default.sep);
|
|
345
|
-
if (pluginOpts.namespacePrefix) {
|
|
346
|
-
namespaceParts.unshift(pluginOpts.namespacePrefix);
|
|
347
|
-
}
|
|
348
|
-
return namespaceParts.join(".");
|
|
349
|
-
} else {
|
|
350
|
-
return undefined;
|
|
351
|
-
}
|
|
394
|
+
return (0, _imports.deriveNamespaceForFile)(filename, sourceRoot, pluginOpts);
|
|
352
395
|
}
|
|
353
396
|
const getQualifiedName = node => {
|
|
354
397
|
let {
|
|
@@ -4,6 +4,17 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.getDecoratorClassInfo = getDecoratorClassInfo;
|
|
7
|
+
// Tags that provide a value for the class info via decorators.
|
|
8
|
+
// The naming tags follow the same conventions as their JSDoc equivalents:
|
|
9
|
+
// @alias — recommended for named UI5 extend-classes; sets the fully-qualified
|
|
10
|
+
// class name passed to .extend().
|
|
11
|
+
// @name — use for anonymous (unnamed) class expressions where @alias cannot
|
|
12
|
+
// be used.
|
|
13
|
+
// @namespace — sets the namespace prefix; the ES class name is appended automatically
|
|
14
|
+
// to form the full FQN passed to .extend().
|
|
15
|
+
// The following two are parsed but their values are not used by the class transformer;
|
|
16
|
+
// metadata and renderer are handled via static class properties instead:
|
|
17
|
+
// @metadata, @renderer
|
|
7
18
|
const classInfoValueTags = ["alias", "name", "namespace", "metadata", "renderer"];
|
|
8
19
|
const classInfoBoolTags = ["nonUI5"];
|
|
9
20
|
function getDecoratorClassInfo(node) {
|
|
@@ -31,10 +42,21 @@ function groupByName(decorators) {
|
|
|
31
42
|
return decorators.reduce((accumulator, decorator) => {
|
|
32
43
|
const expression = decorator.expression;
|
|
33
44
|
const name = expression.name || expression.callee && expression.callee.name;
|
|
45
|
+
// Skip decorators with no resolvable name — e.g. member-expression decorators
|
|
46
|
+
// like `@foo.bar` / `@foo.bar()` (callee is a MemberExpression, so .name is
|
|
47
|
+
// undefined). These are never one of the recognised UI5 tags, and indexing with
|
|
48
|
+
// an undefined name would throw on .toLowerCase().
|
|
49
|
+
if (!name) {
|
|
50
|
+
return accumulator;
|
|
51
|
+
}
|
|
34
52
|
accumulator[name.toLowerCase()] = decorator;
|
|
35
53
|
return accumulator;
|
|
36
54
|
}, {});
|
|
37
55
|
}
|
|
38
56
|
function getDecoratorValue(decorator) {
|
|
39
|
-
|
|
57
|
+
// A recognised value tag may be used without a call (e.g. `@alias` instead of
|
|
58
|
+
// `@alias("x")`). In that case decorator.expression is an Identifier with no
|
|
59
|
+
// `.arguments`, so guard before indexing to avoid a crash.
|
|
60
|
+
const args = decorator && decorator.expression && decorator.expression.arguments || [];
|
|
61
|
+
return (args[0] || {}).value;
|
|
40
62
|
}
|