diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ae7b16234fead..0d593602b4b0e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1194,6 +1194,10 @@ namespace ts { return diagnostic; } + function isDeprecatedSymbol(symbol: Symbol) { + return !!(getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Deprecated); + } + function addDeprecatedSuggestion(location: Node, declarations: Node[], deprecatedEntity: string) { const diagnostic = createDiagnosticForNode(location, Diagnostics._0_is_deprecated, deprecatedEntity); return addDeprecatedSuggestionWorker(declarations, diagnostic); @@ -15175,7 +15179,7 @@ namespace ts { } const prop = getPropertyOfType(objectType, propName); if (prop) { - if (accessFlags & AccessFlags.ReportDeprecated && accessNode && prop.declarations && getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(accessNode, prop)) { + if (accessFlags & AccessFlags.ReportDeprecated && accessNode && prop.declarations && isDeprecatedSymbol(prop) && isUncalledFunctionReference(accessNode, prop)) { const deprecatedNode = accessExpression?.argumentExpression ?? (isIndexedAccessTypeNode(accessNode) ? accessNode.indexType : accessNode); addDeprecatedSuggestion(deprecatedNode, prop.declarations, propName as string); } @@ -25066,9 +25070,9 @@ namespace ts { } const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); - const sourceSymbol = localOrExportSymbol.flags & SymbolFlags.Alias ? resolveAlias(localOrExportSymbol) : localOrExportSymbol; - if (sourceSymbol.declarations && getDeclarationNodeFlagsFromSymbol(sourceSymbol) & NodeFlags.Deprecated && isUncalledFunctionReference(node, sourceSymbol)) { - addDeprecatedSuggestion(node, sourceSymbol.declarations, node.escapedText as string); + const targetSymbol = checkDeprecatedAliasedSymbol(localOrExportSymbol, node); + if (isDeprecatedSymbol(targetSymbol) && isUncalledFunctionReference(node, targetSymbol) && targetSymbol.declarations) { + addDeprecatedSuggestion(node, targetSymbol.declarations, node.escapedText as string); } let declaration = localOrExportSymbol.valueDeclaration; @@ -28460,7 +28464,7 @@ namespace ts { } } else { - if (prop.declarations && getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(node, prop)) { + if (isDeprecatedSymbol(prop) && isUncalledFunctionReference(node, prop) && prop.declarations) { addDeprecatedSuggestion(right, prop.declarations, right.escapedText as string); } checkPropertyNotUsedBeforeDeclaration(prop, node, right); @@ -39802,10 +39806,45 @@ namespace ts { } } - if (isImportSpecifier(node) && target.declarations?.every(d => !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated))) { - addDeprecatedSuggestion(node.name, target.declarations, symbol.escapedName as string); + if (isImportSpecifier(node)) { + const targetSymbol = checkDeprecatedAliasedSymbol(symbol, node); + if (isDeprecatedAliasedSymbol(targetSymbol) && targetSymbol.declarations) { + addDeprecatedSuggestion(node, targetSymbol.declarations, targetSymbol.escapedName as string); + } + } + } + } + + function isDeprecatedAliasedSymbol(symbol: Symbol) { + return !!symbol.declarations && every(symbol.declarations, d => !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated)); + } + + function checkDeprecatedAliasedSymbol(symbol: Symbol, location: Node) { + if (!(symbol.flags & SymbolFlags.Alias)) return symbol; + + const targetSymbol = resolveAlias(symbol); + if (targetSymbol === unknownSymbol) return targetSymbol; + + while (symbol.flags & SymbolFlags.Alias) { + const target = getImmediateAliasedSymbol(symbol); + if (target) { + if (target === targetSymbol) break; + if (target.declarations && length(target.declarations)) { + if (isDeprecatedAliasedSymbol(target)) { + addDeprecatedSuggestion(location, target.declarations, target.escapedName as string); + break; + } + else { + if (symbol === targetSymbol) break; + symbol = target; + } + } + } + else { + break; } } + return targetSymbol; } function checkImportBinding(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier) { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 420ae33d0b5cd..864752d7171ea 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -7406,7 +7406,8 @@ namespace ts { } function parseExportSpecifier() { - return parseImportOrExportSpecifier(SyntaxKind.ExportSpecifier) as ExportSpecifier; + const hasJSDoc = hasPrecedingJSDocComment(); + return withJSDoc(parseImportOrExportSpecifier(SyntaxKind.ExportSpecifier) as ExportSpecifier, hasJSDoc); } function parseImportSpecifier() { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e7e60dc025210..e47ed1a376c60 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -929,6 +929,7 @@ namespace ts { | JSDocFunctionType | ExportDeclaration | NamedTupleMember + | ExportSpecifier | EndOfFileToken ; @@ -3117,7 +3118,7 @@ namespace ts { readonly isTypeOnly: boolean; } - export interface ExportSpecifier extends NamedDeclaration { + export interface ExportSpecifier extends NamedDeclaration, JSDocContainer { readonly kind: SyntaxKind.ExportSpecifier; readonly parent: NamedExports; readonly isTypeOnly: boolean; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 00bc27b973cab..c8ca9fbdf46ca 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1243,7 +1243,8 @@ namespace ts { node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction || node.kind === SyntaxKind.ParenthesizedExpression || - node.kind === SyntaxKind.VariableDeclaration) ? + node.kind === SyntaxKind.VariableDeclaration || + node.kind === SyntaxKind.ExportSpecifier) ? concatenate(getTrailingCommentRanges(text, node.pos), getLeadingCommentRanges(text, node.pos)) : getLeadingCommentRanges(text, node.pos); // True if the comment starts with '/**' but not if it is '/**/' diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 4df9e6328ecbd..5350ab09266ec 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -572,7 +572,7 @@ declare namespace ts { } export interface JSDocContainer { } - export type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ClassStaticBlockDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | EmptyStatement | DebuggerStatement | Block | VariableStatement | ExpressionStatement | IfStatement | DoStatement | WhileStatement | ForStatement | ForInStatement | ForOfStatement | BreakStatement | ContinueStatement | ReturnStatement | WithStatement | SwitchStatement | LabeledStatement | ThrowStatement | TryStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | VariableDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | ImportDeclaration | NamespaceExportDeclaration | ExportAssignment | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | ExportDeclaration | NamedTupleMember | EndOfFileToken; + export type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ClassStaticBlockDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | EmptyStatement | DebuggerStatement | Block | VariableStatement | ExpressionStatement | IfStatement | DoStatement | WhileStatement | ForStatement | ForInStatement | ForOfStatement | BreakStatement | ContinueStatement | ReturnStatement | WithStatement | SwitchStatement | LabeledStatement | ThrowStatement | TryStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | VariableDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | ImportDeclaration | NamespaceExportDeclaration | ExportAssignment | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | ExportDeclaration | NamedTupleMember | ExportSpecifier | EndOfFileToken; export type HasType = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertySignature | PropertyDeclaration | TypePredicateNode | ParenthesizedTypeNode | TypeOperatorNode | MappedTypeNode | AssertionExpression | TypeAliasDeclaration | JSDocTypeExpression | JSDocNonNullableType | JSDocNullableType | JSDocOptionalType | JSDocVariadicType; export type HasTypeArguments = CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement | JsxSelfClosingElement; export type HasInitializer = HasExpressionInitializer | ForStatement | ForInStatement | ForOfStatement | JsxAttribute; @@ -1702,7 +1702,7 @@ declare namespace ts { readonly name: Identifier; readonly isTypeOnly: boolean; } - export interface ExportSpecifier extends NamedDeclaration { + export interface ExportSpecifier extends NamedDeclaration, JSDocContainer { readonly kind: SyntaxKind.ExportSpecifier; readonly parent: NamedExports; readonly isTypeOnly: boolean; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index fdd55dab1f2d9..ebea3e36d65a4 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -572,7 +572,7 @@ declare namespace ts { } export interface JSDocContainer { } - export type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ClassStaticBlockDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | EmptyStatement | DebuggerStatement | Block | VariableStatement | ExpressionStatement | IfStatement | DoStatement | WhileStatement | ForStatement | ForInStatement | ForOfStatement | BreakStatement | ContinueStatement | ReturnStatement | WithStatement | SwitchStatement | LabeledStatement | ThrowStatement | TryStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | VariableDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | ImportDeclaration | NamespaceExportDeclaration | ExportAssignment | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | ExportDeclaration | NamedTupleMember | EndOfFileToken; + export type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ClassStaticBlockDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | EmptyStatement | DebuggerStatement | Block | VariableStatement | ExpressionStatement | IfStatement | DoStatement | WhileStatement | ForStatement | ForInStatement | ForOfStatement | BreakStatement | ContinueStatement | ReturnStatement | WithStatement | SwitchStatement | LabeledStatement | ThrowStatement | TryStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | VariableDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | ImportDeclaration | NamespaceExportDeclaration | ExportAssignment | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | ExportDeclaration | NamedTupleMember | ExportSpecifier | EndOfFileToken; export type HasType = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertySignature | PropertyDeclaration | TypePredicateNode | ParenthesizedTypeNode | TypeOperatorNode | MappedTypeNode | AssertionExpression | TypeAliasDeclaration | JSDocTypeExpression | JSDocNonNullableType | JSDocNullableType | JSDocOptionalType | JSDocVariadicType; export type HasTypeArguments = CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement | JsxSelfClosingElement; export type HasInitializer = HasExpressionInitializer | ForStatement | ForInStatement | ForOfStatement | JsxAttribute; @@ -1702,7 +1702,7 @@ declare namespace ts { readonly name: Identifier; readonly isTypeOnly: boolean; } - export interface ExportSpecifier extends NamedDeclaration { + export interface ExportSpecifier extends NamedDeclaration, JSDocContainer { readonly kind: SyntaxKind.ExportSpecifier; readonly parent: NamedExports; readonly isTypeOnly: boolean; diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion14.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion14.ts new file mode 100644 index 0000000000000..2698cd05ed99d --- /dev/null +++ b/tests/cases/fourslash/jsdocDeprecated_suggestion14.ts @@ -0,0 +1,33 @@ +/// + +// @module: esnext +// @filename: /a.ts +////export const a = 1; +////export const b = 1; + +// @filename: /b.ts +////export { +//// /** @deprecated a is deprecated */ +//// a +////} from "./a"; + +// @filename: /c.ts +////import { [|a|] } from "./b"; +////[|a|] + +goTo.file("/c.ts") + +verify.getSuggestionDiagnostics([ + { + "code": 6385, + "message": "'a' is deprecated.", + "reportsDeprecated": true, + "range": test.ranges()[0] + }, + { + "code": 6385, + "message": "'a' is deprecated.", + "reportsDeprecated": true, + "range": test.ranges()[1] + }, +]); diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion15.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion15.ts new file mode 100644 index 0000000000000..2878cdc2f016d --- /dev/null +++ b/tests/cases/fourslash/jsdocDeprecated_suggestion15.ts @@ -0,0 +1,38 @@ +/// + +// @module: esnext +// @filename: /a.ts +////export const a = 1; +////export const b = 1; + +// @filename: /b.ts +////export { +//// /** @deprecated a is deprecated */ +//// a +////} from "./a"; + +// @filename: /c.ts +////export { +//// a +////} from "./b"; + +// @filename: /d.ts +////import { [|a|] } from "./c"; +////[|a|] + +goTo.file("/d.ts") + +verify.getSuggestionDiagnostics([ + { + "code": 6385, + "message": "'a' is deprecated.", + "reportsDeprecated": true, + "range": test.ranges()[0] + }, + { + "code": 6385, + "message": "'a' is deprecated.", + "reportsDeprecated": true, + "range": test.ranges()[1] + }, +]); diff --git a/tests/cases/fourslash/jsdocDeprecated_suggestion16.ts b/tests/cases/fourslash/jsdocDeprecated_suggestion16.ts new file mode 100644 index 0000000000000..fafc266d89bdb --- /dev/null +++ b/tests/cases/fourslash/jsdocDeprecated_suggestion16.ts @@ -0,0 +1,27 @@ +/// + +// @module: esnext +// @filename: /a.ts +////const a = 1; +////const b = 1; +////export { a, /** @deprecated b is deprecated */ b } + +// @filename: /b.ts +////import { [|b|] } from "./a"; +////[|b|] + +goTo.file("/b.ts") +verify.getSuggestionDiagnostics([ + { + "code": 6385, + "message": "'b' is deprecated.", + "reportsDeprecated": true, + "range": test.ranges()[0] + }, + { + "code": 6385, + "message": "'b' is deprecated.", + "reportsDeprecated": true, + "range": test.ranges()[1] + }, +]);