Skip to content

Commit 481c177

Browse files
author
roman.vasilev
committed
fix: Update source bug when using eslint in watch mode
1 parent e2e7693 commit 481c177

File tree

5 files changed

+104
-68
lines changed

5 files changed

+104
-68
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"test:r": "npm run mocha -- src/*.spec.ts",
1111
"mocha": "node -r ts-node/register/transpile-only node_modules/mocha/bin/_mocha --timeout 5000",
1212
"test:w": "npm run mocha -- --watch-extensions ts --watch src/**/*.spec.ts",
13-
"test:d": "node --inspect-brk -r ts-node/register/transpile-only node_modules/mocha/bin/_mocha --no-timeouts src/**/*.spec.ts",
13+
"test:d": "node --inspect-brk -r ts-node/register/transpile-only node_modules/mocha/bin/_mocha --no-timeouts --watch-extensions ts --watch src/**/*.spec.ts",
1414
"tscheck": "echo tscheck... && tsc --noEmit",
1515
"tscheck:w": "npm run tscheck -- --watch",
1616
"tsclint": "tsc --noEmit --pretty --strict --forceConsistentCasingInFileNames --noImplicitReturns --noImplicitThis --noUnusedLocals --noUnusedParameters",

src/create-program.ts

-52
This file was deleted.

src/index.spec.ts

+44-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import * as ts from 'typescript';
12
import * as assert from 'assert';
23
import * as lib from './index';
4+
import mockFs = require('mock-fs');
35

46
const root = process.cwd();
57

