Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verbose API results #75

Open
wants to merge 50 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
0122488
Remove VSCode config folder and add source code generation in TS build
smartclash Jun 24, 2020
e706b63
Allow passing of path to type definiton file manually
smartclash Jun 24, 2020
43ac091
Update README.md
smartclash Jun 24, 2020
1282ce6
Refactor code to check definition file existence if specified manually
smartclash Jun 24, 2020
8185162
Fix the checking of type file existence
smartclash Jun 24, 2020
35063ce
Use expect error instead of ignoring it
smartclash Jun 24, 2020
4d23908
Allow specifing test files manually
smartclash Jun 25, 2020
c7e427b
Allow passing relative path of typing def file
smartclash Jul 2, 2020
4cf8cb4
Allow passing relative path to test files
smartclash Jul 2, 2020
1bf9d15
Counts number of tests. Still needs to pass it.
smartclash Jul 8, 2020
544c401
[WIP] Verbose mode
smartclash Jul 9, 2020
039d8e9
Remove verbose mode option
smartclash Jul 22, 2020
8a9c90e
Support for ExtendedDiagnostics
smartclash Jul 22, 2020
ea3961a
Make changes according to suggestions
smartclash Jul 23, 2020
2f7e26a
Merge branch 'relative-paths' into feature/verbose-reporting
smartclash Jul 24, 2020
134b88c
Fixed README alignment
smartclash Jul 24, 2020
a447c7d
Remove counting custom rules as a test
smartclash Jul 29, 2020
da0741a
Provide docs for new changes
smartclash Aug 6, 2020
a477f69
Merge branch 'relative-paths' into feature/verbose-reporting
smartclash Aug 6, 2020
64409b3
Add tests for new typingsFile option
smartclash Aug 7, 2020
6d24bca
Fixed tests file resolving issue when typingsFile option is set
smartclash Aug 7, 2020
1d391ae
Add tests for testFiles option
smartclash Aug 7, 2020
d925296
Add tests to check cases where typings file is not found in specified…
smartclash Aug 7, 2020
414e242
Merge branch 'relative-paths' into feature/verbose-reporting
smartclash Aug 9, 2020
59670d6
Use globby for testFiles option
smartclash Aug 9, 2020
eb4a6ea
Merge branch 'relative-paths' into feature/verbose-reporting
smartclash Aug 9, 2020
9eb0912
Add unit test to check numTests parameter
SaurabhAgarwala Aug 10, 2020
41e9e0c
Remove .vscode entry in gitignore
smartclash Oct 4, 2020
02927f0
Update docs
smartclash Oct 4, 2020
285c6cb
Make testFiles property readonly
smartclash Oct 4, 2020
59c5f4b
Fix grammar
smartclash Oct 4, 2020
d27c0df
Merge branch 'relative-paths' into feature/verbose-reporting
smartclash Oct 4, 2020
641e5cd
Grammar and typo fix
smartclash Oct 4, 2020
caf42f2
Merge branch 'feature/verbose-reporting' of github.com:MLH-Fellowship…
smartclash Oct 4, 2020
9ece40e
Grammar in README
smartclash Oct 5, 2020
7b8d323
Merge branch 'relative-paths' into feature/verbose-reporting
smartclash Oct 5, 2020
44c066e
Update readme.md
smartclash Dec 3, 2020
c549587
Improve readme
smartclash Dec 3, 2020
cb3da21
Merge branch 'relative-paths' into feature/verbose-reporting
smartclash Dec 3, 2020
9424f46
Refactor numTests into testCount
smartclash Dec 3, 2020
473f255
Counts number of tests. Still needs to pass it.
smartclash Jul 8, 2020
796568e
[WIP] Verbose mode
smartclash Jul 9, 2020
015277e
Remove verbose mode option
smartclash Jul 22, 2020
67662cd
Support for ExtendedDiagnostics
smartclash Jul 22, 2020
d8e7301
Remove counting custom rules as a test
smartclash Jul 29, 2020
6e7c49c
Provide docs for new changes
smartclash Aug 6, 2020
101eb4f
Grammar and typo fix
smartclash Oct 4, 2020
de2b549
Add unit test to check numTests parameter
SaurabhAgarwala Aug 10, 2020
f6e579a
Refactor numTests into testCount
smartclash Dec 3, 2020
1be32cf
Merge remote-tracking branch 'origin/feature/verbose-reporting' into …
smartclash Oct 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
@@ -1,3 +1,4 @@
node_modules
yarn.lock
dist/
.vscode/
22 changes: 19 additions & 3 deletions readme.md
Expand Up @@ -191,10 +191,13 @@ You can use the programmatic API to retrieve the diagnostics and do something wi
import tsd from 'tsd';

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

