Skip to content

Commit cbba955

Browse files
committed
Fix observer/observes implementation
1 parent e120812 commit cbba955

File tree

6 files changed

+142
-126
lines changed

6 files changed

+142
-126
lines changed

transforms/helpers/eo-extend-expression.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export default class EOExtendExpression {
9797
templateLayout: false,
9898
off: false,
9999
tagName: false,
100+
observes: false,
100101
unobserves: false,
101102
};
102103
const { properties } = this;
@@ -115,6 +116,7 @@ export default class EOExtendExpression {
115116
specs.templateLayout || prop.decoratorImportSpecs.templateLayout,
116117
off: specs.off || prop.decoratorImportSpecs.off,
117118
tagName: specs.tagName || prop.decoratorImportSpecs.tagName,
119+
observes: specs.observes || prop.decoratorImportSpecs.observes,
118120
unobserves: specs.unobserves || prop.decoratorImportSpecs.unobserves,
119121
};
120122
}

transforms/helpers/eo-prop/private/abstract.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Options } from '../../options';
44
import type { RuntimeData } from '../../runtime-data';
55
import type { DecoratorImportSpecs } from '../../util/index';
66
import {
7+
OBSERVES_DECORATOR_NAME,
78
OFF_DECORATOR_NAME,
89
UNOBSERVES_DECORATOR_NAME,
910
allowObjectLiteralDecorator,
@@ -91,6 +92,7 @@ export default abstract class AbstractEOProp<
9192
templateLayout: false,
9293
off: this.hasOffDecorator,
9394
tagName: false,
95+
observes: this.hasObservesDecorator,
9496
unobserves: this.hasUnobservesDecorator,
9597
};
9698
}
@@ -182,6 +184,10 @@ export default abstract class AbstractEOProp<
182184
return this.hasExistingDecorators || this.hasDecorators;
183185
}
184186

187+
private get hasObservesDecorator(): boolean {
188+
return this.decorators.some((d) => d.name === OBSERVES_DECORATOR_NAME);
189+
}
190+
185191
private get hasUnobservesDecorator(): boolean {
186192
return this.decorators.some((d) => d.name === UNOBSERVES_DECORATOR_NAME);
187193
}

transforms/helpers/import-helper.ts

Lines changed: 57 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,7 @@ import {
77
createEmberDecoratorSpecifiers,
88
createImportDeclaration,
99
} from './transform-helper';
10-
import {
11-
DECORATOR_PATHS,
12-
DECORATOR_PATH_OVERRIDES,
13-
EMBER_DECORATOR_SPECIFIERS,
14-
} from './util/index';
10+
import { EMBER_DECORATOR_SPECIFIERS, PROPS_TO_DECORATORS } from './util/index';
1511
import { assert, defined, isString, verified } from './util/types';
1612

