diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8b8a12fa723..1aab728f6bb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3855,17 +3855,17 @@ namespace ts { return false; } - function isTypeSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined): boolean { - const access = isSymbolAccessible(typeSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false); + function isTypeSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined, allowModules: boolean): boolean { + const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false, allowModules); return access.accessibility === SymbolAccessibility.Accessible; } function isValueSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined): boolean { - const access = isSymbolAccessible(typeSymbol, enclosingDeclaration, SymbolFlags.Value, /*shouldComputeAliasesToMakeVisible*/ false); + const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, SymbolFlags.Value, /*shouldComputeAliasesToMakeVisible*/ false, /*allowModules*/ true); return access.accessibility === SymbolAccessibility.Accessible; } - function isAnySymbolAccessible(symbols: Symbol[] | undefined, enclosingDeclaration: Node | undefined, initialSymbol: Symbol, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean): SymbolAccessibilityResult | undefined { + function isAnySymbolAccessible(symbols: Symbol[] | undefined, enclosingDeclaration: Node | undefined, initialSymbol: Symbol, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean, allowModules: boolean): SymbolAccessibilityResult | undefined { if (!length(symbols)) return; let hadAccessibleChain: Symbol | undefined; @@ -3880,7 +3880,7 @@ namespace ts { return hasAccessibleDeclarations; } } - else { + else if (allowModules) { if (some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { if (shouldComputeAliasesToMakeVisible) { earlyModuleBail = true; @@ -3920,7 +3920,7 @@ namespace ts { containers = [getSymbolOfNode(firstDecl.parent)]; } } - const parentResult = isAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, initialSymbol === symbol ? getQualifiedLeftMeaning(meaning) : meaning, shouldComputeAliasesToMakeVisible); + const parentResult = isAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, initialSymbol === symbol ? getQualifiedLeftMeaning(meaning) : meaning, shouldComputeAliasesToMakeVisible, allowModules); if (parentResult) { return parentResult; } @@ -3950,8 +3950,12 @@ namespace ts { * @param shouldComputeAliasToMakeVisible a boolean value to indicate whether to return aliases to be mark visible in case the symbol is accessible */ function isSymbolAccessible(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean): SymbolAccessibilityResult { + return isSymbolAccessibleWorker(symbol, enclosingDeclaration, meaning, shouldComputeAliasesToMakeVisible, true); + } + + function isSymbolAccessibleWorker(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean, allowModules: boolean): SymbolAccessibilityResult { if (symbol && enclosingDeclaration) { - const result = isAnySymbolAccessible([symbol], enclosingDeclaration, symbol, meaning, shouldComputeAliasesToMakeVisible); + const result = isAnySymbolAccessible([symbol], enclosingDeclaration, symbol, meaning, shouldComputeAliasesToMakeVisible, allowModules); if (result) { return result; } @@ -4331,7 +4335,7 @@ namespace ts { return createThis(); } - if (!inTypeAlias && type.aliasSymbol && (context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope || isTypeSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration))) { + if (!inTypeAlias && type.aliasSymbol && (context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope || isTypeSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration, /*allowModules*/ true))) { const typeArgumentNodes = mapToTypeNodes(type.aliasTypeArguments, context); if (isReservedMemberName(type.aliasSymbol.escapedName) && !(type.aliasSymbol.flags & SymbolFlags.Class)) return createTypeReferenceNode(createIdentifier(""), typeArgumentNodes); return symbolToTypeNode(type.aliasSymbol, context, SymbolFlags.Type, typeArgumentNodes); @@ -4350,7 +4354,7 @@ namespace ts { } if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && type.flags & TypeFlags.TypeParameter && - !isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration)) { + !isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration, /*allowModules*/ true)) { const name = typeParameterToName(type, context); context.approximateLength += idText(name).length; return createTypeReferenceNode(createIdentifier(idText(name)), /*typeArguments*/ undefined); @@ -6325,7 +6329,7 @@ namespace ts { : anyType; const heritageClauses = [ ...!length(baseTypes) ? [] : [createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))], - ...!length(implementsTypes) ? [] : [createHeritageClause(SyntaxKind.ImplementsKeyword, map(implementsTypes, b => serializeBaseType(b, staticBaseType, localName)))] + ...!length(implementsTypes) ? [] : [createHeritageClause(SyntaxKind.ImplementsKeyword, map(implementsTypes, b => serializeImplementedType(b)))] ]; const symbolProps = getNonInterhitedProperties(classType, baseTypes, getPropertiesOfType(classType)); const publicSymbolProps = filter(symbolProps, s => { @@ -6838,6 +6842,28 @@ namespace ts { } } + function serializeImplementedType(t: Type) { + let typeArgs: TypeNode[] | undefined; + let reference: Expression | undefined; + if ((t as TypeReference).target && isTypeSymbolAccessible((t as TypeReference).target.symbol, enclosingDeclaration, /*allowModules*/ false)) { + typeArgs = map(getTypeArguments(t as TypeReference), t => typeToTypeNodeHelper(t, context)); + reference = symbolToExpression((t as TypeReference).target.symbol, context, SymbolFlags.Type); + } + else if (t.symbol && isTypeSymbolAccessible(t.symbol, enclosingDeclaration, /*allowModules*/ false)) { + reference = symbolToExpression(t.symbol, context, SymbolFlags.Type); + } + if (reference) { + return createExpressionWithTypeArguments(typeArgs, reference); + } + else if (context.tracker.reportInaccessibleBaseType) { + context.tracker.reportInaccessibleBaseType(); + return createExpressionWithTypeArguments(/*typeArgs*/ undefined, symbolToExpression(t.symbol, context, SymbolFlags.Type)); + } + else { + Debug.fail("context.tracker missing some error reporting methods"); + } + } + function getUnusedName(input: string, symbol?: Symbol): string { if (symbol) { if (context.remappedSymbolNames!.has("" + getSymbolId(symbol))) { @@ -23925,7 +23951,7 @@ namespace ts { // if jsx emit was not react as there wont be error being emitted reactSym.isReferenced = SymbolFlags.All; - // If react symbol is alias, mark it as refereced + // If react symbol is alias, mark it as referenced if (reactSym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(reactSym)) { markAliasSymbolAsReferenced(reactSym); } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 2dfabe540ad..c4374fedaae 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2971,6 +2971,10 @@ "category": "Error", "code": 2791 }, + "The type '{0}' references an inaccessible base type.": { + "category": "Error", + "code": 2792 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 1e9aa130f68..61c1e21200a 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -70,6 +70,7 @@ namespace ts { const host = context.getEmitHost(); const symbolTracker: SymbolTracker = { trackSymbol, + reportInaccessibleBaseType, reportInaccessibleThisError, reportInaccessibleUniqueSymbolError, reportPrivateInBaseOfClassExpression, @@ -175,6 +176,13 @@ namespace ts { } } + function reportInaccessibleBaseType() { + if (errorNameNode) { + context.addDiagnostic(createDiagnosticForNode(errorNameNode, Diagnostics.The_type_0_references_an_inaccessible_base_type, + declarationNameToString(errorNameNode))); + } + } + function reportInaccessibleThisError() { if (errorNameNode) { context.addDiagnostic(createDiagnosticForNode(errorNameNode, Diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index aef4364a6b5..84221ff2a4c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6465,6 +6465,7 @@ namespace ts { // declaration emitter to help determine if it should patch up the final declaration file // with import statements it previously saw (but chose not to emit). trackSymbol?(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags): void; + reportInaccessibleBaseType?(): void; reportInaccessibleThisError?(): void; reportPrivateInBaseOfClassExpression?(propertyName: string): void; reportInaccessibleUniqueSymbolError?(): void; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 17ce0562122..696098a35d3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -80,6 +80,7 @@ namespace ts { decreaseIndent: noop, clear: () => str = "", trackSymbol: noop, + reportInaccessibleBaseType: noop, reportInaccessibleThisError: noop, reportInaccessibleUniqueSymbolError: noop, reportPrivateInBaseOfClassExpression: noop, diff --git a/tests/baselines/reference/jsdocImplements_namespacedInterface.js b/tests/baselines/reference/jsdocImplements_namespacedInterface.js new file mode 100644 index 00000000000..4a3e1ddf4e6 --- /dev/null +++ b/tests/baselines/reference/jsdocImplements_namespacedInterface.js @@ -0,0 +1,37 @@ +//// [tests/cases/conformance/jsdoc/jsdocImplements_namespacedInterface.ts] //// + +//// [defs.d.ts] +declare namespace N { + interface A { + mNumber(): number; + } + interface AT { + gen(): T; + } +} +//// [a.js] +/** @implements N.A */ +class B { + mNumber() { + return 0; + } +} +/** @implements {N.AT} */ +class BAT { + gen() { + return ""; + } +} + + + + +//// [a.d.ts] +/** @implements N.A */ +declare class B implements N.A { + mNumber(): number; +} +/** @implements {N.AT} */ +declare class BAT implements N.AT { + gen(): string; +} diff --git a/tests/baselines/reference/jsdocImplements_namespacedInterface.symbols b/tests/baselines/reference/jsdocImplements_namespacedInterface.symbols new file mode 100644 index 00000000000..17697471add --- /dev/null +++ b/tests/baselines/reference/jsdocImplements_namespacedInterface.symbols @@ -0,0 +1,41 @@ +=== /defs.d.ts === +declare namespace N { +>N : Symbol(N, Decl(defs.d.ts, 0, 0)) + + interface A { +>A : Symbol(A, Decl(defs.d.ts, 0, 21)) + + mNumber(): number; +>mNumber : Symbol(A.mNumber, Decl(defs.d.ts, 1, 17)) + } + interface AT { +>AT : Symbol(AT, Decl(defs.d.ts, 3, 5)) +>T : Symbol(T, Decl(defs.d.ts, 4, 17)) + + gen(): T; +>gen : Symbol(AT.gen, Decl(defs.d.ts, 4, 21)) +>T : Symbol(T, Decl(defs.d.ts, 4, 17)) + } +} +=== /a.js === +/** @implements N.A */ +class B { +>B : Symbol(B, Decl(a.js, 0, 0)) + + mNumber() { +>mNumber : Symbol(B.mNumber, Decl(a.js, 1, 9)) + + return 0; + } +} +/** @implements {N.AT} */ +class BAT { +>BAT : Symbol(BAT, Decl(a.js, 5, 1)) + + gen() { +>gen : Symbol(BAT.gen, Decl(a.js, 7, 11)) + + return ""; + } +} + diff --git a/tests/baselines/reference/jsdocImplements_namespacedInterface.types b/tests/baselines/reference/jsdocImplements_namespacedInterface.types new file mode 100644 index 00000000000..ca6fb4e58aa --- /dev/null +++ b/tests/baselines/reference/jsdocImplements_namespacedInterface.types @@ -0,0 +1,35 @@ +=== /defs.d.ts === +declare namespace N { + interface A { + mNumber(): number; +>mNumber : () => number + } + interface AT { + gen(): T; +>gen : () => T + } +} +=== /a.js === +/** @implements N.A */ +class B { +>B : B + + mNumber() { +>mNumber : () => number + + return 0; +>0 : 0 + } +} +/** @implements {N.AT} */ +class BAT { +>BAT : BAT + + gen() { +>gen : () => string + + return ""; +>"" : "" + } +} + diff --git a/tests/cases/conformance/jsdoc/jsdocImplements_namespacedInterface.ts b/tests/cases/conformance/jsdoc/jsdocImplements_namespacedInterface.ts new file mode 100644 index 00000000000..c298e6685a5 --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocImplements_namespacedInterface.ts @@ -0,0 +1,28 @@ +// @allowJs: true +// @checkJs: true +// @declaration: true +// @emitDeclarationOnly: true +// @outDir: ./out + +// @Filename: /defs.d.ts +declare namespace N { + interface A { + mNumber(): number; + } + interface AT { + gen(): T; + } +} +// @Filename: /a.js +/** @implements N.A */ +class B { + mNumber() { + return 0; + } +} +/** @implements {N.AT} */ +class BAT { + gen() { + return ""; + } +}