Skip to content

Commit f13e6d4

Browse files
authored
enable syntactic features if project size exceeded the limit, send events when state of language service changes (microsoft#12190)
* enable syntactic features if project size exceeded the limit, send events when state of language service changes * allow getting compiler options diagnostics when language service is disabled
2 parents 04aaa32 + 3651183 commit f13e6d4

File tree

9 files changed

+285
-93
lines changed

9 files changed

+285
-93
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3089,6 +3089,10 @@
30893089
"category": "Error",
30903090
"code": 9003
30913091
},
3092+
"Language service is disabled.": {
3093+
"category": "Error",
3094+
"code": 9004
3095+
},
30923096
"JSX attributes must only be assigned a non-empty 'expression'.": {
30933097
"category": "Error",
30943098
"code": 17000

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@ namespace ts.projectSystem {
154154
params.executingFilePath || getExecutingFilePathFromLibFile(),
155155
params.currentDirectory || "/",
156156
fileOrFolderList);
157-
host.createFileOrFolder(safeList, /*createParentDirectory*/ true);
158157
return host;
159158
}
160159

@@ -355,7 +354,8 @@ namespace ts.projectSystem {
355354
reloadFS(filesOrFolders: FileOrFolder[]) {
356355
this.filesOrFolders = filesOrFolders;
357356
this.fs = createFileMap<FSEntry>();
358-
for (const fileOrFolder of filesOrFolders) {
357+
// always inject safelist file in the list of files
358+
for (const fileOrFolder of filesOrFolders.concat(safeList)) {
359359
const path = this.toPath(fileOrFolder.path);
360360
const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory);
361361
if (typeof fileOrFolder.content === "string") {
@@ -1585,6 +1585,104 @@ namespace ts.projectSystem {
15851585
projectService.closeClientFile(file1.path);
15861586
checkNumberOfProjects(projectService, { configuredProjects: 0 });
15871587
});
1588+
1589+
it("language service disabled events are triggered", () => {
1590+
const f1 = {
1591+
path: "/a/app.js",
1592+
content: "let x = 1;"
1593+
};
1594+
const f2 = {
1595+
path: "/a/largefile.js",
1596+
content: ""
1597+
};
1598+
const config = {
1599+
path: "/a/jsconfig.json",
1600+
content: "{}"
1601+
};
1602+
const configWithExclude = {
1603+
path: config.path,
1604+
content: JSON.stringify({ exclude: ["largefile.js"] })
1605+
};
1606+
const host = createServerHost([f1, f2, config]);
1607+
const originalGetFileSize = host.getFileSize;
1608+
host.getFileSize = (filePath: string) =>
1609+
filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath);
1610+
1611+
let lastEvent: server.ProjectLanguageServiceStateEvent;
1612+
const session = createSession(host, /*typingsInstaller*/ undefined, e => {
1613+
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ContextEvent) {
1614+
return;
1615+
}
1616+
assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent);
1617+
lastEvent = <server.ProjectLanguageServiceStateEvent>e;
1618+
});
1619+
session.executeCommand(<protocol.OpenRequest>{
1620+
seq: 0,
1621+
type: "request",
1622+
command: "open",
1623+
arguments: { file: f1.path }
1624+
});
1625+
const projectService = session.getProjectService();
1626+
checkNumberOfProjects(projectService, { configuredProjects: 1 });
1627+
const project = projectService.configuredProjects[0];
1628+
assert.isFalse(project.languageServiceEnabled, "Language service enabled");
1629+
assert.isTrue(!!lastEvent, "should receive event");
1630+
assert.equal(lastEvent.data.project, project, "project name");
1631+
assert.isFalse(lastEvent.data.languageServiceEnabled, "Language service state");
1632+
1633+
host.reloadFS([f1, f2, configWithExclude]);
1634+
host.triggerFileWatcherCallback(config.path, /*removed*/ false);
1635+
1636+
checkNumberOfProjects(projectService, { configuredProjects: 1 });
1637+
assert.isTrue(project.languageServiceEnabled, "Language service enabled");
1638+
assert.equal(lastEvent.data.project, project, "project");
1639+
assert.isTrue(lastEvent.data.languageServiceEnabled, "Language service state");
1640+
});
1641+
1642+
it("syntactic features work even if language service is disabled", () => {
1643+
const f1 = {
1644+
path: "/a/app.js",
1645+
content: "let x = 1;"
1646+
};
1647+
const f2 = {
1648+
path: "/a/largefile.js",
1649+
content: ""
1650+
};
1651+
const config = {
1652+
path: "/a/jsconfig.json",
1653+
content: "{}"
1654+
};
1655+
const host = createServerHost([f1, f2, config]);
1656+
const originalGetFileSize = host.getFileSize;
1657+
host.getFileSize = (filePath: string) =>
1658+
filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath);
1659+
let lastEvent: server.ProjectLanguageServiceStateEvent;
1660+
const session = createSession(host, /*typingsInstaller*/ undefined, e => {
1661+
if (e.eventName === server.ConfigFileDiagEvent) {
1662+
return;
1663+
}
1664+
assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent);
1665+
lastEvent = <server.ProjectLanguageServiceStateEvent>e;
1666+
});
1667+
session.executeCommand(<protocol.OpenRequest>{
1668+
seq: 0,
1669+
type: "request",
1670+
command: "open",
1671+
arguments: { file: f1.path }
1672+
});
1673+
1674+
const projectService = session.getProjectService();
1675+
checkNumberOfProjects(projectService, { configuredProjects: 1 });
1676+
const project = projectService.configuredProjects[0];
1677+
assert.isFalse(project.languageServiceEnabled, "Language service enabled");
1678+
assert.isTrue(!!lastEvent, "should receive event");
1679+
assert.equal(lastEvent.data.project, project, "project name");
1680+
assert.isFalse(lastEvent.data.languageServiceEnabled, "Language service state");
1681+
1682+
const options = projectService.getFormatCodeOptions();
1683+
const edits = project.getLanguageService().getFormattingEditsForDocument(f1.path, options);
1684+
assert.deepEqual(edits, [{ span: createTextSpan(/*start*/ 7, /*length*/ 3), newText: " " }]);
1685+
});
15881686
});
15891687

