Skip to content

Commit

Permalink
feat: tsd expectDeprecated and expectNotDeprecated
Browse files Browse the repository at this point in the history
  • Loading branch information
skarab42 committed Jul 12, 2022
1 parent b7393f8 commit e9daa32
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 13 deletions.
4 changes: 4 additions & 0 deletions src/common/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export enum ErrorCode {
ASSERT_TYPE_IDENTICAL,
ASSERT_TYPE_TOO_WIDE,
ASSERT_ERROR,
ASSERT_DEPRECATED,
ASSERT_NOT_DEPRECATED,
}

export const errorMessages: Record<ErrorCode, string> = {
Expand All @@ -30,6 +32,8 @@ export const errorMessages: Record<ErrorCode, string> = {
[ErrorCode.ASSERT_TYPE_IDENTICAL]: "Type '{expected}' is identical to argument type '{argument}'.",
[ErrorCode.ASSERT_TYPE_TOO_WIDE]: "Type '{expected}' is declared too wide for argument type '{argument}'.",
[ErrorCode.ASSERT_ERROR]: 'An error is expected.',
[ErrorCode.ASSERT_DEPRECATED]: "Expected '{argument}' to be marked as '@deprecated'.",
[ErrorCode.ASSERT_NOT_DEPRECATED]: "Expected '{argument}' to not be marked as '@deprecated'.",
};

export function errorMessage(code: ErrorCode, data?: Record<string, unknown>): string {
Expand Down
23 changes: 23 additions & 0 deletions src/plugin/assert/tsd/expect-deprecated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type ts from 'unleashed-typescript';
import type { Assertion } from '../../types';
import { ErrorCode } from '../../../common/error';
import type { Compiler } from '../../../typescript/types';
import { argumentError, hasDeprecatedTag, missingArgument } from './util';

// https://github.dev/SamVerschueren/tsd/blob/e4a398c1b47a4d2f914446b662840e2be5994997/source/lib/assertions/handlers/expect-deprecated.ts#L11
export function expectDeprecated(
{ node }: Assertion,
{ sourceFile, typeChecker }: Compiler,
): ts.Diagnostic | undefined {
const argument = node.arguments[0];

if (!argument) {
return missingArgument(node, sourceFile);
}

if (!hasDeprecatedTag(argument, typeChecker)) {
return argumentError(ErrorCode.ASSERT_DEPRECATED, typeChecker, argument, sourceFile, node);
}

return;
}
23 changes: 23 additions & 0 deletions src/plugin/assert/tsd/expect-not-deprecated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type ts from 'unleashed-typescript';
import type { Assertion } from '../../types';
import { ErrorCode } from '../../../common/error';
import type { Compiler } from '../../../typescript/types';
import { argumentError, hasDeprecatedTag, missingArgument } from './util';

// https://github.dev/SamVerschueren/tsd/blob/e4a398c1b47a4d2f914446b662840e2be5994997/source/lib/assertions/handlers/expect-deprecated.ts#L11
export function expectNotDeprecated(
{ node }: Assertion,
{ sourceFile, typeChecker }: Compiler,
): ts.Diagnostic | undefined {
const argument = node.arguments[0];

if (!argument) {
return missingArgument(node, sourceFile);
}

if (hasDeprecatedTag(argument, typeChecker)) {
return argumentError(ErrorCode.ASSERT_NOT_DEPRECATED, typeChecker, argument, sourceFile, node);
}

return;
}
18 changes: 6 additions & 12 deletions src/plugin/assert/tsd/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,17 @@ import type { Assertion } from '../../types';
import type { Compiler } from '../../../typescript/types';
import { createAssertionDiagnostic } from '../../diagnostics';

// All credits go to tsd! Most of the logic here comme from their code:
// https://github.com/SamVerschueren/tsd/blob/main/source/lib/assertions/index.ts
// https://github.com/SamVerschueren/tsd/tree/main/source/lib/assertions/handlers

export * from './expect-type';
export * from './expect-not-type';
export * from './expect-assignable';
export * from './expect-not-assignable';
export * from './expect-error';

// All credits go to tsd! Most of the logic here comme from their code:
// https://github.com/SamVerschueren/tsd/blob/main/source/lib/assertions/index.ts
// https://github.com/SamVerschueren/tsd/tree/main/source/lib/assertions/handlers

export function expectDeprecated(assertion: Assertion, compiler: Compiler): ts.Diagnostic | undefined {
return createAssertionDiagnostic('Not yet implemented.', compiler.sourceFile, assertion.node.getStart());
}

export function expectNotDeprecated(assertion: Assertion, compiler: Compiler): ts.Diagnostic | undefined {
return createAssertionDiagnostic('Not yet implemented.', compiler.sourceFile, assertion.node.getStart());
}
export * from './expect-deprecated';
export * from './expect-not-deprecated';

export function printType(assertion: Assertion, compiler: Compiler): ts.Diagnostic | undefined {
return createAssertionDiagnostic('Not yet implemented.', compiler.sourceFile, assertion.node.getStart());
Expand Down
62 changes: 61 additions & 1 deletion src/plugin/assert/tsd/util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type ts from 'unleashed-typescript';
import ts from 'unleashed-typescript';
import { getMiddle } from '../../../typescript/util';
import { createAssertionDiagnostic } from '../../diagnostics';
import { ErrorCode, errorMessage } from '../../../common/error';
Expand Down Expand Up @@ -36,3 +36,63 @@ export function typeError(
getMiddle(node.typeArguments?.[0] ?? node),
);
}

export function argumentError(
code: ErrorCode,
typeChecker: ts.TypeChecker,
argument: ts.Expression,
sourceFile: ts.SourceFile,
node: ts.CallExpression,
): ts.Diagnostic | undefined {
return createAssertionDiagnostic(
errorMessage(code, {
argument: expressionToString(typeChecker, argument),
}),
sourceFile,
getMiddle(node.arguments[0] ?? node),
);
}

export function hasDeprecatedTag(argument: ts.Expression, typeChecker: ts.TypeChecker): boolean {
const signatureOrSymbol = ts.isCallLikeExpression(argument)
? typeChecker.getResolvedSignature(argument)
: typeChecker.getSymbolAtLocation(argument);

if (!signatureOrSymbol) {
return false;
}

const tags = signatureOrSymbol.getJsDocTags();

if (!tags.length) {
return false;
}

return !!tags.find((tag) => tag.name === 'deprecated');
}

export function expressionToString(typeChecker: ts.TypeChecker, expression: ts.Expression): string | undefined {
if (ts.isTypeNode(expression)) {
const type = typeChecker.getTypeAtLocation(expression);

return typeChecker.typeToString(type);
}

if (ts.isCallLikeExpression(expression)) {
const signature = typeChecker.getResolvedSignature(expression);

if (signature) {
return typeChecker.signatureToString(signature);
}

return;
}

const symbol = typeChecker.getSymbolAtLocation(expression);

if (symbol) {
return typeChecker.symbolToString(symbol, expression);
}

return;
}
24 changes: 24 additions & 0 deletions test/tsd.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,27 @@ test('test-4', () => {
test('test-5', () => {
tsd.expectError(true);
});

test('test-6', () => {
tsd.expectDeprecated(UnicornClass);
tsd.expectNotDeprecated(UnicornClass);
});

/**
* @deprecated
*/
export class UnicornClass {
readonly key = '🦄';
}

export class RainbowClass {
readonly key = '🌈';
}

export interface Options {
/**
* @deprecated
*/
readonly separator: string;
readonly delimiter: string;
}

0 comments on commit e9daa32

Please sign in to comment.