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

Add full diagnostics to tserror #1706

Merged
14 changes: 12 additions & 2 deletions src/index.ts
Expand Up @@ -470,14 +470,24 @@ export const DEFAULTS: RegisterOptions = {
export class TSError extends BaseError {
name = 'TSError';
diagnosticText!: string;
diagnostics!: ReadonlyArray<_ts.Diagnostic>;

constructor(diagnosticText: string, public diagnosticCodes: number[]) {
constructor(
diagnosticText: string,
public diagnosticCodes: number[],
diagnostics: ReadonlyArray<_ts.Diagnostic> = []
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this argument optional to avoid a breaking change to our API surface, since TSError is part of our API?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this argument optional to avoid a breaking change to our API surface, since TSError is part of our API?

Yes, that's exactly why it's optional — I didn't want to make this a breaking change.

) {
super(`⨯ Unable to compile TypeScript:\n${diagnosticText}`);
Object.defineProperty(this, 'diagnosticText', {
configurable: true,
writable: true,
value: diagnosticText,
});
Object.defineProperty(this, 'diagnostics', {
configurable: true,
writable: true,
value: diagnostics,
});
}

/**
Expand Down Expand Up @@ -829,7 +839,7 @@ export function createFromPreloadedConfig(
function createTSError(diagnostics: ReadonlyArray<_ts.Diagnostic>) {
const diagnosticText = formatDiagnostics(diagnostics, diagnosticHost);
const diagnosticCodes = diagnostics.map((x) => x.code);
return new TSError(diagnosticText, diagnosticCodes);
return new TSError(diagnosticText, diagnosticCodes, diagnostics);
}

function reportTSError(configDiagnosticList: _ts.Diagnostic[]) {
Expand Down
91 changes: 91 additions & 0 deletions src/test/index.spec.ts
Expand Up @@ -31,6 +31,7 @@ import {
CMD_ESM_LOADER_WITHOUT_PROJECT,
EXPERIMENTAL_MODULES_FLAG,
} from './helpers';
import type { TSError } from '..';

const exec = createExec({
cwd: TEST_DIR,
Expand Down Expand Up @@ -974,6 +975,96 @@ test.suite('ts-node', (test) => {
expect(output).toMatch('var x = 10;');
});

test('should throw errors', ({ context: { service } }) => {
let err: unknown = null;

try {
service.compile('new Error(123)', 'test.ts');
} catch (error) {
err = error;
}

if (err === null) {
throw new Error('Command was expected to fail, but it succeeded.');
}

expect((err as Error).message).toMatch(
new RegExp(
"TS2345: Argument of type '123' " +
"is not assignable to parameter of type 'string | undefined'\\."
)
);
});

test('should throw errors with diagnostic text', ({
context: { service },
}) => {
let err: unknown = null;

try {
service.compile('new Error(123)', 'test.ts');
} catch (error) {
err = error;
}

if (err === null) {
throw new Error('Command was expected to fail, but it succeeded.');
}

expect((err as TSError).diagnosticText).toMatch(
new RegExp(
"TS2345: Argument of type '123' " +
"is not assignable to parameter of type 'string | undefined'\\."
)
);
});

test('should throw errors with diagnostic codes', ({
context: { service },
}) => {
let err: unknown = null;

try {
service.compile('new Error(123)', 'test.ts');
} catch (error) {
err = error;
}

if (err === null) {
throw new Error('Command was expected to fail, but it succeeded.');
}

expect((err as TSError).diagnosticCodes).toEqual([2345]);
});

test('should throw errors with complete diagnostic information', ({
context: { service },
}) => {
let err: unknown = null;

try {
service.compile('new Error(123)', 'test.ts');
} catch (error) {
err = error;
}

if (err === null) {
throw new Error('Command was expected to fail, but it succeeded.');
}

const diagnostics = (err as TSError).diagnostics;

expect(diagnostics).toHaveLength(1);
expect(diagnostics[0]).toMatchObject({
code: 2345,
start: 10,
length: 3,
messageText:
"Argument of type '123' " +
"is not assignable to parameter of type 'string | undefined'.",
});
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

Note to self: consider moving these tests into a new spec file, perhaps tserror.spec.ts or throws-diagnostics.spec.ts Consider also combining it with pre-existing tests for diagnostics.


test.suite('should get type information', (test) => {
test('given position of identifier', ({ context: { service } }) => {
expect(
Expand Down