Skip to content

Commit 580834d

Browse files
authored
Merge branch 'main' into align-jekyll-versions-to-github-pages
2 parents b677612 + 800d7ee commit 580834d

4 files changed

Lines changed: 197 additions & 88 deletions

File tree

docs/_guide/decorators.md

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,51 @@ class HelloWorldElement extends HTMLElement {
3737

3838
Class Field decorators get given the class and the field name so they can add custom functionality to the field. Because they operate on the fields, they must be put on top of or to the left of the field.
3939

40-
#### Supporting `strictPropertyInitialization`
40+
### Method Decorators
41+
42+
Method decorators work just like Field Decorators. Put them on top or on the left of the method, like so:
43+
44+
45+
```js
46+
class HelloWorldElement extends HTMLElement {
47+
48+
@log
49+
submit() {
50+
// ...
51+
}
52+
53+
// Alternative style
54+
55+
@log load() {
56+
// ...
57+
}
58+
59+
}
60+
```
61+
62+
### Getter/Setter
63+
64+
Decorators can also be used over a `get` or `set` field. These work just like Field Decorators, but you can put them over one or both the `get` or `set` field. Some decorators might throw an error if you put them over a `get` field, when they expect to be put over a `set` field:
65+
66+
67+
```js
68+
class HelloWorldElement extends HTMLElement {
69+
70+
@target set something() {
71+
// ...
72+
}
73+
74+
// Can be used over just one field
75+
@attr get data() {
76+
return {}
77+
}
78+
set data() {
79+
80+
}
81+
}
82+
```
83+
84+
### Supporting `strictPropertyInitialization`
4185

4286
TypeScript comes with various "strict" mode settings, one of which is `strictPropertyInitialization` which lets TypeScript catch potential class properties which might not be assigned during construction of a class. This option conflicts with Catalyst's `@target`/`@targets` decorators, which safely do the assignment but TypeScript's simple heuristics cannot detect this. There are two ways to work around this:
4387

@@ -63,28 +107,6 @@ TypeScript comes with various "strict" mode settings, one of which is `strictPro
63107
}
64108
```
65109

66-
### Method Decorators
67-
68-
Catalyst doesn't currently ship with any method decorators, but you might see them in code. They work just like Field Decorators (in fact they're the same thing). Put them on top or on the left of the method, like so:
69-
70-
71-
```js
72-
class HelloWorldElement extends HTMLElement {
73-
74-
@log
75-
submit() {
76-
// ...
77-
}
78-
79-
// Alternative style
80-
81-
@log load() {
82-
// ...
83-
}
84-
85-
}
86-
```
87-
88110
### Function Calling Decorators
89111

90112
You might see some decorators that look like function calls, and that's because they are! Some decorators allow for customisation; calling with additional arguments. Decorators that expect to be called are generally not interchangeable with the non-call variant, a decorators documentation should tell you how to use it.

src/get-property-descriptor.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const getPropertyDescriptor = (instance: unknown, key: PropertyKey): PropertyDescriptor | undefined => {
2+
while (instance) {
3+
const descriptor = Object.getOwnPropertyDescriptor(instance, key)
4+
if (descriptor) return descriptor
5+
instance = Object.getPrototypeOf(instance)
6+
}
7+
}

src/mark.ts

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,67 @@
1+
import {getPropertyDescriptor} from './get-property-descriptor.js'
2+
13
type PropertyType = 'field' | 'getter' | 'setter' | 'method'
2-
interface PropertyDecorator {
4+
export interface PropertyDecorator {
35
(proto: object, key: PropertyKey, descriptor?: PropertyDescriptor): void
46
readonly static: unique symbol
57
}
6-
type GetMarks = (instance: object) => Set<PropertyKey>
7-
export function createMark(validate: (key: PropertyKey, type: PropertyType) => void): [PropertyDecorator, GetMarks] {
8+
type GetMarks<T> = (instance: T) => Set<PropertyKey>
9+
type InitializeMarks<T> = (instance: T) => void
10+
11+
type Context = {
12+
kind: PropertyType
13+
name: PropertyKey
14+
access: PropertyDescriptor
15+
}
16+
17+
const getType = (descriptor?: PropertyDescriptor): PropertyType => {
18+
if (descriptor) {
19+
if (typeof descriptor.value === 'function') return 'method'
20+
if (typeof descriptor.get === 'function') return 'getter'
21+
if (typeof descriptor.set === 'function') return 'setter'
22+
}
23+
return 'field'
24+
}
25+
26+
export function createMark<T extends object>(
27+
validate: (context: {name: PropertyKey; kind: PropertyType}) => void,
28+
initialize: (instance: T, context: Context) => PropertyDescriptor | void
29+
): [PropertyDecorator, GetMarks<T>, InitializeMarks<T>] {
830
const marks = new WeakMap<object, Set<PropertyKey>>()
9-
function get(proto: object): Set<PropertyKey> {
31+
const get = (proto: object): Set<PropertyKey> => {
1032
if (!marks.has(proto)) {
1133
const parent = Object.getPrototypeOf(proto)
12-
marks.set(proto, new Set(marks.get(parent) || []))
34+
marks.set(proto, new Set(parent ? get(parent) || [] : []))
1335
}
1436
return marks.get(proto)!
1537
}
16-
const marker = (proto: object, key: PropertyKey, descriptor?: PropertyDescriptor): void => {
17-
if (get(proto).has(key)) return
18-
let type: PropertyType = 'field'
19-
if (descriptor) {
20-
if (typeof descriptor.value === 'function') type = 'method'
21-
if (typeof descriptor.get === 'function') type = 'getter'
22-
if (typeof descriptor.set === 'function') type = 'setter'
23-
}
24-
validate(key, type)
25-
get(proto).add(key)
38+
const marker = (proto: object, name: PropertyKey, descriptor?: PropertyDescriptor): void => {
39+
if (get(proto).has(name)) return
40+
validate({name, kind: getType(descriptor)})
41+
get(proto).add(name)
2642
}
2743
marker.static = Symbol()
28-
44+
const getMarks = (instance: T): Set<PropertyKey> => {
45+
const proto = Object.getPrototypeOf(instance)
46+
for (const key of proto.constructor[marker.static] || []) {
47+
marker(proto, key, Object.getOwnPropertyDescriptor(proto, key))
48+
}
49+
return new Set(get(proto))
50+
}
2951
return [
3052
marker as PropertyDecorator,
31-
(instance: object): Set<PropertyKey> => {
32-
const proto = Object.getPrototypeOf(instance)
33-
for (const key of proto.constructor[marker.static] || []) {
34-
marker(proto, key, Object.getOwnPropertyDescriptor(proto, key))
53+
getMarks,
54+
(instance: T): void => {
55+
for (const name of getMarks(instance)) {
56+
const access: PropertyDescriptor = getPropertyDescriptor(instance, name) || {
57+
value: void 0,
58+
configurable: true,
59+
writable: true,
60+
enumerable: true
61+
}
62+
const newDescriptor = initialize(instance, {name, kind: getType(access), access}) || access
63+
Object.defineProperty(instance, name, newDescriptor)
3564
}
36-
return new Set(get(proto))
3765
}
3866
]
3967
}

0 commit comments

Comments
 (0)