From 6d1b6987a7a1f84c37bcc9882e659b93c46a7b15 Mon Sep 17 00:00:00 2001 From: Titian Cernicova-Dragomir Date: Wed, 22 May 2024 19:04:43 +0100 Subject: [PATCH 1/3] Error if type node uses inaccessible type in isolated declarations --- src/compiler/checker.ts | 6 +++ src/compiler/diagnosticMessages.json | 4 ++ .../transformers/declarations/diagnostics.ts | 11 +++++ .../fixMissingTypeAnnotationOnExports.ts | 4 +- .../transpile/declarationNotInScopeTypes.d.ts | 47 ++++++++++++++++++ .../transpile/declarationNotInScopeTypes.js | 27 +++++++++++ ...gTypeAnnotationOnExports49-private-name.ts | 48 +++++++++++++++++++ .../transpile/declarationNotInScopeTypes.ts | 17 +++++++ 8 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/transpile/declarationNotInScopeTypes.d.ts create mode 100644 tests/baselines/reference/transpile/declarationNotInScopeTypes.js create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports49-private-name.ts create mode 100644 tests/cases/transpile/declarationNotInScopeTypes.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a4020504f9c07..76a44aaf83716 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6079,6 +6079,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } } + context.tracker.reportInferenceFallback(existing); return undefined; } @@ -8295,6 +8296,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // If the symbol is found both in declaration scope and in current scope then it shoudl point to the same reference (symAtLocation && sym && !getSymbolIfSameReference(getExportSymbolOfValueSymbolIfExported(symAtLocation), sym)) ) { + // In isolated declaration we will not do rest parameter expansion so there is no need to report on these. + if (symAtLocation !== unknownSymbol) { + context.tracker.reportInferenceFallback(node); + } introducesError = true; return { introducesError, node, sym }; } @@ -8315,6 +8320,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { !isDeclarationName(node) && isSymbolAccessible(sym, context.enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible ) { + context.tracker.reportInferenceFallback(node); introducesError = true; } else { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 725a6b73aa450..fe52ff352a578 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -7014,6 +7014,10 @@ "category": "Error", "code": 9037 }, + "Type containing private name '{0}' can't be used with --isolatedDeclarations.": { + "category": "Error", + "code": 9038 + }, "JSX attributes must only be assigned a non-empty 'expression'.": { "category": "Error", "code": 17000 diff --git a/src/compiler/transformers/declarations/diagnostics.ts b/src/compiler/transformers/declarations/diagnostics.ts index 478eeb3aa26a7..a7c859fb31af2 100644 --- a/src/compiler/transformers/declarations/diagnostics.ts +++ b/src/compiler/transformers/declarations/diagnostics.ts @@ -19,6 +19,7 @@ import { DiagnosticWithLocation, ElementAccessExpression, EmitResolver, + EntityNameOrEntityNameExpression, ExportAssignment, Expression, ExpressionWithTypeArguments, @@ -40,6 +41,8 @@ import { isConstructorDeclaration, isConstructSignatureDeclaration, isElementAccessExpression, + isEntityName, + isEntityNameExpression, isExportAssignment, isExpressionWithTypeArguments, isFunctionDeclaration, @@ -53,6 +56,7 @@ import { isParameter, isParameterPropertyDeclaration, isParenthesizedExpression, + isPartOfTypeNode, isPropertyAccessExpression, isPropertyDeclaration, isPropertySignature, @@ -62,6 +66,7 @@ import { isTypeAliasDeclaration, isTypeAssertionExpression, isTypeParameterDeclaration, + isTypeQueryNode, isVariableDeclaration, JSDocCallbackTag, JSDocEnumTag, @@ -658,6 +663,9 @@ export function createGetIsolatedDeclarationErrors(resolver: EmitResolver) { if (heritageClause) { return createDiagnosticForNode(node, Diagnostics.Extends_clause_can_t_contain_an_expression_with_isolatedDeclarations); } + if ((isPartOfTypeNode(node) || isTypeQueryNode(node.parent)) && (isEntityName(node) || isEntityNameExpression(node))) { + return createEntityInTypeNodeError(node); + } Debug.type(node); switch (node.kind) { case SyntaxKind.GetAccessor: @@ -768,6 +776,9 @@ export function createGetIsolatedDeclarationErrors(resolver: EmitResolver) { function createClassExpressionError(node: Expression) { return createExpressionError(node, Diagnostics.Inference_from_class_expressions_is_not_supported_with_isolatedDeclarations); } + function createEntityInTypeNodeError(node: EntityNameOrEntityNameExpression) { + return createDiagnosticForNode(node, Diagnostics.Type_containing_private_name_0_can_t_be_used_with_isolatedDeclarations, getTextOfNode(node, /*includeTrivia*/ false)); + } function createExpressionError(node: Expression, diagnosticMessage?: DiagnosticMessage) { const parentDeclaration = findNearestDeclaration(node); let diag: DiagnosticWithLocation; diff --git a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts index 42e0d2141126c..b0856de9a81c6 100644 --- a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts +++ b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts @@ -65,6 +65,7 @@ import { isSpreadAssignment, isSpreadElement, isStatement, + isTypeNode, isValueSignatureDeclaration, isVariableDeclaration, ModifierFlags, @@ -131,6 +132,7 @@ const errorCodes = [ Diagnostics.Only_const_arrays_can_be_inferred_with_isolatedDeclarations.code, Diagnostics.Assigning_properties_to_functions_without_declaring_them_is_not_supported_with_isolatedDeclarations_Add_an_explicit_declaration_for_the_properties_assigned_to_this_function.code, Diagnostics.Declaration_emit_for_this_parameter_requires_implicitly_adding_undefined_to_it_s_type_This_is_not_supported_with_isolatedDeclarations.code, + Diagnostics.Type_containing_private_name_0_can_t_be_used_with_isolatedDeclarations.code, Diagnostics.Add_satisfies_and_a_type_assertion_to_this_expression_satisfies_T_as_T_to_make_the_type_explicit.code, ]; @@ -351,7 +353,7 @@ function withContext( return undefined; } // No support for typeof in extends clauses - if (isExpressionTarget && findAncestor(targetNode, isHeritageClause)) { + if (isExpressionTarget && (findAncestor(targetNode, isHeritageClause) || findAncestor(targetNode, isTypeNode))) { return undefined; } // Can't inline type spread elements. Whatever you do isolated declarations will not infer from them diff --git a/tests/baselines/reference/transpile/declarationNotInScopeTypes.d.ts b/tests/baselines/reference/transpile/declarationNotInScopeTypes.d.ts new file mode 100644 index 0000000000000..226e9dd61437d --- /dev/null +++ b/tests/baselines/reference/transpile/declarationNotInScopeTypes.d.ts @@ -0,0 +1,47 @@ +//// [variables.ts] //// +const x = ""; +export function one() { + return {} as typeof x; +} + +export function two() { + const y = ""; + return {} as typeof y; +} + +export function three() { + type Z = string; + return {} as Z; +} +//// [variables.d.ts] //// +declare const x = ""; +export declare function one(): typeof x; +export declare function two(): ""; +export declare function three(): string; +export {}; + + +//// [Diagnostics reported] +variables.ts(8,25): error TS9013: Expression type can't be inferred with --isolatedDeclarations. +variables.ts(13,18): error TS9013: Expression type can't be inferred with --isolatedDeclarations. + + +==== variables.ts (2 errors) ==== + const x = ""; + export function one() { + return {} as typeof x; + } + + export function two() { + const y = ""; + return {} as typeof y; + ~ +!!! error TS9013: Expression type can't be inferred with --isolatedDeclarations. + } + + export function three() { + type Z = string; + return {} as Z; + ~ +!!! error TS9013: Expression type can't be inferred with --isolatedDeclarations. + } diff --git a/tests/baselines/reference/transpile/declarationNotInScopeTypes.js b/tests/baselines/reference/transpile/declarationNotInScopeTypes.js new file mode 100644 index 0000000000000..0cdd7d39fd145 --- /dev/null +++ b/tests/baselines/reference/transpile/declarationNotInScopeTypes.js @@ -0,0 +1,27 @@ +//// [variables.ts] //// +const x = ""; +export function one() { + return {} as typeof x; +} + +export function two() { + const y = ""; + return {} as typeof y; +} + +export function three() { + type Z = string; + return {} as Z; +} +//// [variables.js] //// +const x = ""; +export function one() { + return {}; +} +export function two() { + const y = ""; + return {}; +} +export function three() { + return {}; +} diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports49-private-name.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports49-private-name.ts new file mode 100644 index 0000000000000..72403ecf4a713 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports49-private-name.ts @@ -0,0 +1,48 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @moduleResolution: node +// @target: es2018 +// @jsx: react-jsx + +////export function two() { +//// const y = ""; +//// return {} as typeof y; +////} +//// +////export function three() { +//// type Z = string; +//// return {} as Z; +////} + +verify.codeFix({ + description: "Add return type '\"\"'", + index: 0, + newFileContent: +`export function two(): "" { + const y = ""; + return {} as typeof y; +} + +export function three() { + type Z = string; + return {} as Z; +}`, +}); + + +verify.codeFix({ + description: "Add return type 'string'", + index: 1, + newFileContent: +`export function two() { + const y = ""; + return {} as typeof y; +} + +export function three(): string { + type Z = string; + return {} as Z; +}`, +}); \ No newline at end of file diff --git a/tests/cases/transpile/declarationNotInScopeTypes.ts b/tests/cases/transpile/declarationNotInScopeTypes.ts new file mode 100644 index 0000000000000..9d407bd9dd716 --- /dev/null +++ b/tests/cases/transpile/declarationNotInScopeTypes.ts @@ -0,0 +1,17 @@ +// @declaration: true +// @target: es6 +// @filename: variables.ts +const x = ""; +export function one() { + return {} as typeof x; +} + +export function two() { + const y = ""; + return {} as typeof y; +} + +export function three() { + type Z = string; + return {} as Z; +} \ No newline at end of file From b8447e29cc7c0b5380e0f925d5499385ac61c7c8 Mon Sep 17 00:00:00 2001 From: Titian Cernicova-Dragomir Date: Wed, 22 May 2024 19:11:26 +0100 Subject: [PATCH 2/3] Added updated baseline. --- .../reference/transpile/declarationNotInScopeTypes.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/baselines/reference/transpile/declarationNotInScopeTypes.d.ts b/tests/baselines/reference/transpile/declarationNotInScopeTypes.d.ts index 226e9dd61437d..b4d2b68769df5 100644 --- a/tests/baselines/reference/transpile/declarationNotInScopeTypes.d.ts +++ b/tests/baselines/reference/transpile/declarationNotInScopeTypes.d.ts @@ -22,8 +22,8 @@ export {}; //// [Diagnostics reported] -variables.ts(8,25): error TS9013: Expression type can't be inferred with --isolatedDeclarations. -variables.ts(13,18): error TS9013: Expression type can't be inferred with --isolatedDeclarations. +variables.ts(8,25): error TS9038: Type containing private name 'y' can't be used with --isolatedDeclarations. +variables.ts(13,18): error TS9038: Type containing private name 'Z' can't be used with --isolatedDeclarations. ==== variables.ts (2 errors) ==== @@ -36,12 +36,12 @@ variables.ts(13,18): error TS9013: Expression type can't be inferred with --isol const y = ""; return {} as typeof y; ~ -!!! error TS9013: Expression type can't be inferred with --isolatedDeclarations. +!!! error TS9038: Type containing private name 'y' can't be used with --isolatedDeclarations. } export function three() { type Z = string; return {} as Z; ~ -!!! error TS9013: Expression type can't be inferred with --isolatedDeclarations. +!!! error TS9038: Type containing private name 'Z' can't be used with --isolatedDeclarations. } From 728cdce50cbf5ad07032f91e023e5db05a22db6e Mon Sep 17 00:00:00 2001 From: Titian Cernicova-Dragomir Date: Wed, 22 May 2024 23:20:32 +0100 Subject: [PATCH 3/3] Added related spans for return type diagnostics. --- .../transformers/declarations/diagnostics.ts | 43 +++++++++++-------- .../transpile/declarationNotInScopeTypes.d.ts | 2 + 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/compiler/transformers/declarations/diagnostics.ts b/src/compiler/transformers/declarations/diagnostics.ts index a7c859fb31af2..48e2b472d58a9 100644 --- a/src/compiler/transformers/declarations/diagnostics.ts +++ b/src/compiler/transformers/declarations/diagnostics.ts @@ -26,6 +26,7 @@ import { findAncestor, FunctionDeclaration, FunctionExpression, + FunctionLikeDeclaration, GetAccessorDeclaration, getAllAccessorDeclarations, getNameOfDeclaration, @@ -46,6 +47,7 @@ import { isExportAssignment, isExpressionWithTypeArguments, isFunctionDeclaration, + isFunctionLikeDeclaration, isGetAccessor, isHeritageClause, isImportEqualsDeclaration, @@ -60,6 +62,7 @@ import { isPropertyAccessExpression, isPropertyDeclaration, isPropertySignature, + isReturnStatement, isSetAccessor, isStatement, isStatic, @@ -702,8 +705,15 @@ export function createGetIsolatedDeclarationErrors(resolver: EmitResolver) { } function findNearestDeclaration(node: Node) { - const result = findAncestor(node, n => isExportAssignment(n) || (isStatement(n) ? "quit" : isVariableDeclaration(n) || isPropertyDeclaration(n) || isParameter(n))); - return result as VariableDeclaration | PropertyDeclaration | ParameterDeclaration | ExportAssignment | undefined; + const result = findAncestor(node, n => isExportAssignment(n) || isStatement(n) || isVariableDeclaration(n) || isPropertyDeclaration(n) || isParameter(n)); + if (!result) return undefined; + + if (isExportAssignment(result)) return result; + + if (isReturnStatement(result)) { + return findAncestor(result, (n): n is Exclude => isFunctionLikeDeclaration(n) && !isConstructorDeclaration(n)); + } + return (isStatement(result) ? undefined : result) as VariableDeclaration | PropertyDeclaration | ParameterDeclaration | ExportAssignment | undefined; } function createAccessorTypeError(node: GetAccessorDeclaration | SetAccessorDeclaration) { @@ -720,31 +730,27 @@ export function createGetIsolatedDeclarationErrors(resolver: EmitResolver) { } return diag; } - function createObjectLiteralError(node: ShorthandPropertyAssignment | SpreadAssignment | ComputedPropertyName) { - const diag = createDiagnosticForNode(node, errorByDeclarationKind[node.kind]); + function addParentDeclarationRelatedInfo(node: Node, diag: DiagnosticWithLocation) { const parentDeclaration = findNearestDeclaration(node); if (parentDeclaration) { - const targetStr = isExportAssignment(parentDeclaration) ? "" : getTextOfNode(parentDeclaration.name, /*includeTrivia*/ false); + const targetStr = isExportAssignment(parentDeclaration) || !parentDeclaration.name ? "" : getTextOfNode(parentDeclaration.name, /*includeTrivia*/ false); addRelatedInfo(diag, createDiagnosticForNode(parentDeclaration, relatedSuggestionByDeclarationKind[parentDeclaration.kind], targetStr)); } return diag; } + function createObjectLiteralError(node: ShorthandPropertyAssignment | SpreadAssignment | ComputedPropertyName) { + const diag = createDiagnosticForNode(node, errorByDeclarationKind[node.kind]); + addParentDeclarationRelatedInfo(node, diag); + return diag; + } function createArrayLiteralError(node: ArrayLiteralExpression | SpreadElement) { const diag = createDiagnosticForNode(node, errorByDeclarationKind[node.kind]); - const parentDeclaration = findNearestDeclaration(node); - if (parentDeclaration) { - const targetStr = isExportAssignment(parentDeclaration) ? "" : getTextOfNode(parentDeclaration.name, /*includeTrivia*/ false); - addRelatedInfo(diag, createDiagnosticForNode(parentDeclaration, relatedSuggestionByDeclarationKind[parentDeclaration.kind], targetStr)); - } + addParentDeclarationRelatedInfo(node, diag); return diag; } function createReturnTypeError(node: FunctionDeclaration | FunctionExpression | ArrowFunction | MethodDeclaration | ConstructSignatureDeclaration) { const diag = createDiagnosticForNode(node, errorByDeclarationKind[node.kind]); - const parentDeclaration = findNearestDeclaration(node); - if (parentDeclaration) { - const targetStr = isExportAssignment(parentDeclaration) ? "" : getTextOfNode(parentDeclaration.name, /*includeTrivia*/ false); - addRelatedInfo(diag, createDiagnosticForNode(parentDeclaration, relatedSuggestionByDeclarationKind[parentDeclaration.kind], targetStr)); - } + addParentDeclarationRelatedInfo(node, diag); addRelatedInfo(diag, createDiagnosticForNode(node, relatedSuggestionByDeclarationKind[node.kind])); return diag; } @@ -777,14 +783,17 @@ export function createGetIsolatedDeclarationErrors(resolver: EmitResolver) { return createExpressionError(node, Diagnostics.Inference_from_class_expressions_is_not_supported_with_isolatedDeclarations); } function createEntityInTypeNodeError(node: EntityNameOrEntityNameExpression) { - return createDiagnosticForNode(node, Diagnostics.Type_containing_private_name_0_can_t_be_used_with_isolatedDeclarations, getTextOfNode(node, /*includeTrivia*/ false)); + const diag = createDiagnosticForNode(node, Diagnostics.Type_containing_private_name_0_can_t_be_used_with_isolatedDeclarations, getTextOfNode(node, /*includeTrivia*/ false)); + addParentDeclarationRelatedInfo(node, diag); + return diag; } function createExpressionError(node: Expression, diagnosticMessage?: DiagnosticMessage) { const parentDeclaration = findNearestDeclaration(node); let diag: DiagnosticWithLocation; if (parentDeclaration) { - const targetStr = isExportAssignment(parentDeclaration) ? "" : getTextOfNode(parentDeclaration.name, /*includeTrivia*/ false); + const targetStr = isExportAssignment(parentDeclaration) || !parentDeclaration.name ? "" : getTextOfNode(parentDeclaration.name, /*includeTrivia*/ false); const parent = findAncestor(node.parent, n => isExportAssignment(n) || (isStatement(n) ? "quit" : !isParenthesizedExpression(n) && !isTypeAssertionExpression(n) && !isAsExpression(n))); + if (parentDeclaration === parent) { diag = createDiagnosticForNode(node, diagnosticMessage ?? errorByDeclarationKind[parentDeclaration.kind]); addRelatedInfo(diag, createDiagnosticForNode(parentDeclaration, relatedSuggestionByDeclarationKind[parentDeclaration.kind], targetStr)); diff --git a/tests/baselines/reference/transpile/declarationNotInScopeTypes.d.ts b/tests/baselines/reference/transpile/declarationNotInScopeTypes.d.ts index b4d2b68769df5..7f8db6189d192 100644 --- a/tests/baselines/reference/transpile/declarationNotInScopeTypes.d.ts +++ b/tests/baselines/reference/transpile/declarationNotInScopeTypes.d.ts @@ -37,6 +37,7 @@ variables.ts(13,18): error TS9038: Type containing private name 'Z' can't be use return {} as typeof y; ~ !!! error TS9038: Type containing private name 'y' can't be used with --isolatedDeclarations. +!!! related TS9031 variables.ts:6:17: Add a return type to the function declaration. } export function three() { @@ -44,4 +45,5 @@ variables.ts(13,18): error TS9038: Type containing private name 'Z' can't be use return {} as Z; ~ !!! error TS9038: Type containing private name 'Z' can't be used with --isolatedDeclarations. +!!! related TS9031 variables.ts:11:17: Add a return type to the function declaration. }