1713
/** Returns true of the specifier is a decorator */
@@ -66,7 +62,7 @@ function getExistingDecoratorImports(
6662
): Array<AST.Path<AST.DecoratorImportDeclaration>> {
6763
const imports: Array<AST.Path<AST.DecoratorImportDeclaration>> = [];
6864

69-
for (const path in Object.fromEntries(DECORATOR_PATHS)) {
65+
for (const path in Object.fromEntries(PROPS_TO_DECORATORS)) {
7066
const decoratorImport = getExistingImportForPath(root, path);
7167
if (decoratorImport) {
7268
imports.push(decoratorImport);
@@ -143,71 +139,60 @@ function getDecoratorPathSpecifiers(
143139
// Extract and process the specifiers
144140
// Construct the map with path as key and value as list of specifiers to import from the path
145141
for (const decoratorImport of existingDecoratorImports) {
146-
const { importPropDecoratorMap, decoratorPath } = defined(
147-
DECORATOR_PATHS.get(
148-
verified(decoratorImport.value.source.value, isString)
149-
)
150-
);
151-
// Decorators to be imported for the path
152-
// These are typically additional decorators which need to be imported for a path
153-
// For example - `@action` decorator
154-
const decoratorsForPath = edPathNameMap.get(decoratorPath) ?? [];
155-
// delete the visited path to avoid duplicate imports
156-
edPathNameMap.delete(decoratorPath);
157-
158-
// Create decorator specifiers for which no existing specifiers present in the current path
159-
// e.g. `actions` need not to be imported but `@action` need to be imported from `@ember-decorators/object`
160-
const decoratedSpecifiers = createEmberDecoratorSpecifiers(
161-
decoratorsForPath,
162-
decoratorsToImport
163-
);
164-
const existingSpecifiers = decoratorImport.value.specifiers ?? [];
165-
166-
// Iterate over existing specifiers for the current path. This is needed
167-
// to pick the only required specifiers from the existing imports
168-
// For example - To pick `observer` from `import { get, set, observer } from "@ember/object"`
169-
for (let i = existingSpecifiers.length - 1; i >= 0; i -= 1) {
170-
const existingSpecifier = defined(existingSpecifiers[i]);
171-
172-
if (isSpecifierDecorator(existingSpecifier, importPropDecoratorMap)) {
173-
// Update decorator local and imported names,
174-
// Needed in case of `observer` which need to be renamed to `@observes`
175-
setSpecifierNames(existingSpecifier, importPropDecoratorMap);
176-
// Check if the decorator import path is overridden
177-
// Needed in case of `observes` which need to be imported from `@ember-decorators/object`
178-
const overriddenPath = DECORATOR_PATH_OVERRIDES.get(
179-
existingSpecifier.imported.name
180-
);
181-
if (overriddenPath) {
182-
decoratorPathSpecifierMap[overriddenPath] = [
183-
...(decoratorPathSpecifierMap[overriddenPath] ?? []),
184-
existingSpecifier,
185-
];
186-
} else {
187-
const isSpecifierPresent = decoratedSpecifiers.some((specifier) => {
142+
const path = verified(decoratorImport.value.source.value, isString);
143+
const infos = defined(PROPS_TO_DECORATORS.get(path));
144+
for (const { decoratorPath, importPropDecoratorMap } of infos) {
145+
// Decorators to be imported for the path
146+
// These are typically additional decorators which need to be imported for a path
147+
// For example - `@action` decorator
148+
const decoratorsForPath = edPathNameMap.get(decoratorPath) ?? [];
149+
// delete the visited path to avoid duplicate imports
150+
edPathNameMap.delete(decoratorPath);
151+
152+
// Create decorator specifiers for which no existing specifiers present in the current path
153+
// e.g. `actions` need not to be imported but `@action` need to be imported from `@ember-decorators/object`
154+
const decoratorSpecifiers = createEmberDecoratorSpecifiers(
155+
decoratorsForPath,
156+
decoratorsToImport
157+
);
158+
159+
const existingSpecifiers = decoratorImport.value.specifiers ?? [];
160+
161+
// Iterate over existing specifiers for the current path. This is needed
162+
// to pick the only required specifiers from the existing imports
163+
// For example - To pick `observer` from `import { get, set, observer } from "@ember/object"`
164+
for (let i = existingSpecifiers.length - 1; i >= 0; i -= 1) {
165+
const existingSpecifier = defined(existingSpecifiers[i]);
166+
167+
if (isSpecifierDecorator(existingSpecifier, importPropDecoratorMap)) {
168+
// Update decorator local and imported names,
169+
// Needed in case of `observer` which need to be renamed to `@observes`
170+
setSpecifierNames(existingSpecifier, importPropDecoratorMap);
171+
172+
const isSpecifierPresent = decoratorSpecifiers.some((specifier) => {
188173
return (
189174
!specifier.local?.name &&
190175
specifier.imported.name === existingSpecifier.imported.name
191176
);
192177
});
193178
if (!isSpecifierPresent) {
194-
decoratedSpecifiers.push(existingSpecifier);
179+
decoratorSpecifiers.push(existingSpecifier);
195180
}
196-
}
197181

198-
// Remove the specifier from the existing import
199-
existingSpecifiers.splice(i, 1);
182+
// Remove the specifier from the existing import
183+
existingSpecifiers.splice(i, 1);
184+
}
200185
}
201-
}
202186

203-
if (decoratedSpecifiers.length > 0) {
204-
decoratorPathSpecifierMap[decoratorPath] = [
205-
...(decoratorPathSpecifierMap[decoratorPath] ?? []),
206-
...decoratedSpecifiers,
207-
];
187+
if (decoratorSpecifiers.length > 0) {
188+
decoratorPathSpecifierMap[decoratorPath] = [
189+
...(decoratorPathSpecifierMap[decoratorPath] ?? []),
190+
...decoratorSpecifiers,
191+
];
208192

209-
if (existingSpecifiers.length <= 0) {
210-
j(decoratorImport).remove();
193+
if (existingSpecifiers.length <= 0) {
194+
j(decoratorImport).remove();
195+
}
211196
}
212197
}
213198
}
@@ -311,22 +296,19 @@ export function getDecoratorImportInfos(
311296
const decoratorImportInfo: DecoratorImportInfoMap = new Map();
312297

313298
for (const decoratorImport of existingDecoratorImports) {
314-
const { importPropDecoratorMap } = defined(
315-
DECORATOR_PATHS.get(
316-
verified(decoratorImport.value.source.value, isString)
317-
)
318-
);
319-
299+
const path = verified(decoratorImport.value.source.value, isString);
300+
const infos = defined(PROPS_TO_DECORATORS.get(path));
320301
const specifiers = decoratorImport.value.specifiers ?? [];
321-
322-
for (const specifier of specifiers) {
323-
if (isSpecifierDecorator(specifier, importPropDecoratorMap)) {
324-
const localName = specifier.local?.name;
325-
assert(localName, 'expected localName');
326-
decoratorImportInfo.set(
327-
localName,
328-
getDecoratorImportInfo(specifier, importPropDecoratorMap)
329-
);
302+
for (const { importPropDecoratorMap } of infos) {
303+
for (const specifier of specifiers) {
304+
if (isSpecifierDecorator(specifier, importPropDecoratorMap)) {
305+
const localName = specifier.local?.name;
306+
assert(localName, 'expected localName');
307+
decoratorImportInfo.set(
308+
localName,
309+
getDecoratorImportInfo(specifier, importPropDecoratorMap)
310+
);
311+
}
330312
}
331313
}
332314
}

transforms/helpers/runtime-data.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const RuntimeDataSchema = z.object({
1010
overriddenActions: z.array(z.string()).default([]),
1111
overriddenProperties: z.array(z.string()).default([]),
1212
unobservedProperties: z.record(z.array(z.string())).default({}),
13+
observerProperties: z.record(z.array(z.string())).default({}),
1314
});
1415

1516
export type RuntimeData = z.infer<typeof RuntimeDataSchema>;

transforms/helpers/transform.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ function _maybeTransformEmberObjects(
6464
templateLayout: false,
6565
off: false,
6666
tagName: false,
67+
observes: false,
6768
unobserves: false,
6869
};
6970

transforms/helpers/util/index.ts

Lines changed: 75 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const LAYOUT_DECORATOR_LOCAL_NAME = 'templateLayout' as const;
1212
export const LAYOUT_DECORATOR_NAME = 'layout' as const;
1313
export const OFF_DECORATOR_NAME = 'off' as const;
1414
export const TAG_NAME_DECORATOR_NAME = 'tagName' as const;
15+
export const OBSERVES_DECORATOR_NAME = 'observes' as const;
1516
export const UNOBSERVES_DECORATOR_NAME = 'unobserves' as const;
1617
export type ATTRIBUTE_BINDINGS_DECORATOR_NAME =
1718
typeof ATTRIBUTE_BINDINGS_DECORATOR_NAME;
@@ -21,7 +22,6 @@ export type CLASS_NAMES_DECORATOR_NAME = typeof CLASS_NAMES_DECORATOR_NAME;
2122
export type LAYOUT_DECORATOR_NAME = typeof LAYOUT_DECORATOR_NAME;
2223
export type TAG_NAME_DECORATOR_NAME = typeof TAG_NAME_DECORATOR_NAME;
2324

24-
const OBSERVES_DECORATOR_NAME = 'observes' as const;
2525
const ON_DECORATOR_NAME = 'on' as const;
2626

2727
interface DecoratorPathInfo {
@@ -38,59 +38,83 @@ export interface DecoratorImportSpecs {
3838
[LAYOUT_DECORATOR_LOCAL_NAME]: boolean;
3939
[OFF_DECORATOR_NAME]: boolean;
4040
[TAG_NAME_DECORATOR_NAME]: boolean;
41+
[OBSERVES_DECORATOR_NAME]: boolean;
4142
[UNOBSERVES_DECORATOR_NAME]: boolean;
4243
}
4344

44-
export const DECORATOR_PATHS: ReadonlyMap<string, DecoratorPathInfo> = new Map([
45-
[
46-
'@ember/object',
47-
{
48-
importPropDecoratorMap: {
49-
observer: OBSERVES_DECORATOR_NAME,
50-
computed: COMPUTED_DECORATOR_NAME,
51-
},
52-
decoratorPath: '@ember/object',
53-
},
54-
],
55-
[
56-
'@ember/object/evented',
57-
{
58-
importPropDecoratorMap: {
59-
on: ON_DECORATOR_NAME,
60-
},
61-
decoratorPath: '@ember-decorators/object',
62-
},
63-
],
64-
[
65-
'@ember/controller',
66-
{
67-
importPropDecoratorMap: {
68-
inject: 'inject',
69-
},
70-
decoratorPath: '@ember/controller',
71-
},
72-
],
73-
[
74-
'@ember/service',
75-
{
76-
importPropDecoratorMap: {
77-
inject: 'inject',
78-
service: 'service',
79-
},
80-
decoratorPath: '@ember/service',
81-
},
82-
],
83-
[
84-
'@ember/object/computed',
85-
{
86-
decoratorPath: '@ember/object/computed',
87-
},
88-
],
89-
]);
90-
91-
export const DECORATOR_PATH_OVERRIDES: ReadonlyMap<string, string> = new Map([
92-
[OBSERVES_DECORATOR_NAME, '@ember-decorators/object'],
93-
]);
45+
export const PROPS_TO_DECORATORS: ReadonlyMap<string, DecoratorPathInfo[]> =
46+
new Map([
47+
[
48+
'@ember/object',
49+
[
50+
{
51+
decoratorPath: '@ember-decorators/object',
52+
importPropDecoratorMap: { observer: OBSERVES_DECORATOR_NAME },
53+
},
54+
{
55+
decoratorPath: '@ember/object',
56+
importPropDecoratorMap: {
57+
[COMPUTED_DECORATOR_NAME]: COMPUTED_DECORATOR_NAME,
58+
},
59+
},
60+
],
61+
],
62+
[
63+
'@ember/object/evented',
64+
[
65+
{
66+
decoratorPath: '@ember-decorators/object',
67+
importPropDecoratorMap: {
68+
[ON_DECORATOR_NAME]: ON_DECORATOR_NAME,
69+
},
70+
},
71+
{
72+
decoratorPath: '@ember-decorators/object',
73+
importPropDecoratorMap: {
74+
[OFF_DECORATOR_NAME]: OFF_DECORATOR_NAME,
75+
},
76+
},
77+
],
78+
],
79+
[
80+
'@ember/controller',
81+
[
82+
{
83+
decoratorPath: '@ember/controller',
84+
importPropDecoratorMap: {
85+
inject: 'inject',
86+
},
87+
},
88+
],
89+
],
90+
[
91+
'@ember/service',
92+
[
93+
{
94+
decoratorPath: '@ember/service',
95+
importPropDecoratorMap: {
96+
// FIXME: service?
97+
inject: 'inject',
98+
},
99+
},
100+
{
101+
decoratorPath: '@ember/service',
102+
importPropDecoratorMap: {
103+
service: 'service',
104+
},
105+
},
106+
],
107+
],
108+
// FIXME: Do we need this?
109+
[
110+
'@ember/object/computed',
111+
[
112+
{
113+
decoratorPath: '@ember/object/computed',
114+
},
115+
],
116+
],
117+
]);
94118

95119
export const CLASS_DECORATOR_NAMES = [
96120
ATTRIBUTE_BINDINGS_DECORATOR_NAME,

0 commit comments

Comments
 (0)