console.log(diagnostics.length);
//=> 2
// Returns the number of tests evaludated.
smartclash marked this conversation as resolved.
Show resolved Hide resolved
console.log(diagnoser.numTests)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
console.log(diagnoser.numTests)
console.log(diagnoser.testCount)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you like the numTests to be renamed into testCount?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've refactored all instances of numTests into testCount.


// Returns the diagnostics if any or just an empty array
smartclash marked this conversation as resolved.
Show resolved Hide resolved
console.log(diagnoser.diagnostics);
})();
```

Expand All @@ -213,6 +216,19 @@ Default: `process.cwd()`

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

##### typingsFile

Type: `string`<br>
Default: `''`

Path to the type definitions of the project.

##### testFiles

type: `string[]`<br>
default: `['']`

An array of test files with their path

## License

Expand Down
5 changes: 3 additions & 2 deletions source/cli.ts
Expand Up @@ -23,10 +23,11 @@ const cli = meow(`
try {
const options = cli.input.length > 0 ? {cwd: cli.input[0]} : undefined;

const diagnostics = await tsd(options);
const extendedDiagnostics = await tsd(options);
const diagnostics = extendedDiagnostics.diagnostics;

if (diagnostics.length > 0) {
throw new Error(formatter(diagnostics));
throw new Error(formatter(extendedDiagnostics));
}
} catch (error) {
console.error(error.message);
Expand Down
16 changes: 9 additions & 7 deletions source/lib/compiler.ts
@@ -1,4 +1,3 @@
import * as path from 'path';
import {
flattenDiagnosticMessageText,
createProgram,
Expand All @@ -7,7 +6,7 @@ import {
} from '../../libraries/typescript';
import {TypeChecker} from './entities/typescript';
import {extractAssertions, parseErrorAssertionToLocation} from './parser';
import {Diagnostic, DiagnosticCode, Context, Location} from './interfaces';
import {Diagnostic, DiagnosticCode, Context, Location, ExtendedDiagnostic} from './interfaces';
import {handle} from './assertions';

// List of diagnostic codes that should be ignored in general
Expand Down Expand Up @@ -64,19 +63,22 @@ const ignoreDiagnostic = (diagnostic: TSDiagnostic, expectedErrors: Map<Location
* @param context - The context object.
* @returns List of diagnostics
*/
export const getDiagnostics = (context: Context): Diagnostic[] => {
const fileNames = context.testFiles.map(fileName => path.join(context.cwd, fileName));

export const getDiagnostics = (context: Context): ExtendedDiagnostic => {
let numTests = 0;
const diagnostics: Diagnostic[] = [];

const program = createProgram(fileNames, context.config.compilerOptions);
const program = createProgram(context.testFiles, context.config.compilerOptions);

const tsDiagnostics = program
.getSemanticDiagnostics()
.concat(program.getSyntacticDiagnostics());

const assertions = extractAssertions(program);

for (const assertion of assertions) {
numTests = numTests + assertion[1].size;
}

diagnostics.push(...handle(program.getTypeChecker() as TypeChecker, assertions));

const expectedErrors = parseErrorAssertionToLocation(assertions);
Expand Down Expand Up @@ -105,5 +107,5 @@ export const getDiagnostics = (context: Context): Diagnostic[] => {
});
}

return diagnostics;
return {numTests, diagnostics};
};
5 changes: 3 additions & 2 deletions source/lib/formatter.ts
@@ -1,13 +1,14 @@
import * as formatter from 'eslint-formatter-pretty';
import {Diagnostic} from './interfaces';
import {ExtendedDiagnostic} from './interfaces';

/**
* Format the TypeScript diagnostics to a human readable output.
*
* @param diagnostics - List of TypeScript diagnostics.
* @returns Beautiful diagnostics output
*/
export default (diagnostics: Diagnostic[]) => {
export default (extendedDiagnostics: ExtendedDiagnostic) => {
const diagnostics = extendedDiagnostics.diagnostics;
const fileMap = new Map<string, any>();

for (const diagnostic of diagnostics) {
Expand Down
51 changes: 41 additions & 10 deletions source/lib/index.ts
Expand Up @@ -9,11 +9,12 @@ import {Context, Config} from './interfaces';

export interface Options {
cwd: string;
typingsFile?: string;
testFiles?: string[];
}

const findTypingsFile = async (pkg: any, options: Options) => {
const typings = pkg.types || pkg.typings || 'index.d.ts';

const typings = options.typingsFile || pkg.types || pkg.typings || 'index.d.ts';
const typingsExist = await pathExists(path.join(options.cwd, typings));

if (!typingsExist) {
Expand All @@ -23,13 +24,37 @@ const findTypingsFile = async (pkg: any, options: Options) => {
return typings;
};

const findTestFiles = async (typingsFile: string, options: Options & {config: Config}) => {
const normalizeTypingsFilePath = (typingsFilePath: string, options: Options) => {
if (options.typingsFile) {
return path.basename(typingsFilePath);
}

return typingsFilePath;
};

const findCustomTestFiles = async (testFilesPattern: string[], cwd: string) => {
const testFiles = await globby(testFilesPattern, {cwd});

if (testFiles.length === 0) {
throw new Error('Could not find test files. Create one and try again');
}

return testFiles.map(file => path.join(cwd, file));
};

const findTestFiles = async (typingsFilePath: string, options: Options & {config: Config}) => {
if (options.testFiles?.length) {
return findCustomTestFiles(options.testFiles, options.cwd);
}

// Return only the file name if typingsFile option is used.
const typingsFile = normalizeTypingsFilePath(typingsFilePath, options);

const testFile = typingsFile.replace(/\.d\.ts$/, '.test-d.ts');
const tsxTestFile = typingsFile.replace(/\.d\.ts$/, '.test-d.tsx');
const testDir = options.config.directory;

let testFiles = await globby([testFile, tsxTestFile], {cwd: options.cwd});

const testDirExists = await pathExists(path.join(options.cwd, testDir));

if (testFiles.length === 0 && !testDirExists) {
Expand All @@ -40,7 +65,7 @@ const findTestFiles = async (typingsFile: string, options: Options & {config: Co
testFiles = await globby([`${testDir}/**/*.ts`, `${testDir}/**/*.tsx`], {cwd: options.cwd});
}

return testFiles;
return testFiles.map(fileName => path.join(options.cwd, fileName));
};

/**
Expand All @@ -56,7 +81,6 @@ export default async (options: Options = {cwd: process.cwd()}) => {
}

const pkg = pkgResult.packageJson;

const config = loadConfig(pkg as any, 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.
Expand All @@ -75,8 +99,15 @@ export default async (options: Options = {cwd: process.cwd()}) => {
config
};

return [
...getCustomDiagnostics(context),
...getTSDiagnostics(context)
];
const tsDiagnostics = getTSDiagnostics(context);
const customDiagnostics = getCustomDiagnostics(context);
const numTests = tsDiagnostics.numTests;

return {
numTests,
diagnostics: [
...customDiagnostics,
...tsDiagnostics.diagnostics
]
};
};
5 changes: 5 additions & 0 deletions source/lib/interfaces.ts
Expand Up @@ -38,6 +38,11 @@ export interface Diagnostic {
column?: number;
}

export interface ExtendedDiagnostic {
numTests: number;
diagnostics: Diagnostic[];
}

export interface Location {
fileName: string;
start: number;
Expand Down
2 changes: 1 addition & 1 deletion source/lib/rules/index.ts
Expand Up @@ -16,7 +16,7 @@ const rules = new Set<RuleFunction>([
* @param context - The context object.
* @returns List of diagnostics
*/
export default (context: Context) => {
export default (context: Context): Diagnostic[] => {
const diagnostics: Diagnostic[] = [];

for (const rule of rules) {
Expand Down
6 changes: 6 additions & 0 deletions source/test/fixtures/specify-test-files/index.d.ts
@@ -0,0 +1,6 @@
declare const one: {
(foo: string, bar: string): string;
(foo: number, bar: number): number;
};

export default one;
3 changes: 3 additions & 0 deletions source/test/fixtures/specify-test-files/index.js
@@ -0,0 +1,3 @@
module.exports.default = (foo, bar) => {
return foo + bar;
};
3 changes: 3 additions & 0 deletions source/test/fixtures/specify-test-files/package.json
@@ -0,0 +1,3 @@
{
"name": "foo"
}
5 changes: 5 additions & 0 deletions source/test/fixtures/specify-test-files/unknown.test.ts
@@ -0,0 +1,5 @@
import {expectType} from '../../..';
import one from '.';

expectType<string>(one('foo', 'bar'));
expectType<string>(one(1, 2));
3 changes: 3 additions & 0 deletions source/test/fixtures/typings-custom-dir/index.js
@@ -0,0 +1,3 @@
module.exports.default = (foo, bar) => {
return foo + bar;
};
5 changes: 5 additions & 0 deletions source/test/fixtures/typings-custom-dir/index.test-d.ts
@@ -0,0 +1,5 @@
import {expectType} from '../../..';
import one from './utils';

expectType<string>(one('foo', 'bar'));
expectType<string>(one(1, 2));
3 changes: 3 additions & 0 deletions source/test/fixtures/typings-custom-dir/package.json
@@ -0,0 +1,3 @@
{
"name": "foo"
}
6 changes: 6 additions & 0 deletions source/test/fixtures/typings-custom-dir/utils/index.d.ts
@@ -0,0 +1,6 @@
declare const one: {
(foo: string, bar: string): string;
(foo: number, bar: number): number;
};

export default one;
7 changes: 4 additions & 3 deletions source/test/fixtures/utils.ts
@@ -1,16 +1,17 @@
import {ExecutionContext} from 'ava';
import {Diagnostic} from '../../lib/interfaces';
import {ExtendedDiagnostic} from '../../lib/interfaces';

type Expectation = [number, number, 'error' | 'warning', string, (string | RegExp)?];

/**
* Verify a list of diagnostics.
*
* @param t - The AVA execution context.
* @param diagnostics - List of diagnostics to verify.
* @param extendedDiagnostics - Object containing numTests and list of diagnostics to verify
* @param expectations - Expected diagnostics.
*/
export const verify = (t: ExecutionContext, diagnostics: Diagnostic[], expectations: Expectation[]) => {
export const verify = (t: ExecutionContext, extendedDiagnostics: ExtendedDiagnostic, expectations: Expectation[]) => {
const diagnostics = extendedDiagnostics.diagnostics;
t.true(diagnostics.length === expectations.length);

for (const [index, diagnostic] of diagnostics.entries()) {
Expand Down
39 changes: 39 additions & 0 deletions source/test/test.ts
Expand Up @@ -220,3 +220,42 @@ test('strict types', async t => {

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

test('typings in custom directory', async t => {
const diagnostics = await tsd({
cwd: path.join(__dirname, 'fixtures/typings-custom-dir'),
typingsFile: 'utils/index.d.ts'
});

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

test('specify test files manually', async t => {
const diagnostics = await tsd({
cwd: path.join(__dirname, 'fixtures/specify-test-files'),
testFiles: [
'unknown.test.ts'
]
});

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

test('fails if typings file is not found in the specified path', async t => {
const error = await t.throwsAsync(tsd({
cwd: path.join(__dirname, 'fixtures/typings-custom-dir'),
typingsFile: 'unknown.d.ts'
}));

t.is(error.message, 'The type definition `unknown.d.ts` does not exist. Create one and try again.');
});

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

t.is(diagnostics.numTests, 2);
});