Skip to content

Commit deef947

Browse files
Add full diagnostics to tserror (#1706)
* Add error locations to TSErrors * Simplify tests / code formatting * Expose the full TypeScript diagnostics on TSErrors * Test re-org and deduplication * lintfix * fix * bang head against wall * lintfix * fix * fix * fix * fix * fix * tweaks * Revert "tweaks" This reverts commit 30b78bd. * fix * finally * fix Co-authored-by: Andrew Bradley <[email protected]>
1 parent dc907bc commit deef947

File tree

9 files changed

+108
-14
lines changed

9 files changed

+108
-14
lines changed

package-lock.json

Lines changed: 15 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
],
6363
"scripts": {
6464
"lint": "prettier --check .",
65-
"lint-fix": "prettier --write .",
65+
"lint-fix": "prettier --loglevel warn --write .",
6666
"clean": "rimraf dist tsconfig.schema.json tsconfig.schemastore-schema.json tsconfig.tsbuildinfo tests/ts-node-packed.tgz",
6767
"rebuild": "npm run clean && npm run build",
6868
"build": "npm run build-nopack && npm run build-pack",
@@ -140,7 +140,7 @@
140140
"semver": "^7.1.3",
141141
"throat": "^6.0.1",
142142
"typedoc": "^0.22.10",
143-
"typescript": "4.5.5",
143+
"typescript": "4.6.3",
144144
"typescript-json-schema": "^0.53.0",
145145
"util.promisify": "^1.0.1"
146146
},