@@ -28,13 +30,13 @@ describe('tsconfig-files', () => {
2830

2931
it('get source file which are not in files', () => {
3032
const testFile = `${root}/test-project/file.spec.ts`;
31-
const sourceFile = service.getSourceFile(testFile);
33+
const sourceFile = service.getSourceFile(testFile, undefined);
3234
assert(sourceFile);
3335
});
3436

3537
it('typescript checker (file which is not defined in tsconfig)', () => {
3638
const testFile = `${root}/test-project/file.spec.ts`;
37-
const sourceFile = service.getSourceFile(testFile);
39+
const sourceFile = service.getSourceFile(testFile, undefined);
3840
const checker = service.getProgram().getTypeChecker();
3941
const [itstmt] = sourceFile.statements.filter(x => x.getText() === `it('example test');`);
4042
const itid = (itstmt as any).expression.expression;
@@ -58,7 +60,7 @@ describe('create service', () => {
5860
const testFile = `${root}/test-project/errors.ts`;
5961
const sourceFile = service.getProgram().getSourceFile(testFile);
6062
assert(sourceFile);
61-
const diagnostics = service.getDiagnostics(testFile);
63+
const diagnostics = service.getDiagnostics(testFile, undefined);
6264
assert.equal(diagnostics.length, 2);
6365
assert.equal(diagnostics[0].messageText, `Type '1' is not assignable to type 'string'.`);
6466
assert.equal(diagnostics[1].messageText, `Type '"foo"' is not assignable to type 'number'.`);
@@ -68,48 +70,81 @@ describe('create service', () => {
6870
const testFile = `${root}/test-project/number.ts`;
6971
const sourceFile = service.getProgram().getSourceFile(testFile);
7072
assert(sourceFile);
71-
const diagnostics = service.getDiagnostics(testFile);
73+
const diagnostics = service.getDiagnostics(testFile, undefined);
7274
assert.equal(diagnostics.length, 0);
7375
});
7476

7577
it('built in', () => {
7678
const testFile = `${root}/test-project/builtin.ts`;
7779
const sourceFile = service.getProgram().getSourceFile(testFile);
7880
assert(sourceFile);
79-
const diagnostics = service.getDiagnostics(testFile);
81+
const diagnostics = service.getDiagnostics(testFile, undefined);
8082
assert.equal(diagnostics.length, 0);
8183
});
8284

8385
it('types', () => {
8486
const testFile = `${root}/test-project/types.ts`;
8587
const sourceFile = service.getProgram().getSourceFile(testFile);
8688
assert(sourceFile);
87-
const diagnostics = service.getDiagnostics(testFile);
89+
const diagnostics = service.getDiagnostics(testFile, undefined);
8890
assert.equal(diagnostics.length, 0);
8991
});
9092

9193
it('decorator', () => {
9294
const testFile = `${root}/test-project/decorator.ts`;
9395
const sourceFile = service.getProgram().getSourceFile(testFile);
9496
assert(sourceFile);
95-
const diagnostics = service.getDiagnostics(testFile);
97+
const diagnostics = service.getDiagnostics(testFile, undefined);
9698
assert.equal(diagnostics.length, 0);
9799
});
98100

99101
it('global types', () => {
100102
const testFile = `${root}/test-project/global-types.ts`;
101103
const sourceFile = service.getProgram().getSourceFile(testFile);
102104
assert(sourceFile);
103-
const diagnostics = service.getDiagnostics(testFile);
105+
const diagnostics = service.getDiagnostics(testFile, undefined);
104106
assert.equal(diagnostics.length, 0);
105107
});
106108

107109
it('date', () => {
108110
const testFile = `${root}/test-project/date.ts`;
109111
const sourceFile = service.getProgram().getSourceFile(testFile);
110112
assert(sourceFile);
111-
const diagnostics = service.getDiagnostics(testFile);
113+
const diagnostics = service.getDiagnostics(testFile, undefined);
112114
assert.equal(diagnostics.length, 0);
113115
});
114116

115117
});
118+
119+
describe('source file of changed file', () => {
120+
121+
const testFile = `${root}/test-project/amok.ts`;
122+
const configFile = `${root}/test-project/tsconfig-empty.json`;
123+
let sourceFile: ts.SourceFile;
124+
125+
before(() => {
126+
mockFs({
127+
[testFile]: 'const x = 1',
128+
[configFile]: '{}',
129+
});
130+
service = lib.createService({ configFile });
131+
sourceFile = service.getProgram().getSourceFile(testFile);
132+
});
133+
134+
after(() => {
135+
mockFs.restore();
136+
});
137+
138+
it('smoke', () => {
139+
assert(service);
140+
assert(sourceFile);
141+
});
142+
143+
it('changes should be reflected', () => {
144+
mockFs({ [testFile]: 'let x = 2' });
145+
sourceFile = service.getSourceFile(testFile, 'let x = 2');
146+
assert(sourceFile);
147+
assert.equal(sourceFile.getText(), 'let x = 2');
148+
});
149+
150+
});

src/index.ts

+54-6
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
11
import * as ts from 'typescript';
2-
import { createProgram } from './create-program';
32
import { getSourceFile } from './get-source-file';
3+
import { dirname, resolve } from 'path';
4+
import { existsSync, readFileSync } from 'fs';
45

56
type createServiceOptions = {
67
configFile: string;
78
compilerOptions?: ts.CompilerOptions;
9+
projectDirectory?: string;
810
};
911

1012
export function createService({ compilerOptions, configFile }: createServiceOptions) {
11-
let { program, host } = createProgram({ configFile, compilerOptions });
13+
let { program, compilerHost } = createTypescriptServices({ configFile, compilerOptions });
1214
const api = {
1315
getProgram: () => program,
14-
getSourceFile: (fileName: string, sourceText?: string) => {
15-
// todo: fix me optimization sourceText is not used
16+
getSourceFile: (fileName: string, sourceText: string) => {
1617
let sourceFile = program.getSourceFile(fileName);
1718
if (sourceFile === undefined) {
19+
fileName = fileName.replace(/\\/g, '/');
1820
const rootFileNames = [...program.getRootFileNames(), fileName];
19-
program = ts.createProgram(rootFileNames, program.getCompilerOptions(), host, program);
21+
program = ts.createProgram(rootFileNames, program.getCompilerOptions(), compilerHost, program);
22+
sourceFile = program.getSourceFile(fileName);
23+
} else if (sourceFile.text !== sourceText) {
24+
program = ts.createProgram(program.getRootFileNames(), program.getCompilerOptions(), compilerHost, program);
2025
sourceFile = program.getSourceFile(fileName);
2126
}
2227
return sourceFile || getSourceFile(program, fileName, sourceText);
2328
},
24-
getDiagnostics: (fileName: string, sourceText?: string) => {
29+
getDiagnostics: (fileName: string, sourceText: string) => {
2530
const sourceFile = api.getSourceFile(fileName, sourceText);
2631
return [
2732
...program.getSyntacticDiagnostics(sourceFile),
@@ -31,3 +36,46 @@ export function createService({ compilerOptions, configFile }: createServiceOpti
3136
};
3237
return api;
3338
}
39+
40+
function createTypescriptServices({ configFile, projectDirectory = dirname(configFile), compilerOptions = {} }: createServiceOptions) {
41+
const { config, error } = ts.readConfigFile(configFile, ts.sys.readFile);
42+
if (error !== undefined) {
43+
throw new Error(ts.formatDiagnostics([error], {
44+
getCanonicalFileName: f => f,
45+
getCurrentDirectory: process.cwd,
46+
getNewLine: () => '\n',
47+
}));
48+
}
49+
const parseConfigHost: ts.ParseConfigHost = {
50+
fileExists: (path: string) => {
51+
return existsSync(path);
52+
},
53+
readDirectory: ts.sys.readDirectory,
54+
readFile: (file) => {
55+
return readFileSync(file, 'utf8');
56+
},
57+
useCaseSensitiveFileNames: false,
58+
};
59+
config.compilerOptions = { ...(config.compilerOptions || {}), ...compilerOptions };
60+
const parsed = ts.parseJsonConfigFileContent(config, parseConfigHost, resolve(projectDirectory), {
61+
noEmit: true,
62+
sourceMap: false,
63+
inlineSources: false,
64+
inlineSourceMap: false,
65+
});
66+
if (parsed.errors !== undefined) {
67+
// ignore warnings and 'TS18003: No inputs were found in config file ...'
68+
const errors = parsed.errors.filter(d => d.category === ts.DiagnosticCategory.Error && d.code !== 18003);
69+
if (errors.length !== 0) {
70+
throw new Error(ts.formatDiagnostics(errors, {
71+
getCanonicalFileName: f => f,
72+
getCurrentDirectory: process.cwd,
73+
getNewLine: () => '\n',
74+
}));
75+
}
76+
}
77+
const compilerHost = ts.createCompilerHost(parsed.options, true);
78+
const program = ts.createProgram(parsed.fileNames, parsed.options, compilerHost);
79+
80+
return { program, compilerHost };
81+
}

test-project/tsconfig-empty.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"files": [
3+
"file.ts"
4+
]
5+
}

0 commit comments

Comments
 (0)