mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-23 10:29:01 -06:00
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:
parent
314172a988
commit
d05443bb1d
@ -51,22 +51,25 @@ class DeclarationsWalker {
|
||||
return this.processType((<any>type).typeArguments[0]);
|
||||
}
|
||||
else {
|
||||
for (const decl of s.getDeclarations()) {
|
||||
const sourceFile = decl.getSourceFile();
|
||||
if (sourceFile === this.protocolFile || path.basename(sourceFile.fileName) === "lib.d.ts") {
|
||||
return;
|
||||
}
|
||||
if (decl.kind === ts.SyntaxKind.EnumDeclaration && !isStringEnum(decl as ts.EnumDeclaration)) {
|
||||
this.removedTypes.push(type);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// splice declaration in final d.ts file
|
||||
let text = decl.getFullText();
|
||||
this.text += `${text}\n`;
|
||||
// recursively pull all dependencies into result dts file
|
||||
const declarations = s.getDeclarations();
|
||||
if (declarations) {
|
||||
for (const decl of declarations) {
|
||||
const sourceFile = decl.getSourceFile();
|
||||
if (sourceFile === this.protocolFile || path.basename(sourceFile.fileName) === "lib.d.ts") {
|
||||
return;
|
||||
}
|
||||
if (decl.kind === ts.SyntaxKind.EnumDeclaration && !isStringEnum(decl as ts.EnumDeclaration)) {
|
||||
this.removedTypes.push(type);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// splice declaration in final d.ts file
|
||||
let text = decl.getFullText();
|
||||
this.text += `${text}\n`;
|
||||
// recursively pull all dependencies into result dts file
|
||||
|
||||
this.visitTypeNodes(decl);
|
||||
this.visitTypeNodes(decl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1861,7 +1861,7 @@ namespace ts {
|
||||
return i < 0 ? path : path.substring(i + 1);
|
||||
}
|
||||
|
||||
export function combinePaths(path1: string, path2: string) {
|
||||
export function combinePaths(path1: string, path2: string): string {
|
||||
if (!(path1 && path1.length)) return path2;
|
||||
if (!(path2 && path2.length)) return path1;
|
||||
if (getRootLength(path2) !== 0) return path2;
|
||||
|
||||
@ -1005,7 +1005,8 @@ namespace ts {
|
||||
return withPackageId(packageId, pathAndExtension);
|
||||
}
|
||||
|
||||
function getPackageName(moduleName: string): { packageName: string, rest: string } {
|
||||
/* @internal */
|
||||
export function getPackageName(moduleName: string): { packageName: string, rest: string } {
|
||||
let idx = moduleName.indexOf(directorySeparator);
|
||||
if (moduleName[0] === "@") {
|
||||
idx = moduleName.indexOf(directorySeparator, idx + 1);
|
||||
@ -1063,18 +1064,27 @@ namespace ts {
|
||||
const mangledScopedPackageSeparator = "__";
|
||||
|
||||
/** For a scoped package, we must look in `@types/foo__bar` instead of `@types/@foo/bar`. */
|
||||
function mangleScopedPackage(moduleName: string, state: ModuleResolutionState): string {
|
||||
if (startsWith(moduleName, "@")) {
|
||||
const replaceSlash = moduleName.replace(ts.directorySeparator, mangledScopedPackageSeparator);
|
||||
if (replaceSlash !== moduleName) {
|
||||
const mangled = replaceSlash.slice(1); // Take off the "@"
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Scoped_package_detected_looking_in_0, mangled);
|
||||
}
|
||||
return mangled;
|
||||
function mangleScopedPackage(packageName: string, state: ModuleResolutionState): string {
|
||||
const mangled = getMangledNameForScopedPackage(packageName);
|
||||
if (state.traceEnabled && mangled !== packageName) {
|
||||
trace(state.host, Diagnostics.Scoped_package_detected_looking_in_0, mangled);
|
||||
}
|
||||
return mangled;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function getTypesPackageName(packageName: string): string {
|
||||
return `@types/${getMangledNameForScopedPackage(packageName)}`;
|
||||
}
|
||||
|
||||
function getMangledNameForScopedPackage(packageName: string): string {
|
||||
if (startsWith(packageName, "@")) {
|
||||
const replaceSlash = packageName.replace(ts.directorySeparator, mangledScopedPackageSeparator);
|
||||
if (replaceSlash !== packageName) {
|
||||
return replaceSlash.slice(1); // Take off the "@"
|
||||
}
|
||||
}
|
||||
return moduleName;
|
||||
return packageName;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
||||
@ -953,6 +953,10 @@ namespace FourSlash {
|
||||
return this.getChecker().getSymbolsInScope(node, ts.SymbolFlags.Value | ts.SymbolFlags.Type | ts.SymbolFlags.Namespace);
|
||||
}
|
||||
|
||||
public setTypesRegistry(map: ts.MapLike<void>): void {
|
||||
this.languageServiceAdapterHost.typesRegistry = ts.createMapFromTemplate(map);
|
||||
}
|
||||
|
||||
public verifyTypeOfSymbolAtLocation(range: Range, symbol: ts.Symbol, expected: string): void {
|
||||
const node = this.goToAndGetNode(range);
|
||||
const checker = this.getChecker();
|
||||
@ -2777,16 +2781,26 @@ Actual: ${stringify(fullActual)}`);
|
||||
}
|
||||
}
|
||||
|
||||
public verifyCodeFixAvailable(negative: boolean) {
|
||||
const codeFix = this.getCodeFixActions(this.activeFile.fileName);
|
||||
public verifyCodeFixAvailable(negative: boolean, info: FourSlashInterface.VerifyCodeFixAvailableOptions[] | undefined) {
|
||||
const codeFixes = this.getCodeFixActions(this.activeFile.fileName);
|
||||
|
||||
if (negative && codeFix.length) {
|
||||
this.raiseError(`verifyCodeFixAvailable failed - expected no fixes but found one.`);
|
||||
if (negative) {
|
||||
if (codeFixes.length) {
|
||||
this.raiseError(`verifyCodeFixAvailable failed - expected no fixes but found one.`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(negative || codeFix.length)) {
|
||||
if (!codeFixes.length) {
|
||||
this.raiseError(`verifyCodeFixAvailable failed - expected code fixes but none found.`);
|
||||
}
|
||||
if (info) {
|
||||
assert.equal(info.length, codeFixes.length);
|
||||
ts.zipWith(codeFixes, info, (fix, info) => {
|
||||
assert.equal(fix.description, info.description);
|
||||
this.assertObjectsEqual(fix.commands, info.commands);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public verifyApplicableRefactorAvailableAtMarker(negative: boolean, markerName: string) {
|
||||
@ -2830,6 +2844,14 @@ Actual: ${stringify(fullActual)}`);
|
||||
}
|
||||
}
|
||||
|
||||
public verifyRefactor({ name, actionName, refactors }: FourSlashInterface.VerifyRefactorOptions) {
|
||||
const selection = this.getSelection();
|
||||
|
||||
const actualRefactors = (this.languageService.getApplicableRefactors(this.activeFile.fileName, selection) || ts.emptyArray)
|
||||
.filter(r => r.name === name && r.actions.some(a => a.name === actionName));
|
||||
this.assertObjectsEqual(actualRefactors, refactors);
|
||||
}
|
||||
|
||||
public verifyApplicableRefactorAvailableForRange(negative: boolean) {
|
||||
const ranges = this.getRanges();
|
||||
if (!(ranges && ranges.length === 1)) {
|
||||
@ -3614,6 +3636,10 @@ namespace FourSlashInterface {
|
||||
public symbolsInScope(range: FourSlash.Range): ts.Symbol[] {
|
||||
return this.state.symbolsInScope(range);
|
||||
}
|
||||
|
||||
public setTypesRegistry(map: ts.MapLike<void>): void {
|
||||
this.state.setTypesRegistry(map);
|
||||
}
|
||||
}
|
||||
|
||||
export class GoTo {
|
||||
@ -3789,8 +3815,8 @@ namespace FourSlashInterface {
|
||||
this.state.verifyCodeFix(options);
|
||||
}
|
||||
|
||||
public codeFixAvailable() {
|
||||
this.state.verifyCodeFixAvailable(this.negative);
|
||||
public codeFixAvailable(options?: VerifyCodeFixAvailableOptions[]) {
|
||||
this.state.verifyCodeFixAvailable(this.negative, options);
|
||||
}
|
||||
|
||||
public applicableRefactorAvailableAtMarker(markerName: string) {
|
||||
@ -3801,6 +3827,10 @@ namespace FourSlashInterface {
|
||||
this.state.verifyApplicableRefactorAvailableForRange(this.negative);
|
||||
}
|
||||
|
||||
public refactor(options: VerifyRefactorOptions) {
|
||||
this.state.verifyRefactor(options);
|
||||
}
|
||||
|
||||
public refactorAvailable(name: string, actionName?: string) {
|
||||
this.state.verifyRefactorAvailable(this.negative, name, actionName);
|
||||
}
|
||||
@ -4449,6 +4479,17 @@ namespace FourSlashInterface {
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export interface VerifyCodeFixAvailableOptions {
|
||||
description: string;
|
||||
commands?: ts.CodeActionCommand[];
|
||||
}
|
||||
|
||||
export interface VerifyRefactorOptions {
|
||||
name: string;
|
||||
actionName: string;
|
||||
refactors: ts.ApplicableRefactorInfo[];
|
||||
}
|
||||
|
||||
export interface VerifyCompletionActionOptions extends NewContentOptions {
|
||||
name: string;
|
||||
description: string;
|
||||
|
||||
@ -123,6 +123,7 @@ namespace Harness.LanguageService {
|
||||
}
|
||||
|
||||
export class LanguageServiceAdapterHost {
|
||||
public typesRegistry: ts.Map<void> | undefined;
|
||||
protected virtualFileSystem: Utils.VirtualFileSystem = new Utils.VirtualFileSystem(virtualFileSystemRoot, /*useCaseSensitiveFilenames*/false);
|
||||
|
||||
constructor(protected cancellationToken = DefaultHostCancellationToken.Instance,
|
||||
@ -182,6 +183,11 @@ namespace Harness.LanguageService {
|
||||
|
||||
/// Native adapter
|
||||
class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost {
|
||||
isKnownTypesPackageName(name: string): boolean {
|
||||
return this.typesRegistry && this.typesRegistry.has(name);
|
||||
}
|
||||
installPackage = ts.notImplemented;
|
||||
|
||||
getCompilationSettings() { return this.settings; }
|
||||
getCancellationToken() { return this.cancellationToken; }
|
||||
getDirectories(path: string): string[] {
|
||||
@ -493,6 +499,7 @@ namespace Harness.LanguageService {
|
||||
getCodeFixesAtPosition(): ts.CodeAction[] {
|
||||
throw new Error("Not supported on the shim.");
|
||||
}
|
||||
applyCodeActionCommand = ts.notImplemented;
|
||||
getCodeFixDiagnostics(): ts.Diagnostic[] {
|
||||
throw new Error("Not supported on the shim.");
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ namespace ts.projectSystem {
|
||||
|
||||
describe("CompileOnSave affected list", () => {
|
||||
function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: { projectFileName: string, files: FileOrFolder[] }[]) {
|
||||
const response: server.protocol.CompileOnSaveAffectedFileListSingleProject[] = session.executeCommand(request).response;
|
||||
const response = session.executeCommand(request).response as server.protocol.CompileOnSaveAffectedFileListSingleProject[];
|
||||
const actualResult = response.sort((list1, list2) => compareStrings(list1.projectFileName, list2.projectFileName));
|
||||
expectedFileList = expectedFileList.sort((list1, list2) => compareStrings(list1.projectFileName, list2.projectFileName));
|
||||
|
||||
|
||||
@ -97,6 +97,14 @@ namespace ts {
|
||||
return rulesProvider;
|
||||
}
|
||||
|
||||
const notImplementedHost: LanguageServiceHost = {
|
||||
getCompilationSettings: notImplemented,
|
||||
getScriptFileNames: notImplemented,
|
||||
getScriptVersion: notImplemented,
|
||||
getScriptSnapshot: notImplemented,
|
||||
getDefaultLibFileName: notImplemented,
|
||||
};
|
||||
|
||||
export function testExtractSymbol(caption: string, text: string, baselineFolder: string, description: DiagnosticMessage) {
|
||||
const t = extractTest(text);
|
||||
const selectionRange = t.ranges.get("selection");
|
||||
@ -125,6 +133,7 @@ namespace ts {
|
||||
file: sourceFile,
|
||||
startPosition: selectionRange.start,
|
||||
endPosition: selectionRange.end,
|
||||
host: notImplementedHost,
|
||||
rulesProvider: getRuleProvider()
|
||||
};
|
||||
const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromBounds(selectionRange.start, selectionRange.end));
|
||||
@ -188,6 +197,7 @@ namespace ts {
|
||||
file: sourceFile,
|
||||
startPosition: selectionRange.start,
|
||||
endPosition: selectionRange.end,
|
||||
host: notImplementedHost,
|
||||
rulesProvider: getRuleProvider()
|
||||
};
|
||||
const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromBounds(selectionRange.start, selectionRange.end));
|
||||
|
||||
@ -57,7 +57,7 @@ namespace ts.projectSystem {
|
||||
});
|
||||
|
||||
checkNumberOfProjects(projectService, { externalProjects: 1 });
|
||||
const diags = session.executeCommand(compilerOptionsRequest).response;
|
||||
const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
|
||||
// only file1 exists - expect error
|
||||
checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]);
|
||||
}
|
||||
@ -65,7 +65,7 @@ namespace ts.projectSystem {
|
||||
{
|
||||
// only file2 exists - expect error
|
||||
checkNumberOfProjects(projectService, { externalProjects: 1 });
|
||||
const diags = session.executeCommand(compilerOptionsRequest).response;
|
||||
const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
|
||||
checkDiagnosticsWithLinePos(diags, ["File '/a/b/app.ts' not found."]);
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ namespace ts.projectSystem {
|
||||
{
|
||||
// both files exist - expect no errors
|
||||
checkNumberOfProjects(projectService, { externalProjects: 1 });
|
||||
const diags = session.executeCommand(compilerOptionsRequest).response;
|
||||
const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
|
||||
checkDiagnosticsWithLinePos(diags, []);
|
||||
}
|
||||
});
|
||||
@ -103,13 +103,13 @@ namespace ts.projectSystem {
|
||||
seq: 2,
|
||||
arguments: { projectFileName: project.getProjectName() }
|
||||
};
|
||||
let diags = session.executeCommand(compilerOptionsRequest).response;
|
||||
let diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
|
||||
checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]);
|
||||
|
||||
host.reloadFS([file1, file2, config, libFile]);
|
||||
|
||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
diags = session.executeCommand(compilerOptionsRequest).response;
|
||||
diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[];
|
||||
checkDiagnosticsWithLinePos(diags, []);
|
||||
});
|
||||
|
||||
|
||||
@ -315,7 +315,7 @@ namespace ts.server {
|
||||
item: false
|
||||
};
|
||||
const command = "newhandle";
|
||||
const result = {
|
||||
const result: ts.server.HandlerResponse = {
|
||||
response: respBody,
|
||||
responseRequired: true
|
||||
};
|
||||
@ -332,7 +332,7 @@ namespace ts.server {
|
||||
const respBody = {
|
||||
item: false
|
||||
};
|
||||
const resp = {
|
||||
const resp: ts.server.HandlerResponse = {
|
||||
response: respBody,
|
||||
responseRequired: true
|
||||
};
|
||||
@ -372,7 +372,7 @@ namespace ts.server {
|
||||
};
|
||||
const command = "test";
|
||||
|
||||
session.output(body, command);
|
||||
session.output(body, command, /*reqSeq*/ 0);
|
||||
|
||||
expect(lastSent).to.deep.equal({
|
||||
seq: 0,
|
||||
@ -475,7 +475,7 @@ namespace ts.server {
|
||||
};
|
||||
const command = "test";
|
||||
|
||||
session.output(body, command);
|
||||
session.output(body, command, /*reqSeq*/ 0);
|
||||
|
||||
expect(session.lastSent).to.deep.equal({
|
||||
seq: 0,
|
||||
@ -542,7 +542,7 @@ namespace ts.server {
|
||||
handleRequest(msg: protocol.Request) {
|
||||
let response: protocol.Response;
|
||||
try {
|
||||
({ response } = this.executeCommand(msg));
|
||||
response = this.executeCommand(msg).response as protocol.Response;
|
||||
}
|
||||
catch (e) {
|
||||
this.output(undefined, msg.command, msg.seq, e.toString());
|
||||
|
||||
@ -70,6 +70,9 @@ namespace ts.projectSystem {
|
||||
|
||||
protected postExecActions: PostExecAction[] = [];
|
||||
|
||||
isKnownTypesPackageName = notImplemented;
|
||||
installPackage = notImplemented;
|
||||
|
||||
executePendingCommands() {
|
||||
const actionsToRun = this.postExecActions;
|
||||
this.postExecActions = [];
|
||||
@ -769,7 +772,7 @@ namespace ts.projectSystem {
|
||||
);
|
||||
|
||||
// Two errors: CommonFile2 not found and cannot find name y
|
||||
let diags: server.protocol.Diagnostic[] = session.executeCommand(getErrRequest).response;
|
||||
let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
|
||||
verifyDiagnostics(diags, [
|
||||
{ diagnosticMessage: Diagnostics.Cannot_find_name_0, errorTextArguments: ["y"] },
|
||||
{ diagnosticMessage: Diagnostics.File_0_not_found, errorTextArguments: [commonFile2.path] }
|
||||
@ -781,7 +784,7 @@ namespace ts.projectSystem {
|
||||
assert.strictEqual(projectService.inferredProjects[0], project, "Inferred project should be same");
|
||||
checkProjectRootFiles(project, [file1.path]);
|
||||
checkProjectActualFiles(project, [file1.path, libFile.path, commonFile2.path]);
|
||||
diags = session.executeCommand(getErrRequest).response;
|
||||
diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
|
||||
verifyNoDiagnostics(diags);
|
||||
});
|
||||
|
||||
@ -2614,11 +2617,11 @@ namespace ts.projectSystem {
|
||||
|
||||
// Try to find some interface type defined in lib.d.ts
|
||||
const libTypeNavToRequest = makeSessionRequest<protocol.NavtoRequestArgs>(CommandNames.Navto, { searchValue: "Document", file: file1.path, projectFileName: configFile.path });
|
||||
const items: protocol.NavtoItem[] = session.executeCommand(libTypeNavToRequest).response;
|
||||
const items = session.executeCommand(libTypeNavToRequest).response as protocol.NavtoItem[];
|
||||
assert.isFalse(containsNavToItem(items, "Document", "interface"), `Found lib.d.ts symbol in JavaScript project nav to request result.`);
|
||||
|
||||
const localFunctionNavToRequst = makeSessionRequest<protocol.NavtoRequestArgs>(CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path });
|
||||
const items2: protocol.NavtoItem[] = session.executeCommand(localFunctionNavToRequst).response;
|
||||
const items2 = session.executeCommand(localFunctionNavToRequst).response as protocol.NavtoItem[];
|
||||
assert.isTrue(containsNavToItem(items2, "foo", "function"), `Cannot find function symbol "foo".`);
|
||||
});
|
||||
});
|
||||
@ -3073,7 +3076,7 @@ namespace ts.projectSystem {
|
||||
server.CommandNames.SemanticDiagnosticsSync,
|
||||
{ file: file1.path }
|
||||
);
|
||||
let diags: server.protocol.Diagnostic[] = session.executeCommand(getErrRequest).response;
|
||||
let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
|
||||
verifyNoDiagnostics(diags);
|
||||
|
||||
const moduleFileOldPath = moduleFile.path;
|
||||
@ -3081,7 +3084,7 @@ namespace ts.projectSystem {
|
||||
moduleFile.path = moduleFileNewPath;
|
||||
host.reloadFS([moduleFile, file1]);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
diags = session.executeCommand(getErrRequest).response;
|
||||
diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
|
||||
verifyDiagnostics(diags, [
|
||||
{ diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] }
|
||||
]);
|
||||
@ -3099,7 +3102,7 @@ namespace ts.projectSystem {
|
||||
session.executeCommand(changeRequest);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
|
||||
diags = session.executeCommand(getErrRequest).response;
|
||||
diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
|
||||
verifyNoDiagnostics(diags);
|
||||
});
|
||||
|
||||
@ -3124,7 +3127,7 @@ namespace ts.projectSystem {
|
||||
server.CommandNames.SemanticDiagnosticsSync,
|
||||
{ file: file1.path }
|
||||
);
|
||||
let diags: server.protocol.Diagnostic[] = session.executeCommand(getErrRequest).response;
|
||||
let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
|
||||
verifyNoDiagnostics(diags);
|
||||
|
||||
const moduleFileOldPath = moduleFile.path;
|
||||
@ -3132,7 +3135,7 @@ namespace ts.projectSystem {
|
||||
moduleFile.path = moduleFileNewPath;
|
||||
host.reloadFS([moduleFile, file1, configFile]);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
diags = session.executeCommand(getErrRequest).response;
|
||||
diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
|
||||
verifyDiagnostics(diags, [
|
||||
{ diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] }
|
||||
]);
|
||||
@ -3140,7 +3143,7 @@ namespace ts.projectSystem {
|
||||
moduleFile.path = moduleFileOldPath;
|
||||
host.reloadFS([moduleFile, file1, configFile]);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
diags = session.executeCommand(getErrRequest).response;
|
||||
diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
|
||||
verifyNoDiagnostics(diags);
|
||||
});
|
||||
|
||||
@ -3207,7 +3210,7 @@ namespace ts.projectSystem {
|
||||
server.CommandNames.SemanticDiagnosticsSync,
|
||||
{ file: file1.path }
|
||||
);
|
||||
let diags: server.protocol.Diagnostic[] = session.executeCommand(getErrRequest).response;
|
||||
let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
|
||||
verifyDiagnostics(diags, [
|
||||
{ diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] }
|
||||
]);
|
||||
@ -3223,7 +3226,7 @@ namespace ts.projectSystem {
|
||||
session.executeCommand(changeRequest);
|
||||
|
||||
// Recheck
|
||||
diags = session.executeCommand(getErrRequest).response;
|
||||
diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[];
|
||||
verifyNoDiagnostics(diags);
|
||||
});
|
||||
});
|
||||
@ -3923,7 +3926,7 @@ namespace ts.projectSystem {
|
||||
command: server.CommandNames.CompilerOptionsDiagnosticsFull,
|
||||
seq: 2,
|
||||
arguments: { projectFileName: projectName }
|
||||
}).response;
|
||||
}).response as ReadonlyArray<protocol.DiagnosticWithLinePosition>;
|
||||
assert.isTrue(diags.length === 0);
|
||||
|
||||
session.executeCommand(<server.protocol.SetCompilerOptionsForInferredProjectsRequest>{
|
||||
@ -3937,7 +3940,7 @@ namespace ts.projectSystem {
|
||||
command: server.CommandNames.CompilerOptionsDiagnosticsFull,
|
||||
seq: 4,
|
||||
arguments: { projectFileName: projectName }
|
||||
}).response;
|
||||
}).response as ReadonlyArray<protocol.DiagnosticWithLinePosition>;
|
||||
assert.isTrue(diagsAfterUpdate.length === 0);
|
||||
});
|
||||
|
||||
@ -3964,7 +3967,7 @@ namespace ts.projectSystem {
|
||||
command: server.CommandNames.CompilerOptionsDiagnosticsFull,
|
||||
seq: 2,
|
||||
arguments: { projectFileName }
|
||||
}).response;
|
||||
}).response as ReadonlyArray<ts.server.protocol.DiagnosticWithLinePosition>;
|
||||
assert.isTrue(diags.length === 0);
|
||||
|
||||
session.executeCommand(<server.protocol.OpenExternalProjectRequest>{
|
||||
@ -3982,7 +3985,7 @@ namespace ts.projectSystem {
|
||||
command: server.CommandNames.CompilerOptionsDiagnosticsFull,
|
||||
seq: 4,
|
||||
arguments: { projectFileName }
|
||||
}).response;
|
||||
}).response as ReadonlyArray<ts.server.protocol.DiagnosticWithLinePosition>;
|
||||
assert.isTrue(diagsAfterUpdate.length === 0);
|
||||
});
|
||||
});
|
||||
@ -4463,7 +4466,7 @@ namespace ts.projectSystem {
|
||||
command: server.CommandNames.SemanticDiagnosticsSync,
|
||||
seq: 2,
|
||||
arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true }
|
||||
}).response;
|
||||
}).response as ReadonlyArray<server.protocol.DiagnosticWithLinePosition>;
|
||||
assert.isTrue(diags.length === 2);
|
||||
|
||||
configFile.content = configFileContentWithoutCommentLine;
|
||||
@ -4474,7 +4477,7 @@ namespace ts.projectSystem {
|
||||
command: server.CommandNames.SemanticDiagnosticsSync,
|
||||
seq: 2,
|
||||
arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true }
|
||||
}).response;
|
||||
}).response as ReadonlyArray<server.protocol.DiagnosticWithLinePosition>;
|
||||
assert.isTrue(diagsAfterEdit.length === 2);
|
||||
|
||||
verifyDiagnostic(diags[0], diagsAfterEdit[0]);
|
||||
@ -4864,7 +4867,7 @@ namespace ts.projectSystem {
|
||||
line: undefined,
|
||||
offset: undefined
|
||||
});
|
||||
const { response } = session.executeCommand(getDefinitionRequest);
|
||||
const response = session.executeCommand(getDefinitionRequest).response as server.protocol.FileSpan[];
|
||||
assert.equal(response[0].file, moduleFile.path, "Should go to definition of vessel: response: " + JSON.stringify(response));
|
||||
callsTrackingHost.verifyNoHostCalls();
|
||||
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
|
||||
namespace ts.projectSystem {
|
||||
import TI = server.typingsInstaller;
|
||||
import validatePackageName = JsTyping.validatePackageName;
|
||||
import PackageNameValidationResult = JsTyping.PackageNameValidationResult;
|
||||
|
||||
interface InstallerParams {
|
||||
globalTypingsCacheLocation?: string;
|
||||
@ -266,7 +268,7 @@ namespace ts.projectSystem {
|
||||
};
|
||||
const host = createServerHost([file1]);
|
||||
let enqueueIsCalled = false;
|
||||
const installer = new (class extends Installer {
|
||||
const installer: Installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { typesRegistry: createTypesRegistry("jquery") });
|
||||
}
|
||||
@ -983,21 +985,21 @@ namespace ts.projectSystem {
|
||||
for (let i = 0; i < 8; i++) {
|
||||
packageName += packageName;
|
||||
}
|
||||
assert.equal(TI.validatePackageName(packageName), TI.PackageNameValidationResult.NameTooLong);
|
||||
assert.equal(validatePackageName(packageName), PackageNameValidationResult.NameTooLong);
|
||||
});
|
||||
it("name cannot start with dot", () => {
|
||||
assert.equal(TI.validatePackageName(".foo"), TI.PackageNameValidationResult.NameStartsWithDot);
|
||||
assert.equal(validatePackageName(".foo"), PackageNameValidationResult.NameStartsWithDot);
|
||||
});
|
||||
it("name cannot start with underscore", () => {
|
||||
assert.equal(TI.validatePackageName("_foo"), TI.PackageNameValidationResult.NameStartsWithUnderscore);
|
||||
assert.equal(validatePackageName("_foo"), PackageNameValidationResult.NameStartsWithUnderscore);
|
||||
});
|
||||
it("scoped packages not supported", () => {
|
||||
assert.equal(TI.validatePackageName("@scope/bar"), TI.PackageNameValidationResult.ScopedPackagesNotSupported);
|
||||
assert.equal(validatePackageName("@scope/bar"), PackageNameValidationResult.ScopedPackagesNotSupported);
|
||||
});
|
||||
it("non URI safe characters are not supported", () => {
|
||||
assert.equal(TI.validatePackageName(" scope "), TI.PackageNameValidationResult.NameContainsNonURISafeCharacters);
|
||||
assert.equal(TI.validatePackageName("; say ‘Hello from TypeScript!’ #"), TI.PackageNameValidationResult.NameContainsNonURISafeCharacters);
|
||||
assert.equal(TI.validatePackageName("a/b/c"), TI.PackageNameValidationResult.NameContainsNonURISafeCharacters);
|
||||
assert.equal(validatePackageName(" scope "), PackageNameValidationResult.NameContainsNonURISafeCharacters);
|
||||
assert.equal(validatePackageName("; say ‘Hello from TypeScript!’ #"), PackageNameValidationResult.NameContainsNonURISafeCharacters);
|
||||
assert.equal(validatePackageName("a/b/c"), PackageNameValidationResult.NameContainsNonURISafeCharacters);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1250,7 +1252,7 @@ namespace ts.projectSystem {
|
||||
const host = createServerHost([f1, packageFile]);
|
||||
let beginEvent: server.BeginInstallTypes;
|
||||
let endEvent: server.EndInstallTypes;
|
||||
const installer = new (class extends Installer {
|
||||
const installer: Installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") });
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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?: {}[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
35
src/services/codefixes/fixCannotFindModule.ts
Normal file
35
src/services/codefixes/fixCannotFindModule.ts
Normal file
@ -0,0 +1,35 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
registerCodeFix({
|
||||
errorCodes: [
|
||||
Diagnostics.Cannot_find_module_0.code,
|
||||
Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type.code,
|
||||
],
|
||||
getCodeActions: context => {
|
||||
const { sourceFile, span: { start } } = context;
|
||||
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
|
||||
if (!isStringLiteral(token)) {
|
||||
throw Debug.fail(); // These errors should only happen on the module name.
|
||||
}
|
||||
|
||||
const action = tryGetCodeActionForInstallPackageTypes(context.host, token.text);
|
||||
return action && [action];
|
||||
},
|
||||
});
|
||||
|
||||
export function tryGetCodeActionForInstallPackageTypes(host: LanguageServiceHost, moduleName: string): CodeAction | undefined {
|
||||
const { packageName } = getPackageName(moduleName);
|
||||
|
||||
if (!host.isKnownTypesPackageName(packageName)) {
|
||||
// If !registry, registry not available yet, can't do anything.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const typesPackageName = getTypesPackageName(packageName);
|
||||
return {
|
||||
description: `Install '${typesPackageName}'`,
|
||||
changes: [],
|
||||
commands: [{ type: "install package", packageName: typesPackageName }],
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@
|
||||
/// <reference path="fixClassIncorrectlyImplementsInterface.ts" />
|
||||
/// <reference path="fixAddMissingMember.ts" />
|
||||
/// <reference path="fixSpelling.ts" />
|
||||
/// <reference path="fixCannotFindModule.ts" />
|
||||
/// <reference path="fixClassDoesntImplementInheritedAbstractMember.ts" />
|
||||
/// <reference path="fixClassSuperMustPrecedeThisAccess.ts" />
|
||||
/// <reference path="fixConstructorForDerivedNeedSuperCall.ts" />
|
||||
|
||||
@ -246,4 +246,65 @@ namespace ts.JsTyping {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const enum PackageNameValidationResult {
|
||||
Ok,
|
||||
ScopedPackagesNotSupported,
|
||||
EmptyName,
|
||||
NameTooLong,
|
||||
NameStartsWithDot,
|
||||
NameStartsWithUnderscore,
|
||||
NameContainsNonURISafeCharacters
|
||||
}
|
||||
|
||||
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 function renderPackageNameValidationFailure(result: PackageNameValidationResult, typing: string): string {
|
||||
switch (result) {
|
||||
case PackageNameValidationResult.EmptyName:
|
||||
return `Package name '${typing}' cannot be empty`;
|
||||
case PackageNameValidationResult.NameTooLong:
|
||||
return `Package name '${typing}' should be less than ${MaxPackageNameLength} characters`;
|
||||
case PackageNameValidationResult.NameStartsWithDot:
|
||||
return `Package name '${typing}' cannot start with '.'`;
|
||||
case PackageNameValidationResult.NameStartsWithUnderscore:
|
||||
return `Package name '${typing}' cannot start with '_'`;
|
||||
case PackageNameValidationResult.ScopedPackagesNotSupported:
|
||||
return `Package '${typing}' is scoped and currently is not supported`;
|
||||
case PackageNameValidationResult.NameContainsNonURISafeCharacters:
|
||||
return `Package name '${typing}' contains non URI safe characters`;
|
||||
case PackageNameValidationResult.Ok:
|
||||
throw Debug.fail(); // Shouldn't have called this.
|
||||
default:
|
||||
Debug.assertNever(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ namespace ts {
|
||||
startPosition: number;
|
||||
endPosition?: number;
|
||||
program: Program;
|
||||
host: LanguageServiceHost;
|
||||
cancellationToken?: CancellationToken;
|
||||
}
|
||||
|
||||
|
||||
63
src/services/refactors/installTypesForPackage.ts
Normal file
63
src/services/refactors/installTypesForPackage.ts
Normal file
@ -0,0 +1,63 @@
|
||||
/* @internal */
|
||||
namespace ts.refactor.installTypesForPackage {
|
||||
const actionName = "install";
|
||||
|
||||
const installTypesForPackage: Refactor = {
|
||||
name: "Install missing types package",
|
||||
description: "Install missing types package",
|
||||
getEditsForAction,
|
||||
getAvailableActions,
|
||||
};
|
||||
|
||||
registerRefactor(installTypesForPackage);
|
||||
|
||||
function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined {
|
||||
if (context.program.getCompilerOptions().noImplicitAny) {
|
||||
// Then it will be available via `fixCannotFindModule`.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const action = getAction(context);
|
||||
return action && [
|
||||
{
|
||||
name: installTypesForPackage.name,
|
||||
description: installTypesForPackage.description,
|
||||
actions: [
|
||||
{
|
||||
description: action.description,
|
||||
name: actionName,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getEditsForAction(context: RefactorContext, _actionName: string): RefactorEditInfo | undefined {
|
||||
Debug.assertEqual(actionName, _actionName);
|
||||
const action = getAction(context)!; // Should be defined if we said there was an action available.
|
||||
return {
|
||||
edits: [],
|
||||
renameFilename: undefined,
|
||||
renameLocation: undefined,
|
||||
commands: action.commands,
|
||||
};
|
||||
}
|
||||
|
||||
function getAction(context: RefactorContext): CodeAction | undefined {
|
||||
const { file, startPosition } = context;
|
||||
const node = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false);
|
||||
if (isStringLiteral(node) && isModuleIdentifier(node) && getResolvedModule(file, node.text) === undefined) {
|
||||
return codefix.tryGetCodeActionForInstallPackageTypes(context.host, node.text);
|
||||
}
|
||||
}
|
||||
|
||||
function isModuleIdentifier(node: StringLiteral): boolean {
|
||||
switch (node.parent.kind) {
|
||||
case SyntaxKind.ImportDeclaration:
|
||||
case SyntaxKind.ExternalModuleReference:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
/// <reference path="annotateWithTypeFromJSDoc.ts" />
|
||||
/// <reference path="convertFunctionToEs6Class.ts" />
|
||||
/// <reference path="extractSymbol.ts" />
|
||||
/// <reference path="installTypesForPackage.ts" />
|
||||
|
||||
@ -1763,6 +1763,19 @@ namespace ts {
|
||||
});
|
||||
}
|
||||
|
||||
function applyCodeActionCommand(fileName: Path, action: CodeActionCommand): PromiseLike<ApplyCodeActionCommandResult> {
|
||||
fileName = toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||
switch (action.type) {
|
||||
case "install package":
|
||||
return host.installPackage
|
||||
? host.installPackage({ fileName, packageName: action.packageName })
|
||||
: Promise.reject("Host does not implement `installPackage`");
|
||||
default:
|
||||
Debug.fail();
|
||||
// TODO: Debug.assertNever(action); will only work if there is more than one type.
|
||||
}
|
||||
}
|
||||
|
||||
function getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion {
|
||||
return JsDoc.getDocCommentTemplateAtPosition(getNewLineOrDefaultFromHost(host), syntaxTreeCache.getCurrentSourceFile(fileName), position);
|
||||
}
|
||||
@ -1972,8 +1985,9 @@ namespace ts {
|
||||
endPosition,
|
||||
program: getProgram(),
|
||||
newLineCharacter: formatOptions ? formatOptions.newLineCharacter : host.getNewLine(),
|
||||
host,
|
||||
rulesProvider: getRuleProvider(formatOptions),
|
||||
cancellationToken
|
||||
cancellationToken,
|
||||
};
|
||||
}
|
||||
|
||||
@ -2035,6 +2049,7 @@ namespace ts {
|
||||
isValidBraceCompletionAtPosition,
|
||||
getSpanOfEnclosingComment,
|
||||
getCodeFixesAtPosition,
|
||||
applyCodeActionCommand,
|
||||
getEmitOutput,
|
||||
getNonBoundSourceFile,
|
||||
getSourceFile,
|
||||
|
||||
@ -145,10 +145,15 @@ namespace ts {
|
||||
isCancellationRequested(): boolean;
|
||||
}
|
||||
|
||||
export interface InstallPackageOptions {
|
||||
fileName: Path;
|
||||
packageName: string;
|
||||
}
|
||||
|
||||
//
|
||||
// Public interface of the host of a language service instance.
|
||||
//
|
||||
export interface LanguageServiceHost {
|
||||
export interface LanguageServiceHost extends GetEffectiveTypeRootsHost {
|
||||
getCompilationSettings(): CompilerOptions;
|
||||
getNewLine?(): string;
|
||||
getProjectVersion?(): string;
|
||||
@ -158,7 +163,6 @@ namespace ts {
|
||||
getScriptSnapshot(fileName: string): IScriptSnapshot | undefined;
|
||||
getLocalizedDiagnosticMessages?(): any;
|
||||
getCancellationToken?(): HostCancellationToken;
|
||||
getCurrentDirectory(): string;
|
||||
getDefaultLibFileName(options: CompilerOptions): string;
|
||||
log?(s: string): void;
|
||||
trace?(s: string): void;
|
||||
@ -187,7 +191,6 @@ namespace ts {
|
||||
resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
||||
/* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution;
|
||||
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
|
||||
directoryExists?(directoryName: string): boolean;
|
||||
|
||||
/*
|
||||
* getDirectories is also required for full import and type reference completions. Without it defined, certain
|
||||
@ -199,6 +202,9 @@ namespace ts {
|
||||
* Gets a set of custom transformers to use during emit.
|
||||
*/
|
||||
getCustomTransformers?(): CustomTransformers | undefined;
|
||||
|
||||
isKnownTypesPackageName?(name: string): boolean;
|
||||
installPackage?(options: InstallPackageOptions): PromiseLike<ApplyCodeActionCommandResult>;
|
||||
}
|
||||
|
||||
//
|
||||
@ -276,6 +282,7 @@ namespace ts {
|
||||
getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan;
|
||||
|
||||
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[];
|
||||
applyCodeActionCommand(fileName: string, action: CodeActionCommand): PromiseLike<ApplyCodeActionCommandResult>;
|
||||
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[];
|
||||
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined;
|
||||
|
||||
@ -295,6 +302,10 @@ namespace ts {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface ApplyCodeActionCommandResult {
|
||||
successMessage: string;
|
||||
}
|
||||
|
||||
export interface Classifications {
|
||||
spans: number[];
|
||||
endOfLineState: EndOfLineState;
|
||||
@ -367,6 +378,20 @@ namespace ts {
|
||||
description: string;
|
||||
/** Text changes to apply to each file as part of the code action */
|
||||
changes: FileTextChanges[];
|
||||
/**
|
||||
* If the user accepts the code fix, the editor should send the action back in a `applyAction` request.
|
||||
* This allows the language service to have side effects (e.g. installing dependencies) upon a code fix.
|
||||
*/
|
||||
commands?: CodeActionCommand[];
|
||||
}
|
||||
|
||||
// Publicly, this type is just `{}`. Internally it is a union of all the actions we use.
|
||||
// See `commands?: {}[]` in protocol.ts
|
||||
export type CodeActionCommand = InstallPackageAction;
|
||||
|
||||
export interface InstallPackageAction {
|
||||
/* @internal */ type: "install package";
|
||||
/* @internal */ packageName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -420,6 +445,7 @@ namespace ts {
|
||||
edits: FileTextChanges[];
|
||||
renameFilename: string | undefined;
|
||||
renameLocation: number | undefined;
|
||||
commands?: CodeActionCommand[];
|
||||
}
|
||||
|
||||
export interface TextInsertion {
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
/* @internal */ // Don't expose that we use this
|
||||
// Based on lib.es6.d.ts
|
||||
interface PromiseConstructor {
|
||||
new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
|
||||
reject(reason: any): Promise<never>;
|
||||
}
|
||||
/* @internal */
|
||||
declare var Promise: PromiseConstructor;
|
||||
|
||||
// These utilities are common to multiple language service features.
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
|
||||
@ -3869,7 +3869,11 @@ declare namespace ts {
|
||||
interface HostCancellationToken {
|
||||
isCancellationRequested(): boolean;
|
||||
}
|
||||
interface LanguageServiceHost {
|
||||
interface InstallPackageOptions {
|
||||
fileName: Path;
|
||||
packageName: string;
|
||||
}
|
||||
interface LanguageServiceHost extends GetEffectiveTypeRootsHost {
|
||||
getCompilationSettings(): CompilerOptions;
|
||||
getNewLine?(): string;
|
||||
getProjectVersion?(): string;
|
||||
@ -3879,7 +3883,6 @@ declare namespace ts {
|
||||
getScriptSnapshot(fileName: string): IScriptSnapshot | undefined;
|
||||
getLocalizedDiagnosticMessages?(): any;
|
||||
getCancellationToken?(): HostCancellationToken;
|
||||
getCurrentDirectory(): string;
|
||||
getDefaultLibFileName(options: CompilerOptions): string;
|
||||
log?(s: string): void;
|
||||
trace?(s: string): void;
|
||||
@ -3891,12 +3894,13 @@ declare namespace ts {
|
||||
getTypeRootsVersion?(): number;
|
||||
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[];
|
||||
resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
||||
directoryExists?(directoryName: string): boolean;
|
||||
getDirectories?(directoryName: string): string[];
|
||||
/**
|
||||
* Gets a set of custom transformers to use during emit.
|
||||
*/
|
||||
getCustomTransformers?(): CustomTransformers | undefined;
|
||||
isKnownTypesPackageName?(name: string): boolean;
|
||||
installPackage?(options: InstallPackageOptions): PromiseLike<ApplyCodeActionCommandResult>;
|
||||
}
|
||||
interface LanguageService {
|
||||
cleanupSemanticCache(): void;
|
||||
@ -3944,6 +3948,7 @@ declare namespace ts {
|
||||
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean;
|
||||
getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan;
|
||||
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[];
|
||||
applyCodeActionCommand(fileName: string, action: CodeActionCommand): PromiseLike<ApplyCodeActionCommandResult>;
|
||||
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[];
|
||||
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined;
|
||||
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
|
||||
@ -3951,6 +3956,9 @@ declare namespace ts {
|
||||
getProgram(): Program;
|
||||
dispose(): void;
|
||||
}
|
||||
interface ApplyCodeActionCommandResult {
|
||||
successMessage: string;
|
||||
}
|
||||
interface Classifications {
|
||||
spans: number[];
|
||||
endOfLineState: EndOfLineState;
|
||||
@ -4015,6 +4023,14 @@ declare namespace ts {
|
||||
description: string;
|
||||
/** Text changes to apply to each file as part of the code action */
|
||||
changes: FileTextChanges[];
|
||||
/**
|
||||
* If the user accepts the code fix, the editor should send the action back in a `applyAction` request.
|
||||
* This allows the language service to have side effects (e.g. installing dependencies) upon a code fix.
|
||||
*/
|
||||
commands?: CodeActionCommand[];
|
||||
}
|
||||
type CodeActionCommand = InstallPackageAction;
|
||||
interface InstallPackageAction {
|
||||
}
|
||||
/**
|
||||
* A set of one or more available refactoring actions, grouped under a parent refactoring.
|
||||
@ -4063,6 +4079,7 @@ declare namespace ts {
|
||||
edits: FileTextChanges[];
|
||||
renameFilename: string | undefined;
|
||||
renameLocation: number | undefined;
|
||||
commands?: CodeActionCommand[];
|
||||
}
|
||||
interface TextInsertion {
|
||||
newText: string;
|
||||
@ -4633,11 +4650,10 @@ declare namespace ts.server {
|
||||
interface SortedReadonlyArray<T> extends ReadonlyArray<T> {
|
||||
" __sortedArrayBrand": any;
|
||||
}
|
||||
interface TypingInstallerRequest {
|
||||
interface TypingInstallerRequestWithProjectName {
|
||||
readonly projectName: string;
|
||||
readonly kind: "discover" | "closeProject";
|
||||
}
|
||||
interface DiscoverTypings extends TypingInstallerRequest {
|
||||
interface DiscoverTypings extends TypingInstallerRequestWithProjectName {
|
||||
readonly fileNames: string[];
|
||||
readonly projectRootPath: Path;
|
||||
readonly compilerOptions: CompilerOptions;
|
||||
@ -4646,16 +4662,27 @@ declare namespace ts.server {
|
||||
readonly cachePath?: string;
|
||||
readonly kind: "discover";
|
||||
}
|
||||
interface CloseProject extends TypingInstallerRequest {
|
||||
interface CloseProject extends TypingInstallerRequestWithProjectName {
|
||||
readonly kind: "closeProject";
|
||||
}
|
||||
interface TypesRegistryRequest {
|
||||
readonly kind: "typesRegistry";
|
||||
}
|
||||
interface InstallPackageRequest {
|
||||
readonly kind: "installPackage";
|
||||
readonly fileName: Path;
|
||||
readonly packageName: string;
|
||||
readonly projectRootPath: Path;
|
||||
}
|
||||
type ActionSet = "action::set";
|
||||
type ActionInvalidate = "action::invalidate";
|
||||
type EventTypesRegistry = "event::typesRegistry";
|
||||
type EventPackageInstalled = "event::packageInstalled";
|
||||
type EventBeginInstallTypes = "event::beginInstallTypes";
|
||||
type EventEndInstallTypes = "event::endInstallTypes";
|
||||
type EventInitializationFailed = "event::initializationFailed";
|
||||
interface TypingInstallerResponse {
|
||||
readonly kind: ActionSet | ActionInvalidate | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed;
|
||||
readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | EventPackageInstalled | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed;
|
||||
}
|
||||
interface InitializationFailedResponse extends TypingInstallerResponse {
|
||||
readonly kind: EventInitializationFailed;
|
||||
@ -4691,6 +4718,8 @@ declare namespace ts.server {
|
||||
declare namespace ts.server {
|
||||
const ActionSet: ActionSet;
|
||||
const ActionInvalidate: ActionInvalidate;
|
||||
const EventTypesRegistry: EventTypesRegistry;
|
||||
const EventPackageInstalled: EventPackageInstalled;
|
||||
const EventBeginInstallTypes: EventBeginInstallTypes;
|
||||
const EventEndInstallTypes: EventEndInstallTypes;
|
||||
const EventInitializationFailed: EventInitializationFailed;
|
||||
@ -4828,6 +4857,7 @@ declare namespace ts.server.protocol {
|
||||
DocCommentTemplate = "docCommentTemplate",
|
||||
CompilerOptionsForInferredProjects = "compilerOptionsForInferredProjects",
|
||||
GetCodeFixes = "getCodeFixes",
|
||||
ApplyCodeActionCommand = "applyCodeActionCommand",
|
||||
GetSupportedCodeFixes = "getSupportedCodeFixes",
|
||||
GetApplicableRefactors = "getApplicableRefactors",
|
||||
GetEditsForRefactor = "getEditsForRefactor",
|
||||
@ -4849,6 +4879,7 @@ declare namespace ts.server.protocol {
|
||||
* Client-initiated request message
|
||||
*/
|
||||
interface Request extends Message {
|
||||
type: "request";
|
||||
/**
|
||||
* The command to execute
|
||||
*/
|
||||
@ -4868,6 +4899,7 @@ declare namespace ts.server.protocol {
|
||||
* Server-initiated event message
|
||||
*/
|
||||
interface Event extends Message {
|
||||
type: "event";
|
||||
/**
|
||||
* Name of event
|
||||
*/
|
||||
@ -4881,6 +4913,7 @@ declare namespace ts.server.protocol {
|
||||
* Response by server to client request message.
|
||||
*/
|
||||
interface Response extends Message {
|
||||
type: "response";
|
||||
/**
|
||||
* Sequence number of the request message.
|
||||
*/
|
||||
@ -4894,7 +4927,8 @@ declare 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;
|
||||
/**
|
||||
@ -5170,6 +5204,12 @@ declare namespace ts.server.protocol {
|
||||
command: CommandTypes.GetCodeFixes;
|
||||
arguments: CodeFixRequestArgs;
|
||||
}
|
||||
interface ApplyCodeActionCommandRequest extends Request {
|
||||
command: CommandTypes.ApplyCodeActionCommand;
|
||||
arguments: ApplyCodeActionCommandRequestArgs;
|
||||
}
|
||||
interface ApplyCodeActionCommandResponse extends Response {
|
||||
}
|
||||
interface FileRangeRequestArgs extends FileRequestArgs {
|
||||
/**
|
||||
* The line number for the request (1-based).
|
||||
@ -5197,6 +5237,9 @@ declare namespace ts.server.protocol {
|
||||
*/
|
||||
errorCodes?: number[];
|
||||
}
|
||||
interface ApplyCodeActionCommandRequestArgs extends FileRequestArgs {
|
||||
command: {};
|
||||
}
|
||||
/**
|
||||
* Response for GetCodeFixes request.
|
||||
*/
|
||||
@ -5935,6 +5978,8 @@ declare 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?: {}[];
|
||||
}
|
||||
/**
|
||||
* Format and format on key response message.
|
||||
@ -6852,6 +6897,7 @@ declare namespace ts.server {
|
||||
send(msg: protocol.Message): void;
|
||||
event<T>(info: T, eventName: string): void;
|
||||
output(info: any, cmdName: string, reqSeq?: number, errorMsg?: string): void;
|
||||
private doOutput(info, cmdName, reqSeq, success, message?);
|
||||
private semanticCheck(file, project);
|
||||
private syntacticCheck(file, project);
|
||||
private updateErrorCheck(next, checkList, ms, requireOpen?);
|
||||
@ -6927,8 +6973,9 @@ declare namespace ts.server {
|
||||
private getApplicableRefactors(args);
|
||||
private getEditsForRefactor(args, simplifiedResult);
|
||||
private getCodeFixes(args, simplifiedResult);
|
||||
private applyCodeActionCommand(commandName, requestSeq, args);
|
||||
private getStartAndEndPosition(args, scriptInfo);
|
||||
private mapCodeAction(codeAction, scriptInfo);
|
||||
private mapCodeAction({description, changes: unmappedChanges, commands}, scriptInfo);
|
||||
private mapTextChangesToCodeEdits(project, textChanges);
|
||||
private convertTextChangeToCodeEdit(change, scriptInfo);
|
||||
private getBraceMatching(args, simplifiedResult);
|
||||
@ -6938,19 +6985,17 @@ declare namespace ts.server {
|
||||
private notRequired();
|
||||
private requiredResponse(response);
|
||||
private handlers;
|
||||
addProtocolHandler(command: string, handler: (request: protocol.Request) => {
|
||||
response?: any;
|
||||
responseRequired: boolean;
|
||||
}): void;
|
||||
addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse): void;
|
||||
private setCurrentRequest(requestId);
|
||||
private resetCurrentRequest(requestId);
|
||||
executeWithRequestId<T>(requestId: number, f: () => T): T;
|
||||
executeCommand(request: protocol.Request): {
|
||||
response?: any;
|
||||
responseRequired?: boolean;
|
||||
};
|
||||
executeCommand(request: protocol.Request): HandlerResponse;
|
||||
onMessage(message: string): void;
|
||||
}
|
||||
interface HandlerResponse {
|
||||
response?: {};
|
||||
responseRequired?: boolean;
|
||||
}
|
||||
}
|
||||
declare namespace ts.server {
|
||||
class ScriptInfo {
|
||||
@ -7015,7 +7060,12 @@ declare namespace ts {
|
||||
function updateWatchingWildcardDirectories(existingWatchedForWildcards: Map<WildcardDirectoryWatcher>, wildcardDirectories: Map<WatchDirectoryFlags>, watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher): void;
|
||||
}
|
||||
declare namespace ts.server {
|
||||
interface InstallPackageOptionsWithProjectRootPath extends InstallPackageOptions {
|
||||
projectRootPath: Path;
|
||||
}
|
||||
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;
|
||||
@ -7026,6 +7076,8 @@ declare namespace ts.server {
|
||||
private readonly installer;
|
||||
private readonly perProjectCache;
|
||||
constructor(installer: ITypingsInstaller);
|
||||
isKnownTypesPackageName(name: string): boolean;
|
||||
installPackage(options: InstallPackageOptionsWithProjectRootPath): PromiseLike<ApplyCodeActionCommandResult>;
|
||||
getTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray<string>, forceRefresh: boolean): SortedReadonlyArray<string>;
|
||||
updateTypingsForProject(projectName: string, compilerOptions: CompilerOptions, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>, newTypings: string[]): void;
|
||||
deleteTypingsForProject(projectName: string): void;
|
||||
@ -7120,6 +7172,9 @@ declare namespace ts.server {
|
||||
isJsOnlyProject(): boolean;
|
||||
getCachedUnresolvedImportsPerFile_TestOnly(): UnresolvedImportsMap;
|
||||
static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void): {};
|
||||
isKnownTypesPackageName(name: string): boolean;
|
||||
installPackage(options: InstallPackageOptions): PromiseLike<ApplyCodeActionCommandResult>;
|
||||
private readonly typingsCache;
|
||||
getCompilationSettings(): CompilerOptions;
|
||||
getCompilerOptions(): CompilerOptions;
|
||||
getNewLine(): string;
|
||||
|
||||
23
tests/baselines/reference/api/typescript.d.ts
vendored
23
tests/baselines/reference/api/typescript.d.ts
vendored
@ -3869,7 +3869,11 @@ declare namespace ts {
|
||||
interface HostCancellationToken {
|
||||
isCancellationRequested(): boolean;
|
||||
}
|
||||
interface LanguageServiceHost {
|
||||
interface InstallPackageOptions {
|
||||
fileName: Path;
|
||||
packageName: string;
|
||||
}
|
||||
interface LanguageServiceHost extends GetEffectiveTypeRootsHost {
|
||||
getCompilationSettings(): CompilerOptions;
|
||||
getNewLine?(): string;
|
||||
getProjectVersion?(): string;
|
||||
@ -3879,7 +3883,6 @@ declare namespace ts {
|
||||
getScriptSnapshot(fileName: string): IScriptSnapshot | undefined;
|
||||
getLocalizedDiagnosticMessages?(): any;
|
||||
getCancellationToken?(): HostCancellationToken;
|
||||
getCurrentDirectory(): string;
|
||||
getDefaultLibFileName(options: CompilerOptions): string;
|
||||
log?(s: string): void;
|
||||
trace?(s: string): void;
|
||||
@ -3891,12 +3894,13 @@ declare namespace ts {
|
||||
getTypeRootsVersion?(): number;
|
||||
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[];
|
||||
resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
||||
directoryExists?(directoryName: string): boolean;
|
||||
getDirectories?(directoryName: string): string[];
|
||||
/**
|
||||
* Gets a set of custom transformers to use during emit.
|
||||
*/
|
||||
getCustomTransformers?(): CustomTransformers | undefined;
|
||||
isKnownTypesPackageName?(name: string): boolean;
|
||||
installPackage?(options: InstallPackageOptions): PromiseLike<ApplyCodeActionCommandResult>;
|
||||
}
|
||||
interface LanguageService {
|
||||
cleanupSemanticCache(): void;
|
||||
@ -3944,6 +3948,7 @@ declare namespace ts {
|
||||
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean;
|
||||
getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan;
|
||||
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[];
|
||||
applyCodeActionCommand(fileName: string, action: CodeActionCommand): PromiseLike<ApplyCodeActionCommandResult>;
|
||||
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[];
|
||||
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined;
|
||||
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
|
||||
@ -3951,6 +3956,9 @@ declare namespace ts {
|
||||
getProgram(): Program;
|
||||
dispose(): void;
|
||||
}
|
||||
interface ApplyCodeActionCommandResult {
|
||||
successMessage: string;
|
||||
}
|
||||
interface Classifications {
|
||||
spans: number[];
|
||||
endOfLineState: EndOfLineState;
|
||||
@ -4015,6 +4023,14 @@ declare namespace ts {
|
||||
description: string;
|
||||
/** Text changes to apply to each file as part of the code action */
|
||||
changes: FileTextChanges[];
|
||||
/**
|
||||
* If the user accepts the code fix, the editor should send the action back in a `applyAction` request.
|
||||
* This allows the language service to have side effects (e.g. installing dependencies) upon a code fix.
|
||||
*/
|
||||
commands?: CodeActionCommand[];
|
||||
}
|
||||
type CodeActionCommand = InstallPackageAction;
|
||||
interface InstallPackageAction {
|
||||
}
|
||||
/**
|
||||
* A set of one or more available refactoring actions, grouped under a parent refactoring.
|
||||
@ -4063,6 +4079,7 @@ declare namespace ts {
|
||||
edits: FileTextChanges[];
|
||||
renameFilename: string | undefined;
|
||||
renameLocation: number | undefined;
|
||||
commands?: CodeActionCommand[];
|
||||
}
|
||||
interface TextInsertion {
|
||||
newText: string;
|
||||
|
||||
15
tests/cases/fourslash/codeFixCannotFindModule.ts
Normal file
15
tests/cases/fourslash/codeFixCannotFindModule.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////import * as abs from "abs";
|
||||
|
||||
test.setTypesRegistry({
|
||||
"abs": undefined,
|
||||
});
|
||||
|
||||
verify.codeFixAvailable([{
|
||||
description: "Install '@types/abs'",
|
||||
commands: [{
|
||||
type: "install package",
|
||||
packageName: "@types/abs",
|
||||
}],
|
||||
}]);
|
||||
@ -118,6 +118,7 @@ declare namespace FourSlashInterface {
|
||||
rangesByText(): ts.Map<Range[]>;
|
||||
markerByName(s: string): Marker;
|
||||
symbolsInScope(range: Range): any[];
|
||||
setTypesRegistry(map: { [key: string]: void }): void;
|
||||
}
|
||||
class goTo {
|
||||
marker(name?: string | Marker): void;
|
||||
@ -169,12 +170,17 @@ declare namespace FourSlashInterface {
|
||||
errorCode?: number,
|
||||
index?: number,
|
||||
});
|
||||
codeFixAvailable(): void;
|
||||
codeFixAvailable(options: Array<{ description: string, actions: Array<{ type: string, data: {} }> }>): void;
|
||||
applicableRefactorAvailableAtMarker(markerName: string): void;
|
||||
codeFixDiagnosticsAvailableAtMarkers(markerNames: string[], diagnosticCode?: number): void;
|
||||
applicableRefactorAvailableForRange(): void;
|
||||
|
||||
refactorAvailable(name: string, actionName?: string);
|
||||
refactorAvailable(name: string, actionName?: string): void;
|
||||
refactor(options: {
|
||||
name: string;
|
||||
actionName: string;
|
||||
refactors: any[];
|
||||
}): void;
|
||||
}
|
||||
class verify extends verifyNegatable {
|
||||
assertHasRanges(ranges: Range[]): void;
|
||||
@ -277,6 +283,7 @@ declare namespace FourSlashInterface {
|
||||
fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, actionName: string, formattingOptions?: FormatCodeOptions): void;
|
||||
rangeIs(expectedText: string, includeWhiteSpace?: boolean): void;
|
||||
fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, formattingOptions?: FormatCodeOptions): void;
|
||||
getAndApplyCodeFix(errorCode?: number, index?: number): void;
|
||||
importFixAtPosition(expectedTextArray: string[], errorCode?: number): void;
|
||||
|
||||
navigationBar(json: any, options?: { checkSpans?: boolean }): void;
|
||||
|
||||
25
tests/cases/fourslash/refactorInstallTypesForPackage.ts
Normal file
25
tests/cases/fourslash/refactorInstallTypesForPackage.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////import * as abs from "/*a*/abs/subModule/*b*/";
|
||||
|
||||
test.setTypesRegistry({
|
||||
"abs": undefined,
|
||||
});
|
||||
|
||||
goTo.select("a", "b");
|
||||
verify.refactor({
|
||||
name: "Install missing types package",
|
||||
actionName: "install",
|
||||
refactors: [
|
||||
{
|
||||
name: "Install missing types package",
|
||||
description: "Install missing types package",
|
||||
actions: [
|
||||
{
|
||||
description: "Install '@types/abs'",
|
||||
name: "install",
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
});
|
||||
@ -0,0 +1,25 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////import abs = require("/*a*/abs/subModule/*b*/");
|
||||
|
||||
test.setTypesRegistry({
|
||||
"abs": undefined,
|
||||
});
|
||||
|
||||
goTo.select("a", "b");
|
||||
verify.refactor({
|
||||
name: "Install missing types package",
|
||||
actionName: "install",
|
||||
refactors: [
|
||||
{
|
||||
name: "Install missing types package",
|
||||
description: "Install missing types package",
|
||||
actions: [
|
||||
{
|
||||
description: "Install '@types/abs'",
|
||||
name: "install",
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user