Skip to content

Commit 3ae68fa

Browse files
Merge remote-tracking branch 'origin/master' into release-4.1
2 parents eec3a3e + 98314d7 commit 3ae68fa

26 files changed

+390
-52
lines changed

.github/workflows/sync-branch.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jobs:
2121
- uses: actions/checkout@v2
2222
with:
2323
ref: ${{ github.event.inputs.branch_name || github.event.client_payload.branch_name }}
24+
fetch-depth: 0
2425
# This does a test post-merge and only pushes the result if the test succeeds
2526
# required client_payload members:
2627
# branch_name - the target branch

src/compiler/checker.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5101,7 +5101,9 @@ namespace ts {
51015101
typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, context));
51025102
}
51035103

5104-
const parameters = getExpandedParameters(signature, /*skipUnionExpanding*/ true)[0].map(parameter => symbolToParameterDeclaration(parameter, context, kind === SyntaxKind.Constructor, options?.privateSymbolVisitor, options?.bundledImports));
5104+
const expandedParams = getExpandedParameters(signature, /*skipUnionExpanding*/ true)[0];
5105+
// If the expanded parameter list had a variadic in a non-trailing position, don't expand it
5106+
const parameters = (some(expandedParams, p => p !== expandedParams[expandedParams.length - 1] && !!(getCheckFlags(p) & CheckFlags.RestParameter)) ? signature.parameters : expandedParams).map(parameter => symbolToParameterDeclaration(parameter, context, kind === SyntaxKind.Constructor, options?.privateSymbolVisitor, options?.bundledImports));
51055107
if (signature.thisParameter) {
51065108
const thisParameter = symbolToParameterDeclaration(signature.thisParameter, context);
51075109
parameters.unshift(thisParameter);
@@ -26708,6 +26710,22 @@ namespace ts {
2670826710
}
2670926711
}
2671026712

