diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index efa10943793..d131cb68f1c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3135,9 +3135,8 @@ namespace ts { for (const prop of getPropertiesOfType(source)) { const inNamesToRemove = names.has(prop.name); const isPrivate = getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected); - const isMethod = prop.flags & SymbolFlags.Method; const isSetOnlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); - if (!inNamesToRemove && !isPrivate && !isMethod && !isSetOnlyAccessor) { + if (!inNamesToRemove && !isPrivate && !isClassMethod(prop) && !isSetOnlyAccessor) { members.set(prop.name, prop); } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 6bdf9932fca..52fc401d1f1 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3247,7 +3247,7 @@ "category": "Message", "code": 90003 }, - "Remove unused identifiers.": { + "Remove declaration for: {0}": { "category": "Message", "code": 90004 }, diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index a75acec53a5..b790e75d168 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2141,7 +2141,7 @@ namespace FourSlash { public verifyBraceCompletionAtPosition(negative: boolean, openingBrace: string) { - const openBraceMap = ts.createMap({ + const openBraceMap = ts.createMapFromTemplate({ "(": ts.CharacterCodes.openParen, "{": ts.CharacterCodes.openBrace, "[": ts.CharacterCodes.openBracket, diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 8bdf787a8f0..ad2e62067d4 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -909,7 +909,7 @@ namespace Harness { export const defaultLibFileName = "lib.d.ts"; export const es2015DefaultLibFileName = "lib.es2015.d.ts"; - const libFileNameSourceFileMap = ts.createMap({ + const libFileNameSourceFileMap = ts.createMapFromTemplate({ [defaultLibFileName]: createSourceFileAndAssertInvariants(defaultLibFileName, IO.readFile(libFolder + "lib.es5.d.ts"), /*languageVersion*/ ts.ScriptTarget.Latest) }); diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index b2e2eca2951..5ed0d981511 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -1616,7 +1616,7 @@ namespace ts.projectSystem { const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions }); session.executeCommand(configureHostRequest).response; - // HTML file still not included in the project as it is closed + // HTML file still not included in the project as it is closed checkNumberOfProjects(projectService, { configuredProjects: 1 }); checkProjectActualFiles(projectService.configuredProjects[0], [file1.path]); @@ -3047,4 +3047,59 @@ namespace ts.projectSystem { service.checkNumberOfProjects({ externalProjects: 1 }); }); }); + + describe("maxNodeModuleJsDepth for inferred projects", () => { + it("should be set to 2 if the project has js root files", () => { + const file1: FileOrFolder = { + path: "/a/b/file1.js", + content: `var t = require("test"); t.` + }; + const moduleFile: FileOrFolder = { + path: "/a/b/node_modules/test/index.js", + content: `var v = 10; module.exports = v;` + }; + + const host = createServerHost([file1, moduleFile]); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + + let project = projectService.inferredProjects[0]; + let options = project.getCompilerOptions(); + assert.isTrue(options.maxNodeModuleJsDepth === 2); + + // Assert the option sticks + projectService.setCompilerOptionsForInferredProjects({ target: ScriptTarget.ES2016 }); + project = projectService.inferredProjects[0]; + options = project.getCompilerOptions(); + assert.isTrue(options.maxNodeModuleJsDepth === 2); + }); + + it("should return to normal state when all js root files are removed from project", () => { + const file1 = { + path: "/a/file1.ts", + content: "let x =1;" + }; + const file2 = { + path: "/a/file2.js", + content: "let x =1;" + }; + + const host = createServerHost([file1, file2, libFile]); + const projectService = createProjectService(host, { useSingleInferredProject: true }); + + projectService.openClientFile(file1.path); + checkNumberOfInferredProjects(projectService, 1); + let project = projectService.inferredProjects[0]; + assert.isUndefined(project.getCompilerOptions().maxNodeModuleJsDepth); + + projectService.openClientFile(file2.path); + project = projectService.inferredProjects[0]; + assert.isTrue(project.getCompilerOptions().maxNodeModuleJsDepth === 2); + + projectService.closeClientFile(file2.path); + project = projectService.inferredProjects[0]; + assert.isUndefined(project.getCompilerOptions().maxNodeModuleJsDepth); + }); + }); + } \ No newline at end of file diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index d99f12294af..d4956a387ee 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -944,7 +944,7 @@ namespace ts.projectSystem { content: "" }; const host = createServerHost([f, node]); - const cache = createMap({ "node": node.path }); + const cache = createMapFromTemplate({ "node": node.path }); const result = JsTyping.discoverTypings(host, [f.path], getDirectoryPath(f.path), /*safeListPath*/ undefined, cache, { enable: true }, ["fs", "bar"]); assert.deepEqual(result.cachedTypingPaths, [node.path]); assert.deepEqual(result.newTypingNames, ["bar"]); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 25d58142a06..edc4fab9a19 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -845,7 +845,7 @@ namespace ts.server { files: parsedCommandLine.fileNames, compilerOptions: parsedCommandLine.options, configHasFilesProperty: config["files"] !== undefined, - wildcardDirectories: createMap(parsedCommandLine.wildcardDirectories), + wildcardDirectories: createMapFromTemplate(parsedCommandLine.wildcardDirectories), typeAcquisition: parsedCommandLine.typeAcquisition, compileOnSave: parsedCommandLine.compileOnSave }; diff --git a/src/server/project.ts b/src/server/project.ts index 5132de3f8d3..6042fd9a62b 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -396,7 +396,9 @@ namespace ts.server { } removeFile(info: ScriptInfo, detachFromProject = true) { - this.removeRootFileIfNecessary(info); + if (this.isRoot(info)) { + this.removeRoot(info); + } this.lsHost.notifyFileRemoved(info); this.cachedUnresolvedImportsPerFile.remove(info.path); @@ -567,9 +569,6 @@ namespace ts.server { setCompilerOptions(compilerOptions: CompilerOptions) { if (compilerOptions) { - if (this.projectKind === ProjectKind.Inferred) { - compilerOptions.allowJs = true; - } compilerOptions.allowNonTsExtensions = true; if (changesAffectModuleResolution(this.compilerOptions, compilerOptions)) { // reset cached unresolved imports if changes in compiler options affected module resolution @@ -698,11 +697,9 @@ namespace ts.server { } // remove a root file from project - private removeRootFileIfNecessary(info: ScriptInfo): void { - if (this.isRoot(info)) { - remove(this.rootFiles, info); - this.rootFilesMap.remove(info.path); - } + protected removeRoot(info: ScriptInfo): void { + remove(this.rootFiles, info); + this.rootFilesMap.remove(info.path); } } @@ -717,6 +714,32 @@ namespace ts.server { } })(); + private _isJsInferredProject = false; + + toggleJsInferredProject(isJsInferredProject: boolean) { + if (isJsInferredProject !== this._isJsInferredProject) { + this._isJsInferredProject = isJsInferredProject; + this.setCompilerOptions(); + } + } + + setCompilerOptions(options?: CompilerOptions) { + // Avoid manipulating the given options directly + const newOptions = options ? clone(options) : this.getCompilerOptions(); + if (!newOptions) { + return; + } + + if (this._isJsInferredProject && typeof newOptions.maxNodeModuleJsDepth !== "number") { + newOptions.maxNodeModuleJsDepth = 2; + } + else if (!this._isJsInferredProject) { + newOptions.maxNodeModuleJsDepth = undefined; + } + newOptions.allowJs = true; + super.setCompilerOptions(newOptions); + } + // Used to keep track of what directories are watched for this project directoriesWatchedForTsconfig: string[] = []; @@ -731,6 +754,22 @@ namespace ts.server { /*compileOnSaveEnabled*/ false); } + addRoot(info: ScriptInfo) { + if (!this._isJsInferredProject && info.isJavaScript()) { + this.toggleJsInferredProject(/*isJsInferredProject*/ true); + } + super.addRoot(info); + } + + removeRoot(info: ScriptInfo) { + if (this._isJsInferredProject && info.isJavaScript()) { + if (filter(this.getRootScriptInfos(), info => info.isJavaScript()).length === 0) { + this.toggleJsInferredProject(/*isJsInferredProject*/ false); + } + } + super.removeRoot(info); + } + getProjectRootPath() { // Single inferred project does not have a project root. if (this.projectService.useSingleInferredProject) { diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 340510f6580..be3ef34ce3a 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -355,5 +355,9 @@ namespace ts.server { positionToLineOffset(position: number): ILineInfo { return this.textStorage.positionToLineOffset(position); } + + public isJavaScript() { + return this.scriptKind === ScriptKind.JS || this.scriptKind === ScriptKind.JSX; + } } } \ No newline at end of file diff --git a/src/server/session.ts b/src/server/session.ts index 2f36c327142..1fa3986f66f 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1390,7 +1390,7 @@ namespace ts.server { return { response, responseRequired: true }; } - private handlers = createMap<(request: protocol.Request) => { response?: any, responseRequired?: boolean }>({ + private handlers = createMapFromTemplate<(request: protocol.Request) => { response?: any, responseRequired?: boolean }>({ [CommandNames.OpenExternalProject]: (request: protocol.OpenExternalProjectRequest) => { this.projectService.openExternalProject(request.arguments, /*suppressRefreshOfInferredProjects*/ false); // TODO: report errors diff --git a/src/services/codefixes/unusedIdentifierFixes.ts b/src/services/codefixes/unusedIdentifierFixes.ts index 8e14bdcb73e..2784a09a504 100644 --- a/src/services/codefixes/unusedIdentifierFixes.ts +++ b/src/services/codefixes/unusedIdentifierFixes.ts @@ -146,7 +146,7 @@ namespace ts.codefix { function createCodeFix(newText: string, start: number, length: number): CodeAction[] { return [{ - description: getLocaleSpecificMessage(Diagnostics.Remove_unused_identifiers), + description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Remove_declaration_for_Colon_0), { 0: token.getText() }), changes: [{ fileName: sourceFile.fileName, textChanges: [{ newText, span: { start, length } }] diff --git a/tests/baselines/reference/objectRest.js b/tests/baselines/reference/objectRest.js index 48a1bf8daf8..59eb11542c0 100644 --- a/tests/baselines/reference/objectRest.js +++ b/tests/baselines/reference/objectRest.js @@ -29,8 +29,15 @@ class Removable { removed: string; remainder: string; } +interface I { + m(): void; + removed: string; + remainder: string; +} var removable = new Removable(); var { removed, ...removableRest } = removable; +var i: I = removable; +var { removed, ...removableRest2 } = i; let computed = 'b'; let computed2 = 'a'; @@ -74,6 +81,8 @@ class Removable { } var removable = new Removable(); var { removed } = removable, removableRest = __rest(removable, ["removed"]); +var i = removable; +var { removed } = i, removableRest2 = __rest(i, ["removed"]); let computed = 'b'; let computed2 = 'a'; var _g = computed, stillNotGreat = o[_g], _h = computed2, soSo = o[_h], o = __rest(o, [typeof _g === "symbol" ? _g : _g + "", typeof _h === "symbol" ? _h : _h + ""]); diff --git a/tests/baselines/reference/objectRest.symbols b/tests/baselines/reference/objectRest.symbols index 46309392135..2992220d8b2 100644 --- a/tests/baselines/reference/objectRest.symbols +++ b/tests/baselines/reference/objectRest.symbols @@ -1,42 +1,42 @@ === tests/cases/conformance/types/rest/objectRest.ts === var o = { a: 1, b: 'no' } ->o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 35, 51)) +>o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 42, 51)) >a : Symbol(a, Decl(objectRest.ts, 0, 9)) >b : Symbol(b, Decl(objectRest.ts, 0, 15)) var { ...clone } = o; >clone : Symbol(clone, Decl(objectRest.ts, 1, 5)) ->o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 35, 51)) +>o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 42, 51)) var { a, ...justB } = o; >a : Symbol(a, Decl(objectRest.ts, 2, 5), Decl(objectRest.ts, 3, 5)) >justB : Symbol(justB, Decl(objectRest.ts, 2, 8)) ->o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 35, 51)) +>o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 42, 51)) var { a, b: renamed, ...empty } = o; >a : Symbol(a, Decl(objectRest.ts, 2, 5), Decl(objectRest.ts, 3, 5)) >b : Symbol(b, Decl(objectRest.ts, 0, 15)) >renamed : Symbol(renamed, Decl(objectRest.ts, 3, 8), Decl(objectRest.ts, 4, 5), Decl(objectRest.ts, 5, 5), Decl(objectRest.ts, 9, 5)) >empty : Symbol(empty, Decl(objectRest.ts, 3, 20)) ->o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 35, 51)) +>o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 42, 51)) var { ['b']: renamed, ...justA } = o; >'b' : Symbol(renamed, Decl(objectRest.ts, 3, 8), Decl(objectRest.ts, 4, 5), Decl(objectRest.ts, 5, 5), Decl(objectRest.ts, 9, 5)) >renamed : Symbol(renamed, Decl(objectRest.ts, 3, 8), Decl(objectRest.ts, 4, 5), Decl(objectRest.ts, 5, 5), Decl(objectRest.ts, 9, 5)) >justA : Symbol(justA, Decl(objectRest.ts, 4, 21), Decl(objectRest.ts, 5, 19), Decl(objectRest.ts, 6, 31)) ->o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 35, 51)) +>o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 42, 51)) var { 'b': renamed, ...justA } = o; >renamed : Symbol(renamed, Decl(objectRest.ts, 3, 8), Decl(objectRest.ts, 4, 5), Decl(objectRest.ts, 5, 5), Decl(objectRest.ts, 9, 5)) >justA : Symbol(justA, Decl(objectRest.ts, 4, 21), Decl(objectRest.ts, 5, 19), Decl(objectRest.ts, 6, 31)) ->o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 35, 51)) +>o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 42, 51)) var { b: { '0': n, '1': oooo }, ...justA } = o; >b : Symbol(b, Decl(objectRest.ts, 0, 15)) >n : Symbol(n, Decl(objectRest.ts, 6, 10)) >oooo : Symbol(oooo, Decl(objectRest.ts, 6, 18)) >justA : Symbol(justA, Decl(objectRest.ts, 4, 21), Decl(objectRest.ts, 5, 19), Decl(objectRest.ts, 6, 31)) ->o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 35, 51)) +>o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 42, 51)) let o2 = { c: 'terrible idea?', d: 'yes' }; >o2 : Symbol(o2, Decl(objectRest.ts, 8, 3)) @@ -138,41 +138,63 @@ class Removable { remainder: string; >remainder : Symbol(Removable.remainder, Decl(objectRest.ts, 27, 20)) } +interface I { +>I : Symbol(I, Decl(objectRest.ts, 29, 1)) + + m(): void; +>m : Symbol(I.m, Decl(objectRest.ts, 30, 13)) + + removed: string; +>removed : Symbol(I.removed, Decl(objectRest.ts, 31, 14)) + + remainder: string; +>remainder : Symbol(I.remainder, Decl(objectRest.ts, 32, 20)) +} var removable = new Removable(); ->removable : Symbol(removable, Decl(objectRest.ts, 30, 3)) +>removable : Symbol(removable, Decl(objectRest.ts, 35, 3)) >Removable : Symbol(Removable, Decl(objectRest.ts, 18, 35)) var { removed, ...removableRest } = removable; ->removed : Symbol(removed, Decl(objectRest.ts, 31, 5)) ->removableRest : Symbol(removableRest, Decl(objectRest.ts, 31, 14)) ->removable : Symbol(removable, Decl(objectRest.ts, 30, 3)) +>removed : Symbol(removed, Decl(objectRest.ts, 36, 5), Decl(objectRest.ts, 38, 5)) +>removableRest : Symbol(removableRest, Decl(objectRest.ts, 36, 14)) +>removable : Symbol(removable, Decl(objectRest.ts, 35, 3)) + +var i: I = removable; +>i : Symbol(i, Decl(objectRest.ts, 37, 3)) +>I : Symbol(I, Decl(objectRest.ts, 29, 1)) +>removable : Symbol(removable, Decl(objectRest.ts, 35, 3)) + +var { removed, ...removableRest2 } = i; +>removed : Symbol(removed, Decl(objectRest.ts, 36, 5), Decl(objectRest.ts, 38, 5)) +>removableRest2 : Symbol(removableRest2, Decl(objectRest.ts, 38, 14)) +>i : Symbol(i, Decl(objectRest.ts, 37, 3)) let computed = 'b'; ->computed : Symbol(computed, Decl(objectRest.ts, 33, 3)) +>computed : Symbol(computed, Decl(objectRest.ts, 40, 3)) let computed2 = 'a'; ->computed2 : Symbol(computed2, Decl(objectRest.ts, 34, 3)) +>computed2 : Symbol(computed2, Decl(objectRest.ts, 41, 3)) var { [computed]: stillNotGreat, [computed2]: soSo, ...o } = o; ->computed : Symbol(computed, Decl(objectRest.ts, 33, 3)) ->stillNotGreat : Symbol(stillNotGreat, Decl(objectRest.ts, 35, 5)) ->computed2 : Symbol(computed2, Decl(objectRest.ts, 34, 3)) ->soSo : Symbol(soSo, Decl(objectRest.ts, 35, 32)) ->o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 35, 51)) ->o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 35, 51)) +>computed : Symbol(computed, Decl(objectRest.ts, 40, 3)) +>stillNotGreat : Symbol(stillNotGreat, Decl(objectRest.ts, 42, 5)) +>computed2 : Symbol(computed2, Decl(objectRest.ts, 41, 3)) +>soSo : Symbol(soSo, Decl(objectRest.ts, 42, 32)) +>o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 42, 51)) +>o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 42, 51)) ({ [computed]: stillNotGreat, [computed2]: soSo, ...o } = o); ->computed : Symbol(computed, Decl(objectRest.ts, 33, 3)) ->stillNotGreat : Symbol(stillNotGreat, Decl(objectRest.ts, 35, 5)) ->computed2 : Symbol(computed2, Decl(objectRest.ts, 34, 3)) ->soSo : Symbol(soSo, Decl(objectRest.ts, 35, 32)) ->o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 35, 51)) ->o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 35, 51)) +>computed : Symbol(computed, Decl(objectRest.ts, 40, 3)) +>stillNotGreat : Symbol(stillNotGreat, Decl(objectRest.ts, 42, 5)) +>computed2 : Symbol(computed2, Decl(objectRest.ts, 41, 3)) +>soSo : Symbol(soSo, Decl(objectRest.ts, 42, 32)) +>o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 42, 51)) +>o : Symbol(o, Decl(objectRest.ts, 0, 3), Decl(objectRest.ts, 42, 51)) var noContextualType = ({ aNumber = 12, ...notEmptyObject }) => aNumber + notEmptyObject['anythingGoes']; ->noContextualType : Symbol(noContextualType, Decl(objectRest.ts, 38, 3)) ->aNumber : Symbol(aNumber, Decl(objectRest.ts, 38, 25)) ->notEmptyObject : Symbol(notEmptyObject, Decl(objectRest.ts, 38, 39)) ->aNumber : Symbol(aNumber, Decl(objectRest.ts, 38, 25)) ->notEmptyObject : Symbol(notEmptyObject, Decl(objectRest.ts, 38, 39)) +>noContextualType : Symbol(noContextualType, Decl(objectRest.ts, 45, 3)) +>aNumber : Symbol(aNumber, Decl(objectRest.ts, 45, 25)) +>notEmptyObject : Symbol(notEmptyObject, Decl(objectRest.ts, 45, 39)) +>aNumber : Symbol(aNumber, Decl(objectRest.ts, 45, 25)) +>notEmptyObject : Symbol(notEmptyObject, Decl(objectRest.ts, 45, 39)) diff --git a/tests/baselines/reference/objectRest.types b/tests/baselines/reference/objectRest.types index 1dc05741713..5347f09f5a3 100644 --- a/tests/baselines/reference/objectRest.types +++ b/tests/baselines/reference/objectRest.types @@ -158,6 +158,18 @@ class Removable { remainder: string; >remainder : string } +interface I { +>I : I + + m(): void; +>m : () => void + + removed: string; +>removed : string + + remainder: string; +>remainder : string +} var removable = new Removable(); >removable : Removable >new Removable() : Removable @@ -168,6 +180,16 @@ var { removed, ...removableRest } = removable; >removableRest : { both: number; remainder: string; } >removable : Removable +var i: I = removable; +>i : I +>I : I +>removable : Removable + +var { removed, ...removableRest2 } = i; +>removed : string +>removableRest2 : { m(): void; remainder: string; } +>i : I + let computed = 'b'; >computed : string >'b' : "b" diff --git a/tests/cases/conformance/types/rest/objectRest.ts b/tests/cases/conformance/types/rest/objectRest.ts index 2fba5d0b6dc..77f0fca1ed7 100644 --- a/tests/cases/conformance/types/rest/objectRest.ts +++ b/tests/cases/conformance/types/rest/objectRest.ts @@ -29,8 +29,15 @@ class Removable { removed: string; remainder: string; } +interface I { + m(): void; + removed: string; + remainder: string; +} var removable = new Removable(); var { removed, ...removableRest } = removable; +var i: I = removable; +var { removed, ...removableRest2 } = i; let computed = 'b'; let computed2 = 'a';