Skip to content

Commit

Permalink
deprecation: handle signatures of CallExpressions (palantir#2883)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajafff authored and HyphnKnight committed Apr 9, 2018
1 parent 7a0b0d4 commit 09dbe89
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 21 deletions.
101 changes: 80 additions & 21 deletions src/rules/deprecationRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
*/

import {
getDeclarationOfBindingElement, isBindingElement, isIdentifier, isJsDoc, isVariableDeclaration, isVariableDeclarationList,
getDeclarationOfBindingElement, isBindingElement, isCallExpression, isIdentifier, isJsDoc,
isPropertyAccessExpression, isTaggedTemplateExpression, isVariableDeclaration, isVariableDeclarationList,
} from "tsutils";
import * as ts from "typescript";
import * as Lint from "../index";
Expand Down Expand Up @@ -101,30 +102,65 @@ function isDeclaration(identifier: ts.Identifier): boolean {
}
}

function getCallExpresion(node: ts.Expression): ts.CallLikeExpression | undefined {
let parent = node.parent!;
if (isPropertyAccessExpression(parent) && parent.name === node) {
node = parent;
parent = node.parent!;
}
return isTaggedTemplateExpression(parent) || isCallExpression(parent) && parent.expression === node ? parent : undefined;
}

function getDeprecation(node: ts.Identifier, tc: ts.TypeChecker): string | undefined {
const callExpression = getCallExpresion(node);
if (callExpression !== undefined) {
const result = getSignatureDeprecation(tc.getResolvedSignature(callExpression));
if (result !== undefined) {
return result;
}
}
let symbol = tc.getSymbolAtLocation(node);
if (symbol !== undefined && Lint.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias)) {
symbol = tc.getAliasedSymbol(symbol);
}
if (symbol !== undefined) {
return getDeprecationValue(symbol);
if (symbol === undefined ||
// if this is a CallExpression and the declaration is a function or method,
// stop here to avoid collecting JsDoc of all overload signatures
callExpression !== undefined && isFunctionOrMethod(symbol.declarations)) {
return undefined;
}
return getSymbolDeprecation(symbol);
}

function findDeprecationTag(tags: ts.JSDocTagInfo[]): string | undefined {
for (const tag of tags) {
if (tag.name === "deprecated") {
return tag.text;
}
}
return undefined;
}

function getDeprecationValue(symbol: ts.Symbol): string | undefined {
function getSymbolDeprecation(symbol: ts.Symbol): string | undefined {
if (symbol.getJsDocTags !== undefined) {
for (const tag of symbol.getJsDocTags()) {
if (tag.name === "deprecated") {
return tag.text;
}
}
return undefined;
return findDeprecationTag(symbol.getJsDocTags());
}
// for compatibility with typescript@<2.3.0
return getDeprecationFromDeclarations(symbol.declarations);
}

function getSignatureDeprecation(signature?: ts.Signature): string | undefined {
if (signature === undefined) {
return undefined;
}
if (signature.getJsDocTags !== undefined) {
return findDeprecationTag(signature.getJsDocTags());
}

// for compatibility with typescript@<2.3.0
return signature.declaration === undefined ? undefined : getDeprecationFromDeclaration(signature.declaration);
}

function getDeprecationFromDeclarations(declarations?: ts.Declaration[]): string | undefined {
if (declarations === undefined) {
return undefined;
Expand All @@ -140,19 +176,42 @@ function getDeprecationFromDeclarations(declarations?: ts.Declaration[]): string
if (isVariableDeclarationList(declaration)) {
declaration = declaration.parent!;
}
for (const child of declaration.getChildren()) {
if (!isJsDoc(child)) {
break;
}
if (child.tags === undefined) {
continue;
}
for (const tag of child.tags) {
if (tag.tagName.text === "deprecated") {
return tag.comment === undefined ? "" : tag.comment;
}
const result = getDeprecationFromDeclaration(declaration);
if (result !== undefined) {
return result;
}
}
return undefined;
}

function getDeprecationFromDeclaration(declaration: ts.Node): string | undefined {
for (const child of declaration.getChildren()) {
if (!isJsDoc(child)) {
break;
}
if (child.tags === undefined) {
continue;
}
for (const tag of child.tags) {
if (tag.tagName.text === "deprecated") {
return tag.comment === undefined ? "" : tag.comment;
}
}
}
return undefined;
}

function isFunctionOrMethod(declarations?: ts.Declaration[]) {
if (declarations === undefined || declarations.length === 0) {
return false;
}
switch (declarations[0].kind) {
case ts.SyntaxKind.MethodDeclaration:
case ts.SyntaxKind.FunctionDeclaration:
case ts.SyntaxKind.FunctionExpression:
case ts.SyntaxKind.MethodSignature:
return true;
default:
return false;
}
}
67 changes: 67 additions & 0 deletions test/rules/deprecation/test.ts.lint
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,73 @@ A + B;

declarationIsMissing();

function fn<T>(): T;
/** @deprecated */
function fn(bar: any): any;
function fn() { }

fn<number>();
fn(1);
~~ [err % ('fn')]
fn;
~~ [err % ('fn')]

class TestClass {
/** @deprecated */
static method(): void;
static method(param): void;
static method(param?): void {}
}

TestClass.method();
~~~~~~ [err % ('method')]
TestClass.method(1);

interface TestInterface {
/** @deprecated */
method(): void;
method(param): void;
}
declare let interfaceTest: TestInterface;
interfaceTest.method();
~~~~~~ [err % ('method')]
interfaceTest.method(1);

declare let callSignatureTest: {
/** @deprecated */
(): void;
(param): void;
}
callSignatureTest();
~~~~~~~~~~~~~~~~~ [err % ('callSignatureTest')]
callSignatureTest(1);

/** @deprecated */
declare let variableWithCallSignature: {
(): void;
}
variableWithCallSignature();
~~~~~~~~~~~~~~~~~~~~~~~~~ [err % ('variableWithCallSignature')]

/** @deprecated */
declare let variableWithCallSignature2: () => void;
variableWithCallSignature2();
~~~~~~~~~~~~~~~~~~~~~~~~~~ [err % ('variableWithCallSignature2')]

function dedent(strings: TemplateStringsArray, ...values: string[]): string;
function dedent(strings: TemplateStringsArray, ...values: number[]): string;
/** @deprecated */
function dedent(strings: TemplateStringsArray, ...values: any[]): string;
function dedent(strings: TemplateStringsArray, ...values: any[]): string {
return "foo";
}

dedent``;
dedent`${"foo"}`;
dedent`${1}`;
dedent`${[]}`;
~~~~~~ [err % ('dedent')]

// TODO: those should be an error
let {f, g, h} = p;
(function ({f, g}: I) {})
Expand Down

0 comments on commit 09dbe89

Please sign in to comment.