Skip to content

Commit d893724

Browse files
Keen Yee Liaukyliau
authored andcommitted
fix: retain typecheck files after project reload
It turns out typecheck files are not retained even after the fix in angular/angular#40162. For a complete explanation of the situation, see prior issue: #1030 This is because even after adding the typecheck files to the list of files to retain, tsserver will still remove them from a project if they do not exist on disk: https://github.com/microsoft/TypeScript/blob/3c32f6e154ead6749b76ec9c19cbfdd2acad97d6/src/server/editorServices.ts#L2188-L2193 In order to force tsserver to retain the files, we need to fake their existence. This is done in the `ServerHost`. Admittedly, this is not a great fix because it relies on the knowledge of the typecheck file suffix, which is an implementation detail in the compiler. The alternative is to explore the concept of "dynamic files", which are files that exist only in memory and not on disk. This would require changes to the Ivy compiler and `@angular/language-service`. Ideally, the compiler should ask `@angular/language-service` how to name the typecheck files, so that the naming is dictated by LS rather than the compiler. fix #1090
1 parent e6caf2b commit d893724

File tree

3 files changed

+42
-3
lines changed

3 files changed

+42
-3
lines changed

integration/lsp/ivy_spec.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import * as fs from 'fs';
10+
import {promisify} from 'util';
1011
import {MessageConnection} from 'vscode-jsonrpc';
1112
import * as lsp from 'vscode-languageserver-protocol';
1213
import {URI} from 'vscode-uri';
@@ -15,7 +16,9 @@ import {ProjectLanguageService, ProjectLanguageServiceParams, SuggestStrictMode,
1516
import {NgccProgress, NgccProgressToken, NgccProgressType} from '../../common/progress';
1617
import {GetTcbRequest} from '../../common/requests';
1718

18-
import {APP_COMPONENT, createConnection, createTracer, FOO_COMPONENT, FOO_TEMPLATE, initializeServer, openTextDocument, TSCONFIG} from './test_utils';
19+
import {APP_COMPONENT, createConnection, createTracer, FOO_COMPONENT, FOO_TEMPLATE, initializeServer, openTextDocument, PROJECT_PATH, TSCONFIG} from './test_utils';
20+
21+
const setTimeoutP = promisify(setTimeout);
1922

2023
describe('Angular Ivy language server', () => {
2124
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; /* 10 seconds */
@@ -125,6 +128,30 @@ describe('Angular Ivy language server', () => {
125128
});
126129
});
127130

131+
describe('project reload', () => {
132+
const dummy = `${PROJECT_PATH}/node_modules/__foo__`;
133+
134+
afterEach(() => {
135+
fs.unlinkSync(dummy);
136+
});
137+
138+
it('should retain typecheck files', async () => {
139+
openTextDocument(client, APP_COMPONENT);
140+
const languageServiceEnabled = await waitForNgcc(client);
141+
expect(languageServiceEnabled).toBeTrue();
142+
// Create a file in node_modules, this will trigger a project reload via
143+
// the directory watcher
144+
fs.writeFileSync(dummy, '');
145+
// Project reload happens after 250ms delay
146+
// https://github.com/microsoft/TypeScript/blob/3c32f6e154ead6749b76ec9c19cbfdd2acad97d6/src/server/editorServices.ts#L957
147+
await setTimeoutP(500);
148+
// The following operation would result in compiler crash if typecheck
149+
// files are not retained after project reload
150+
const diagnostics = await getDiagnosticsForFile(client, APP_COMPONENT);
151+
expect(diagnostics.length).toBe(0);
152+
});
153+
});
154+
128155
describe('renaming', () => {
129156
describe('from template files', () => {
130157
beforeEach(async () => {

integration/lsp/test_utils.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import * as lsp from 'vscode-languageserver-protocol';
1414

1515
const SERVER_PATH = resolve(__dirname, '../../../dist/npm/server/index.js');
1616
const PACKAGE_ROOT = resolve(__dirname, '../../..');
17-
const PROJECT_PATH = `${PACKAGE_ROOT}/integration/project`;
17+
export const PROJECT_PATH = `${PACKAGE_ROOT}/integration/project`;
1818
export const APP_COMPONENT = `${PROJECT_PATH}/app/app.component.ts`;
1919
export const FOO_TEMPLATE = `${PROJECT_PATH}/app/foo.component.html`;
2020
export const FOO_COMPONENT = `${PROJECT_PATH}/app/foo.component.ts`;
@@ -40,7 +40,11 @@ export function createConnection(serverOptions: ServerOptions): MessageConnectio
4040
// uncomment to debug server process
4141
// execArgv: ['--inspect-brk=9330']
4242
});
43-
43+
server.on('close', (code: number) => {
44+
if (code !== null && code !== 0) {
45+
throw new Error(`Server exited with code: ${code}`);
46+
}
47+
});
4448
const connection = createMessageConnection(
4549
new IPCMessageReader(server),
4650
new IPCMessageWriter(server),

server/src/server_host.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ export class ServerHost implements ts.server.ServerHost {
6565
}
6666

6767
fileExists(path: string): boolean {
68+
// When a project is reloaded (due to changes in node_modules for example),
69+
// the typecheck files ought to be retained. However, if they do not exist
70+
// on disk, tsserver will remove them from project. See
71+
// https://github.com/microsoft/TypeScript/blob/3c32f6e154ead6749b76ec9c19cbfdd2acad97d6/src/server/editorServices.ts#L2188-L2193
72+
// To fix this, we fake the existence of the typecheck files.
73+
if (path.endsWith('.ngtypecheck.ts')) {
74+
return true;
75+
}
6876
return ts.sys.fileExists(path);
6977
}
7078

0 commit comments

Comments
 (0)