Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3089,6 +3089,10 @@
"category": "Error",
"code": 9003
},
"Language service is disabled.": {
"category": "Error",
"code": 9004
},
"JSX attributes must only be assigned a non-empty 'expression'.": {
"category": "Error",
"code": 17000
Expand Down
102 changes: 100 additions & 2 deletions src/harness/unittests/tsserverProjectSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ namespace ts.projectSystem {
params.executingFilePath || getExecutingFilePathFromLibFile(),
params.currentDirectory || "/",
fileOrFolderList);
host.createFileOrFolder(safeList, /*createParentDirectory*/ true);
return host;
}

Expand Down Expand Up @@ -355,7 +354,8 @@ namespace ts.projectSystem {
reloadFS(filesOrFolders: FileOrFolder[]) {
this.filesOrFolders = filesOrFolders;
this.fs = createFileMap<FSEntry>();
for (const fileOrFolder of filesOrFolders) {
// always inject safelist file in the list of files
for (const fileOrFolder of filesOrFolders.concat(safeList)) {
const path = this.toPath(fileOrFolder.path);
const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory);
if (typeof fileOrFolder.content === "string") {
Expand Down Expand Up @@ -1585,6 +1585,104 @@ namespace ts.projectSystem {
projectService.closeClientFile(file1.path);
checkNumberOfProjects(projectService, { configuredProjects: 0 });
});

it("language service disabled events are triggered", () => {
const f1 = {
path: "/a/app.js",
content: "let x = 1;"
};
const f2 = {
path: "/a/largefile.js",
content: ""
};
const config = {
path: "/a/jsconfig.json",
content: "{}"
};
const configWithExclude = {
path: config.path,
content: JSON.stringify({ exclude: ["largefile.js"] })
};
const host = createServerHost([f1, f2, config]);
const originalGetFileSize = host.getFileSize;
host.getFileSize = (filePath: string) =>
filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath);

let lastEvent: server.ProjectLanguageServiceStateEvent;
const session = createSession(host, /*typingsInstaller*/ undefined, e => {
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ContextEvent) {
return;
}
assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent);
lastEvent = <server.ProjectLanguageServiceStateEvent>e;
});
session.executeCommand(<protocol.OpenRequest>{
seq: 0,
type: "request",
command: "open",
arguments: { file: f1.path }
});
const projectService = session.getProjectService();
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const project = projectService.configuredProjects[0];
assert.isFalse(project.languageServiceEnabled, "Language service enabled");
assert.isTrue(!!lastEvent, "should receive event");
assert.equal(lastEvent.data.project, project, "project name");
assert.isFalse(lastEvent.data.languageServiceEnabled, "Language service state");

host.reloadFS([f1, f2, configWithExclude]);
host.triggerFileWatcherCallback(config.path, /*removed*/ false);

checkNumberOfProjects(projectService, { configuredProjects: 1 });
assert.isTrue(project.languageServiceEnabled, "Language service enabled");
assert.equal(lastEvent.data.project, project, "project");
assert.isTrue(lastEvent.data.languageServiceEnabled, "Language service state");
});

it("syntactic features work even if language service is disabled", () => {
const f1 = {
path: "/a/app.js",
content: "let x = 1;"
};
const f2 = {
path: "/a/largefile.js",
content: ""
};
const config = {
path: "/a/jsconfig.json",
content: "{}"
};
const host = createServerHost([f1, f2, config]);
const originalGetFileSize = host.getFileSize;
host.getFileSize = (filePath: string) =>
filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath);
let lastEvent: server.ProjectLanguageServiceStateEvent;
const session = createSession(host, /*typingsInstaller*/ undefined, e => {
if (e.eventName === server.ConfigFileDiagEvent) {
return;
}
assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent);
lastEvent = <server.ProjectLanguageServiceStateEvent>e;
});
session.executeCommand(<protocol.OpenRequest>{
seq: 0,
type: "request",
command: "open",
arguments: { file: f1.path }
});

