Skip to content

Commit

Permalink
Add deprecated related feature (#38523)
Browse files Browse the repository at this point in the history
* Add deprecated related feature

* Add more support

* fix navtree

* Add identifier check

* Add more deprecated

* fix crash

* fix more crash

* fix crash

* improve diagnostic

* avoid new tag

* avoid tags

* accept baseline

* Check deprecated in binder

* fix baseline

* fix jsdoc cache

* fix incorrect fix

* Avoid useless changes

* Accept baseline

* Add tests

* fix perf

* fix public api

* Adjust deprecated mark on qualifed name

* Revolve alias symbol

* Use modifier flags insted of symbol props

* Fix modifier flag resolve

* Make lint happy

* Fix crash

* fix crash

* Add cached utils function

* Accept baseline

* Add more tests

* try pinning octokit again

* Avoid tests

* Use utils some

* Deprecated perf test (#3)

* check valueDeclaration only

* check without modifierFlags

* donot check alias

* use cached tag

* remove call to jsdoc

* use deprecated tag

* revert changes

* Revert mission changes

* use node flags

* cache result

* cache

* avoid modifier flags

* Opts

* fix jsdoc include modifier

* fix tests

* fix again

* use symbol flag

* set @octokit/rest back to latest

* fix trailing spacel int

Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
  • Loading branch information
Kingwl and sandersn authored Jun 19, 2020
1 parent 520103f commit 59ad375
Show file tree
Hide file tree
Showing 32 changed files with 495 additions and 107 deletions.
11 changes: 7 additions & 4 deletions scripts/processDiagnosticMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface DiagnosticDetails {
category: string;
code: number;
reportsUnnecessary?: {};
reportsDeprecated?: {};
isEarly?: boolean;
elidedInCompatabilityPyramid?: boolean;
}
Expand Down Expand Up @@ -64,15 +65,17 @@ function buildInfoFileOutput(messageTable: InputDiagnosticMessageTable, inputFil
"// generated from '" + inputFilePathRel + "' by '" + thisFilePathRel.replace(/\\/g, "/") + "'\r\n" +
"/* @internal */\r\n" +
"namespace ts {\r\n" +
" function diag(code: number, category: DiagnosticCategory, key: string, message: string, reportsUnnecessary?: {}, elidedInCompatabilityPyramid?: boolean): DiagnosticMessage {\r\n" +
" return { code, category, key, message, reportsUnnecessary, elidedInCompatabilityPyramid };\r\n" +
" function diag(code: number, category: DiagnosticCategory, key: string, message: string, reportsUnnecessary?: {}, elidedInCompatabilityPyramid?: boolean, reportsDeprecated?: {}): DiagnosticMessage {\r\n" +
" return { code, category, key, message, reportsUnnecessary, elidedInCompatabilityPyramid, reportsDeprecated };\r\n" +
" }\r\n" +
" export const Diagnostics = {\r\n";
messageTable.forEach(({ code, category, reportsUnnecessary, elidedInCompatabilityPyramid }, name) => {
messageTable.forEach(({ code, category, reportsUnnecessary, elidedInCompatabilityPyramid, reportsDeprecated }, name) => {
const propName = convertPropertyName(name);
const argReportsUnnecessary = reportsUnnecessary ? `, /*reportsUnnecessary*/ ${reportsUnnecessary}` : "";
const argElidedInCompatabilityPyramid = elidedInCompatabilityPyramid ? `${!reportsUnnecessary ? ", /*reportsUnnecessary*/ undefined" : ""}, /*elidedInCompatabilityPyramid*/ ${elidedInCompatabilityPyramid}` : "";
result += ` ${propName}: diag(${code}, DiagnosticCategory.${category}, "${createKey(propName, code)}", ${JSON.stringify(name)}${argReportsUnnecessary}${argElidedInCompatabilityPyramid}),\r\n`;
const argReportsDeprecated = reportsDeprecated ? `${!argElidedInCompatabilityPyramid ? ", /*reportsUnnecessary*/ undefined, /*elidedInCompatabilityPyramid*/ undefined" : ""}, /*reportsDeprecated*/ ${reportsDeprecated}` : "";

result += ` ${propName}: diag(${code}, DiagnosticCategory.${category}, "${createKey(propName, code)}", ${JSON.stringify(name)}${argReportsUnnecessary}${argElidedInCompatabilityPyramid}${argReportsDeprecated}),\r\n`;
});

result += " };\r\n}";
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,10 @@ namespace ts {
symbol.parent = parent;
}

if (node.flags & NodeFlags.Deprecated) {
symbol.flags |= SymbolFlags.Deprecated;
}

return symbol;
}

Expand Down
3 changes: 3 additions & 0 deletions src/compiler/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace ts {
export interface ReusableDiagnostic extends ReusableDiagnosticRelatedInformation {
/** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
reportsUnnecessary?: {};
reportDeprecated?: {}
source?: string;
relatedInformation?: ReusableDiagnosticRelatedInformation[];
skippedOn?: keyof CompilerOptions;
Expand Down Expand Up @@ -268,6 +269,7 @@ namespace ts {
return diagnostics.map(diagnostic => {
const result: Diagnostic = convertToDiagnosticRelatedInformation(diagnostic, newProgram, toPath);
result.reportsUnnecessary = diagnostic.reportsUnnecessary;
result.reportsDeprecated = diagnostic.reportDeprecated;
result.source = diagnostic.source;
result.skippedOn = diagnostic.skippedOn;
const { relatedInformation } = diagnostic;
Expand Down Expand Up @@ -817,6 +819,7 @@ namespace ts {
return diagnostics.map(diagnostic => {
const result: ReusableDiagnostic = convertToReusableDiagnosticRelatedInformation(diagnostic, relativeToBuildInfo);
result.reportsUnnecessary = diagnostic.reportsUnnecessary;
result.reportDeprecated = diagnostic.reportsDeprecated;
result.source = diagnostic.source;
result.skippedOn = diagnostic.skippedOn;
const { relatedInformation } = diagnostic;
Expand Down
84 changes: 55 additions & 29 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13168,6 +13168,10 @@ namespace ts {
if (propName !== undefined) {
const prop = getPropertyOfType(objectType, propName);
if (prop) {
if (accessNode && prop.flags & SymbolFlags.Deprecated) {
const deprecatedNode = accessExpression?.argumentExpression ?? (isIndexedAccessTypeNode(accessNode) ? accessNode.indexType : accessNode);
errorOrSuggestion(/* isError */ false, deprecatedNode, Diagnostics._0_is_deprecated, propName as string);
}
if (accessExpression) {
markPropertyAsReferenced(prop, accessExpression, /*isThisAccess*/ accessExpression.expression.kind === SyntaxKind.ThisKeyword);
if (isAssignmentToReadonlyEntity(accessExpression, prop, getAssignmentTargetKind(accessExpression))) {
Expand Down Expand Up @@ -21698,6 +21702,10 @@ namespace ts {
const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol);
let declaration: Declaration | undefined = localOrExportSymbol.valueDeclaration;

const target = (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol);
if (target.flags & SymbolFlags.Deprecated) {
errorOrSuggestion(/* isError */ false, node, Diagnostics._0_is_deprecated, node.escapedText as string);
}
if (localOrExportSymbol.flags & SymbolFlags.Class) {
// Due to the emit for class decorators, any reference to the class from inside of the class body
// must instead be rewritten to point to a temporary variable to avoid issues with the double-bind
Expand Down Expand Up @@ -24679,6 +24687,10 @@ namespace ts {
propType = indexInfo.type;
}
else {
if (prop.flags & SymbolFlags.Deprecated) {
errorOrSuggestion(/* isError */ false, right, Diagnostics._0_is_deprecated, right.escapedText as string);
}

checkPropertyNotUsedBeforeDeclaration(prop, node, right);
markPropertyAsReferenced(prop, node, left.kind === SyntaxKind.ThisKeyword);
getNodeLinks(node).resolvedSymbol = prop;
Expand Down Expand Up @@ -30499,8 +30511,15 @@ namespace ts {
checkTypeArgumentConstraints(node, typeParameters);
}
}
if (type.flags & TypeFlags.Enum && getNodeLinks(node).resolvedSymbol!.flags & SymbolFlags.EnumMember) {
error(node, Diagnostics.Enum_type_0_has_members_with_initializers_that_are_not_literals, typeToString(type));
const symbol = getNodeLinks(node).resolvedSymbol;
if (symbol) {
if (symbol.flags & SymbolFlags.Deprecated) {
const diagLocation = isTypeReferenceNode(node) && isQualifiedName(node.typeName) ? node.typeName.right : node;
errorOrSuggestion(/* isError */ false, diagLocation, Diagnostics._0_is_deprecated, symbol.escapedName as string);
}
if (type.flags & TypeFlags.Enum && symbol.flags & SymbolFlags.EnumMember) {
error(node, Diagnostics.Enum_type_0_has_members_with_initializers_that_are_not_literals, typeToString(type));
}
}
}
}
Expand Down Expand Up @@ -31644,6 +31663,7 @@ namespace ts {
error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName));
}
}

function checkJSDocAugmentsTag(node: JSDocAugmentsTag): void {
const classLike = getEffectiveJSDocHost(node);
if (!classLike || !isClassDeclaration(classLike) && !isClassExpression(classLike)) {
Expand Down Expand Up @@ -34803,33 +34823,39 @@ namespace ts {
let symbol = getSymbolOfNode(node);
const target = resolveAlias(symbol);

const shouldSkipWithJSExpandoTargets = symbol.flags & SymbolFlags.Assignment;
if (!shouldSkipWithJSExpandoTargets && target !== unknownSymbol) {
// For external modules symbol represents local symbol for an alias.
// This local symbol will merge any other local declarations (excluding other aliases)
// and symbol.flags will contains combined representation for all merged declaration.
// Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have,
// otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export*
// in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names).
symbol = getMergedSymbol(symbol.exportSymbol || symbol);
const excludedMeanings =
(symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) |
(symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) |
(symbol.flags & SymbolFlags.Namespace ? SymbolFlags.Namespace : 0);
if (target.flags & excludedMeanings) {
const message = node.kind === SyntaxKind.ExportSpecifier ?
Diagnostics.Export_declaration_conflicts_with_exported_declaration_of_0 :
Diagnostics.Import_declaration_conflicts_with_local_declaration_of_0;
error(node, message, symbolToString(symbol));
}

// Don't allow to re-export something with no value side when `--isolatedModules` is set.
if (compilerOptions.isolatedModules
&& node.kind === SyntaxKind.ExportSpecifier
&& !node.parent.parent.isTypeOnly
&& !(target.flags & SymbolFlags.Value)
&& !(node.flags & NodeFlags.Ambient)) {
error(node, Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type);
if (target !== unknownSymbol) {
const shouldSkipWithJSExpandoTargets = symbol.flags & SymbolFlags.Assignment;
if (!shouldSkipWithJSExpandoTargets) {
// For external modules symbol represents local symbol for an alias.
// This local symbol will merge any other local declarations (excluding other aliases)
// and symbol.flags will contains combined representation for all merged declaration.
// Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have,
// otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export*
// in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names).
symbol = getMergedSymbol(symbol.exportSymbol || symbol);
const excludedMeanings =
(symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) |
(symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) |
(symbol.flags & SymbolFlags.Namespace ? SymbolFlags.Namespace : 0);
if (target.flags & excludedMeanings) {
const message = node.kind === SyntaxKind.ExportSpecifier ?
Diagnostics.Export_declaration_conflicts_with_exported_declaration_of_0 :
Diagnostics.Import_declaration_conflicts_with_local_declaration_of_0;
error(node, message, symbolToString(symbol));
}

// Don't allow to re-export something with no value side when `--isolatedModules` is set.
if (compilerOptions.isolatedModules
&& node.kind === SyntaxKind.ExportSpecifier
&& !node.parent.parent.isTypeOnly
&& !(target.flags & SymbolFlags.Value)
&& !(node.flags & NodeFlags.Ambient)) {
error(node, Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type);
}
}

if (isImportSpecifier(node) && target.flags & SymbolFlags.Deprecated) {
errorOrSuggestion(/* isError */ false, node.name, Diagnostics._0_is_deprecated, symbol.escapedName as string);
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -4602,6 +4602,11 @@
"category": "Message",
"code": 6384
},
"'{0}' is deprecated": {
"category": "Suggestion",
"code": 6385,
"reportsDeprecated": true
},

"The expected type comes from property '{0}' which is declared here on type '{1}'": {
"category": "Message",
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,8 @@ namespace ts {
get updateJSDocProtectedTag() { return getJSDocSimpleTagUpdateFunction<JSDocProtectedTag>(SyntaxKind.JSDocProtectedTag); },
get createJSDocReadonlyTag() { return getJSDocSimpleTagCreateFunction<JSDocReadonlyTag>(SyntaxKind.JSDocReadonlyTag); },
get updateJSDocReadonlyTag() { return getJSDocSimpleTagUpdateFunction<JSDocReadonlyTag>(SyntaxKind.JSDocReadonlyTag); },
get createJSDocDeprecatedTag() { return getJSDocSimpleTagCreateFunction<JSDocDeprecatedTag>(SyntaxKind.JSDocDeprecatedTag); },
get updateJSDocDeprecatedTag() { return getJSDocSimpleTagUpdateFunction<JSDocDeprecatedTag>(SyntaxKind.JSDocDeprecatedTag); },
createJSDocUnknownTag,
updateJSDocUnknownTag,
createJSDocComment,
Expand Down Expand Up @@ -4225,6 +4227,7 @@ namespace ts {
// createJSDocPrivateTag
// createJSDocProtectedTag
// createJSDocReadonlyTag
// createJSDocDeprecatedTag
function createJSDocSimpleTagWorker<T extends JSDocTag>(kind: T["kind"], tagName: Identifier | undefined, comment?: string) {
const node = createBaseJSDocTag<T>(kind, tagName ?? createIdentifier(getDefaultTagNameForKind(kind)), comment);
return node;
Expand All @@ -4237,6 +4240,7 @@ namespace ts {
// updateJSDocPrivateTag
// updateJSDocProtectedTag
// updateJSDocReadonlyTag
// updateJSDocDeprecatedTag
function updateJSDocSimpleTagWorker<T extends JSDocTag>(kind: T["kind"], node: T, tagName: Identifier = getDefaultTagName(node), comment: string | undefined) {
return node.tagName !== tagName
|| node.comment !== comment
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,10 @@ namespace ts {
return node.kind === SyntaxKind.JSDocReadonlyTag;
}

export function isJSDocDeprecatedTag(node: Node): node is JSDocDeprecatedTag {
return node.kind === SyntaxKind.JSDocDeprecatedTag;

}
export function isJSDocEnumTag(node: Node): node is JSDocEnumTag {
return node.kind === SyntaxKind.JSDocEnumTag;
}
Expand Down
9 changes: 9 additions & 0 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1014,10 +1014,15 @@ namespace ts {
return hasJSDoc ? addJSDocComment(node) : node;
}

let hasDeprecatedTag = false;
function addJSDocComment<T extends HasJSDoc>(node: T): T {
Debug.assert(!node.jsDoc); // Should only be called once per node
const jsDoc = mapDefined(getJSDocCommentRanges(node, sourceText), comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos));
if (jsDoc.length) node.jsDoc = jsDoc;
if (hasDeprecatedTag) {
hasDeprecatedTag = false;
(node as Mutable<T>).flags |= NodeFlags.Deprecated;
}
return node;
}

Expand Down Expand Up @@ -7178,6 +7183,10 @@ namespace ts {
case "readonly":
tag = parseSimpleTag(start, factory.createJSDocReadonlyTag, tagName, margin, indentText);
break;
case "deprecated":
hasDeprecatedTag = true;
tag = parseSimpleTag(start, factory.createJSDocDeprecatedTag, tagName, margin, indentText);
break;
case "this":
tag = parseThisTag(start, tagName, margin, indentText);
break;
Expand Down
Loading

0 comments on commit 59ad375

Please sign in to comment.