26713+
function isPromiseResolveArityError(node: CallLikeExpression) {
26714+
if (!isCallExpression(node) || !isIdentifier(node.expression)) return false;
26715+
26716+
const symbol = resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, undefined, undefined, false);
26717+
const decl = symbol?.valueDeclaration;
26718+
if (!decl || !isParameter(decl) || !isFunctionExpressionOrArrowFunction(decl.parent) || !isNewExpression(decl.parent.parent) || !isIdentifier(decl.parent.parent.expression)) {
26719+
return false;
26720+
}
26721+
26722+
const globalPromiseSymbol = getGlobalPromiseConstructorSymbol(/*reportErrors*/ false);
26723+
if (!globalPromiseSymbol) return false;
26724+
26725+
const constructorSymbol = getSymbolAtLocation(decl.parent.parent.expression, /*ignoreErrors*/ true);
26726+
return constructorSymbol === globalPromiseSymbol;
26727+
}
26728+
2671126729
function getArgumentArityError(node: CallLikeExpression, signatures: readonly Signature[], args: readonly Expression[]) {
2671226730
let min = Number.POSITIVE_INFINITY;
2671326731
let max = Number.NEGATIVE_INFINITY;
@@ -26740,9 +26758,15 @@ namespace ts {
2674026758
let spanArray: NodeArray<Node>;
2674126759
let related: DiagnosticWithLocation | undefined;
2674226760

26743-
const error = hasRestParameter || hasSpreadArgument ? hasRestParameter && hasSpreadArgument ? Diagnostics.Expected_at_least_0_arguments_but_got_1_or_more :
26744-
hasRestParameter ? Diagnostics.Expected_at_least_0_arguments_but_got_1 :
26745-
Diagnostics.Expected_0_arguments_but_got_1_or_more : Diagnostics.Expected_0_arguments_but_got_1;
26761+
const error = hasRestParameter || hasSpreadArgument ?
26762+
hasRestParameter && hasSpreadArgument ?
26763+
Diagnostics.Expected_at_least_0_arguments_but_got_1_or_more :
26764+
hasRestParameter ?
26765+
Diagnostics.Expected_at_least_0_arguments_but_got_1 :
26766+
Diagnostics.Expected_0_arguments_but_got_1_or_more :
26767+
paramRange === 1 && argCount === 0 && isPromiseResolveArityError(node) ?
26768+
Diagnostics.Expected_0_arguments_but_got_1_Did_you_forget_to_include_void_in_your_type_argument_to_Promise :
26769+
Diagnostics.Expected_0_arguments_but_got_1;
2674626770

2674726771
if (closestSignature && getMinArgumentCount(closestSignature) > argCount && closestSignature.declaration) {
2674826772
const paramDecl = closestSignature.declaration.parameters[closestSignature.thisParameter ? argCount + 1 : argCount];

src/compiler/diagnosticMessages.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3039,6 +3039,10 @@
30393039
"category": "Error",
30403040
"code": 2793
30413041
},
3042+
"Expected {0} arguments, but got {1}. Did you forget to include 'void' in your type argument to 'Promise'?": {
3043+
"category": "Error",
3044+
"code": 2794
3045+
},
30423046

30433047
"Import declaration '{0}' is using private name '{1}'.": {
30443048
"category": "Error",
@@ -5927,6 +5931,14 @@
59275931
"category": "Message",
59285932
"code": 95142
59295933
},
5934+
"Add 'void' to Promise resolved without a value": {
5935+
"category": "Message",
5936+
"code": 95143
5937+
},
5938+
"Add 'void' to all Promises resolved without a value": {
5939+
"category": "Message",
5940+
"code": 95144
5941+
},
59305942

59315943
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
59325944
"category": "Error",

src/harness/fourslashImpl.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -339,17 +339,23 @@ namespace FourSlash {
339339
this.languageServiceAdapterHost.addScript(fileName, file, /*isRootFile*/ true);
340340
}
341341
});
342-
if (!compilationOptions.noLib) {
343-
this.languageServiceAdapterHost.addScript(Harness.Compiler.defaultLibFileName,
344-
Harness.Compiler.getDefaultLibrarySourceFile()!.text, /*isRootFile*/ false);
345342

346-
compilationOptions.lib?.forEach(fileName => {
343+
if (!compilationOptions.noLib) {
344+
const seen = new Set<string>();
345+
const addSourceFile = (fileName: string) => {
346+
if (seen.has(fileName)) return;
347+
seen.add(fileName);
347348
const libFile = Harness.Compiler.getDefaultLibrarySourceFile(fileName);
348349
ts.Debug.assertIsDefined(libFile, `Could not find lib file '${fileName}'`);
349-
if (libFile) {
350-
this.languageServiceAdapterHost.addScript(fileName, libFile.text, /*isRootFile*/ false);
350+
this.languageServiceAdapterHost.addScript(fileName, libFile.text, /*isRootFile*/ false);
351+
if (!ts.some(libFile.libReferenceDirectives)) return;
352+
for (const directive of libFile.libReferenceDirectives) {
353+
addSourceFile(`lib.${directive.fileName}.d.ts`);
351354
}
352-
});
355+
};
356+
357+
addSourceFile(Harness.Compiler.defaultLibFileName);
358+
compilationOptions.lib?.forEach(addSourceFile);
353359
}
354360
}
355361

@@ -3878,7 +3884,7 @@ namespace FourSlash {
38783884
const testData = parseTestData(absoluteBasePath, content, absoluteFileName);
38793885
const state = new TestState(absoluteFileName, absoluteBasePath, testType, testData);
38803886
const actualFileName = Harness.IO.resolvePath(fileName) || absoluteFileName;
3881-
const output = ts.transpileModule(content, { reportDiagnostics: true, fileName: actualFileName, compilerOptions: { target: ts.ScriptTarget.ES2015, inlineSourceMap: true } });
3887+
const output = ts.transpileModule(content, { reportDiagnostics: true, fileName: actualFileName, compilerOptions: { target: ts.ScriptTarget.ES2015, inlineSourceMap: true, inlineSources: true } });
38823888
if (output.diagnostics!.length > 0) {
38833889
throw new Error(`Syntax error in ${absoluteBasePath}: ${output.diagnostics![0].messageText}`);
38843890
}
@@ -3888,7 +3894,7 @@ namespace FourSlash {
38883894
function runCode(code: string, state: TestState, fileName: string): void {
38893895
// Compile and execute the test
38903896
const generatedFile = ts.changeExtension(fileName, ".js");
3891-
const wrappedCode = `(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, completion, verifyOperationIsCancelled) {${code}\n//# sourceURL=${generatedFile}\n})`;
3897+
const wrappedCode = `(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, completion, verifyOperationIsCancelled) {${code}\n//# sourceURL=${ts.getBaseFileName(generatedFile)}\n})`;
38923898

38933899
type SourceMapSupportModule = typeof import("source-map-support") & {
38943900
// TODO(rbuckton): This is missing from the DT definitions and needs to be added.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
const fixName = "addVoidToPromise";
4+
const fixId = "addVoidToPromise";
5+
const errorCodes = [
6+
Diagnostics.Expected_0_arguments_but_got_1_Did_you_forget_to_include_void_in_your_type_argument_to_Promise.code
7+
];
8+
registerCodeFix({
9+
errorCodes,
10+
fixIds: [fixId],
11+
getCodeActions(context) {
12+
const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span, context.program));
13+
if (changes.length > 0) {
14+
return [createCodeFixAction(fixName, changes, Diagnostics.Add_void_to_Promise_resolved_without_a_value, fixId, Diagnostics.Add_void_to_all_Promises_resolved_without_a_value)];
15+
}
16+
},
17+
getAllCodeActions(context: CodeFixAllContext) {
18+
return codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag, context.program, new Set()));
19+
}
20+
});
21+
22+
function makeChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, span: TextSpan, program: Program, seen?: Set<ParameterDeclaration>) {
23+
const node = getTokenAtPosition(sourceFile, span.start);
24+
if (!isIdentifier(node) || !isCallExpression(node.parent) || node.parent.expression !== node || node.parent.arguments.length !== 0) return;
25+
26+
const checker = program.getTypeChecker();
27+
const symbol = checker.getSymbolAtLocation(node);
28+
29+
// decl should be `new Promise((<decl>) => {})`
30+
const decl = symbol?.valueDeclaration;
31+
if (!decl || !isParameter(decl) || !isNewExpression(decl.parent.parent)) return;
32+
33+
// no need to make this change if we have already seen this parameter.
34+
if (seen?.has(decl)) return;
35+
seen?.add(decl);
36+
37+
const typeArguments = getEffectiveTypeArguments(decl.parent.parent);
38+
if (some(typeArguments)) {
39+
// append ` | void` to type argument
40+
const typeArgument = typeArguments[0];
41+
const needsParens = !isUnionTypeNode(typeArgument) && !isParenthesizedTypeNode(typeArgument) &&
42+
isParenthesizedTypeNode(factory.createUnionTypeNode([typeArgument, factory.createKeywordTypeNode(SyntaxKind.VoidKeyword)]).types[0]);
43+
if (needsParens) {
44+
changes.insertText(sourceFile, typeArgument.pos, "(");
45+
}
46+
changes.insertText(sourceFile, typeArgument.end, needsParens ? ") | void" : " | void");
47+
}
48+
else {
49+
// make sure the Promise is type is untyped (i.e., `unknown`)
50+
const signature = checker.getResolvedSignature(node.parent);
51+
const parameter = signature?.parameters[0];
52+
const parameterType = parameter && checker.getTypeOfSymbolAtLocation(parameter, decl.parent.parent);
53+
if (isInJSFile(decl)) {
54+
if (!parameterType || parameterType.flags & TypeFlags.AnyOrUnknown) {
55+
// give the expression a type
56+
changes.insertText(sourceFile, decl.parent.parent.end, `)`);
57+
changes.insertText(sourceFile, skipTrivia(sourceFile.text, decl.parent.parent.pos), `/** @type {Promise<void>} */(`);
58+
}
59+
}
60+
else {
61+
if (!parameterType || parameterType.flags & TypeFlags.Unknown) {
62+
// add `void` type argument
63+
changes.insertText(sourceFile, decl.parent.parent.expression.end, "<void>");
64+
}
65+
}
66+
}
67+
}
68+
69+
function getEffectiveTypeArguments(node: NewExpression) {
70+
if (isInJSFile(node)) {
71+
if (isParenthesizedExpression(node.parent)) {
72+
const jsDocType = getJSDocTypeTag(node.parent)?.typeExpression.type;
73+
if (jsDocType && isTypeReferenceNode(jsDocType) && isIdentifier(jsDocType.typeName) && idText(jsDocType.typeName) === "Promise") {
74+
return jsDocType.typeArguments;
75+
}
76+
}
77+
}
78+
else {
79+
return node.typeArguments;
80+
}
81+
}
82+
}

