|
1 | 1 | import * as ts from "typescript"; |
2 | 2 | import { CompilerOptions } from "../../CompilerOptions"; |
3 | 3 | import { TransformationContext } from "../context"; |
4 | | -import { |
5 | | - AnnotationKind, |
6 | | - getFileAnnotations, |
7 | | - getNodeAnnotations, |
8 | | -} from "./annotations"; |
9 | | -import { |
10 | | - findFirstNodeAbove, |
11 | | - getAllCallSignatures, |
12 | | - inferAssignedType, |
13 | | -} from "./typescript"; |
| 4 | +import { AnnotationKind, getFileAnnotations, getNodeAnnotations } from "./annotations"; |
| 5 | +import { findFirstNodeAbove, getAllCallSignatures, inferAssignedType } from "./typescript"; |
14 | 6 |
|
15 | 7 | export enum ContextType { |
16 | | - None = 0, |
17 | | - Void = 1 << 0, |
18 | | - NonVoid = 1 << 1, |
19 | | - Mixed = Void | NonVoid, |
| 8 | + None = 0, |
| 9 | + Void = 1 << 0, |
| 10 | + NonVoid = 1 << 1, |
| 11 | + Mixed = Void | NonVoid, |
20 | 12 | } |
21 | 13 |
|
22 | 14 | function hasNoSelfAncestor(declaration: ts.Declaration): boolean { |
23 | | - const scopeDeclaration = findFirstNodeAbove( |
24 | | - declaration, |
25 | | - (node): node is ts.SourceFile | ts.ModuleDeclaration => |
26 | | - ts.isSourceFile(node) || ts.isModuleDeclaration(node), |
27 | | - ); |
28 | | - |
29 | | - if (!scopeDeclaration) { |
30 | | - return false; |
31 | | - } else if (ts.isSourceFile(scopeDeclaration)) { |
32 | | - return getFileAnnotations(scopeDeclaration).has( |
33 | | - AnnotationKind.NoSelfInFile, |
| 15 | + const scopeDeclaration = findFirstNodeAbove( |
| 16 | + declaration, |
| 17 | + (node): node is ts.SourceFile | ts.ModuleDeclaration => ts.isSourceFile(node) || ts.isModuleDeclaration(node) |
34 | 18 | ); |
35 | | - } else if (getNodeAnnotations(scopeDeclaration).has(AnnotationKind.NoSelf)) { |
36 | | - return true; |
37 | | - } else { |
38 | | - return hasNoSelfAncestor(scopeDeclaration); |
39 | | - } |
| 19 | + |
| 20 | + if (!scopeDeclaration) { |
| 21 | + return false; |
| 22 | + } else if (ts.isSourceFile(scopeDeclaration)) { |
| 23 | + return getFileAnnotations(scopeDeclaration).has(AnnotationKind.NoSelfInFile); |
| 24 | + } else if (getNodeAnnotations(scopeDeclaration).has(AnnotationKind.NoSelf)) { |
| 25 | + return true; |
| 26 | + } else { |
| 27 | + return hasNoSelfAncestor(scopeDeclaration); |
| 28 | + } |
40 | 29 | } |
41 | 30 |
|
42 | | -function getExplicitThisParameter( |
43 | | - signatureDeclaration: ts.SignatureDeclaration, |
44 | | -): ts.ParameterDeclaration | undefined { |
45 | | - const param = signatureDeclaration.parameters[0]; |
46 | | - if ( |
47 | | - param && ts.isIdentifier(param.name) && |
48 | | - ts.identifierToKeywordKind(param.name) === ts.SyntaxKind.ThisKeyword |
49 | | - ) { |
50 | | - return param; |
51 | | - } |
| 31 | +function getExplicitThisParameter(signatureDeclaration: ts.SignatureDeclaration): ts.ParameterDeclaration | undefined { |
| 32 | + const param = signatureDeclaration.parameters[0]; |
| 33 | + if (param && ts.isIdentifier(param.name) && ts.identifierToKeywordKind(param.name) === ts.SyntaxKind.ThisKeyword) { |
| 34 | + return param; |
| 35 | + } |
52 | 36 | } |
53 | 37 |
|
54 | | -const signatureDeclarationContextTypes = new WeakMap< |
55 | | - ts.SignatureDeclaration, |
56 | | - ContextType |
57 | | ->(); |
| 38 | +const signatureDeclarationContextTypes = new WeakMap<ts.SignatureDeclaration, ContextType>(); |
58 | 39 |
|
59 | 40 | export function getDeclarationContextType( |
60 | | - context: TransformationContext, |
61 | | - signatureDeclaration: ts.SignatureDeclaration, |
| 41 | + context: TransformationContext, |
| 42 | + signatureDeclaration: ts.SignatureDeclaration |
62 | 43 | ): ContextType { |
63 | | - const known = signatureDeclarationContextTypes.get(signatureDeclaration); |
64 | | - if (known !== undefined) return known; |
65 | | - const contextType = computeDeclarationContextType( |
66 | | - context, |
67 | | - signatureDeclaration, |
68 | | - ); |
69 | | - signatureDeclarationContextTypes.set(signatureDeclaration, contextType); |
70 | | - return contextType; |
| 44 | + const known = signatureDeclarationContextTypes.get(signatureDeclaration); |
| 45 | + if (known !== undefined) return known; |
| 46 | + const contextType = computeDeclarationContextType(context, signatureDeclaration); |
| 47 | + signatureDeclarationContextTypes.set(signatureDeclaration, contextType); |
| 48 | + return contextType; |
71 | 49 | } |
72 | 50 |
|
73 | | -function computeDeclarationContextType( |
74 | | - context: TransformationContext, |
75 | | - signatureDeclaration: ts.SignatureDeclaration, |
76 | | -) { |
77 | | - const thisParameter = getExplicitThisParameter(signatureDeclaration); |
78 | | - if (thisParameter) { |
79 | | - // Explicit 'this' |
80 | | - return thisParameter.type && |
81 | | - thisParameter.type.kind === ts.SyntaxKind.VoidKeyword |
82 | | - ? ContextType.Void |
83 | | - : ContextType.NonVoid; |
84 | | - } |
85 | | - |
86 | | - // noSelf declaration on function signature |
87 | | - if (getNodeAnnotations(signatureDeclaration).has(AnnotationKind.NoSelf)) { |
88 | | - return ContextType.Void; |
89 | | - } |
90 | | - |
91 | | - if ( |
92 | | - ts.isMethodSignature(signatureDeclaration) || |
93 | | - ts.isMethodDeclaration(signatureDeclaration) || |
94 | | - ts.isConstructSignatureDeclaration(signatureDeclaration) || |
95 | | - ts.isConstructorDeclaration(signatureDeclaration) || |
96 | | - (signatureDeclaration.parent && |
97 | | - ts.isPropertyDeclaration(signatureDeclaration.parent)) || |
98 | | - (signatureDeclaration.parent && |
99 | | - ts.isPropertySignature(signatureDeclaration.parent)) |
100 | | - ) { |
101 | | - // Class/interface methods only respect @noSelf on their parent |
102 | | - const scopeDeclaration = findFirstNodeAbove( |
103 | | - signatureDeclaration, |
104 | | - (n): n is ts.ClassLikeDeclaration | ts.InterfaceDeclaration => |
105 | | - ts.isClassDeclaration(n) || ts.isClassExpression(n) || |
106 | | - ts.isInterfaceDeclaration(n), |
107 | | - ); |
| 51 | +function computeDeclarationContextType(context: TransformationContext, signatureDeclaration: ts.SignatureDeclaration) { |
| 52 | + const thisParameter = getExplicitThisParameter(signatureDeclaration); |
| 53 | + if (thisParameter) { |
| 54 | + // Explicit 'this' |
| 55 | + return thisParameter.type && thisParameter.type.kind === ts.SyntaxKind.VoidKeyword |
| 56 | + ? ContextType.Void |
| 57 | + : ContextType.NonVoid; |
| 58 | + } |
108 | 59 |
|
109 | | - if ( |
110 | | - scopeDeclaration !== undefined && |
111 | | - getNodeAnnotations(scopeDeclaration).has(AnnotationKind.NoSelf) |
112 | | - ) { |
113 | | - return ContextType.Void; |
| 60 | + // noSelf declaration on function signature |
| 61 | + if (getNodeAnnotations(signatureDeclaration).has(AnnotationKind.NoSelf)) { |
| 62 | + return ContextType.Void; |
114 | 63 | } |
115 | 64 |
|
116 | | - return ContextType.NonVoid; |
117 | | - } |
118 | | - |
119 | | - // When using --noImplicitSelf and the signature is defined in a file targeted by the program apply the @noSelf rule. |
120 | | - const program = context.program; |
121 | | - const options = program.getCompilerOptions() as CompilerOptions; |
122 | | - if (options.noImplicitSelf) { |
123 | | - const sourceFile = program.getSourceFile( |
124 | | - signatureDeclaration.getSourceFile().fileName, |
125 | | - ); |
126 | 65 | if ( |
127 | | - sourceFile !== undefined && |
128 | | - !program.isSourceFileDefaultLibrary(sourceFile) && |
129 | | - !program.isSourceFileFromExternalLibrary(sourceFile) |
| 66 | + ts.isMethodSignature(signatureDeclaration) || |
| 67 | + ts.isMethodDeclaration(signatureDeclaration) || |
| 68 | + ts.isConstructSignatureDeclaration(signatureDeclaration) || |
| 69 | + ts.isConstructorDeclaration(signatureDeclaration) || |
| 70 | + (signatureDeclaration.parent && ts.isPropertyDeclaration(signatureDeclaration.parent)) || |
| 71 | + (signatureDeclaration.parent && ts.isPropertySignature(signatureDeclaration.parent)) |
130 | 72 | ) { |
131 | | - return ContextType.Void; |
| 73 | + // Class/interface methods only respect @noSelf on their parent |
| 74 | + const scopeDeclaration = findFirstNodeAbove( |
| 75 | + signatureDeclaration, |
| 76 | + (n): n is ts.ClassLikeDeclaration | ts.InterfaceDeclaration => |
| 77 | + ts.isClassDeclaration(n) || ts.isClassExpression(n) || ts.isInterfaceDeclaration(n) |
| 78 | + ); |
| 79 | + |
| 80 | + if (scopeDeclaration !== undefined && getNodeAnnotations(scopeDeclaration).has(AnnotationKind.NoSelf)) { |
| 81 | + return ContextType.Void; |
| 82 | + } |
| 83 | + |
| 84 | + return ContextType.NonVoid; |
132 | 85 | } |
133 | | - } |
134 | 86 |
|
135 | | - if (options.disableSelfGlobal) { |
136 | | - return ContextType.Void; |
137 | | - } |
| 87 | + // When using --noImplicitSelf and the signature is defined in a file targeted by the program apply the @noSelf rule. |
| 88 | + const program = context.program; |
| 89 | + const options = program.getCompilerOptions() as CompilerOptions; |
| 90 | + if (options.noImplicitSelf) { |
| 91 | + const sourceFile = program.getSourceFile(signatureDeclaration.getSourceFile().fileName); |
| 92 | + if ( |
| 93 | + sourceFile !== undefined && |
| 94 | + !program.isSourceFileDefaultLibrary(sourceFile) && |
| 95 | + !program.isSourceFileFromExternalLibrary(sourceFile) |
| 96 | + ) { |
| 97 | + return ContextType.Void; |
| 98 | + } |
| 99 | + } |
138 | 100 |
|
139 | | - // Walk up to find @noSelf or @noSelfInFile |
140 | | - if (hasNoSelfAncestor(signatureDeclaration)) { |
141 | | - return ContextType.Void; |
142 | | - } |
| 101 | + if (options.disableSelfGlobal) { |
| 102 | + return ContextType.Void; |
| 103 | + } |
143 | 104 |
|
144 | | - return ContextType.NonVoid; |
| 105 | + // Walk up to find @noSelf or @noSelfInFile |
| 106 | + if (hasNoSelfAncestor(signatureDeclaration)) { |
| 107 | + return ContextType.Void; |
| 108 | + } |
| 109 | + |
| 110 | + return ContextType.NonVoid; |
145 | 111 | } |
146 | 112 |
|
147 | 113 | function reduceContextTypes(contexts: ContextType[]): ContextType { |
148 | | - let type = ContextType.None; |
149 | | - for (const context of contexts) { |
150 | | - type |= context; |
151 | | - if (type === ContextType.Mixed) break; |
152 | | - } |
153 | | - return type; |
| 114 | + let type = ContextType.None; |
| 115 | + for (const context of contexts) { |
| 116 | + type |= context; |
| 117 | + if (type === ContextType.Mixed) break; |
| 118 | + } |
| 119 | + return type; |
154 | 120 | } |
155 | 121 |
|
156 | | -function getSignatureDeclarations( |
157 | | - context: TransformationContext, |
158 | | - signature: ts.Signature, |
159 | | -): ts.SignatureDeclaration[] { |
160 | | - if (signature.compositeSignatures) { |
161 | | - return signature.compositeSignatures.flatMap((s) => |
162 | | - getSignatureDeclarations(context, s) |
163 | | - ); |
164 | | - } |
165 | | - |
166 | | - const signatureDeclaration = signature.getDeclaration(); |
167 | | - if (signatureDeclaration === undefined) { |
168 | | - return []; |
169 | | - } |
170 | | - |
171 | | - let inferredType: ts.Type | undefined; |
172 | | - if ( |
173 | | - ts.isMethodDeclaration(signatureDeclaration) && |
174 | | - ts.isObjectLiteralExpression(signatureDeclaration.parent) && |
175 | | - !getExplicitThisParameter(signatureDeclaration) |
176 | | - ) { |
177 | | - inferredType = context.checker.getContextualTypeForObjectLiteralElement( |
178 | | - signatureDeclaration, |
179 | | - ); |
180 | | - } else if ( |
181 | | - (ts.isFunctionExpression(signatureDeclaration) || |
182 | | - ts.isArrowFunction(signatureDeclaration)) && |
183 | | - !getExplicitThisParameter(signatureDeclaration) |
184 | | - ) { |
185 | | - // Infer type of function expressions/arrow functions |
186 | | - inferredType = inferAssignedType(context, signatureDeclaration); |
187 | | - } |
188 | | - |
189 | | - if (inferredType) { |
190 | | - const inferredSignatures = getAllCallSignatures(inferredType); |
191 | | - if (inferredSignatures.length > 0) { |
192 | | - return inferredSignatures.map((s) => s.getDeclaration()); |
| 122 | +function getSignatureDeclarations(context: TransformationContext, signature: ts.Signature): ts.SignatureDeclaration[] { |
| 123 | + if (signature.compositeSignatures) { |
| 124 | + return signature.compositeSignatures.flatMap(s => getSignatureDeclarations(context, s)); |
| 125 | + } |
| 126 | + |
| 127 | + const signatureDeclaration = signature.getDeclaration(); |
| 128 | + if (signatureDeclaration === undefined) { |
| 129 | + return []; |
193 | 130 | } |
194 | | - } |
195 | 131 |
|
196 | | - return [signatureDeclaration]; |
| 132 | + let inferredType: ts.Type | undefined; |
| 133 | + if ( |
| 134 | + ts.isMethodDeclaration(signatureDeclaration) && |
| 135 | + ts.isObjectLiteralExpression(signatureDeclaration.parent) && |
| 136 | + !getExplicitThisParameter(signatureDeclaration) |
| 137 | + ) { |
| 138 | + inferredType = context.checker.getContextualTypeForObjectLiteralElement(signatureDeclaration); |
| 139 | + } else if ( |
| 140 | + (ts.isFunctionExpression(signatureDeclaration) || ts.isArrowFunction(signatureDeclaration)) && |
| 141 | + !getExplicitThisParameter(signatureDeclaration) |
| 142 | + ) { |
| 143 | + // Infer type of function expressions/arrow functions |
| 144 | + inferredType = inferAssignedType(context, signatureDeclaration); |
| 145 | + } |
| 146 | + |
| 147 | + if (inferredType) { |
| 148 | + const inferredSignatures = getAllCallSignatures(inferredType); |
| 149 | + if (inferredSignatures.length > 0) { |
| 150 | + return inferredSignatures.map(s => s.getDeclaration()); |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + return [signatureDeclaration]; |
197 | 155 | } |
198 | 156 |
|
199 | 157 | const typeContextTypes = new WeakMap<ts.Type, ContextType>(); |
200 | 158 |
|
201 | | -export function getFunctionContextType( |
202 | | - context: TransformationContext, |
203 | | - type: ts.Type, |
204 | | -): ContextType { |
205 | | - const known = typeContextTypes.get(type); |
206 | | - if (known !== undefined) return known; |
207 | | - const contextType = computeFunctionContextType(context, type); |
208 | | - typeContextTypes.set(type, contextType); |
209 | | - return contextType; |
| 159 | +export function getFunctionContextType(context: TransformationContext, type: ts.Type): ContextType { |
| 160 | + const known = typeContextTypes.get(type); |
| 161 | + if (known !== undefined) return known; |
| 162 | + const contextType = computeFunctionContextType(context, type); |
| 163 | + typeContextTypes.set(type, contextType); |
| 164 | + return contextType; |
210 | 165 | } |
211 | 166 |
|
212 | | -function computeFunctionContextType( |
213 | | - context: TransformationContext, |
214 | | - type: ts.Type, |
215 | | -): ContextType { |
216 | | - if (type.isTypeParameter()) { |
217 | | - const constraint = type.getConstraint(); |
218 | | - if (constraint) return getFunctionContextType(context, constraint); |
219 | | - } |
220 | | - |
221 | | - const signatures = context.checker.getSignaturesOfType( |
222 | | - type, |
223 | | - ts.SignatureKind.Call, |
224 | | - ); |
225 | | - if (signatures.length === 0) { |
226 | | - return ContextType.None; |
227 | | - } |
228 | | - |
229 | | - return reduceContextTypes( |
230 | | - signatures.flatMap((s) => getSignatureDeclarations(context, s)).map((s) => |
231 | | - getDeclarationContextType(context, s) |
232 | | - ), |
233 | | - ); |
| 167 | +function computeFunctionContextType(context: TransformationContext, type: ts.Type): ContextType { |
| 168 | + if (type.isTypeParameter()) { |
| 169 | + const constraint = type.getConstraint(); |
| 170 | + if (constraint) return getFunctionContextType(context, constraint); |
| 171 | + } |
| 172 | + |
| 173 | + const signatures = context.checker.getSignaturesOfType(type, ts.SignatureKind.Call); |
| 174 | + if (signatures.length === 0) { |
| 175 | + return ContextType.None; |
| 176 | + } |
| 177 | + |
| 178 | + return reduceContextTypes( |
| 179 | + signatures.flatMap(s => getSignatureDeclarations(context, s)).map(s => getDeclarationContextType(context, s)) |
| 180 | + ); |
234 | 181 | } |
0 commit comments