Skip to content

Commit ae48275

Browse files
authored
Merge pull request #227 from github/marks
add marks feature
2 parents df0e263 + 9c68a5d commit ae48275

2 files changed

Lines changed: 193 additions & 0 deletions

File tree

src/mark.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
type PropertyType = 'field' | 'getter' | 'setter' | 'method'
2+
type PropertyDecorator = (proto: object, key: PropertyKey) => void
3+
type GetMarks = (instance: object) => Set<PropertyKey>
4+
export function createMark(validate: (key: PropertyKey, type: PropertyType) => void): [PropertyDecorator, GetMarks] {
5+
const marks = new WeakMap<object, Set<PropertyKey>>()
6+
const sym = Symbol()
7+
function get(proto: object): Set<PropertyKey> {
8+
if (!marks.has(proto)) {
9+
const parent = Object.getPrototypeOf(proto)
10+
marks.set(proto, new Set(marks.get(parent) || []))
11+
}
12+
return marks.get(proto)!
13+
}
14+
const marker = (proto: object, key: PropertyKey, descriptor?: PropertyDescriptor): void => {
15+
if (get(proto).has(key)) return
16+
let type: PropertyType = 'field'
17+
if (descriptor) {
18+
if (typeof descriptor.value === 'function') type = 'method'
19+
if (typeof descriptor.get === 'function') type = 'getter'
20+
if (typeof descriptor.set === 'function') type = 'setter'
21+
}
22+
validate(key, type)
23+
get(proto).add(key)
24+
}
25+
marker.static = sym
26+
27+
return [
28+
marker,
29+
(instance: object): Set<PropertyKey> => {
30+
const proto = Object.getPrototypeOf(instance)
31+
for (const key of proto.constructor[sym] || []) marker(proto, key, Object.getOwnPropertyDescriptor(proto, key))
32+
return new Set(get(proto))
33+
}
34+
]
35+
}

