Skip to content

Commit 8421f73

Browse files
committed
Report missing global diagnostics
1 parent 02493de commit 8421f73

File tree

2 files changed

+173
-32
lines changed

2 files changed

+173
-32
lines changed

src/compiler/checker.ts

Lines changed: 144 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5787,6 +5787,90 @@ namespace ts {
57875787
}
57885788
}
57895789

5790+
function getStaticTypeFromTypeNode(node: TypeNode) {
5791+
switch (node.kind) {
5792+
case SyntaxKind.AnyKeyword:
5793+
case SyntaxKind.JSDocAllType:
5794+
case SyntaxKind.JSDocUnknownType:
5795+
return anyType;
5796+
case SyntaxKind.StringKeyword:
5797+
return stringType;
5798+
case SyntaxKind.NumberKeyword:
5799+
return numberType;
5800+
case SyntaxKind.BooleanKeyword:
5801+
return booleanType;
5802+
case SyntaxKind.SymbolKeyword:
5803+
return esSymbolType;
5804+
case SyntaxKind.VoidKeyword:
5805+
return voidType;
5806+
case SyntaxKind.UndefinedKeyword:
5807+
return undefinedType;
5808+
case SyntaxKind.NullKeyword:
5809+
return nullType;
5810+
case SyntaxKind.NeverKeyword:
5811+
return neverType;
5812+
case SyntaxKind.JSDocNullKeyword:
5813+
return nullType;
5814+
case SyntaxKind.JSDocUndefinedKeyword:
5815+
return undefinedType;
5816+
case SyntaxKind.JSDocNeverKeyword:
5817+
return neverType;
5818+
case SyntaxKind.ThisType:
5819+
case SyntaxKind.ThisKeyword:
5820+
return getTypeFromThisTypeNode(node);
5821+
case SyntaxKind.LiteralType:
5822+
return getTypeFromLiteralTypeNode(<LiteralTypeNode>node);
5823+
case SyntaxKind.JSDocLiteralType:
5824+
return getTypeFromLiteralTypeNode((<JSDocLiteralType>node).literal);
5825+
case SyntaxKind.TypeReference:
5826+
case SyntaxKind.JSDocTypeReference:
5827+
return getTypeFromTypeReference(<TypeReferenceNode>node);
5828+
case SyntaxKind.TypePredicate:
5829+
return booleanType;
5830+
case SyntaxKind.ExpressionWithTypeArguments:
5831+
return getTypeFromTypeReference(<ExpressionWithTypeArguments>node);
5832+
case SyntaxKind.TypeQuery:
5833+
return getTypeFromTypeQueryNode(<TypeQueryNode>node);
5834+
case SyntaxKind.ArrayType:
5835+
case SyntaxKind.JSDocArrayType:
5836+
return getTypeFromArrayTypeNode(<ArrayTypeNode>node);
5837+
case SyntaxKind.TupleType:
5838+
return getTypeFromTupleTypeNode(<TupleTypeNode>node);
5839+
case SyntaxKind.UnionType:
5840+
case SyntaxKind.JSDocUnionType:
5841+
return getTypeFromUnionTypeNode(<UnionTypeNode>node, aliasSymbol, aliasTypeArguments);
5842+
case SyntaxKind.IntersectionType:
5843+
return getTypeFromIntersectionTypeNode(<IntersectionTypeNode>node, aliasSymbol, aliasTypeArguments);
5844+
case SyntaxKind.ParenthesizedType:
5845+
case SyntaxKind.JSDocNullableType:
5846+
case SyntaxKind.JSDocNonNullableType:
5847+
case SyntaxKind.JSDocConstructorType:
5848+
case SyntaxKind.JSDocThisType:
5849+
case SyntaxKind.JSDocOptionalType:
5850+
return getTypeFromTypeNode((<ParenthesizedTypeNode | JSDocTypeReferencingNode>node).type);
5851+
case SyntaxKind.JSDocRecordType:
5852+
return getTypeFromTypeNode((node as JSDocRecordType).literal);
5853+
case SyntaxKind.FunctionType:
5854+
case SyntaxKind.ConstructorType:
5855+
case SyntaxKind.TypeLiteral:
5856+
case SyntaxKind.JSDocTypeLiteral:
5857+
case SyntaxKind.JSDocFunctionType:
5858+
return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node, aliasSymbol, aliasTypeArguments);
5859+
// This function assumes that an identifier or qualified name is a type expression
5860+
// Callers should first ensure this by calling isTypeNode
5861+
case SyntaxKind.Identifier:
5862+
case SyntaxKind.QualifiedName:
5863+
const symbol = getSymbolAtLocation(node);
5864+
return symbol && getDeclaredTypeOfSymbol(symbol);
5865+
case SyntaxKind.JSDocTupleType:
5866+
return getTypeFromJSDocTupleType(<JSDocTupleType>node);
5867+
case SyntaxKind.JSDocVariadicType:
5868+
return getTypeFromJSDocVariadicType(<JSDocVariadicType>node);
5869+
default:
5870+
return unknownType;
5871+
}
5872+
}
5873+
57905874
function instantiateList<T>(items: T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): T[] {
57915875
if (items && items.length) {
57925876
const result: T[] = [];
@@ -15181,31 +15265,24 @@ namespace ts {
1518115265
}
1518215266

1518315267
/**
15184-
* Checks the return type of an async function to ensure it is a compatible
15185-
* Promise implementation.
15186-
* @param node The signature to check
15187-
* @param returnType The return type for the function
15188-
* @remarks
15189-
* This checks that an async function has a valid Promise-compatible return type,
15190-
* and returns the *awaited type* of the promise. An async function has a valid
15191-
* Promise-compatible return type if the resolved value of the return type has a
15192-
* construct signature that takes in an `initializer` function that in turn supplies
15193-
* a `resolve` function as one of its arguments and results in an object with a
15194-
* callable `then` signature.
15195-
*/
15268+
* Checks the return type of an async function to ensure it is a compatible
15269+
* Promise implementation.
15270+
*
15271+
* This checks that an async function has a valid Promise-compatible return type,
15272+
* and returns the *awaited type* of the promise. An async function has a valid
15273+
* Promise-compatible return type if the resolved value of the return type has a
15274+
* construct signature that takes in an `initializer` function that in turn supplies
15275+
* a `resolve` function as one of its arguments and results in an object with a
15276+
* callable `then` signature.
15277+
*
15278+
* @param node The signature to check
15279+
*/
1519615280
function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration): Type {
1519715281
if (languageVersion >= ScriptTarget.ES6) {
1519815282
const returnType = getTypeFromTypeNode(node.type);
1519915283
return checkCorrectPromiseType(returnType, node.type, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type);
1520015284
}
1520115285

15202-
const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType();
15203-
if (globalPromiseConstructorLikeType === emptyObjectType) {
15204-
// If we couldn't resolve the global PromiseConstructorLike type we cannot verify
15205-
// compatibility with __awaiter.
15206-
return unknownType;
15207-
}
15208-
1520915286
// As part of our emit for an async function, we will need to emit the entity name of
1521015287
// the return type annotation as an expression. To meet the necessary runtime semantics
1521115288
// for __awaiter, we must also check that the type of the declaration (e.g. the static
@@ -15230,18 +15307,35 @@ namespace ts {
1523015307
// then<U>(...): Promise<U>;
1523115308
// }
1523215309
//
15233-
// When we get the type of the `Promise` symbol here, we get the type of the static
15234-
// side of the `Promise` class, which would be `{ new <T>(...): Promise<T> }`.
15310+
15311+
const promiseName = getEntityNameFromTypeNode(node.type);
15312+
const rootName = getFirstIdentifier(promiseName);
15313+
15314+
// Mark the root symbol as referenced.
15315+
getSymbolLinks(rootName.symbol).referenced = true;
1523515316

1523615317
const promiseType = getTypeFromTypeNode(node.type);
1523715318
if (promiseType === unknownType && compilerOptions.isolatedModules) {
1523815319
// If we are compiling with isolatedModules, we may not be able to resolve the
15239-
// type as a value. As such, we will just return unknownType;
15320+
// type as a value. As such, we will just return unknownType.
1524015321
return unknownType;
1524115322
}
1524215323

15324+
const promiseConstructorType = getStaticTypeFromTypeNode(node.type);
15325+
15326+
const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType();
15327+
if (globalPromiseConstructorLikeType === emptyObjectType) {
15328+
// If we couldn't resolve the global PromiseConstructorLike type we cannot verify
15329+
// compatibility with __awaiter.
15330+
error(node.type || node.name || node, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type);
15331+
return unknownType;
15332+
}
15333+
15334+
// When we get the type of the `Promise` symbol here, we get the type of the static
15335+
// side of the `Promise` class, which would be `{ new <T>(...): Promise<T> }`.
15336+
1524315337
const promiseConstructor = getNodeLinks(node.type).resolvedSymbol;
15244-
if (!promiseConstructor || !symbolIsValue(promiseConstructor)) {
15338+
if (!promiseConstructor) {
1524515339
// try to fall back to global promise type.
1524615340
const typeName = promiseConstructor
1524715341
? symbolToString(promiseConstructor)
@@ -15259,12 +15353,10 @@ namespace ts {
1525915353
}
1526015354

1526115355
// Verify there is no local declaration that could collide with the promise constructor.
15262-
const promiseName = getEntityNameFromTypeNode(node.type);
15263-
const promiseNameOrNamespaceRoot = getFirstIdentifier(promiseName);
15264-
const rootSymbol = getSymbol(node.locals, promiseNameOrNamespaceRoot.text, SymbolFlags.Value);
15356+
const rootSymbol = getSymbol(node.locals, rootName.text, SymbolFlags.Value);
1526515357
if (rootSymbol) {
1526615358
error(rootSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions,
15267-
promiseNameOrNamespaceRoot.text,
15359+
rootName.text,
1526815360
getFullyQualifiedName(promiseConstructor));
1526915361
return unknownType;
1527015362
}
@@ -18086,9 +18178,33 @@ namespace ts {
1808618178
function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] {
1808718179
throwIfNonDiagnosticsProducing();
1808818180
if (sourceFile) {
18181+
// Some global diagnostics are deferred until they are needed and
18182+
// may not be reported in the firt call to getGlobalDiagnostics.
18183+
// We should catch these changes and report them.
18184+
const previousGlobalDiagnostics = diagnostics.getGlobalDiagnostics();
18185+
const previousGlobalDiagnosticsSize = previousGlobalDiagnostics.length;
18186+
1808918187
checkSourceFile(sourceFile);
18090-
return diagnostics.getDiagnostics(sourceFile.fileName);
18188+
18189+
const semanticDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName);
18190+
const currentGlobalDiagnostics = diagnostics.getGlobalDiagnostics();
18191+
if (currentGlobalDiagnostics !== previousGlobalDiagnostics) {
18192+
// If the arrays are not the same reference, new diagnostics were added.
18193+
const deferredGlobalDiagnostics = relativeComplement(previousGlobalDiagnostics, currentGlobalDiagnostics, compareDiagnostics);
18194+
return concatenate(deferredGlobalDiagnostics, semanticDiagnostics);
18195+
}
18196+
else if (previousGlobalDiagnosticsSize === 0 && currentGlobalDiagnostics.length > 0) {
18197+
// If the arrays are the same reference, but the length has changed, a single
18198+
// new diagnostic was added as DiagnosticCollection attempts to reuse the
18199+
// same array.
18200+
return concatenate(currentGlobalDiagnostics, semanticDiagnostics);
18201+
}
18202+
18203+
return semanticDiagnostics;
1809118204
}
18205+
18206+
// Global diagnostics are always added when a file is not provided to
18207+
// getDiagnostics
1809218208
forEach(host.getSourceFiles(), checkSourceFile);
1809318209
return diagnostics.getDiagnostics();
1809418210
}

src/compiler/core.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -400,8 +400,8 @@ namespace ts {
400400
}
401401

402402
export function concatenate<T>(array1: T[], array2: T[]): T[] {
403-
if (!array2 || !array2.length) return array1;
404-
if (!array1 || !array1.length) return array2;
403+
if (isEmptyArray(array2)) return array1;
404+
if (isEmptyArray(array1)) return array2;
405405
return [...array1, ...array2];
406406
}
407407

@@ -443,6 +443,27 @@ namespace ts {
443443
return result || array;
444444
}
445445

446+
/**
447+
* Gets the relative complement of `arrayA` with respect to `b`, returning the elements that
448+
* are not present in `arrayA` but are present in `arrayB`. Assumes both arrays are sorted
449+
* based on the provided comparer.
450+
*/
451+
export function relativeComplement<T>(arrayA: T[] | undefined, arrayB: T[] | undefined, comparer: (x: T, y: T) => Comparison = compareValues, offsetA: number = 0, offsetB: number = 0): T[] | undefined {
452+
if (!arrayB || !arrayA || arrayB.length === 0 || arrayA.length === 0) return arrayB;
453+
const result: T[] = [];
454+
outer: for (; offsetB < arrayB.length; offsetB++) {
455+
inner: for (; offsetA < arrayA.length; offsetA++) {
456+
switch (comparer(arrayB[offsetB], arrayA[offsetA])) {
457+
case Comparison.LessThan: break inner;
458+
case Comparison.EqualTo: continue outer;
459+
case Comparison.GreaterThan: continue inner;
460+
}
461+
}
462+
result.push(arrayB[offsetB]);
463+
}
464+
return result;
465+
}
466+
446467
export function sum(array: any[], prop: string): number {
447468
let result = 0;
448469
for (const v of array) {
@@ -505,12 +526,12 @@ namespace ts {
505526
* @param array A sorted array whose first element must be no larger than number
506527
* @param number The value to be searched for in the array.
507528
*/
508-
export function binarySearch<T>(array: T[], value: T, comparer?: (v1: T, v2: T) => number): number {
529+
export function binarySearch<T>(array: T[], value: T, comparer?: (v1: T, v2: T) => number, offset?: number): number {
509530
if (!array || array.length === 0) {
510531
return -1;
511532
}
512533

513-
let low = 0;
534+
let low = offset || 0;
514535
let high = array.length - 1;
515536
comparer = comparer !== undefined
516537
? comparer
@@ -829,6 +850,10 @@ namespace ts {
829850
return Array.isArray ? Array.isArray(value) : value instanceof Array;
830851
}
831852

853+
export function isEmptyArray(value: any[] | undefined): boolean {
854+
return !value || !value.length;
855+
}
856+
832857
export function memoize<T>(callback: () => T): () => T {
833858
let value: T;
834859
return () => {

0 commit comments

Comments
 (0)