Skip to content

Commit

Permalink
Expose programming API - fixes #37
Browse files Browse the repository at this point in the history
  • Loading branch information
SamVerschueren committed Oct 7, 2019
1 parent 874092b commit d6d32e7
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 34 deletions.
5 changes: 3 additions & 2 deletions package.json
Expand Up @@ -16,10 +16,10 @@
"node": ">=6"
},
"scripts": {
"prepublishOnly": "npm run build && chmod +x dist/cli.js",
"prepublishOnly": "npm run build",
"pretest": "npm run build && cpy \"./**/**\" \"../../../dist/test/fixtures/\" --parents --cwd=source/test/fixtures",
"test": "npm run lint && ava dist/test/test.js",
"build": "npm run clean && tsc",
"build": "npm run clean && tsc && chmod +x dist/cli.js",
"clean": "del-cli dist",
"lint": "tslint -p . --format stylish"
},
Expand All @@ -41,6 +41,7 @@
],
"dependencies": {
"eslint-formatter-pretty": "^1.3.0",
"execa": "^2.0.4",
"globby": "^9.1.0",
"meow": "^5.0.0",
"path-exists": "^3.0.0",
Expand Down
31 changes: 31 additions & 0 deletions readme.md
Expand Up @@ -153,6 +153,37 @@ Check if the function call has argument type errors.
Check if a value is of the provided type `T`.


## Programmatic API

You can use the programmatic API to retrieve the diagnostics and do something with them. This can be useful to run the tests with AVA, Jest or any other testing framework.

```ts
import tsd from 'tsd';

(async () => {
const diagnostics = await tsd();

console.log(diagnostics.length);
//=> 2
})();
```

### tsd([options])

Retrieve the type definition diagnostics of the project.

#### options

Type: `object`

##### cwd

Type: `string`<br>
Default: `process.cwd()`

Current working directory of the project to retrieve the diagnostics for.


## License

MIT © [Sam Verschueren](https://github.com/SamVerschueren)
2 changes: 1 addition & 1 deletion source/cli.ts
Expand Up @@ -6,7 +6,7 @@ import tsd from './lib';

const cli = meow(`
Usage
$ tsd [path]
$ tsd [path]
Examples
$ tsd /path/to/project
Expand Down
3 changes: 3 additions & 0 deletions source/index.ts
@@ -1 +1,4 @@
import tsd from './lib';

export * from './lib/assert';
export default tsd;
2 changes: 1 addition & 1 deletion source/lib/index.ts
Expand Up @@ -7,7 +7,7 @@ import loadConfig from './config';
import getCustomDiagnostics from './rules';
import {Context, Config} from './interfaces';

interface Options {
export interface Options {
cwd: string;
}

Expand Down
34 changes: 34 additions & 0 deletions source/test/cli.ts
@@ -0,0 +1,34 @@
import * as path from 'path';
import test from 'ava';
import * as execa from 'execa';

interface ExecaError extends Error {
readonly exitCode: number;
readonly stderr: string;
}

test('fail if errors are found', async t => {
const {exitCode, stderr} = await t.throwsAsync<ExecaError>(execa('../../../cli.js', {
cwd: path.join(__dirname, 'fixtures/failure')
}));

t.is(exitCode, 1);
t.regex(stderr, /5:19[ ]{2}Argument of type number is not assignable to parameter of type string./);
});

test('succeed if no errors are found', async t => {
const {exitCode} = await execa('../../../cli.js', {
cwd: path.join(__dirname, 'fixtures/success')
});

t.is(exitCode, 0);
});

test('provide a path', async t => {
const file = path.join(__dirname, 'fixtures/failure');

const {exitCode, stderr} = await t.throwsAsync<ExecaError>(execa('dist/cli.js', [file]));

t.is(exitCode, 1);
t.regex(stderr, /5:19[ ]{2}Argument of type number is not assignable to parameter of type string./);
});
60 changes: 30 additions & 30 deletions source/test/test.ts
@@ -1,6 +1,6 @@
import * as path from 'path';
import test, {ExecutionContext} from 'ava';
import m from '../lib';
import tsd from '..';
import {Diagnostic} from '../lib/interfaces';

type Expectation = [number, number, 'error' | 'warning', string, (string | RegExp)?];
Expand Down Expand Up @@ -32,23 +32,23 @@ const verify = (t: ExecutionContext, diagnostics: Diagnostic[], expectations: Ex
};

test('throw if no type definition was found', async t => {
await t.throwsAsync(m({cwd: path.join(__dirname, 'fixtures/no-tsd')}), 'The type definition `index.d.ts` does not exist. Create one and try again.');
await t.throwsAsync(tsd({cwd: path.join(__dirname, 'fixtures/no-tsd')}), 'The type definition `index.d.ts` does not exist. Create one and try again.');
});

test('throw if no test is found', async t => {
await t.throwsAsync(m({cwd: path.join(__dirname, 'fixtures/no-test')}), 'The test file `index.test-d.ts` or `index.test-d.tsx` does not exist. Create one and try again.');
await t.throwsAsync(tsd({cwd: path.join(__dirname, 'fixtures/no-test')}), 'The test file `index.test-d.ts` or `index.test-d.tsx` does not exist. Create one and try again.');
});

test('return diagnostics', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/failure')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/failure')});

verify(t, diagnostics, [
[5, 19, 'error', 'Argument of type \'number\' is not assignable to parameter of type \'string\'.']
]);
});

test('return diagnostics from imported files as well', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/failure-nested')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/failure-nested')});

verify(t, diagnostics, [
[5, 19, 'error', 'Argument of type \'number\' is not assignable to parameter of type \'string\'.', /child.test-d.ts$/],
Expand All @@ -57,23 +57,23 @@ test('return diagnostics from imported files as well', async t => {
});

test('fail if typings file is not part of `files` list', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/no-files')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/no-files')});

verify(t, diagnostics, [
[3, 1, 'error', 'TypeScript type definition `index.d.ts` is not part of the `files` list.', 'package.json'],
]);
});

test('fail if `typings` property is used instead of `types`', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/types-property/typings')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/types-property/typings')});

verify(t, diagnostics, [
[3, 1, 'error', 'Use property `types` instead of `typings`.', 'package.json'],
]);
});

test('fail if tests don\'t pass in strict mode', async t => {
const diagnostics = await m({
const diagnostics = await tsd({
cwd: path.join(__dirname, 'fixtures/failure-strict-null-checks')
});

Expand All @@ -83,7 +83,7 @@ test('fail if tests don\'t pass in strict mode', async t => {
});

test('overridden config defaults to `strict` if `strict` is not explicitly overridden', async t => {
const diagnostics = await m({
const diagnostics = await tsd({
cwd: path.join(__dirname, 'fixtures/strict-null-checks-as-default-config-value')
});

Expand All @@ -93,7 +93,7 @@ test('overridden config defaults to `strict` if `strict` is not explicitly overr
});

test('fail if types are used from a lib that was not explicitly specified', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/lib-config/failure-missing-lib')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/lib-config/failure-missing-lib')});

verify(t, diagnostics, [
[1, 22, 'error', 'Cannot find name \'Window\'.', /failure-missing-lib\/index.d.ts$/],
Expand All @@ -102,97 +102,97 @@ test('fail if types are used from a lib that was not explicitly specified', asyn
});

test('allow specifying a lib as a triple-slash-reference', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/lib-config/lib-as-triple-slash-reference')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/lib-config/lib-as-triple-slash-reference')});

verify(t, diagnostics, []);
});

test('allow specifying a lib in package.json\'s `tsd` field', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/lib-config/lib-from-package-json')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/lib-config/lib-from-package-json')});

verify(t, diagnostics, []);
});

test('allow specifying a lib in tsconfig.json', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/lib-config/lib-from-tsconfig-json')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/lib-config/lib-from-tsconfig-json')});

verify(t, diagnostics, []);
});

test('a lib option in package.json overrdides a lib option in tsconfig.json', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/lib-config/lib-from-package-json-overrides-tsconfig-json')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/lib-config/lib-from-package-json-overrides-tsconfig-json')});

verify(t, diagnostics, []);
});

test('pass in loose mode when strict mode is disabled in settings', async t => {
const diagnostics = await m({
const diagnostics = await tsd({
cwd: path.join(__dirname, 'fixtures/non-strict-check-with-config')
});

verify(t, diagnostics, []);
});

test('return no diagnostics', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/success')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/success')});

verify(t, diagnostics, []);
});

test('support non-barrel main', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/test-non-barrel-main')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/test-non-barrel-main')});

verify(t, diagnostics, []);
});

test('support non-barrel main using `types` property', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/test-non-barrel-main-via-types')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/test-non-barrel-main-via-types')});

verify(t, diagnostics, []);
});

test('support testing in sub-directories', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/test-in-subdir')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/test-in-subdir')});

verify(t, diagnostics, []);
});

test('support top-level await', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/top-level-await')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/top-level-await')});

verify(t, diagnostics, []);
});

test('support default test directory', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/test-directory/default')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/test-directory/default')});

verify(t, diagnostics, []);
});

test('support tsx in subdirectory', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/test-directory/tsx')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/test-directory/tsx')});

verify(t, diagnostics, []);
});

test('support setting a custom test directory', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/test-directory/custom')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/test-directory/custom')});

verify(t, diagnostics, [
[4, 0, 'error', 'Expected an error, but found none.']
]);
});

test('expectError for functions', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/expect-error/functions')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/functions')});

verify(t, diagnostics, [
[5, 0, 'error', 'Expected an error, but found none.']
]);
});

test('expectError should not ignore syntactical errors', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/expect-error/syntax')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/syntax')});

verify(t, diagnostics, [
[4, 29, 'error', '\')\' expected.'],
Expand All @@ -203,29 +203,29 @@ test('expectError should not ignore syntactical errors', async t => {
});

test('expectError for values', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/expect-error/values')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/expect-error/values')});

verify(t, diagnostics, [
[5, 0, 'error', 'Expected an error, but found none.']
]);
});

test('missing import', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/missing-import')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/missing-import')});

verify(t, diagnostics, [
[3, 18, 'error', 'Cannot find name \'Primitive\'.']
]);
});

test('tsx', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/tsx')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/tsx')});

verify(t, diagnostics, []);
});

test('loose types', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/strict-types/loose')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/strict-types/loose')});

verify(t, diagnostics, [
[5, 0, 'error', 'Parameter type `string` is declared too wide for argument type `"cat"`.'],
Expand All @@ -242,7 +242,7 @@ test('loose types', async t => {
});

test('strict types', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/strict-types/strict')});
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/strict-types/strict')});

verify(t, diagnostics, []);
});

0 comments on commit d6d32e7

Please sign in to comment.