Skip to content

Commit

Permalink
Don't log stacktrace if CLI succeeds (#182)
Browse files Browse the repository at this point in the history
  • Loading branch information
tommy-mitchell committed Mar 15, 2023
1 parent c454571 commit 2dc5358
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 34 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -61,6 +61,7 @@
"eslint-config-xo-typescript": "^0.41.1",
"execa": "^5.0.0",
"react": "^16.9.0",
"resolve-from": "^5.0.0",
"rxjs": "^6.5.3",
"typescript": "~4.9.5"
},
Expand Down
33 changes: 23 additions & 10 deletions source/cli.ts
Expand Up @@ -44,26 +44,39 @@ const cli = meow(`
},
});

/**
* Displays a message and exits, conditionally erroring.
*
* @param message The message to display.
* @param isError Whether or not to fail on exit.
*/
const exit = (message: string, {isError = true}: {isError?: boolean} = {}) => {
if (isError) {
console.error(message);
process.exit(1);
} else {
console.log(message);
process.exit(0);
}
};

(async () => {
try {
const cwd = cli.input.length > 0 ? cli.input[0] : process.cwd();
const {typings: typingsFile, files: testFiles, showDiff} = cli.flags;

const options = {cwd, typingsFile, testFiles};

const diagnostics = await tsd(options);
const diagnostics = await tsd({cwd, typingsFile, testFiles});

if (diagnostics.length > 0) {
throw new Error(formatter(diagnostics, showDiff));
const hasErrors = diagnostics.some(diagnostic => diagnostic.severity === 'error');
const formattedDiagnostics = formatter(diagnostics, showDiff);

exit(formattedDiagnostics, {isError: hasErrors});
}
} catch (error: unknown) {
const potentialError = error as Error | undefined;
const errorMessage = potentialError?.stack ?? potentialError?.message;

if (errorMessage) {
console.error(`Error running tsd: ${errorMessage}`);
}
const errorMessage = potentialError?.stack ?? potentialError?.message ?? 'tsd unexpectedly crashed.';

process.exit(1);
exit(`Error running tsd:\n${errorMessage}`);
}
})();
64 changes: 53 additions & 11 deletions source/test/cli.ts
Expand Up @@ -3,6 +3,8 @@ import test from 'ava';
import execa from 'execa';
import readPkgUp from 'read-pkg-up';
import tsd, {formatter} from '..';
import {verifyCli} from './fixtures/utils';
import resolveFrom from 'resolve-from';

interface ExecaError extends Error {
readonly exitCode: number;
Expand All @@ -15,7 +17,11 @@ test('fail if errors are found', async t => {
}));

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

test('succeed if no errors are found', async t => {
Expand All @@ -32,7 +38,11 @@ test('provide a path', async t => {
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./);
verifyCli(t, stderr, [
'✖ 5:19 Argument of type number is not assignable to parameter of type string.',
'',
'1 error',
]);
});

test('cli help flag', async t => {
Expand All @@ -57,7 +67,11 @@ test('cli typings flag', async t => {
}));

t.is(exitCode, 1);
t.true(stderr.includes('✖ 5:19 Argument of type number is not assignable to parameter of type string.'));
verifyCli(t, stderr, [
'✖ 5:19 Argument of type number is not assignable to parameter of type string.',
'',
'1 error',
]);
};

await runTest('--typings');
Expand All @@ -71,7 +85,11 @@ test('cli files flag', async t => {
}));

t.is(exitCode, 1);
t.true(stderr.includes('✖ 5:19 Argument of type number is not assignable to parameter of type string.'));
verifyCli(t, stderr, [
'✖ 5:19 Argument of type number is not assignable to parameter of type string.',
'',
'1 error',
]);
};

await runTest('--files');
Expand All @@ -84,7 +102,11 @@ test('cli files flag array', async t => {
}));

t.is(exitCode, 1);
t.true(stderr.includes('✖ 5:19 Argument of type number is not assignable to parameter of type string.'));
verifyCli(t, stderr, [
'✖ 5:19 Argument of type number is not assignable to parameter of type string.',
'',
'1 error',
]);
});

test('cli typings and files flags', async t => {
Expand All @@ -94,17 +116,29 @@ test('cli typings and files flags', async t => {
const {exitCode, stderr} = t.throws<ExecaError>(() => execa.commandSync(`dist/cli.js -t ${typingsFile} -f ${testFile}`));

t.is(exitCode, 1);
t.true(stderr.includes('✖ 5:19 Argument of type number is not assignable to parameter of type string.'));
verifyCli(t, stderr, [
'✖ 5:19 Argument of type number is not assignable to parameter of type string.',
'',
'1 error',
]);
});

test('tsd logs stacktrace on failure', async t => {
const {exitCode, stderr, stack} = await t.throwsAsync<ExecaError>(execa('../../../cli.js', {
const {exitCode, stderr} = await t.throwsAsync<ExecaError>(execa('../../../cli.js', {
cwd: path.join(__dirname, 'fixtures/empty-package-json')
}));

const nodeModulesPath = path.resolve('node_modules');
const parseJsonPath = resolveFrom.silent(`${nodeModulesPath}/read-pkg`, 'parse-json') ?? `${nodeModulesPath}/index.js`;

t.is(exitCode, 1);
t.true(stderr.includes('Error running tsd: JSONError: Unexpected end of JSON input while parsing empty string'));
t.truthy(stack);
verifyCli(t, stderr, [
'Error running tsd:',
'JSONError: Unexpected end of JSON input while parsing empty string',
`at parseJson (${parseJsonPath}:29:21)`,
`at module.exports (${nodeModulesPath}/read-pkg/index.js:17:15)`,
`at async module.exports (${nodeModulesPath}/read-pkg-up/index.js:14:16)`,
], {startLine: 0});
});

test('exported formatter matches cli results', async t => {
Expand All @@ -114,10 +148,18 @@ test('exported formatter matches cli results', async t => {

const {stderr: cliResults} = await t.throwsAsync<ExecaError>(execa('../../../cli.js', options));

t.true(cliResults.includes('✖ 5:19 Argument of type number is not assignable to parameter of type string.'));
verifyCli(t, cliResults, [
'✖ 5:19 Argument of type number is not assignable to parameter of type string.',
'',
'1 error',
]);

const tsdResults = await tsd(options);
const formattedResults = formatter(tsdResults);

t.true(formattedResults.includes('✖ 5:19 Argument of type number is not assignable to parameter of type string.'));
verifyCli(t, formattedResults, [
'✖ 5:19 Argument of type number is not assignable to parameter of type string.',
'',
'1 error',
]);
});
15 changes: 3 additions & 12 deletions source/test/diff.ts
@@ -1,4 +1,4 @@
import {verifyWithDiff} from './fixtures/utils';
import {verifyWithDiff, verifyCli} from './fixtures/utils';
import execa, {ExecaError} from 'execa';
import path from 'path';
import test from 'ava';
Expand Down Expand Up @@ -41,8 +41,7 @@ test('diff cli', async t => {
const {exitCode, stderr} = await t.throwsAsync<ExecaError>(execa('dist/cli.js', [file, '--show-diff']));

t.is(exitCode, 1);

const expectedLines = [
verifyCli(t, stderr, [
'✖ 8:0 Parameter type { life?: number | undefined; } is declared too wide for argument type { life: number; }.',
'',
'- { life?: number | undefined; }',
Expand All @@ -69,13 +68,5 @@ test('diff cli', async t => {
'+ This is a comment.',
'',
'6 errors'
];

// NOTE: If lines are added to the output in the future startLine and endLine should be adjusted.
const startLine = 2; // Skip tsd error message and file location.
const endLine = startLine + expectedLines.length; // Grab diff output only and skip stack trace.

const receivedLines = stderr.trim().split('\n').slice(startLine, endLine).map(line => line.trim());

t.deepEqual(receivedLines, expectedLines);
]);
});
21 changes: 20 additions & 1 deletion source/test/fixtures/utils.ts
Expand Up @@ -94,7 +94,7 @@ export const verifyWithFileName = (
* @param diagnostics - List of diagnostics to verify.
* @param expectations - Expected diagnostics.
*/
export const verifyWithDiff = (
export const verifyWithDiff = (
t: ExecutionContext,
diagnostics: Diagnostic[],
expectations: ExpectationWithDiff[]
Expand All @@ -117,3 +117,22 @@ export const verifyWithFileName = (

t.deepEqual(diagnosticObjs, expectationObjs, 'Received diagnostics that are different from expectations!');
};

/**
* Verify a list of diagnostics reported from the CLI.
*
* @param t - The AVA execution context.
* @param diagnostics - List of diagnostics to verify.
* @param expectations - Expected diagnostics.
* @param startLine - Optionally specify how many lines to skip from start.
*/
export const verifyCli = (
t: ExecutionContext,
diagnostics: string,
expectedLines: string[],
{startLine}: {startLine: number} = {startLine: 1} // Skip file location.
) => {
const receivedLines = diagnostics.trim().split('\n').slice(startLine).map(line => line.trim());

t.deepEqual(receivedLines, expectedLines, 'Received diagnostics that are different from expectations!');
};

0 comments on commit 2dc5358

Please sign in to comment.