Add quickfix and refactoring to install @types packages (#19130)

* Add quickfix and refactoring to install @types packages

* Move `validatePackageName` to `jsTyping.ts`

* Remove combinePaths overloads

* Respond to code review

* Update api baselines

* Use native PromiseConstructor

* Return false instead of undefined

* Remove getProjectRootPath

* Update api
This commit is contained in:
Andy
2017-10-17 15:04:09 -07:00
committed by GitHub
parent 314172a988
commit d05443bb1d
36 changed files with 773 additions and 209 deletions

View File

@@ -14,7 +14,7 @@ namespace ts.server {
}
/* @internal */
export function extractMessage(message: string) {
export function extractMessage(message: string): string {
// Read the content length
const contentLengthPrefix = "Content-Length: ";
const lines = message.split(/\r?\n/);
@@ -542,6 +542,8 @@ namespace ts.server {
return response.body.map(entry => this.convertCodeActions(entry, file));
}
applyCodeActionCommand = notImplemented;
private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs {
return typeof positionOrRange === "number"
? this.createFileLocationRequestArgs(fileName, positionOrRange)

View File

@@ -242,6 +242,16 @@ namespace ts.server {
this.markAsDirty();
}
isKnownTypesPackageName(name: string): boolean {
return this.typingsCache.isKnownTypesPackageName(name);
}
installPackage(options: InstallPackageOptions): PromiseLike<ApplyCodeActionCommandResult> {
return this.typingsCache.installPackage({ ...options, projectRootPath: this.toPath(this.currentDirectory) });
}
private get typingsCache(): TypingsCache {
return this.projectService.typingsCache;
}
// Method of LanguageServiceHost
getCompilationSettings() {
return this.compilerOptions;

View File

@@ -94,6 +94,7 @@ namespace ts.server.protocol {
BreakpointStatement = "breakpointStatement",
CompilerOptionsForInferredProjects = "compilerOptionsForInferredProjects",
GetCodeFixes = "getCodeFixes",
ApplyCodeActionCommand = "applyCodeActionCommand",
/* @internal */
GetCodeFixesFull = "getCodeFixes-full",
GetSupportedCodeFixes = "getSupportedCodeFixes",
@@ -125,6 +126,8 @@ namespace ts.server.protocol {
* Client-initiated request message
*/
export interface Request extends Message {
type: "request";
/**
* The command to execute
*/
@@ -147,6 +150,8 @@ namespace ts.server.protocol {
* Server-initiated event message
*/
export interface Event extends Message {
type: "event";
/**
* Name of event
*/
@@ -162,6 +167,8 @@ namespace ts.server.protocol {
* Response by server to client request message.
*/
export interface Response extends Message {
type: "response";
/**
* Sequence number of the request message.
*/
@@ -178,7 +185,8 @@ namespace ts.server.protocol {
command: string;
/**
* Contains error message if success === false.
* If success === false, this should always be provided.
* Otherwise, may (or may not) contain a success message.
*/
message?: string;
@@ -520,6 +528,14 @@ namespace ts.server.protocol {
arguments: CodeFixRequestArgs;
}
export interface ApplyCodeActionCommandRequest extends Request {
command: CommandTypes.ApplyCodeActionCommand;
arguments: ApplyCodeActionCommandRequestArgs;
}
// All we need is the `success` and `message` fields of Response.
export interface ApplyCodeActionCommandResponse extends Response {}
export interface FileRangeRequestArgs extends FileRequestArgs {
/**
* The line number for the request (1-based).
@@ -564,6 +580,10 @@ namespace ts.server.protocol {
errorCodes?: number[];
}
export interface ApplyCodeActionCommandRequestArgs extends FileRequestArgs {
command: {};
}
/**
* Response for GetCodeFixes request.
*/
@@ -1541,6 +1561,8 @@ namespace ts.server.protocol {
description: string;
/** Text changes to apply to each file as part of the code action */
changes: FileCodeEdits[];
/** A command is an opaque object that should be passed to `ApplyCodeActionCommandRequestArgs` without modification. */
commands?: {}[];
}
/**

View File

@@ -250,6 +250,9 @@ namespace ts.server {
private activeRequestCount = 0;
private requestQueue: QueuedOperation[] = [];
private requestMap = createMap<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: boolean;
private typesRegistryCache: Map<void> | 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
@@ -258,7 +261,7 @@ namespace ts.server {
// 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: any): void };
constructor(
private readonly telemetryEnabled: boolean,
@@ -278,6 +281,31 @@ namespace ts.server {
}
}
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.PackageNameValidationResult.Ok) {
return false;
}
if (this.requestedRegistry) {
return !!this.typesRegistryCache && this.typesRegistryCache.has(name);
}
this.requestedRegistry = true;
this.send({ kind: "typesRegistry" });
return false;
}
installPackage(options: InstallPackageOptionsWithProjectRootPath): PromiseLike<ApplyCodeActionCommandResult> {
const rq: InstallPackageRequest = { kind: "installPackage", ...options };
this.send(rq);
Debug.assert(this.packageInstalledPromise === undefined);
return new Promise((resolve, reject) => {
this.packageInstalledPromise = { resolve, reject };
});
}
private reportInstallerProcessId() {
if (this.installerPidReported) {
return;
@@ -343,7 +371,11 @@ namespace ts.server {
}
onProjectClosed(p: Project): void {
this.installer.send({ projectName: p.getProjectName(), kind: "closeProject" });
this.send({ projectName: p.getProjectName(), kind: "closeProject" });
}
private send(rq: TypingInstallerRequestUnion): void {
this.installer.send(rq);
}
enqueueInstallTypingsRequest(project: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>): void {
@@ -359,7 +391,7 @@ namespace ts.server {
if (this.logger.hasLevel(LogLevel.verbose)) {
this.logger.info(`Sending request:${stringifyIndented(request)}`);
}
this.installer.send(request);
this.send(request);
};
const queuedRequest: QueuedOperation = { operationId, operation };
@@ -375,12 +407,26 @@ namespace ts.server {
}
}
private handleMessage(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) {
private handleMessage(response: TypesRegistryResponse | PackageInstalledResponse | SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) {
if (this.logger.hasLevel(LogLevel.verbose)) {
this.logger.info(`Received response:${stringifyIndented(response)}`);
}
switch (response.kind) {
case EventTypesRegistry:
this.typesRegistryCache = ts.createMapFromTemplate(response.typesRegistry);
break;
case EventPackageInstalled: {
const { success, message } = response;
if (success) {
this.packageInstalledPromise.resolve({ successMessage: message });
}
else {
this.packageInstalledPromise.reject(message);
}
this.packageInstalledPromise = undefined;
break;
}
case EventInitializationFailed:
{
if (!this.eventSender) {

View File

@@ -411,19 +411,27 @@ namespace ts.server {
this.send(ev);
}
public output(info: any, cmdName: string, reqSeq = 0, errorMsg?: string) {
// For backwards-compatibility only.
public output(info: any, cmdName: string, reqSeq?: number, errorMsg?: string): void {
this.doOutput(info, cmdName, reqSeq, /*success*/ !errorMsg, errorMsg);
}
private doOutput(info: {} | undefined, cmdName: string, reqSeq: number, success: boolean, message?: string): void {
const res: protocol.Response = {
seq: 0,
type: "response",
command: cmdName,
request_seq: reqSeq,
success: !errorMsg,
success,
};
if (!errorMsg) {
if (success) {
res.body = info;
}
else {
res.message = errorMsg;
Debug.assert(info === undefined);
}
if (message) {
res.message = message;
}
this.send(res);
}
@@ -1307,7 +1315,7 @@ namespace ts.server {
this.changeSeq++;
// make sure no changes happen before this one is finished
if (project.reloadScript(file, tempFileName)) {
this.output(undefined, CommandNames.Reload, reqSeq);
this.doOutput(/*info*/ undefined, CommandNames.Reload, reqSeq, /*success*/ true);
}
}
@@ -1545,6 +1553,15 @@ namespace ts.server {
}
}
private applyCodeActionCommand(commandName: string, requestSeq: number, args: protocol.ApplyCodeActionCommandRequestArgs): void {
const { file, project } = this.getFileAndProject(args);
const output = (success: boolean, message: string) => this.doOutput({}, commandName, requestSeq, success, message);
const command = args.command as CodeActionCommand; // They should be sending back the command we sent them.
project.getLanguageService().applyCodeActionCommand(file, command).then(
({ successMessage }) => { output(/*success*/ true, successMessage); },
error => { output(/*success*/ false, error); });
}
private getStartAndEndPosition(args: protocol.FileRangeRequestArgs, scriptInfo: ScriptInfo) {
let startPosition: number = undefined, endPosition: number = undefined;
if (args.startPosition !== undefined) {
@@ -1567,14 +1584,12 @@ namespace ts.server {
return { startPosition, endPosition };
}
private mapCodeAction(codeAction: CodeAction, scriptInfo: ScriptInfo): protocol.CodeAction {
return {
description: codeAction.description,
changes: codeAction.changes.map(change => ({
fileName: change.fileName,
textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo))
}))
};
private mapCodeAction({ description, changes: unmappedChanges, commands }: CodeAction, scriptInfo: ScriptInfo): protocol.CodeAction {
const changes = unmappedChanges.map(change => ({
fileName: change.fileName,
textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo))
}));
return { description, changes, commands };
}
private mapTextChangesToCodeEdits(project: Project, textChanges: FileTextChanges): protocol.FileCodeEdits {
@@ -1660,15 +1675,15 @@ namespace ts.server {
exit() {
}
private notRequired() {
private notRequired(): HandlerResponse {
return { responseRequired: false };
}
private requiredResponse(response: any) {
private requiredResponse(response: {}): HandlerResponse {
return { response, responseRequired: true };
}
private handlers = createMapFromTemplate<(request: protocol.Request) => { response?: any, responseRequired?: boolean }>({
private handlers = createMapFromTemplate<(request: protocol.Request) => HandlerResponse>({
[CommandNames.OpenExternalProject]: (request: protocol.OpenExternalProjectRequest) => {
this.projectService.openExternalProject(request.arguments, /*suppressRefreshOfInferredProjects*/ false);
// TODO: report errors
@@ -1846,7 +1861,7 @@ namespace ts.server {
},
[CommandNames.Configure]: (request: protocol.ConfigureRequest) => {
this.projectService.setHostConfiguration(request.arguments);
this.output(undefined, CommandNames.Configure, request.seq);
this.doOutput(/*info*/ undefined, CommandNames.Configure, request.seq, /*success*/ true);
return this.notRequired();
},
[CommandNames.Reload]: (request: protocol.ReloadRequest) => {
@@ -1913,6 +1928,10 @@ namespace ts.server {
[CommandNames.GetCodeFixesFull]: (request: protocol.CodeFixRequest) => {
return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ false));
},
[CommandNames.ApplyCodeActionCommand]: (request: protocol.ApplyCodeActionCommandRequest) => {
this.applyCodeActionCommand(request.command, request.seq, request.arguments);
return this.notRequired(); // Response will come asynchronously.
},
[CommandNames.GetSupportedCodeFixes]: () => {
return this.requiredResponse(this.getSupportedCodeFixes());
},
@@ -1927,7 +1946,7 @@ namespace ts.server {
}
});
public addProtocolHandler(command: string, handler: (request: protocol.Request) => { response?: any, responseRequired: boolean }) {
public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) {
if (this.handlers.has(command)) {
throw new Error(`Protocol handler already exists for command "${command}"`);
}
@@ -1956,14 +1975,14 @@ namespace ts.server {
}
}
public executeCommand(request: protocol.Request): { response?: any, responseRequired?: boolean } {
public executeCommand(request: protocol.Request): HandlerResponse {
const handler = this.handlers.get(request.command);
if (handler) {
return this.executeWithRequestId(request.seq, () => handler(request));
}
else {
this.logger.msg(`Unrecognized JSON command:${stringifyIndented(request)}`, Msg.Err);
this.output(undefined, CommandNames.Unknown, request.seq, `Unrecognized JSON command: ${request.command}`);
this.doOutput(/*info*/ undefined, CommandNames.Unknown, request.seq, /*success*/ false, `Unrecognized JSON command: ${request.command}`);
return { responseRequired: false };
}
}
@@ -1994,25 +2013,31 @@ namespace ts.server {
}
if (response) {
this.output(response, request.command, request.seq);
this.doOutput(response, request.command, request.seq, /*success*/ true);
}
else if (responseRequired) {
this.output(undefined, request.command, request.seq, "No content available.");
this.doOutput(/*info*/ undefined, request.command, request.seq, /*success*/ false, "No content available.");
}
}
catch (err) {
if (err instanceof OperationCanceledException) {
// Handle cancellation exceptions
this.output({ canceled: true }, request.command, request.seq);
this.doOutput({ canceled: true }, request.command, request.seq, /*success*/ true);
return;
}
this.logError(err, message);
this.output(
undefined,
this.doOutput(
/*info*/ undefined,
request ? request.command : CommandNames.Unknown,
request ? request.seq : 0,
/*success*/ false,
"Error processing request. " + (<StackTraceError>err).message + "\n" + (<StackTraceError>err).stack);
}
}
}
export interface HandlerResponse {
response?: {};
responseRequired?: boolean;
}
}

View File

@@ -3,6 +3,8 @@
namespace ts.server {
export const ActionSet: ActionSet = "action::set";
export const ActionInvalidate: ActionInvalidate = "action::invalidate";
export const EventTypesRegistry: EventTypesRegistry = "event::typesRegistry";
export const EventPackageInstalled: EventPackageInstalled = "event::packageInstalled";
export const EventBeginInstallTypes: EventBeginInstallTypes = "event::beginInstallTypes";
export const EventEndInstallTypes: EventEndInstallTypes = "event::endInstallTypes";
export const EventInitializationFailed: EventInitializationFailed = "event::initializationFailed";

View File

@@ -28,12 +28,14 @@ declare namespace ts.server {
" __sortedArrayBrand": any;
}
export interface TypingInstallerRequest {
export interface TypingInstallerRequestWithProjectName {
readonly projectName: string;
readonly kind: "discover" | "closeProject";
}
export interface DiscoverTypings extends TypingInstallerRequest {
/* @internal */
export type TypingInstallerRequestUnion = DiscoverTypings | CloseProject | TypesRegistryRequest | InstallPackageRequest;
export interface DiscoverTypings extends TypingInstallerRequestWithProjectName {
readonly fileNames: string[];
readonly projectRootPath: Path;
readonly compilerOptions: CompilerOptions;
@@ -43,18 +45,46 @@ declare namespace ts.server {
readonly kind: "discover";
}
export interface CloseProject extends TypingInstallerRequest {
export interface CloseProject extends TypingInstallerRequestWithProjectName {
readonly kind: "closeProject";
}
export interface TypesRegistryRequest {
readonly kind: "typesRegistry";
}
export interface InstallPackageRequest {
readonly kind: "installPackage";
readonly fileName: Path;
readonly packageName: string;
readonly projectRootPath: Path;
}
export type ActionSet = "action::set";
export type ActionInvalidate = "action::invalidate";
export type EventTypesRegistry = "event::typesRegistry";
export type EventPackageInstalled = "event::packageInstalled";
export type EventBeginInstallTypes = "event::beginInstallTypes";
export type EventEndInstallTypes = "event::endInstallTypes";
export type EventInitializationFailed = "event::initializationFailed";
export interface TypingInstallerResponse {
readonly kind: ActionSet | ActionInvalidate | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed;
readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | EventPackageInstalled | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed;
}
/* @internal */
export type TypingInstallerResponseUnion = SetTypings | InvalidateCachedTypings | TypesRegistryResponse | PackageInstalledResponse | InstallTypes | InitializationFailedResponse;
/* @internal */
export interface TypesRegistryResponse extends TypingInstallerResponse {
readonly kind: EventTypesRegistry;
readonly typesRegistry: MapLike<void>;
}
/* @internal */
export interface PackageInstalledResponse extends TypingInstallerResponse {
readonly kind: EventPackageInstalled;
readonly success: boolean;
readonly message: string;
}
export interface InitializationFailedResponse extends TypingInstallerResponse {

View File

@@ -1,7 +1,13 @@
/// <reference path="project.ts"/>
namespace ts.server {
export interface InstallPackageOptionsWithProjectRootPath extends InstallPackageOptions {
projectRootPath: Path;
}
export interface ITypingsInstaller {
isKnownTypesPackageName(name: string): boolean;
installPackage(options: InstallPackageOptionsWithProjectRootPath): PromiseLike<ApplyCodeActionCommandResult>;
enqueueInstallTypingsRequest(p: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>): void;
attach(projectService: ProjectService): void;
onProjectClosed(p: Project): void;
@@ -9,6 +15,9 @@ namespace ts.server {
}
export const nullTypingsInstaller: ITypingsInstaller = {
isKnownTypesPackageName: returnFalse,
// Should never be called because we never provide a types registry.
installPackage: notImplemented,
enqueueInstallTypingsRequest: noop,
attach: noop,
onProjectClosed: noop,
@@ -77,6 +86,14 @@ namespace ts.server {
constructor(private readonly installer: ITypingsInstaller) {
}
isKnownTypesPackageName(name: string): boolean {
return this.installer.isKnownTypesPackageName(name);
}
installPackage(options: InstallPackageOptionsWithProjectRootPath): PromiseLike<ApplyCodeActionCommandResult> {
return this.installer.installPackage(options);
}
getTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray<string>, forceRefresh: boolean): SortedReadonlyArray<string> {
const typeAcquisition = project.getTypeAcquisition();

View File

@@ -53,7 +53,7 @@ namespace ts.server.typingsInstaller {
}
try {
const content = <TypesRegistryFile>JSON.parse(host.readFile(typesRegistryFilePath));
return createMapFromTemplate<void>(content.entries);
return createMapFromTemplate(content.entries);
}
catch (e) {
if (log.isEnabled()) {
@@ -79,7 +79,7 @@ namespace ts.server.typingsInstaller {
private readonly npmPath: string;
readonly typesRegistry: Map<void>;
private delayedInitializationError: InitializationFailedResponse;
private delayedInitializationError: InitializationFailedResponse | undefined;
constructor(globalTypingsCacheLocation: string, typingSafeListLocation: string, typesMapLocation: string, npmLocation: string | undefined, throttleLimit: number, log: Log) {
super(
@@ -127,7 +127,7 @@ namespace ts.server.typingsInstaller {
}
listen() {
process.on("message", (req: DiscoverTypings | CloseProject) => {
process.on("message", (req: TypingInstallerRequestUnion) => {
if (this.delayedInitializationError) {
// report initializationFailed error
this.sendResponse(this.delayedInitializationError);
@@ -139,11 +139,39 @@ namespace ts.server.typingsInstaller {
break;
case "closeProject":
this.closeProject(req);
break;
case "typesRegistry": {
const typesRegistry: { [key: string]: void } = {};
this.typesRegistry.forEach((value, key) => {
typesRegistry[key] = value;
});
const response: TypesRegistryResponse = { kind: EventTypesRegistry, typesRegistry };
this.sendResponse(response);
break;
}
case "installPackage": {
const { fileName, packageName, projectRootPath } = req;
const cwd = getDirectoryOfPackageJson(fileName, this.installTypingHost) || projectRootPath;
if (cwd) {
this.installWorker(-1, [packageName], cwd, success => {
const message = success ? `Package ${packageName} installed.` : `There was an error installing ${packageName}.`;
const response: PackageInstalledResponse = { kind: EventPackageInstalled, success, message };
this.sendResponse(response);
});
}
else {
const response: PackageInstalledResponse = { kind: EventPackageInstalled, success: false, message: "Could not determine a project root path." };
this.sendResponse(response);
}
break;
}
default:
Debug.assertNever(req);
}
});
}
protected sendResponse(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) {
protected sendResponse(response: TypingInstallerResponseUnion) {
if (this.log.isEnabled()) {
this.log.writeLine(`Sending response:\n ${JSON.stringify(response)}`);
}
@@ -153,11 +181,11 @@ namespace ts.server.typingsInstaller {
}
}
protected installWorker(requestId: number, args: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void {
protected installWorker(requestId: number, packageNames: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void {
if (this.log.isEnabled()) {
this.log.writeLine(`#${requestId} with arguments'${JSON.stringify(args)}'.`);
this.log.writeLine(`#${requestId} with arguments'${JSON.stringify(packageNames)}'.`);
}
const command = `${this.npmPath} install --ignore-scripts ${args.join(" ")} --save-dev --user-agent="typesInstaller/${version}"`;
const command = `${this.npmPath} install --ignore-scripts ${packageNames.join(" ")} --save-dev --user-agent="typesInstaller/${version}"`;
const start = Date.now();
const hasError = this.execSyncAndLog(command, { cwd });
if (this.log.isEnabled()) {
@@ -186,6 +214,14 @@ namespace ts.server.typingsInstaller {
}
}
function getDirectoryOfPackageJson(fileName: string, host: InstallTypingHost): string | undefined {
return forEachAncestorDirectory(getDirectoryPath(fileName), directory => {
if (host.fileExists(combinePaths(directory, "package.json"))) {
return directory;
}
});
}
const logFilePath = findArgument(server.Arguments.LogFile);
const globalTypingsCacheLocation = findArgument(server.Arguments.GlobalCacheLocation);
const typingSafeListLocation = findArgument(server.Arguments.TypingSafeListLocation);

View File

@@ -32,50 +32,11 @@ namespace ts.server.typingsInstaller {
}
}
export enum PackageNameValidationResult {
Ok,
ScopedPackagesNotSupported,
EmptyName,
NameTooLong,
NameStartsWithDot,
NameStartsWithUnderscore,
NameContainsNonURISafeCharacters
}
export const MaxPackageNameLength = 214;
/**
* Validates package name using rules defined at https://docs.npmjs.com/files/package.json
*/
export function validatePackageName(packageName: string): PackageNameValidationResult {
if (!packageName) {
return PackageNameValidationResult.EmptyName;
}
if (packageName.length > MaxPackageNameLength) {
return PackageNameValidationResult.NameTooLong;
}
if (packageName.charCodeAt(0) === CharacterCodes.dot) {
return PackageNameValidationResult.NameStartsWithDot;
}
if (packageName.charCodeAt(0) === CharacterCodes._) {
return PackageNameValidationResult.NameStartsWithUnderscore;
}
// check if name is scope package like: starts with @ and has one '/' in the middle
// scoped packages are not currently supported
// TODO: when support will be added we'll need to split and check both scope and package name
if (/^@[^/]+\/[^/]+$/.test(packageName)) {
return PackageNameValidationResult.ScopedPackagesNotSupported;
}
if (encodeURIComponent(packageName) !== packageName) {
return PackageNameValidationResult.NameContainsNonURISafeCharacters;
}
return PackageNameValidationResult.Ok;
}
export type RequestCompletedAction = (success: boolean) => void;
interface PendingRequest {
requestId: number;
args: string[];
packageNames: string[];
cwd: string;
onRequestCompleted: RequestCompletedAction;
}
@@ -255,8 +216,8 @@ namespace ts.server.typingsInstaller {
if (this.missingTypingsSet.get(typing) || this.packageNameToTypingLocation.get(typing)) {
continue;
}
const validationResult = validatePackageName(typing);
if (validationResult === PackageNameValidationResult.Ok) {
const validationResult = JsTyping.validatePackageName(typing);
if (validationResult === JsTyping.PackageNameValidationResult.Ok) {
if (this.typesRegistry.has(typing)) {
result.push(typing);
}
@@ -270,26 +231,7 @@ namespace ts.server.typingsInstaller {
// add typing name to missing set so we won't process it again
this.missingTypingsSet.set(typing, true);
if (this.log.isEnabled()) {
switch (validationResult) {
case PackageNameValidationResult.EmptyName:
this.log.writeLine(`Package name '${typing}' cannot be empty`);
break;
case PackageNameValidationResult.NameTooLong:
this.log.writeLine(`Package name '${typing}' should be less than ${MaxPackageNameLength} characters`);
break;
case PackageNameValidationResult.NameStartsWithDot:
this.log.writeLine(`Package name '${typing}' cannot start with '.'`);
break;
case PackageNameValidationResult.NameStartsWithUnderscore:
this.log.writeLine(`Package name '${typing}' cannot start with '_'`);
break;
case PackageNameValidationResult.ScopedPackagesNotSupported:
this.log.writeLine(`Package '${typing}' is scoped and currently is not supported`);
break;
case PackageNameValidationResult.NameContainsNonURISafeCharacters:
this.log.writeLine(`Package name '${typing}' contains non URI safe characters`);
break;
}
this.log.writeLine(JsTyping.renderPackageNameValidationFailure(validationResult, typing));
}
}
}
@@ -430,8 +372,8 @@ namespace ts.server.typingsInstaller {
};
}
private installTypingsAsync(requestId: number, args: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void {
this.pendingRunRequests.unshift({ requestId, args, cwd, onRequestCompleted });
private installTypingsAsync(requestId: number, packageNames: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void {
this.pendingRunRequests.unshift({ requestId, packageNames, cwd, onRequestCompleted });
this.executeWithThrottling();
}
@@ -439,7 +381,7 @@ namespace ts.server.typingsInstaller {
while (this.inFlightRequestCount < this.throttleLimit && this.pendingRunRequests.length) {
this.inFlightRequestCount++;
const request = this.pendingRunRequests.pop();
this.installWorker(request.requestId, request.args, request.cwd, ok => {
this.installWorker(request.requestId, request.packageNames, request.cwd, ok => {
this.inFlightRequestCount--;
request.onRequestCompleted(ok);
this.executeWithThrottling();
@@ -447,7 +389,7 @@ namespace ts.server.typingsInstaller {
}
}
protected abstract installWorker(requestId: number, args: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void;
protected abstract installWorker(requestId: number, packageNames: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void;
protected abstract sendResponse(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes): void;
}