diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..4143bf84 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +source/test/fixtures diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..992a2ac0 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,9 @@ +{ + "extends": ["xo", "xo-typescript"], + "parserOptions": { + "project": "tsconfig.tsd.json" + }, + "rules": { + "@typescript-eslint/comma-dangle": "off" + } +} diff --git a/package.json b/package.json index ad2ab4f6..cfc818a6 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "test": "npm run lint && ava", "build": "npm run clean && tsc --project tsconfig.tsd.json && chmod +x dist/cli.js", "clean": "del-cli dist", - "lint": "tslint -p tsconfig.tsd.json --format stylish" + "lint": "eslint 'source/**/*'", + "lint:fix": "eslint --fix 'source/**/*'" }, "files": [ "dist/**/*.js", @@ -49,15 +50,18 @@ "@ava/typescript": "^1.1.1", "@types/node": "^14.0.0", "@types/react": "^16.9.2", + "@typescript-eslint/eslint-plugin": "^4.26.0", + "@typescript-eslint/parser": "^4.26.0", "ava": "^3.8.2", "cpy-cli": "^3.0.0", "del-cli": "^3.0.0", + "eslint": "^7.27.0", + "eslint-config-xo": "^0.36.0", + "eslint-config-xo-typescript": "^0.41.1", "execa": "^5.0.0", "react": "^16.9.0", "rxjs": "^6.5.3", - "tslint": "^5.11.0", - "tslint-xo": "^0.9.0", - "typescript": "^4.1.5" + "typescript": "^4.3.2" }, "ava": { "timeout": "2m", diff --git a/source/cli.ts b/source/cli.ts index 7d298c07..7e15aba1 100644 --- a/source/cli.ts +++ b/source/cli.ts @@ -16,7 +16,7 @@ const cli = meow(` ✖ 10:20 Argument of type string is not assignable to parameter of type number. `); -(async () => { // tslint:disable-line:no-floating-promises +(async () => { try { const options = cli.input.length > 0 ? {cwd: cli.input[0]} : undefined; @@ -25,8 +25,11 @@ const cli = meow(` if (diagnostics.length > 0) { throw new Error(formatter(diagnostics)); } - } catch (error) { - console.error(error.message); + } catch (error: unknown) { + if (error && typeof (error as Error).message === 'string') { + console.error((error as Error).message); + } + process.exit(1); } })(); diff --git a/source/lib/assertions/assert.ts b/source/lib/assertions/assert.ts index 6101bda8..93fc8bd9 100644 --- a/source/lib/assertions/assert.ts +++ b/source/lib/assertions/assert.ts @@ -1,10 +1,12 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ + /** * Check that the type of `value` is identical to type `T`. * * @param value - Value that should be identical to type `T`. */ -// @ts-ignore -export const expectType = (value: T) => { // tslint:disable-line:no-unused +// @ts-expect-error +export const expectType = (value: T) => { // Do nothing, the TypeScript compiler handles this for us }; @@ -13,8 +15,9 @@ export const expectType = (value: T) => { // tslint:disable-line:no-unused * * @param value - Value that should be identical to type `T`. */ -// @ts-ignore -export const expectNotType = (value: any) => { // tslint:disable-line:no-unused +// @ts-expect-error +export const expectNotType = (value: any) => { + // eslint-disable-next-line no-warning-comments // TODO Use a `not T` type when possible https://github.com/microsoft/TypeScript/pull/29317 // Do nothing, the TypeScript compiler handles this for us }; @@ -24,8 +27,8 @@ export const expectNotType = (value: any) => { // tslint:disable-line:no-unu * * @param value - Value that should be assignable to type `T`. */ -// @ts-ignore -export const expectAssignable = (value: T) => { // tslint:disable-line:no-unused +// @ts-expect-error +export const expectAssignable = (value: T) => { // Do nothing, the TypeScript compiler handles this for us }; @@ -34,8 +37,8 @@ export const expectAssignable = (value: T) => { // tslint:disable-line:no-un * * @param value - Value that should not be assignable to type `T`. */ -// @ts-ignore -export const expectNotAssignable = (value: any) => { // tslint:disable-line:no-unused +// @ts-expect-error +export const expectNotAssignable = (value: any) => { // Do nothing, the TypeScript compiler handles this for us }; @@ -44,8 +47,8 @@ export const expectNotAssignable = (value: any) => { // tslint:disable-line: * * @param value - Value that should be checked. */ -// @ts-ignore -export const expectError = (value: T) => { // tslint:disable-line:no-unused +// @ts-expect-error +export const expectError = (value: T) => { // Do nothing, the TypeScript compiler handles this for us }; @@ -54,8 +57,8 @@ export const expectError = (value: T) => { // tslint:disable-line:no-u * * @param expression - Expression that should be marked as `@deprecated`. */ -// @ts-ignore -export const expectDeprecated = (expression: any) => { // tslint:disable-line:no-unused +// @ts-expect-error +export const expectDeprecated = (expression: any) => { // Do nothing, the TypeScript compiler handles this for us }; @@ -64,8 +67,8 @@ export const expectDeprecated = (expression: any) => { // tslint:disable-line:n * * @param expression - Expression that should not be marked as `@deprecated`. */ -// @ts-ignore -export const expectNotDeprecated = (expression: any) => { // tslint:disable-line:no-unused +// @ts-expect-error +export const expectNotDeprecated = (expression: any) => { // Do nothing, the TypeScript compiler handles this for us }; @@ -74,7 +77,7 @@ export const expectNotDeprecated = (expression: any) => { // tslint:disable-lin * * @param expression - Expression whose type should be printed as a warning. */ -// @ts-ignore -export const printType = (expression: any) => { // tslint:disable-line:no-unused +// @ts-expect-error +export const printType = (expression: any) => { // Do nothing, the TypeScript compiler handles this for us }; diff --git a/source/lib/assertions/handlers/expect-deprecated.ts b/source/lib/assertions/handlers/expect-deprecated.ts index 14245519..44972089 100644 --- a/source/lib/assertions/handlers/expect-deprecated.ts +++ b/source/lib/assertions/handlers/expect-deprecated.ts @@ -29,7 +29,7 @@ const expectDeprecatedHelper = (options: Options): Handler => { const message = tsutils.expressionToString(checker, argument); - diagnostics.push(makeDiagnostic(node, options.message(message || '?'))); + diagnostics.push(makeDiagnostic(node, options.message(message ?? '?'))); } return diagnostics; diff --git a/source/lib/compiler.ts b/source/lib/compiler.ts index f822af06..102a5a40 100644 --- a/source/lib/compiler.ts +++ b/source/lib/compiler.ts @@ -1,8 +1,7 @@ import { flattenDiagnosticMessageText, createProgram, - Diagnostic as TSDiagnostic, - SourceFile + Diagnostic as TSDiagnostic } from '@tsd/typescript'; import {ExpectedError, extractAssertions, parseErrorAssertionToLocation} from './parser'; import {Diagnostic, DiagnosticCode, Context, Location} from './interfaces'; @@ -63,10 +62,12 @@ const ignoreDiagnostic = ( return 'preserve'; } - const diagnosticFileName = (diagnostic.file as SourceFile).fileName; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const diagnosticFileName = diagnostic.file!.fileName; for (const [location] of expectedErrors) { - const start = diagnostic.start as number; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const start = diagnostic.start!; if (diagnosticFileName === location.fileName && start > location.start && start < location.end) { return location; @@ -116,7 +117,8 @@ export const getDiagnostics = (context: Context): Diagnostic[] => { continue; } - const position = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start as number); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const position = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); diagnostics.push({ fileName: diagnostic.file.fileName, diff --git a/source/lib/config.ts b/source/lib/config.ts index 9f1261c8..a57af9b4 100644 --- a/source/lib/config.ts +++ b/source/lib/config.ts @@ -10,7 +10,7 @@ import { parseJsonSourceFileConfigFileContent, ModuleKind } from '@tsd/typescript'; -import {Config, RawConfig, RawCompilerOptions} from './interfaces'; +import {Config, PackageJsonWithTsdConfig, RawCompilerOptions} from './interfaces'; /** * Load the configuration settings. @@ -18,12 +18,12 @@ import {Config, RawConfig, RawCompilerOptions} from './interfaces'; * @param pkg - The package.json object. * @returns The config object. */ -export default (pkg: {tsd?: RawConfig}, cwd: string): Config => { - const pkgConfig = pkg.tsd || {}; +export default (pkg: PackageJsonWithTsdConfig, cwd: string): Config => { + const pkgConfig = pkg.tsd ?? {}; const tsConfigCompilerOptions = getOptionsFromTsConfig(cwd); const packageJsonCompilerOptions = parseCompilerConfigObject( - pkgConfig.compilerOptions || {}, + pkgConfig.compilerOptions ?? {}, cwd ); @@ -70,5 +70,5 @@ function parseCompilerConfigObject(compilerOptions: RawCompilerOptions, cwd: str } function parseRawLibs(libs: string[], cwd: string): string[] { - return parseCompilerConfigObject({lib: libs}, cwd).lib || []; + return parseCompilerConfigObject({lib: libs}, cwd).lib ?? []; } diff --git a/source/lib/formatter.ts b/source/lib/formatter.ts index 3fdb23eb..fb135b33 100644 --- a/source/lib/formatter.ts +++ b/source/lib/formatter.ts @@ -1,14 +1,21 @@ import * as formatter from 'eslint-formatter-pretty'; import {Diagnostic} from './interfaces'; +interface FileWithDiagnostics { + filePath: string; + errorCount: number; + warningCount: number; + messages: Diagnostic[]; +} + /** * Format the TypeScript diagnostics to a human readable output. * * @param diagnostics - List of TypeScript diagnostics. * @returns Beautiful diagnostics output */ -export default (diagnostics: Diagnostic[]) => { - const fileMap = new Map(); +export default (diagnostics: Diagnostic[]): string => { + const fileMap = new Map(); for (const diagnostic of diagnostics) { let entry = fileMap.get(diagnostic.fileName); @@ -28,5 +35,6 @@ export default (diagnostics: Diagnostic[]) => { entry.messages.push(diagnostic); } - return formatter(Array.from(fileMap.values())); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + return String(formatter([...fileMap.values()])); }; diff --git a/source/lib/index.ts b/source/lib/index.ts index 3c1580dc..b36e5db5 100644 --- a/source/lib/index.ts +++ b/source/lib/index.ts @@ -5,7 +5,7 @@ import * as globby from 'globby'; import {getDiagnostics as getTSDiagnostics} from './compiler'; import loadConfig from './config'; import getCustomDiagnostics from './rules'; -import {Context, Config} from './interfaces'; +import {Context, Config, Diagnostic, PackageJsonWithTsdConfig} from './interfaces'; export interface Options { cwd: string; @@ -13,7 +13,7 @@ export interface Options { testFiles?: readonly string[]; } -const findTypingsFile = async (pkg: any, options: Options) => { +const findTypingsFile = async (pkg: PackageJsonWithTsdConfig, options: Options): Promise => { const typings = options.typingsFile || pkg.types || @@ -78,15 +78,15 @@ const findTestFiles = async (typingsFilePath: string, options: Options & {config * * @returns A promise which resolves the diagnostics of the type definition. */ -export default async (options: Options = {cwd: process.cwd()}) => { +export default async (options: Options = {cwd: process.cwd()}): Promise => { const pkgResult = await readPkgUp({cwd: options.cwd}); if (!pkgResult) { throw new Error('No `package.json` file found. Make sure you are running the command in a Node.js project.'); } - const pkg = pkgResult.packageJson; - const config = loadConfig(pkg as any, options.cwd); + const pkg = pkgResult.packageJson as PackageJsonWithTsdConfig; + const config = loadConfig(pkg, options.cwd); // Look for a typings file, otherwise use `index.d.ts` in the root directory. If the file is not found, throw an error. const typingsFile = await findTypingsFile(pkg, options); diff --git a/source/lib/interfaces.ts b/source/lib/interfaces.ts index 685c9614..a15c0f0d 100644 --- a/source/lib/interfaces.ts +++ b/source/lib/interfaces.ts @@ -1,8 +1,7 @@ import {CompilerOptions} from '@tsd/typescript'; +import {NormalizedPackageJson} from 'read-pkg-up'; -export interface RawCompilerOptions { - [option: string]: any; -} +export type RawCompilerOptions = Record; export interface Config { directory: string; @@ -11,9 +10,13 @@ export interface Config { export type RawConfig = Partial>; +export type PackageJsonWithTsdConfig = NormalizedPackageJson & { + tsd?: RawConfig; +}; + export interface Context { cwd: string; - pkg: any; + pkg: PackageJsonWithTsdConfig; typingsFile: string; testFiles: string[]; config: Config; diff --git a/source/lib/parser.ts b/source/lib/parser.ts index 6bc8cf05..0a7da37e 100644 --- a/source/lib/parser.ts +++ b/source/lib/parser.ts @@ -23,7 +23,7 @@ export const extractAssertions = (program: Program): Map(); + const nodes = assertions.get(assertion) ?? new Set(); nodes.add(node); diff --git a/source/lib/rules/files-property.ts b/source/lib/rules/files-property.ts index 61a2094e..5f5e9b9e 100644 --- a/source/lib/rules/files-property.ts +++ b/source/lib/rules/files-property.ts @@ -43,7 +43,7 @@ export default (context: Context): Diagnostic[] => { function processGitIgnoreStylePatterns(patterns: readonly string[]): string[] { const processedPatterns = patterns .map(pattern => { - const [negatePatternMatch] = pattern.match(/^!+/) || []; + const [negatePatternMatch] = /^!+/.exec(pattern) ?? []; const negationMarkersCount = negatePatternMatch ? negatePatternMatch.length : 0; return [ diff --git a/source/lib/utils/typescript.ts b/source/lib/utils/typescript.ts index f039db35..9886edb7 100644 --- a/source/lib/utils/typescript.ts +++ b/source/lib/utils/typescript.ts @@ -8,9 +8,9 @@ import {TypeChecker, Expression, isCallLikeExpression, JSDocTagInfo} from '@tsd/ * @return A unique Set of JSDoc tags or `undefined` if they couldn't be resolved. */ export const resolveJSDocTags = (checker: TypeChecker, expression: Expression): Map | undefined => { - const ref = isCallLikeExpression(expression) - ? checker.getResolvedSignature(expression) - : checker.getSymbolAtLocation(expression); + const ref = isCallLikeExpression(expression) ? + checker.getResolvedSignature(expression) : + checker.getSymbolAtLocation(expression); if (!ref) { return; diff --git a/source/test/test.ts b/source/test/test.ts index 0abfd794..ea773147 100644 --- a/source/test/test.ts +++ b/source/test/test.ts @@ -370,7 +370,7 @@ test('includes extended config files along with found ones', async t => { test('errors in libs from node_modules are not reported', async t => { const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/exclude-node-modules')}); - const [nodeModuleDiagnostics, testFileDiagnostics, otherDiagnostics] = diagnostics.reduce( + const [nodeModuleDiagnostics, testFileDiagnostics, otherDiagnostics] = diagnostics.reduce( ([nodeModuleDiags, testFileDiags, otherDiags], diagnostic) => { if (/[/\\]node_modules[/\\]/.test(diagnostic.fileName)) { nodeModuleDiags.push(diagnostic); @@ -379,9 +379,10 @@ test('errors in libs from node_modules are not reported', async t => { } else { otherDiags.push(diagnostic); } + return [nodeModuleDiags, testFileDiags, otherDiags]; }, - [[], [], []] as Diagnostic[][] + [[], [], []] ); t.deepEqual( diff --git a/source/types.d.ts b/source/types.d.ts new file mode 100644 index 00000000..11ecc1ec --- /dev/null +++ b/source/types.d.ts @@ -0,0 +1 @@ +declare module 'eslint-formatter-pretty'; diff --git a/tsconfig.tsd.json b/tsconfig.tsd.json index e7ad6173..fcf58f64 100644 --- a/tsconfig.tsd.json +++ b/tsconfig.tsd.json @@ -12,8 +12,8 @@ "newLine": "lf", "stripInternal": true, "strict": true, - "noImplicitAny": false, "noImplicitReturns": true, + "noImplicitOverride": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, @@ -24,6 +24,5 @@ "node_modules", "dist", "source/test/fixtures", - "libraries" ] } diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 55e9f361..00000000 --- a/tslint.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "tslint-xo" -}