src/index.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -471,14 +471,24 @@ export const DEFAULTS: RegisterOptions = {
471471
export class TSError extends BaseError {
472472
name = 'TSError';
473473
diagnosticText!: string;
474+
diagnostics!: ReadonlyArray<_ts.Diagnostic>;
474475

475-
constructor(diagnosticText: string, public diagnosticCodes: number[]) {
476+
constructor(
477+
diagnosticText: string,
478+
public diagnosticCodes: number[],
479+
diagnostics: ReadonlyArray<_ts.Diagnostic> = []
480+
) {
476481
super(`⨯ Unable to compile TypeScript:\n${diagnosticText}`);
477482
Object.defineProperty(this, 'diagnosticText', {
478483
configurable: true,
479484
writable: true,
480485
value: diagnosticText,
481486
});
487+
Object.defineProperty(this, 'diagnostics', {
488+
configurable: true,
489+
writable: true,
490+
value: diagnostics,
491+
});
482492
}
483493

484494
/**
@@ -830,7 +840,7 @@ export function createFromPreloadedConfig(
830840
function createTSError(diagnostics: ReadonlyArray<_ts.Diagnostic>) {
831841
const diagnosticText = formatDiagnostics(diagnostics, diagnosticHost);
832842
const diagnosticCodes = diagnostics.map((x) => x.code);
833-
return new TSError(diagnosticText, diagnosticCodes);
843+
return new TSError(diagnosticText, diagnosticCodes, diagnostics);
834844
}
835845

836846
function reportTSError(configDiagnosticList: _ts.Diagnostic[]) {

src/test/diagnostics.spec.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import type { TSError } from '..';
2+
import { contextTsNodeUnderTest, ts } from './helpers';
3+
import { context, expect } from './testlib';
4+
import * as semver from 'semver';
5+
import { once } from 'lodash';
6+
const test = context(contextTsNodeUnderTest);
7+
8+
test.suite('TSError diagnostics', ({ context }) => {
9+
const test = context(
10+
once(async (t) => {
11+
const service = t.context.tsNodeUnderTest.create({
12+
compilerOptions: { target: 'es5' },
13+
skipProject: true,
14+
});
15+
try {
16+
service.compile('new Error(123)', 'test.ts');
17+
} catch (err) {
18+
return { service, err };
19+
}
20+
return { service, err: undefined };
21+
})
22+
);
23+
24+
const diagnosticCode = 2345;
25+
const diagnosticMessage = semver.satisfies(ts.version, '2.7')
26+
? "Argument of type '123' " +
27+
"is not assignable to parameter of type 'string | undefined'."
28+
: "Argument of type 'number' " +
29+
"is not assignable to parameter of type 'string'.";
30+
const diagnosticErrorMessage = `TS${diagnosticCode}: ${diagnosticMessage}`;
31+
32+
const cwdBefore = process.cwd();
33+
test('should throw errors', ({ log, context: { err, service } }) => {
34+
log({
35+
version: ts.version,
36+
serviceVersion: service.ts.version,
37+
cwdBefore,
38+
cwd: process.cwd(),
39+
configFilePath: service.configFilePath,
40+
config: service.config.options,
41+
});
42+
expect(err).toBeDefined();
43+
expect((err as Error).message).toMatch(diagnosticErrorMessage);
44+
});
45+
46+
test('should throw errors with diagnostic text', ({ context: { err } }) => {
47+
expect((err as TSError).diagnosticText).toMatch(diagnosticErrorMessage);
48+
});
49+
50+
test('should throw errors with diagnostic codes', ({ context: { err } }) => {
51+
expect((err as TSError).diagnosticCodes).toEqual([2345]);
52+
});
53+
54+
test('should throw errors with complete diagnostic information', ({
55+
context: { err },
56+
}) => {
57+
const diagnostics = (err as TSError).diagnostics;
58+
59+
expect(diagnostics).toHaveLength(1);
60+
expect(diagnostics[0]).toMatchObject({
61+
code: 2345,
62+
start: 10,
63+
length: 3,
64+
messageText: expect.stringMatching(diagnosticMessage),
65+
});
66+
});
67+
});

src/test/helpers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export const BIN_SCRIPT_PATH = join(
3434
);
3535
export const BIN_CWD_PATH = join(TEST_DIR, 'node_modules/.bin/ts-node-cwd');
3636
export const BIN_ESM_PATH = join(TEST_DIR, 'node_modules/.bin/ts-node-esm');
37+
38+
process.chdir(TEST_DIR);
3739
//#endregion
3840

3941
//#region command lines

src/test/pluggable-dep-resolution.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { context } from './testlib';
22
import {
33
contextTsNodeUnderTest,
44
resetNodeEnvironment,
5+
TEST_DIR,
56
tsSupportsTsconfigInheritanceViaNodePackages,
67
} from './helpers';
78
import * as expect from 'expect';
@@ -87,7 +88,7 @@ test.suite(
8788

8889
const output = t.context.tsNodeUnderTest
8990
.create({
90-
project: resolve('tests/pluggable-dep-resolution', config),
91+
project: resolve(TEST_DIR, 'pluggable-dep-resolution', config),
9192
})
9293
.compile('', 'index.ts');
9394

src/test/repl/repl-environment.spec.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ test.suite(
137137

138138
/** Every possible ./node_modules directory ascending upwards starting with ./tests/node_modules */
139139
const modulePaths = createModulePaths(TEST_DIR);
140-
const rootModulePaths = createModulePaths(ROOT_DIR);
141140
function createModulePaths(dir: string) {
142141
const modulePaths: string[] = [];
143142
for (let path = dir; ; path = dirname(path)) {
@@ -430,7 +429,7 @@ test.suite(
430429

431430
// Note: vanilla node uses different name. See #1360
432431
stackTest: expect.stringContaining(
433-
` at ${join(ROOT_DIR, '<repl>.ts')}:1:`
432+
` at ${join(TEST_DIR, '<repl>.ts')}:1:`
434433
),
435434
},
436435
});
@@ -455,13 +454,13 @@ test.suite(
455454
modulePath: '.',
456455
moduleFilename: null,
457456
modulePaths: expect.objectContaining({
458-
...[join(ROOT_DIR, `repl/node_modules`), ...rootModulePaths],
457+
...[join(TEST_DIR, `repl/node_modules`), ...modulePaths],
459458
}),
460459
// Note: vanilla node REPL does not set exports
461460
exportsTest: true,
462461
// Note: vanilla node uses different name. See #1360
463462
stackTest: expect.stringContaining(
464-
` at ${join(ROOT_DIR, '<repl>.ts')}:1:`
463+
` at ${join(TEST_DIR, '<repl>.ts')}:1:`
465464
),
466465
moduleAccessorsTest: true,
467466
},

src/test/repl/repl.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,13 +246,13 @@ test.suite('top level await', (_test) => {
246246
'should error with typing information when importing a file with type errors',
247247
async (t) => {
248248
const { stdout, stderr } = await t.context.executeInTlaRepl(
249-
`const {foo} = await import('./tests/repl/tla-import');`,
249+
`const {foo} = await import('./repl/tla-import');`,
250250
'error'
251251
);
252252

253253
expect(stdout).toBe('> > ');
254254
expect(stderr.replace(/\r\n/g, '\n')).toBe(
255-
'tests/repl/tla-import.ts(1,14): error TS2322: ' +
255+
'repl/tla-import.ts(1,14): error TS2322: ' +
256256
(semver.gte(ts.version, '4.0.0')
257257
? `Type 'number' is not assignable to type 'string'.\n`
258258
: `Type '1' is not assignable to type 'string'.\n`) +

src/test/testlib.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import * as expect from 'expect';
1515

1616
export { ExecutionContext, expect };
1717

18+
// HACK ensure ts-node-specific bootstrapping is executed
19+
import './helpers';
20+
1821
// NOTE: this limits concurrency within a single process, but AVA launches
1922
// each .spec file in its own process, so actual concurrency is higher.
2023
const concurrencyLimiter = throat(16);

0 commit comments

Comments
 (0)