src/services/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
"codefixes/splitTypeOnlyImport.ts",
108108
"codefixes/convertConstToLet.ts",
109109
"codefixes/fixExpectedComma.ts",
110+
"codefixes/fixAddVoidToPromise.ts",
110111
"refactors/convertExport.ts",
111112
"refactors/convertImport.ts",
112113
"refactors/convertToOptionalChainExpression.ts",
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//// [declarationEmitTupleRestSignatureLeadingVariadic.ts]
2+
const f = <TFirstArgs extends any[], TLastArg>(...args: [...TFirstArgs, TLastArg]): void => {};
3+
4+
//// [declarationEmitTupleRestSignatureLeadingVariadic.js]
5+
var f = function () {
6+
var args = [];
7+
for (var _i = 0; _i < arguments.length; _i++) {
8+
args[_i] = arguments[_i];
9+
}
10+
};
11+
12+
13+
//// [declarationEmitTupleRestSignatureLeadingVariadic.d.ts]
14+
declare const f: <TFirstArgs extends any[], TLastArg>(...args: [...TFirstArgs, TLastArg]) => void;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
=== tests/cases/compiler/declarationEmitTupleRestSignatureLeadingVariadic.ts ===
2+
const f = <TFirstArgs extends any[], TLastArg>(...args: [...TFirstArgs, TLastArg]): void => {};
3+
>f : Symbol(f, Decl(declarationEmitTupleRestSignatureLeadingVariadic.ts, 0, 5))
4+
>TFirstArgs : Symbol(TFirstArgs, Decl(declarationEmitTupleRestSignatureLeadingVariadic.ts, 0, 11))
5+
>TLastArg : Symbol(TLastArg, Decl(declarationEmitTupleRestSignatureLeadingVariadic.ts, 0, 36))
6+
>args : Symbol(args, Decl(declarationEmitTupleRestSignatureLeadingVariadic.ts, 0, 47))
7+
>TFirstArgs : Symbol(TFirstArgs, Decl(declarationEmitTupleRestSignatureLeadingVariadic.ts, 0, 11))
8+
>TLastArg : Symbol(TLastArg, Decl(declarationEmitTupleRestSignatureLeadingVariadic.ts, 0, 36))
9+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
=== tests/cases/compiler/declarationEmitTupleRestSignatureLeadingVariadic.ts ===
2+
const f = <TFirstArgs extends any[], TLastArg>(...args: [...TFirstArgs, TLastArg]): void => {};
3+
>f : <TFirstArgs extends any[], TLastArg>(...args: [...TFirstArgs, TLastArg]) => void
4+
><TFirstArgs extends any[], TLastArg>(...args: [...TFirstArgs, TLastArg]): void => {} : <TFirstArgs extends any[], TLastArg>(...args: [...TFirstArgs, TLastArg]) => void
5+
>args : [...TFirstArgs, TLastArg]
6+

0 commit comments

Comments
 (0)