Skip to content

Commit

Permalink
Fix typescript rechecking after files change. (#4187)
Browse files Browse the repository at this point in the history
* Fix bug where ts files wouldn't be re-checked after changes.

* Try to get better code formatting.

* Add unit test and use ParseConfigHost.

* Improve the unit test.

* Fix bug so it works on Windows too.

* Ignore flow error from incomplete flow-typed definitions.
  • Loading branch information
astegmaier authored Feb 25, 2020
1 parent 04f2e66 commit 7418b91
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 14 deletions.
64 changes: 62 additions & 2 deletions packages/core/integration-tests/test/ts-validation.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
import assert from 'assert';
import path from 'path';
import {bundle, normalizeFilePath} from '@parcel/test-utils';
import {
bundle,
bundler,
getNextBuild,
normalizeFilePath,
outputFS,
overlayFS,
run,
} from '@parcel/test-utils';
import defaultConfigContents from '@parcel/config-default';

const config = {
...defaultConfigContents,
validators: {
'*.{ts,tsx}': ['@parcel/validator-typescript'],
},
reporters: [],
filePath: require.resolve('@parcel/config-default'),
};

const inputDir = path.join(__dirname, '/ts-validator');

describe('ts-validator', function() {
let subscription;
afterEach(async () => {
if (subscription) {
await subscription.unsubscribe();
}
subscription = null;
});

it('should throw validation error on typescript typing errors', async function() {
let didThrow = false;
let entry = normalizeFilePath(
Expand All @@ -38,4 +55,47 @@ describe('ts-validator', function() {

assert(didThrow);
});

it('should re-run when .ts files change', async function() {
await outputFS.mkdirp(inputDir);
await outputFS.writeFile(path.join(inputDir, '/tsconfig.json'), `{}`);
await outputFS.writeFile(
path.join(inputDir, '/index.ts'),
`export const message: number = "This is a type error!"`,
);

let b = bundler(path.join(inputDir, '/index.ts'), {
inputFS: overlayFS,
defaultConfig: config,
});
subscription = await b.watch();
let buildEvent = await getNextBuild(b);
assert.equal(buildEvent.type, 'buildFailure');
assert.equal(buildEvent.diagnostics.length, 1);
assert.equal(
buildEvent.diagnostics[0].message,
"Type '\"This is a type error!\"' is not assignable to type 'number'.",
);

await outputFS.writeFile(
path.join(inputDir, '/index.ts'),
`export const message: string = "The type error is fixed!"`,
);
buildEvent = await getNextBuild(b);
assert.equal(buildEvent.type, 'buildSuccess');
let output = await run(buildEvent.bundleGraph);
assert.equal(output.message, 'The type error is fixed!');

await outputFS.writeFile(
path.join(inputDir, '/index.ts'),
`export const message: boolean = "Now it is back!"`,
);
buildEvent = await getNextBuild(b);
assert.equal(buildEvent.type, 'buildFailure');
assert.equal(buildEvent.diagnostics.length, 1);
assert.equal(
buildEvent.diagnostics[0].message,
"Type '\"Now it is back!\"' is not assignable to type 'boolean'.",
);
});
});
7 changes: 5 additions & 2 deletions packages/utils/ts-utils/src/LanguageServiceHost.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ export class LanguageServiceHost extends FSHost {
}

invalidate(fileName: FilePath) {
const entry = this.files[fileName];
// When the typescript language server calls "getScriptVersion", it will normalize paths for cross-platform (e.g. C:\myFile.ts on Windows becomes C:/myFile.ts). We need to do the same thing.
// $FlowFixMe getNormalizedAbsolutePath is missing from the flow-typed definition.
const normalizedFileName = this.ts.getNormalizedAbsolutePath(fileName);
const entry = this.files[normalizedFileName];

if (entry) {
entry.version++;
} else {
this.files[fileName] = {
this.files[normalizedFileName] = {
version: 0,
};
}
Expand Down
30 changes: 20 additions & 10 deletions packages/validators/typescript/src/TypeScriptValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import type {DiagnosticCodeFrame} from '@parcel/diagnostic';
import path from 'path';
import {md5FromObject} from '@parcel/utils';
import {Validator} from '@parcel/plugin';
import {LanguageServiceHost} from '@parcel/ts-utils';
import {LanguageServiceHost, ParseConfigHost} from '@parcel/ts-utils';

let langServiceCache = {};
let langServiceCache: {
[configHash: string]: {|host: LanguageServiceHost, service: any|},
...,
} = {};

type TSValidatorConfig = {|
filepath: string | null,
Expand Down Expand Up @@ -45,21 +48,28 @@ export default new Validator({
if (tsconfig && !langServiceCache[configHash]) {
let parsedCommandLine = ts.parseJsonConfigFileContent(
tsconfig,
ts.sys,
new ParseConfigHost(options.inputFS, ts),
baseDir,
);

langServiceCache[configHash] = ts.createLanguageService(
new LanguageServiceHost(options.inputFS, ts, parsedCommandLine),
ts.createDocumentRegistry(),
const host = new LanguageServiceHost(
options.inputFS,
ts,
parsedCommandLine,
);
langServiceCache[configHash] = {
host,
service: ts.createLanguageService(host, ts.createDocumentRegistry()),
};
}

if (!langServiceCache[configHash]) return;

const diagnostics = langServiceCache[configHash].getSemanticDiagnostics(
asset.filePath,
);
// Make sure that when the typescript language service asks us for this file, we let it know that there is a new version.
langServiceCache[configHash].host.invalidate(asset.filePath);

const diagnostics = langServiceCache[
configHash
].service.getSemanticDiagnostics(asset.filePath);

let validatorResult = {
warnings: [],
Expand Down

0 comments on commit 7418b91

Please sign in to comment.