mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-19 20:37:00 -05:00
Pull out parts of TI Adapter so we can test that more correctly instead of having to copy things (#56387)
This commit is contained in:
@@ -15,3 +15,4 @@ export * from "../moduleSpecifierCache";
|
||||
export * from "../packageJsonCache";
|
||||
export * from "../session";
|
||||
export * from "../scriptVersionCache";
|
||||
export * from "../typingInstallerAdapter";
|
||||
|
||||
250
src/server/typingInstallerAdapter.ts
Normal file
250
src/server/typingInstallerAdapter.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import {
|
||||
ApplyCodeActionCommandResult,
|
||||
assertType,
|
||||
createQueue,
|
||||
Debug,
|
||||
JsTyping,
|
||||
MapLike,
|
||||
server,
|
||||
SortedReadonlyArray,
|
||||
TypeAcquisition,
|
||||
} from "./_namespaces/ts";
|
||||
import {
|
||||
ActionInvalidate,
|
||||
ActionPackageInstalled,
|
||||
ActionSet,
|
||||
ActionWatchTypingLocations,
|
||||
BeginInstallTypes,
|
||||
createInstallTypingsRequest,
|
||||
DiscoverTypings,
|
||||
EndInstallTypes,
|
||||
Event,
|
||||
EventBeginInstallTypes,
|
||||
EventEndInstallTypes,
|
||||
EventInitializationFailed,
|
||||
EventTypesRegistry,
|
||||
InitializationFailedResponse,
|
||||
InstallPackageOptionsWithProject,
|
||||
InstallPackageRequest,
|
||||
InvalidateCachedTypings,
|
||||
ITypingsInstaller,
|
||||
Logger,
|
||||
LogLevel,
|
||||
PackageInstalledResponse,
|
||||
Project,
|
||||
ProjectService,
|
||||
protocol,
|
||||
ServerHost,
|
||||
SetTypings,
|
||||
stringifyIndented,
|
||||
TypesRegistryResponse,
|
||||
TypingInstallerRequestUnion,
|
||||
} from "./_namespaces/ts.server";
|
||||
|
||||
/** @internal */
|
||||
export interface TypingsInstallerWorkerProcess {
|
||||
send<T extends TypingInstallerRequestUnion>(rq: T): void;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export abstract class TypingsInstallerAdapter implements ITypingsInstaller {
|
||||
protected installer!: TypingsInstallerWorkerProcess;
|
||||
private projectService!: ProjectService;
|
||||
protected activeRequestCount = 0;
|
||||
private requestQueue = createQueue<DiscoverTypings>();
|
||||
private requestMap = new Map<string, DiscoverTypings>(); // Maps project name to newest requestQueue entry for that project
|
||||
/** We will lazily request the types registry on the first call to `isKnownTypesPackageName` and store it in `typesRegistryCache`. */
|
||||
private requestedRegistry = false;
|
||||
private typesRegistryCache: Map<string, MapLike<string>> | undefined;
|
||||
|
||||
// This number is essentially arbitrary. Processing more than one typings request
|
||||
// at a time makes sense, but having too many in the pipe results in a hang
|
||||
// (see https://github.com/nodejs/node/issues/7657).
|
||||
// It would be preferable to base our limit on the amount of space left in the
|
||||
// buffer, but we have yet to find a way to retrieve that value.
|
||||
private static readonly requestDelayMillis = 100;
|
||||
private packageInstalledPromise: {
|
||||
resolve(value: ApplyCodeActionCommandResult): void;
|
||||
reject(reason: unknown): void;
|
||||
} | undefined;
|
||||
|
||||
constructor(
|
||||
protected readonly telemetryEnabled: boolean,
|
||||
protected readonly logger: Logger,
|
||||
protected readonly host: ServerHost,
|
||||
readonly globalTypingsCacheLocation: string,
|
||||
protected event: Event,
|
||||
private readonly maxActiveRequestCount: number,
|
||||
) {
|
||||
}
|
||||
|
||||
isKnownTypesPackageName(name: string): boolean {
|
||||
// We want to avoid looking this up in the registry as that is expensive. So first check that it's actually an NPM package.
|
||||
const validationResult = JsTyping.validatePackageName(name);
|
||||
if (validationResult !== JsTyping.NameValidationResult.Ok) {
|
||||
return false;
|
||||
}
|
||||
if (!this.requestedRegistry) {
|
||||
this.requestedRegistry = true;
|
||||
this.installer.send({ kind: "typesRegistry" });
|
||||
}
|
||||
return !!this.typesRegistryCache?.has(name);
|
||||
}
|
||||
|
||||
installPackage(options: InstallPackageOptionsWithProject): Promise<ApplyCodeActionCommandResult> {
|
||||
this.installer.send<InstallPackageRequest>({ kind: "installPackage", ...options });
|
||||
Debug.assert(this.packageInstalledPromise === undefined);
|
||||
return new Promise<ApplyCodeActionCommandResult>((resolve, reject) => {
|
||||
this.packageInstalledPromise = { resolve, reject };
|
||||
});
|
||||
}
|
||||
|
||||
attach(projectService: ProjectService) {
|
||||
this.projectService = projectService;
|
||||
this.installer = this.createInstallerProcess();
|
||||
}
|
||||
|
||||
onProjectClosed(p: Project): void {
|
||||
this.installer.send({ projectName: p.getProjectName(), kind: "closeProject" });
|
||||
}
|
||||
|
||||
enqueueInstallTypingsRequest(project: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>): void {
|
||||
const request = createInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`TIAdapter:: Scheduling throttled operation:${stringifyIndented(request)}`);
|
||||
}
|
||||
|
||||
if (this.activeRequestCount < this.maxActiveRequestCount) {
|
||||
this.scheduleRequest(request);
|
||||
}
|
||||
else {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`TIAdapter:: Deferring request for: ${request.projectName}`);
|
||||
}
|
||||
this.requestQueue.enqueue(request);
|
||||
this.requestMap.set(request.projectName, request);
|
||||
}
|
||||
}
|
||||
|
||||
handleMessage(response: TypesRegistryResponse | PackageInstalledResponse | SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse | server.WatchTypingLocations) {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`TIAdapter:: Received response:${stringifyIndented(response)}`);
|
||||
}
|
||||
|
||||
switch (response.kind) {
|
||||
case EventTypesRegistry:
|
||||
this.typesRegistryCache = new Map(Object.entries(response.typesRegistry));
|
||||
break;
|
||||
case ActionPackageInstalled: {
|
||||
const { success, message } = response;
|
||||
if (success) {
|
||||
this.packageInstalledPromise!.resolve({ successMessage: message });
|
||||
}
|
||||
else {
|
||||
this.packageInstalledPromise!.reject(message);
|
||||
}
|
||||
this.packageInstalledPromise = undefined;
|
||||
|
||||
this.projectService.updateTypingsForProject(response);
|
||||
|
||||
// The behavior is the same as for setTypings, so send the same event.
|
||||
this.event(response, "setTypings");
|
||||
break;
|
||||
}
|
||||
case EventInitializationFailed: {
|
||||
const body: protocol.TypesInstallerInitializationFailedEventBody = {
|
||||
message: response.message,
|
||||
};
|
||||
const eventName: protocol.TypesInstallerInitializationFailedEventName = "typesInstallerInitializationFailed";
|
||||
this.event(body, eventName);
|
||||
break;
|
||||
}
|
||||
case EventBeginInstallTypes: {
|
||||
const body: protocol.BeginInstallTypesEventBody = {
|
||||
eventId: response.eventId,
|
||||
packages: response.packagesToInstall,
|
||||
};
|
||||
const eventName: protocol.BeginInstallTypesEventName = "beginInstallTypes";
|
||||
this.event(body, eventName);
|
||||
break;
|
||||
}
|
||||
case EventEndInstallTypes: {
|
||||
if (this.telemetryEnabled) {
|
||||
const body: protocol.TypingsInstalledTelemetryEventBody = {
|
||||
telemetryEventName: "typingsInstalled",
|
||||
payload: {
|
||||
installedPackages: response.packagesToInstall.join(","),
|
||||
installSuccess: response.installSuccess,
|
||||
typingsInstallerVersion: response.typingsInstallerVersion,
|
||||
},
|
||||
};
|
||||
const eventName: protocol.TelemetryEventName = "telemetry";
|
||||
this.event(body, eventName);
|
||||
}
|
||||
|
||||
const body: protocol.EndInstallTypesEventBody = {
|
||||
eventId: response.eventId,
|
||||
packages: response.packagesToInstall,
|
||||
success: response.installSuccess,
|
||||
};
|
||||
const eventName: protocol.EndInstallTypesEventName = "endInstallTypes";
|
||||
this.event(body, eventName);
|
||||
break;
|
||||
}
|
||||
case ActionInvalidate: {
|
||||
this.projectService.updateTypingsForProject(response);
|
||||
break;
|
||||
}
|
||||
case ActionSet: {
|
||||
if (this.activeRequestCount > 0) {
|
||||
this.activeRequestCount--;
|
||||
}
|
||||
else {
|
||||
Debug.fail("TIAdapter:: Received too many responses");
|
||||
}
|
||||
|
||||
while (!this.requestQueue.isEmpty()) {
|
||||
const queuedRequest = this.requestQueue.dequeue();
|
||||
if (this.requestMap.get(queuedRequest.projectName) === queuedRequest) {
|
||||
this.requestMap.delete(queuedRequest.projectName);
|
||||
this.scheduleRequest(queuedRequest);
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`TIAdapter:: Skipping defunct request for: ${queuedRequest.projectName}`);
|
||||
}
|
||||
}
|
||||
|
||||
this.projectService.updateTypingsForProject(response);
|
||||
this.event(response, "setTypings");
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionWatchTypingLocations:
|
||||
this.projectService.watchTypingLocations(response);
|
||||
break;
|
||||
default:
|
||||
assertType<never>(response);
|
||||
}
|
||||
}
|
||||
|
||||
scheduleRequest(request: DiscoverTypings) {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`TIAdapter:: Scheduling request for: ${request.projectName}`);
|
||||
}
|
||||
this.activeRequestCount++;
|
||||
this.host.setTimeout(
|
||||
() => {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`TIAdapter:: Sending request:${stringifyIndented(request)}`);
|
||||
}
|
||||
this.installer.send(request);
|
||||
},
|
||||
TypingsInstallerAdapter.requestDelayMillis,
|
||||
`${request.projectName}::${request.kind}`,
|
||||
);
|
||||
}
|
||||
|
||||
protected abstract createInstallerProcess(): TypingsInstallerWorkerProcess;
|
||||
}
|
||||
@@ -157,7 +157,8 @@ export class TypingsCache {
|
||||
}
|
||||
|
||||
onProjectClosed(project: Project) {
|
||||
this.perProjectCache.delete(project.getProjectName());
|
||||
this.installer.onProjectClosed(project);
|
||||
if (this.perProjectCache.delete(project.getProjectName())) {
|
||||
this.installer.onProjectClosed(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from "./solutionBuilder";
|
||||
import {
|
||||
customTypesMap,
|
||||
TestTypingsInstaller,
|
||||
TestTypingsInstallerAdapter,
|
||||
TestTypingsInstallerOptions,
|
||||
} from "./typingsInstaller";
|
||||
import {
|
||||
@@ -103,13 +103,13 @@ export class TestSession extends ts.server.Session {
|
||||
private seq = 0;
|
||||
public override host!: TestSessionAndServiceHost;
|
||||
public override logger!: LoggerWithInMemoryLogs;
|
||||
public override readonly typingsInstaller!: TestTypingsInstaller;
|
||||
public override readonly typingsInstaller!: TestTypingsInstallerAdapter;
|
||||
public serverCancellationToken: TestServerCancellationToken;
|
||||
|
||||
constructor(optsOrHost: TestSessionConstructorOptions) {
|
||||
const opts = getTestSessionPartialOptionsAndHost(optsOrHost);
|
||||
opts.logger = opts.logger || createLoggerWithInMemoryLogs(opts.host);
|
||||
const typingsInstaller = !opts.disableAutomaticTypingAcquisition ? new TestTypingsInstaller(opts) : undefined;
|
||||
const typingsInstaller = !opts.disableAutomaticTypingAcquisition ? new TestTypingsInstallerAdapter(opts) : undefined;
|
||||
const cancellationToken = opts.useCancellationToken ?
|
||||
new TestServerCancellationToken(
|
||||
opts.logger,
|
||||
|
||||
@@ -4,12 +4,6 @@ import {
|
||||
} from "../../../harness/tsserverLogger";
|
||||
import * as ts from "../../_namespaces/ts";
|
||||
import {
|
||||
ActionInvalidate,
|
||||
ActionPackageInstalled,
|
||||
ActionSet,
|
||||
ActionWatchTypingLocations,
|
||||
EventBeginInstallTypes,
|
||||
EventEndInstallTypes,
|
||||
stringifyIndented,
|
||||
} from "../../_namespaces/ts.server";
|
||||
import {
|
||||
@@ -91,7 +85,7 @@ export type PendingInstallCallback = (
|
||||
) => void;
|
||||
export class TestTypingsInstallerWorker extends ts.server.typingsInstaller.TypingsInstaller {
|
||||
readonly typesRegistry: Map<string, ts.MapLike<string>>;
|
||||
constructor(readonly testTypingInstaller: TestTypingsInstaller) {
|
||||
constructor(readonly testTypingInstaller: TestTypingsInstallerAdapter) {
|
||||
const log = loggerToTypingsInstallerLog(testTypingInstaller.session.logger);
|
||||
ts.Debug.assert(testTypingInstaller.session.host.patched);
|
||||
testTypingInstaller.session.host.baselineHost("TI:: Creating typing installer");
|
||||
@@ -175,17 +169,7 @@ export class TestTypingsInstallerWorker extends ts.server.typingsInstaller.Typin
|
||||
|
||||
sendResponse(response: ts.server.SetTypings | ts.server.InvalidateCachedTypings | ts.server.BeginInstallTypes | ts.server.EndInstallTypes | ts.server.WatchTypingLocations | ts.server.PackageInstalledResponse) {
|
||||
this.log.writeLine(`Sending response:${stringifyIndented(response)}`);
|
||||
this.testTypingInstaller.onResponse(response);
|
||||
}
|
||||
|
||||
enqueueInstallTypingsRequest(project: ts.server.Project, typeAcquisition: ts.TypeAcquisition, unresolvedImports: ts.SortedReadonlyArray<string>) {
|
||||
const request = ts.server.createInstallTypingsRequest(
|
||||
project,
|
||||
typeAcquisition,
|
||||
unresolvedImports,
|
||||
this.testTypingInstaller.globalTypingsCacheLocation,
|
||||
);
|
||||
this.install(request);
|
||||
this.testTypingInstaller.handleMessage(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,111 +180,50 @@ export interface TestTypingsInstallerOptions {
|
||||
throttleLimit?: number;
|
||||
installAction?: InstallAction;
|
||||
typesRegistry?: string | readonly string[];
|
||||
throttledRequests?: number;
|
||||
}
|
||||
|
||||
export class TestTypingsInstaller implements ts.server.ITypingsInstaller {
|
||||
protected projectService!: ts.server.ProjectService;
|
||||
public installer!: TestTypingsInstallerWorker;
|
||||
export class TestTypingsInstallerAdapter extends ts.server.TypingsInstallerAdapter {
|
||||
worker: TestTypingsInstallerWorker | undefined;
|
||||
session!: TestSession;
|
||||
packageInstalledPromise: { resolve(value: ts.ApplyCodeActionCommandResult): void; reject(reason: unknown): void; } | undefined;
|
||||
|
||||
// Options
|
||||
readonly globalTypingsCacheLocation: string;
|
||||
readonly throttleLimit: number;
|
||||
readonly installAction: InstallAction;
|
||||
readonly typesRegistry: string | readonly string[] | undefined;
|
||||
readonly throttledRequests: number | undefined;
|
||||
|
||||
constructor(options: TestTypingsInstallerOptions) {
|
||||
this.globalTypingsCacheLocation = options.globalTypingsCacheLocation || options.host.getHostSpecificPath("/a/data");
|
||||
const globalTypingsCacheLocation = options.globalTypingsCacheLocation || options.host.getHostSpecificPath("/a/data");
|
||||
super(
|
||||
/*telemetryEnabled*/ false,
|
||||
options.throttledRequests === undefined ?
|
||||
{ ...options.logger!, hasLevel: ts.returnFalse } :
|
||||
options.logger!,
|
||||
options.host,
|
||||
globalTypingsCacheLocation,
|
||||
(...args) => this.session.event(...args),
|
||||
// Some large number so requests arent throttled
|
||||
options.throttledRequests === undefined ? 10 : options.throttledRequests,
|
||||
);
|
||||
this.throttleLimit = options.throttleLimit || 5;
|
||||
this.installAction = options.installAction !== undefined ? options.installAction : true;
|
||||
this.typesRegistry = options.typesRegistry;
|
||||
this.throttledRequests = options.throttledRequests;
|
||||
}
|
||||
|
||||
isKnownTypesPackageName(name: string): boolean {
|
||||
// We want to avoid looking this up in the registry as that is expensive. So first check that it's actually an NPM package.
|
||||
const validationResult = ts.JsTyping.validatePackageName(name);
|
||||
if (validationResult !== ts.JsTyping.NameValidationResult.Ok) {
|
||||
return false;
|
||||
protected override createInstallerProcess(): ts.server.TypingsInstallerWorkerProcess {
|
||||
return {
|
||||
send: req => (this.worker ??= new TestTypingsInstallerWorker(this)).handleRequest(req),
|
||||
};
|
||||
}
|
||||
|
||||
override scheduleRequest(request: ts.server.DiscoverTypings): void {
|
||||
if (this.throttledRequests === undefined) {
|
||||
this.activeRequestCount++;
|
||||
this.installer.send(request);
|
||||
}
|
||||
|
||||
return this.ensureInstaller().typesRegistry.has(name);
|
||||
}
|
||||
|
||||
installPackage(options: ts.server.InstallPackageOptionsWithProject): Promise<ts.ApplyCodeActionCommandResult> {
|
||||
this.ensureInstaller().installPackage({ kind: "installPackage", ...options });
|
||||
ts.Debug.assert(this.packageInstalledPromise === undefined);
|
||||
return new Promise<ts.ApplyCodeActionCommandResult>((resolve, reject) => {
|
||||
this.packageInstalledPromise = { resolve, reject };
|
||||
});
|
||||
}
|
||||
|
||||
attach(projectService: ts.server.ProjectService) {
|
||||
this.projectService = projectService;
|
||||
}
|
||||
|
||||
onProjectClosed(p: ts.server.Project) {
|
||||
this.installer?.closeProject({ projectName: p.getProjectName(), kind: "closeProject" });
|
||||
}
|
||||
|
||||
enqueueInstallTypingsRequest(project: ts.server.Project, typeAcquisition: ts.TypeAcquisition, unresolvedImports: ts.SortedReadonlyArray<string>) {
|
||||
this.ensureInstaller().enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
|
||||
}
|
||||
|
||||
private ensureInstaller() {
|
||||
return this.installer ??= new TestTypingsInstallerWorker(this);
|
||||
}
|
||||
|
||||
onResponse(response: ts.server.SetTypings | ts.server.InvalidateCachedTypings | ts.server.BeginInstallTypes | ts.server.EndInstallTypes | ts.server.WatchTypingLocations | ts.server.PackageInstalledResponse) {
|
||||
switch (response.kind) {
|
||||
case ActionPackageInstalled: {
|
||||
const { success, message } = response;
|
||||
if (success) {
|
||||
this.packageInstalledPromise!.resolve({ successMessage: message });
|
||||
}
|
||||
else {
|
||||
this.packageInstalledPromise!.reject(message);
|
||||
}
|
||||
this.packageInstalledPromise = undefined;
|
||||
|
||||
this.projectService.updateTypingsForProject(response);
|
||||
// The behavior is the same as for setTypings, so send the same event.
|
||||
this.session.event(response, "setTypings");
|
||||
break;
|
||||
}
|
||||
case EventBeginInstallTypes: {
|
||||
const body: ts.server.protocol.BeginInstallTypesEventBody = {
|
||||
eventId: response.eventId,
|
||||
packages: response.packagesToInstall,
|
||||
};
|
||||
const eventName: ts.server.protocol.BeginInstallTypesEventName = "beginInstallTypes";
|
||||
this.session.event(body, eventName);
|
||||
break;
|
||||
}
|
||||
case EventEndInstallTypes: {
|
||||
const body: ts.server.protocol.EndInstallTypesEventBody = {
|
||||
eventId: response.eventId,
|
||||
packages: response.packagesToInstall,
|
||||
success: response.installSuccess,
|
||||
};
|
||||
const eventName: ts.server.protocol.EndInstallTypesEventName = "endInstallTypes";
|
||||
this.session.event(body, eventName);
|
||||
break;
|
||||
}
|
||||
case ActionInvalidate: {
|
||||
this.projectService.updateTypingsForProject(response);
|
||||
break;
|
||||
}
|
||||
case ActionSet: {
|
||||
this.projectService.updateTypingsForProject(response);
|
||||
this.session.event(response, "setTypings");
|
||||
break;
|
||||
}
|
||||
case ActionWatchTypingLocations:
|
||||
this.projectService.watchTypingLocations(response);
|
||||
break;
|
||||
default:
|
||||
ts.assertType<never>(response);
|
||||
else {
|
||||
super.scheduleRequest(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,87 +537,242 @@ describe("unittests:: tsserver:: typingsInstaller:: General functionality", () =
|
||||
baselineTsserverLogs("typingsInstaller", "throttle delayed typings to install", session);
|
||||
});
|
||||
|
||||
it("Throttle - delayed run install requests", () => {
|
||||
const lodashJs = {
|
||||
path: "/a/b/lodash.js",
|
||||
content: "",
|
||||
};
|
||||
const commanderJs = {
|
||||
path: "/a/b/commander.js",
|
||||
content: "",
|
||||
};
|
||||
const file3 = {
|
||||
path: "/a/b/file3.d.ts",
|
||||
content: "",
|
||||
};
|
||||
describe("throttled testing", () => {
|
||||
function setup() {
|
||||
const lodashJs = {
|
||||
path: "/a/b/lodash.js",
|
||||
content: "",
|
||||
};
|
||||
const commanderJs = {
|
||||
path: "/a/b/commander.js",
|
||||
content: "",
|
||||
};
|
||||
const file3 = {
|
||||
path: "/a/b/file3.d.ts",
|
||||
content: "",
|
||||
};
|
||||
|
||||
const commander: FileWithPackageName = {
|
||||
path: "/a/data/node_modules/@types/commander/index.d.ts",
|
||||
content: "declare const commander: { x: number }",
|
||||
package: "commander",
|
||||
};
|
||||
const jquery: FileWithPackageName = {
|
||||
path: "/a/data/node_modules/@types/jquery/index.d.ts",
|
||||
content: "declare const jquery: { x: number }",
|
||||
package: "jquery",
|
||||
};
|
||||
const lodash: FileWithPackageName = {
|
||||
path: "/a/data/node_modules/@types/lodash/index.d.ts",
|
||||
content: "declare const lodash: { x: number }",
|
||||
package: "lodash",
|
||||
};
|
||||
const cordova: FileWithPackageName = {
|
||||
path: "/a/data/node_modules/@types/cordova/index.d.ts",
|
||||
content: "declare const cordova: { x: number }",
|
||||
package: "cordova",
|
||||
};
|
||||
const grunt: FileWithPackageName = {
|
||||
path: "/a/data/node_modules/@types/grunt/index.d.ts",
|
||||
content: "declare const grunt: { x: number }",
|
||||
package: "grunt",
|
||||
};
|
||||
const gulp: FileWithPackageName = {
|
||||
path: "/a/data/node_modules/@types/gulp/index.d.ts",
|
||||
content: "declare const gulp: { x: number }",
|
||||
package: "gulp",
|
||||
};
|
||||
const commander: FileWithPackageName = {
|
||||
path: "/a/data/node_modules/@types/commander/index.d.ts",
|
||||
content: "declare const commander: { x: number }",
|
||||
package: "commander",
|
||||
};
|
||||
const jquery: FileWithPackageName = {
|
||||
path: "/a/data/node_modules/@types/jquery/index.d.ts",
|
||||
content: "declare const jquery: { x: number }",
|
||||
package: "jquery",
|
||||
};
|
||||
const lodash: FileWithPackageName = {
|
||||
path: "/a/data/node_modules/@types/lodash/index.d.ts",
|
||||
content: "declare const lodash: { x: number }",
|
||||
package: "lodash",
|
||||
};
|
||||
const cordova: FileWithPackageName = {
|
||||
path: "/a/data/node_modules/@types/cordova/index.d.ts",
|
||||
content: "declare const cordova: { x: number }",
|
||||
package: "cordova",
|
||||
};
|
||||
const grunt: FileWithPackageName = {
|
||||
path: "/a/data/node_modules/@types/grunt/index.d.ts",
|
||||
content: "declare const grunt: { x: number }",
|
||||
package: "grunt",
|
||||
};
|
||||
const gulp: FileWithPackageName = {
|
||||
path: "/a/data/node_modules/@types/gulp/index.d.ts",
|
||||
content: "declare const gulp: { x: number }",
|
||||
package: "gulp",
|
||||
};
|
||||
|
||||
const host = createServerHost([lodashJs, commanderJs, file3, customTypesMap]);
|
||||
// Create project #1 with 4 typings
|
||||
const session = new TestSession({
|
||||
host,
|
||||
installAction: [commander, jquery, lodash, cordova, grunt, gulp],
|
||||
throttleLimit: 1,
|
||||
typesRegistry: ["commander", "jquery", "lodash", "cordova", "gulp", "grunt"],
|
||||
const host = createServerHost([lodashJs, commanderJs, file3, customTypesMap]);
|
||||
return { lodashJs, commanderJs, file3, commander, jquery, lodash, cordova, grunt, gulp, host };
|
||||
}
|
||||
it("Throttle - delayed run install requests", () => {
|
||||
const { lodashJs, commanderJs, file3, commander, jquery, lodash, cordova, grunt, gulp, host } = setup();
|
||||
|
||||
// Create project #1 with 4 typings
|
||||
const session = new TestSession({
|
||||
host,
|
||||
installAction: [commander, jquery, lodash, cordova, grunt, gulp],
|
||||
throttleLimit: 1,
|
||||
typesRegistry: ["commander", "jquery", "lodash", "cordova", "gulp", "grunt"],
|
||||
});
|
||||
const projectFileName1 = "/a/app/test1.csproj";
|
||||
openExternalProjectForSession({
|
||||
projectFileName: projectFileName1,
|
||||
options: { allowJS: true, moduleResolution: ts.ModuleResolutionKind.Node10 },
|
||||
rootFiles: [toExternalFile(lodashJs.path), toExternalFile(commanderJs.path), toExternalFile(file3.path)],
|
||||
typeAcquisition: { include: ["jquery", "cordova"] },
|
||||
}, session);
|
||||
|
||||
// Create project #2 with 2 typings
|
||||
const projectFileName2 = "/a/app/test2.csproj";
|
||||
openExternalProjectForSession({
|
||||
projectFileName: projectFileName2,
|
||||
options: { allowJS: true, moduleResolution: ts.ModuleResolutionKind.Node10 },
|
||||
rootFiles: [toExternalFile(file3.path)],
|
||||
typeAcquisition: { include: ["grunt", "gulp"] },
|
||||
}, session);
|
||||
|
||||
host.runPendingInstalls();
|
||||
host.runPendingInstalls();
|
||||
host.runQueuedTimeoutCallbacks(); // for 2 projects
|
||||
baselineTsserverLogs("typingsInstaller", "throttle delayed run install requests", session);
|
||||
});
|
||||
const projectFileName1 = "/a/app/test1.csproj";
|
||||
openExternalProjectForSession({
|
||||
projectFileName: projectFileName1,
|
||||
options: { allowJS: true, moduleResolution: ts.ModuleResolutionKind.Node10 },
|
||||
rootFiles: [toExternalFile(lodashJs.path), toExternalFile(commanderJs.path), toExternalFile(file3.path)],
|
||||
typeAcquisition: { include: ["jquery", "cordova"] },
|
||||
}, session);
|
||||
|
||||
assert.equal(session.typingsInstaller.installer.pendingRunRequests.length, 0, "expect no throttled requests");
|
||||
it("Throttle - scheduled run install requests without reaching limit", () => {
|
||||
const { lodashJs, commanderJs, file3, commander, jquery, lodash, cordova, grunt, gulp, host } = setup();
|
||||
|
||||
// Create project #2 with 2 typings
|
||||
const projectFileName2 = "/a/app/test2.csproj";
|
||||
openExternalProjectForSession({
|
||||
projectFileName: projectFileName2,
|
||||
options: { allowJS: true, moduleResolution: ts.ModuleResolutionKind.Node10 },
|
||||
rootFiles: [toExternalFile(file3.path)],
|
||||
typeAcquisition: { include: ["grunt", "gulp"] },
|
||||
}, session);
|
||||
assert.equal(session.typingsInstaller.installer.pendingRunRequests.length, 1, "expect one throttled request");
|
||||
const session = new TestSession({
|
||||
host,
|
||||
installAction: [commander, jquery, lodash, cordova, grunt, gulp],
|
||||
throttledRequests: 1,
|
||||
typesRegistry: ["commander", "jquery", "lodash", "cordova", "gulp", "grunt"],
|
||||
});
|
||||
const projectFileName1 = "/a/app/test1.csproj";
|
||||
openExternalProjectForSession({
|
||||
projectFileName: projectFileName1,
|
||||
options: { allowJS: true, moduleResolution: ts.ModuleResolutionKind.Node10 },
|
||||
rootFiles: [toExternalFile(lodashJs.path), toExternalFile(commanderJs.path), toExternalFile(file3.path)],
|
||||
typeAcquisition: { include: ["jquery", "cordova"] },
|
||||
}, session);
|
||||
|
||||
host.runPendingInstalls();
|
||||
host.runQueuedTimeoutCallbacks(); // Send the request to worker for project1
|
||||
host.runPendingInstalls(); // Actual install for project1
|
||||
|
||||
// expected one install request from the second project
|
||||
assert.equal(session.typingsInstaller.installer.pendingRunRequests.length, 0, "expected no throttled requests");
|
||||
const id = host.getNextTimeoutId();
|
||||
const projectFileName2 = "/a/app/test2.csproj";
|
||||
openExternalProjectForSession({
|
||||
projectFileName: projectFileName2,
|
||||
options: { allowJS: true, moduleResolution: ts.ModuleResolutionKind.Node10 },
|
||||
rootFiles: [toExternalFile(file3.path)],
|
||||
typeAcquisition: { include: ["grunt", "gulp"] },
|
||||
}, session);
|
||||
|
||||
host.runPendingInstalls();
|
||||
host.runQueuedTimeoutCallbacks(); // for 2 projects
|
||||
baselineTsserverLogs("typingsInstaller", "throttle delayed run install requests", session);
|
||||
host.runQueuedTimeoutCallbacks(id); // Send the request to worker for project2
|
||||
host.runPendingInstalls(); // Actual install for project2
|
||||
baselineTsserverLogs("typingsInstaller", "throttle scheduled run install requests without reaching limit", session);
|
||||
});
|
||||
|
||||
it("Throttle - scheduled run install requests with defer", () => {
|
||||
const { lodashJs, commanderJs, file3, commander, jquery, lodash, cordova, grunt, gulp, host } = setup();
|
||||
|
||||
const session = new TestSession({
|
||||
host,
|
||||
installAction: [commander, jquery, lodash, cordova, grunt, gulp],
|
||||
throttledRequests: 1,
|
||||
typesRegistry: ["commander", "jquery", "lodash", "cordova", "gulp", "grunt"],
|
||||
});
|
||||
const projectFileName1 = "/a/app/test1.csproj";
|
||||
openExternalProjectForSession({
|
||||
projectFileName: projectFileName1,
|
||||
options: { allowJS: true, moduleResolution: ts.ModuleResolutionKind.Node10 },
|
||||
rootFiles: [toExternalFile(lodashJs.path), toExternalFile(commanderJs.path), toExternalFile(file3.path)],
|
||||
typeAcquisition: { include: ["jquery", "cordova"] },
|
||||
}, session);
|
||||
|
||||
// this will be deferred
|
||||
const projectFileName2 = "/a/app/test2.csproj";
|
||||
openExternalProjectForSession({
|
||||
projectFileName: projectFileName2,
|
||||
options: { allowJS: true, moduleResolution: ts.ModuleResolutionKind.Node10 },
|
||||
rootFiles: [toExternalFile(file3.path)],
|
||||
typeAcquisition: { include: ["grunt", "gulp"] },
|
||||
}, session);
|
||||
const id = host.getNextTimeoutId();
|
||||
host.runQueuedTimeoutCallbacks(); // Send the request to worker for project1
|
||||
host.runPendingInstalls(); // Actual install for project1
|
||||
|
||||
host.runQueuedTimeoutCallbacks(id); // Send the request to worker for project2
|
||||
host.runPendingInstalls(); // Actual install for project2
|
||||
baselineTsserverLogs("typingsInstaller", "throttle scheduled run install requests with defer", session);
|
||||
});
|
||||
|
||||
it("Throttle - scheduled run install requests with defer refreshed", () => {
|
||||
const { lodashJs, commanderJs, file3, commander, jquery, lodash, cordova, grunt, gulp, host } = setup();
|
||||
|
||||
const session = new TestSession({
|
||||
host,
|
||||
installAction: [commander, jquery, lodash, cordova, grunt, gulp],
|
||||
throttledRequests: 1,
|
||||
typesRegistry: ["commander", "jquery", "lodash", "cordova", "gulp", "grunt"],
|
||||
});
|
||||
const projectFileName1 = "/a/app/test1.csproj";
|
||||
openExternalProjectForSession({
|
||||
projectFileName: projectFileName1,
|
||||
options: { allowJS: true, moduleResolution: ts.ModuleResolutionKind.Node10 },
|
||||
rootFiles: [toExternalFile(commanderJs.path), toExternalFile(file3.path)],
|
||||
typeAcquisition: { include: ["jquery", "cordova"] },
|
||||
}, session);
|
||||
|
||||
// Create project #2 with 2 typings - this will be deferred
|
||||
const projectFileName2 = "/a/app/test2.csproj";
|
||||
openExternalProjectForSession({
|
||||
projectFileName: projectFileName2,
|
||||
options: { allowJS: true, moduleResolution: ts.ModuleResolutionKind.Node10 },
|
||||
rootFiles: [toExternalFile(file3.path)],
|
||||
typeAcquisition: { include: ["grunt", "gulp"] },
|
||||
}, session);
|
||||
// Update project for 3 typings and this should be used instead of first one
|
||||
openExternalProjectForSession({
|
||||
projectFileName: projectFileName2,
|
||||
options: { allowJS: true, moduleResolution: ts.ModuleResolutionKind.Node10 },
|
||||
rootFiles: [toExternalFile(lodashJs.path), toExternalFile(file3.path)],
|
||||
typeAcquisition: { include: ["grunt", "gulp"] },
|
||||
}, session);
|
||||
const id = host.getNextTimeoutId();
|
||||
host.runQueuedTimeoutCallbacks(); // Send the request to worker for project1
|
||||
host.runPendingInstalls(); // Actual install for project1
|
||||
|
||||
host.runQueuedTimeoutCallbacks(id); // Send the request to worker for project2
|
||||
host.runPendingInstalls(); // Actual install for project2
|
||||
baselineTsserverLogs("typingsInstaller", "throttle scheduled run install requests with defer refreshed", session);
|
||||
});
|
||||
|
||||
it("Throttle - scheduled run install requests with defer while queuing again", () => {
|
||||
const { lodashJs, commanderJs, file3, commander, jquery, lodash, cordova, grunt, gulp, host } = setup();
|
||||
|
||||
const session = new TestSession({
|
||||
host,
|
||||
installAction: [commander, jquery, lodash, cordova, grunt, gulp],
|
||||
throttledRequests: 1,
|
||||
typesRegistry: ["commander", "jquery", "lodash", "cordova", "gulp", "grunt"],
|
||||
});
|
||||
const projectFileName1 = "/a/app/test1.csproj";
|
||||
openExternalProjectForSession({
|
||||
projectFileName: projectFileName1,
|
||||
options: { allowJS: true, moduleResolution: ts.ModuleResolutionKind.Node10 },
|
||||
rootFiles: [toExternalFile(commanderJs.path), toExternalFile(file3.path)],
|
||||
typeAcquisition: { include: ["jquery"] },
|
||||
}, session);
|
||||
|
||||
const projectFileName2 = "/a/app/test2.csproj";
|
||||
openExternalProjectForSession({
|
||||
projectFileName: projectFileName2,
|
||||
options: { allowJS: true, moduleResolution: ts.ModuleResolutionKind.Node10 },
|
||||
rootFiles: [toExternalFile(file3.path)],
|
||||
typeAcquisition: { include: ["grunt", "gulp"] },
|
||||
}, session);
|
||||
|
||||
const projectFileName3 = "/a/app/test3.csproj";
|
||||
openExternalProjectForSession({
|
||||
projectFileName: projectFileName3,
|
||||
options: { allowJS: true, moduleResolution: ts.ModuleResolutionKind.Node10 },
|
||||
rootFiles: [toExternalFile(lodashJs.path), toExternalFile(file3.path)],
|
||||
typeAcquisition: { include: ["cordova"] },
|
||||
}, session);
|
||||
const id = host.getNextTimeoutId();
|
||||
host.runQueuedTimeoutCallbacks(); // Send the request to worker for project1
|
||||
host.runPendingInstalls(); // Actual install for project1
|
||||
|
||||
const id2 = host.getNextTimeoutId();
|
||||
host.runQueuedTimeoutCallbacks(id); // Send the request to worker for project2
|
||||
host.runPendingInstalls(); // Actual install for project2
|
||||
|
||||
host.runQueuedTimeoutCallbacks(id2); // Send the request to worker for project3
|
||||
host.runPendingInstalls(); // Actual install for project3
|
||||
|
||||
baselineTsserverLogs("typingsInstaller", "throttle scheduled run install requests with defer while queuing again", session);
|
||||
});
|
||||
});
|
||||
|
||||
it("configured scoped name projects discover from node_modules", () => {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import * as protocol from "../server/protocol";
|
||||
import * as ts from "./_namespaces/ts";
|
||||
import {
|
||||
ApplyCodeActionCommandResult,
|
||||
assertType,
|
||||
CharacterCodes,
|
||||
combinePaths,
|
||||
createQueue,
|
||||
@@ -12,7 +10,6 @@ import {
|
||||
FileWatcher,
|
||||
getDirectoryPath,
|
||||
getRootLength,
|
||||
JsTyping,
|
||||
LanguageServiceMode,
|
||||
MapLike,
|
||||
noop,
|
||||
@@ -20,60 +17,36 @@ import {
|
||||
normalizePath,
|
||||
normalizeSlashes,
|
||||
perfLogger,
|
||||
SortedReadonlyArray,
|
||||
startTracing,
|
||||
stripQuotes,
|
||||
sys,
|
||||
toFileNameLowerCase,
|
||||
tracing,
|
||||
TypeAcquisition,
|
||||
validateLocaleAndSetLanguage,
|
||||
versionMajorMinor,
|
||||
WatchOptions,
|
||||
} from "./_namespaces/ts";
|
||||
import * as server from "./_namespaces/ts.server";
|
||||
import {
|
||||
ActionInvalidate,
|
||||
ActionPackageInstalled,
|
||||
ActionSet,
|
||||
ActionWatchTypingLocations,
|
||||
Arguments,
|
||||
BeginInstallTypes,
|
||||
createInstallTypingsRequest,
|
||||
EndInstallTypes,
|
||||
EventBeginInstallTypes,
|
||||
EventEndInstallTypes,
|
||||
EventInitializationFailed,
|
||||
EventTypesRegistry,
|
||||
Event,
|
||||
findArgument,
|
||||
formatMessage,
|
||||
getLogLevel,
|
||||
hasArgument,
|
||||
indent,
|
||||
InitializationFailedResponse,
|
||||
InstallPackageOptionsWithProject,
|
||||
InstallPackageRequest,
|
||||
InvalidateCachedTypings,
|
||||
ITypingsInstaller,
|
||||
Logger,
|
||||
LogLevel,
|
||||
Msg,
|
||||
nowString,
|
||||
nullCancellationToken,
|
||||
nullTypingsInstaller,
|
||||
PackageInstalledResponse,
|
||||
Project,
|
||||
ProjectService,
|
||||
ServerCancellationToken,
|
||||
ServerHost,
|
||||
Session,
|
||||
SetTypings,
|
||||
StartInput,
|
||||
StartSessionOptions,
|
||||
stringifyIndented,
|
||||
toEvent,
|
||||
TypesRegistryResponse,
|
||||
TypingInstallerRequestUnion,
|
||||
TypingsInstallerAdapter,
|
||||
} from "./_namespaces/ts.server";
|
||||
|
||||
interface LogOptions {
|
||||
@@ -520,69 +493,37 @@ function startNodeSession(options: StartSessionOptions, logger: Logger, cancella
|
||||
terminal: false,
|
||||
});
|
||||
|
||||
interface QueuedOperation {
|
||||
operationId: string;
|
||||
operation: () => void;
|
||||
}
|
||||
|
||||
class NodeTypingsInstaller implements ITypingsInstaller {
|
||||
private installer!: NodeChildProcess;
|
||||
private projectService!: ProjectService;
|
||||
private activeRequestCount = 0;
|
||||
private requestQueue = createQueue<QueuedOperation>();
|
||||
private requestMap = new Map<string, QueuedOperation>(); // Maps operation ID to newest requestQueue entry with that ID
|
||||
/** We will lazily request the types registry on the first call to `isKnownTypesPackageName` and store it in `typesRegistryCache`. */
|
||||
private requestedRegistry = false;
|
||||
private typesRegistryCache: Map<string, MapLike<string>> | undefined;
|
||||
|
||||
class NodeTypingsInstallerAdapter extends TypingsInstallerAdapter {
|
||||
protected override installer!: NodeChildProcess;
|
||||
// This number is essentially arbitrary. Processing more than one typings request
|
||||
// at a time makes sense, but having too many in the pipe results in a hang
|
||||
// (see https://github.com/nodejs/node/issues/7657).
|
||||
// It would be preferable to base our limit on the amount of space left in the
|
||||
// buffer, but we have yet to find a way to retrieve that value.
|
||||
private static readonly maxActiveRequestCount = 10;
|
||||
private static readonly requestDelayMillis = 100;
|
||||
private packageInstalledPromise: { resolve(value: ApplyCodeActionCommandResult): void; reject(reason: unknown): void; } | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly telemetryEnabled: boolean,
|
||||
private readonly logger: Logger,
|
||||
private readonly host: ServerHost,
|
||||
readonly globalTypingsCacheLocation: string,
|
||||
telemetryEnabled: boolean,
|
||||
logger: Logger,
|
||||
host: ServerHost,
|
||||
globalTypingsCacheLocation: string,
|
||||
readonly typingSafeListLocation: string,
|
||||
readonly typesMapLocation: string,
|
||||
private readonly npmLocation: string | undefined,
|
||||
private readonly validateDefaultNpmLocation: boolean,
|
||||
private event: server.Event,
|
||||
event: Event,
|
||||
) {
|
||||
super(
|
||||
telemetryEnabled,
|
||||
logger,
|
||||
host,
|
||||
globalTypingsCacheLocation,
|
||||
event,
|
||||
NodeTypingsInstallerAdapter.maxActiveRequestCount,
|
||||
);
|
||||
}
|
||||
|
||||
isKnownTypesPackageName(name: string): boolean {
|
||||
// We want to avoid looking this up in the registry as that is expensive. So first check that it's actually an NPM package.
|
||||
const validationResult = JsTyping.validatePackageName(name);
|
||||
if (validationResult !== JsTyping.NameValidationResult.Ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.requestedRegistry) {
|
||||
return !!this.typesRegistryCache && this.typesRegistryCache.has(name);
|
||||
}
|
||||
|
||||
this.requestedRegistry = true;
|
||||
this.send({ kind: "typesRegistry" });
|
||||
return false;
|
||||
}
|
||||
|
||||
installPackage(options: InstallPackageOptionsWithProject): Promise<ApplyCodeActionCommandResult> {
|
||||
this.send<InstallPackageRequest>({ kind: "installPackage", ...options });
|
||||
Debug.assert(this.packageInstalledPromise === undefined);
|
||||
return new Promise<ApplyCodeActionCommandResult>((resolve, reject) => {
|
||||
this.packageInstalledPromise = { resolve, reject };
|
||||
});
|
||||
}
|
||||
|
||||
attach(projectService: ProjectService) {
|
||||
this.projectService = projectService;
|
||||
createInstallerProcess() {
|
||||
if (this.logger.hasLevel(LogLevel.requestTime)) {
|
||||
this.logger.info("Binding...");
|
||||
}
|
||||
@@ -634,155 +575,7 @@ function startNodeSession(options: StartSessionOptions, logger: Logger, cancella
|
||||
process.on("exit", () => {
|
||||
this.installer.kill();
|
||||
});
|
||||
}
|
||||
|
||||
onProjectClosed(p: Project): void {
|
||||
this.send({ projectName: p.getProjectName(), kind: "closeProject" });
|
||||
}
|
||||
|
||||
private send<T extends TypingInstallerRequestUnion>(rq: T): void {
|
||||
this.installer.send(rq);
|
||||
}
|
||||
|
||||
enqueueInstallTypingsRequest(project: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>): void {
|
||||
const request = createInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Scheduling throttled operation:${stringifyIndented(request)}`);
|
||||
}
|
||||
}
|
||||
|
||||
const operationId = project.getProjectName();
|
||||
const operation = () => {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Sending request:${stringifyIndented(request)}`);
|
||||
}
|
||||
this.send(request);
|
||||
};
|
||||
const queuedRequest: QueuedOperation = { operationId, operation };
|
||||
|
||||
if (this.activeRequestCount < NodeTypingsInstaller.maxActiveRequestCount) {
|
||||
this.scheduleRequest(queuedRequest);
|
||||
}
|
||||
else {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Deferring request for: ${operationId}`);
|
||||
}
|
||||
this.requestQueue.enqueue(queuedRequest);
|
||||
this.requestMap.set(operationId, queuedRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private handleMessage(response: TypesRegistryResponse | PackageInstalledResponse | SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse | server.WatchTypingLocations) {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Received response:${stringifyIndented(response)}`);
|
||||
}
|
||||
|
||||
switch (response.kind) {
|
||||
case EventTypesRegistry:
|
||||
this.typesRegistryCache = new Map(Object.entries(response.typesRegistry));
|
||||
break;
|
||||
case ActionPackageInstalled: {
|
||||
const { success, message } = response;
|
||||
if (success) {
|
||||
this.packageInstalledPromise!.resolve({ successMessage: message });
|
||||
}
|
||||
else {
|
||||
this.packageInstalledPromise!.reject(message);
|
||||
}
|
||||
this.packageInstalledPromise = undefined;
|
||||
|
||||
this.projectService.updateTypingsForProject(response);
|
||||
|
||||
// The behavior is the same as for setTypings, so send the same event.
|
||||
this.event(response, "setTypings");
|
||||
break;
|
||||
}
|
||||
case EventInitializationFailed: {
|
||||
const body: protocol.TypesInstallerInitializationFailedEventBody = {
|
||||
message: response.message,
|
||||
};
|
||||
const eventName: protocol.TypesInstallerInitializationFailedEventName = "typesInstallerInitializationFailed";
|
||||
this.event(body, eventName);
|
||||
break;
|
||||
}
|
||||
case EventBeginInstallTypes: {
|
||||
const body: protocol.BeginInstallTypesEventBody = {
|
||||
eventId: response.eventId,
|
||||
packages: response.packagesToInstall,
|
||||
};
|
||||
const eventName: protocol.BeginInstallTypesEventName = "beginInstallTypes";
|
||||
this.event(body, eventName);
|
||||
break;
|
||||
}
|
||||
case EventEndInstallTypes: {
|
||||
if (this.telemetryEnabled) {
|
||||
const body: protocol.TypingsInstalledTelemetryEventBody = {
|
||||
telemetryEventName: "typingsInstalled",
|
||||
payload: {
|
||||
installedPackages: response.packagesToInstall.join(","),
|
||||
installSuccess: response.installSuccess,
|
||||
typingsInstallerVersion: response.typingsInstallerVersion,
|
||||
},
|
||||
};
|
||||
const eventName: protocol.TelemetryEventName = "telemetry";
|
||||
this.event(body, eventName);
|
||||
}
|
||||
|
||||
const body: protocol.EndInstallTypesEventBody = {
|
||||
eventId: response.eventId,
|
||||
packages: response.packagesToInstall,
|
||||
success: response.installSuccess,
|
||||
};
|
||||
const eventName: protocol.EndInstallTypesEventName = "endInstallTypes";
|
||||
this.event(body, eventName);
|
||||
break;
|
||||
}
|
||||
case ActionInvalidate: {
|
||||
this.projectService.updateTypingsForProject(response);
|
||||
break;
|
||||
}
|
||||
case ActionSet: {
|
||||
if (this.activeRequestCount > 0) {
|
||||
this.activeRequestCount--;
|
||||
}
|
||||
else {
|
||||
Debug.fail("Received too many responses");
|
||||
}
|
||||
|
||||
while (!this.requestQueue.isEmpty()) {
|
||||
const queuedRequest = this.requestQueue.dequeue();
|
||||
if (this.requestMap.get(queuedRequest.operationId) === queuedRequest) {
|
||||
this.requestMap.delete(queuedRequest.operationId);
|
||||
this.scheduleRequest(queuedRequest);
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Skipping defunct request for: ${queuedRequest.operationId}`);
|
||||
}
|
||||
}
|
||||
|
||||
this.projectService.updateTypingsForProject(response);
|
||||
|
||||
this.event(response, "setTypings");
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionWatchTypingLocations:
|
||||
this.projectService.watchTypingLocations(response);
|
||||
break;
|
||||
default:
|
||||
assertType<never>(response);
|
||||
}
|
||||
}
|
||||
|
||||
private scheduleRequest(request: QueuedOperation) {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Scheduling request for: ${request.operationId}`);
|
||||
}
|
||||
this.activeRequestCount++;
|
||||
this.host.setTimeout(request.operation, NodeTypingsInstaller.requestDelayMillis);
|
||||
return this.installer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -802,13 +595,13 @@ function startNodeSession(options: StartSessionOptions, logger: Logger, cancella
|
||||
|
||||
const typingsInstaller = disableAutomaticTypingAcquisition
|
||||
? undefined
|
||||
: new NodeTypingsInstaller(telemetryEnabled, logger, host, getGlobalTypingsCacheLocation(), typingSafeListLocation, typesMapLocation, npmLocation, validateDefaultNpmLocation, event);
|
||||
: new NodeTypingsInstallerAdapter(telemetryEnabled, logger, host, getGlobalTypingsCacheLocation(), typingSafeListLocation, typesMapLocation, npmLocation, validateDefaultNpmLocation, event);
|
||||
|
||||
super({
|
||||
host,
|
||||
cancellationToken,
|
||||
...options,
|
||||
typingsInstaller: typingsInstaller || nullTypingsInstaller,
|
||||
typingsInstaller,
|
||||
byteLength: Buffer.byteLength,
|
||||
hrtime: process.hrtime,
|
||||
logger,
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as path from "path";
|
||||
import {
|
||||
combinePaths,
|
||||
createGetCanonicalFileName,
|
||||
Debug,
|
||||
getDirectoryPath,
|
||||
MapLike,
|
||||
normalizePath,
|
||||
@@ -15,14 +14,12 @@ import {
|
||||
} from "./_namespaces/ts";
|
||||
import {
|
||||
Arguments,
|
||||
EventTypesRegistry,
|
||||
findArgument,
|
||||
hasArgument,
|
||||
InitializationFailedResponse,
|
||||
InstallTypingHost,
|
||||
nowString,
|
||||
stringifyIndented,
|
||||
TypesRegistryResponse,
|
||||
TypingInstallerRequestUnion,
|
||||
TypingInstallerResponseUnion,
|
||||
} from "./_namespaces/ts.server";
|
||||
@@ -156,35 +153,13 @@ export class NodeTypingsInstaller extends TypingsInstaller {
|
||||
this.typesRegistry = loadTypesRegistryFile(getTypesRegistryFileLocation(globalTypingsCacheLocation), this.installTypingHost, this.log);
|
||||
}
|
||||
|
||||
handleRequest(req: TypingInstallerRequestUnion) {
|
||||
override handleRequest(req: TypingInstallerRequestUnion) {
|
||||
if (this.delayedInitializationError) {
|
||||
// report initializationFailed error
|
||||
this.sendResponse(this.delayedInitializationError);
|
||||
this.delayedInitializationError = undefined;
|
||||
}
|
||||
switch (req.kind) {
|
||||
case "discover":
|
||||
this.install(req);
|
||||
break;
|
||||
case "closeProject":
|
||||
this.closeProject(req);
|
||||
break;
|
||||
case "typesRegistry": {
|
||||
const typesRegistry: { [key: string]: MapLike<string>; } = {};
|
||||
this.typesRegistry.forEach((value, key) => {
|
||||
typesRegistry[key] = value;
|
||||
});
|
||||
const response: TypesRegistryResponse = { kind: EventTypesRegistry, typesRegistry };
|
||||
this.sendResponse(response);
|
||||
break;
|
||||
}
|
||||
case "installPackage": {
|
||||
this.installPackage(req);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Debug.assertNever(req);
|
||||
}
|
||||
super.handleRequest(req);
|
||||
}
|
||||
|
||||
protected sendResponse(response: TypingInstallerResponseUnion) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
combinePaths,
|
||||
Debug,
|
||||
forEachAncestorDirectory,
|
||||
forEachKey,
|
||||
getBaseFileName,
|
||||
@@ -28,12 +29,15 @@ import {
|
||||
EndInstallTypes,
|
||||
EventBeginInstallTypes,
|
||||
EventEndInstallTypes,
|
||||
EventTypesRegistry,
|
||||
InstallPackageRequest,
|
||||
InstallTypingHost,
|
||||
InvalidateCachedTypings,
|
||||
PackageInstalledResponse,
|
||||
SetTypings,
|
||||
stringifyIndented,
|
||||
TypesRegistryResponse,
|
||||
TypingInstallerRequestUnion,
|
||||
WatchTypingLocations,
|
||||
} from "./_namespaces/ts.server";
|
||||
|
||||
@@ -110,7 +114,7 @@ export abstract class TypingsInstaller {
|
||||
private readonly projectWatchers = new Map<string, Set<string>>();
|
||||
private safeList: JsTyping.SafeList | undefined;
|
||||
/** @internal */
|
||||
readonly pendingRunRequests: PendingRequest[] = [];
|
||||
private pendingRunRequests: PendingRequest[] = [];
|
||||
|
||||
private installRunCount = 1;
|
||||
private inFlightRequestCount = 0;
|
||||
@@ -132,6 +136,33 @@ export abstract class TypingsInstaller {
|
||||
this.processCacheLocation(this.globalCachePath);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
handleRequest(req: TypingInstallerRequestUnion) {
|
||||
switch (req.kind) {
|
||||
case "discover":
|
||||
this.install(req);
|
||||
break;
|
||||
case "closeProject":
|
||||
this.closeProject(req);
|
||||
break;
|
||||
case "typesRegistry": {
|
||||
const typesRegistry: { [key: string]: MapLike<string>; } = {};
|
||||
this.typesRegistry.forEach((value, key) => {
|
||||
typesRegistry[key] = value;
|
||||
});
|
||||
const response: TypesRegistryResponse = { kind: EventTypesRegistry, typesRegistry };
|
||||
this.sendResponse(response);
|
||||
break;
|
||||
}
|
||||
case "installPackage": {
|
||||
this.installPackage(req);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Debug.assertNever(req);
|
||||
}
|
||||
}
|
||||
|
||||
closeProject(req: CloseProject) {
|
||||
this.closeWatchers(req.projectName);
|
||||
}
|
||||
@@ -493,7 +524,7 @@ export abstract class TypingsInstaller {
|
||||
protected abstract installWorker(requestId: number, packageNames: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void;
|
||||
protected abstract sendResponse(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | WatchTypingLocations): void;
|
||||
/** @internal */
|
||||
protected abstract sendResponse(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | WatchTypingLocations | PackageInstalledResponse): void;
|
||||
protected abstract sendResponse(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | WatchTypingLocations | PackageInstalledResponse | TypesRegistryResponse): void;
|
||||
protected readonly latestDistTag = "latest";
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user