test/mark.ts

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import {expect} from '@open-wc/testing'
2+
import {fake} from 'sinon'
3+
import {createMark} from '../src/mark.js'
4+
5+
describe('createMark', () => {
6+
it('returns a tuple of functions: a mark and getMarks', () => {
7+
const mark = createMark(() => {})
8+
expect(mark).to.be.an('array').with.lengthOf(2)
9+
expect(mark).to.have.property(0).a('function')
10+
expect(mark).to.have.property(1).a('function')
11+
})
12+
13+
it('attaches a `static` unique symbol to the first function', () => {
14+
const mark = createMark(() => {})
15+
expect(mark).to.have.nested.property('0.static').a('symbol')
16+
const otherMark = createMark(() => {})
17+
expect(otherMark).to.have.nested.property('0.static').a('symbol').not.equal(mark[0].static)
18+
})
19+
20+
it('can be added to class fields without errors', () => {
21+
const [mark] = createMark(() => {})
22+
class FooBar {
23+
@mark foo
24+
@mark bar = 1
25+
@mark baz = 'hi'
26+
}
27+
new FooBar()
28+
})
29+
30+
it('can be added to getters or setters without errors', () => {
31+
const [mark] = createMark(() => {})
32+
class FooBar {
33+
@mark get foo() {}
34+
set foo(v) {}
35+
36+
@mark get bar() {}
37+
@mark set baz(v) {}
38+
}
39+
new FooBar()
40+
})
41+
42+
it('can be added to methods without errors', () => {
43+
const [mark] = createMark(() => {})
44+
class Foo {
45+
@mark foo() {}
46+
}
47+
new Foo()
48+
})
49+
50+
it('retrieves all marked fields with the get mark function', () => {
51+
const [mark, getMark] = createMark(() => {})
52+
class FooBar {
53+
@mark foo
54+
@mark bar = 1
55+
@mark baz = 'hi'
56+
@mark get bing() {}
57+
@mark get qux() {}
58+
@mark set quuz(v) {}
59+
@mark set corge(v) {}
60+
@mark grault() {}
61+
}
62+
expect(getMark(new FooBar())).to.eql(new Set(['foo', 'bar', 'baz', 'bing', 'qux', 'quuz', 'corge', 'grault']))
63+
})
64+
65+
it('retrieves marked symbol methods correctly', () => {
66+
const [mark, getMark] = createMark(() => {})
67+
const sym = Symbol('foo')
68+
class FooBar {
69+
@mark [sym]() {}
70+
}
71+
expect(getMark(new FooBar()).has(sym)).to.equal(true)
72+
})
73+
74+
it('retrieves fields declared using the `mark.static` symbol as a static class field', () => {
75+
const [mark, getMark] = createMark(() => {})
76+
class FooBar {
77+
static [mark.static] = ['bar', 'bing', 'quuz', 'grault']
78+
@mark foo
79+
bar = 1
80+
@mark baz = 'hi'
81+
get bing() {}
82+
@mark get qux() {}
83+
set quuz(v) {}
84+
@mark set corge(v) {}
85+
grault() {}
86+
}
87+
const instance = new FooBar()
88+
expect(getMark(instance)).to.eql(new Set(['foo', 'baz', 'qux', 'corge', 'bar', 'bing', 'quuz', 'grault']))
89+
})
90+
91+
it('will not contain duplicates', () => {
92+
const [mark, getMark] = createMark(() => {})
93+
class FooBar {
94+
static [mark.static] = ['bar', 'bing', 'quuz', 'grault']
95+
@mark foo
96+
@mark bar = 1
97+
@mark baz = 'hi'
98+
@mark get bing() {}
99+
@mark get qux() {}
100+
@mark set quuz(v) {}
101+
@mark set corge(v) {}
102+
@mark grault() {}
103+
}
104+
expect(getMark(new FooBar())).to.eql(new Set(['foo', 'bar', 'baz', 'bing', 'qux', 'quuz', 'corge', 'grault']))
105+
})
106+
107+
it('calls the given function for each field, with name and type', () => {
108+
const validate = fake()
109+
const [mark] = createMark(validate)
110+
const sym = Symbol('garply')
111+
class FooBar {
112+
@mark foo
113+
@mark bar = 1
114+
@mark baz = 'hi'
115+
@mark get bing() {}
116+
@mark get qux() {}
117+
@mark set quuz(v) {}
118+
@mark set corge(v) {}
119+
@mark grault() {}
120+
@mark [sym]() {}
121+
}
122+
expect(validate).to.be.calledWith('foo', 'field')
123+
expect(validate).to.be.calledWith('bar', 'field')
124+
expect(validate).to.be.calledWith('baz', 'field')
125+
expect(validate).to.be.calledWith('bing', 'getter')
126+
expect(validate).to.be.calledWith('qux', 'getter')
127+
expect(validate).to.be.calledWith('quuz', 'setter')
128+
expect(validate).to.be.calledWith('corge', 'setter')
129+
expect(validate).to.be.calledWith('grault', 'method')
130+
expect(validate).to.be.calledWith(sym, 'method')
131+
return new FooBar()
132+
})
133+
134+
it('calls the given function for each static defined field once initialized, with name and type', () => {
135+
const validate = fake()
136+
const [mark, getMark] = createMark(validate)
137+
class FooBar {
138+
static [mark.static] = ['foo', 'bar', 'baz', 'bing', 'qux', 'quuz', 'corge', 'grault']
139+
foo
140+
bar = 1
141+
baz = 'hi'
142+
get bing() {}
143+
get qux() {}
144+
set quuz(v) {}
145+
set corge(v) {}
146+
grault() {}
147+
}
148+
getMark(new FooBar())
149+
expect(validate).to.be.calledWith('foo', 'field')
150+
expect(validate).to.be.calledWith('bar', 'field')
151+
expect(validate).to.be.calledWith('baz', 'field')
152+
expect(validate).to.be.calledWith('bing', 'getter')
153+
expect(validate).to.be.calledWith('qux', 'getter')
154+
expect(validate).to.be.calledWith('quuz', 'setter')
155+
expect(validate).to.be.calledWith('corge', 'setter')
156+
expect(validate).to.be.calledWith('grault', 'method')
157+
})
158+
})

0 commit comments

Comments
 (0)