Skip to content

Commit 620cea5

Browse files
Merge pull request #139 from h-joo/expando-function-fixer
Add an expando function fixer
2 parents 5d5062b + 23e2962 commit 620cea5

65 files changed

Lines changed: 548 additions & 3422 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7220,6 +7220,10 @@
72207220
"category": "Message",
72217221
"code": 90070
72227222
},
7223+
"Annotate types of properties expando function in a namespace": {
7224+
"category": "Message",
7225+
"code": 90071
7226+
},
72237227

72247228
"Convert function to an ES2015 class": {
72257229
"category": "Message",

src/services/codefixes/fixMissingTypeAnnotationOnExports.ts

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
factory,
2727
FileTextChanges,
2828
findAncestor,
29+
FunctionDeclaration,
2930
GeneratedIdentifierFlags,
3031
getEmitScriptTarget,
3132
getSourceFileOfNode,
@@ -48,6 +49,7 @@ import {
4849
isEnumMember,
4950
isExpandoPropertyDeclaration,
5051
isExpression,
52+
isFunctionDeclaration,
5153
isFunctionExpressionOrArrowFunction,
5254
isHeritageClause,
5355
isIdentifier,
@@ -69,6 +71,7 @@ import {
6971
isValueSignatureDeclaration,
7072
isVariableDeclaration,
7173
ModifierFlags,
74+
ModifierLike,
7275
Node,
7376
NodeArray,
7477
NodeBuilderFlags,
@@ -224,6 +227,7 @@ function withChanges<T>(
224227
const scriptTarget = getEmitScriptTarget(program.getCompilerOptions());
225228
const importAdder = createImportAdder(context.sourceFile, context.program, context.preferences, context.host);
226229
const fixedNodes = new Set<Node>();
230+
const expandoPropertiesAdded = new Set<Node>();
227231

228232
const result = cb({ addFullAnnotation, addInlineAnnotation, extractAsVariable });
229233
importAdder.writeFixes(changeTracker);
@@ -234,16 +238,63 @@ function withChanges<T>(
234238

235239
function addFullAnnotation(span: TextSpan) {
236240
const nodeWithDiag = getTokenAtPosition(sourceFile, span.start);
237-
const nodeWithNoType = findNearestParentWithTypeAnnotation(nodeWithDiag) ?? findExpandoFunction(nodeWithDiag);
241+
const expandoFunction = findExpandoFunction(nodeWithDiag);
242+
if (expandoFunction) {
243+
if (isFunctionDeclaration(expandoFunction)) {
244+
return createNamespaceForExpandoProperties(expandoFunction);
245+
}
246+
return fixupForIsolatedDeclarations(expandoFunction);
247+
}
248+
const nodeWithNoType = findNearestParentWithTypeAnnotation(nodeWithDiag)
238249
if (nodeWithNoType) {
239250
return fixupForIsolatedDeclarations(nodeWithNoType);
240251
}
241252
return undefined;
242253
}
243254

255+
function createNamespaceForExpandoProperties(expandoFunc: FunctionDeclaration): DiagnosticOrDiagnosticAndArguments|undefined {
256+
if (expandoPropertiesAdded?.has(expandoFunc)) return undefined;
257+
expandoPropertiesAdded?.add(expandoFunc);
258+
const type = typeChecker.getTypeAtLocation(expandoFunc);
259+
const elements = typeChecker.getPropertiesOfType(type);
260+
if (!expandoFunc.name || elements.length === 0) return undefined;
261+
const newProperties = []
262+
for (const symbol of elements) {
263+
if (!isIdentifierText(symbol.name, program.getCompilerOptions().target)) continue;
264+
// If there's an existing variable declaration for this property - skip.
265+
if (symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration)) continue;
266+
newProperties.push(factory.createVariableStatement(
267+
[factory.createModifier(SyntaxKind.ExportKeyword)],
268+
factory.createVariableDeclarationList(
269+
[factory.createVariableDeclaration(
270+
symbol.name,
271+
/*exclamationToken*/ undefined,
272+
typeToTypeNode(typeChecker.getTypeOfSymbol(symbol), expandoFunc),
273+
/*initializer*/ undefined,
274+
)],
275+
)
276+
));
277+
}
278+
if (newProperties.length === 0) return undefined;
279+
const modifiers: ModifierLike[] = [];
280+
if (expandoFunc.modifiers?.some((modifier)=> modifier.kind === SyntaxKind.ExportKeyword)) {
281+
modifiers.push(factory.createModifier(SyntaxKind.ExportKeyword))
282+
}
283+
modifiers.push(factory.createModifier(SyntaxKind.DeclareKeyword));
284+
const namespace = factory.createModuleDeclaration(
285+
modifiers,
286+
expandoFunc.name,
287+
factory.createModuleBlock(newProperties),
288+
/*flags*/ NodeFlags.Namespace | NodeFlags.ExportContext | NodeFlags.Ambient | NodeFlags.ContextFlags,
289+
);
290+
changeTracker.insertNodeAfter(sourceFile, expandoFunc, namespace);
291+
return [Diagnostics.Annotate_types_of_properties_expando_function_in_a_namespace];
292+
}
293+
244294
function needsParenthesizedExpressionForAssertion(node: Expression) {
245295
return !isEntityNameExpression(node) && !isCallExpression(node) && !isObjectLiteralExpression(node) && !isArrayLiteralExpression(node);
246296
}
297+
247298
function createAsExpression(node: Expression, type: TypeNode) {
248299
if (needsParenthesizedExpressionForAssertion(node)) {
249300
node = factory.createParenthesizedExpression(node);
@@ -290,7 +341,7 @@ function withChanges<T>(
290341
// We can't use typeof un an unique symbol. Would result in either
291342
// const s = Symbol("") as unique symbol
292343
// const s = Symbol("") as typeof s
293-
// both of which are not cirrect
344+
// both of which are not correct
294345
if (type && type.flags & TypeFlags.UniqueESSymbol) {
295346
return undefined;
296347
}
@@ -515,8 +566,13 @@ function withChanges<T>(
515566
const properties = typeChecker.getPropertiesOfType(targetType);
516567
if (some(properties, p => p.valueDeclaration === expandoDeclaration || p.valueDeclaration === expandoDeclaration.parent)) {
517568
const fn = targetType.symbol.valueDeclaration;
518-
if (fn && isFunctionExpressionOrArrowFunction(fn) && isVariableDeclaration(fn.parent)) {
569+
if (fn) {
570+
if (isFunctionExpressionOrArrowFunction(fn) && isVariableDeclaration(fn.parent)) {
519571
return fn.parent;
572+
}
573+
if (isFunctionDeclaration(fn)) {
574+
return fn;
575+
}
520576
}
521577
}
522578
}

tests/baselines/reference/isolated-declarations/auto-fixed/diff/declarationEmitDefaultExportWithStaticAssignment.d.ts.diff

Lines changed: 0 additions & 99 deletions
This file was deleted.
Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,22 @@
1-
// [[Reason: Function declarations are not fixed]] ////
1+
// [[Reason: Cannot name internal type]] ////
22

33
//// [tests/cases/compiler/declarationEmitExpandoPropertyPrivateName.ts] ////
44

55
===================================================================
66
--- TSC declarations
77
+++ DTE declarations
8-
@@ -5,20 +5,29 @@
8+
@@ -5,8 +5,14 @@
99
}
1010
export declare function f(): I;
1111
export {};
1212
//# sourceMappingURL=a.d.ts.map
1313
+//// [b.d.ts]
1414
+export declare function q(): void;
1515
+export declare namespace q {
16-
+ var val: invalid;
16+
+ var val: I;
1717
+}
1818
+//# sourceMappingURL=b.d.ts.map
1919
/// [Errors] ////
2020

21-
b.ts(4,1): error TS4032: Property 'val' of exported interface has or is using name 'I' from private module '"a"'.
22-
+b.ts(4,1): error TS9023: Assigning properties to functions without declaring them is not supported with --isolatedDeclarations. Add an explicit declaration for the properties assigned to this function.
23-
24-
25-
==== a.ts (0 errors) ====
26-
interface I {}
27-
export function f(): I { return null as I; }
28-
-==== b.ts (1 errors) ====
29-
+==== b.ts (2 errors) ====
30-
import {f} from "./a";
31-
32-
export function q(): void {}
33-
q.val = f();
34-
~~~~~
35-
!!! error TS4032: Property 'val' of exported interface has or is using name 'I' from private module '"a"'.
36-
+ ~~~~~
37-
+!!! error TS9023: Assigning properties to functions without declaring them is not supported with --isolatedDeclarations. Add an explicit declaration for the properties assigned to this function.
38-
39-
\ No newline at end of file
21+
b.ts(5,14): error TS2304: Cannot find name 'I'.
22+
b.ts(5,14): error TS4025: Exported variable 'val' has or is using private name 'I'.

tests/baselines/reference/isolated-declarations/auto-fixed/diff/declarationEmitFunctionDuplicateNamespace.d.ts.diff

Lines changed: 0 additions & 35 deletions
This file was deleted.

tests/baselines/reference/isolated-declarations/auto-fixed/diff/declarationEmitFunctionKeywordProp.d.ts.diff

Lines changed: 0 additions & 65 deletions
This file was deleted.

0 commit comments

Comments
 (0)