15901688
describe("Proper errors", () => {

src/server/editorServices.ts

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,26 @@
1010
namespace ts.server {
1111
export const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024;
1212

13-
export type ProjectServiceEvent =
14-
{ eventName: "context", data: { project: Project, fileName: NormalizedPath } } | { eventName: "configFileDiag", data: { triggerFile: string, configFileName: string, diagnostics: Diagnostic[] } };
13+
export const ContextEvent = "context";
14+
export const ConfigFileDiagEvent = "configFileDiag";
15+
export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState";
16+
17+
export interface ContextEvent {
18+
eventName: typeof ContextEvent;
19+
data: { project: Project; fileName: NormalizedPath };
20+
}
21+
22+
export interface ConfigFileDiagEvent {
23+
eventName: typeof ConfigFileDiagEvent;
24+
data: { triggerFile: string, configFileName: string, diagnostics: Diagnostic[] };
25+
}
26+
27+
export interface ProjectLanguageServiceStateEvent {
28+
eventName: typeof ProjectLanguageServiceStateEvent;
29+
data: { project: Project, languageServiceEnabled: boolean };
30+
}
31+
32+
export type ProjectServiceEvent = ContextEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent;
1533

1634
export interface ProjectServiceEventHandler {
1735
(event: ProjectServiceEvent): void;
@@ -282,6 +300,16 @@ namespace ts.server {
282300
return this.compilerOptionsForInferredProjects;
283301
}
284302

303+
onUpdateLanguageServiceStateForProject(project: Project, languageServiceEnabled: boolean) {
304+
if (!this.eventHandler) {
305+
return;
306+
}
307+
this.eventHandler(<ProjectLanguageServiceStateEvent>{
308+
eventName: ProjectLanguageServiceStateEvent,
309+
data: { project, languageServiceEnabled }
310+
});
311+
}
312+
285313
updateTypingsForProject(response: SetTypings | InvalidateCachedTypings): void {
286314
const project = this.findProject(response.projectName);
287315
if (!project) {
@@ -430,7 +458,10 @@ namespace ts.server {
430458
}
431459

432460
for (const openFile of this.openFiles) {
433-
this.eventHandler({ eventName: "context", data: { project: openFile.getDefaultProject(), fileName: openFile.fileName } });
461+
this.eventHandler(<ContextEvent>{
462+
eventName: ContextEvent,
463+
data: { project: openFile.getDefaultProject(), fileName: openFile.fileName }
464+
});
434465
}
435466
}
436467

@@ -834,8 +865,8 @@ namespace ts.server {
834865
return;
835866
}
836867

837-
this.eventHandler({
838-
eventName: "configFileDiag",
868+
this.eventHandler(<ConfigFileDiagEvent>{
869+
eventName: ConfigFileDiagEvent,
839870
data: { configFileName, diagnostics: diagnostics || [], triggerFile }
840871
});
841872
}
@@ -1013,7 +1044,7 @@ namespace ts.server {
10131044
const useExistingProject = this.useSingleInferredProject && this.inferredProjects.length;
10141045
const project = useExistingProject
10151046
? this.inferredProjects[0]
1016-
: new InferredProject(this, this.documentRegistry, /*languageServiceEnabled*/ true, this.compilerOptionsForInferredProjects);
1047+
: new InferredProject(this, this.documentRegistry, this.compilerOptionsForInferredProjects);
10171048

10181049
project.addRoot(root);
10191050

src/server/lsHost.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
/// <reference path="scriptInfo.ts" />
44

55
namespace ts.server {
6-
export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost, ServerLanguageServiceHost {
6+
export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost {
77
private compilationSettings: ts.CompilerOptions;
8-
private readonly resolvedModuleNames= createFileMap<Map<ResolvedModuleWithFailedLookupLocations>>();
8+
private readonly resolvedModuleNames = createFileMap<Map<ResolvedModuleWithFailedLookupLocations>>();
99
private readonly resolvedTypeReferenceDirectives = createFileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
1010
private readonly getCanonicalFileName: (fileName: string) => string;
1111

0 commit comments

Comments
 (0)