Skip to content

Commit 7ecb599

Browse files
johnjenkinsJohn Jenkins
and
John Jenkins
authored
feat: prop get set new (#6050)
* chore: updating things * chore: * chore: second pass. Basically working now * chore: tidy up * chore: fixed lazy initial setting * chore: tidy * auto add '@readonly' to jsdoc * chore: more tests.. and ready? * chore: make tests pass * chore: one more test --------- Co-authored-by: John Jenkins <john.jenkins@nanoporetech.com>
1 parent 7bdf128 commit 7ecb599

33 files changed

+842
-77
lines changed

src/compiler/docs/generate-doc-data.ts

+6
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ const getRealProperties = (properties: d.ComponentCompilerProperty[]): d.JsonDoc
204204

205205
optional: member.optional,
206206
required: member.required,
207+
208+
getter: member.getter,
209+
setter: member.setter,
207210
}));
208211
};
209212

@@ -227,6 +230,9 @@ const getVirtualProperties = (virtualProps: d.ComponentCompilerVirtualProperty[]
227230

228231
optional: true,
229232
required: false,
233+
234+
getter: undefined,
235+
setter: undefined,
230236
}));
231237
};
232238

src/compiler/docs/test/markdown-props.spec.ts

+10
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ describe('markdown props', () => {
1515
reflectToAttr: false,
1616
docsTags: [],
1717
values: [],
18+
getter: false,
19+
setter: false,
1820
},
1921
{
2022
name: 'hello',
@@ -28,6 +30,8 @@ describe('markdown props', () => {
2830
reflectToAttr: false,
2931
docsTags: [],
3032
values: [],
33+
getter: false,
34+
setter: false,
3135
},
3236
]).join('\n');
3337
expect(markdown).toEqual(`## Properties
@@ -54,6 +58,8 @@ describe('markdown props', () => {
5458
reflectToAttr: false,
5559
docsTags: [],
5660
values: [],
61+
getter: false,
62+
setter: false,
5763
},
5864
]).join('\n');
5965

@@ -80,6 +86,8 @@ describe('markdown props', () => {
8086
reflectToAttr: false,
8187
docsTags: [],
8288
values: [],
89+
getter: false,
90+
setter: false,
8391
},
8492
]).join('\n');
8593

@@ -106,6 +114,8 @@ describe('markdown props', () => {
106114
reflectToAttr: false,
107115
docsTags: [],
108116
values: [],
117+
getter: false,
118+
setter: false,
109119
},
110120
]).join('\n');
111121

src/compiler/transformers/decorators-to-static/convert-decorators.ts

+13
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,19 @@ const removeStencilMethodDecorators = (
229229
member.type,
230230
member.body,
231231
);
232+
} else if (ts.isGetAccessor(member)) {
233+
return ts.factory.updateGetAccessorDeclaration(
234+
member,
235+
ts.canHaveModifiers(member) ? ts.getModifiers(member) : undefined,
236+
member.name,
237+
member.parameters,
238+
member.type,
239+
member.body,
240+
);
241+
} else if (ts.isSetAccessor(member)) {
242+
const err = buildError(diagnostics);
243+
err.messageText = 'A get accessor should be decorated before a set accessor';
244+
augmentDiagnosticWithNode(err, member);
232245
} else if (ts.isPropertyDeclaration(member)) {
233246
if (shouldInitializeInConstructor(member, importAliasMap)) {
234247
// if the current class member is decorated with either 'State' or

src/compiler/transformers/decorators-to-static/prop-decorator.ts

+56-7
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ export const propDecoratorsToStatic = (
3838
decoratorName: string,
3939
): void => {
4040
const properties = decoratedProps
41-
.filter(ts.isPropertyDeclaration)
42-
.map((prop) => parsePropDecorator(diagnostics, typeChecker, program, prop, decoratorName))
41+
.filter((prop) => ts.isPropertyDeclaration(prop) || ts.isGetAccessor(prop))
42+
.map((prop) => parsePropDecorator(diagnostics, typeChecker, program, prop, decoratorName, newMembers))
4343
.filter((prop): prop is ts.PropertyAssignment => prop != null);
4444

4545
if (properties.length > 0) {
@@ -55,14 +55,16 @@ export const propDecoratorsToStatic = (
5555
* @param program a {@link ts.Program} object
5656
* @param prop the TypeScript `PropertyDeclaration` to parse
5757
* @param decoratorName the name of the decorator to look for
58+
* @param newMembers a collection of parsed `@Prop` annotated class members. Used for `get()` decorated props to find a corresponding `set()`
5859
* @returns a property assignment expression to be added to the Stencil component's class
5960
*/
6061
const parsePropDecorator = (
6162
diagnostics: d.Diagnostic[],
6263
typeChecker: ts.TypeChecker,
6364
program: ts.Program,
64-
prop: ts.PropertyDeclaration,
65+
prop: ts.PropertyDeclaration | ts.GetAccessorDeclaration,
6566
decoratorName: string,
67+
newMembers: ts.ClassElement[],
6668
): ts.PropertyAssignment | null => {
6769
const propDecorator = retrieveTsDecorators(prop)?.find(isDecoratorNamed(decoratorName));
6870
if (propDecorator == null) {
@@ -92,6 +94,7 @@ const parsePropDecorator = (
9294
const symbol = typeChecker.getSymbolAtLocation(prop.name);
9395
const type = typeChecker.getTypeAtLocation(prop);
9496
const typeStr = propTypeFromTSType(type);
97+
const foundSetter = ts.isGetAccessor(prop) ? findSetter(propName, newMembers) : null;
9598

9699
const propMeta: d.ComponentCompilerStaticProperty = {
97100
type: typeStr,
@@ -100,6 +103,8 @@ const parsePropDecorator = (
100103
required: prop.exclamationToken !== undefined && propName !== 'mode',
101104
optional: prop.questionToken !== undefined,
102105
docs: serializeSymbol(typeChecker, symbol),
106+
getter: ts.isGetAccessor(prop),
107+
setter: !!foundSetter,
103108
};
104109

105110
// prop can have an attribute if type is NOT "unknown"
@@ -109,9 +114,30 @@ const parsePropDecorator = (
109114
}
110115

111116
// extract default value
112-
const initializer = prop.initializer;
113-
if (initializer) {
114-
propMeta.defaultValue = initializer.getText();
117+
if (ts.isPropertyDeclaration(prop) && prop.initializer) {
118+
propMeta.defaultValue = prop.initializer.getText();
119+
} else if (ts.isGetAccessorDeclaration(prop)) {
120+
// shallow comb to find default value for a getter
121+
const returnStatement = prop.body?.statements.find((st) => ts.isReturnStatement(st)) as ts.ReturnStatement;
122+
const returnExpression = returnStatement.expression;
123+
124+
if (returnExpression && ts.isLiteralExpression(returnExpression)) {
125+
// the getter has a literal return value
126+
propMeta.defaultValue = returnExpression.getText();
127+
} else if (returnExpression && ts.isPropertyAccessExpression(returnExpression)) {
128+
const nameToFind = returnExpression.name.getText();
129+
const foundProp = findGetProp(nameToFind, newMembers);
130+
131+
if (foundProp && foundProp.initializer) {
132+
propMeta.defaultValue = foundProp.initializer.getText();
133+
134+
if (propMeta.type === 'unknown') {
135+
const type = typeChecker.getTypeAtLocation(foundProp);
136+
propMeta.type = propTypeFromTSType(type);
137+
propMeta.complexType = getComplexType(typeChecker, foundProp, type, program);
138+
}
139+
}
140+
}
115141
}
116142

117143
const staticProp = ts.factory.createPropertyAssignment(
@@ -164,7 +190,7 @@ const getReflect = (diagnostics: d.Diagnostic[], propDecorator: ts.Decorator, pr
164190

165191
const getComplexType = (
166192
typeChecker: ts.TypeChecker,
167-
node: ts.PropertyDeclaration,
193+
node: ts.PropertyDeclaration | ts.GetAccessorDeclaration,
168194
type: ts.Type,
169195
program: ts.Program,
170196
): d.ComponentCompilerPropertyComplexType => {
@@ -293,3 +319,26 @@ const isAny = (t: ts.Type): boolean => {
293319
}
294320
return false;
295321
};
322+
323+
/**
324+
* Attempts to find a `set` member of the class when there is a corresponding getter
325+
* @param propName - the property name of the setter to find
326+
* @param members - all the component class members
327+
* @returns the found typescript AST setter node
328+
*/
329+
const findSetter = (propName: string, members: ts.ClassElement[]): ts.SetAccessorDeclaration | undefined => {
330+
return members.find((m) => ts.isSetAccessor(m) && m.name.getText() === propName) as
331+
| ts.SetAccessorDeclaration
332+
| undefined;
333+
};
334+
335+
/**
336+
* When attempting to find the default value of a decorated `get` prop, if a member like `this.something`
337+
* is returned, this method is used to comb the class members to attempt to get it's default value
338+
* @param propName - the property name of the member to find
339+
* @param members - all the component class members
340+
* @returns the found typescript AST class member
341+
*/
342+
const findGetProp = (propName: string, members: ts.ClassElement[]): ts.PropertyDeclaration | undefined => {
343+
return members.find((m) => ts.isPropertyDeclaration(m) && m.name.getText() === propName) as ts.PropertyDeclaration;
344+
};

src/compiler/transformers/static-to-meta/props.ts

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export const parseStaticProps = (staticMembers: ts.ClassElement[]): d.ComponentC
3737
complexType: val.complexType,
3838
docs: val.docs,
3939
internal: isInternal(val.docs),
40+
getter: !!val.getter,
41+
setter: !!val.setter,
4042
};
4143
});
4244
};

src/compiler/transformers/test/convert-decorators.spec.ts

+6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ describe('convert-decorators', () => {
3333
"required": false,
3434
"optional": false,
3535
"docs": { "tags": [], "text": "" },
36+
"getter": false,
37+
"setter": false,
3638
"attribute": "val",
3739
"reflect": false,
3840
"defaultValue": "\\"initial value\\""
@@ -84,6 +86,8 @@ describe('convert-decorators', () => {
8486
complexType: { original: 'string', resolved: 'string', references: {} },
8587
docs: { tags: [], text: '' },
8688
internal: false,
89+
getter: false,
90+
setter: false,
8791
},
8892
]);
8993
});
@@ -110,6 +114,8 @@ describe('convert-decorators', () => {
110114
complexType: { original: 'string', resolved: 'string', references: {} },
111115
docs: { tags: [], text: '' },
112116
internal: false,
117+
getter: false,
118+
setter: false,
113119
},
114120
]);
115121
});

src/compiler/transformers/test/parse-comments.spec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ describe('parse comments', () => {
5656
reflect: false,
5757
required: false,
5858
type: 'string',
59+
getter: false,
60+
setter: false,
5961
});
6062
expect(t.method).toEqual({
6163
complexType: {

0 commit comments

Comments
 (0)