const projectService = session.getProjectService();
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const project = projectService.configuredProjects[0];
assert.isFalse(project.languageServiceEnabled, "Language service enabled");
assert.isTrue(!!lastEvent, "should receive event");
assert.equal(lastEvent.data.project, project, "project name");
assert.isFalse(lastEvent.data.languageServiceEnabled, "Language service state");

const options = projectService.getFormatCodeOptions();
const edits = project.getLanguageService().getFormattingEditsForDocument(f1.path, options);
assert.deepEqual(edits, [{ span: createTextSpan(/*start*/ 7, /*length*/ 3), newText: " " }]);
});
});

describe("Proper errors", () => {
Expand Down
43 changes: 37 additions & 6 deletions src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,26 @@
namespace ts.server {
export const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024;

export type ProjectServiceEvent =
{ eventName: "context", data: { project: Project, fileName: NormalizedPath } } | { eventName: "configFileDiag", data: { triggerFile: string, configFileName: string, diagnostics: Diagnostic[] } };
export const ContextEvent = "context";
export const ConfigFileDiagEvent = "configFileDiag";
export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState";

export interface ContextEvent {
eventName: typeof ContextEvent;
data: { project: Project; fileName: NormalizedPath };
}

export interface ConfigFileDiagEvent {
eventName: typeof ConfigFileDiagEvent;
data: { triggerFile: string, configFileName: string, diagnostics: Diagnostic[] };
}

export interface ProjectLanguageServiceStateEvent {
eventName: typeof ProjectLanguageServiceStateEvent;
data: { project: Project, languageServiceEnabled: boolean };
}

export type ProjectServiceEvent = ContextEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent;

export interface ProjectServiceEventHandler {
(event: ProjectServiceEvent): void;
Expand Down Expand Up @@ -282,6 +300,16 @@ namespace ts.server {
return this.compilerOptionsForInferredProjects;
}

onUpdateLanguageServiceStateForProject(project: Project, languageServiceEnabled: boolean) {
if (!this.eventHandler) {
return;
}
this.eventHandler(<ProjectLanguageServiceStateEvent>{
eventName: ProjectLanguageServiceStateEvent,
data: { project, languageServiceEnabled }
});
}

updateTypingsForProject(response: SetTypings | InvalidateCachedTypings): void {
const project = this.findProject(response.projectName);
if (!project) {
Expand Down Expand Up @@ -430,7 +458,10 @@ namespace ts.server {
}

for (const openFile of this.openFiles) {
this.eventHandler({ eventName: "context", data: { project: openFile.getDefaultProject(), fileName: openFile.fileName } });
this.eventHandler(<ContextEvent>{
eventName: ContextEvent,
data: { project: openFile.getDefaultProject(), fileName: openFile.fileName }
});
}
}

Expand Down Expand Up @@ -834,8 +865,8 @@ namespace ts.server {
return;
}

this.eventHandler({
eventName: "configFileDiag",
this.eventHandler(<ConfigFileDiagEvent>{
eventName: ConfigFileDiagEvent,
data: { configFileName, diagnostics: diagnostics || [], triggerFile }
});
}
Expand Down Expand Up @@ -1013,7 +1044,7 @@ namespace ts.server {
const useExistingProject = this.useSingleInferredProject && this.inferredProjects.length;
const project = useExistingProject
? this.inferredProjects[0]
: new InferredProject(this, this.documentRegistry, /*languageServiceEnabled*/ true, this.compilerOptionsForInferredProjects);
: new InferredProject(this, this.documentRegistry, this.compilerOptionsForInferredProjects);

project.addRoot(root);

Expand Down
4 changes: 2 additions & 2 deletions src/server/lsHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
/// <reference path="scriptInfo.ts" />

namespace ts.server {
export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost, ServerLanguageServiceHost {
export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost {
private compilationSettings: ts.CompilerOptions;
private readonly resolvedModuleNames= createFileMap<Map<ResolvedModuleWithFailedLookupLocations>>();
private readonly resolvedModuleNames = createFileMap<Map<ResolvedModuleWithFailedLookupLocations>>();
private readonly resolvedTypeReferenceDirectives = createFileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
private readonly getCanonicalFileName: (fileName: string) => string;

Expand Down
Loading