/
method-decorator.ts
91 lines (79 loc) · 3.94 KB
/
method-decorator.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import * as d from '../../../declarations';
import { augmentDiagnosticWithNode, buildError, buildWarn } from '@utils';
import { convertValueToLiteral, createStaticGetter, getAttributeTypeInfo, isMemberPrivate, serializeSymbol, typeToString, validateReferences } from '../transform-utils';
import { isDecoratorNamed } from './decorator-utils';
import { validatePublicName } from '../reserved-public-members';
import ts from 'typescript';
export const methodDecoratorsToStatic = (
config: d.Config,
diagnostics: d.Diagnostic[],
cmpNode: ts.ClassDeclaration,
decoratedProps: ts.ClassElement[],
typeChecker: ts.TypeChecker,
newMembers: ts.ClassElement[],
) => {
const tsSourceFile = cmpNode.getSourceFile();
const methods = decoratedProps
.filter(ts.isMethodDeclaration)
.map(method => parseMethodDecorator(config, diagnostics, tsSourceFile, typeChecker, method))
.filter(method => !!method);
if (methods.length > 0) {
newMembers.push(createStaticGetter('methods', ts.createObjectLiteral(methods, true)));
}
};
const parseMethodDecorator = (config: d.Config, diagnostics: d.Diagnostic[], tsSourceFile: ts.SourceFile, typeChecker: ts.TypeChecker, method: ts.MethodDeclaration) => {
const methodDecorator = method.decorators.find(isDecoratorNamed('Method'));
if (methodDecorator == null) {
return null;
}
const methodName = method.name.getText();
const flags = ts.TypeFormatFlags.WriteArrowStyleSignature | ts.TypeFormatFlags.NoTruncation;
const signature = typeChecker.getSignatureFromDeclaration(method);
const returnType = typeChecker.getReturnTypeOfSignature(signature);
const returnTypeNode = typeChecker.typeToTypeNode(returnType);
let returnString = typeToString(typeChecker, returnType);
let signatureString = typeChecker.signatureToString(signature, method, flags, ts.SignatureKind.Call);
if (!config._isTesting) {
if (returnString === 'void') {
const warn = buildWarn(diagnostics);
warn.header = '@Method requires async';
warn.messageText = `External @Method() ${methodName}() must return a Promise.\n\n Consider prefixing the method with async, such as @Method() async ${methodName}().`;
augmentDiagnosticWithNode(warn, method.name);
returnString = 'Promise<void>';
signatureString = signatureString.replace(/=> void$/, '=> Promise<void>');
} else if (!isTypePromise(returnString)) {
const err = buildError(diagnostics);
err.header = '@Method requires async';
err.messageText = `External @Method() ${methodName}() must return a Promise.\n\n Consider prefixing the method with async, such as @Method() async ${methodName}().`;
augmentDiagnosticWithNode(err, method.name);
}
}
if (isMemberPrivate(method)) {
const err = buildError(diagnostics);
err.messageText = 'Methods decorated with the @Method() decorator cannot be "private" nor "protected". More info: https://stenciljs.com/docs/methods';
augmentDiagnosticWithNode(err, method.modifiers[0]);
}
// Validate if the method name does not conflict with existing public names
validatePublicName(diagnostics, methodName, '@Method()', 'method', method.name);
const methodMeta: d.ComponentCompilerStaticMethod = {
complexType: {
signature: signatureString,
parameters: signature.parameters.map(symbol => serializeSymbol(typeChecker, symbol)),
references: {
...getAttributeTypeInfo(returnTypeNode, tsSourceFile),
...getAttributeTypeInfo(method, tsSourceFile),
},
return: returnString,
},
docs: {
text: ts.displayPartsToString(signature.getDocumentationComment(typeChecker)),
tags: signature.getJsDocTags(),
},
};
validateReferences(diagnostics, methodMeta.complexType.references, method.type || method.name);
const staticProp = ts.createPropertyAssignment(ts.createLiteral(methodName), convertValueToLiteral(methodMeta));
return staticProp;
};
const isTypePromise = (typeStr: string) => {
return /^Promise<.+>$/.test(typeStr);
};